From 6338b3246846357cb886b639a3bc8aba54619440 Mon Sep 17 00:00:00 2001 From: Dmitry Panov Date: Sat, 4 Sep 2021 11:26:40 +0100 Subject: [PATCH] Treat date-only formats as UTC and date-time as local timezone. Added support for additional datetime formats. Fixes #281, fixes #292. --- date.go | 95 +++++++++++++++++++++++++-------------- date_parser.go | 9 +++- date_test.go | 120 +++++++++++++++++++++---------------------------- 3 files changed, 120 insertions(+), 104 deletions(-) diff --git a/date.go b/date.go index 66ac80b5..aa8ef62b 100644 --- a/date.go +++ b/date.go @@ -24,51 +24,80 @@ type dateObject struct { msec int64 } +type dateLayoutDesc struct { + layout string + dateOnly bool +} + var ( - dateLayoutList = []string{ - "2006-01-02T15:04:05Z0700", - "2006-01-02T15:04:05", - "2006-01-02", - "2006-01-02 15:04:05", - time.RFC1123, - time.RFC1123Z, - dateTimeLayout, - time.UnixDate, - time.ANSIC, - time.RubyDate, - "Mon, 02 Jan 2006 15:04:05 GMT-0700 (MST)", - "Mon, 02 Jan 2006 15:04:05 -0700 (MST)", - - "2006", - "2006-01", - - "2006T15:04", - "2006-01T15:04", - "2006-01-02T15:04", - - "2006T15:04:05", - "2006-01T15:04:05", - - "2006T15:04Z0700", - "2006-01T15:04Z0700", - "2006-01-02T15:04Z0700", - - "2006T15:04:05Z0700", - "2006-01T15:04:05Z0700", + dateLayoutsNumeric = []dateLayoutDesc{ + {layout: "2006-01-02T15:04:05Z0700"}, + {layout: "2006-01-02T15:04:05"}, + {layout: "2006-01-02", dateOnly: true}, + {layout: "2006-01-02 15:04:05"}, + + {layout: "2006", dateOnly: true}, + {layout: "2006-01", dateOnly: true}, + + {layout: "2006T15:04"}, + {layout: "2006-01T15:04"}, + {layout: "2006-01-02T15:04"}, + + {layout: "2006T15:04:05"}, + {layout: "2006-01T15:04:05"}, + + {layout: "2006T15:04Z0700"}, + {layout: "2006-01T15:04Z0700"}, + {layout: "2006-01-02T15:04Z0700"}, + + {layout: "2006T15:04:05Z0700"}, + {layout: "2006-01T15:04:05Z0700"}, + } + + dateLayoutsAlpha = []dateLayoutDesc{ + {layout: time.RFC1123}, + {layout: time.RFC1123Z}, + {layout: dateTimeLayout}, + {layout: time.UnixDate}, + {layout: time.ANSIC}, + {layout: time.RubyDate}, + {layout: "Mon, _2 Jan 2006 15:04:05 GMT-0700 (MST)"}, + {layout: "Mon, _2 Jan 2006 15:04:05 -0700 (MST)"}, + {layout: "Jan _2, 2006", dateOnly: true}, } ) func dateParse(date string) (time.Time, bool) { var t time.Time var err error - for _, layout := range dateLayoutList { - t, err = parseDate(layout, date, time.UTC) + var layouts []dateLayoutDesc + if len(date) > 0 { + first := date[0] + if first <= '9' && (first >= '0' || first == '-' || first == '+') { + layouts = dateLayoutsNumeric + } else { + layouts = dateLayoutsAlpha + } + } else { + return time.Time{}, false + } + for _, desc := range layouts { + var defLoc *time.Location + if desc.dateOnly { + defLoc = time.UTC + } else { + defLoc = time.Local + } + t, err = parseDate(desc.layout, date, defLoc) if err == nil { break } } + if err != nil { + return time.Time{}, false + } unix := timeToMsec(t) - return t, err == nil && unix >= -maxTime && unix <= maxTime + return t, unix >= -maxTime && unix <= maxTime } func (r *Runtime) newDateObject(t time.Time, isSet bool, proto *Object) *Object { diff --git a/date_parser.go b/date_parser.go index 0841cf40..f8360532 100644 --- a/date_parser.go +++ b/date_parser.go @@ -5,6 +5,8 @@ package goja // - 6-digit extended years are supported in place of long year (2006) in the form of +123456 // - Timezone formats tolerate colons, e.g. -0700 will parse -07:00 // - Short week day will also parse long week day +// - Short month ("Jan") will also parse long month ("January") +// - Long day ("02") will also parse short day ("2"). // - Timezone in brackets, "(MST)", will match any string in brackets (e.g. "(GMT Standard Time)") // - If offset is not set and timezone name is unknown, an error is returned // - If offset and timezone name are both set the offset takes precedence and the resulting Location will be FixedZone("", offset) @@ -133,7 +135,10 @@ func parseDate(layout, value string, defaultLocation *time.Location) (time.Time, } case stdMonth: - month, value, err = lookup(shortMonthNames, value) + month, value, err = lookup(longMonthNames, value) + if err != nil { + month, value, err = lookup(shortMonthNames, value) + } month++ case stdLongMonth: month, value, err = lookup(longMonthNames, value) @@ -155,7 +160,7 @@ func parseDate(layout, value string, defaultLocation *time.Location) (time.Time, if std == stdUnderDay && len(value) > 0 && value[0] == ' ' { value = value[1:] } - day, value, err = getnum(value, std == stdZeroDay) + day, value, err = getnum(value, false) if day < 0 { // Note that we allow any one- or two-digit day here. rangeErrString = "day" diff --git a/date_test.go b/date_test.go index 8905ece8..6a45c98d 100644 --- a/date_test.go +++ b/date_test.go @@ -304,91 +304,73 @@ func TestDateSetters(t *testing.T) { func TestDateParse(t *testing.T) { const SCRIPT = ` -var zero = new Date(0); + var zero = new Date(0); -assert.sameValue(zero.valueOf(), Date.parse(zero.toString()), - "Date.parse(zeroDate.toString())"); -assert.sameValue(zero.valueOf(), Date.parse(zero.toUTCString()), - "Date.parse(zeroDate.toUTCString())"); -assert.sameValue(zero.valueOf(), Date.parse(zero.toISOString()), - "Date.parse(zeroDate.toISOString())"); + assert.sameValue(zero.valueOf(), Date.parse(zero.toString()), + "Date.parse(zeroDate.toString())"); + assert.sameValue(zero.valueOf(), Date.parse(zero.toUTCString()), + "Date.parse(zeroDate.toUTCString())"); + assert.sameValue(zero.valueOf(), Date.parse(zero.toISOString()), + "Date.parse(zeroDate.toISOString())"); -assert.sameValue(Date.parse("Mon, 02 Jan 2006 15:04:05 MST"), 1136239445000, - "Date.parse(\"Mon, 02 Jan 2006 15:04:05 MST\")"); - -assert.sameValue(Date.parse("Mon, 02 Jan 2006 15:04:05 GMT-07:00 (MST)"), 1136239445000, - "Date.parse(\"Mon, 02 Jan 2006 15:04:05 GMT-07:00 (MST)\")"); - -assert.sameValue(Date.parse("Mon, 02 Jan 2006 15:04:05 -07:00 (MST)"), 1136239445000, - "Date.parse(\"Mon, 02 Jan 2006 15:04:05 -07:00 (MST)\")"); - -assert.sameValue(Date.parse("Monday, 02 Jan 2006 15:04:05 -0700 (MST)"), 1136239445000, - "Date.parse(\"Monday, 02 Jan 2006 15:04:05 -0700 (MST)\")"); - -assert.sameValue(Date.parse("Mon Jan 02 2006 15:04:05 GMT-0700 (GMT Standard Time)"), 1136239445000, - "Date.parse(\"Mon Jan 02 2006 15:04:05 GMT-0700 (GMT Standard Time)\")"); - -assert.sameValue(Date.parse("2006-01-02T15:04:05.000Z"), 1136214245000, - "Date.parse(\"2006-01-02T15:04:05.000Z\")"); - -assert.sameValue(Date.parse("2006-06-02T15:04:05.000"), 1149260645000, - "Date.parse(\"2006-01-02T15:04:05.000\")"); - -assert.sameValue(Date.parse("2006-01-02T15:04:05"), 1136214245000, - "Date.parse(\"2006-01-02T15:04:05\")"); - -assert.sameValue(Date.parse("2006-01-02"), 1136160000000, - "Date.parse(\"2006-01-02\")"); - -assert.sameValue(Date.parse("2006T15:04-0700"), 1136153040000, - "Date.parse(\"2006T15:04-0700\")"); - -assert.sameValue(Date.parse("2006T15:04Z"), 1136127840000, - "Date.parse(\"2006T15:04Z\")"); - -assert.sameValue(Date.parse("Mon Jan 2 15:04:05 MST 2006"), 1136239445000, - "Date.parse(\"Mon Jan 2 15:04:05 MST 2006\")"); + function testParse(str, expected) { + assert.sameValue(Date.parse(str), expected, str); + } -assert.sameValue(Date.parse("Mon Jan 02 15:04:05 MST 2006"), 1136239445000, - "Date.parse(\"Mon Jan 02 15:04:05 MST 2006\")"); + testParse("Mon, 02 Jan 2006 15:04:05 MST", 1136239445000); + testParse("Tue, 22 Jun 2021 13:54:40 GMT", 1624370080000); + testParse("Tuesday, 22 Jun 2021 13:54:40 GMT", 1624370080000); + testParse("Mon, 02 Jan 2006 15:04:05 GMT-07:00 (MST)", 1136239445000); + testParse("Mon, 02 Jan 2006 15:04:05 -07:00 (MST)", 1136239445000); + testParse("Monday, 02 Jan 2006 15:04:05 -0700 (MST)", 1136239445000); + testParse("Mon Jan 02 2006 15:04:05 GMT-0700 (GMT Standard Time)", 1136239445000); + testParse("Mon Jan 2 15:04:05 MST 2006", 1136239445000); + testParse("Mon Jan 02 15:04:05 MST 2006", 1136239445000); + testParse("Mon Jan 02 15:04:05 -0700 2006", 1136239445000); -assert.sameValue(Date.parse("Mon Jan 02 15:04:05 -0700 2006"), 1136239445000, - "Date.parse(\"Mon Jan 02 15:04:05 -0700 2006\")"); + testParse("December 04, 1986", 534038400000); + testParse("Dec 04, 1986", 534038400000); + testParse("Dec 4, 1986", 534038400000); -assert.sameValue(Date.parse("2019-01-01T12:00:00.52Z"), 1546344000520, - "Date.parse(\"2019-01-01T12:00:00.52\")"); + testParse("2006-01-02T15:04:05.000Z", 1136214245000); + testParse("2006-06-02T15:04:05.000", 1149275045000); + testParse("2006-01-02T15:04:05", 1136232245000); + testParse("2006-01-02", 1136160000000); + testParse("2006T15:04-0700", 1136153040000); + testParse("2006T15:04Z", 1136127840000); + testParse("2019-01-01T12:00:00.52Z", 1546344000520); -var d = new Date("Mon, 02 Jan 2006 15:04:05 MST"); + var d = new Date("Mon, 02 Jan 2006 15:04:05 MST"); -assert.sameValue(d.getUTCHours(), 22, - "new Date(\"Mon, 02 Jan 2006 15:04:05 MST\").getUTCHours()"); + assert.sameValue(d.getUTCHours(), 22, + "new Date(\"Mon, 02 Jan 2006 15:04:05 MST\").getUTCHours()"); -assert.sameValue(d.getHours(), 17, - "new Date(\"Mon, 02 Jan 2006 15:04:05 MST\").getHours()"); + assert.sameValue(d.getHours(), 17, + "new Date(\"Mon, 02 Jan 2006 15:04:05 MST\").getHours()"); -assert.sameValue(Date.parse("Mon, 02 Jan 2006 15:04:05 zzz"), NaN, - "Date.parse(\"Mon, 02 Jan 2006 15:04:05 zzz\")"); + assert.sameValue(Date.parse("Mon, 02 Jan 2006 15:04:05 zzz"), NaN, + "Date.parse(\"Mon, 02 Jan 2006 15:04:05 zzz\")"); -assert.sameValue(Date.parse("Mon, 02 Jan 2006 15:04:05 ZZZ"), NaN, - "Date.parse(\"Mon, 02 Jan 2006 15:04:05 ZZZ\")"); + assert.sameValue(Date.parse("Mon, 02 Jan 2006 15:04:05 ZZZ"), NaN, + "Date.parse(\"Mon, 02 Jan 2006 15:04:05 ZZZ\")"); -var minDateStr = "-271821-04-20T00:00:00.000Z"; -var minDate = new Date(-8640000000000000); + var minDateStr = "-271821-04-20T00:00:00.000Z"; + var minDate = new Date(-8640000000000000); -assert.sameValue(minDate.toISOString(), minDateStr, "minDateStr"); -assert.sameValue(Date.parse(minDateStr), minDate.valueOf(), "parse minDateStr"); + assert.sameValue(minDate.toISOString(), minDateStr, "minDateStr"); + assert.sameValue(Date.parse(minDateStr), minDate.valueOf(), "parse minDateStr"); -var maxDateStr = "+275760-09-13T00:00:00.000Z"; -var maxDate = new Date(8640000000000000); + var maxDateStr = "+275760-09-13T00:00:00.000Z"; + var maxDate = new Date(8640000000000000); -assert.sameValue(maxDate.toISOString(), maxDateStr, "maxDateStr"); -assert.sameValue(Date.parse(maxDateStr), maxDate.valueOf(), "parse maxDateStr"); + assert.sameValue(maxDate.toISOString(), maxDateStr, "maxDateStr"); + assert.sameValue(Date.parse(maxDateStr), maxDate.valueOf(), "parse maxDateStr"); -var belowRange = "-271821-04-19T23:59:59.999Z"; -var aboveRange = "+275760-09-13T00:00:00.001Z"; + var belowRange = "-271821-04-19T23:59:59.999Z"; + var aboveRange = "+275760-09-13T00:00:00.001Z"; -assert.sameValue(Date.parse(belowRange), NaN, "parse below minimum time value"); -assert.sameValue(Date.parse(aboveRange), NaN, "parse above maximum time value"); + assert.sameValue(Date.parse(belowRange), NaN, "parse below minimum time value"); + assert.sameValue(Date.parse(aboveRange), NaN, "parse above maximum time value"); ` l := time.Local