Skip to content

Commit

Permalink
Date.parse() now returns a number. Switched to own date parser for be…
Browse files Browse the repository at this point in the history
…tter compatibility (extended years, etc.). Fixes #79
  • Loading branch information
dop251 committed Nov 25, 2018
1 parent c7a914b commit 5e65f92
Show file tree
Hide file tree
Showing 5 changed files with 1,051 additions and 52 deletions.
53 changes: 34 additions & 19 deletions builtin_date.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package goja

import (
"fmt"
"math"
"time"
)
Expand All @@ -15,6 +16,10 @@ func timeFromMsec(msec int64) time.Time {
return time.Unix(sec, nsec)
}

func timeToMsec(t time.Time) int64 {
return t.Unix()*1000 + int64(t.Nanosecond())/1e6
}

func makeDate(args []Value, loc *time.Location) (t time.Time, valid bool) {
pick := func(index int, default_ int64) (int64, bool) {
if index >= len(args) {
Expand Down Expand Up @@ -111,19 +116,23 @@ func (r *Runtime) builtin_date(call FunctionCall) Value {
}

func (r *Runtime) date_parse(call FunctionCall) Value {
return r.newDateObject(dateParse(call.Argument(0).String()))
t, set := dateParse(call.Argument(0).String())
if set {
return intToValue(timeToMsec(t))
}
return _NaN
}

func (r *Runtime) date_UTC(call FunctionCall) Value {
t, valid := makeDate(call.Arguments, time.UTC)
if !valid {
return _NaN
}
return intToValue(int64(t.UnixNano() / 1e6))
return intToValue(timeToMsec(t))
}

func (r *Runtime) date_now(call FunctionCall) Value {
return intToValue(time.Now().UnixNano() / 1e6)
return intToValue(timeToMsec(time.Now()))
}

func (r *Runtime) dateproto_toString(call FunctionCall) Value {
Expand Down Expand Up @@ -156,7 +165,13 @@ func (r *Runtime) dateproto_toISOString(call FunctionCall) Value {
obj := r.toObject(call.This)
if d, ok := obj.self.(*dateObject); ok {
if d.isSet {
return asciiString(d.time.In(time.UTC).Format(isoDateTimeLayout))
utc := d.time.In(time.UTC)
year := utc.Year()
if year >= -9999 && year <= 9999 {
return asciiString(utc.Format(isoDateTimeLayout))
}
// extended year
return asciiString(fmt.Sprintf("%+06d-", year) + utc.Format(isoDateTimeLayout[5:]))
} else {
panic(r.newError(r.global.RangeError, "Invalid time value"))
}
Expand Down Expand Up @@ -270,7 +285,7 @@ func (r *Runtime) dateproto_getTime(call FunctionCall) Value {
obj := r.toObject(call.This)
if d, ok := obj.self.(*dateObject); ok {
if d.isSet {
return intToValue(d.time.UnixNano() / 1e6)
return intToValue(timeToMsec(d.time))
} else {
return _NaN
}
Expand Down Expand Up @@ -518,7 +533,7 @@ func (r *Runtime) dateproto_setMilliseconds(call FunctionCall) Value {
if d.isSet {
msec := int(call.Argument(0).ToInteger())
d.time = time.Date(d.time.Year(), d.time.Month(), d.time.Day(), d.time.Hour(), d.time.Minute(), d.time.Second(), msec*1e6, time.Local)
return intToValue(d.time.UnixNano() / 1e6)
return intToValue(timeToMsec(d.time))
} else {
return _NaN
}
Expand All @@ -534,7 +549,7 @@ func (r *Runtime) dateproto_setUTCMilliseconds(call FunctionCall) Value {
msec := int(call.Argument(0).ToInteger())
t := d.time.In(time.UTC)
d.time = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), msec*1e6, time.UTC).In(time.Local)
return intToValue(d.time.UnixNano() / 1e6)
return intToValue(timeToMsec(d.time))
} else {
return _NaN
}
Expand All @@ -555,7 +570,7 @@ func (r *Runtime) dateproto_setSeconds(call FunctionCall) Value {
nsec = d.time.Nanosecond()
}
d.time = time.Date(d.time.Year(), d.time.Month(), d.time.Day(), d.time.Hour(), d.time.Minute(), sec, nsec, time.Local)
return intToValue(d.time.UnixNano() / 1e6)
return intToValue(timeToMsec(d.time))
} else {
return _NaN
}
Expand All @@ -577,7 +592,7 @@ func (r *Runtime) dateproto_setUTCSeconds(call FunctionCall) Value {
nsec = t.Nanosecond()
}
d.time = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), sec, nsec, time.UTC).In(time.Local)
return intToValue(d.time.UnixNano() / 1e6)
return intToValue(timeToMsec(d.time))
} else {
return _NaN
}
Expand All @@ -603,7 +618,7 @@ func (r *Runtime) dateproto_setMinutes(call FunctionCall) Value {
nsec = d.time.Nanosecond()
}
d.time = time.Date(d.time.Year(), d.time.Month(), d.time.Day(), d.time.Hour(), min, sec, nsec, time.Local)
return intToValue(d.time.UnixNano() / 1e6)
return intToValue(timeToMsec(d.time))
} else {
return _NaN
}
Expand All @@ -630,7 +645,7 @@ func (r *Runtime) dateproto_setUTCMinutes(call FunctionCall) Value {
nsec = t.Nanosecond()
}
d.time = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), min, sec, nsec, time.UTC).In(time.Local)
return intToValue(d.time.UnixNano() / 1e6)
return intToValue(timeToMsec(d.time))
} else {
return _NaN
}
Expand Down Expand Up @@ -661,7 +676,7 @@ func (r *Runtime) dateproto_setHours(call FunctionCall) Value {
nsec = d.time.Nanosecond()
}
d.time = time.Date(d.time.Year(), d.time.Month(), d.time.Day(), hour, min, sec, nsec, time.Local)
return intToValue(d.time.UnixNano() / 1e6)
return intToValue(timeToMsec(d.time))
} else {
return _NaN
}
Expand Down Expand Up @@ -693,7 +708,7 @@ func (r *Runtime) dateproto_setUTCHours(call FunctionCall) Value {
nsec = t.Nanosecond()
}
d.time = time.Date(d.time.Year(), d.time.Month(), d.time.Day(), hour, min, sec, nsec, time.UTC).In(time.Local)
return intToValue(d.time.UnixNano() / 1e6)
return intToValue(timeToMsec(d.time))
} else {
return _NaN
}
Expand All @@ -707,7 +722,7 @@ func (r *Runtime) dateproto_setDate(call FunctionCall) Value {
if d, ok := obj.self.(*dateObject); ok {
if d.isSet {
d.time = time.Date(d.time.Year(), d.time.Month(), int(call.Argument(0).ToInteger()), d.time.Hour(), d.time.Minute(), d.time.Second(), d.time.Nanosecond(), time.Local)
return intToValue(d.time.UnixNano() / 1e6)
return intToValue(timeToMsec(d.time))
} else {
return _NaN
}
Expand All @@ -722,7 +737,7 @@ func (r *Runtime) dateproto_setUTCDate(call FunctionCall) Value {
if d.isSet {
t := d.time.In(time.UTC)
d.time = time.Date(t.Year(), t.Month(), int(call.Argument(0).ToInteger()), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), time.UTC).In(time.Local)
return intToValue(d.time.UnixNano() / 1e6)
return intToValue(timeToMsec(d.time))
} else {
return _NaN
}
Expand All @@ -743,7 +758,7 @@ func (r *Runtime) dateproto_setMonth(call FunctionCall) Value {
day = d.time.Day()
}
d.time = time.Date(d.time.Year(), month, day, d.time.Hour(), d.time.Minute(), d.time.Second(), d.time.Nanosecond(), time.Local)
return intToValue(d.time.UnixNano() / 1e6)
return intToValue(timeToMsec(d.time))
} else {
return _NaN
}
Expand All @@ -765,7 +780,7 @@ func (r *Runtime) dateproto_setUTCMonth(call FunctionCall) Value {
day = t.Day()
}
d.time = time.Date(t.Year(), month, day, t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), time.UTC).In(time.Local)
return intToValue(d.time.UnixNano() / 1e6)
return intToValue(timeToMsec(d.time))
} else {
return _NaN
}
Expand Down Expand Up @@ -794,7 +809,7 @@ func (r *Runtime) dateproto_setFullYear(call FunctionCall) Value {
day = d.time.Day()
}
d.time = time.Date(year, month, day, d.time.Hour(), d.time.Minute(), d.time.Second(), d.time.Nanosecond(), time.Local)
return intToValue(d.time.UnixNano() / 1e6)
return intToValue(timeToMsec(d.time))
}
r.typeErrorResult(true, "Method Date.prototype.setFullYear is called on incompatible receiver")
panic("Unreachable")
Expand All @@ -821,7 +836,7 @@ func (r *Runtime) dateproto_setUTCFullYear(call FunctionCall) Value {
day = t.Day()
}
d.time = time.Date(year, month, day, t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), time.UTC).In(time.Local)
return intToValue(d.time.UnixNano() / 1e6)
return intToValue(timeToMsec(d.time))
}
r.typeErrorResult(true, "Method Date.prototype.setUTCFullYear is called on incompatible receiver")
panic("Unreachable")
Expand Down
60 changes: 27 additions & 33 deletions date.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package goja

