Files
anonymous_github/backend/internal/server/javascript_legacy_date.go
T

318 lines
7.6 KiB
Go

// Copyright 2011 the V8 project authors. All rights reserved.
// Adapted date parsing rules; BSD-3-Clause terms in ../../licenses/V8-LICENSE.
package server
// The parsing rules follow V8's dateparser (BSD-3-Clause); see
// https://github.com/v8/v8/tree/main/src/date. Runtime parsing remains in Go.
import (
"math"
"strconv"
"strings"
"time"
"unicode"
)
type dateToken struct {
kind byte
text string
n int
}
func dateSpace(r rune) bool { return unicode.IsSpace(r) && r != '\u0085' || r == '\ufeff' }
func dateTokens(value string) []dateToken {
runes := []rune(value)
out := []dateToken{}
for i := 0; i < len(runes) && runes[i] != 0; {
start := i
r := runes[i]
i++
switch {
case r >= '0' && r <= '9':
for i < len(runes) && runes[i] >= '0' && runes[i] <= '9' {
i++
}
text := string(runes[start:i])
significant := strings.TrimLeft(text, "0")
if len(significant) > 9 {
significant = significant[:9]
}
n, _ := strconv.Atoi(significant)
out = append(out, dateToken{'n', text, n})
case strings.ContainsRune(":-+.)", r):
out = append(out, dateToken{byte(r), string(r), 0})
case dateSpace(r):
out = append(out, dateToken{'s', "", 0})
case r >= 'A':
for i < len(runes) && runes[i] >= 'A' && !dateSpace(runes[i]) {
i++
}
out = append(out, dateToken{'w', strings.ToLower(string(runes[start:i])), 0})
case r == '(':
depth := 1
for i < len(runes) && runes[i] != 0 && depth > 0 {
if runes[i] == '(' {
depth++
}
if runes[i] == ')' {
depth--
}
i++
}
out = append(out, dateToken{'?', "", 0})
default:
out = append(out, dateToken{'?', "", 0})
}
}
return append(out, dateToken{})
}
func javascriptLegacyDate(value string) (time.Time, bool) {
tokens := dateTokens(value)
pos := 0
peek := func() dateToken { return tokens[pos] }
next := func() dateToken {
t := tokens[pos]
if t.kind != 0 {
pos++
}
return t
}
skip := func(kind byte) bool {
if tokens[pos].kind == kind {
pos++
return true
}
return false
}
dates := []int{}
// Retain the consumed ISO prefix when legacy parsing takes over. Once an
// ISO time separator is reached, invalid time syntax cannot fall back.
token := dateToken{}
if peek().kind == '+' || peek().kind == '-' {
sign := next()
token = sign
if peek().kind == 'n' && len(peek().text) == 6 {
year := next().n
if sign.kind != '-' || year != 0 {
if sign.kind == '-' {
year = -year
}
dates = append(dates, year)
token = dateToken{kind: 'i'}
}
}
} else if peek().kind == 'n' && len(peek().text) == 4 {
dates = append(dates, next().n)
token.kind = 'i'
} else {
token = next()
}
if token.kind == 'i' {
validPrefix := true
if skip('-') {
t := next()
if t.kind != 'n' || len(t.text) != 2 || t.n < 1 || t.n > 12 {
token = t
validPrefix = false
} else {
dates = append(dates, t.n)
if skip('-') {
t = next()
if t.kind != 'n' || len(t.text) != 2 || t.n < 1 || t.n > 31 {
token = t
validPrefix = false
} else {
dates = append(dates, t.n)
}
}
}
}
if validPrefix {
if peek().kind == 'w' && peek().text == "t" {
return time.Time{}, false
}
token = next()
}
}
clock := []int{}
month := 0
ampm := -1
tzHour, tzMinute, tzSign := -1, -1, 1
readNumber := len(dates) > 0
timeExpect := func(n int) bool { return (len(clock) == 1 || len(clock) == 2) && n < 60 || len(clock) == 3 && n < 1000 }
addTime := func(n int) bool {
if len(clock) >= 4 {
return false
}
clock = append(clock, n)
return true
}
finishTime := func(n int) bool {
if !addTime(n) {
return false
}
for len(clock) < 4 {
clock = append(clock, 0)
}
return true
}
months := []string{"jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"}
zones := map[string]int{"ut": 0, "utc": 0, "z": 0, "gmt": 0, "cdt": -5, "cst": -6, "edt": -4, "est": -5, "mdt": -6, "mst": -7, "pdt": -7, "pst": -8}
for ; token.kind != 0; token = next() {
switch token.kind {
case 'n':
readNumber = true
n := token.n
if skip(':') {
if skip(':') {
if len(clock) != 0 {
return time.Time{}, false
}
clock = append(clock, n, 0)
} else {
if !addTime(n) {
return time.Time{}, false
}
skip('.')
}
} else if skip('.') && timeExpect(n) {
if !addTime(n) || peek().kind != 'n' {
return time.Time{}, false
}
fraction := next().text + "000"
ms, _ := strconv.Atoi(fraction[:3])
if !finishTime(ms) {
return time.Time{}, false
}
} else if tzHour >= 0 && tzMinute < 0 && n < 60 {
tzMinute = n
} else if timeExpect(n) {
if !finishTime(n) {
return time.Time{}, false
}
t := peek()
if t.kind != 0 && t.kind != 's' && t.kind != '+' && t.kind != '-' && !(t.kind == 'w' && t.text == "z") {
return time.Time{}, false
}
} else {
if len(dates) >= 3 {
return time.Time{}, false
}
dates = append(dates, n)
skip('-')
}
case 'w':
named := 0
if len(token.text) >= 3 {
for i, name := range months {
if token.text[:3] == name {
named = i + 1
break
}
}
}
if (token.text == "am" || token.text == "pm") && len(clock) > 0 {
ampm = 0
if token.text == "pm" {
ampm = 12
}
} else if named > 0 {
month = named
skip('-')
} else if zone, ok := zones[token.text]; ok && readNumber {
tzHour = zone
tzSign = 1
if zone < 0 {
tzSign = -1
tzHour = -zone
}
tzMinute = 0
} else if readNumber || peek().kind == 'n' {
return time.Time{}, false
}
case '+', '-':
if tzHour == 0 && tzMinute == 0 || len(clock) > 0 {
tzSign = 1
if token.kind == '-' {
tzSign = -1
}
n, width := 0, 0
if peek().kind == 'n' {
t := next()
n, width = t.n, len(t.text)
}
readNumber = true
if peek().kind == ':' {
tzHour, tzMinute = n, -1
} else if width == 1 || width == 2 {
tzHour, tzMinute = n, 0
} else if width == 3 || width == 4 {
tzHour, tzMinute = n/100, n%100
} else {
return time.Time{}, false
}
} else if readNumber {
return time.Time{}, false
}
case ')':
if readNumber {
return time.Time{}, false
}
}
}
if len(dates) == 0 {
return time.Time{}, false
}
for len(dates) < 3 {
dates = append(dates, 1)
}
year, day := 0, 0
if month == 0 {
if dates[0] < 1 || dates[0] > 31 {
year, month, day = dates[0], dates[1], dates[2]
} else {
month, day, year = dates[0], dates[1], dates[2]
}
} else if dates[0] < 1 || dates[0] > 31 {
year, day = dates[0], dates[1]
} else {
day, year = dates[0], dates[1]
}
if year >= 0 && year <= 49 {
year += 2000
} else if year >= 50 && year <= 99 {
year += 1900
}
if year < -300000 || year > 300000 || month < 1 || month > 12 || day < 1 || day > 31 {
return time.Time{}, false
}
for len(clock) < 4 {
clock = append(clock, 0)
}
if ampm >= 0 {
if clock[0] > 12 {
return time.Time{}, false
}
clock[0] = clock[0]%12 + ampm
}
if clock[0] > 24 || clock[1] > 59 || clock[2] > 59 || clock[3] > 999 || clock[0] == 24 && (clock[1] != 0 || clock[2] != 0 || clock[3] != 0) {
return time.Time{}, false
}
offset := 0
if tzHour >= 0 {
if tzMinute < 0 {
tzMinute = 0
}
offset = (tzHour*60 + tzMinute) * 60 * tzSign
}
if math.Abs(float64(offset)) > math.MaxInt32 {
return time.Time{}, false
}
parsed := time.Date(year, time.Month(month), day, clock[0], clock[1], clock[2], clock[3]*int(time.Millisecond), time.UTC).Add(-time.Duration(offset) * time.Second)
if math.Abs(float64(parsed.UnixMilli())) > 8.64e15 {
return time.Time{}, false
}
return parsed, true
}