package otto import ( "fmt" "math" "regexp" Time "time" ) type _dateObject struct { time Time.Time // Time from the "time" package, a cached version of time epoch int64 value Value isNaN bool } var ( invalidDateObject = _dateObject{ time: Time.Time{}, epoch: -1, value: NaNValue(), isNaN: true, } ) type _ecmaTime struct { year int month int day int hour int minute int second int millisecond int location *Time.Location // Basically, either local or UTC } func ecmaTime(goTime Time.Time) _ecmaTime { return _ecmaTime{ goTime.Year(), dateFromGoMonth(goTime.Month()), goTime.Day(), goTime.Hour(), goTime.Minute(), goTime.Second(), goTime.Nanosecond() / (100 * 100 * 100), goTime.Location(), } } func (self *_ecmaTime) goTime() Time.Time { return Time.Date( self.year, dateToGoMonth(self.month), self.day, self.hour, self.minute, self.second, self.millisecond*(100*100*100), self.location, ) } func (self *_dateObject) Time() Time.Time { return self.time } func (self *_dateObject) Epoch() int64 { return self.epoch } func (self *_dateObject) Value() Value { return self.value } // FIXME A date should only be in the range of -100,000,000 to +100,000,000 (1970): 15.9.1.1 func (self *_dateObject) SetNaN() { self.time = Time.Time{} self.epoch = -1 self.value = NaNValue() self.isNaN = true } func (self *_dateObject) SetTime(time Time.Time) { self.Set(timeToEpoch(time)) } func epoch2dateObject(epoch float64) _dateObject { date := _dateObject{} date.Set(epoch) return date } func (self *_dateObject) Set(epoch float64) { // epoch self.epoch = epochToInteger(epoch) // time time, err := epochToTime(epoch) self.time = time // Is either a valid time, or the zero-value for time.Time // value & isNaN if err != nil { self.isNaN = true self.epoch = -1 self.value = NaNValue() } else { self.value = toValue_int64(self.epoch) } } func epochToInteger(value float64) int64 { if value > 0 { return int64(math.Floor(value)) } return int64(math.Ceil(value)) } func epochToTime(value float64) (time Time.Time, err error) { epochWithMilli := value if math.IsNaN(epochWithMilli) || math.IsInf(epochWithMilli, 0) { err = fmt.Errorf("Invalid time %v", value) return } epoch := int64(epochWithMilli / 1000) milli := int64(epochWithMilli) % 1000 time = Time.Unix(int64(epoch), milli*1000000).UTC() return } func timeToEpoch(time Time.Time) float64 { return float64(time.UnixNano() / (1000 * 1000)) } func (runtime *_runtime) newDateObject(epoch float64) *_object { self := runtime.newObject() self.class = "Date" // FIXME This is ugly... date := _dateObject{} date.Set(epoch) self.value = date return self } func (self *_object) dateValue() _dateObject { value, _ := self.value.(_dateObject) return value } func dateObjectOf(rt *_runtime, _dateObject *_object) _dateObject { if _dateObject == nil || _dateObject.class != "Date" { panic(rt.panicTypeError()) } return _dateObject.dateValue() } // JavaScript is 0-based, Go is 1-based (15.9.1.4) func dateToGoMonth(month int) Time.Month { return Time.Month(month + 1) } func dateFromGoMonth(month Time.Month) int { return int(month) - 1 } // Both JavaScript & Go are 0-based (Sunday == 0) func dateToGoDay(day int) Time.Weekday { return Time.Weekday(day) } func dateFromGoDay(day Time.Weekday) int { return int(day) } func newDateTime(argumentList []Value, location *Time.Location) (epoch float64) { pick := func(index int, default_ float64) (float64, bool) { if index >= len(argumentList) { return default_, false } value := argumentList[index].float64() if math.IsNaN(value) || math.IsInf(value, 0) { return 0, true } return value, false } if len(argumentList) >= 2 { // 2-argument, 3-argument, ... var year, month, day, hour, minute, second, millisecond float64 var invalid bool if year, invalid = pick(0, 1900.0); invalid { goto INVALID } if month, invalid = pick(1, 0.0); invalid { goto INVALID } if day, invalid = pick(2, 1.0); invalid { goto INVALID } if hour, invalid = pick(3, 0.0); invalid { goto INVALID } if minute, invalid = pick(4, 0.0); invalid { goto INVALID } if second, invalid = pick(5, 0.0); invalid { goto INVALID } if millisecond, invalid = pick(6, 0.0); invalid { goto INVALID } if year >= 0 && year <= 99 { year += 1900 } time := Time.Date(int(year), dateToGoMonth(int(month)), int(day), int(hour), int(minute), int(second), int(millisecond)*1000*1000, location) return timeToEpoch(time) } else if len(argumentList) == 0 { // 0-argument time := Time.Now().UTC() return timeToEpoch(time) } else { // 1-argument value := valueOfArrayIndex(argumentList, 0) value = toPrimitive(value) if value.IsString() { return dateParse(value.string()) } return value.float64() } INVALID: epoch = math.NaN() return } var ( dateLayoutList = []string{ "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:04:05.000-0700", "2006-01T15:04:05.000-0700", "2006-01-02T15:04:05.000-0700", Time.RFC1123, } matchDateTimeZone = regexp.MustCompile(`^(.*)(?:(Z)|([\+\-]\d{2}):(\d{2}))$`) ) func dateParse(date string) (epoch float64) { // YYYY-MM-DDTHH:mm:ss.sssZ var time 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 { time, err = Time.Parse(layout, date) if err == nil { break } } } if err != nil { return math.NaN() } return float64(time.UnixNano()) / (1000 * 1000) // UnixMilli() }