import (
"regexp"
"time"
)

Expand All @@ -23,61 +22,56 @@ type dateObject struct {

var (
dateLayoutList = []string{
"2006-01-02T15:04:05.000Z0700",
"2006-01-02T15:04:05.000",
"2006-01-02T15:04:05Z0700",
"2006-01-02T15:04:05",
"2006-01-02",
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",
"2006-01-02",

"2006T15:04",
"2006-01T15:04",
"2006-01-02T15:04",

"2006T15:04:05",
"2006-01T15:04:05",
"2006-01-02T15:04:05",

"2006T15:04:05.000",
"2006-01T15:04:05.000",
"2006-01-02T15:04:05.000",

"2006T15:04-0700",
"2006-01T15:04-0700",
"2006-01-02T15:04-0700",

"2006T15:04:05-0700",
"2006-01T15:04:05-0700",
"2006-01-02T15:04:05-0700",
"2006T15:04Z0700",
"2006-01T15:04Z0700",
"2006-01-02T15:04Z0700",

"2006T15:04:05.000-0700",
"2006-01T15:04:05.000-0700",
"2006-01-02T15:04:05.000-0700",
"2006T15:04:05Z0700",
"2006-01T15:04:05Z0700",

time.RFC1123,
dateTimeLayout,
"2006T15:04:05.000Z0700",
"2006-01T15:04:05.000Z0700",
}
matchDateTimeZone = regexp.MustCompile(`^(.*)(?:(Z)|([\+\-]\d{2}):(\d{2}))$`)
)

func dateParse(date string) (time.Time, bool) {
// YYYY-MM-DDTHH:mm:ss.sssZ
var t time.Time
var err error
{
date := date
if match := matchDateTimeZone.FindStringSubmatch(date); match != nil {
if match[2] == "Z" {
date = match[1] + "+0000"
} else {
date = match[1] + match[3] + match[4]
}
}
for _, layout := range dateLayoutList {
t, err = time.Parse(layout, date)
if err == nil {
break
}
for _, layout := range dateLayoutList {
t, err = parseDate(layout, date, time.UTC)
if err == nil {
break
}
}
return t, err == nil
unix := timeToMsec(t)
return t, err == nil && unix >= -8640000000000000 && unix <= 8640000000000000
}

func (r *Runtime) newDateObject(t time.Time, isSet bool) *Object {
Expand Down
Loading

0 comments on commit 5e65f92

Please sign in to comment.