From 82b543fd980aaacab9f24c7705878df260d261f6 Mon Sep 17 00:00:00 2001 From: Mikael Ganehag Brorsson Date: Tue, 31 Jan 2023 10:51:04 +0100 Subject: [PATCH] Better support for TZ locations in the times module (#397) * Handle panics by deferring recover as an error * Check for type in recover handler * Added support for times.in_location * Added support for location (TZ) to times.date * Updated documentation --- docs/stdlib-times.md | 10 +++++-- stdlib/times.go | 68 ++++++++++++++++++++++++++++++++++++++++++-- stdlib/times_test.go | 6 ++++ 3 files changed, 79 insertions(+), 5 deletions(-) diff --git a/docs/stdlib-times.md b/docs/stdlib-times.md index ce4b997..ba58ab7 100644 --- a/docs/stdlib-times.md +++ b/docs/stdlib-times.md @@ -62,9 +62,10 @@ times := import("times") duration. - `month_string(month int) => string`: returns the English name of the month ("January", "February", ...). -- `date(year int, month int, day int, hour int, min int, sec int, nsec int) => time`: - returns the Time corresponding to "yyyy-mm-dd hh:mm:ss + nsec nanoseconds". - Current location is used. +- `date(year int, month int, day int, hour int, min int, sec int, nsec int, loc string) => time`: + returns the Time corresponding to "yyyy-mm-dd hh:mm:ss + nsec nanoseconds" in + the appropriate zone for that Time in the given (optional) location. + The Local time zone will be used if executed without specifying a location. - `now() => time`: returns the current local time. - `parse(format string, s string) => time`: parses a formatted string and returns the time value it represents. The layout defines the format by @@ -116,5 +117,8 @@ times := import("times") string "2006-01-02 15:04:05.999999999 -0700 MST". - `is_zero(t time) => bool`: reports whether t represents the zero time instant, January 1, year 1, 00:00:00 UTC. +- `in_location(t time, l string) => time`: returns a copy of t representing + the same time instant, but with the copy's location information set to l for + display purposes. - `to_local(t time) => time`: returns t with the location set to local time. - `to_utc(t time) => time`: returns t with the location set to UTC. diff --git a/stdlib/times.go b/stdlib/times.go index 0b6f7bd..dc9a1a5 100644 --- a/stdlib/times.go +++ b/stdlib/times.go @@ -180,6 +180,10 @@ var timesModule = map[string]tengo.Object{ Name: "to_utc", Value: timesToUTC, }, // to_utc(time) => time + "in_location": &tengo.UserFunction{ + Name: "in_location", + Value: timesInLocation, + }, // in_location(time, location) => time } func timesSleep(args ...tengo.Object) (ret tengo.Object, err error) { @@ -430,7 +434,7 @@ func timesDate(args ...tengo.Object) ( ret tengo.Object, err error, ) { - if len(args) != 7 { + if len(args) < 7 || len(args) > 8 { err = tengo.ErrWrongNumArguments return } @@ -499,9 +503,29 @@ func timesDate(args ...tengo.Object) ( return } + var loc *time.Location + if len(args) == 8 { + i8, ok := tengo.ToString(args[7]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "eighth", + Expected: "string(compatible)", + Found: args[7].TypeName(), + } + return + } + loc, err = time.LoadLocation(i8) + if err != nil { + ret = wrapError(err) + return + } + } else { + loc = time.Now().Location() + } + ret = &tengo.Time{ Value: time.Date(i1, - time.Month(i2), i3, i4, i5, i6, i7, time.Now().Location()), + time.Month(i2), i3, i4, i5, i6, i7, loc), } return @@ -1113,6 +1137,46 @@ func timesTimeLocation(args ...tengo.Object) ( return } +func timesInLocation(args ...tengo.Object) ( + ret tengo.Object, + err error, +) { + if len(args) != 2 { + err = tengo.ErrWrongNumArguments + return + } + + t1, ok := tengo.ToTime(args[0]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + s2, ok := tengo.ToString(args[1]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "second", + Expected: "string(compatible)", + Found: args[1].TypeName(), + } + return + } + + location, err := time.LoadLocation(s2) + if err != nil { + ret = wrapError(err) + return + } + + ret = &tengo.Time{Value: t1.In(location)} + + return +} + func timesTimeString(args ...tengo.Object) (ret tengo.Object, err error) { if len(args) != 1 { err = tengo.ErrWrongNumArguments diff --git a/stdlib/times_test.go b/stdlib/times_test.go index 6977ba4..afe34bd 100644 --- a/stdlib/times_test.go +++ b/stdlib/times_test.go @@ -11,6 +11,8 @@ import ( func TestTimes(t *testing.T) { time1 := time.Date(1982, 9, 28, 19, 21, 44, 999, time.Now().Location()) time2 := time.Now() + location, _ := time.LoadLocation("Pacific/Auckland") + time3 := time.Date(1982, 9, 28, 19, 21, 44, 999, location) module(t, "times").call("sleep", 1).expect(tengo.UndefinedValue) @@ -35,6 +37,9 @@ func TestTimes(t *testing.T) { module(t, "times").call("date", 1982, 9, 28, 19, 21, 44, 999). expect(time1) + module(t, "times").call("date", 1982, 9, 28, 19, 21, 44, 999, "Pacific/Auckland"). + expect(time3) + nowD := time.Until(module(t, "times").call("now"). o.(*tengo.Time).Value).Nanoseconds() require.True(t, 0 > nowD && nowD > -100000000) // within 100ms @@ -80,4 +85,5 @@ func TestTimes(t *testing.T) { module(t, "times").call("time_location", time1). expect(time1.Location().String()) module(t, "times").call("time_string", time1).expect(time1.String()) + module(t, "times").call("in_location", time1, location.String()).expect(time1.In(location)) }