mirror of
https://github.com/kataras/iris.git
synced 2025-01-23 02:31:04 +01:00
improvements on the x/jsonx/iso8601 time format
This commit is contained in:
parent
028ee70d4a
commit
dd4bc50f78
|
@ -1,18 +1,34 @@
|
|||
package jsonx
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var fixedEastUTCLocations = make(map[int]*time.Location)
|
||||
|
||||
func registerFixedEastUTCLocation(name string, secondsFromUTC int) {
|
||||
loc := time.FixedZone(name, secondsFromUTC)
|
||||
fixedEastUTCLocations[secondsFromUTC] = loc
|
||||
}
|
||||
|
||||
func init() {
|
||||
registerFixedEastUTCLocation("EEST", 3*60*60) // + 3 hours.
|
||||
}
|
||||
|
||||
const (
|
||||
// ISO8601Layout holds the time layout for the the javascript iso time.
|
||||
// Read more at: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString.
|
||||
ISO8601Layout = "2006-01-02T15:04:05"
|
||||
// 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.
|
||||
ISO8601ZUTCOffsetLayout = "2006-01-02T15:04:05.999999Z07:00"
|
||||
)
|
||||
|
||||
// ISO8601 describes a time compatible with javascript time format.
|
||||
|
@ -29,7 +45,28 @@ func ParseISO8601(s string) (ISO8601, error) {
|
|||
err error
|
||||
)
|
||||
|
||||
if s[len(s)-1] == 'Z' {
|
||||
if idx := strings.LastIndexFunc(s, startUTCOffsetIndexFunc); idx > 20 /* should have some distance, e.g. 26 */ {
|
||||
length := parseSignedOffset(s[idx:])
|
||||
|
||||
if idx+1 > idx+length || len(s) <= idx+length+1 {
|
||||
return ISO8601{}, fmt.Errorf("ISO8601: invalid timezone format: %s", s[idx:])
|
||||
}
|
||||
|
||||
offsetText := s[idx+1 : idx+length]
|
||||
offset, parseErr := strconv.Atoi(offsetText)
|
||||
if parseErr != nil {
|
||||
return ISO8601{}, err
|
||||
}
|
||||
|
||||
// E.g. offset of +0300 is returned as 10800 which is - (3 * 60 * 60).
|
||||
secondsEastUTC := offset * 60 * 60
|
||||
|
||||
if loc, ok := fixedEastUTCLocations[secondsEastUTC]; ok { // Specific (fixed) zone.
|
||||
tt, err = time.ParseInLocation(ISO8601ZUTCOffsetLayout, s, loc)
|
||||
} else { // Local or UTC.
|
||||
tt, err = time.Parse(ISO8601ZUTCOffsetLayout, s)
|
||||
}
|
||||
} else if s[len(s)-1] == 'Z' {
|
||||
tt, err = time.Parse(ISO8601ZLayout, s)
|
||||
} else {
|
||||
tt, err = time.Parse(ISO8601Layout, s)
|
||||
|
@ -39,7 +76,7 @@ func ParseISO8601(s string) (ISO8601, error) {
|
|||
return ISO8601{}, err
|
||||
}
|
||||
|
||||
return ISO8601(tt.UTC()), nil
|
||||
return ISO8601(tt), nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON parses the "b" into ISO8601 time.
|
||||
|
@ -90,6 +127,11 @@ func (t ISO8601) String() string {
|
|||
return tt.Format(ISO8601Layout)
|
||||
}
|
||||
|
||||
// Value returns the database value of time.Time.
|
||||
func (t ISO8601) Value() (driver.Value, error) {
|
||||
return time.Time(t), nil
|
||||
}
|
||||
|
||||
// Scan completes the sql driver.Scanner interface.
|
||||
func (t *ISO8601) Scan(src interface{}) error {
|
||||
switch v := src.(type) {
|
||||
|
@ -104,6 +146,8 @@ func (t *ISO8601) Scan(src interface{}) error {
|
|||
return err
|
||||
}
|
||||
*t = tt
|
||||
case []byte:
|
||||
return t.Scan(string(v))
|
||||
case nil:
|
||||
*t = ISO8601(time.Time{})
|
||||
default:
|
||||
|
@ -112,3 +156,54 @@ func (t *ISO8601) Scan(src interface{}) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
// parseSignedOffset parses a signed timezone offset (e.g. "+03" or "-04").
|
||||
// The function checks for a signed number in the range -23 through +23 excluding zero.
|
||||
// Returns length of the found offset string or 0 otherwise.
|
||||
//
|
||||
// Language internal function.
|
||||
func parseSignedOffset(value string) int {
|
||||
sign := value[0]
|
||||
if sign != '-' && sign != '+' {
|
||||
return 0
|
||||
}
|
||||
x, rem, err := leadingInt(value[1:])
|
||||
|
||||
// fail if nothing consumed by leadingInt
|
||||
if err != nil || value[1:] == rem {
|
||||
return 0
|
||||
}
|
||||
if x > 23 {
|
||||
return 0
|
||||
}
|
||||
return len(value) - len(rem)
|
||||
}
|
||||
|
||||
var errLeadingInt = errors.New("ISO8601: time: bad [0-9]*") // never printed.
|
||||
|
||||
// leadingInt consumes the leading [0-9]* from s.
|
||||
//
|
||||
// Language internal function.
|
||||
func leadingInt(s string) (x uint64, rem string, err error) {
|
||||
i := 0
|
||||
for ; i < len(s); i++ {
|
||||
c := s[i]
|
||||
if c < '0' || c > '9' {
|
||||
break
|
||||
}
|
||||
if x > 1<<63/10 {
|
||||
// overflow
|
||||
return 0, "", errLeadingInt
|
||||
}
|
||||
x = x*10 + uint64(c) - '0'
|
||||
if x > 1<<63 {
|
||||
// overflow
|
||||
return 0, "", errLeadingInt
|
||||
}
|
||||
}
|
||||
return x, s[i:], nil
|
||||
}
|
||||
|
||||
func startUTCOffsetIndexFunc(char rune) bool {
|
||||
return char == '+' || char == '-'
|
||||
}
|
||||
|
|
81
x/jsonx/iso8601_test.go
Normal file
81
x/jsonx/iso8601_test.go
Normal file
|
@ -0,0 +1,81 @@
|
|||
package jsonx
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestISO8601(t *testing.T) {
|
||||
data := `{"start": "2021-08-20T10:05:01", "end": "2021-12-01T17:05:06", "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.Fatal(err)
|
||||
}
|
||||
|
||||
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.UTC
|
||||
|
||||
if expected, got := time.Date(2021, time.August, 20, 10, 5, 1, 0, loc), v.Start.ToTime(); expected != got {
|
||||
t.Fatalf("expected 'start' to be: %v but got: %v", expected, got)
|
||||
}
|
||||
|
||||
if expected, got := time.Date(2021, time.December, 1, 17, 5, 6, 0, loc), v.End.ToTime(); expected != got {
|
||||
t.Fatalf("expected 'start' to be: %v but got: %v", expected, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestISO8601WithZoneUTCOffset(t *testing.T) {
|
||||
data := `{"start": "2022-08-10T03:21:00.000000+03:00", "end": "2022-08-10T09:49:00.000000+03: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("EEST", 10800)
|
||||
|
||||
if expected, got := time.Date(2022, time.August, 10, 3, 21, 0, 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(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)
|
||||
}
|
||||
|
||||
if expected, got := time.Date(2022, time.August, 10, 3, 21, 0, 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(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)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user