123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426 |
- package goja
- import (
- "strings"
- )
- type date struct {
- year, month, day int
- hour, min, sec, msec int
- timeZoneOffset int // time zone offset in minutes
- isLocal bool
- }
- func skip(s string, c byte) (string, bool) {
- if len(s) > 0 && s[0] == c {
- return s[1:], true
- }
- return s, false
- }
- func skipSpaces(s string) string {
- for len(s) > 0 && s[0] == ' ' {
- s = s[1:]
- }
- return s
- }
- func skipUntil(s string, stopList string) string {
- for len(s) > 0 && !strings.ContainsRune(stopList, rune(s[0])) {
- s = s[1:]
- }
- return s
- }
- func match(s string, lower string) (string, bool) {
- if len(s) < len(lower) {
- return s, false
- }
- for i := 0; i < len(lower); i++ {
- c1 := s[i]
- c2 := lower[i]
- if c1 != c2 {
- // switch to lower-case; 'a'-'A' is known to be a single bit
- c1 |= 'a' - 'A'
- if c1 != c2 || c1 < 'a' || c1 > 'z' {
- return s, false
- }
- }
- }
- return s[len(lower):], true
- }
- func getDigits(s string, minDigits, maxDigits int) (int, string, bool) {
- var i, v int
- for i < len(s) && i < maxDigits && s[i] >= '0' && s[i] <= '9' {
- v = v*10 + int(s[i]-'0')
- i++
- }
- if i < minDigits {
- return 0, s, false
- }
- return v, s[i:], true
- }
- func getMilliseconds(s string) (int, string) {
- mul, v := 100, 0
- if len(s) > 0 && (s[0] == '.' || s[0] == ',') {
- const I_START = 1
- i := I_START
- for i < len(s) && i-I_START < 9 && s[i] >= '0' && s[i] <= '9' {
- v += int(s[i]-'0') * mul
- mul /= 10
- i++
- }
- if i > I_START {
- // only consume the separator if digits are present
- return v, s[i:]
- }
- }
- return 0, s
- }
- // [+-]HH:mm or [+-]HHmm or Z
- func getTimeZoneOffset(s string, strict bool) (int, string, bool) {
- if len(s) == 0 {
- return 0, s, false
- }
- sign := s[0]
- if sign == '+' || sign == '-' {
- var hh, mm, v int
- var ok bool
- t := s[1:]
- n := len(t)
- if hh, t, ok = getDigits(t, 1, 9); !ok {
- return 0, s, false
- }
- n -= len(t)
- if strict && n != 2 && n != 4 {
- return 0, s, false
- }
- for n > 4 {
- n -= 2
- hh /= 100
- }
- if n > 2 {
- mm = hh % 100
- hh = hh / 100
- } else if t, ok = skip(t, ':'); ok {
- if mm, t, ok = getDigits(t, 2, 2); !ok {
- return 0, s, false
- }
- }
- if hh > 23 || mm > 59 {
- return 0, s, false
- }
- v = hh*60 + mm
- if sign == '-' {
- v = -v
- }
- return v, t, true
- } else if sign == 'Z' {
- return 0, s[1:], true
- }
- return 0, s, false
- }
- var tzAbbrs = []struct {
- nameLower string
- offset int
- }{
- {"gmt", 0}, // Greenwich Mean Time
- {"utc", 0}, // Coordinated Universal Time
- {"ut", 0}, // Universal Time
- {"z", 0}, // Zulu Time
- {"edt", -4 * 60}, // Eastern Daylight Time
- {"est", -5 * 60}, // Eastern Standard Time
- {"cdt", -5 * 60}, // Central Daylight Time
- {"cst", -6 * 60}, // Central Standard Time
- {"mdt", -6 * 60}, // Mountain Daylight Time
- {"mst", -7 * 60}, // Mountain Standard Time
- {"pdt", -7 * 60}, // Pacific Daylight Time
- {"pst", -8 * 60}, // Pacific Standard Time
- {"wet", +0 * 60}, // Western European Time
- {"west", +1 * 60}, // Western European Summer Time
- {"cet", +1 * 60}, // Central European Time
- {"cest", +2 * 60}, // Central European Summer Time
- {"eet", +2 * 60}, // Eastern European Time
- {"eest", +3 * 60}, // Eastern European Summer Time
- }
- func getTimeZoneAbbr(s string) (int, string, bool) {
- for _, tzAbbr := range tzAbbrs {
- if s, ok := match(s, tzAbbr.nameLower); ok {
- return tzAbbr.offset, s, true
- }
- }
- return 0, s, false
- }
- var monthNamesLower = []string{
- "jan",
- "feb",
- "mar",
- "apr",
- "may",
- "jun",
- "jul",
- "aug",
- "sep",
- "oct",
- "nov",
- "dec",
- }
- func getMonth(s string) (int, string, bool) {
- for i, monthNameLower := range monthNamesLower {
- if s, ok := match(s, monthNameLower); ok {
- return i + 1, s, true
- }
- }
- return 0, s, false
- }
- func parseDateISOString(s string) (date, bool) {
- if len(s) == 0 {
- return date{}, false
- }
- var d = date{month: 1, day: 1}
- var ok bool
- // year is either yyyy digits or [+-]yyyyyy
- sign := s[0]
- if sign == '-' || sign == '+' {
- s = s[1:]
- if d.year, s, ok = getDigits(s, 6, 6); !ok {
- return date{}, false
- }
- if sign == '-' {
- if d.year == 0 {
- // reject -000000
- return date{}, false
- }
- d.year = -d.year
- }
- } else if d.year, s, ok = getDigits(s, 4, 4); !ok {
- return date{}, false
- }
- if s, ok = skip(s, '-'); ok {
- if d.month, s, ok = getDigits(s, 2, 2); !ok || d.month < 1 {
- return date{}, false
- }
- if s, ok = skip(s, '-'); ok {
- if d.day, s, ok = getDigits(s, 2, 2); !ok || d.day < 1 {
- return date{}, false
- }
- }
- }
- if s, ok = skip(s, 'T'); ok {
- if d.hour, s, ok = getDigits(s, 2, 2); !ok {
- return date{}, false
- }
- if s, ok = skip(s, ':'); !ok {
- return date{}, false
- }
- if d.min, s, ok = getDigits(s, 2, 2); !ok {
- return date{}, false
- }
- if s, ok = skip(s, ':'); ok {
- if d.sec, s, ok = getDigits(s, 2, 2); !ok {
- return date{}, false
- }
- d.msec, s = getMilliseconds(s)
- }
- d.isLocal = true
- }
- // parse the time zone offset if present
- if len(s) > 0 {
- if d.timeZoneOffset, s, ok = getTimeZoneOffset(s, true); !ok {
- return date{}, false
- }
- d.isLocal = false
- }
- // error if extraneous characters
- return d, len(s) == 0
- }
- func parseDateOtherString(s string) (date, bool) {
- var d = date{
- year: 2001,
- month: 1,
- day: 1,
- isLocal: true,
- }
- var nums [3]int
- var numIndex int
- var hasYear, hasMon, hasTime, ok bool
- for {
- s = skipSpaces(s)
- if len(s) == 0 {
- break
- }
- c := s[0]
- n, val := len(s), 0
- if c == '+' || c == '-' {
- if hasTime {
- if val, s, ok = getTimeZoneOffset(s, false); ok {
- d.timeZoneOffset = val
- d.isLocal = false
- }
- }
- if !hasTime || !ok {
- s = s[1:]
- if val, s, ok = getDigits(s, 1, 9); ok {
- d.year = val
- if c == '-' {
- if d.year == 0 {
- return date{}, false
- }
- d.year = -d.year
- }
- hasYear = true
- }
- }
- } else if val, s, ok = getDigits(s, 1, 9); ok {
- if s, ok = skip(s, ':'); ok {
- // time part
- d.hour = val
- if d.min, s, ok = getDigits(s, 1, 2); !ok {
- return date{}, false
- }
- if s, ok = skip(s, ':'); ok {
- if d.sec, s, ok = getDigits(s, 1, 2); !ok {
- return date{}, false
- }
- d.msec, s = getMilliseconds(s)
- }
- hasTime = true
- if t := skipSpaces(s); len(t) > 0 {
- if t, ok = match(t, "pm"); ok {
- if d.hour < 12 {
- d.hour += 12
- }
- s = t
- continue
- } else if t, ok = match(t, "am"); ok {
- if d.hour == 12 {
- d.hour = 0
- }
- s = t
- continue
- }
- }
- } else if n-len(s) > 2 {
- d.year = val
- hasYear = true
- } else if val < 1 || val > 31 {
- d.year = val
- if val < 100 {
- d.year += 1900
- }
- if val < 50 {
- d.year += 100
- }
- hasYear = true
- } else {
- if numIndex == 3 {
- return date{}, false
- }
- nums[numIndex] = val
- numIndex++
- }
- } else if val, s, ok = getMonth(s); ok {
- d.month = val
- hasMon = true
- s = skipUntil(s, "0123456789 -/(")
- } else if val, s, ok = getTimeZoneAbbr(s); ok {
- d.timeZoneOffset = val
- if len(s) > 0 {
- if c := s[0]; (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') {
- return date{}, false
- }
- }
- d.isLocal = false
- continue
- } else if c == '(' {
- // skip parenthesized phrase
- level := 1
- s = s[1:]
- for len(s) > 0 && level != 0 {
- if s[0] == '(' {
- level++
- } else if s[0] == ')' {
- level--
- }
- s = s[1:]
- }
- if level > 0 {
- return date{}, false
- }
- } else if c == ')' {
- return date{}, false
- } else {
- if hasYear || hasMon || hasTime || numIndex > 0 {
- return date{}, false
- }
- // skip a word
- s = skipUntil(s, " -/(")
- }
- for len(s) > 0 && strings.ContainsRune("-/.,", rune(s[0])) {
- s = s[1:]
- }
- }
- n := numIndex
- if hasYear {
- n++
- }
- if hasMon {
- n++
- }
- if n > 3 {
- return date{}, false
- }
- switch numIndex {
- case 0:
- if !hasYear {
- return date{}, false
- }
- case 1:
- if hasMon {
- d.day = nums[0]
- } else {
- d.month = nums[0]
- }
- case 2:
- if hasYear {
- d.month = nums[0]
- d.day = nums[1]
- } else if hasMon {
- d.year = nums[1]
- if nums[1] < 100 {
- d.year += 1900
- }
- if nums[1] < 50 {
- d.year += 100
- }
- d.day = nums[0]
- } else {
- d.month = nums[0]
- d.day = nums[1]
- }
- case 3:
- d.year = nums[2]
- if nums[2] < 100 {
- d.year += 1900
- }
- if nums[2] < 50 {
- d.year += 100
- }
- d.month = nums[0]
- d.day = nums[1]
- default:
- return date{}, false
- }
- return d, d.month > 0 && d.day > 0
- }
|