1
0
mirror of https://github.com/kataras/iris.git synced 2025-12-17 09:57:01 +00:00

improvements on the x/jsonx/iso8601 time format

This commit is contained in:
Gerasimos (Makis) Maropoulos
2022-08-12 00:05:44 +03:00
parent 028ee70d4a
commit dd4bc50f78
2 changed files with 178 additions and 2 deletions

View File

@@ -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 == '-'
}