2017-10-04 18:50:25 +08:00
|
|
|
// Package dateparse parses date-strings without knowing the format
|
|
|
|
// in advance, using a fast lex based approach to eliminate shotgun
|
|
|
|
// attempts. It leans towards US style dates when there is a conflict.
|
2014-04-21 10:56:17 +08:00
|
|
|
package dateparse
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2014-04-26 07:59:10 +08:00
|
|
|
"strconv"
|
2018-05-24 12:11:15 +08:00
|
|
|
"strings"
|
2014-04-21 10:56:17 +08:00
|
|
|
"time"
|
|
|
|
"unicode"
|
2017-11-19 03:07:33 +08:00
|
|
|
"unicode/utf8"
|
2014-04-21 10:56:17 +08:00
|
|
|
)
|
|
|
|
|
2018-03-12 10:00:26 +08:00
|
|
|
// func init() {
|
|
|
|
// gou.SetupLogging("debug")
|
|
|
|
// gou.SetColorOutput()
|
|
|
|
// }
|
|
|
|
|
2020-05-06 10:10:21 +08:00
|
|
|
var days = []string{
|
|
|
|
"mon",
|
|
|
|
"tue",
|
|
|
|
"wed",
|
|
|
|
"thu",
|
|
|
|
"fri",
|
|
|
|
"sat",
|
|
|
|
"sun",
|
|
|
|
"monday",
|
|
|
|
"tuesday",
|
|
|
|
"wednesday",
|
|
|
|
"thursday",
|
|
|
|
"friday",
|
|
|
|
"saturday",
|
|
|
|
"sunday",
|
|
|
|
}
|
|
|
|
|
2018-05-24 12:11:15 +08:00
|
|
|
var months = []string{
|
|
|
|
"january",
|
|
|
|
"february",
|
|
|
|
"march",
|
|
|
|
"april",
|
|
|
|
"may",
|
|
|
|
"june",
|
|
|
|
"july",
|
|
|
|
"august",
|
|
|
|
"september",
|
|
|
|
"october",
|
|
|
|
"november",
|
|
|
|
"december",
|
|
|
|
}
|
|
|
|
|
2018-02-26 08:35:33 +08:00
|
|
|
type dateState uint8
|
|
|
|
type timeState uint8
|
2014-05-21 13:08:25 +08:00
|
|
|
|
2014-04-21 10:56:17 +08:00
|
|
|
const (
|
2018-11-23 05:54:48 +08:00
|
|
|
dateStart dateState = iota // 0
|
2018-02-26 08:35:33 +08:00
|
|
|
dateDigit
|
2021-02-04 14:23:19 +08:00
|
|
|
dateDigitSt
|
2018-10-10 11:50:09 +08:00
|
|
|
dateYearDash
|
|
|
|
dateYearDashAlphaDash
|
|
|
|
dateYearDashDash
|
2023-12-13 14:07:11 +08:00
|
|
|
dateYearDashDashWs // 6
|
2018-10-10 11:50:09 +08:00
|
|
|
dateYearDashDashT
|
2021-02-07 08:14:29 +08:00
|
|
|
dateYearDashDashOffset
|
2018-02-26 08:35:33 +08:00
|
|
|
dateDigitDash
|
2018-10-10 11:50:09 +08:00
|
|
|
dateDigitDashAlpha
|
2023-12-13 14:07:11 +08:00
|
|
|
dateDigitDashAlphaDash // 11
|
|
|
|
dateDigitDashDigit
|
|
|
|
dateDigitDashDigitDash
|
2021-02-07 08:14:29 +08:00
|
|
|
dateDigitDot
|
2018-02-26 08:35:33 +08:00
|
|
|
dateDigitDotDot
|
2023-12-15 14:47:31 +08:00
|
|
|
dateDigitDotDotWs
|
|
|
|
dateDigitDotDotT
|
|
|
|
dateDigitDotDotOffset
|
2018-02-26 08:35:33 +08:00
|
|
|
dateDigitSlash
|
2021-02-06 05:11:38 +08:00
|
|
|
dateDigitYearSlash
|
2023-12-15 14:47:31 +08:00
|
|
|
dateDigitSlashAlpha // 21
|
2021-02-07 08:14:29 +08:00
|
|
|
dateDigitColon
|
2018-02-26 08:35:33 +08:00
|
|
|
dateDigitChineseYear
|
2021-02-06 05:11:38 +08:00
|
|
|
dateDigitChineseYearWs
|
2020-06-18 04:55:25 +08:00
|
|
|
dateDigitWs
|
2023-12-15 14:47:31 +08:00
|
|
|
dateDigitWsMoYear // 26
|
2018-02-26 08:35:33 +08:00
|
|
|
dateAlpha
|
2021-02-06 05:11:38 +08:00
|
|
|
dateAlphaWs
|
2020-06-18 04:55:25 +08:00
|
|
|
dateAlphaWsDigit
|
2023-12-15 14:47:31 +08:00
|
|
|
dateAlphaWsDigitMore // 30
|
2021-02-07 08:14:29 +08:00
|
|
|
dateAlphaWsDigitMoreWs
|
2018-11-23 05:54:48 +08:00
|
|
|
dateAlphaWsDigitMoreWsYear
|
2021-02-06 05:11:38 +08:00
|
|
|
dateAlphaWsMonth
|
2020-05-06 10:10:21 +08:00
|
|
|
dateAlphaWsDigitYearmaybe
|
2018-11-23 05:54:48 +08:00
|
|
|
dateAlphaWsMonthMore
|
2021-02-07 08:14:29 +08:00
|
|
|
dateAlphaWsMonthSuffix
|
2018-11-23 05:54:48 +08:00
|
|
|
dateAlphaWsMore
|
2021-02-06 05:11:38 +08:00
|
|
|
dateAlphaWsAtTime
|
2018-03-02 10:36:46 +08:00
|
|
|
dateAlphaWsAlpha
|
2023-12-15 14:47:31 +08:00
|
|
|
dateAlphaWsAlphaYearmaybe // 40
|
2021-02-07 08:14:29 +08:00
|
|
|
dateAlphaPeriodWsDigit
|
2023-12-13 12:24:17 +08:00
|
|
|
dateAlphaSlash
|
|
|
|
dateAlphaSlashDigit
|
|
|
|
dateAlphaSlashDigitSlash
|
2018-02-26 08:35:33 +08:00
|
|
|
dateWeekdayComma
|
|
|
|
dateWeekdayAbbrevComma
|
2023-12-13 12:42:48 +08:00
|
|
|
dateYearWs
|
|
|
|
dateYearWsMonthWs
|
2018-02-26 14:46:45 +08:00
|
|
|
)
|
|
|
|
const (
|
|
|
|
// Time state
|
2018-04-18 05:16:01 +08:00
|
|
|
timeIgnore timeState = iota // 0
|
2018-02-26 08:35:33 +08:00
|
|
|
timeStart
|
|
|
|
timeWs
|
2018-03-12 08:39:59 +08:00
|
|
|
timeWsAlpha
|
2023-12-14 14:58:04 +08:00
|
|
|
timeWsAlphaRParen
|
2018-03-12 08:39:59 +08:00
|
|
|
timeWsAlphaWs
|
2023-12-14 14:58:04 +08:00
|
|
|
timeWsAlphaZoneOffset // 6
|
2018-03-02 10:36:46 +08:00
|
|
|
timeWsAlphaZoneOffsetWs
|
|
|
|
timeWsAlphaZoneOffsetWsYear
|
|
|
|
timeWsAlphaZoneOffsetWsExtra
|
2018-02-26 08:35:33 +08:00
|
|
|
timeWsAMPMMaybe
|
2023-12-14 14:58:04 +08:00
|
|
|
timeWsAMPM // 11
|
2018-03-02 10:36:46 +08:00
|
|
|
timeWsOffset
|
2023-12-14 14:58:04 +08:00
|
|
|
timeWsOffsetWs // 13
|
2018-02-26 08:35:33 +08:00
|
|
|
timeWsOffsetColonAlpha
|
|
|
|
timeWsOffsetColon
|
2023-12-14 14:58:04 +08:00
|
|
|
timeWsYear // 16
|
2023-12-16 08:42:07 +08:00
|
|
|
timeWsYearOffset
|
2018-03-02 10:36:46 +08:00
|
|
|
timeOffset
|
2018-02-26 08:35:33 +08:00
|
|
|
timeOffsetColon
|
2023-12-12 14:45:58 +08:00
|
|
|
timeOffsetColonAlpha
|
2018-02-26 08:35:33 +08:00
|
|
|
timePeriod
|
2023-12-12 14:45:58 +08:00
|
|
|
timePeriodAMPM
|
2018-02-26 08:35:33 +08:00
|
|
|
timeZ
|
2014-04-21 10:56:17 +08:00
|
|
|
)
|
|
|
|
|
2014-12-16 03:20:40 +08:00
|
|
|
var (
|
2018-07-01 01:20:13 +08:00
|
|
|
// ErrAmbiguousMMDD for date formats such as 04/02/2014 the mm/dd vs dd/mm are
|
|
|
|
// ambiguous, so it is an error for strict parse rules.
|
2023-02-15 13:26:18 +08:00
|
|
|
ErrAmbiguousMMDD = fmt.Errorf("this date has ambiguous mm/dd vs dd/mm type format")
|
|
|
|
ErrCouldntFindFormat = fmt.Errorf("could not find format for")
|
2023-12-12 14:45:58 +08:00
|
|
|
ErrUnexpectedTail = fmt.Errorf("unexpected content after date/time: ")
|
2014-12-16 03:20:40 +08:00
|
|
|
)
|
|
|
|
|
2018-06-09 09:01:07 +08:00
|
|
|
func unknownErr(datestr string) error {
|
2023-02-15 13:26:18 +08:00
|
|
|
return fmt.Errorf("%w %q", ErrCouldntFindFormat, datestr)
|
2018-06-09 09:01:07 +08:00
|
|
|
}
|
|
|
|
|
2023-12-12 14:45:58 +08:00
|
|
|
func unexpectedTail(tail string) error {
|
|
|
|
return fmt.Errorf("%w %q", ErrUnexpectedTail, tail)
|
|
|
|
}
|
|
|
|
|
2018-03-26 07:48:11 +08:00
|
|
|
// ParseAny parse an unknown date format, detect the layout.
|
|
|
|
// Normal parse. Equivalent Timezone rules as time.Parse().
|
|
|
|
// NOTE: please see readme on mmdd vs ddmm ambiguous dates.
|
2019-08-08 11:24:47 +08:00
|
|
|
func ParseAny(datestr string, opts ...ParserOption) (time.Time, error) {
|
|
|
|
p, err := parseTime(datestr, nil, opts...)
|
2018-03-25 06:56:26 +08:00
|
|
|
if err != nil {
|
|
|
|
return time.Time{}, err
|
|
|
|
}
|
|
|
|
return p.parse()
|
2017-07-27 07:42:12 +08:00
|
|
|
}
|
|
|
|
|
2017-08-13 00:48:28 +08:00
|
|
|
// ParseIn with Location, equivalent to time.ParseInLocation() timezone/offset
|
2017-07-27 13:37:37 +08:00
|
|
|
// rules. Using location arg, if timezone/offset info exists in the
|
|
|
|
// datestring, it uses the given location rules for any zone interpretation.
|
|
|
|
// That is, MST means one thing when using America/Denver and something else
|
|
|
|
// in other locations.
|
2019-08-08 11:24:47 +08:00
|
|
|
func ParseIn(datestr string, loc *time.Location, opts ...ParserOption) (time.Time, error) {
|
|
|
|
p, err := parseTime(datestr, loc, opts...)
|
2018-03-25 06:56:26 +08:00
|
|
|
if err != nil {
|
|
|
|
return time.Time{}, err
|
|
|
|
}
|
|
|
|
return p.parse()
|
2017-07-27 07:42:12 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// ParseLocal Given an unknown date format, detect the layout,
|
|
|
|
// using time.Local, parse.
|
2017-07-27 08:08:58 +08:00
|
|
|
//
|
2017-07-27 13:37:37 +08:00
|
|
|
// Set Location to time.Local. Same as ParseIn Location but lazily uses
|
|
|
|
// the global time.Local variable for Location argument.
|
|
|
|
//
|
2023-02-15 12:56:17 +08:00
|
|
|
// denverLoc, _ := time.LoadLocation("America/Denver")
|
|
|
|
// time.Local = denverLoc
|
2017-07-27 13:37:37 +08:00
|
|
|
//
|
2023-02-15 12:56:17 +08:00
|
|
|
// t, err := dateparse.ParseLocal("3/1/2014")
|
2017-07-27 13:37:37 +08:00
|
|
|
//
|
|
|
|
// Equivalent to:
|
|
|
|
//
|
2023-02-15 12:56:17 +08:00
|
|
|
// t, err := dateparse.ParseIn("3/1/2014", denverLoc)
|
2019-08-08 11:24:47 +08:00
|
|
|
func ParseLocal(datestr string, opts ...ParserOption) (time.Time, error) {
|
|
|
|
p, err := parseTime(datestr, time.Local, opts...)
|
2018-03-25 06:56:26 +08:00
|
|
|
if err != nil {
|
|
|
|
return time.Time{}, err
|
|
|
|
}
|
|
|
|
return p.parse()
|
2017-07-27 07:42:12 +08:00
|
|
|
}
|
|
|
|
|
2017-08-13 00:48:28 +08:00
|
|
|
// MustParse parse a date, and panic if it can't be parsed. Used for testing.
|
|
|
|
// Not recommended for most use-cases.
|
2019-08-08 11:24:47 +08:00
|
|
|
func MustParse(datestr string, opts ...ParserOption) time.Time {
|
|
|
|
p, err := parseTime(datestr, nil, opts...)
|
2018-03-25 06:56:26 +08:00
|
|
|
if err != nil {
|
|
|
|
panic(err.Error())
|
|
|
|
}
|
|
|
|
t, err := p.parse()
|
2017-07-27 13:39:44 +08:00
|
|
|
if err != nil {
|
|
|
|
panic(err.Error())
|
|
|
|
}
|
|
|
|
return t
|
|
|
|
}
|
|
|
|
|
2018-03-26 07:48:11 +08:00
|
|
|
// ParseFormat parse's an unknown date-time string and returns a layout
|
|
|
|
// string that can parse this (and exact same format) other date-time strings.
|
|
|
|
//
|
2023-02-15 12:56:17 +08:00
|
|
|
// layout, err := dateparse.ParseFormat("2013-02-01 00:00:00")
|
|
|
|
// // layout = "2006-01-02 15:04:05"
|
2019-08-08 11:24:47 +08:00
|
|
|
func ParseFormat(datestr string, opts ...ParserOption) (string, error) {
|
|
|
|
p, err := parseTime(datestr, nil, opts...)
|
2018-03-25 06:56:26 +08:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
_, err = p.parse()
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
2017-07-27 07:42:12 +08:00
|
|
|
}
|
2018-03-25 06:56:26 +08:00
|
|
|
return string(p.format), nil
|
2017-07-27 07:42:12 +08:00
|
|
|
}
|
2014-04-21 10:56:17 +08:00
|
|
|
|
2018-03-25 08:49:27 +08:00
|
|
|
// ParseStrict parse an unknown date format. IF the date is ambigous
|
2018-03-26 07:48:11 +08:00
|
|
|
// mm/dd vs dd/mm then return an error. These return errors: 3.3.2014 , 8/8/71 etc
|
2019-08-08 11:24:47 +08:00
|
|
|
func ParseStrict(datestr string, opts ...ParserOption) (time.Time, error) {
|
|
|
|
p, err := parseTime(datestr, nil, opts...)
|
2018-03-25 08:49:27 +08:00
|
|
|
if err != nil {
|
|
|
|
return time.Time{}, err
|
|
|
|
}
|
|
|
|
if p.ambiguousMD {
|
|
|
|
return time.Time{}, ErrAmbiguousMMDD
|
|
|
|
}
|
|
|
|
return p.parse()
|
|
|
|
}
|
|
|
|
|
2019-08-08 11:47:05 +08:00
|
|
|
func parseTime(datestr string, loc *time.Location, opts ...ParserOption) (p *parser, err error) {
|
|
|
|
|
2023-02-15 12:49:18 +08:00
|
|
|
p, err = newParser(datestr, loc, opts...)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
2023-12-12 14:45:58 +08:00
|
|
|
|
|
|
|
// if this string is impossibly long, don't even try. longest date might be something like:
|
|
|
|
// 'Wednesday, 8 February 2023 19:00:46.999999999 +11:00 (AEDT) m=+0.000000001'
|
|
|
|
if len(datestr) > 75 {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
|
|
|
|
2019-08-08 11:47:05 +08:00
|
|
|
if p.retryAmbiguousDateWithSwap {
|
|
|
|
// month out of range signifies that a day/month swap is the correct solution to an ambiguous date
|
|
|
|
// this is because it means that a day is being interpreted as a month and overflowing the valid value for that
|
|
|
|
// by retrying in this case, we can fix a common situation with no assumptions
|
|
|
|
defer func() {
|
2021-04-28 23:23:48 +08:00
|
|
|
if p != nil && p.ambiguousMD {
|
2019-08-13 09:38:23 +08:00
|
|
|
// if it errors out with the following error, swap before we
|
|
|
|
// get out of this function to reduce scope it needs to be applied on
|
2023-12-15 14:14:26 +08:00
|
|
|
_, err = p.parse()
|
2019-08-13 09:38:23 +08:00
|
|
|
if err != nil && strings.Contains(err.Error(), "month out of range") {
|
|
|
|
// create the option to reverse the preference
|
|
|
|
preferMonthFirst := PreferMonthFirst(!p.preferMonthFirst)
|
|
|
|
// turn off the retry to avoid endless recursion
|
|
|
|
retryAmbiguousDateWithSwap := RetryAmbiguousDateWithSwap(false)
|
|
|
|
modifiedOpts := append(opts, preferMonthFirst, retryAmbiguousDateWithSwap)
|
2023-12-15 14:14:26 +08:00
|
|
|
p, err = parseTime(datestr, time.Local, modifiedOpts...)
|
2019-08-13 09:38:23 +08:00
|
|
|
}
|
2019-08-08 11:47:05 +08:00
|
|
|
}
|
2019-08-13 09:38:23 +08:00
|
|
|
|
2019-08-08 11:47:05 +08:00
|
|
|
}()
|
|
|
|
}
|
2018-02-26 08:35:33 +08:00
|
|
|
|
2023-12-12 14:45:58 +08:00
|
|
|
// IMPORTANT: we may need to modify the datestr while we are parsing (e.g., to
|
|
|
|
// remove pieces of the string that should be ignored during golang parsing).
|
|
|
|
// We will iterate over the modified datestr, and whenever we update datestr,
|
|
|
|
// we need to make sure that i is adjusted accordingly to resume parsing in
|
|
|
|
// the correct place. In error messages though we'll use the original datestr.
|
2018-02-26 08:35:33 +08:00
|
|
|
i := 0
|
2014-04-21 10:56:17 +08:00
|
|
|
|
2014-06-16 03:11:29 +08:00
|
|
|
// General strategy is to read rune by rune through the date looking for
|
|
|
|
// certain hints of what type of date we are dealing with.
|
|
|
|
// Hopefully we only need to read about 5 or 6 bytes before
|
|
|
|
// we figure it out and then attempt a parse
|
2014-05-07 12:15:43 +08:00
|
|
|
iterRunes:
|
2023-12-12 14:45:58 +08:00
|
|
|
for ; i < len(p.datestr); i++ {
|
|
|
|
r, bytesConsumed := utf8.DecodeRuneInString(p.datestr[i:])
|
2017-11-19 03:07:33 +08:00
|
|
|
if bytesConsumed > 1 {
|
2023-02-15 13:10:45 +08:00
|
|
|
i += bytesConsumed - 1
|
2017-11-19 03:07:33 +08:00
|
|
|
}
|
2014-05-07 12:15:43 +08:00
|
|
|
|
2023-12-12 14:45:58 +08:00
|
|
|
// gou.Debugf("i=%d r=%s state=%d %s", i, string(r), p.stateDate, p.datestr)
|
2018-02-26 14:46:45 +08:00
|
|
|
switch p.stateDate {
|
2018-02-26 08:35:33 +08:00
|
|
|
case dateStart:
|
2014-05-07 12:15:43 +08:00
|
|
|
if unicode.IsDigit(r) {
|
2018-02-26 14:46:45 +08:00
|
|
|
p.stateDate = dateDigit
|
2017-02-06 02:04:03 +08:00
|
|
|
} else if unicode.IsLetter(r) {
|
2018-02-26 14:46:45 +08:00
|
|
|
p.stateDate = dateAlpha
|
2018-03-25 06:56:26 +08:00
|
|
|
} else {
|
2023-12-12 14:45:58 +08:00
|
|
|
return p, unknownErr(datestr)
|
2014-04-21 10:56:17 +08:00
|
|
|
}
|
2018-02-26 14:46:45 +08:00
|
|
|
case dateDigit:
|
|
|
|
|
2014-05-07 12:15:43 +08:00
|
|
|
switch r {
|
2017-07-14 10:57:15 +08:00
|
|
|
case '-', '\u2212':
|
2018-02-26 14:46:45 +08:00
|
|
|
// 2006-01-02
|
|
|
|
// 2013-Feb-03
|
2018-10-10 11:50:09 +08:00
|
|
|
// 13-Feb-03
|
|
|
|
// 29-Jun-2016
|
2018-02-26 14:46:45 +08:00
|
|
|
if i == 4 {
|
2018-10-10 11:50:09 +08:00
|
|
|
p.stateDate = dateYearDash
|
|
|
|
p.yeari = 0
|
|
|
|
p.yearlen = i
|
|
|
|
p.moi = i + 1
|
2018-02-26 14:46:45 +08:00
|
|
|
p.set(0, "2006")
|
2018-10-10 11:50:09 +08:00
|
|
|
} else {
|
|
|
|
p.stateDate = dateDigitDash
|
2018-02-26 14:46:45 +08:00
|
|
|
}
|
2014-05-07 12:15:43 +08:00
|
|
|
case '/':
|
2021-02-04 15:51:24 +08:00
|
|
|
// 08/May/2005
|
2018-03-11 03:50:19 +08:00
|
|
|
// 03/31/2005
|
|
|
|
// 2014/02/24
|
2018-02-26 14:46:45 +08:00
|
|
|
p.stateDate = dateDigitSlash
|
2018-03-11 03:50:19 +08:00
|
|
|
if i == 4 {
|
2021-02-06 05:11:38 +08:00
|
|
|
// 2014/02/24 - Year first /
|
|
|
|
p.yearlen = i // since it was start of datestr, i=len
|
2018-03-11 03:50:19 +08:00
|
|
|
p.moi = i + 1
|
2023-12-12 14:45:58 +08:00
|
|
|
if !p.setYear() {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
2021-02-06 05:11:38 +08:00
|
|
|
p.stateDate = dateDigitYearSlash
|
2018-03-11 03:50:19 +08:00
|
|
|
} else {
|
2021-02-06 05:11:38 +08:00
|
|
|
// Either Ambiguous dd/mm vs mm/dd OR dd/month/yy
|
|
|
|
// 08/May/2005
|
|
|
|
// 03/31/2005
|
|
|
|
// 31/03/2005
|
2023-12-12 14:45:58 +08:00
|
|
|
if i+2 < len(p.datestr) && unicode.IsLetter(rune(p.datestr[i+1])) {
|
2021-02-06 05:11:38 +08:00
|
|
|
// 08/May/2005
|
|
|
|
p.stateDate = dateDigitSlashAlpha
|
|
|
|
p.moi = i + 1
|
|
|
|
p.daylen = 2
|
|
|
|
p.dayi = 0
|
2023-12-12 14:45:58 +08:00
|
|
|
if !p.setDay() {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
2021-02-06 05:11:38 +08:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
// Ambiguous dd/mm vs mm/dd the bane of date-parsing
|
|
|
|
// 03/31/2005
|
|
|
|
// 31/03/2005
|
2018-03-25 06:56:26 +08:00
|
|
|
p.ambiguousMD = true
|
2018-03-11 03:50:19 +08:00
|
|
|
if p.preferMonthFirst {
|
|
|
|
if p.molen == 0 {
|
2021-02-06 05:11:38 +08:00
|
|
|
// 03/31/2005
|
2018-03-11 03:50:19 +08:00
|
|
|
p.molen = i
|
2023-12-12 14:45:58 +08:00
|
|
|
if !p.setMonth() {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
2018-03-11 03:50:19 +08:00
|
|
|
p.dayi = i + 1
|
|
|
|
}
|
2019-08-08 11:10:40 +08:00
|
|
|
} else {
|
|
|
|
if p.daylen == 0 {
|
|
|
|
p.daylen = i
|
2023-12-12 14:45:58 +08:00
|
|
|
if !p.setDay() {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
2019-08-08 11:10:40 +08:00
|
|
|
p.moi = i + 1
|
2019-08-08 11:12:45 +08:00
|
|
|
}
|
2018-03-11 03:50:19 +08:00
|
|
|
}
|
2021-02-06 05:11:38 +08:00
|
|
|
|
2018-03-11 03:50:19 +08:00
|
|
|
}
|
|
|
|
|
2020-06-18 04:55:25 +08:00
|
|
|
case ':':
|
|
|
|
// 03/31/2005
|
|
|
|
// 2014/02/24
|
|
|
|
p.stateDate = dateDigitColon
|
|
|
|
if i == 4 {
|
|
|
|
p.yearlen = i
|
|
|
|
p.moi = i + 1
|
2023-12-12 14:45:58 +08:00
|
|
|
if !p.setYear() {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
2020-06-18 04:55:25 +08:00
|
|
|
} else {
|
|
|
|
p.ambiguousMD = true
|
|
|
|
if p.preferMonthFirst {
|
|
|
|
if p.molen == 0 {
|
|
|
|
p.molen = i
|
2023-12-12 14:45:58 +08:00
|
|
|
if !p.setMonth() {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
2020-06-18 04:55:25 +08:00
|
|
|
p.dayi = i + 1
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-12 05:21:04 +08:00
|
|
|
case '.':
|
2018-02-28 12:05:14 +08:00
|
|
|
// 3.31.2014
|
2018-05-04 09:06:52 +08:00
|
|
|
// 08.21.71
|
|
|
|
// 2014.05
|
2018-02-26 14:46:45 +08:00
|
|
|
p.stateDate = dateDigitDot
|
2018-05-04 09:06:52 +08:00
|
|
|
if i == 4 {
|
|
|
|
p.yearlen = i
|
|
|
|
p.moi = i + 1
|
2023-12-12 14:45:58 +08:00
|
|
|
if !p.setYear() {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
2023-12-13 14:19:35 +08:00
|
|
|
} else if i <= 2 {
|
2018-05-04 09:06:52 +08:00
|
|
|
p.ambiguousMD = true
|
2023-12-12 14:46:44 +08:00
|
|
|
if p.preferMonthFirst {
|
|
|
|
if p.molen == 0 {
|
|
|
|
// 03.31.2005
|
|
|
|
p.molen = i
|
|
|
|
if !p.setMonth() {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
|
|
|
p.dayi = i + 1
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if p.daylen == 0 {
|
|
|
|
p.daylen = i
|
|
|
|
if !p.setDay() {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
|
|
|
p.moi = i + 1
|
|
|
|
}
|
2023-12-12 14:45:58 +08:00
|
|
|
}
|
2018-05-04 09:06:52 +08:00
|
|
|
}
|
2023-12-13 14:19:35 +08:00
|
|
|
// else this might be a unixy combined datetime of the form:
|
|
|
|
// yyyyMMddhhmmss.SSS
|
2018-05-04 09:06:52 +08:00
|
|
|
|
2018-01-25 09:59:25 +08:00
|
|
|
case ' ':
|
2018-02-28 12:05:14 +08:00
|
|
|
// 18 January 2018
|
|
|
|
// 8 January 2018
|
2018-03-11 05:43:16 +08:00
|
|
|
// 8 jan 2018
|
2018-02-28 12:05:14 +08:00
|
|
|
// 02 Jan 2018 23:59
|
|
|
|
// 02 Jan 2018 23:59:34
|
|
|
|
// 12 Feb 2006, 19:17
|
|
|
|
// 12 Feb 2006, 19:17:22
|
2023-12-13 12:42:48 +08:00
|
|
|
// 2013 Jan 06 15:04:05
|
|
|
|
if i == 4 {
|
|
|
|
p.yearlen = i
|
|
|
|
p.moi = i + 1
|
|
|
|
if !p.setYear() {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
|
|
|
p.stateDate = dateYearWs
|
|
|
|
} else if i == 6 {
|
2021-02-04 14:23:19 +08:00
|
|
|
p.stateDate = dateDigitSt
|
|
|
|
} else {
|
|
|
|
p.stateDate = dateDigitWs
|
|
|
|
p.dayi = 0
|
|
|
|
p.daylen = i
|
|
|
|
}
|
2018-02-26 14:46:45 +08:00
|
|
|
case '年':
|
|
|
|
// Chinese Year
|
|
|
|
p.stateDate = dateDigitChineseYear
|
2023-12-16 08:14:03 +08:00
|
|
|
p.yearlen = i - 2
|
|
|
|
p.moi = i + 1
|
|
|
|
if !p.setYear() {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
2018-04-19 08:36:22 +08:00
|
|
|
case ',':
|
2023-12-12 14:45:58 +08:00
|
|
|
return p, unknownErr(datestr)
|
2023-12-13 11:18:58 +08:00
|
|
|
case 's', 'S', 'r', 'R', 't', 'T', 'n', 'N':
|
|
|
|
// 1st January 2018
|
|
|
|
// 2nd Jan 2018 23:59
|
|
|
|
// st, rd, nd, st
|
|
|
|
p.stateDate = dateAlphaWsMonthSuffix
|
|
|
|
i--
|
2018-02-26 14:46:45 +08:00
|
|
|
default:
|
|
|
|
continue
|
2014-04-25 08:51:56 +08:00
|
|
|
}
|
2018-02-26 14:46:45 +08:00
|
|
|
p.part1Len = i
|
2018-01-25 09:59:25 +08:00
|
|
|
|
2021-02-04 14:23:19 +08:00
|
|
|
case dateDigitSt:
|
|
|
|
p.set(0, "060102")
|
2021-02-04 15:51:24 +08:00
|
|
|
i = i - 1
|
2021-02-04 14:23:19 +08:00
|
|
|
p.stateTime = timeStart
|
|
|
|
break iterRunes
|
2018-10-10 11:50:09 +08:00
|
|
|
case dateYearDash:
|
|
|
|
// dateYearDashDashT
|
2018-02-14 12:44:26 +08:00
|
|
|
// 2006-01-02T15:04:05Z07:00
|
2021-02-06 07:19:04 +08:00
|
|
|
// 2020-08-17T17:00:00:000+0100
|
2018-10-10 11:50:09 +08:00
|
|
|
// dateYearDashDashWs
|
2018-02-14 12:44:26 +08:00
|
|
|
// 2013-04-01 22:43:22
|
2018-10-10 11:50:09 +08:00
|
|
|
// dateYearDashAlphaDash
|
|
|
|
// 2013-Feb-03
|
2018-02-26 14:46:45 +08:00
|
|
|
switch r {
|
|
|
|
case '-':
|
|
|
|
p.molen = i - p.moi
|
|
|
|
p.dayi = i + 1
|
2018-10-10 11:50:09 +08:00
|
|
|
p.stateDate = dateYearDashDash
|
2023-12-12 14:45:58 +08:00
|
|
|
if !p.setMonth() {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
2014-12-03 06:57:00 +08:00
|
|
|
default:
|
2018-10-10 11:50:09 +08:00
|
|
|
if unicode.IsLetter(r) {
|
|
|
|
p.stateDate = dateYearDashAlphaDash
|
2014-12-03 06:57:00 +08:00
|
|
|
}
|
2014-05-07 12:15:43 +08:00
|
|
|
}
|
2018-10-10 11:50:09 +08:00
|
|
|
|
|
|
|
case dateYearDashDash:
|
|
|
|
// dateYearDashDashT
|
2018-02-26 14:46:45 +08:00
|
|
|
// 2006-01-02T15:04:05Z07:00
|
2018-10-10 11:50:09 +08:00
|
|
|
// dateYearDashDashWs
|
2018-02-26 14:46:45 +08:00
|
|
|
// 2013-04-01 22:43:22
|
2021-02-07 08:14:29 +08:00
|
|
|
// dateYearDashDashOffset
|
|
|
|
// 2020-07-20+00:00
|
2018-02-26 14:46:45 +08:00
|
|
|
switch r {
|
2021-02-07 08:14:29 +08:00
|
|
|
case '+', '-':
|
|
|
|
p.offseti = i
|
|
|
|
p.daylen = i - p.dayi
|
|
|
|
p.stateDate = dateYearDashDashOffset
|
2023-12-12 14:45:58 +08:00
|
|
|
if !p.setDay() {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
2018-02-26 14:46:45 +08:00
|
|
|
case ' ':
|
|
|
|
p.daylen = i - p.dayi
|
2018-10-10 11:50:09 +08:00
|
|
|
p.stateDate = dateYearDashDashWs
|
2018-02-26 14:46:45 +08:00
|
|
|
p.stateTime = timeStart
|
2023-12-12 14:45:58 +08:00
|
|
|
if !p.setDay() {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
2018-02-26 14:46:45 +08:00
|
|
|
break iterRunes
|
|
|
|
case 'T':
|
|
|
|
p.daylen = i - p.dayi
|
2018-10-10 11:50:09 +08:00
|
|
|
p.stateDate = dateYearDashDashT
|
2018-02-26 14:46:45 +08:00
|
|
|
p.stateTime = timeStart
|
2023-12-12 14:45:58 +08:00
|
|
|
if !p.setDay() {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
2018-02-26 14:46:45 +08:00
|
|
|
break iterRunes
|
|
|
|
}
|
2021-02-06 07:19:04 +08:00
|
|
|
|
|
|
|
case dateYearDashDashT:
|
|
|
|
// dateYearDashDashT
|
|
|
|
// 2006-01-02T15:04:05Z07:00
|
|
|
|
// 2020-08-17T17:00:00:000+0100
|
|
|
|
|
2021-02-07 08:14:29 +08:00
|
|
|
case dateYearDashDashOffset:
|
|
|
|
// 2020-07-20+00:00
|
|
|
|
switch r {
|
|
|
|
case ':':
|
|
|
|
p.set(p.offseti, "-07:00")
|
|
|
|
}
|
|
|
|
|
2018-10-10 11:50:09 +08:00
|
|
|
case dateYearDashAlphaDash:
|
2018-02-26 14:46:45 +08:00
|
|
|
// 2013-Feb-03
|
|
|
|
switch r {
|
|
|
|
case '-':
|
2018-02-28 12:05:14 +08:00
|
|
|
p.molen = i - p.moi
|
2018-02-26 14:46:45 +08:00
|
|
|
p.set(p.moi, "Jan")
|
|
|
|
p.dayi = i + 1
|
|
|
|
}
|
2018-10-10 11:50:09 +08:00
|
|
|
case dateDigitDash:
|
|
|
|
// 13-Feb-03
|
|
|
|
// 29-Jun-2016
|
|
|
|
if unicode.IsLetter(r) {
|
|
|
|
p.stateDate = dateDigitDashAlpha
|
|
|
|
p.moi = i
|
2023-12-13 14:07:11 +08:00
|
|
|
} else if unicode.IsDigit(r) {
|
|
|
|
p.stateDate = dateDigitDashDigit
|
|
|
|
p.moi = i
|
2018-10-10 11:50:09 +08:00
|
|
|
} else {
|
2023-12-12 14:45:58 +08:00
|
|
|
return p, unknownErr(datestr)
|
2018-10-10 11:50:09 +08:00
|
|
|
}
|
|
|
|
case dateDigitDashAlpha:
|
|
|
|
// 13-Feb-03
|
|
|
|
// 28-Feb-03
|
|
|
|
// 29-Jun-2016
|
|
|
|
switch r {
|
|
|
|
case '-':
|
|
|
|
p.molen = i - p.moi
|
|
|
|
p.set(p.moi, "Jan")
|
|
|
|
p.yeari = i + 1
|
|
|
|
p.stateDate = dateDigitDashAlphaDash
|
|
|
|
}
|
|
|
|
|
2023-12-13 14:07:11 +08:00
|
|
|
case dateDigitDashDigit:
|
|
|
|
// 29-06-2026
|
|
|
|
switch r {
|
|
|
|
case '-':
|
|
|
|
// X
|
|
|
|
// 29-06-2026
|
|
|
|
p.molen = i - p.moi
|
|
|
|
if p.molen == 2 {
|
|
|
|
p.set(p.moi, "01")
|
|
|
|
p.yeari = i + 1
|
|
|
|
p.stateDate = dateDigitDashDigitDash
|
|
|
|
} else {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
case dateDigitDashAlphaDash, dateDigitDashDigitDash:
|
|
|
|
// dateDigitDashAlphaDash:
|
|
|
|
// 13-Feb-03 ambiguous
|
|
|
|
// 28-Feb-03 ambiguous
|
|
|
|
// 29-Jun-2016 dd-month(alpha)-yyyy
|
|
|
|
// dateDigitDashDigitDash:
|
|
|
|
// 29-06-2026
|
2018-10-10 11:50:09 +08:00
|
|
|
switch r {
|
|
|
|
case ' ':
|
|
|
|
// we need to find if this was 4 digits, aka year
|
|
|
|
// or 2 digits which makes it ambiguous year/day
|
|
|
|
length := i - (p.moi + p.molen + 1)
|
|
|
|
if length == 4 {
|
|
|
|
p.yearlen = 4
|
|
|
|
p.set(p.yeari, "2006")
|
|
|
|
// We now also know that part1 was the day
|
|
|
|
p.dayi = 0
|
|
|
|
p.daylen = p.part1Len
|
2023-12-12 14:45:58 +08:00
|
|
|
if !p.setDay() {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
2018-10-10 11:50:09 +08:00
|
|
|
} else if length == 2 {
|
|
|
|
// We have no idea if this is
|
|
|
|
// yy-mon-dd OR dd-mon-yy
|
2023-12-13 14:07:11 +08:00
|
|
|
// (or for dateDigitDashDigitDash, yy-mm-dd OR dd-mm-yy)
|
2018-10-10 11:50:09 +08:00
|
|
|
//
|
2023-12-13 14:07:11 +08:00
|
|
|
// We are going to ASSUME (bad, bad) that it is dd-mon-yy (dd-mm-yy),
|
|
|
|
// which is a horrible assumption, but seems to be the convention for
|
|
|
|
// dates that are formatted in this way.
|
2018-10-10 11:50:09 +08:00
|
|
|
p.ambiguousMD = true
|
|
|
|
p.yearlen = 2
|
|
|
|
p.set(p.yeari, "06")
|
|
|
|
// We now also know that part1 was the day
|
|
|
|
p.dayi = 0
|
|
|
|
p.daylen = p.part1Len
|
2023-12-12 14:45:58 +08:00
|
|
|
if !p.setDay() {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
2023-12-13 14:07:11 +08:00
|
|
|
} else {
|
|
|
|
return p, unknownErr(datestr)
|
2018-10-10 11:50:09 +08:00
|
|
|
}
|
|
|
|
p.stateTime = timeStart
|
|
|
|
break iterRunes
|
|
|
|
}
|
|
|
|
|
2021-02-06 05:11:38 +08:00
|
|
|
case dateDigitYearSlash:
|
2014-07-11 06:25:23 +08:00
|
|
|
// 2014/07/10 06:55:38.156283
|
2021-02-06 05:11:38 +08:00
|
|
|
// I honestly don't know if this format ever shows up as yyyy/
|
2018-02-26 14:46:45 +08:00
|
|
|
|
2014-05-07 12:15:43 +08:00
|
|
|
switch r {
|
2023-02-15 13:27:43 +08:00
|
|
|
case ' ':
|
|
|
|
fallthrough
|
|
|
|
case ':':
|
2018-03-11 03:50:19 +08:00
|
|
|
p.stateTime = timeStart
|
2021-02-06 05:11:38 +08:00
|
|
|
if p.daylen == 0 {
|
2018-03-11 03:50:19 +08:00
|
|
|
p.daylen = i - p.dayi
|
2023-12-12 14:45:58 +08:00
|
|
|
if !p.setDay() {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
2018-03-11 03:50:19 +08:00
|
|
|
}
|
|
|
|
break iterRunes
|
2021-02-06 05:11:38 +08:00
|
|
|
case '/':
|
|
|
|
if p.molen == 0 {
|
|
|
|
p.molen = i - p.moi
|
2023-12-12 14:45:58 +08:00
|
|
|
if !p.setMonth() {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
2021-02-06 05:11:38 +08:00
|
|
|
p.dayi = i + 1
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
case dateDigitSlashAlpha:
|
|
|
|
// 06/May/2008
|
|
|
|
|
|
|
|
switch r {
|
|
|
|
case '/':
|
|
|
|
// |
|
|
|
|
// 06/May/2008
|
|
|
|
if p.molen == 0 {
|
|
|
|
p.set(p.moi, "Jan")
|
|
|
|
p.yeari = i + 1
|
|
|
|
}
|
|
|
|
// We aren't breaking because we are going to re-use this case
|
|
|
|
// to find where the date starts, and possible time begins
|
2023-02-15 13:27:43 +08:00
|
|
|
case ' ':
|
|
|
|
fallthrough
|
|
|
|
case ':':
|
2021-02-04 14:23:19 +08:00
|
|
|
p.stateTime = timeStart
|
|
|
|
if p.yearlen == 0 {
|
|
|
|
p.yearlen = i - p.yeari
|
2023-12-12 14:45:58 +08:00
|
|
|
if !p.setYear() {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
2021-02-04 14:23:19 +08:00
|
|
|
}
|
|
|
|
break iterRunes
|
2021-02-06 05:11:38 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
case dateDigitSlash:
|
|
|
|
// 03/19/2012 10:11:59
|
|
|
|
// 04/2/2014 03:00:37
|
2023-12-15 14:14:26 +08:00
|
|
|
// 04/2/2014, 03:00:37
|
2021-02-06 05:11:38 +08:00
|
|
|
// 3/1/2012 10:11:59
|
|
|
|
// 4/8/2014 22:05
|
|
|
|
// 3/1/2014
|
|
|
|
// 10/13/2014
|
|
|
|
// 01/02/2006
|
|
|
|
// 1/2/06
|
|
|
|
|
|
|
|
switch r {
|
2018-02-26 14:46:45 +08:00
|
|
|
case '/':
|
2021-02-06 05:11:38 +08:00
|
|
|
// This is the 2nd / so now we should know start pts of all of the dd, mm, yy
|
|
|
|
if p.preferMonthFirst {
|
2018-03-11 03:50:19 +08:00
|
|
|
if p.daylen == 0 {
|
|
|
|
p.daylen = i - p.dayi
|
2023-12-12 14:45:58 +08:00
|
|
|
if !p.setDay() {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
2018-03-11 03:50:19 +08:00
|
|
|
p.yeari = i + 1
|
|
|
|
}
|
2019-08-08 11:10:40 +08:00
|
|
|
} else {
|
|
|
|
if p.molen == 0 {
|
|
|
|
p.molen = i - p.moi
|
2023-12-12 14:45:58 +08:00
|
|
|
if !p.setMonth() {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
2019-08-08 11:10:40 +08:00
|
|
|
p.yeari = i + 1
|
2019-08-08 11:12:45 +08:00
|
|
|
}
|
2018-03-11 03:50:19 +08:00
|
|
|
}
|
2021-02-06 05:11:38 +08:00
|
|
|
// Note no break, we are going to pass by and re-enter this dateDigitSlash
|
|
|
|
// and look for ending (space) or not (just date)
|
2023-12-15 14:14:26 +08:00
|
|
|
case ' ', ',':
|
2021-02-06 05:11:38 +08:00
|
|
|
p.stateTime = timeStart
|
|
|
|
if p.yearlen == 0 {
|
|
|
|
p.yearlen = i - p.yeari
|
2023-12-15 14:14:26 +08:00
|
|
|
if r == ',' {
|
|
|
|
// skip the comma
|
|
|
|
i++
|
|
|
|
}
|
2023-12-12 14:45:58 +08:00
|
|
|
if !p.setYear() {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
2021-02-04 15:51:24 +08:00
|
|
|
}
|
2021-02-06 05:11:38 +08:00
|
|
|
break iterRunes
|
2014-05-07 12:15:43 +08:00
|
|
|
}
|
2018-01-25 09:59:25 +08:00
|
|
|
|
2020-06-18 04:55:25 +08:00
|
|
|
case dateDigitColon:
|
|
|
|
// 2014:07:10 06:55:38.156283
|
|
|
|
// 03:19:2012 10:11:59
|
|
|
|
// 04:2:2014 03:00:37
|
|
|
|
// 3:1:2012 10:11:59
|
|
|
|
// 4:8:2014 22:05
|
|
|
|
// 3:1:2014
|
|
|
|
// 10:13:2014
|
|
|
|
// 01:02:2006
|
|
|
|
// 1:2:06
|
|
|
|
|
|
|
|
switch r {
|
|
|
|
case ' ':
|
|
|
|
p.stateTime = timeStart
|
|
|
|
if p.yearlen == 0 {
|
|
|
|
p.yearlen = i - p.yeari
|
2023-12-12 14:45:58 +08:00
|
|
|
if !p.setYear() {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
2020-06-18 04:55:25 +08:00
|
|
|
} else if p.daylen == 0 {
|
|
|
|
p.daylen = i - p.dayi
|
2023-12-12 14:45:58 +08:00
|
|
|
if !p.setDay() {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
2020-06-18 04:55:25 +08:00
|
|
|
}
|
|
|
|
break iterRunes
|
|
|
|
case ':':
|
|
|
|
if p.yearlen > 0 {
|
|
|
|
// 2014:07:10 06:55:38.156283
|
|
|
|
if p.molen == 0 {
|
|
|
|
p.molen = i - p.moi
|
2023-12-12 14:45:58 +08:00
|
|
|
if !p.setMonth() {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
2020-06-18 04:55:25 +08:00
|
|
|
p.dayi = i + 1
|
|
|
|
}
|
|
|
|
} else if p.preferMonthFirst {
|
|
|
|
if p.daylen == 0 {
|
|
|
|
p.daylen = i - p.dayi
|
2023-12-12 14:45:58 +08:00
|
|
|
if !p.setDay() {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
2020-06-18 04:55:25 +08:00
|
|
|
p.yeari = i + 1
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-26 08:35:33 +08:00
|
|
|
case dateDigitWs:
|
2018-01-25 09:59:25 +08:00
|
|
|
// 18 January 2018
|
|
|
|
// 8 January 2018
|
2018-03-11 05:43:16 +08:00
|
|
|
// 8 jan 2018
|
|
|
|
// 1 jan 18
|
2018-02-28 12:05:14 +08:00
|
|
|
// 02 Jan 2018 23:59
|
|
|
|
// 02 Jan 2018 23:59:34
|
|
|
|
// 12 Feb 2006, 19:17
|
|
|
|
// 12 Feb 2006, 19:17:22
|
|
|
|
switch r {
|
|
|
|
case ' ':
|
|
|
|
p.yeari = i + 1
|
2018-03-11 05:43:16 +08:00
|
|
|
//p.yearlen = 4
|
2018-02-28 12:05:14 +08:00
|
|
|
p.dayi = 0
|
|
|
|
p.daylen = p.part1Len
|
2023-12-12 14:45:58 +08:00
|
|
|
if !p.setDay() {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
2018-02-28 12:05:14 +08:00
|
|
|
p.stateTime = timeStart
|
2018-10-21 03:01:51 +08:00
|
|
|
if i > p.daylen+len(" Sep") { // November etc
|
2023-12-12 14:45:58 +08:00
|
|
|
// If this is a legit full month, then change the string we're parsing
|
|
|
|
// to compensate for the longest month, and do the same with the format string. We
|
|
|
|
// must maintain a corresponding length/content and this is the easiest
|
|
|
|
// way to do this.
|
|
|
|
possibleFullMonth := strings.ToLower(p.datestr[(p.dayi + (p.daylen + 1)):i])
|
|
|
|
if isMonthFull(possibleFullMonth) {
|
|
|
|
p.moi = p.dayi + p.daylen + 1
|
|
|
|
p.molen = i - p.moi
|
|
|
|
p.fullMonth = possibleFullMonth
|
|
|
|
p.stateDate = dateDigitWsMoYear
|
|
|
|
} else {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
2018-10-21 03:01:51 +08:00
|
|
|
} else {
|
|
|
|
// If len=3, the might be Feb or May? Ie ambigous abbreviated but
|
|
|
|
// we can parse may with either. BUT, that means the
|
|
|
|
// format may not be correct?
|
2023-12-12 14:45:58 +08:00
|
|
|
// mo := strings.ToLower(p.datestr[p.daylen+1 : i])
|
|
|
|
p.moi = p.dayi + p.daylen + 1
|
2018-10-21 03:01:51 +08:00
|
|
|
p.molen = i - p.moi
|
2018-02-28 12:05:14 +08:00
|
|
|
p.set(p.moi, "Jan")
|
2018-03-02 10:36:46 +08:00
|
|
|
p.stateDate = dateDigitWsMoYear
|
2018-02-09 04:52:03 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-28 12:05:14 +08:00
|
|
|
case dateDigitWsMoYear:
|
2018-03-11 05:43:16 +08:00
|
|
|
// 8 jan 2018
|
2018-02-28 12:05:14 +08:00
|
|
|
// 02 Jan 2018 23:59
|
|
|
|
// 02 Jan 2018 23:59:34
|
|
|
|
// 12 Feb 2006, 19:17
|
|
|
|
// 12 Feb 2006, 19:17:22
|
2018-02-09 04:52:03 +08:00
|
|
|
switch r {
|
2018-02-28 12:05:14 +08:00
|
|
|
case ',':
|
2018-03-12 08:39:59 +08:00
|
|
|
p.yearlen = i - p.yeari
|
2023-12-12 14:45:58 +08:00
|
|
|
if !p.setYear() {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
2018-02-28 12:05:14 +08:00
|
|
|
i++
|
|
|
|
break iterRunes
|
2018-03-12 10:00:26 +08:00
|
|
|
case ' ':
|
|
|
|
p.yearlen = i - p.yeari
|
2023-12-12 14:45:58 +08:00
|
|
|
if !p.setYear() {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
2018-03-12 10:00:26 +08:00
|
|
|
break iterRunes
|
2018-01-25 09:59:25 +08:00
|
|
|
}
|
2023-12-13 12:42:48 +08:00
|
|
|
|
|
|
|
case dateYearWs:
|
|
|
|
// 2013 Jan 06 15:04:05
|
|
|
|
// 2013 January 06 15:04:05
|
|
|
|
if r == ' ' {
|
|
|
|
p.molen = i - p.moi
|
|
|
|
// Must be a valid short or long month
|
|
|
|
if p.molen == 3 {
|
|
|
|
p.set(p.moi, "Jan")
|
|
|
|
p.dayi = i + 1
|
|
|
|
p.stateDate = dateYearWsMonthWs
|
|
|
|
} else {
|
|
|
|
possibleFullMonth := strings.ToLower(p.datestr[p.moi:(p.moi + p.molen)])
|
|
|
|
if i > 3 && isMonthFull(possibleFullMonth) {
|
|
|
|
p.fullMonth = possibleFullMonth
|
|
|
|
p.dayi = i + 1
|
|
|
|
p.stateDate = dateYearWsMonthWs
|
|
|
|
} else {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
case dateYearWsMonthWs:
|
|
|
|
// 2013 Jan 06 15:04:05
|
|
|
|
// 2013 January 06 15:04:05
|
|
|
|
switch r {
|
|
|
|
case ',':
|
|
|
|
p.daylen = i - p.dayi
|
|
|
|
p.setDay()
|
|
|
|
i++
|
|
|
|
p.stateTime = timeStart
|
|
|
|
break iterRunes
|
|
|
|
case ' ':
|
|
|
|
p.daylen = i - p.dayi
|
|
|
|
p.setDay()
|
|
|
|
p.stateTime = timeStart
|
|
|
|
break iterRunes
|
|
|
|
}
|
|
|
|
|
2018-02-26 08:35:33 +08:00
|
|
|
case dateDigitChineseYear:
|
|
|
|
// dateDigitChineseYear
|
2017-11-19 03:07:33 +08:00
|
|
|
// 2014年04月08日
|
|
|
|
// weekday %Y年%m月%e日 %A %I:%M %p
|
|
|
|
// 2013年07月18日 星期四 10:27 上午
|
2023-12-16 08:14:03 +08:00
|
|
|
switch r {
|
|
|
|
case '月':
|
|
|
|
// month
|
|
|
|
p.molen = i - p.moi - 2
|
|
|
|
p.dayi = i + 1
|
|
|
|
if !p.setMonth() {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
|
|
|
case '日':
|
|
|
|
// day
|
|
|
|
p.daylen = i - p.dayi - 2
|
|
|
|
if !p.setDay() {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
|
|
|
case ' ':
|
|
|
|
if p.daylen <= 0 {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
2018-02-26 14:46:45 +08:00
|
|
|
p.stateDate = dateDigitChineseYearWs
|
2023-12-16 08:14:03 +08:00
|
|
|
p.stateTime = timeStart
|
2023-12-09 09:31:28 +08:00
|
|
|
break iterRunes
|
2017-11-19 03:07:33 +08:00
|
|
|
}
|
2018-02-26 08:35:33 +08:00
|
|
|
case dateDigitDot:
|
2019-02-23 08:50:43 +08:00
|
|
|
// This is the 2nd period
|
2018-01-12 05:21:04 +08:00
|
|
|
// 3.31.2014
|
2018-05-04 09:06:52 +08:00
|
|
|
// 08.21.71
|
|
|
|
// 2014.05
|
2019-02-23 08:50:43 +08:00
|
|
|
// 2018.09.30
|
2018-01-12 05:21:04 +08:00
|
|
|
if r == '.' {
|
2019-02-23 08:50:43 +08:00
|
|
|
if p.moi == 0 {
|
|
|
|
// 3.31.2014
|
|
|
|
p.daylen = i - p.dayi
|
|
|
|
p.yeari = i + 1
|
2023-12-12 14:45:58 +08:00
|
|
|
if !p.setDay() {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
2019-02-23 08:50:43 +08:00
|
|
|
p.stateDate = dateDigitDotDot
|
2023-12-12 14:46:44 +08:00
|
|
|
} else if p.dayi == 0 && p.yearlen == 0 {
|
|
|
|
// 23.07.2002
|
|
|
|
p.molen = i - p.moi
|
|
|
|
p.yeari = i + 1
|
|
|
|
if !p.setMonth() {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
|
|
|
p.stateDate = dateDigitDotDot
|
2019-02-23 08:50:43 +08:00
|
|
|
} else {
|
|
|
|
// 2018.09.30
|
2023-12-12 14:46:44 +08:00
|
|
|
// p.molen = 2
|
2019-02-23 08:50:43 +08:00
|
|
|
p.molen = i - p.moi
|
|
|
|
p.dayi = i + 1
|
2023-12-12 14:45:58 +08:00
|
|
|
if !p.setMonth() {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
2019-02-23 08:50:43 +08:00
|
|
|
p.stateDate = dateDigitDotDot
|
|
|
|
}
|
2018-01-12 05:21:04 +08:00
|
|
|
}
|
2023-12-15 14:47:31 +08:00
|
|
|
|
2018-02-26 08:35:33 +08:00
|
|
|
case dateDigitDotDot:
|
2023-12-15 14:47:31 +08:00
|
|
|
// dateYearDashDashT
|
|
|
|
// 2006.01.02T15:04:05Z07:00
|
|
|
|
// dateYearDashDashWs
|
|
|
|
// 2013.04.01 22:43:22
|
|
|
|
// dateYearDashDashOffset
|
|
|
|
// 2020.07.20+00:00
|
|
|
|
switch r {
|
|
|
|
case '+', '-':
|
|
|
|
p.offseti = i
|
|
|
|
p.daylen = i - p.dayi
|
|
|
|
p.stateDate = dateDigitDotDotOffset
|
|
|
|
if !p.setDay() {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
|
|
|
case ' ':
|
|
|
|
p.daylen = i - p.dayi
|
|
|
|
p.stateDate = dateDigitDotDotWs
|
|
|
|
p.stateTime = timeStart
|
|
|
|
if !p.setDay() {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
|
|
|
break iterRunes
|
|
|
|
case 'T':
|
|
|
|
p.daylen = i - p.dayi
|
|
|
|
p.stateDate = dateDigitDotDotT
|
|
|
|
p.stateTime = timeStart
|
|
|
|
if !p.setDay() {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
|
|
|
break iterRunes
|
|
|
|
}
|
|
|
|
|
|
|
|
case dateDigitDotDotT:
|
|
|
|
// dateYearDashDashT
|
|
|
|
// 2006-01-02T15:04:05Z07:00
|
|
|
|
// 2020-08-17T17:00:00:000+0100
|
|
|
|
|
|
|
|
case dateDigitDotDotOffset:
|
|
|
|
// 2020-07-20+00:00
|
|
|
|
switch r {
|
|
|
|
case ':':
|
|
|
|
p.set(p.offseti, "-07:00")
|
|
|
|
}
|
|
|
|
|
2018-02-26 08:35:33 +08:00
|
|
|
case dateAlpha:
|
2023-12-16 08:42:07 +08:00
|
|
|
// dateAlphaWs
|
2017-07-14 00:11:41 +08:00
|
|
|
// Mon Jan _2 15:04:05 2006
|
|
|
|
// Mon Jan _2 15:04:05 MST 2006
|
|
|
|
// Mon Jan 02 15:04:05 -0700 2006
|
2023-12-16 08:42:07 +08:00
|
|
|
// Mon Jan 02 15:04:05 2006 -0700
|
2017-07-14 00:11:41 +08:00
|
|
|
// Mon Aug 10 15:44:11 UTC+0100 2015
|
|
|
|
// Fri Jul 03 2015 18:04:07 GMT+0100 (GMT Daylight Time)
|
2023-12-16 08:42:07 +08:00
|
|
|
// dateAlphaWsDigit
|
2017-07-14 00:11:41 +08:00
|
|
|
// May 8, 2009 5:57:51 PM
|
2018-03-11 05:43:16 +08:00
|
|
|
// oct 1, 1970
|
2018-05-24 12:11:15 +08:00
|
|
|
// dateAlphaWsMonth
|
|
|
|
// April 8, 2009
|
2018-11-23 05:54:48 +08:00
|
|
|
// dateAlphaWsMore
|
|
|
|
// dateAlphaWsAtTime
|
|
|
|
// January 02, 2006 at 3:04pm MST-07
|
|
|
|
//
|
2018-06-09 09:01:07 +08:00
|
|
|
// dateAlphaPeriodWsDigit
|
|
|
|
// oct. 1, 1970
|
2023-12-13 12:24:17 +08:00
|
|
|
// dateAlphaSlash
|
|
|
|
// dateAlphaSlashDigit
|
|
|
|
// dateAlphaSlashDigitSlash
|
|
|
|
// Oct/ 7/1970
|
|
|
|
// Oct/07/1970
|
|
|
|
// February/ 7/1970
|
|
|
|
// February/07/1970
|
|
|
|
//
|
2018-02-26 08:35:33 +08:00
|
|
|
// dateWeekdayComma
|
2018-02-10 10:08:04 +08:00
|
|
|
// Monday, 02 Jan 2006 15:04:05 MST
|
2018-03-11 03:50:19 +08:00
|
|
|
// Monday, 02-Jan-06 15:04:05 MST
|
|
|
|
// Monday, 02 Jan 2006 15:04:05 -0700
|
|
|
|
// Monday, 02 Jan 2006 15:04:05 +0100
|
2018-02-26 08:35:33 +08:00
|
|
|
// dateWeekdayAbbrevComma
|
2017-07-14 00:11:41 +08:00
|
|
|
// Mon, 02 Jan 2006 15:04:05 MST
|
2018-03-11 03:50:19 +08:00
|
|
|
// Mon, 02 Jan 2006 15:04:05 -0700
|
|
|
|
// Thu, 13 Jul 2017 08:58:40 +0100
|
|
|
|
// Tue, 11 Jul 2017 16:28:13 +0200 (CEST)
|
2018-03-12 08:39:59 +08:00
|
|
|
// Mon, 02-Jan-06 15:04:05 MST
|
2014-05-07 12:15:43 +08:00
|
|
|
switch {
|
|
|
|
case r == ' ':
|
2018-11-23 05:54:48 +08:00
|
|
|
// X
|
|
|
|
// April 8, 2009
|
2018-06-06 09:01:57 +08:00
|
|
|
if i > 3 {
|
2018-11-23 05:54:48 +08:00
|
|
|
// Check to see if the alpha is name of month? or Day?
|
2023-12-12 14:45:58 +08:00
|
|
|
month := strings.ToLower(p.datestr[0:i])
|
2018-11-23 05:54:48 +08:00
|
|
|
if isMonthFull(month) {
|
2023-12-12 14:45:58 +08:00
|
|
|
p.moi = 0
|
|
|
|
p.molen = i
|
2018-11-23 05:54:48 +08:00
|
|
|
p.fullMonth = month
|
|
|
|
// len(" 31, 2018") = 9
|
2023-12-12 14:45:58 +08:00
|
|
|
if len(p.datestr[i:]) < 10 {
|
2018-11-23 05:54:48 +08:00
|
|
|
// April 8, 2009
|
|
|
|
p.stateDate = dateAlphaWsMonth
|
|
|
|
} else {
|
|
|
|
p.stateDate = dateAlphaWsMore
|
2018-05-24 12:11:15 +08:00
|
|
|
}
|
2018-11-23 05:54:48 +08:00
|
|
|
p.dayi = i + 1
|
|
|
|
break
|
2018-05-24 12:11:15 +08:00
|
|
|
}
|
2018-11-23 05:54:48 +08:00
|
|
|
|
2018-05-24 12:11:15 +08:00
|
|
|
} else {
|
2018-11-23 05:54:48 +08:00
|
|
|
// This is possibly ambiguous? May will parse as either though.
|
|
|
|
// So, it could return in-correct format.
|
2020-05-06 10:10:21 +08:00
|
|
|
// dateAlphaWs
|
|
|
|
// May 05, 2005, 05:05:05
|
|
|
|
// May 05 2005, 05:05:05
|
|
|
|
// Jul 05, 2005, 05:05:05
|
|
|
|
// May 8 17:57:51 2009
|
|
|
|
// May 8 17:57:51 2009
|
|
|
|
// skip & return to dateStart
|
|
|
|
// Tue 05 May 2020, 05:05:05
|
|
|
|
// Mon Jan 2 15:04:05 2006
|
|
|
|
|
2023-12-12 14:45:58 +08:00
|
|
|
maybeDay := strings.ToLower(p.datestr[0:i])
|
2020-05-06 10:10:21 +08:00
|
|
|
if isDay(maybeDay) {
|
|
|
|
// using skip throws off indices used by other code; saner to restart
|
2023-12-12 14:45:58 +08:00
|
|
|
return parseTime(p.datestr[i+1:], loc)
|
2020-05-06 10:10:21 +08:00
|
|
|
}
|
2018-05-24 12:11:15 +08:00
|
|
|
p.stateDate = dateAlphaWs
|
2018-03-16 10:06:38 +08:00
|
|
|
}
|
2018-05-24 12:11:15 +08:00
|
|
|
|
2014-05-21 13:53:52 +08:00
|
|
|
case r == ',':
|
2018-11-23 05:54:48 +08:00
|
|
|
// Mon, 02 Jan 2006
|
2021-02-06 05:44:33 +08:00
|
|
|
|
2017-07-14 00:11:41 +08:00
|
|
|
if i == 3 {
|
2018-02-26 14:46:45 +08:00
|
|
|
p.stateDate = dateWeekdayAbbrevComma
|
|
|
|
p.set(0, "Mon")
|
2017-07-14 00:11:41 +08:00
|
|
|
} else {
|
2018-02-26 14:46:45 +08:00
|
|
|
p.stateDate = dateWeekdayComma
|
2018-03-02 10:36:46 +08:00
|
|
|
p.skip = i + 2
|
2018-03-11 03:50:19 +08:00
|
|
|
i++
|
2018-03-02 10:36:46 +08:00
|
|
|
// TODO: lets just make this "skip" as we don't need
|
|
|
|
// the mon, monday, they are all superfelous and not needed
|
|
|
|
// just lay down the skip, no need to fill and then skip
|
2016-05-04 04:55:11 +08:00
|
|
|
}
|
2018-06-09 09:01:07 +08:00
|
|
|
case r == '.':
|
2018-07-11 09:51:27 +08:00
|
|
|
// sept. 28, 2017
|
|
|
|
// jan. 28, 2017
|
2018-06-09 09:01:07 +08:00
|
|
|
p.stateDate = dateAlphaPeriodWsDigit
|
2018-07-11 09:51:27 +08:00
|
|
|
if i == 3 {
|
|
|
|
p.molen = i
|
|
|
|
p.set(0, "Jan")
|
|
|
|
} else if i == 4 {
|
|
|
|
// gross
|
2023-12-12 14:45:58 +08:00
|
|
|
newDatestr := p.datestr[0:i-1] + p.datestr[i:]
|
|
|
|
return parseTime(newDatestr, loc, opts...)
|
2018-07-11 09:51:27 +08:00
|
|
|
} else {
|
2023-12-12 14:45:58 +08:00
|
|
|
return p, unknownErr(datestr)
|
2018-07-11 09:51:27 +08:00
|
|
|
}
|
2023-12-13 12:24:17 +08:00
|
|
|
case r == '/':
|
|
|
|
// X
|
|
|
|
// Oct/ 7/1970
|
|
|
|
// Oct/07/1970
|
|
|
|
// X
|
|
|
|
// February/ 7/1970
|
|
|
|
// February/07/1970
|
|
|
|
// Must be a valid short or long month
|
|
|
|
if i == 3 {
|
|
|
|
p.moi = 0
|
|
|
|
p.molen = i - p.moi
|
|
|
|
p.set(p.moi, "Jan")
|
|
|
|
p.stateDate = dateAlphaSlash
|
|
|
|
} else {
|
|
|
|
possibleFullMonth := strings.ToLower(p.datestr[:i])
|
|
|
|
if i > 3 && isMonthFull(possibleFullMonth) {
|
|
|
|
p.moi = 0
|
|
|
|
p.molen = i - p.moi
|
|
|
|
p.fullMonth = possibleFullMonth
|
|
|
|
p.stateDate = dateAlphaSlash
|
|
|
|
} else {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
|
|
|
}
|
2017-07-14 22:02:22 +08:00
|
|
|
}
|
|
|
|
|
2018-03-02 10:36:46 +08:00
|
|
|
case dateAlphaWs:
|
|
|
|
// dateAlphaWsAlpha
|
|
|
|
// Mon Jan _2 15:04:05 2006
|
|
|
|
// Mon Jan _2 15:04:05 MST 2006
|
|
|
|
// Mon Jan 02 15:04:05 -0700 2006
|
2023-12-16 08:42:07 +08:00
|
|
|
// Mon Jan 02 15:04:05 2006 -0700
|
2018-03-02 10:36:46 +08:00
|
|
|
// Fri Jul 03 2015 18:04:07 GMT+0100 (GMT Daylight Time)
|
|
|
|
// Mon Aug 10 15:44:11 UTC+0100 2015
|
2020-05-06 10:10:21 +08:00
|
|
|
// dateAlphaWsDigit
|
|
|
|
// May 8, 2009 5:57:51 PM
|
|
|
|
// May 8 2009 5:57:51 PM
|
|
|
|
// May 8 17:57:51 2009
|
|
|
|
// May 8 17:57:51 2009
|
|
|
|
// May 08 17:57:51 2009
|
|
|
|
// oct 1, 1970
|
|
|
|
// oct 7, '70
|
2014-05-07 12:15:43 +08:00
|
|
|
switch {
|
|
|
|
case unicode.IsLetter(r):
|
2018-03-02 10:36:46 +08:00
|
|
|
p.set(0, "Mon")
|
|
|
|
p.stateDate = dateAlphaWsAlpha
|
|
|
|
p.set(i, "Jan")
|
2017-07-14 00:11:41 +08:00
|
|
|
case unicode.IsDigit(r):
|
2018-03-02 10:36:46 +08:00
|
|
|
p.set(0, "Jan")
|
|
|
|
p.stateDate = dateAlphaWsDigit
|
|
|
|
p.dayi = i
|
2014-05-21 13:53:52 +08:00
|
|
|
}
|
2016-05-04 04:02:30 +08:00
|
|
|
|
2018-03-02 10:36:46 +08:00
|
|
|
case dateAlphaWsDigit:
|
2018-03-11 05:43:16 +08:00
|
|
|
// May 8, 2009 5:57:51 PM
|
2018-11-23 05:54:48 +08:00
|
|
|
// May 8 2009 5:57:51 PM
|
2018-03-11 05:43:16 +08:00
|
|
|
// oct 1, 1970
|
|
|
|
// oct 7, '70
|
2018-06-09 09:01:07 +08:00
|
|
|
// oct. 7, 1970
|
2020-05-06 10:10:21 +08:00
|
|
|
// May 8 17:57:51 2009
|
|
|
|
// May 8 17:57:51 2009
|
|
|
|
// May 08 17:57:51 2009
|
2018-03-11 03:50:19 +08:00
|
|
|
if r == ',' {
|
2018-03-02 10:36:46 +08:00
|
|
|
p.daylen = i - p.dayi
|
2023-12-12 14:45:58 +08:00
|
|
|
if !p.setDay() {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
2018-11-23 05:54:48 +08:00
|
|
|
p.stateDate = dateAlphaWsDigitMore
|
|
|
|
} else if r == ' ' {
|
|
|
|
p.daylen = i - p.dayi
|
2023-12-12 14:45:58 +08:00
|
|
|
if !p.setDay() {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
2018-11-23 05:54:48 +08:00
|
|
|
p.yeari = i + 1
|
2020-05-06 10:10:21 +08:00
|
|
|
p.stateDate = dateAlphaWsDigitYearmaybe
|
|
|
|
p.stateTime = timeStart
|
2018-11-23 05:54:48 +08:00
|
|
|
} else if unicode.IsLetter(r) {
|
|
|
|
p.stateDate = dateAlphaWsMonthSuffix
|
|
|
|
i--
|
2018-02-26 08:35:33 +08:00
|
|
|
}
|
2020-05-06 10:10:21 +08:00
|
|
|
case dateAlphaWsDigitYearmaybe:
|
|
|
|
// x
|
|
|
|
// May 8 2009 5:57:51 PM
|
|
|
|
// May 8 17:57:51 2009
|
|
|
|
// May 8 17:57:51 2009
|
|
|
|
// May 08 17:57:51 2009
|
|
|
|
// Jul 03 2015 18:04:07 GMT+0100 (GMT Daylight Time)
|
|
|
|
if r == ':' {
|
|
|
|
// Guessed wrong; was not a year
|
|
|
|
i = i - 3
|
|
|
|
p.stateDate = dateAlphaWsDigit
|
|
|
|
p.yeari = 0
|
|
|
|
break iterRunes
|
|
|
|
} else if r == ' ' {
|
|
|
|
// must be year format, not 15:04
|
|
|
|
p.yearlen = i - p.yeari
|
2023-12-12 14:45:58 +08:00
|
|
|
if !p.setYear() {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
2020-05-06 10:10:21 +08:00
|
|
|
break iterRunes
|
|
|
|
}
|
2018-11-23 05:54:48 +08:00
|
|
|
case dateAlphaWsDigitMore:
|
2018-03-11 05:43:16 +08:00
|
|
|
// x
|
|
|
|
// May 8, 2009 5:57:51 PM
|
2018-11-23 05:54:48 +08:00
|
|
|
// May 05, 2005, 05:05:05
|
|
|
|
// May 05 2005, 05:05:05
|
2018-03-11 05:43:16 +08:00
|
|
|
// oct 1, 1970
|
|
|
|
// oct 7, '70
|
2018-03-11 03:50:19 +08:00
|
|
|
if r == ' ' {
|
2018-03-02 10:36:46 +08:00
|
|
|
p.yeari = i + 1
|
2018-11-23 05:54:48 +08:00
|
|
|
p.stateDate = dateAlphaWsDigitMoreWs
|
2018-02-26 08:35:33 +08:00
|
|
|
}
|
2018-11-23 05:54:48 +08:00
|
|
|
case dateAlphaWsDigitMoreWs:
|
2018-03-11 05:43:16 +08:00
|
|
|
// x
|
|
|
|
// May 8, 2009 5:57:51 PM
|
2018-11-23 05:54:48 +08:00
|
|
|
// May 05, 2005, 05:05:05
|
2018-03-11 05:43:16 +08:00
|
|
|
// oct 1, 1970
|
|
|
|
// oct 7, '70
|
|
|
|
switch r {
|
|
|
|
case '\'':
|
|
|
|
p.yeari = i + 1
|
2023-02-15 13:27:43 +08:00
|
|
|
case ' ':
|
|
|
|
fallthrough
|
|
|
|
case ',':
|
2018-11-23 05:54:48 +08:00
|
|
|
// x
|
|
|
|
// May 8, 2009 5:57:51 PM
|
|
|
|
// x
|
|
|
|
// May 8, 2009, 5:57:51 PM
|
|
|
|
p.stateDate = dateAlphaWsDigitMoreWsYear
|
2018-03-02 10:36:46 +08:00
|
|
|
p.yearlen = i - p.yeari
|
2023-12-12 14:45:58 +08:00
|
|
|
if !p.setYear() {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
2018-03-02 10:36:46 +08:00
|
|
|
p.stateTime = timeStart
|
2018-02-26 08:35:33 +08:00
|
|
|
break iterRunes
|
2018-02-08 23:03:45 +08:00
|
|
|
}
|
2017-07-12 23:22:34 +08:00
|
|
|
|
2018-06-09 09:01:07 +08:00
|
|
|
case dateAlphaWsMonth:
|
|
|
|
// April 8, 2009
|
2018-11-23 05:54:48 +08:00
|
|
|
// April 8 2009
|
|
|
|
switch r {
|
2023-02-15 13:27:43 +08:00
|
|
|
case ' ':
|
|
|
|
fallthrough
|
|
|
|
case ',':
|
2018-11-23 05:54:48 +08:00
|
|
|
// x
|
|
|
|
// June 8, 2009
|
|
|
|
// x
|
|
|
|
// June 8 2009
|
|
|
|
if p.daylen == 0 {
|
|
|
|
p.daylen = i - p.dayi
|
2023-12-12 14:45:58 +08:00
|
|
|
if !p.setDay() {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
2018-06-09 09:01:07 +08:00
|
|
|
}
|
2018-11-23 05:54:48 +08:00
|
|
|
case 's', 'S', 'r', 'R', 't', 'T', 'n', 'N':
|
|
|
|
// st, rd, nd, st
|
|
|
|
i--
|
|
|
|
p.stateDate = dateAlphaWsMonthSuffix
|
|
|
|
default:
|
|
|
|
if p.daylen > 0 && p.yeari == 0 {
|
|
|
|
p.yeari = i
|
|
|
|
}
|
|
|
|
}
|
|
|
|
case dateAlphaWsMonthMore:
|
|
|
|
// X
|
|
|
|
// January 02, 2006, 15:04:05
|
|
|
|
// January 02 2006, 15:04:05
|
|
|
|
// January 02, 2006 15:04:05
|
|
|
|
// January 02 2006 15:04:05
|
|
|
|
switch r {
|
|
|
|
case ',':
|
|
|
|
p.yearlen = i - p.yeari
|
2023-12-12 14:45:58 +08:00
|
|
|
if !p.setYear() {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
2018-11-23 05:54:48 +08:00
|
|
|
p.stateTime = timeStart
|
|
|
|
i++
|
|
|
|
break iterRunes
|
|
|
|
case ' ':
|
|
|
|
p.yearlen = i - p.yeari
|
2023-12-12 14:45:58 +08:00
|
|
|
if !p.setYear() {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
2018-11-23 05:54:48 +08:00
|
|
|
p.stateTime = timeStart
|
|
|
|
break iterRunes
|
|
|
|
}
|
|
|
|
case dateAlphaWsMonthSuffix:
|
|
|
|
// x
|
|
|
|
// April 8th, 2009
|
|
|
|
// April 8th 2009
|
|
|
|
switch r {
|
|
|
|
case 't', 'T':
|
2019-04-26 07:42:47 +08:00
|
|
|
if p.nextIs(i, 'h') || p.nextIs(i, 'H') {
|
2023-12-12 14:45:58 +08:00
|
|
|
if len(p.datestr) > i+2 {
|
2019-08-08 11:12:45 +08:00
|
|
|
return parseTime(fmt.Sprintf("%s%s", p.datestr[0:i], p.datestr[i+2:]), loc, opts...)
|
2018-11-23 05:54:48 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
case 'n', 'N':
|
|
|
|
if p.nextIs(i, 'd') || p.nextIs(i, 'D') {
|
2023-12-12 14:45:58 +08:00
|
|
|
if len(p.datestr) > i+2 {
|
2019-08-08 11:12:45 +08:00
|
|
|
return parseTime(fmt.Sprintf("%s%s", p.datestr[0:i], p.datestr[i+2:]), loc, opts...)
|
2018-11-23 05:54:48 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
case 's', 'S':
|
|
|
|
if p.nextIs(i, 't') || p.nextIs(i, 'T') {
|
2023-12-12 14:45:58 +08:00
|
|
|
if len(p.datestr) > i+2 {
|
2019-08-08 11:12:45 +08:00
|
|
|
return parseTime(fmt.Sprintf("%s%s", p.datestr[0:i], p.datestr[i+2:]), loc, opts...)
|
2018-11-23 05:54:48 +08:00
|
|
|
}
|
|
|
|
}
|
2018-11-24 01:12:28 +08:00
|
|
|
case 'r', 'R':
|
|
|
|
if p.nextIs(i, 'd') || p.nextIs(i, 'D') {
|
2023-12-12 14:45:58 +08:00
|
|
|
if len(p.datestr) > i+2 {
|
2019-08-08 11:12:45 +08:00
|
|
|
return parseTime(fmt.Sprintf("%s%s", p.datestr[0:i], p.datestr[i+2:]), loc, opts...)
|
2018-11-24 01:12:28 +08:00
|
|
|
}
|
|
|
|
}
|
2018-11-23 05:54:48 +08:00
|
|
|
}
|
|
|
|
case dateAlphaWsMore:
|
|
|
|
// January 02, 2006, 15:04:05
|
|
|
|
// January 02 2006, 15:04:05
|
|
|
|
// January 2nd, 2006, 15:04:05
|
|
|
|
// January 2nd 2006, 15:04:05
|
|
|
|
// September 17, 2012 at 5:00pm UTC-05
|
|
|
|
switch {
|
|
|
|
case r == ',':
|
|
|
|
// x
|
|
|
|
// January 02, 2006, 15:04:05
|
|
|
|
if p.nextIs(i, ' ') {
|
|
|
|
p.daylen = i - p.dayi
|
2023-12-12 14:45:58 +08:00
|
|
|
if !p.setDay() {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
2018-11-23 05:54:48 +08:00
|
|
|
p.yeari = i + 2
|
|
|
|
p.stateDate = dateAlphaWsMonthMore
|
|
|
|
i++
|
|
|
|
}
|
|
|
|
|
|
|
|
case r == ' ':
|
|
|
|
// x
|
|
|
|
// January 02 2006, 15:04:05
|
|
|
|
p.daylen = i - p.dayi
|
2023-12-12 14:45:58 +08:00
|
|
|
if !p.setDay() {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
2018-11-23 05:54:48 +08:00
|
|
|
p.yeari = i + 1
|
|
|
|
p.stateDate = dateAlphaWsMonthMore
|
|
|
|
case unicode.IsDigit(r):
|
|
|
|
// XX
|
|
|
|
// January 02, 2006, 15:04:05
|
|
|
|
continue
|
|
|
|
case unicode.IsLetter(r):
|
|
|
|
// X
|
|
|
|
// January 2nd, 2006, 15:04:05
|
|
|
|
p.daylen = i - p.dayi
|
2023-12-12 14:45:58 +08:00
|
|
|
if !p.setDay() {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
2018-11-23 05:54:48 +08:00
|
|
|
p.stateDate = dateAlphaWsMonthSuffix
|
|
|
|
i--
|
2018-06-09 09:01:07 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
case dateAlphaPeriodWsDigit:
|
|
|
|
// oct. 7, '70
|
|
|
|
switch {
|
|
|
|
case r == ' ':
|
|
|
|
// continue
|
|
|
|
case unicode.IsDigit(r):
|
|
|
|
p.stateDate = dateAlphaWsDigit
|
|
|
|
p.dayi = i
|
|
|
|
default:
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
2023-12-13 12:24:17 +08:00
|
|
|
|
|
|
|
case dateAlphaSlash:
|
|
|
|
// Oct/ 7/1970
|
|
|
|
// February/07/1970
|
|
|
|
switch {
|
|
|
|
case r == ' ':
|
|
|
|
// continue
|
|
|
|
case unicode.IsDigit(r):
|
|
|
|
p.stateDate = dateAlphaSlashDigit
|
|
|
|
p.dayi = i
|
|
|
|
default:
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
|
|
|
|
|
|
|
case dateAlphaSlashDigit:
|
|
|
|
// dateAlphaSlash:
|
|
|
|
// dateAlphaSlashDigit:
|
|
|
|
// dateAlphaSlashDigitSlash:
|
|
|
|
// Oct/ 7/1970
|
|
|
|
// Oct/07/1970
|
|
|
|
// February/ 7/1970
|
|
|
|
// February/07/1970
|
|
|
|
switch {
|
|
|
|
case r == '/':
|
|
|
|
p.yeari = i + 1
|
|
|
|
p.daylen = i - p.dayi
|
|
|
|
if !p.setDay() {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
|
|
|
p.stateDate = dateAlphaSlashDigitSlash
|
|
|
|
case unicode.IsDigit(r):
|
|
|
|
// continue
|
|
|
|
default:
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
|
|
|
|
|
|
|
case dateAlphaSlashDigitSlash:
|
|
|
|
switch {
|
|
|
|
case unicode.IsDigit(r):
|
|
|
|
// continue
|
|
|
|
case r == ' ':
|
|
|
|
p.stateTime = timeStart
|
|
|
|
break iterRunes
|
|
|
|
default:
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
|
|
|
|
2018-06-09 09:01:07 +08:00
|
|
|
case dateWeekdayComma:
|
|
|
|
// Monday, 02 Jan 2006 15:04:05 MST
|
|
|
|
// Monday, 02 Jan 2006 15:04:05 -0700
|
|
|
|
// Monday, 02 Jan 2006 15:04:05 +0100
|
|
|
|
// Monday, 02-Jan-06 15:04:05 MST
|
|
|
|
if p.dayi == 0 {
|
|
|
|
p.dayi = i
|
|
|
|
}
|
|
|
|
switch r {
|
2023-02-15 13:27:43 +08:00
|
|
|
case ' ':
|
|
|
|
fallthrough
|
|
|
|
case '-':
|
2018-06-09 09:01:07 +08:00
|
|
|
if p.moi == 0 {
|
|
|
|
p.moi = i + 1
|
|
|
|
p.daylen = i - p.dayi
|
2023-12-12 14:45:58 +08:00
|
|
|
if !p.setDay() {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
2018-06-09 09:01:07 +08:00
|
|
|
} else if p.yeari == 0 {
|
|
|
|
p.yeari = i + 1
|
|
|
|
p.molen = i - p.moi
|
|
|
|
p.set(p.moi, "Jan")
|
|
|
|
} else {
|
|
|
|
p.stateTime = timeStart
|
|
|
|
break iterRunes
|
|
|
|
}
|
|
|
|
}
|
|
|
|
case dateWeekdayAbbrevComma:
|
|
|
|
// Mon, 02 Jan 2006 15:04:05 MST
|
|
|
|
// Mon, 02 Jan 2006 15:04:05 -0700
|
|
|
|
// Thu, 13 Jul 2017 08:58:40 +0100
|
|
|
|
// Thu, 4 Jan 2018 17:53:36 +0000
|
|
|
|
// Tue, 11 Jul 2017 16:28:13 +0200 (CEST)
|
|
|
|
// Mon, 02-Jan-06 15:04:05 MST
|
2023-02-15 20:37:23 +08:00
|
|
|
var offset int
|
2018-06-09 09:01:07 +08:00
|
|
|
switch r {
|
2023-02-15 13:27:43 +08:00
|
|
|
case ' ':
|
2023-12-12 14:45:58 +08:00
|
|
|
for i+1 < len(p.datestr) && p.datestr[i+1] == ' ' {
|
2023-02-15 13:27:43 +08:00
|
|
|
i++
|
2023-02-15 20:37:23 +08:00
|
|
|
offset++
|
2023-02-15 13:27:43 +08:00
|
|
|
}
|
|
|
|
fallthrough
|
|
|
|
case '-':
|
2018-06-09 09:01:07 +08:00
|
|
|
if p.dayi == 0 {
|
|
|
|
p.dayi = i + 1
|
|
|
|
} else if p.moi == 0 {
|
|
|
|
p.daylen = i - p.dayi
|
2023-12-12 14:45:58 +08:00
|
|
|
if !p.setDay() {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
2018-06-09 09:01:07 +08:00
|
|
|
p.moi = i + 1
|
|
|
|
} else if p.yeari == 0 {
|
2023-02-15 20:37:23 +08:00
|
|
|
p.molen = i - p.moi - offset
|
2018-06-09 09:01:07 +08:00
|
|
|
p.set(p.moi, "Jan")
|
|
|
|
p.yeari = i + 1
|
|
|
|
} else {
|
2023-02-15 20:37:23 +08:00
|
|
|
p.yearlen = i - p.yeari - offset
|
2023-12-12 14:45:58 +08:00
|
|
|
if !p.setYear() {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
2018-06-09 09:01:07 +08:00
|
|
|
p.stateTime = timeStart
|
|
|
|
break iterRunes
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-05-07 12:15:43 +08:00
|
|
|
default:
|
|
|
|
break iterRunes
|
2014-04-21 10:56:17 +08:00
|
|
|
}
|
2014-05-07 12:15:43 +08:00
|
|
|
}
|
2023-12-12 14:45:58 +08:00
|
|
|
if !p.coalesceDate(i) {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
2018-02-26 14:46:45 +08:00
|
|
|
if p.stateTime == timeStart {
|
|
|
|
// increment first one, since the i++ occurs at end of loop
|
2018-03-11 05:43:16 +08:00
|
|
|
if i < len(p.datestr) {
|
|
|
|
i++
|
|
|
|
}
|
2018-03-12 08:39:59 +08:00
|
|
|
// ensure we skip any whitespace prefix
|
2023-12-12 14:45:58 +08:00
|
|
|
for ; i < len(p.datestr); i++ {
|
|
|
|
r := rune(p.datestr[i])
|
2018-03-12 08:39:59 +08:00
|
|
|
if r != ' ' {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
2018-02-26 14:46:45 +08:00
|
|
|
|
|
|
|
iterTimeRunes:
|
2023-12-12 14:45:58 +08:00
|
|
|
for ; i < len(p.datestr); i++ {
|
|
|
|
r := rune(p.datestr[i])
|
2018-02-26 14:46:45 +08:00
|
|
|
|
2021-02-06 07:19:04 +08:00
|
|
|
// gou.Debugf("i=%d r=%s state=%d iterTimeRunes %s %s", i, string(r), p.stateTime, p.ds(), p.ts())
|
2018-03-12 05:08:21 +08:00
|
|
|
|
2018-02-26 14:46:45 +08:00
|
|
|
switch p.stateTime {
|
|
|
|
case timeStart:
|
|
|
|
// 22:43:22
|
2018-03-11 03:50:19 +08:00
|
|
|
// 22:43
|
2018-02-26 14:46:45 +08:00
|
|
|
// timeComma
|
|
|
|
// 08:20:13,787
|
|
|
|
// timeWs
|
|
|
|
// 05:24:37 PM
|
|
|
|
// 06:20:00 UTC
|
2018-11-23 05:54:48 +08:00
|
|
|
// 06:20:00 UTC-05
|
2018-02-26 14:46:45 +08:00
|
|
|
// 00:12:00 +0000 UTC
|
2018-04-18 05:16:01 +08:00
|
|
|
// 22:18:00 +0000 UTC m=+0.000000001
|
2018-02-26 14:46:45 +08:00
|
|
|
// 15:04:05 -0700
|
|
|
|
// 15:04:05 -07:00
|
2018-03-02 10:36:46 +08:00
|
|
|
// 15:04:05 2008
|
2018-02-26 14:46:45 +08:00
|
|
|
// timeOffset
|
|
|
|
// 03:21:51+00:00
|
2018-02-28 12:05:14 +08:00
|
|
|
// 19:55:00+0100
|
2018-02-26 14:46:45 +08:00
|
|
|
// timePeriod
|
|
|
|
// 17:24:37.3186369
|
|
|
|
// 00:07:31.945167
|
|
|
|
// 18:31:59.257000000
|
|
|
|
// 00:00:00.000
|
2023-12-12 14:45:58 +08:00
|
|
|
// (and all variants that can follow the seconds portion of a time format, same as above)
|
2018-02-26 14:46:45 +08:00
|
|
|
if p.houri == 0 {
|
|
|
|
p.houri = i
|
|
|
|
}
|
|
|
|
switch r {
|
|
|
|
case ',':
|
2018-03-11 05:05:55 +08:00
|
|
|
// hm, lets just swap out comma for period. for some reason go
|
|
|
|
// won't parse it.
|
|
|
|
// 2014-05-11 08:20:13,787
|
|
|
|
ds := []byte(p.datestr)
|
|
|
|
ds[i] = '.'
|
2019-08-08 11:12:45 +08:00
|
|
|
return parseTime(string(ds), loc, opts...)
|
2018-02-26 14:46:45 +08:00
|
|
|
case '-', '+':
|
|
|
|
// 03:21:51+00:00
|
|
|
|
p.stateTime = timeOffset
|
2018-04-06 21:57:28 +08:00
|
|
|
if p.seci == 0 {
|
|
|
|
// 22:18+0530
|
|
|
|
p.minlen = i - p.mini
|
|
|
|
} else {
|
2021-02-06 07:19:04 +08:00
|
|
|
if p.seclen == 0 {
|
|
|
|
p.seclen = i - p.seci
|
|
|
|
}
|
|
|
|
if p.msi > 0 && p.mslen == 0 {
|
|
|
|
p.mslen = i - p.msi
|
|
|
|
}
|
2018-04-06 21:57:28 +08:00
|
|
|
}
|
2018-02-26 14:46:45 +08:00
|
|
|
p.offseti = i
|
|
|
|
case '.':
|
|
|
|
p.stateTime = timePeriod
|
|
|
|
p.seclen = i - p.seci
|
|
|
|
p.msi = i + 1
|
|
|
|
case 'Z':
|
|
|
|
p.stateTime = timeZ
|
|
|
|
if p.seci == 0 {
|
|
|
|
p.minlen = i - p.mini
|
|
|
|
} else {
|
|
|
|
p.seclen = i - p.seci
|
|
|
|
}
|
2020-07-03 18:21:00 +08:00
|
|
|
// (Z)ulu time
|
|
|
|
p.loc = time.UTC
|
2023-12-12 14:45:58 +08:00
|
|
|
endPos := i + 1
|
|
|
|
if endPos > p.formatSetLen {
|
|
|
|
p.formatSetLen = endPos
|
|
|
|
}
|
|
|
|
case 'a', 'A', 'p', 'P':
|
|
|
|
if (r == 'a' || r == 'A') && (p.nextIs(i, 't') || p.nextIs(i, 'T')) {
|
2018-11-23 05:54:48 +08:00
|
|
|
// x
|
|
|
|
// September 17, 2012 at 5:00pm UTC-05
|
2023-12-12 14:45:58 +08:00
|
|
|
i++ // skip 't'
|
2018-11-23 05:54:48 +08:00
|
|
|
if p.nextIs(i, ' ') {
|
|
|
|
// x
|
|
|
|
// September 17, 2012 at 5:00pm UTC-05
|
2023-12-12 14:45:58 +08:00
|
|
|
i++ // skip ' '
|
2018-11-23 05:54:48 +08:00
|
|
|
p.houri = 0 // reset hour
|
|
|
|
}
|
|
|
|
} else {
|
2023-12-12 14:45:58 +08:00
|
|
|
// Could be AM/PM
|
|
|
|
isLower := r == 'a' || r == 'p'
|
2023-12-13 08:42:09 +08:00
|
|
|
isTwoLetterWord := ((i+2) == len(p.datestr) || p.nextIs(i+1, ' '))
|
2018-11-23 05:54:48 +08:00
|
|
|
switch {
|
2023-12-13 08:42:09 +08:00
|
|
|
case isLower && p.nextIs(i, 'm') && isTwoLetterWord && !p.parsedAMPM:
|
2018-11-23 05:54:48 +08:00
|
|
|
p.coalesceTime(i)
|
2023-12-12 14:45:58 +08:00
|
|
|
p.set(i, "pm")
|
2023-12-13 08:42:09 +08:00
|
|
|
p.parsedAMPM = true
|
2023-12-12 14:45:58 +08:00
|
|
|
// skip 'm'
|
|
|
|
i++
|
2023-12-13 08:42:09 +08:00
|
|
|
case !isLower && p.nextIs(i, 'M') && isTwoLetterWord && !p.parsedAMPM:
|
2018-11-23 05:54:48 +08:00
|
|
|
p.coalesceTime(i)
|
|
|
|
p.set(i, "PM")
|
2023-12-13 08:42:09 +08:00
|
|
|
p.parsedAMPM = true
|
2023-12-12 14:45:58 +08:00
|
|
|
// skip 'M'
|
|
|
|
i++
|
2023-12-13 08:42:09 +08:00
|
|
|
default:
|
|
|
|
return p, unexpectedTail(p.datestr[i:])
|
2018-11-23 05:54:48 +08:00
|
|
|
}
|
|
|
|
}
|
2018-02-26 14:46:45 +08:00
|
|
|
case ' ':
|
2018-03-11 03:50:19 +08:00
|
|
|
p.coalesceTime(i)
|
2018-02-26 14:46:45 +08:00
|
|
|
p.stateTime = timeWs
|
|
|
|
case ':':
|
|
|
|
if p.mini == 0 {
|
|
|
|
p.mini = i + 1
|
|
|
|
p.hourlen = i - p.houri
|
|
|
|
} else if p.seci == 0 {
|
|
|
|
p.seci = i + 1
|
|
|
|
p.minlen = i - p.mini
|
2021-02-06 07:19:04 +08:00
|
|
|
} else if p.seci > 0 {
|
|
|
|
// 18:31:59:257 ms uses colon, wtf
|
|
|
|
p.seclen = i - p.seci
|
|
|
|
p.set(p.seci, "05")
|
|
|
|
p.msi = i + 1
|
|
|
|
|
|
|
|
// gross, gross, gross. manipulating the datestr is horrible.
|
|
|
|
// https://github.com/araddon/dateparse/issues/117
|
|
|
|
// Could not get the parsing to work using golang time.Parse() without
|
|
|
|
// replacing that colon with period.
|
|
|
|
p.set(i, ".")
|
2023-12-12 14:45:58 +08:00
|
|
|
newDatestr := p.datestr[0:i] + "." + p.datestr[i+1:]
|
|
|
|
p.datestr = newDatestr
|
|
|
|
p.stateTime = timePeriod
|
2018-02-26 14:46:45 +08:00
|
|
|
}
|
2018-02-26 08:35:33 +08:00
|
|
|
}
|
2018-02-26 14:46:45 +08:00
|
|
|
case timeOffset:
|
2018-02-28 12:05:14 +08:00
|
|
|
// 19:55:00+0100
|
2018-02-26 14:46:45 +08:00
|
|
|
// timeOffsetColon
|
2018-02-28 12:05:14 +08:00
|
|
|
// 15:04:05+07:00
|
|
|
|
// 15:04:05-07:00
|
2018-02-26 14:46:45 +08:00
|
|
|
if r == ':' {
|
|
|
|
p.stateTime = timeOffsetColon
|
2018-02-26 08:35:33 +08:00
|
|
|
}
|
2018-02-26 14:46:45 +08:00
|
|
|
case timeWs:
|
2018-03-02 10:36:46 +08:00
|
|
|
// timeWsAlpha
|
2018-02-26 14:46:45 +08:00
|
|
|
// 06:20:00 UTC
|
2018-11-23 05:54:48 +08:00
|
|
|
// 06:20:00 UTC-05
|
2018-03-02 10:36:46 +08:00
|
|
|
// 15:44:11 UTC+0100 2015
|
|
|
|
// 18:04:07 GMT+0100 (GMT Daylight Time)
|
2018-03-12 08:39:59 +08:00
|
|
|
// 17:57:51 MST 2009
|
2018-02-26 14:46:45 +08:00
|
|
|
// timeWsAMPMMaybe
|
|
|
|
// 05:24:37 PM
|
|
|
|
// timeWsOffset
|
|
|
|
// 15:04:05 -0700
|
2018-03-12 08:39:59 +08:00
|
|
|
// 00:12:00 +0000 UTC
|
2018-02-26 14:46:45 +08:00
|
|
|
// timeWsOffsetColon
|
|
|
|
// 15:04:05 -07:00
|
2018-03-12 05:08:21 +08:00
|
|
|
// 17:57:51 -0700 2009
|
2018-02-26 14:46:45 +08:00
|
|
|
// timeWsOffsetColonAlpha
|
|
|
|
// 00:12:00 +00:00 UTC
|
2018-03-02 10:36:46 +08:00
|
|
|
// timeWsYear
|
2023-12-16 08:42:07 +08:00
|
|
|
// 00:12:00 2008
|
|
|
|
// timeWsYearOffset
|
|
|
|
// 00:12:00 2008 -0700
|
2018-02-26 14:46:45 +08:00
|
|
|
// timeZ
|
|
|
|
// 15:04:05.99Z
|
2018-02-26 08:35:33 +08:00
|
|
|
switch r {
|
2023-12-12 14:45:58 +08:00
|
|
|
case 'a', 'p', 'A', 'P':
|
2018-02-26 14:46:45 +08:00
|
|
|
// Could be AM/PM or could be PST or similar
|
2018-03-02 10:36:46 +08:00
|
|
|
p.tzi = i
|
2018-02-26 14:46:45 +08:00
|
|
|
p.stateTime = timeWsAMPMMaybe
|
|
|
|
case '+', '-':
|
2018-02-28 12:05:14 +08:00
|
|
|
p.offseti = i
|
2018-02-26 14:46:45 +08:00
|
|
|
p.stateTime = timeWsOffset
|
|
|
|
default:
|
|
|
|
if unicode.IsLetter(r) {
|
|
|
|
// 06:20:00 UTC
|
2018-11-23 05:54:48 +08:00
|
|
|
// 06:20:00 UTC-05
|
2018-03-02 10:36:46 +08:00
|
|
|
// 15:44:11 UTC+0100 2015
|
2018-03-12 08:39:59 +08:00
|
|
|
// 17:57:51 MST 2009
|
2018-03-02 10:36:46 +08:00
|
|
|
p.tzi = i
|
2018-02-26 14:46:45 +08:00
|
|
|
p.stateTime = timeWsAlpha
|
2018-03-02 10:36:46 +08:00
|
|
|
} else if unicode.IsDigit(r) {
|
|
|
|
// 00:12:00 2008
|
|
|
|
p.stateTime = timeWsYear
|
|
|
|
p.yeari = i
|
2018-02-26 14:46:45 +08:00
|
|
|
}
|
2018-02-26 08:35:33 +08:00
|
|
|
}
|
2023-12-16 08:42:07 +08:00
|
|
|
case timeWsYear:
|
|
|
|
// timeWsYearOffset
|
|
|
|
// 00:12:00 2008 -0700
|
|
|
|
switch r {
|
|
|
|
case ' ':
|
|
|
|
p.yearlen = i - p.yeari
|
|
|
|
if !p.setYear() {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
|
|
|
case '+', '-':
|
|
|
|
p.offseti = i
|
|
|
|
p.stateTime = timeWsYearOffset
|
|
|
|
default:
|
|
|
|
if !unicode.IsDigit(r) {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
|
|
|
}
|
2018-03-02 10:36:46 +08:00
|
|
|
case timeWsAlpha:
|
|
|
|
// 06:20:00 UTC
|
2018-11-23 05:54:48 +08:00
|
|
|
// 06:20:00 UTC-05
|
2023-12-14 14:58:04 +08:00
|
|
|
// 06:20:00 (EST)
|
2018-03-12 08:39:59 +08:00
|
|
|
// timeWsAlphaWs
|
|
|
|
// 17:57:51 MST 2009
|
2018-03-02 10:36:46 +08:00
|
|
|
// timeWsAlphaZoneOffset
|
|
|
|
// timeWsAlphaZoneOffsetWs
|
|
|
|
// timeWsAlphaZoneOffsetWsExtra
|
|
|
|
// 18:04:07 GMT+0100 (GMT Daylight Time)
|
|
|
|
// timeWsAlphaZoneOffsetWsYear
|
|
|
|
// 15:44:11 UTC+0100 2015
|
|
|
|
switch r {
|
|
|
|
case '+', '-':
|
2023-12-15 13:57:42 +08:00
|
|
|
tzNameLower := strings.ToLower(p.datestr[p.tzi:i])
|
|
|
|
if tzNameLower == "gmt" || tzNameLower == "utc" {
|
|
|
|
// This is a special form where the actual timezone isn't UTC, but is rather
|
|
|
|
// specifying that the correct offset is a specified numeric offset from UTC:
|
|
|
|
// 06:20:00 UTC-05
|
|
|
|
// 06:20:00 GMT+02
|
2023-02-16 06:39:34 +08:00
|
|
|
p.tzi = 0
|
|
|
|
p.tzlen = 0
|
|
|
|
} else {
|
|
|
|
p.tzlen = i - p.tzi
|
|
|
|
}
|
2018-03-02 10:36:46 +08:00
|
|
|
if p.tzlen == 4 {
|
2018-03-11 03:50:19 +08:00
|
|
|
p.set(p.tzi, " MST")
|
2018-03-02 10:36:46 +08:00
|
|
|
} else if p.tzlen == 3 {
|
|
|
|
p.set(p.tzi, "MST")
|
|
|
|
}
|
|
|
|
p.stateTime = timeWsAlphaZoneOffset
|
|
|
|
p.offseti = i
|
2023-12-14 14:58:04 +08:00
|
|
|
case ' ', ')':
|
2018-03-12 08:39:59 +08:00
|
|
|
// 17:57:51 MST 2009
|
2021-02-07 07:23:24 +08:00
|
|
|
// 17:57:51 MST
|
2023-12-14 14:58:04 +08:00
|
|
|
// 06:20:00 (EST)
|
2018-07-30 01:48:19 +08:00
|
|
|
p.tzlen = i - p.tzi
|
|
|
|
if p.tzlen == 4 {
|
|
|
|
p.set(p.tzi, " MST")
|
|
|
|
} else if p.tzlen == 3 {
|
|
|
|
p.set(p.tzi, "MST")
|
|
|
|
}
|
2023-12-14 14:58:04 +08:00
|
|
|
if r == ' ' {
|
|
|
|
p.stateTime = timeWsAlphaWs
|
|
|
|
p.yeari = i + 1
|
|
|
|
} else {
|
|
|
|
// 06:20:00 (EST)
|
|
|
|
// This must be the end of the datetime or the format is unknown
|
|
|
|
if i+1 == len(p.datestr) {
|
|
|
|
p.stateTime = timeWsAlphaRParen
|
|
|
|
} else {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
|
|
|
}
|
2018-03-02 10:36:46 +08:00
|
|
|
}
|
2018-03-12 08:39:59 +08:00
|
|
|
case timeWsAlphaWs:
|
|
|
|
// 17:57:51 MST 2009
|
|
|
|
|
2018-03-02 10:36:46 +08:00
|
|
|
case timeWsAlphaZoneOffset:
|
2018-11-23 05:54:48 +08:00
|
|
|
// 06:20:00 UTC-05
|
2018-03-02 10:36:46 +08:00
|
|
|
// timeWsAlphaZoneOffset
|
|
|
|
// timeWsAlphaZoneOffsetWs
|
|
|
|
// timeWsAlphaZoneOffsetWsExtra
|
|
|
|
// 18:04:07 GMT+0100 (GMT Daylight Time)
|
|
|
|
// timeWsAlphaZoneOffsetWsYear
|
|
|
|
// 15:44:11 UTC+0100 2015
|
|
|
|
switch r {
|
|
|
|
case ' ':
|
|
|
|
p.set(p.offseti, "-0700")
|
2020-05-06 10:10:21 +08:00
|
|
|
if p.yeari == 0 {
|
|
|
|
p.yeari = i + 1
|
|
|
|
}
|
2018-03-02 10:36:46 +08:00
|
|
|
p.stateTime = timeWsAlphaZoneOffsetWs
|
|
|
|
}
|
|
|
|
case timeWsAlphaZoneOffsetWs:
|
|
|
|
// timeWsAlphaZoneOffsetWs
|
|
|
|
// timeWsAlphaZoneOffsetWsExtra
|
|
|
|
// 18:04:07 GMT+0100 (GMT Daylight Time)
|
|
|
|
// timeWsAlphaZoneOffsetWsYear
|
|
|
|
// 15:44:11 UTC+0100 2015
|
|
|
|
if unicode.IsDigit(r) {
|
|
|
|
p.stateTime = timeWsAlphaZoneOffsetWsYear
|
|
|
|
} else {
|
|
|
|
p.extra = i - 1
|
|
|
|
p.stateTime = timeWsAlphaZoneOffsetWsExtra
|
|
|
|
}
|
|
|
|
case timeWsAlphaZoneOffsetWsYear:
|
|
|
|
// 15:44:11 UTC+0100 2015
|
|
|
|
if unicode.IsDigit(r) {
|
|
|
|
p.yearlen = i - p.yeari + 1
|
|
|
|
if p.yearlen == 4 {
|
2023-12-12 14:45:58 +08:00
|
|
|
if !p.setYear() {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
2018-03-02 10:36:46 +08:00
|
|
|
}
|
|
|
|
}
|
2018-02-26 14:46:45 +08:00
|
|
|
case timeWsAMPMMaybe:
|
|
|
|
// timeWsAMPMMaybe
|
|
|
|
// timeWsAMPM
|
|
|
|
// 05:24:37 PM
|
|
|
|
// timeWsAlpha
|
|
|
|
// 00:12:00 PST
|
2018-03-02 10:36:46 +08:00
|
|
|
// 15:44:11 UTC+0100 2015
|
2023-12-13 08:42:09 +08:00
|
|
|
isTwoLetterWord := ((i+1) == len(p.datestr) || p.nextIs(i, ' '))
|
|
|
|
if (r == 'm' || r == 'M') && isTwoLetterWord {
|
|
|
|
if p.parsedAMPM {
|
|
|
|
return p, unexpectedTail(p.datestr[i:])
|
|
|
|
}
|
2023-12-12 14:45:58 +08:00
|
|
|
// This isn't a time zone after all...
|
|
|
|
p.tzi = 0
|
2018-02-26 14:46:45 +08:00
|
|
|
p.stateTime = timeWsAMPM
|
2023-12-12 14:45:58 +08:00
|
|
|
if r == 'm' {
|
|
|
|
p.set(i-1, "pm")
|
|
|
|
} else {
|
|
|
|
p.set(i-1, "PM")
|
|
|
|
}
|
2023-12-13 08:42:09 +08:00
|
|
|
p.parsedAMPM = true
|
2018-03-11 03:50:19 +08:00
|
|
|
if p.hourlen == 2 {
|
|
|
|
p.set(p.houri, "03")
|
|
|
|
} else if p.hourlen == 1 {
|
|
|
|
p.set(p.houri, "3")
|
|
|
|
}
|
2018-02-26 14:46:45 +08:00
|
|
|
} else {
|
|
|
|
p.stateTime = timeWsAlpha
|
|
|
|
}
|
|
|
|
|
2023-12-12 14:45:58 +08:00
|
|
|
case timeWsAMPM:
|
|
|
|
// If we have a continuation after AM/PM indicator, reset parse state back to ws
|
|
|
|
if r == ' ' {
|
|
|
|
p.stateTime = timeWs
|
|
|
|
} else {
|
|
|
|
// unexpected garbage after AM/PM indicator, fail
|
|
|
|
return p, unexpectedTail(p.datestr[i:])
|
|
|
|
}
|
|
|
|
|
2018-02-26 14:46:45 +08:00
|
|
|
case timeWsOffset:
|
|
|
|
// timeWsOffset
|
|
|
|
// 15:04:05 -0700
|
2018-07-01 01:20:13 +08:00
|
|
|
// timeWsOffsetWsOffset
|
|
|
|
// 17:57:51 -0700 -07
|
2018-03-12 05:08:21 +08:00
|
|
|
// timeWsOffsetWs
|
|
|
|
// 17:57:51 -0700 2009
|
2018-03-12 08:39:59 +08:00
|
|
|
// 00:12:00 +0000 UTC
|
2018-02-26 14:46:45 +08:00
|
|
|
// timeWsOffsetColon
|
|
|
|
// 15:04:05 -07:00
|
|
|
|
// timeWsOffsetColonAlpha
|
|
|
|
// 00:12:00 +00:00 UTC
|
2018-02-28 12:05:14 +08:00
|
|
|
switch r {
|
|
|
|
case ':':
|
2018-02-26 14:46:45 +08:00
|
|
|
p.stateTime = timeWsOffsetColon
|
2018-02-28 12:05:14 +08:00
|
|
|
case ' ':
|
|
|
|
p.set(p.offseti, "-0700")
|
2018-03-12 08:39:59 +08:00
|
|
|
p.yeari = i + 1
|
2018-03-12 05:08:21 +08:00
|
|
|
p.stateTime = timeWsOffsetWs
|
2018-02-26 08:35:33 +08:00
|
|
|
}
|
2018-03-12 05:08:21 +08:00
|
|
|
case timeWsOffsetWs:
|
2018-03-12 08:39:59 +08:00
|
|
|
// 17:57:51 -0700 2009
|
|
|
|
// 00:12:00 +0000 UTC
|
2018-04-18 05:16:01 +08:00
|
|
|
// 22:18:00.001 +0000 UTC m=+0.000000001
|
2018-07-01 01:20:13 +08:00
|
|
|
// w Extra
|
|
|
|
// 17:57:51 -0700 -07
|
|
|
|
switch r {
|
|
|
|
case '=':
|
2018-04-18 05:16:01 +08:00
|
|
|
// eff you golang
|
2023-12-12 14:45:58 +08:00
|
|
|
if p.datestr[i-1] == 'm' {
|
2018-04-18 05:16:01 +08:00
|
|
|
p.extra = i - 2
|
2023-12-12 14:45:58 +08:00
|
|
|
p.trimExtra(false)
|
2018-04-18 05:16:01 +08:00
|
|
|
}
|
2021-02-06 05:44:33 +08:00
|
|
|
case '+', '-', '(':
|
2018-07-01 01:20:13 +08:00
|
|
|
// This really doesn't seem valid, but for some reason when round-tripping a go date
|
|
|
|
// their is an extra +03 printed out. seems like go bug to me, but, parsing anyway.
|
|
|
|
// 00:00:00 +0300 +03
|
|
|
|
// 00:00:00 +0300 +0300
|
|
|
|
p.extra = i - 1
|
|
|
|
p.stateTime = timeWsOffset
|
2023-12-12 14:45:58 +08:00
|
|
|
p.trimExtra(false)
|
2018-07-01 01:20:13 +08:00
|
|
|
default:
|
|
|
|
switch {
|
|
|
|
case unicode.IsDigit(r):
|
|
|
|
p.yearlen = i - p.yeari + 1
|
|
|
|
if p.yearlen == 4 {
|
2023-12-12 14:45:58 +08:00
|
|
|
if !p.setYear() {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
2018-07-01 01:20:13 +08:00
|
|
|
}
|
|
|
|
case unicode.IsLetter(r):
|
2021-02-07 07:23:24 +08:00
|
|
|
// 15:04:05 -0700 MST
|
2018-07-01 01:20:13 +08:00
|
|
|
if p.tzi == 0 {
|
|
|
|
p.tzi = i
|
|
|
|
}
|
|
|
|
}
|
2018-03-12 08:39:59 +08:00
|
|
|
}
|
2018-04-18 05:16:01 +08:00
|
|
|
|
2023-12-12 14:45:58 +08:00
|
|
|
case timeOffsetColon, timeWsOffsetColon:
|
|
|
|
// timeOffsetColon
|
|
|
|
// 15:04:05-07:00
|
|
|
|
// timeOffsetColonAlpha
|
|
|
|
// 2015-02-18 00:12:00+00:00 UTC
|
2018-02-26 14:46:45 +08:00
|
|
|
// timeWsOffsetColon
|
|
|
|
// 15:04:05 -07:00
|
|
|
|
// timeWsOffsetColonAlpha
|
|
|
|
// 2015-02-18 00:12:00 +00:00 UTC
|
|
|
|
if unicode.IsLetter(r) {
|
2023-12-12 14:45:58 +08:00
|
|
|
// TODO: do we need to handle the m=+0.000000001 case?
|
2018-02-26 14:46:45 +08:00
|
|
|
// 2015-02-18 00:12:00 +00:00 UTC
|
2023-12-12 14:45:58 +08:00
|
|
|
if p.stateTime == timeWsOffsetColon {
|
|
|
|
p.stateTime = timeWsOffsetColonAlpha
|
|
|
|
} else {
|
|
|
|
p.stateTime = timeOffsetColonAlpha
|
|
|
|
}
|
|
|
|
p.tzi = i
|
2018-02-26 14:46:45 +08:00
|
|
|
break iterTimeRunes
|
|
|
|
}
|
|
|
|
case timePeriod:
|
2023-12-12 14:45:58 +08:00
|
|
|
// 15:04:05.999999999
|
|
|
|
// 15:04:05.999999999
|
|
|
|
// 15:04:05.999999
|
|
|
|
// 15:04:05.999999
|
|
|
|
// 15:04:05.999
|
|
|
|
// 15:04:05.999
|
2018-02-26 14:46:45 +08:00
|
|
|
// timePeriod
|
2018-02-28 12:05:14 +08:00
|
|
|
// 17:24:37.3186369
|
|
|
|
// 00:07:31.945167
|
|
|
|
// 18:31:59.257000000
|
|
|
|
// 00:00:00.000
|
2023-12-12 14:45:58 +08:00
|
|
|
// (note: if we have an offset (+/-) or whitespace (Ws) after this state, re-enter the timeWs or timeOffset
|
|
|
|
// state above so that we do not have to duplicate all of the logic again for this parsing just because we
|
|
|
|
// have parsed a fractional second...)
|
2018-02-26 14:46:45 +08:00
|
|
|
switch r {
|
|
|
|
case ' ':
|
|
|
|
p.mslen = i - p.msi
|
2023-12-12 14:45:58 +08:00
|
|
|
p.coalesceTime(i)
|
|
|
|
p.stateTime = timeWs
|
2018-02-26 14:46:45 +08:00
|
|
|
case '+', '-':
|
|
|
|
p.mslen = i - p.msi
|
2018-02-28 12:05:14 +08:00
|
|
|
p.offseti = i
|
2023-12-12 14:45:58 +08:00
|
|
|
p.stateTime = timeOffset
|
|
|
|
case 'Z':
|
|
|
|
p.stateTime = timeZ
|
|
|
|
p.mslen = i - p.msi
|
|
|
|
// (Z)ulu time
|
|
|
|
p.loc = time.UTC
|
|
|
|
endPos := i + 1
|
|
|
|
if endPos > p.formatSetLen {
|
|
|
|
p.formatSetLen = endPos
|
|
|
|
}
|
|
|
|
case 'a', 'A', 'p', 'P':
|
|
|
|
// Could be AM/PM
|
|
|
|
isLower := r == 'a' || r == 'p'
|
2023-12-13 08:42:09 +08:00
|
|
|
isTwoLetterWord := ((i+2) == len(p.datestr) || p.nextIs(i+1, ' '))
|
2023-12-12 14:45:58 +08:00
|
|
|
switch {
|
2023-12-13 08:42:09 +08:00
|
|
|
case isLower && p.nextIs(i, 'm') && isTwoLetterWord && !p.parsedAMPM:
|
2023-12-12 14:45:58 +08:00
|
|
|
p.mslen = i - p.msi
|
|
|
|
p.coalesceTime(i)
|
|
|
|
p.set(i, "pm")
|
2023-12-13 08:42:09 +08:00
|
|
|
p.parsedAMPM = true
|
2023-12-12 14:45:58 +08:00
|
|
|
// skip 'm'
|
|
|
|
i++
|
|
|
|
p.stateTime = timePeriodAMPM
|
2023-12-13 08:42:09 +08:00
|
|
|
case !isLower && p.nextIs(i, 'M') && isTwoLetterWord && !p.parsedAMPM:
|
2018-02-26 14:46:45 +08:00
|
|
|
p.mslen = i - p.msi
|
2023-12-12 14:45:58 +08:00
|
|
|
p.coalesceTime(i)
|
|
|
|
p.set(i, "PM")
|
2023-12-13 08:42:09 +08:00
|
|
|
p.parsedAMPM = true
|
2023-12-12 14:45:58 +08:00
|
|
|
// skip 'M'
|
|
|
|
i++
|
|
|
|
p.stateTime = timePeriodAMPM
|
2023-12-13 08:42:09 +08:00
|
|
|
default:
|
|
|
|
return p, unexpectedTail(p.datestr[i:])
|
2018-02-26 14:46:45 +08:00
|
|
|
}
|
2018-02-28 12:05:14 +08:00
|
|
|
default:
|
2023-12-12 14:45:58 +08:00
|
|
|
if !unicode.IsDigit(r) {
|
|
|
|
return p, unexpectedTail(p.datestr[i:])
|
2018-02-28 12:05:14 +08:00
|
|
|
}
|
|
|
|
}
|
2023-12-12 14:45:58 +08:00
|
|
|
case timePeriodAMPM:
|
2018-02-28 12:05:14 +08:00
|
|
|
switch r {
|
|
|
|
case ' ':
|
2023-12-12 14:45:58 +08:00
|
|
|
p.stateTime = timeWs
|
2018-07-01 01:20:13 +08:00
|
|
|
case '+', '-':
|
2023-12-12 14:45:58 +08:00
|
|
|
p.offseti = i
|
|
|
|
p.stateTime = timeOffset
|
2018-03-12 08:39:59 +08:00
|
|
|
default:
|
2023-12-12 14:45:58 +08:00
|
|
|
return p, unexpectedTail(p.datestr[i:])
|
2018-03-12 08:39:59 +08:00
|
|
|
}
|
2018-02-26 14:46:45 +08:00
|
|
|
case timeZ:
|
2023-12-12 14:45:58 +08:00
|
|
|
// nothing expected can come after Z
|
|
|
|
return p, unexpectedTail(p.datestr[i:])
|
2018-02-26 08:35:33 +08:00
|
|
|
}
|
2018-02-26 14:46:45 +08:00
|
|
|
}
|
2018-02-26 08:35:33 +08:00
|
|
|
|
2018-02-26 14:46:45 +08:00
|
|
|
switch p.stateTime {
|
2023-12-12 14:45:58 +08:00
|
|
|
case timeOffsetColonAlpha, timeWsOffsetColonAlpha:
|
|
|
|
// process offset
|
|
|
|
offsetLen := i - p.offseti
|
|
|
|
switch offsetLen {
|
|
|
|
case 6, 7:
|
|
|
|
// may or may not have a space on the end
|
|
|
|
if offsetLen == 7 {
|
|
|
|
if p.datestr[p.offseti+6] != ' ' {
|
|
|
|
return p, fmt.Errorf("TZ offset not recognized %q near %q (expected offset like -07:00)", datestr, string(p.datestr[p.offseti:p.offseti+offsetLen]))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
p.set(p.offseti, "-07:00")
|
|
|
|
default:
|
|
|
|
return p, fmt.Errorf("TZ offset not recognized %q near %q (expected offset like -07:00)", datestr, string(p.datestr[p.offseti:p.offseti+offsetLen]))
|
|
|
|
}
|
|
|
|
// process timezone
|
2021-02-07 07:23:24 +08:00
|
|
|
switch len(p.datestr) - p.tzi {
|
|
|
|
case 3:
|
|
|
|
// 13:31:51.999 +01:00 CET
|
|
|
|
p.set(p.tzi, "MST")
|
|
|
|
case 4:
|
2023-12-12 14:45:58 +08:00
|
|
|
p.set(p.tzi, "MST ")
|
|
|
|
default:
|
|
|
|
return p, fmt.Errorf("timezone not recognized %q near %q (must be 3 or 4 characters)", datestr, string(p.datestr[p.tzi:]))
|
|
|
|
}
|
|
|
|
case timeWsAlpha:
|
|
|
|
switch len(p.datestr) - p.tzi {
|
|
|
|
case 3:
|
|
|
|
// 13:31:51.999 +01:00 CET
|
2021-02-07 07:23:24 +08:00
|
|
|
p.set(p.tzi, "MST")
|
2023-12-12 14:45:58 +08:00
|
|
|
case 4:
|
|
|
|
p.set(p.tzi, "MST ")
|
|
|
|
default:
|
|
|
|
return p, fmt.Errorf("timezone not recognized %q near %q (must be 3 or 4 characters)", datestr, string(p.datestr[p.tzi:]))
|
2021-02-07 07:23:24 +08:00
|
|
|
}
|
|
|
|
|
2023-12-14 14:58:04 +08:00
|
|
|
case timeWsAlphaRParen:
|
|
|
|
// continue
|
|
|
|
|
2018-03-12 08:39:59 +08:00
|
|
|
case timeWsAlphaWs:
|
|
|
|
p.yearlen = i - p.yeari
|
2023-12-12 14:45:58 +08:00
|
|
|
if !p.setYear() {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
2018-03-02 10:36:46 +08:00
|
|
|
case timeWsYear:
|
|
|
|
p.yearlen = i - p.yeari
|
2023-12-12 14:45:58 +08:00
|
|
|
if !p.setYear() {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
2018-03-02 10:36:46 +08:00
|
|
|
case timeWsAlphaZoneOffsetWsExtra:
|
2023-12-12 14:45:58 +08:00
|
|
|
p.trimExtra(false)
|
2018-11-23 05:54:48 +08:00
|
|
|
case timeWsAlphaZoneOffset:
|
|
|
|
// 06:20:00 UTC-05
|
2023-02-16 06:39:34 +08:00
|
|
|
switch i - p.offseti {
|
|
|
|
case 2, 3, 4:
|
2018-11-23 05:54:48 +08:00
|
|
|
p.set(p.offseti, "-07")
|
2023-02-16 06:39:34 +08:00
|
|
|
case 5:
|
2018-11-23 05:54:48 +08:00
|
|
|
p.set(p.offseti, "-0700")
|
2023-02-16 06:39:34 +08:00
|
|
|
case 6:
|
|
|
|
p.set(p.offseti, "-07:00")
|
2023-12-12 14:45:58 +08:00
|
|
|
default:
|
|
|
|
return p, fmt.Errorf("TZ offset not recognized %q near %q (must be 2 or 4 digits optional colon)", datestr, string(p.datestr[p.offseti:i]))
|
2018-11-23 05:54:48 +08:00
|
|
|
}
|
|
|
|
|
2018-02-26 08:35:33 +08:00
|
|
|
case timePeriod:
|
2018-02-26 14:46:45 +08:00
|
|
|
p.mslen = i - p.msi
|
2023-12-12 14:45:58 +08:00
|
|
|
if p.mslen >= 10 {
|
|
|
|
return p, fmt.Errorf("fractional seconds in %q too long near %q", datestr, string(p.datestr[p.msi:p.mslen]))
|
|
|
|
}
|
2023-12-16 08:42:07 +08:00
|
|
|
case timeOffset, timeWsOffset, timeWsYearOffset:
|
2021-02-07 05:42:06 +08:00
|
|
|
switch len(p.datestr) - p.offseti {
|
|
|
|
case 3:
|
2023-12-12 14:45:58 +08:00
|
|
|
// 19:55:00+01 (or 19:55:00 +01)
|
2021-02-07 05:42:06 +08:00
|
|
|
p.set(p.offseti, "-07")
|
|
|
|
case 5:
|
2023-12-12 14:45:58 +08:00
|
|
|
// 19:55:00+0100 (or 19:55:00 +0100)
|
2021-02-07 05:42:06 +08:00
|
|
|
p.set(p.offseti, "-0700")
|
2023-12-12 14:45:58 +08:00
|
|
|
default:
|
|
|
|
return p, fmt.Errorf("TZ offset not recognized %q near %q (must be 2 or 4 digits optional colon)", datestr, string(p.datestr[p.offseti:]))
|
2021-02-07 05:42:06 +08:00
|
|
|
}
|
|
|
|
|
2018-03-12 05:08:21 +08:00
|
|
|
case timeWsOffsetWs:
|
2018-03-12 08:39:59 +08:00
|
|
|
// 17:57:51 -0700 2009
|
|
|
|
// 00:12:00 +0000 UTC
|
2021-02-07 07:23:24 +08:00
|
|
|
if p.tzi > 0 {
|
|
|
|
switch len(p.datestr) - p.tzi {
|
|
|
|
case 3:
|
|
|
|
// 13:31:51.999 +01:00 CET
|
|
|
|
p.set(p.tzi, "MST")
|
|
|
|
case 4:
|
|
|
|
// 13:31:51.999 +01:00 CEST
|
|
|
|
p.set(p.tzi, "MST ")
|
2023-12-12 14:45:58 +08:00
|
|
|
default:
|
|
|
|
return p, fmt.Errorf("timezone not recognized %q near %q (must be 3 or 4 characters)", datestr, string(p.datestr[p.tzi:]))
|
2021-02-07 07:23:24 +08:00
|
|
|
}
|
|
|
|
}
|
2023-12-12 14:45:58 +08:00
|
|
|
case timeOffsetColon, timeWsOffsetColon:
|
|
|
|
// 17:57:51 -07:00 (or 19:55:00.799 +01:00)
|
|
|
|
// 15:04:05+07:00 (or 19:55:00.799+01:00)
|
|
|
|
switch len(p.datestr) - p.offseti {
|
|
|
|
case 6:
|
|
|
|
p.set(p.offseti, "-07:00")
|
|
|
|
default:
|
|
|
|
return p, fmt.Errorf("TZ offset not recognized %q near %q (expected offset like -07:00)", datestr, string(p.datestr[p.offseti:]))
|
2018-03-12 08:39:59 +08:00
|
|
|
}
|
2018-02-26 08:35:33 +08:00
|
|
|
}
|
2018-02-28 12:05:14 +08:00
|
|
|
p.coalesceTime(i)
|
2018-02-26 08:35:33 +08:00
|
|
|
}
|
|
|
|
|
2018-02-26 14:46:45 +08:00
|
|
|
switch p.stateDate {
|
2018-02-26 08:35:33 +08:00
|
|
|
case dateDigit:
|
2014-05-07 12:15:43 +08:00
|
|
|
// unixy timestamps ish
|
2018-07-22 05:28:33 +08:00
|
|
|
// example ct type
|
|
|
|
// 1499979655583057426 19 nanoseconds
|
|
|
|
// 1499979795437000 16 micro-seconds
|
|
|
|
// 20180722105203 14 yyyyMMddhhmmss
|
|
|
|
// 1499979795437 13 milliseconds
|
|
|
|
// 1332151919 10 seconds
|
|
|
|
// 20140601 8 yyyymmdd
|
|
|
|
// 2014 4 yyyy
|
2017-08-27 05:12:55 +08:00
|
|
|
t := time.Time{}
|
2023-12-12 14:45:58 +08:00
|
|
|
if len(p.datestr) == len("1499979655583057426") { // 19
|
2018-07-22 05:28:33 +08:00
|
|
|
// nano-seconds
|
2023-12-12 14:45:58 +08:00
|
|
|
if nanoSecs, err := strconv.ParseInt(p.datestr, 10, 64); err == nil {
|
2017-08-27 05:12:55 +08:00
|
|
|
t = time.Unix(0, nanoSecs)
|
2014-04-26 07:59:10 +08:00
|
|
|
}
|
2023-12-12 14:45:58 +08:00
|
|
|
} else if len(p.datestr) == len("1499979795437000") { // 16
|
2018-07-22 05:28:33 +08:00
|
|
|
// micro-seconds
|
2023-12-12 14:45:58 +08:00
|
|
|
if microSecs, err := strconv.ParseInt(p.datestr, 10, 64); err == nil {
|
2017-08-27 05:12:55 +08:00
|
|
|
t = time.Unix(0, microSecs*1000)
|
2014-05-07 12:15:43 +08:00
|
|
|
}
|
2023-12-12 14:45:58 +08:00
|
|
|
} else if len(p.datestr) == len("yyyyMMddhhmmss") { // 14
|
2018-07-22 05:28:33 +08:00
|
|
|
// yyyyMMddhhmmss
|
2023-12-12 14:45:58 +08:00
|
|
|
p.setEntireFormat([]byte("20060102150405"))
|
2018-07-22 05:28:33 +08:00
|
|
|
return p, nil
|
2023-12-12 14:45:58 +08:00
|
|
|
} else if len(p.datestr) == len("1332151919000") { // 13
|
|
|
|
if miliSecs, err := strconv.ParseInt(p.datestr, 10, 64); err == nil {
|
2017-08-27 05:12:55 +08:00
|
|
|
t = time.Unix(0, miliSecs*1000*1000)
|
2014-10-08 09:30:17 +08:00
|
|
|
}
|
2023-12-12 14:45:58 +08:00
|
|
|
} else if len(p.datestr) == len("1332151919") { //10
|
|
|
|
if secs, err := strconv.ParseInt(p.datestr, 10, 64); err == nil {
|
2018-07-22 05:28:33 +08:00
|
|
|
t = time.Unix(secs, 0)
|
|
|
|
}
|
2023-12-12 14:45:58 +08:00
|
|
|
} else if len(p.datestr) == len("20140601") {
|
|
|
|
p.setEntireFormat([]byte("20060102"))
|
2018-03-25 06:56:26 +08:00
|
|
|
return p, nil
|
2023-12-12 14:45:58 +08:00
|
|
|
} else if len(p.datestr) == len("2014") {
|
|
|
|
p.setEntireFormat([]byte("2006"))
|
2018-03-25 06:56:26 +08:00
|
|
|
return p, nil
|
2023-12-12 14:45:58 +08:00
|
|
|
} else if len(p.datestr) < 4 {
|
|
|
|
return p, fmt.Errorf("unrecognized format, too short %v", datestr)
|
2014-04-26 07:59:10 +08:00
|
|
|
}
|
2017-08-27 05:12:55 +08:00
|
|
|
if !t.IsZero() {
|
|
|
|
if loc == nil {
|
2018-03-25 06:56:26 +08:00
|
|
|
p.t = &t
|
|
|
|
return p, nil
|
2017-08-27 05:12:55 +08:00
|
|
|
}
|
2018-03-25 06:56:26 +08:00
|
|
|
t = t.In(loc)
|
|
|
|
p.t = &t
|
|
|
|
return p, nil
|
2017-08-13 00:48:28 +08:00
|
|
|
}
|
2021-02-04 14:23:19 +08:00
|
|
|
case dateDigitSt:
|
|
|
|
// 171113 14:14:20
|
|
|
|
return p, nil
|
2017-08-27 05:12:55 +08:00
|
|
|
|
2018-10-10 11:50:09 +08:00
|
|
|
case dateYearDash:
|
2018-02-28 12:05:14 +08:00
|
|
|
// 2006-01
|
2018-03-25 06:56:26 +08:00
|
|
|
return p, nil
|
2018-02-28 12:05:14 +08:00
|
|
|
|
2018-10-10 11:50:09 +08:00
|
|
|
case dateYearDashDash:
|
2014-05-07 12:15:43 +08:00
|
|
|
// 2006-01-02
|
2018-02-08 23:03:45 +08:00
|
|
|
// 2006-1-02
|
|
|
|
// 2006-1-2
|
|
|
|
// 2006-01-2
|
2018-03-25 06:56:26 +08:00
|
|
|
return p, nil
|
2018-02-08 23:03:45 +08:00
|
|
|
|
2021-02-07 08:14:29 +08:00
|
|
|
case dateYearDashDashOffset:
|
|
|
|
/// 2020-07-20+00:00
|
|
|
|
switch len(p.datestr) - p.offseti {
|
|
|
|
case 5:
|
|
|
|
p.set(p.offseti, "-0700")
|
|
|
|
case 6:
|
|
|
|
p.set(p.offseti, "-07:00")
|
|
|
|
}
|
|
|
|
return p, nil
|
|
|
|
|
2018-10-10 11:50:09 +08:00
|
|
|
case dateYearDashAlphaDash:
|
2017-07-27 07:55:21 +08:00
|
|
|
// 2013-Feb-03
|
2018-02-08 23:03:45 +08:00
|
|
|
// 2013-Feb-3
|
2018-02-26 14:46:45 +08:00
|
|
|
p.daylen = i - p.dayi
|
2023-12-12 14:45:58 +08:00
|
|
|
if !p.setDay() {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
2018-03-25 06:56:26 +08:00
|
|
|
return p, nil
|
2018-02-26 14:46:45 +08:00
|
|
|
|
2018-10-10 11:50:09 +08:00
|
|
|
case dateYearDashDashWs:
|
|
|
|
// 2013-04-01
|
|
|
|
return p, nil
|
|
|
|
|
|
|
|
case dateYearDashDashT:
|
2018-03-25 06:56:26 +08:00
|
|
|
return p, nil
|
2018-02-26 14:46:45 +08:00
|
|
|
|
2023-12-13 14:07:11 +08:00
|
|
|
case dateDigitDashAlphaDash, dateDigitDashDigitDash:
|
|
|
|
// This has already been done if we parsed the time already
|
|
|
|
if p.stateTime == timeIgnore {
|
|
|
|
// dateDigitDashAlphaDash:
|
|
|
|
// 13-Feb-03 ambiguous
|
|
|
|
// 28-Feb-03 ambiguous
|
|
|
|
// 29-Jun-2016
|
|
|
|
// dateDigitDashDigitDash:
|
|
|
|
// 29-06-2026
|
|
|
|
length := len(p.datestr) - (p.moi + p.molen + 1)
|
|
|
|
if length == 4 {
|
|
|
|
p.yearlen = 4
|
|
|
|
p.set(p.yeari, "2006")
|
|
|
|
// We now also know that part1 was the day
|
|
|
|
p.dayi = 0
|
|
|
|
p.daylen = p.part1Len
|
|
|
|
if !p.setDay() {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
|
|
|
} else if length == 2 {
|
|
|
|
// We have no idea if this is
|
|
|
|
// yy-mon-dd OR dd-mon-yy
|
|
|
|
// (or for dateDigitDashDigitDash, yy-mm-dd OR dd-mm-yy)
|
|
|
|
//
|
|
|
|
// We are going to ASSUME (bad, bad) that it is dd-mon-yy (dd-mm-yy),
|
|
|
|
// which is a horrible assumption, but seems to be the convention for
|
|
|
|
// dates that are formatted in this way.
|
|
|
|
p.ambiguousMD = true
|
|
|
|
p.yearlen = 2
|
|
|
|
p.set(p.yeari, "06")
|
|
|
|
// We now also know that part1 was the day
|
|
|
|
p.dayi = 0
|
|
|
|
p.daylen = p.part1Len
|
|
|
|
if !p.setDay() {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
|
|
|
} else {
|
2023-12-12 14:45:58 +08:00
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
2019-06-20 01:28:39 +08:00
|
|
|
}
|
|
|
|
|
2018-03-25 06:56:26 +08:00
|
|
|
return p, nil
|
2017-07-27 07:55:21 +08:00
|
|
|
|
2018-05-04 09:06:52 +08:00
|
|
|
case dateDigitDot:
|
2023-12-13 14:19:35 +08:00
|
|
|
if len(datestr) == len("yyyyMMddhhmmss.SSS") { // 18
|
|
|
|
p.setEntireFormat([]byte("20060102150405.000"))
|
|
|
|
return p, nil
|
|
|
|
} else {
|
|
|
|
// 2014.05
|
|
|
|
p.molen = i - p.moi
|
|
|
|
if !p.setMonth() {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
|
|
|
return p, nil
|
2023-12-12 14:45:58 +08:00
|
|
|
}
|
2018-05-04 09:06:52 +08:00
|
|
|
|
2018-02-26 08:35:33 +08:00
|
|
|
case dateDigitDotDot:
|
2018-02-28 12:05:14 +08:00
|
|
|
// 03.31.1981
|
2018-05-04 09:06:52 +08:00
|
|
|
// 3.31.2014
|
2018-02-28 12:05:14 +08:00
|
|
|
// 3.2.1981
|
|
|
|
// 3.2.81
|
2018-05-04 09:06:52 +08:00
|
|
|
// 08.21.71
|
2019-02-23 08:50:43 +08:00
|
|
|
// 2018.09.30
|
2018-03-25 06:56:26 +08:00
|
|
|
return p, nil
|
2018-01-12 05:21:04 +08:00
|
|
|
|
2023-12-15 14:47:31 +08:00
|
|
|
case dateDigitDotDotWs:
|
|
|
|
// 2013.04.01
|
|
|
|
return p, nil
|
|
|
|
|
|
|
|
case dateDigitDotDotT:
|
|
|
|
return p, nil
|
|
|
|
|
|
|
|
case dateDigitDotDotOffset:
|
|
|
|
// 2020.07.20+00:00
|
|
|
|
switch len(p.datestr) - p.offseti {
|
|
|
|
case 5:
|
|
|
|
p.set(p.offseti, "-0700")
|
|
|
|
case 6:
|
|
|
|
p.set(p.offseti, "-07:00")
|
|
|
|
}
|
|
|
|
return p, nil
|
|
|
|
|
2018-02-28 12:05:14 +08:00
|
|
|
case dateDigitWsMoYear:
|
2018-03-11 05:43:16 +08:00
|
|
|
// 2 Jan 2018
|
|
|
|
// 2 Jan 18
|
2018-02-09 04:52:03 +08:00
|
|
|
// 2 Jan 2018 23:59
|
|
|
|
// 02 Jan 2018 23:59
|
2018-01-25 09:59:25 +08:00
|
|
|
// 12 Feb 2006, 19:17
|
2018-03-25 06:56:26 +08:00
|
|
|
return p, nil
|
2018-03-11 05:05:55 +08:00
|
|
|
|
2018-11-23 05:54:48 +08:00
|
|
|
case dateAlphaWsMonth:
|
|
|
|
p.yearlen = i - p.yeari
|
2023-12-12 14:45:58 +08:00
|
|
|
if !p.setYear() {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
2018-11-23 05:54:48 +08:00
|
|
|
return p, nil
|
|
|
|
|
|
|
|
case dateAlphaWsMonthMore:
|
|
|
|
return p, nil
|
|
|
|
|
|
|
|
case dateAlphaWsDigitMoreWs:
|
2018-03-11 05:43:16 +08:00
|
|
|
// oct 1, 1970
|
|
|
|
p.yearlen = i - p.yeari
|
2023-12-12 14:45:58 +08:00
|
|
|
if !p.setYear() {
|
|
|
|
return p, unknownErr(datestr)
|
|
|
|
}
|
2018-03-25 06:56:26 +08:00
|
|
|
return p, nil
|
2018-03-11 05:43:16 +08:00
|
|
|
|
2018-11-23 05:54:48 +08:00
|
|
|
case dateAlphaWsDigitMoreWsYear:
|
2018-02-26 08:35:33 +08:00
|
|
|
// May 8, 2009 5:57:51 PM
|
2018-11-23 05:54:48 +08:00
|
|
|
// Jun 7, 2005, 05:57:51
|
2018-03-25 06:56:26 +08:00
|
|
|
return p, nil
|
2018-03-02 10:36:46 +08:00
|
|
|
|
|
|
|
case dateAlphaWsAlpha:
|
2018-03-25 06:56:26 +08:00
|
|
|
return p, nil
|
2018-03-02 10:36:46 +08:00
|
|
|
|
2020-05-06 10:10:21 +08:00
|
|
|
case dateAlphaWsDigit:
|
|
|
|
return p, nil
|
|
|
|
|
|
|
|
case dateAlphaWsDigitYearmaybe:
|
2018-03-25 06:56:26 +08:00
|
|
|
return p, nil
|
2018-03-02 10:36:46 +08:00
|
|
|
|
2018-03-12 08:39:59 +08:00
|
|
|
case dateDigitSlash:
|
2014-05-07 12:15:43 +08:00
|
|
|
// 3/1/2014
|
|
|
|
// 10/13/2014
|
|
|
|
// 01/02/2006
|
2021-02-06 05:11:38 +08:00
|
|
|
return p, nil
|
|
|
|
|
|
|
|
case dateDigitSlashAlpha:
|
|
|
|
// 03/Jun/2014
|
|
|
|
return p, nil
|
|
|
|
|
|
|
|
case dateDigitYearSlash:
|
2014-07-11 06:25:23 +08:00
|
|
|
// 2014/10/13
|
2018-03-25 06:56:26 +08:00
|
|
|
return p, nil
|
2014-05-07 12:15:43 +08:00
|
|
|
|
2020-06-18 04:55:25 +08:00
|
|
|
case dateDigitColon:
|
|
|
|
// 3:1:2014
|
|
|
|
// 10:13:2014
|
|
|
|
// 01:02:2006
|
|
|
|
// 2014:10:13
|
|
|
|
return p, nil
|
|
|
|
|
2018-02-26 08:35:33 +08:00
|
|
|
case dateDigitChineseYear:
|
|
|
|
// dateDigitChineseYear
|
2017-11-19 03:07:33 +08:00
|
|
|
// 2014年04月08日
|
2023-12-16 08:14:03 +08:00
|
|
|
// 2014年4月12日
|
2018-03-25 06:56:26 +08:00
|
|
|
return p, nil
|
2018-03-02 10:36:46 +08:00
|
|
|
|
2018-02-26 08:35:33 +08:00
|
|
|
case dateDigitChineseYearWs:
|
2023-12-16 08:14:03 +08:00
|
|
|
// 2014年04月08日 00:00:00 ...
|
2018-03-25 06:56:26 +08:00
|
|
|
return p, nil
|
2018-03-02 10:36:46 +08:00
|
|
|
|
2023-12-13 12:24:17 +08:00
|
|
|
case dateAlphaSlashDigitSlash:
|
|
|
|
// Oct/ 7/1970
|
|
|
|
// February/07/1970
|
|
|
|
return p, nil
|
|
|
|
|
2018-03-02 10:36:46 +08:00
|
|
|
case dateWeekdayComma:
|
2017-07-14 00:11:41 +08:00
|
|
|
// Monday, 02 Jan 2006 15:04:05 -0700
|
|
|
|
// Monday, 02 Jan 2006 15:04:05 +0100
|
2018-02-10 10:08:04 +08:00
|
|
|
// Monday, 02-Jan-06 15:04:05 MST
|
2018-03-25 06:56:26 +08:00
|
|
|
return p, nil
|
2018-03-02 10:36:46 +08:00
|
|
|
|
2018-03-12 08:39:59 +08:00
|
|
|
case dateWeekdayAbbrevComma:
|
2017-07-14 00:11:41 +08:00
|
|
|
// Mon, 02-Jan-06 15:04:05 MST
|
2016-05-04 04:55:11 +08:00
|
|
|
// Mon, 02 Jan 2006 15:04:05 MST
|
2018-03-25 06:56:26 +08:00
|
|
|
return p, nil
|
2018-02-08 23:03:45 +08:00
|
|
|
|
2023-12-13 12:42:48 +08:00
|
|
|
case dateYearWsMonthWs:
|
|
|
|
// 2013 May 02 11:37:55
|
|
|
|
// 2013 December 02 11:37:55
|
|
|
|
return p, nil
|
|
|
|
|
2014-04-21 10:56:17 +08:00
|
|
|
}
|
|
|
|
|
2023-12-12 14:45:58 +08:00
|
|
|
return p, unknownErr(datestr)
|
2014-04-21 10:56:17 +08:00
|
|
|
}
|
2018-10-10 11:50:09 +08:00
|
|
|
|
|
|
|
type parser struct {
|
2019-08-08 11:47:05 +08:00
|
|
|
loc *time.Location
|
|
|
|
preferMonthFirst bool
|
|
|
|
retryAmbiguousDateWithSwap bool
|
|
|
|
ambiguousMD bool
|
2023-12-12 14:45:58 +08:00
|
|
|
allowPartialStringMatch bool
|
2019-08-08 11:47:05 +08:00
|
|
|
stateDate dateState
|
|
|
|
stateTime timeState
|
|
|
|
format []byte
|
2023-12-12 14:45:58 +08:00
|
|
|
formatSetLen int
|
2019-08-08 11:47:05 +08:00
|
|
|
datestr string
|
|
|
|
fullMonth string
|
2023-12-13 08:42:09 +08:00
|
|
|
parsedAMPM bool
|
2019-08-08 11:47:05 +08:00
|
|
|
skip int
|
|
|
|
extra int
|
|
|
|
part1Len int
|
|
|
|
yeari int
|
|
|
|
yearlen int
|
|
|
|
moi int
|
|
|
|
molen int
|
|
|
|
dayi int
|
|
|
|
daylen int
|
|
|
|
houri int
|
|
|
|
hourlen int
|
|
|
|
mini int
|
|
|
|
minlen int
|
|
|
|
seci int
|
|
|
|
seclen int
|
|
|
|
msi int
|
|
|
|
mslen int
|
|
|
|
offseti int
|
|
|
|
tzi int
|
|
|
|
tzlen int
|
|
|
|
t *time.Time
|
2018-10-10 11:50:09 +08:00
|
|
|
}
|
|
|
|
|
2019-08-13 23:01:04 +08:00
|
|
|
// ParserOption defines a function signature implemented by options
|
|
|
|
// Options defined like this accept the parser and operate on the data within
|
2019-08-08 11:22:19 +08:00
|
|
|
type ParserOption func(*parser) error
|
2019-08-08 11:12:45 +08:00
|
|
|
|
|
|
|
// PreferMonthFirst is an option that allows preferMonthFirst to be changed from its default
|
2019-08-08 11:22:19 +08:00
|
|
|
func PreferMonthFirst(preferMonthFirst bool) ParserOption {
|
2019-08-08 11:12:45 +08:00
|
|
|
return func(p *parser) error {
|
|
|
|
p.preferMonthFirst = preferMonthFirst
|
|
|
|
return nil
|
|
|
|
}
|
2018-10-10 11:50:09 +08:00
|
|
|
}
|
|
|
|
|
2019-08-08 11:47:05 +08:00
|
|
|
// RetryAmbiguousDateWithSwap is an option that allows retryAmbiguousDateWithSwap to be changed from its default
|
|
|
|
func RetryAmbiguousDateWithSwap(retryAmbiguousDateWithSwap bool) ParserOption {
|
|
|
|
return func(p *parser) error {
|
|
|
|
p.retryAmbiguousDateWithSwap = retryAmbiguousDateWithSwap
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-12 14:45:58 +08:00
|
|
|
// AllowPartialStringMatch is an option that allows allowPartialStringMatch to be changed from its default.
|
|
|
|
// If true, then strings can be attempted to be parsed / matched even if the end of the string might contain
|
|
|
|
// more than a date/time. This defaults to false.
|
|
|
|
func AllowPartialStringMatch(allowPartialStringMatch bool) ParserOption {
|
|
|
|
return func(p *parser) error {
|
|
|
|
p.allowPartialStringMatch = allowPartialStringMatch
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-15 12:49:18 +08:00
|
|
|
func newParser(dateStr string, loc *time.Location, opts ...ParserOption) (*parser, error) {
|
2019-08-08 11:12:45 +08:00
|
|
|
p := &parser{
|
2019-08-08 11:47:05 +08:00
|
|
|
stateDate: dateStart,
|
|
|
|
stateTime: timeIgnore,
|
|
|
|
datestr: dateStr,
|
|
|
|
loc: loc,
|
|
|
|
preferMonthFirst: true,
|
|
|
|
retryAmbiguousDateWithSwap: false,
|
2018-10-10 11:50:09 +08:00
|
|
|
}
|
|
|
|
p.format = []byte(dateStr)
|
2023-12-12 14:45:58 +08:00
|
|
|
// this tracks how much of the format string has been set, to make sure all of it is set
|
|
|
|
p.formatSetLen = 0
|
2019-08-08 11:12:45 +08:00
|
|
|
|
|
|
|
// allow the options to mutate the parser fields from their defaults
|
|
|
|
for _, option := range opts {
|
2023-02-15 12:49:18 +08:00
|
|
|
if err := option(p); err != nil {
|
2023-02-15 12:56:17 +08:00
|
|
|
return nil, fmt.Errorf("option error: %w", err)
|
2023-02-15 12:49:18 +08:00
|
|
|
}
|
2019-08-08 11:12:45 +08:00
|
|
|
}
|
2023-02-15 12:56:17 +08:00
|
|
|
return p, nil
|
2018-10-10 11:50:09 +08:00
|
|
|
}
|
2018-11-23 05:54:48 +08:00
|
|
|
|
|
|
|
func (p *parser) nextIs(i int, b byte) bool {
|
|
|
|
if len(p.datestr) > i+1 && p.datestr[i+1] == b {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2023-12-12 14:45:58 +08:00
|
|
|
func (p *parser) setEntireFormat(format []byte) {
|
|
|
|
p.format = format
|
|
|
|
p.formatSetLen = len(format)
|
|
|
|
}
|
|
|
|
|
2018-10-10 11:50:09 +08:00
|
|
|
func (p *parser) set(start int, val string) {
|
|
|
|
if start < 0 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if len(p.format) < start+len(val) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
for i, r := range val {
|
|
|
|
p.format[start+i] = byte(r)
|
|
|
|
}
|
2023-12-12 14:45:58 +08:00
|
|
|
endingPos := start + len(val)
|
|
|
|
if endingPos > p.formatSetLen {
|
|
|
|
p.formatSetLen = endingPos
|
|
|
|
}
|
2018-10-10 11:50:09 +08:00
|
|
|
}
|
2023-12-12 14:45:58 +08:00
|
|
|
func (p *parser) setMonth() bool {
|
2018-10-10 11:50:09 +08:00
|
|
|
if p.molen == 2 {
|
|
|
|
p.set(p.moi, "01")
|
2023-12-12 14:45:58 +08:00
|
|
|
return true
|
2018-10-10 11:50:09 +08:00
|
|
|
} else if p.molen == 1 {
|
|
|
|
p.set(p.moi, "1")
|
2023-12-12 14:45:58 +08:00
|
|
|
return true
|
|
|
|
} else {
|
|
|
|
return false
|
2018-10-10 11:50:09 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-12 14:45:58 +08:00
|
|
|
func (p *parser) setDay() bool {
|
2018-10-10 11:50:09 +08:00
|
|
|
if p.daylen == 2 {
|
|
|
|
p.set(p.dayi, "02")
|
2023-12-12 14:45:58 +08:00
|
|
|
return true
|
2018-10-10 11:50:09 +08:00
|
|
|
} else if p.daylen == 1 {
|
|
|
|
p.set(p.dayi, "2")
|
2023-12-12 14:45:58 +08:00
|
|
|
return true
|
|
|
|
} else {
|
|
|
|
return false
|
2018-10-10 11:50:09 +08:00
|
|
|
}
|
|
|
|
}
|
2023-12-12 14:45:58 +08:00
|
|
|
func (p *parser) setYear() bool {
|
2018-10-10 11:50:09 +08:00
|
|
|
if p.yearlen == 2 {
|
|
|
|
p.set(p.yeari, "06")
|
2023-12-12 14:45:58 +08:00
|
|
|
return true
|
2018-10-10 11:50:09 +08:00
|
|
|
} else if p.yearlen == 4 {
|
|
|
|
p.set(p.yeari, "2006")
|
2023-12-12 14:45:58 +08:00
|
|
|
return true
|
|
|
|
} else {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Find the proper end of the current component (scanning chars starting from start and going
|
|
|
|
// up until the end, and either returning at end or returning the first character that is
|
|
|
|
// not allowed, as determined by allowNumeric, allowAlpha, and allowOther)
|
|
|
|
func findProperEnd(s string, start, end int, allowNumeric bool, allowAlpha bool, allowOther bool) int {
|
|
|
|
for i := start; i < end; i++ {
|
|
|
|
c := s[i]
|
|
|
|
if c >= '0' && c <= '9' {
|
|
|
|
if !allowNumeric {
|
|
|
|
return i
|
|
|
|
}
|
|
|
|
} else if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') {
|
|
|
|
if !allowAlpha {
|
|
|
|
return i
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if !allowOther {
|
|
|
|
return i
|
|
|
|
}
|
|
|
|
}
|
2018-10-10 11:50:09 +08:00
|
|
|
}
|
2023-12-12 14:45:58 +08:00
|
|
|
return end
|
2018-10-10 11:50:09 +08:00
|
|
|
}
|
2023-12-12 14:45:58 +08:00
|
|
|
|
|
|
|
func (p *parser) coalesceDate(end int) bool {
|
2018-10-10 11:50:09 +08:00
|
|
|
if p.yeari > 0 {
|
|
|
|
if p.yearlen == 0 {
|
2023-12-12 14:45:58 +08:00
|
|
|
p.yearlen = findProperEnd(p.datestr, p.yeari, end, true, false, false) - p.yeari
|
|
|
|
}
|
|
|
|
if !p.setYear() {
|
|
|
|
return false
|
2018-10-10 11:50:09 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if p.moi > 0 && p.molen == 0 {
|
2023-12-12 14:45:58 +08:00
|
|
|
p.molen = findProperEnd(p.datestr, p.moi, end, true, true, false) - p.moi
|
|
|
|
// The month may be the name of the month, so don't treat as invalid in this case.
|
|
|
|
// We can ignore the return value here.
|
2018-10-10 11:50:09 +08:00
|
|
|
p.setMonth()
|
|
|
|
}
|
|
|
|
if p.dayi > 0 && p.daylen == 0 {
|
2023-12-12 14:45:58 +08:00
|
|
|
p.daylen = findProperEnd(p.datestr, p.dayi, end, true, false, false) - p.dayi
|
|
|
|
if !p.setDay() {
|
|
|
|
return false
|
|
|
|
}
|
2018-10-10 11:50:09 +08:00
|
|
|
}
|
2023-12-12 14:45:58 +08:00
|
|
|
return true
|
2018-10-10 11:50:09 +08:00
|
|
|
}
|
|
|
|
func (p *parser) ts() string {
|
|
|
|
return fmt.Sprintf("h:(%d:%d) m:(%d:%d) s:(%d:%d)", p.houri, p.hourlen, p.mini, p.minlen, p.seci, p.seclen)
|
|
|
|
}
|
|
|
|
func (p *parser) ds() string {
|
|
|
|
return fmt.Sprintf("%s d:(%d:%d) m:(%d:%d) y:(%d:%d)", p.datestr, p.dayi, p.daylen, p.moi, p.molen, p.yeari, p.yearlen)
|
|
|
|
}
|
|
|
|
func (p *parser) coalesceTime(end int) {
|
|
|
|
// 03:04:05
|
|
|
|
// 15:04:05
|
|
|
|
// 3:04:05
|
|
|
|
// 3:4:5
|
|
|
|
// 15:04:05.00
|
|
|
|
if p.houri > 0 {
|
|
|
|
if p.hourlen == 2 {
|
|
|
|
p.set(p.houri, "15")
|
|
|
|
} else if p.hourlen == 1 {
|
|
|
|
p.set(p.houri, "3")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if p.mini > 0 {
|
|
|
|
if p.minlen == 0 {
|
|
|
|
p.minlen = end - p.mini
|
|
|
|
}
|
|
|
|
if p.minlen == 2 {
|
|
|
|
p.set(p.mini, "04")
|
|
|
|
} else {
|
|
|
|
p.set(p.mini, "4")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if p.seci > 0 {
|
|
|
|
if p.seclen == 0 {
|
|
|
|
p.seclen = end - p.seci
|
|
|
|
}
|
|
|
|
if p.seclen == 2 {
|
|
|
|
p.set(p.seci, "05")
|
|
|
|
} else {
|
|
|
|
p.set(p.seci, "5")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if p.msi > 0 {
|
|
|
|
for i := 0; i < p.mslen; i++ {
|
|
|
|
p.format[p.msi+i] = '0'
|
|
|
|
}
|
2023-12-12 14:45:58 +08:00
|
|
|
endPos := p.msi + p.mslen
|
|
|
|
if endPos > p.formatSetLen {
|
|
|
|
p.formatSetLen = endPos
|
|
|
|
}
|
2018-10-10 11:50:09 +08:00
|
|
|
}
|
|
|
|
}
|
2018-11-23 05:54:48 +08:00
|
|
|
func (p *parser) setFullMonth(month string) {
|
2023-12-12 14:46:44 +08:00
|
|
|
oldLen := len(p.format)
|
|
|
|
const fullMonth = "January"
|
2023-12-12 14:45:58 +08:00
|
|
|
p.format = []byte(fmt.Sprintf("%s%s%s", p.format[0:p.moi], fullMonth, p.format[p.moi+len(month):]))
|
2023-12-12 14:46:44 +08:00
|
|
|
newLen := len(p.format)
|
2023-12-12 14:45:58 +08:00
|
|
|
if newLen > oldLen && p.formatSetLen >= p.moi {
|
2023-12-12 14:46:44 +08:00
|
|
|
p.formatSetLen += newLen - oldLen
|
2023-12-12 14:45:58 +08:00
|
|
|
} else if newLen < oldLen && p.formatSetLen >= p.moi {
|
|
|
|
p.formatSetLen -= oldLen - newLen
|
2023-12-12 14:46:44 +08:00
|
|
|
}
|
2023-12-12 14:45:58 +08:00
|
|
|
|
2023-12-12 14:46:44 +08:00
|
|
|
if p.formatSetLen > len(p.format) {
|
|
|
|
p.formatSetLen = len(p.format)
|
|
|
|
} else if p.formatSetLen < len(fullMonth) {
|
|
|
|
p.formatSetLen = len(fullMonth)
|
2023-12-12 14:45:58 +08:00
|
|
|
} else if p.formatSetLen < 0 {
|
|
|
|
p.formatSetLen = 0
|
2018-11-23 05:54:48 +08:00
|
|
|
}
|
|
|
|
}
|
2018-10-10 11:50:09 +08:00
|
|
|
|
2023-12-12 14:45:58 +08:00
|
|
|
func (p *parser) trimExtra(onlyTrimFormat bool) {
|
2018-10-10 11:50:09 +08:00
|
|
|
if p.extra > 0 && len(p.format) > p.extra {
|
|
|
|
p.format = p.format[0:p.extra]
|
2023-12-12 14:45:58 +08:00
|
|
|
if p.formatSetLen > len(p.format) {
|
|
|
|
p.formatSetLen = len(p.format)
|
|
|
|
}
|
|
|
|
if !onlyTrimFormat {
|
|
|
|
p.datestr = p.datestr[0:p.extra]
|
|
|
|
}
|
2018-10-10 11:50:09 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *parser) parse() (time.Time, error) {
|
|
|
|
if p.t != nil {
|
|
|
|
return *p.t, nil
|
|
|
|
}
|
2018-11-23 05:54:48 +08:00
|
|
|
if len(p.fullMonth) > 0 {
|
|
|
|
p.setFullMonth(p.fullMonth)
|
|
|
|
}
|
2023-12-12 14:45:58 +08:00
|
|
|
|
|
|
|
// Make sure that the entire string matched to a known format that was detected
|
|
|
|
if !p.allowPartialStringMatch && p.formatSetLen < len(p.format) {
|
|
|
|
// We can always ignore punctuation at the end of a date/time, but do not allow
|
|
|
|
// any numbers or letters in the format string.
|
|
|
|
validFormatTo := findProperEnd(string(p.format), p.formatSetLen, len(p.format), false, false, true)
|
|
|
|
if validFormatTo < len(p.format) {
|
|
|
|
return time.Time{}, unexpectedTail(string(p.format[p.formatSetLen:]))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-10 11:50:09 +08:00
|
|
|
if p.skip > 0 && len(p.format) > p.skip {
|
|
|
|
p.format = p.format[p.skip:]
|
2023-12-12 14:45:58 +08:00
|
|
|
p.formatSetLen -= p.skip
|
|
|
|
if p.formatSetLen < 0 {
|
|
|
|
p.formatSetLen = 0
|
|
|
|
}
|
2018-10-10 11:50:09 +08:00
|
|
|
p.datestr = p.datestr[p.skip:]
|
|
|
|
}
|
2021-02-04 14:23:19 +08:00
|
|
|
|
2018-10-10 11:50:09 +08:00
|
|
|
if p.loc == nil {
|
2021-02-07 07:23:24 +08:00
|
|
|
// gou.Debugf("parse layout=%q input=%q \ntx, err := time.Parse(%q, %q)", string(p.format), p.datestr, string(p.format), p.datestr)
|
2018-10-10 11:50:09 +08:00
|
|
|
return time.Parse(string(p.format), p.datestr)
|
|
|
|
}
|
2021-02-07 05:42:06 +08:00
|
|
|
//gou.Debugf("parse layout=%q input=%q \ntx, err := time.ParseInLocation(%q, %q, %v)", string(p.format), p.datestr, string(p.format), p.datestr, p.loc)
|
2018-10-10 11:50:09 +08:00
|
|
|
return time.ParseInLocation(string(p.format), p.datestr, p.loc)
|
|
|
|
}
|
2020-05-06 10:10:21 +08:00
|
|
|
func isDay(alpha string) bool {
|
|
|
|
for _, day := range days {
|
|
|
|
if alpha == day {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
2018-11-23 05:54:48 +08:00
|
|
|
func isMonthFull(alpha string) bool {
|
2023-12-13 12:24:17 +08:00
|
|
|
if len(alpha) > len("september") {
|
|
|
|
return false
|
|
|
|
}
|
2018-11-23 05:54:48 +08:00
|
|
|
for _, month := range months {
|
|
|
|
if alpha == month {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|