From ffb3c04ed155ffe2d627aef55a87777b65eb6dcb Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Sun, 5 Feb 2023 13:28:10 +0200 Subject: [PATCH] minor: jsonx: time: iso8601 --- x/jsonx/iso8601.go | 22 +++++++++++++------- x/jsonx/iso8601_test.go | 46 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 59 insertions(+), 9 deletions(-) diff --git a/x/jsonx/iso8601.go b/x/jsonx/iso8601.go index cae75ebe..8474bbed 100644 --- a/x/jsonx/iso8601.go +++ b/x/jsonx/iso8601.go @@ -7,17 +7,25 @@ import ( "strconv" "strings" "time" + // To load all system and embedded locations by name: + // _ "time/tzdata" // OR build with: -tags timetzdata ) var fixedEastUTCLocations = make(map[int]*time.Location) -func registerFixedEastUTCLocation(name string, secondsFromUTC int) { +// RegisterFixedLocation should be called on initialization of the program. +// It registers a fixed location to the time parser. +// +// E.g. for input of 2023-02-04T09:48:14+03:00 to result a time string of 2023-02-04 09:48:14 +0300 EEST +// you have to RegisterFixedLocation("EEST", 10800) otherwise it will result to: 2023-02-04 09:48:14 +0300 +0300. +func RegisterFixedLocation(name string, secondsFromUTC int) { loc := time.FixedZone(name, secondsFromUTC) fixedEastUTCLocations[secondsFromUTC] = loc } func init() { - registerFixedEastUTCLocation("EEST", 3*60*60) // + 3 hours. + RegisterFixedLocation("EEST", 3*60*60) // + 3 hours. + RegisterFixedLocation("UTC", 0) } const ( @@ -27,8 +35,8 @@ const ( // ISO8601ZLayout same as ISO8601Layout but with the timezone suffix. ISO8601ZLayout = "2006-01-02T15:04:05Z" // ISO8601ZUTCOffsetLayout ISO 8601 format, with full time and zone with UTC offset. - // Example: 2022-08-10T03:21:00.000000+03:00, 2022-08-09T00:00:00.000000. - ISO8601ZUTCOffsetLayout = "2006-01-02T15:04:05.999999Z07:00" + // Example: 2022-08-10T03:21:00.000000+03:00, 2023-02-04T09:48:14+00:00, 2022-08-09T00:00:00.000000. + ISO8601ZUTCOffsetLayout = "2006-01-02T15:04:05.999999Z07:00" // -07:00 ) // ISO8601 describes a time compatible with javascript time format. @@ -45,7 +53,7 @@ func ParseISO8601(s string) (ISO8601, error) { err error ) - if idx := strings.LastIndexFunc(s, startUTCOffsetIndexFunc); idx > 20 /* should have some distance, e.g. 26 */ { + if idx := strings.LastIndexFunc(s, startUTCOffsetIndexFunc); idx > 18 /* should have some distance, with and without milliseconds */ { length := parseSignedOffset(s[idx:]) if idx+1 > idx+length || len(s) <= idx+length+1 { @@ -55,7 +63,7 @@ func ParseISO8601(s string) (ISO8601, error) { offsetText := s[idx+1 : idx+length] offset, parseErr := strconv.Atoi(offsetText) if parseErr != nil { - return ISO8601{}, err + return ISO8601{}, fmt.Errorf("ISO8601: %w", parseErr) } // E.g. offset of +0300 is returned as 10800 which is - (3 * 60 * 60). @@ -73,7 +81,7 @@ func ParseISO8601(s string) (ISO8601, error) { } if err != nil { - return ISO8601{}, err + return ISO8601{}, fmt.Errorf("ISO8601: %w", err) } return ISO8601(tt), nil diff --git a/x/jsonx/iso8601_test.go b/x/jsonx/iso8601_test.go index cba8defd..b77e4025 100644 --- a/x/jsonx/iso8601_test.go +++ b/x/jsonx/iso8601_test.go @@ -68,7 +68,7 @@ func TestISO8601WithZoneUTCOffset(t *testing.T) { } if expected, got := time.Date(2022, time.August, 10, 9, 49, 0, 0, loc).String(), v.End.ToTime().String(); expected != got { - t.Fatalf("expected 'start' string to be: %v but got: %v", expected, got) + t.Fatalf("expected 'end' string to be: %v but got: %v", expected, got) } if expected, got := time.Date(2022, time.August, 10, 3, 21, 0, 0, loc), v.Start.ToTime().In(loc); expected != got { @@ -76,6 +76,48 @@ func TestISO8601WithZoneUTCOffset(t *testing.T) { } if expected, got := time.Date(2022, time.August, 10, 9, 49, 0, 0, loc), v.End.ToTime().In(loc); expected != got { - t.Fatalf("expected 'start' to be: %v but got: %v", expected, got) + t.Fatalf("expected 'end' to be: %v but got: %v", expected, got) + } +} + +func TestISO8601WithZoneUTCOffsetWithoutMilliseconds(t *testing.T) { + data := `{"start": "2023-02-04T09:48:14+00:00", "end": "2023-02-05T00:03:16+00:00", "nothing": null, "empty": ""}` + v := struct { + Start ISO8601 `json:"start"` + End ISO8601 `json:"end"` + Nothing ISO8601 `json:"nothing"` + Empty ISO8601 `json:"empty"` + }{} + err := json.Unmarshal([]byte(data), &v) + if err != nil { + t.Fatalf("unmarshal: %v", err) + } + + // t.Logf("Start: %s, location: %s\n", v.Start.String(), v.Start.ToTime().Location().String()) + + if !v.Nothing.IsZero() { + t.Fatalf("expected 'nothing' to be zero but got: %v", v.Nothing) + } + + if !v.Empty.IsZero() { + t.Fatalf("expected 'empty' to be zero but got: %v", v.Empty) + } + + loc := time.FixedZone("UTC", 0) + + if expected, got := time.Date(2023, time.February, 04, 9, 48, 14, 0, loc).String(), v.Start.ToTime().String(); expected != got { + t.Fatalf("expected 'start' string to be: %v but got: %v", expected, got) + } + + if expected, got := time.Date(2023, time.February, 05, 0, 3, 16, 0, loc).String(), v.End.ToTime().String(); expected != got { + t.Fatalf("expected 'end' string to be: %v but got: %v", expected, got) + } + + if expected, got := time.Date(2023, time.February, 04, 9, 48, 14, 0, loc), v.Start.ToTime().In(loc); expected != got { + t.Fatalf("expected 'start' to be: %v but got: %v", expected, got) + } + + if expected, got := time.Date(2023, time.February, 05, 0, 3, 16, 0, loc), v.End.ToTime().In(loc); expected != got { + t.Fatalf("expected 'end' to be: %v but got: %v", expected, got) } }