1// Package dateparse parses date-strings without knowing the format
2// in advance, using a fast lex based approach to eliminate shotgun
3// attempts. It leans towards US style dates when there is a conflict.
4package dateparse
5
6import (
7 "fmt"
8 "strconv"
9 "strings"
10 "time"
11 "unicode"
12 "unicode/utf8"
13)
14
15// func init() {
16// gou.SetupLogging("debug")
17// gou.SetColorOutput()
18// }
19
20var months = []string{
21 "january",
22 "february",
23 "march",
24 "april",
25 "may",
26 "june",
27 "july",
28 "august",
29 "september",
30 "october",
31 "november",
32 "december",
33}
34
35type dateState uint8
36type timeState uint8
37
38const (
39 dateStart dateState = iota // 0
40 dateDigit
41 dateYearDash
42 dateYearDashAlphaDash
43 dateYearDashDash
44 dateYearDashDashWs // 5
45 dateYearDashDashT
46 dateDigitDash
47 dateDigitDashAlpha
48 dateDigitDashAlphaDash
49 dateDigitDot // 10
50 dateDigitDotDot
51 dateDigitSlash
52 dateDigitChineseYear
53 dateDigitChineseYearWs
54 dateDigitWs // 15
55 dateDigitWsMoYear
56 dateDigitWsMolong
57 dateAlpha
58 dateAlphaWs
59 dateAlphaWsDigit // 20
60 dateAlphaWsDigitMore
61 dateAlphaWsDigitMoreWs
62 dateAlphaWsDigitMoreWsYear
63 dateAlphaWsMonth
64 dateAlphaWsMonthMore
65 dateAlphaWsMonthSuffix
66 dateAlphaWsMore
67 dateAlphaWsAtTime
68 dateAlphaWsAlpha
69 dateAlphaWsAlphaYearmaybe
70 dateAlphaPeriodWsDigit
71 dateWeekdayComma
72 dateWeekdayAbbrevComma
73)
74const (
75 // Time state
76 timeIgnore timeState = iota // 0
77 timeStart
78 timeWs
79 timeWsAlpha
80 timeWsAlphaWs
81 timeWsAlphaZoneOffset // 5
82 timeWsAlphaZoneOffsetWs
83 timeWsAlphaZoneOffsetWsYear
84 timeWsAlphaZoneOffsetWsExtra
85 timeWsAMPMMaybe
86 timeWsAMPM // 10
87 timeWsOffset
88 timeWsOffsetWs // 12
89 timeWsOffsetColonAlpha
90 timeWsOffsetColon
91 timeWsYear // 15
92 timeOffset
93 timeOffsetColon
94 timeAlpha
95 timePeriod
96 timePeriodOffset // 20
97 timePeriodOffsetColon
98 timePeriodOffsetColonWs
99 timePeriodWs
100 timePeriodWsAlpha
101 timePeriodWsOffset // 25
102 timePeriodWsOffsetWs
103 timePeriodWsOffsetWsAlpha
104 timePeriodWsOffsetColon
105 timePeriodWsOffsetColonAlpha
106 timeZ
107 timeZDigit
108)
109
110var (
111 // ErrAmbiguousMMDD for date formats such as 04/02/2014 the mm/dd vs dd/mm are
112 // ambiguous, so it is an error for strict parse rules.
113 ErrAmbiguousMMDD = fmt.Errorf("This date has ambiguous mm/dd vs dd/mm type format")
114)
115
116func unknownErr(datestr string) error {
117 return fmt.Errorf("Could not find format for %q", datestr)
118}
119
120// ParseAny parse an unknown date format, detect the layout.
121// Normal parse. Equivalent Timezone rules as time.Parse().
122// NOTE: please see readme on mmdd vs ddmm ambiguous dates.
123func ParseAny(datestr string) (time.Time, error) {
124 p, err := parseTime(datestr, nil)
125 if err != nil {
126 return time.Time{}, err
127 }
128 return p.parse()
129}
130
131// ParseIn with Location, equivalent to time.ParseInLocation() timezone/offset
132// rules. Using location arg, if timezone/offset info exists in the
133// datestring, it uses the given location rules for any zone interpretation.
134// That is, MST means one thing when using America/Denver and something else
135// in other locations.
136func ParseIn(datestr string, loc *time.Location) (time.Time, error) {
137 p, err := parseTime(datestr, loc)
138 if err != nil {
139 return time.Time{}, err
140 }
141 return p.parse()
142}
143
144// ParseLocal Given an unknown date format, detect the layout,
145// using time.Local, parse.
146//
147// Set Location to time.Local. Same as ParseIn Location but lazily uses
148// the global time.Local variable for Location argument.
149//
150// denverLoc, _ := time.LoadLocation("America/Denver")
151// time.Local = denverLoc
152//
153// t, err := dateparse.ParseLocal("3/1/2014")
154//
155// Equivalent to:
156//
157// t, err := dateparse.ParseIn("3/1/2014", denverLoc)
158//
159func ParseLocal(datestr string) (time.Time, error) {
160 p, err := parseTime(datestr, time.Local)
161 if err != nil {
162 return time.Time{}, err
163 }
164 return p.parse()
165}
166
167// MustParse parse a date, and panic if it can't be parsed. Used for testing.
168// Not recommended for most use-cases.
169func MustParse(datestr string) time.Time {
170 p, err := parseTime(datestr, nil)
171 if err != nil {
172 panic(err.Error())
173 }
174 t, err := p.parse()
175 if err != nil {
176 panic(err.Error())
177 }
178 return t
179}
180
181// ParseFormat parse's an unknown date-time string and returns a layout
182// string that can parse this (and exact same format) other date-time strings.
183//
184// layout, err := dateparse.ParseFormat("2013-02-01 00:00:00")
185// // layout = "2006-01-02 15:04:05"
186//
187func ParseFormat(datestr string) (string, error) {
188 p, err := parseTime(datestr, nil)
189 if err != nil {
190 return "", err
191 }
192 _, err = p.parse()
193 if err != nil {
194 return "", err
195 }
196 return string(p.format), nil
197}
198
199// ParseStrict parse an unknown date format. IF the date is ambigous
200// mm/dd vs dd/mm then return an error. These return errors: 3.3.2014 , 8/8/71 etc
201func ParseStrict(datestr string) (time.Time, error) {
202 p, err := parseTime(datestr, nil)
203 if err != nil {
204 return time.Time{}, err
205 }
206 if p.ambiguousMD {
207 return time.Time{}, ErrAmbiguousMMDD
208 }
209 return p.parse()
210}
211
212func parseTime(datestr string, loc *time.Location) (*parser, error) {
213
214 p := newParser(datestr, loc)
215 i := 0
216
217 // General strategy is to read rune by rune through the date looking for
218 // certain hints of what type of date we are dealing with.
219 // Hopefully we only need to read about 5 or 6 bytes before
220 // we figure it out and then attempt a parse
221iterRunes:
222 for ; i < len(datestr); i++ {
223 //r := rune(datestr[i])
224 r, bytesConsumed := utf8.DecodeRuneInString(datestr[i:])
225 if bytesConsumed > 1 {
226 i += (bytesConsumed - 1)
227 }
228
229 //gou.Debugf("i=%d r=%s state=%d %s", i, string(r), p.stateDate, datestr)
230 switch p.stateDate {
231 case dateStart:
232 if unicode.IsDigit(r) {
233 p.stateDate = dateDigit
234 } else if unicode.IsLetter(r) {
235 p.stateDate = dateAlpha
236 } else {
237 return nil, unknownErr(datestr)
238 }
239 case dateDigit:
240
241 switch r {
242 case '-', '\u2212':
243 // 2006-01-02
244 // 2013-Feb-03
245 // 13-Feb-03
246 // 29-Jun-2016
247 if i == 4 {
248 p.stateDate = dateYearDash
249 p.yeari = 0
250 p.yearlen = i
251 p.moi = i + 1
252 p.set(0, "2006")
253 } else {
254 p.stateDate = dateDigitDash
255 }
256 case '/':
257 // 03/31/2005
258 // 2014/02/24
259 p.stateDate = dateDigitSlash
260 if i == 4 {
261 p.yearlen = i
262 p.moi = i + 1
263 p.setYear()
264 } else {
265 p.ambiguousMD = true
266 if p.preferMonthFirst {
267 if p.molen == 0 {
268 p.molen = i
269 p.setMonth()
270 p.dayi = i + 1
271 }
272 }
273 }
274
275 case '.':
276 // 3.31.2014
277 // 08.21.71
278 // 2014.05
279 p.stateDate = dateDigitDot
280 if i == 4 {
281 p.yearlen = i
282 p.moi = i + 1
283 p.setYear()
284 } else {
285 p.ambiguousMD = true
286 p.moi = 0
287 p.molen = i
288 p.setMonth()
289 p.dayi = i + 1
290 }
291
292 case ' ':
293 // 18 January 2018
294 // 8 January 2018
295 // 8 jan 2018
296 // 02 Jan 2018 23:59
297 // 02 Jan 2018 23:59:34
298 // 12 Feb 2006, 19:17
299 // 12 Feb 2006, 19:17:22
300 p.stateDate = dateDigitWs
301 p.dayi = 0
302 p.daylen = i
303 case '年':
304 // Chinese Year
305 p.stateDate = dateDigitChineseYear
306 case ',':
307 return nil, unknownErr(datestr)
308 default:
309 continue
310 }
311 p.part1Len = i
312
313 case dateYearDash:
314 // dateYearDashDashT
315 // 2006-01-02T15:04:05Z07:00
316 // dateYearDashDashWs
317 // 2013-04-01 22:43:22
318 // dateYearDashAlphaDash
319 // 2013-Feb-03
320 switch r {
321 case '-':
322 p.molen = i - p.moi
323 p.dayi = i + 1
324 p.stateDate = dateYearDashDash
325 p.setMonth()
326 default:
327 if unicode.IsLetter(r) {
328 p.stateDate = dateYearDashAlphaDash
329 }
330 }
331
332 case dateYearDashDash:
333 // dateYearDashDashT
334 // 2006-01-02T15:04:05Z07:00
335 // dateYearDashDashWs
336 // 2013-04-01 22:43:22
337 switch r {
338 case ' ':
339 p.daylen = i - p.dayi
340 p.stateDate = dateYearDashDashWs
341 p.stateTime = timeStart
342 p.setDay()
343 break iterRunes
344 case 'T':
345 p.daylen = i - p.dayi
346 p.stateDate = dateYearDashDashT
347 p.stateTime = timeStart
348 p.setDay()
349 break iterRunes
350 }
351 case dateYearDashAlphaDash:
352 // 2013-Feb-03
353 switch r {
354 case '-':
355 p.molen = i - p.moi
356 p.set(p.moi, "Jan")
357 p.dayi = i + 1
358 }
359 case dateDigitDash:
360 // 13-Feb-03
361 // 29-Jun-2016
362 if unicode.IsLetter(r) {
363 p.stateDate = dateDigitDashAlpha
364 p.moi = i
365 } else {
366 return nil, unknownErr(datestr)
367 }
368 case dateDigitDashAlpha:
369 // 13-Feb-03
370 // 28-Feb-03
371 // 29-Jun-2016
372 switch r {
373 case '-':
374 p.molen = i - p.moi
375 p.set(p.moi, "Jan")
376 p.yeari = i + 1
377 p.stateDate = dateDigitDashAlphaDash
378 }
379
380 case dateDigitDashAlphaDash:
381 // 13-Feb-03 ambiguous
382 // 28-Feb-03 ambiguous
383 // 29-Jun-2016
384 switch r {
385 case ' ':
386 // we need to find if this was 4 digits, aka year
387 // or 2 digits which makes it ambiguous year/day
388 length := i - (p.moi + p.molen + 1)
389 if length == 4 {
390 p.yearlen = 4
391 p.set(p.yeari, "2006")
392 // We now also know that part1 was the day
393 p.dayi = 0
394 p.daylen = p.part1Len
395 p.setDay()
396 } else if length == 2 {
397 // We have no idea if this is
398 // yy-mon-dd OR dd-mon-yy
399 //
400 // We are going to ASSUME (bad, bad) that it is dd-mon-yy which is a horible assumption
401 p.ambiguousMD = true
402 p.yearlen = 2
403 p.set(p.yeari, "06")
404 // We now also know that part1 was the day
405 p.dayi = 0
406 p.daylen = p.part1Len
407 p.setDay()
408 }
409 p.stateTime = timeStart
410 break iterRunes
411 }
412
413 case dateDigitSlash:
414 // 2014/07/10 06:55:38.156283
415 // 03/19/2012 10:11:59
416 // 04/2/2014 03:00:37
417 // 3/1/2012 10:11:59
418 // 4/8/2014 22:05
419 // 3/1/2014
420 // 10/13/2014
421 // 01/02/2006
422 // 1/2/06
423
424 switch r {
425 case ' ':
426 p.stateTime = timeStart
427 if p.yearlen == 0 {
428 p.yearlen = i - p.yeari
429 p.setYear()
430 } else if p.daylen == 0 {
431 p.daylen = i - p.dayi
432 p.setDay()
433 }
434 break iterRunes
435 case '/':
436 if p.yearlen > 0 {
437 // 2014/07/10 06:55:38.156283
438 if p.molen == 0 {
439 p.molen = i - p.moi
440 p.setMonth()
441 p.dayi = i + 1
442 }
443 } else if p.preferMonthFirst {
444 if p.daylen == 0 {
445 p.daylen = i - p.dayi
446 p.setDay()
447 p.yeari = i + 1
448 }
449 }
450 }
451
452 case dateDigitWs:
453 // 18 January 2018
454 // 8 January 2018
455 // 8 jan 2018
456 // 1 jan 18
457 // 02 Jan 2018 23:59
458 // 02 Jan 2018 23:59:34
459 // 12 Feb 2006, 19:17
460 // 12 Feb 2006, 19:17:22
461 switch r {
462 case ' ':
463 p.yeari = i + 1
464 //p.yearlen = 4
465 p.dayi = 0
466 p.daylen = p.part1Len
467 p.setDay()
468 p.stateTime = timeStart
469 if i > p.daylen+len(" Sep") { // November etc
470 // If len greather than space + 3 it must be full month
471 p.stateDate = dateDigitWsMolong
472 } else {
473 // If len=3, the might be Feb or May? Ie ambigous abbreviated but
474 // we can parse may with either. BUT, that means the
475 // format may not be correct?
476 // mo := strings.ToLower(datestr[p.daylen+1 : i])
477 p.moi = p.daylen + 1
478 p.molen = i - p.moi
479 p.set(p.moi, "Jan")
480 p.stateDate = dateDigitWsMoYear
481 }
482 }
483
484 case dateDigitWsMoYear:
485 // 8 jan 2018
486 // 02 Jan 2018 23:59
487 // 02 Jan 2018 23:59:34
488 // 12 Feb 2006, 19:17
489 // 12 Feb 2006, 19:17:22
490 switch r {
491 case ',':
492 p.yearlen = i - p.yeari
493 p.setYear()
494 i++
495 break iterRunes
496 case ' ':
497 p.yearlen = i - p.yeari
498 p.setYear()
499 break iterRunes
500 }
501 case dateDigitWsMolong:
502 // 18 January 2018
503 // 8 January 2018
504
505 case dateDigitChineseYear:
506 // dateDigitChineseYear
507 // 2014年04月08日
508 // weekday %Y年%m月%e日 %A %I:%M %p
509 // 2013年07月18日 星期四 10:27 上午
510 if r == ' ' {
511 p.stateDate = dateDigitChineseYearWs
512 break
513 }
514 case dateDigitDot:
515 // This is the 2nd period
516 // 3.31.2014
517 // 08.21.71
518 // 2014.05
519 // 2018.09.30
520 if r == '.' {
521 if p.moi == 0 {
522 // 3.31.2014
523 p.daylen = i - p.dayi
524 p.yeari = i + 1
525 p.setDay()
526 p.stateDate = dateDigitDotDot
527 } else {
528 // 2018.09.30
529 //p.molen = 2
530 p.molen = i - p.moi
531 p.dayi = i + 1
532 p.setMonth()
533 p.stateDate = dateDigitDotDot
534 }
535 }
536 case dateDigitDotDot:
537 // iterate all the way through
538 case dateAlpha:
539 // dateAlphaWS
540 // Mon Jan _2 15:04:05 2006
541 // Mon Jan _2 15:04:05 MST 2006
542 // Mon Jan 02 15:04:05 -0700 2006
543 // Mon Aug 10 15:44:11 UTC+0100 2015
544 // Fri Jul 03 2015 18:04:07 GMT+0100 (GMT Daylight Time)
545 // dateAlphaWSDigit
546 // May 8, 2009 5:57:51 PM
547 // oct 1, 1970
548 // dateAlphaWsMonth
549 // April 8, 2009
550 // dateAlphaWsMore
551 // dateAlphaWsAtTime
552 // January 02, 2006 at 3:04pm MST-07
553 //
554 // dateAlphaPeriodWsDigit
555 // oct. 1, 1970
556 // dateWeekdayComma
557 // Monday, 02 Jan 2006 15:04:05 MST
558 // Monday, 02-Jan-06 15:04:05 MST
559 // Monday, 02 Jan 2006 15:04:05 -0700
560 // Monday, 02 Jan 2006 15:04:05 +0100
561 // dateWeekdayAbbrevComma
562 // Mon, 02 Jan 2006 15:04:05 MST
563 // Mon, 02 Jan 2006 15:04:05 -0700
564 // Thu, 13 Jul 2017 08:58:40 +0100
565 // Tue, 11 Jul 2017 16:28:13 +0200 (CEST)
566 // Mon, 02-Jan-06 15:04:05 MST
567 switch {
568 case r == ' ':
569 // X
570 // April 8, 2009
571 if i > 3 {
572 // Check to see if the alpha is name of month? or Day?
573 month := strings.ToLower(datestr[0:i])
574 if isMonthFull(month) {
575 p.fullMonth = month
576 // len(" 31, 2018") = 9
577 if len(datestr[i:]) < 10 {
578 // April 8, 2009
579 p.stateDate = dateAlphaWsMonth
580 } else {
581 p.stateDate = dateAlphaWsMore
582 }
583 p.dayi = i + 1
584 break
585 }
586
587 } else {
588 // This is possibly ambiguous? May will parse as either though.
589 // So, it could return in-correct format.
590 // May 05, 2005, 05:05:05
591 // May 05 2005, 05:05:05
592 // Jul 05, 2005, 05:05:05
593 p.stateDate = dateAlphaWs
594 }
595
596 case r == ',':
597 // Mon, 02 Jan 2006
598 // p.moi = 0
599 // p.molen = i
600 if i == 3 {
601 p.stateDate = dateWeekdayAbbrevComma
602 p.set(0, "Mon")
603 } else {
604 p.stateDate = dateWeekdayComma
605 p.skip = i + 2
606 i++
607 // TODO: lets just make this "skip" as we don't need
608 // the mon, monday, they are all superfelous and not needed
609 // just lay down the skip, no need to fill and then skip
610 }
611 case r == '.':
612 // sept. 28, 2017
613 // jan. 28, 2017
614 p.stateDate = dateAlphaPeriodWsDigit
615 if i == 3 {
616 p.molen = i
617 p.set(0, "Jan")
618 } else if i == 4 {
619 // gross
620 datestr = datestr[0:i-1] + datestr[i:]
621 return parseTime(datestr, loc)
622 } else {
623 return nil, unknownErr(datestr)
624 }
625 }
626
627 case dateAlphaWs:
628 // dateAlphaWsAlpha
629 // Mon Jan _2 15:04:05 2006
630 // Mon Jan _2 15:04:05 MST 2006
631 // Mon Jan 02 15:04:05 -0700 2006
632 // Fri Jul 03 2015 18:04:07 GMT+0100 (GMT Daylight Time)
633 // Mon Aug 10 15:44:11 UTC+0100 2015
634 // dateAlphaWsDigit
635 // May 8, 2009 5:57:51 PM
636 // May 8 2009 5:57:51 PM
637 // oct 1, 1970
638 // oct 7, '70
639 switch {
640 case unicode.IsLetter(r):
641 p.set(0, "Mon")
642 p.stateDate = dateAlphaWsAlpha
643 p.set(i, "Jan")
644 case unicode.IsDigit(r):
645 p.set(0, "Jan")
646 p.stateDate = dateAlphaWsDigit
647 p.dayi = i
648 }
649
650 case dateAlphaWsDigit:
651 // May 8, 2009 5:57:51 PM
652 // May 8 2009 5:57:51 PM
653 // oct 1, 1970
654 // oct 7, '70
655 // oct. 7, 1970
656 if r == ',' {
657 p.daylen = i - p.dayi
658 p.setDay()
659 p.stateDate = dateAlphaWsDigitMore
660 } else if r == ' ' {
661 p.daylen = i - p.dayi
662 p.setDay()
663 p.yeari = i + 1
664 p.stateDate = dateAlphaWsDigitMoreWs
665 } else if unicode.IsLetter(r) {
666 p.stateDate = dateAlphaWsMonthSuffix
667 i--
668 }
669 case dateAlphaWsDigitMore:
670 // x
671 // May 8, 2009 5:57:51 PM
672 // May 05, 2005, 05:05:05
673 // May 05 2005, 05:05:05
674 // oct 1, 1970
675 // oct 7, '70
676 if r == ' ' {
677 p.yeari = i + 1
678 p.stateDate = dateAlphaWsDigitMoreWs
679 }
680 case dateAlphaWsDigitMoreWs:
681 // x
682 // May 8, 2009 5:57:51 PM
683 // May 05, 2005, 05:05:05
684 // oct 1, 1970
685 // oct 7, '70
686 switch r {
687 case '\'':
688 p.yeari = i + 1
689 case ' ', ',':
690 // x
691 // May 8, 2009 5:57:51 PM
692 // x
693 // May 8, 2009, 5:57:51 PM
694 p.stateDate = dateAlphaWsDigitMoreWsYear
695 p.yearlen = i - p.yeari
696 p.setYear()
697 p.stateTime = timeStart
698 break iterRunes
699 }
700
701 case dateAlphaWsAlpha:
702 // Mon Jan _2 15:04:05 2006
703 // Mon Jan 02 15:04:05 -0700 2006
704 // Mon Jan _2 15:04:05 MST 2006
705 // Mon Aug 10 15:44:11 UTC+0100 2015
706 // Fri Jul 03 2015 18:04:07 GMT+0100 (GMT Daylight Time)
707 if r == ' ' {
708 if p.dayi > 0 {
709 p.daylen = i - p.dayi
710 p.setDay()
711 p.yeari = i + 1
712 p.stateDate = dateAlphaWsAlphaYearmaybe
713 p.stateTime = timeStart
714 }
715 } else if unicode.IsDigit(r) {
716 if p.dayi == 0 {
717 p.dayi = i
718 }
719 }
720
721 case dateAlphaWsAlphaYearmaybe:
722 // x
723 // Mon Jan _2 15:04:05 2006
724 // Fri Jul 03 2015 18:04:07 GMT+0100 (GMT Daylight Time)
725 if r == ':' {
726 i = i - 3
727 p.stateDate = dateAlphaWsAlpha
728 p.yeari = 0
729 break iterRunes
730 } else if r == ' ' {
731 // must be year format, not 15:04
732 p.yearlen = i - p.yeari
733 p.setYear()
734 break iterRunes
735 }
736
737 case dateAlphaWsMonth:
738 // April 8, 2009
739 // April 8 2009
740 switch r {
741 case ' ', ',':
742 // x
743 // June 8, 2009
744 // x
745 // June 8 2009
746 if p.daylen == 0 {
747 p.daylen = i - p.dayi
748 p.setDay()
749 }
750 case 's', 'S', 'r', 'R', 't', 'T', 'n', 'N':
751 // st, rd, nd, st
752 i--
753 p.stateDate = dateAlphaWsMonthSuffix
754 default:
755 if p.daylen > 0 && p.yeari == 0 {
756 p.yeari = i
757 }
758 }
759 case dateAlphaWsMonthMore:
760 // X
761 // January 02, 2006, 15:04:05
762 // January 02 2006, 15:04:05
763 // January 02, 2006 15:04:05
764 // January 02 2006 15:04:05
765 switch r {
766 case ',':
767 p.yearlen = i - p.yeari
768 p.setYear()
769 p.stateTime = timeStart
770 i++
771 break iterRunes
772 case ' ':
773 p.yearlen = i - p.yeari
774 p.setYear()
775 p.stateTime = timeStart
776 break iterRunes
777 }
778 case dateAlphaWsMonthSuffix:
779 // x
780 // April 8th, 2009
781 // April 8th 2009
782 switch r {
783 case 't', 'T':
784 if p.nextIs(i, 'h') || p.nextIs(i, 'H') {
785 if len(datestr) > i+2 {
786 return parseTime(fmt.Sprintf("%s%s", p.datestr[0:i], p.datestr[i+2:]), loc)
787 }
788 }
789 case 'n', 'N':
790 if p.nextIs(i, 'd') || p.nextIs(i, 'D') {
791 if len(datestr) > i+2 {
792 return parseTime(fmt.Sprintf("%s%s", p.datestr[0:i], p.datestr[i+2:]), loc)
793 }
794 }
795 case 's', 'S':
796 if p.nextIs(i, 't') || p.nextIs(i, 'T') {
797 if len(datestr) > i+2 {
798 return parseTime(fmt.Sprintf("%s%s", p.datestr[0:i], p.datestr[i+2:]), loc)
799 }
800 }
801 case 'r', 'R':
802 if p.nextIs(i, 'd') || p.nextIs(i, 'D') {
803 if len(datestr) > i+2 {
804 return parseTime(fmt.Sprintf("%s%s", p.datestr[0:i], p.datestr[i+2:]), loc)
805 }
806 }
807 }
808 case dateAlphaWsMore:
809 // January 02, 2006, 15:04:05
810 // January 02 2006, 15:04:05
811 // January 2nd, 2006, 15:04:05
812 // January 2nd 2006, 15:04:05
813 // September 17, 2012 at 5:00pm UTC-05
814 switch {
815 case r == ',':
816 // x
817 // January 02, 2006, 15:04:05
818 if p.nextIs(i, ' ') {
819 p.daylen = i - p.dayi
820 p.setDay()
821 p.yeari = i + 2
822 p.stateDate = dateAlphaWsMonthMore
823 i++
824 }
825
826 case r == ' ':
827 // x
828 // January 02 2006, 15:04:05
829 p.daylen = i - p.dayi
830 p.setDay()
831 p.yeari = i + 1
832 p.stateDate = dateAlphaWsMonthMore
833 case unicode.IsDigit(r):
834 // XX
835 // January 02, 2006, 15:04:05
836 continue
837 case unicode.IsLetter(r):
838 // X
839 // January 2nd, 2006, 15:04:05
840 p.daylen = i - p.dayi
841 p.setDay()
842 p.stateDate = dateAlphaWsMonthSuffix
843 i--
844 }
845
846 case dateAlphaPeriodWsDigit:
847 // oct. 7, '70
848 switch {
849 case r == ' ':
850 // continue
851 case unicode.IsDigit(r):
852 p.stateDate = dateAlphaWsDigit
853 p.dayi = i
854 default:
855 return p, unknownErr(datestr)
856 }
857 case dateWeekdayComma:
858 // Monday, 02 Jan 2006 15:04:05 MST
859 // Monday, 02 Jan 2006 15:04:05 -0700
860 // Monday, 02 Jan 2006 15:04:05 +0100
861 // Monday, 02-Jan-06 15:04:05 MST
862 if p.dayi == 0 {
863 p.dayi = i
864 }
865 switch r {
866 case ' ', '-':
867 if p.moi == 0 {
868 p.moi = i + 1
869 p.daylen = i - p.dayi
870 p.setDay()
871 } else if p.yeari == 0 {
872 p.yeari = i + 1
873 p.molen = i - p.moi
874 p.set(p.moi, "Jan")
875 } else {
876 p.stateTime = timeStart
877 break iterRunes
878 }
879 }
880 case dateWeekdayAbbrevComma:
881 // Mon, 02 Jan 2006 15:04:05 MST
882 // Mon, 02 Jan 2006 15:04:05 -0700
883 // Thu, 13 Jul 2017 08:58:40 +0100
884 // Thu, 4 Jan 2018 17:53:36 +0000
885 // Tue, 11 Jul 2017 16:28:13 +0200 (CEST)
886 // Mon, 02-Jan-06 15:04:05 MST
887 switch r {
888 case ' ', '-':
889 if p.dayi == 0 {
890 p.dayi = i + 1
891 } else if p.moi == 0 {
892 p.daylen = i - p.dayi
893 p.setDay()
894 p.moi = i + 1
895 } else if p.yeari == 0 {
896 p.molen = i - p.moi
897 p.set(p.moi, "Jan")
898 p.yeari = i + 1
899 } else {
900 p.yearlen = i - p.yeari
901 p.setYear()
902 p.stateTime = timeStart
903 break iterRunes
904 }
905 }
906
907 default:
908 break iterRunes
909 }
910 }
911 p.coalesceDate(i)
912 if p.stateTime == timeStart {
913 // increment first one, since the i++ occurs at end of loop
914 if i < len(p.datestr) {
915 i++
916 }
917 // ensure we skip any whitespace prefix
918 for ; i < len(datestr); i++ {
919 r := rune(datestr[i])
920 if r != ' ' {
921 break
922 }
923 }
924
925 iterTimeRunes:
926 for ; i < len(datestr); i++ {
927 r := rune(datestr[i])
928
929 //gou.Debugf("%d %s %d iterTimeRunes %s %s", i, string(r), p.stateTime, p.ds(), p.ts())
930
931 switch p.stateTime {
932 case timeStart:
933 // 22:43:22
934 // 22:43
935 // timeComma
936 // 08:20:13,787
937 // timeWs
938 // 05:24:37 PM
939 // 06:20:00 UTC
940 // 06:20:00 UTC-05
941 // 00:12:00 +0000 UTC
942 // 22:18:00 +0000 UTC m=+0.000000001
943 // 15:04:05 -0700
944 // 15:04:05 -07:00
945 // 15:04:05 2008
946 // timeOffset
947 // 03:21:51+00:00
948 // 19:55:00+0100
949 // timePeriod
950 // 17:24:37.3186369
951 // 00:07:31.945167
952 // 18:31:59.257000000
953 // 00:00:00.000
954 // timePeriodOffset
955 // 19:55:00.799+0100
956 // timePeriodOffsetColon
957 // 15:04:05.999-07:00
958 // timePeriodWs
959 // timePeriodWsOffset
960 // 00:07:31.945167 +0000
961 // 00:00:00.000 +0000
962 // timePeriodWsOffsetAlpha
963 // 00:07:31.945167 +0000 UTC
964 // 22:18:00.001 +0000 UTC m=+0.000000001
965 // 00:00:00.000 +0000 UTC
966 // timePeriodWsAlpha
967 // 06:20:00.000 UTC
968 if p.houri == 0 {
969 p.houri = i
970 }
971 switch r {
972 case ',':
973 // hm, lets just swap out comma for period. for some reason go
974 // won't parse it.
975 // 2014-05-11 08:20:13,787
976 ds := []byte(p.datestr)
977 ds[i] = '.'
978 return parseTime(string(ds), loc)
979 case '-', '+':
980 // 03:21:51+00:00
981 p.stateTime = timeOffset
982 if p.seci == 0 {
983 // 22:18+0530
984 p.minlen = i - p.mini
985 } else {
986 p.seclen = i - p.seci
987 }
988 p.offseti = i
989 case '.':
990 p.stateTime = timePeriod
991 p.seclen = i - p.seci
992 p.msi = i + 1
993 case 'Z':
994 p.stateTime = timeZ
995 if p.seci == 0 {
996 p.minlen = i - p.mini
997 } else {
998 p.seclen = i - p.seci
999 }
1000 case 'a', 'A':
1001 if p.nextIs(i, 't') || p.nextIs(i, 'T') {
1002 // x
1003 // September 17, 2012 at 5:00pm UTC-05
1004 i++ // skip t
1005 if p.nextIs(i, ' ') {
1006 // x
1007 // September 17, 2012 at 5:00pm UTC-05
1008 i++ // skip '
1009 p.houri = 0 // reset hour
1010 }
1011 } else {
1012 switch {
1013 case r == 'a' && p.nextIs(i, 'm'):
1014 p.coalesceTime(i)
1015 p.set(i, "am")
1016 case r == 'A' && p.nextIs(i, 'M'):
1017 p.coalesceTime(i)
1018 p.set(i, "PM")
1019 }
1020 }
1021
1022 case 'p', 'P':
1023 // Could be AM/PM
1024 switch {
1025 case r == 'p' && p.nextIs(i, 'm'):
1026 p.coalesceTime(i)
1027 p.set(i, "pm")
1028 case r == 'P' && p.nextIs(i, 'M'):
1029 p.coalesceTime(i)
1030 p.set(i, "PM")
1031 }
1032 case ' ':
1033 p.coalesceTime(i)
1034 p.stateTime = timeWs
1035 case ':':
1036 if p.mini == 0 {
1037 p.mini = i + 1
1038 p.hourlen = i - p.houri
1039 } else if p.seci == 0 {
1040 p.seci = i + 1
1041 p.minlen = i - p.mini
1042 }
1043 }
1044 case timeOffset:
1045 // 19:55:00+0100
1046 // timeOffsetColon
1047 // 15:04:05+07:00
1048 // 15:04:05-07:00
1049 if r == ':' {
1050 p.stateTime = timeOffsetColon
1051 }
1052 case timeWs:
1053 // timeWsAlpha
1054 // 06:20:00 UTC
1055 // 06:20:00 UTC-05
1056 // 15:44:11 UTC+0100 2015
1057 // 18:04:07 GMT+0100 (GMT Daylight Time)
1058 // 17:57:51 MST 2009
1059 // timeWsAMPMMaybe
1060 // 05:24:37 PM
1061 // timeWsOffset
1062 // 15:04:05 -0700
1063 // 00:12:00 +0000 UTC
1064 // timeWsOffsetColon
1065 // 15:04:05 -07:00
1066 // 17:57:51 -0700 2009
1067 // timeWsOffsetColonAlpha
1068 // 00:12:00 +00:00 UTC
1069 // timeWsYear
1070 // 00:12:00 2008
1071 // timeZ
1072 // 15:04:05.99Z
1073 switch r {
1074 case 'A', 'P':
1075 // Could be AM/PM or could be PST or similar
1076 p.tzi = i
1077 p.stateTime = timeWsAMPMMaybe
1078 case '+', '-':
1079 p.offseti = i
1080 p.stateTime = timeWsOffset
1081 default:
1082 if unicode.IsLetter(r) {
1083 // 06:20:00 UTC
1084 // 06:20:00 UTC-05
1085 // 15:44:11 UTC+0100 2015
1086 // 17:57:51 MST 2009
1087 p.tzi = i
1088 p.stateTime = timeWsAlpha
1089 //break iterTimeRunes
1090 } else if unicode.IsDigit(r) {
1091 // 00:12:00 2008
1092 p.stateTime = timeWsYear
1093 p.yeari = i
1094 }
1095 }
1096 case timeWsAlpha:
1097 // 06:20:00 UTC
1098 // 06:20:00 UTC-05
1099 // timeWsAlphaWs
1100 // 17:57:51 MST 2009
1101 // timeWsAlphaZoneOffset
1102 // timeWsAlphaZoneOffsetWs
1103 // timeWsAlphaZoneOffsetWsExtra
1104 // 18:04:07 GMT+0100 (GMT Daylight Time)
1105 // timeWsAlphaZoneOffsetWsYear
1106 // 15:44:11 UTC+0100 2015
1107 switch r {
1108 case '+', '-':
1109 p.tzlen = i - p.tzi
1110 if p.tzlen == 4 {
1111 p.set(p.tzi, " MST")
1112 } else if p.tzlen == 3 {
1113 p.set(p.tzi, "MST")
1114 }
1115 p.stateTime = timeWsAlphaZoneOffset
1116 p.offseti = i
1117 case ' ':
1118 // 17:57:51 MST 2009
1119 p.tzlen = i - p.tzi
1120 if p.tzlen == 4 {
1121 p.set(p.tzi, " MST")
1122 } else if p.tzlen == 3 {
1123 p.set(p.tzi, "MST")
1124 }
1125 p.stateTime = timeWsAlphaWs
1126 p.yeari = i + 1
1127 }
1128 case timeWsAlphaWs:
1129 // 17:57:51 MST 2009
1130
1131 case timeWsAlphaZoneOffset:
1132 // 06:20:00 UTC-05
1133 // timeWsAlphaZoneOffset
1134 // timeWsAlphaZoneOffsetWs
1135 // timeWsAlphaZoneOffsetWsExtra
1136 // 18:04:07 GMT+0100 (GMT Daylight Time)
1137 // timeWsAlphaZoneOffsetWsYear
1138 // 15:44:11 UTC+0100 2015
1139 switch r {
1140 case ' ':
1141 p.set(p.offseti, "-0700")
1142 p.yeari = i + 1
1143 p.stateTime = timeWsAlphaZoneOffsetWs
1144 }
1145 case timeWsAlphaZoneOffsetWs:
1146 // timeWsAlphaZoneOffsetWs
1147 // timeWsAlphaZoneOffsetWsExtra
1148 // 18:04:07 GMT+0100 (GMT Daylight Time)
1149 // timeWsAlphaZoneOffsetWsYear
1150 // 15:44:11 UTC+0100 2015
1151 if unicode.IsDigit(r) {
1152 p.stateTime = timeWsAlphaZoneOffsetWsYear
1153 } else {
1154 p.extra = i - 1
1155 p.stateTime = timeWsAlphaZoneOffsetWsExtra
1156 }
1157 case timeWsAlphaZoneOffsetWsYear:
1158 // 15:44:11 UTC+0100 2015
1159 if unicode.IsDigit(r) {
1160 p.yearlen = i - p.yeari + 1
1161 if p.yearlen == 4 {
1162 p.setYear()
1163 }
1164 }
1165 case timeWsAMPMMaybe:
1166 // timeWsAMPMMaybe
1167 // timeWsAMPM
1168 // 05:24:37 PM
1169 // timeWsAlpha
1170 // 00:12:00 PST
1171 // 15:44:11 UTC+0100 2015
1172 if r == 'M' {
1173 //return parse("2006-01-02 03:04:05 PM", datestr, loc)
1174 p.stateTime = timeWsAMPM
1175 p.set(i-1, "PM")
1176 if p.hourlen == 2 {
1177 p.set(p.houri, "03")
1178 } else if p.hourlen == 1 {
1179 p.set(p.houri, "3")
1180 }
1181 } else {
1182 p.stateTime = timeWsAlpha
1183 }
1184
1185 case timeWsOffset:
1186 // timeWsOffset
1187 // 15:04:05 -0700
1188 // timeWsOffsetWsOffset
1189 // 17:57:51 -0700 -07
1190 // timeWsOffsetWs
1191 // 17:57:51 -0700 2009
1192 // 00:12:00 +0000 UTC
1193 // timeWsOffsetColon
1194 // 15:04:05 -07:00
1195 // timeWsOffsetColonAlpha
1196 // 00:12:00 +00:00 UTC
1197 switch r {
1198 case ':':
1199 p.stateTime = timeWsOffsetColon
1200 case ' ':
1201 p.set(p.offseti, "-0700")
1202 p.yeari = i + 1
1203 p.stateTime = timeWsOffsetWs
1204 }
1205 case timeWsOffsetWs:
1206 // 17:57:51 -0700 2009
1207 // 00:12:00 +0000 UTC
1208 // 22:18:00.001 +0000 UTC m=+0.000000001
1209 // w Extra
1210 // 17:57:51 -0700 -07
1211 switch r {
1212 case '=':
1213 // eff you golang
1214 if datestr[i-1] == 'm' {
1215 p.extra = i - 2
1216 p.trimExtra()
1217 break
1218 }
1219 case '+', '-':
1220 // This really doesn't seem valid, but for some reason when round-tripping a go date
1221 // their is an extra +03 printed out. seems like go bug to me, but, parsing anyway.
1222 // 00:00:00 +0300 +03
1223 // 00:00:00 +0300 +0300
1224 p.extra = i - 1
1225 p.stateTime = timeWsOffset
1226 p.trimExtra()
1227 break
1228 default:
1229 switch {
1230 case unicode.IsDigit(r):
1231 p.yearlen = i - p.yeari + 1
1232 if p.yearlen == 4 {
1233 p.setYear()
1234 }
1235 case unicode.IsLetter(r):
1236 if p.tzi == 0 {
1237 p.tzi = i
1238 }
1239 }
1240 }
1241
1242 case timeWsOffsetColon:
1243 // timeWsOffsetColon
1244 // 15:04:05 -07:00
1245 // timeWsOffsetColonAlpha
1246 // 2015-02-18 00:12:00 +00:00 UTC
1247 if unicode.IsLetter(r) {
1248 // 2015-02-18 00:12:00 +00:00 UTC
1249 p.stateTime = timeWsOffsetColonAlpha
1250 break iterTimeRunes
1251 }
1252 case timePeriod:
1253 // 15:04:05.999999999+07:00
1254 // 15:04:05.999999999-07:00
1255 // 15:04:05.999999+07:00
1256 // 15:04:05.999999-07:00
1257 // 15:04:05.999+07:00
1258 // 15:04:05.999-07:00
1259 // timePeriod
1260 // 17:24:37.3186369
1261 // 00:07:31.945167
1262 // 18:31:59.257000000
1263 // 00:00:00.000
1264 // timePeriodOffset
1265 // 19:55:00.799+0100
1266 // timePeriodOffsetColon
1267 // 15:04:05.999-07:00
1268 // timePeriodWs
1269 // timePeriodWsOffset
1270 // 00:07:31.945167 +0000
1271 // 00:00:00.000 +0000
1272 // With Extra
1273 // 00:00:00.000 +0300 +03
1274 // timePeriodWsOffsetAlpha
1275 // 00:07:31.945167 +0000 UTC
1276 // 00:00:00.000 +0000 UTC
1277 // 22:18:00.001 +0000 UTC m=+0.000000001
1278 // timePeriodWsAlpha
1279 // 06:20:00.000 UTC
1280 switch r {
1281 case ' ':
1282 p.mslen = i - p.msi
1283 p.stateTime = timePeriodWs
1284 case '+', '-':
1285 // This really shouldn't happen
1286 p.mslen = i - p.msi
1287 p.offseti = i
1288 p.stateTime = timePeriodOffset
1289 default:
1290 if unicode.IsLetter(r) {
1291 // 06:20:00.000 UTC
1292 p.mslen = i - p.msi
1293 p.stateTime = timePeriodWsAlpha
1294 }
1295 }
1296 case timePeriodOffset:
1297 // timePeriodOffset
1298 // 19:55:00.799+0100
1299 // timePeriodOffsetColon
1300 // 15:04:05.999-07:00
1301 // 13:31:51.999-07:00 MST
1302 if r == ':' {
1303 p.stateTime = timePeriodOffsetColon
1304 }
1305 case timePeriodOffsetColon:
1306 // timePeriodOffset
1307 // timePeriodOffsetColon
1308 // 15:04:05.999-07:00
1309 // 13:31:51.999 -07:00 MST
1310 switch r {
1311 case ' ':
1312 p.set(p.offseti, "-07:00")
1313 p.stateTime = timePeriodOffsetColonWs
1314 p.tzi = i + 1
1315 }
1316 case timePeriodOffsetColonWs:
1317 // continue
1318 case timePeriodWs:
1319 // timePeriodWs
1320 // timePeriodWsOffset
1321 // 00:07:31.945167 +0000
1322 // 00:00:00.000 +0000
1323 // timePeriodWsOffsetAlpha
1324 // 00:07:31.945167 +0000 UTC
1325 // 00:00:00.000 +0000 UTC
1326 // timePeriodWsOffsetColon
1327 // 13:31:51.999 -07:00 MST
1328 // timePeriodWsAlpha
1329 // 06:20:00.000 UTC
1330 if p.offseti == 0 {
1331 p.offseti = i
1332 }
1333 switch r {
1334 case '+', '-':
1335 p.mslen = i - p.msi - 1
1336 p.stateTime = timePeriodWsOffset
1337 default:
1338 if unicode.IsLetter(r) {
1339 // 00:07:31.945167 +0000 UTC
1340 // 00:00:00.000 +0000 UTC
1341 p.stateTime = timePeriodWsOffsetWsAlpha
1342 break iterTimeRunes
1343 }
1344 }
1345
1346 case timePeriodWsOffset:
1347 // timePeriodWs
1348 // timePeriodWsOffset
1349 // 00:07:31.945167 +0000
1350 // 00:00:00.000 +0000
1351 // With Extra
1352 // 00:00:00.000 +0300 +03
1353 // timePeriodWsOffsetAlpha
1354 // 00:07:31.945167 +0000 UTC
1355 // 00:00:00.000 +0000 UTC
1356 // 03:02:00.001 +0300 MSK m=+0.000000001
1357 // timePeriodWsOffsetColon
1358 // 13:31:51.999 -07:00 MST
1359 // timePeriodWsAlpha
1360 // 06:20:00.000 UTC
1361 switch r {
1362 case ':':
1363 p.stateTime = timePeriodWsOffsetColon
1364 case ' ':
1365 p.set(p.offseti, "-0700")
1366 case '+', '-':
1367 // This really doesn't seem valid, but for some reason when round-tripping a go date
1368 // their is an extra +03 printed out. seems like go bug to me, but, parsing anyway.
1369 // 00:00:00.000 +0300 +03
1370 // 00:00:00.000 +0300 +0300
1371 p.extra = i - 1
1372 p.trimExtra()
1373 break
1374 default:
1375 if unicode.IsLetter(r) {
1376 // 00:07:31.945167 +0000 UTC
1377 // 00:00:00.000 +0000 UTC
1378 // 03:02:00.001 +0300 MSK m=+0.000000001
1379 p.stateTime = timePeriodWsOffsetWsAlpha
1380 }
1381 }
1382 case timePeriodWsOffsetWsAlpha:
1383 // 03:02:00.001 +0300 MSK m=+0.000000001
1384 // eff you golang
1385 if r == '=' && datestr[i-1] == 'm' {
1386 p.extra = i - 2
1387 p.trimExtra()
1388 break
1389 }
1390
1391 case timePeriodWsOffsetColon:
1392 // 13:31:51.999 -07:00 MST
1393 switch r {
1394 case ' ':
1395 p.set(p.offseti, "-07:00")
1396 default:
1397 if unicode.IsLetter(r) {
1398 // 13:31:51.999 -07:00 MST
1399 p.tzi = i
1400 p.stateTime = timePeriodWsOffsetColonAlpha
1401 }
1402 }
1403 case timePeriodWsOffsetColonAlpha:
1404 // continue
1405 case timeZ:
1406 // timeZ
1407 // 15:04:05.99Z
1408 // With a time-zone at end after Z
1409 // 2006-01-02T15:04:05.999999999Z07:00
1410 // 2006-01-02T15:04:05Z07:00
1411 // RFC3339 = "2006-01-02T15:04:05Z07:00"
1412 // RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00"
1413 if unicode.IsDigit(r) {
1414 p.stateTime = timeZDigit
1415 }
1416
1417 }
1418 }
1419
1420 switch p.stateTime {
1421 case timeWsAlphaWs:
1422 p.yearlen = i - p.yeari
1423 p.setYear()
1424 case timeWsYear:
1425 p.yearlen = i - p.yeari
1426 p.setYear()
1427 case timeWsAlphaZoneOffsetWsExtra:
1428 p.trimExtra()
1429 case timeWsAlphaZoneOffset:
1430 // 06:20:00 UTC-05
1431 if i-p.offseti < 4 {
1432 p.set(p.offseti, "-07")
1433 } else {
1434 p.set(p.offseti, "-0700")
1435 }
1436
1437 case timePeriod:
1438 p.mslen = i - p.msi
1439 case timeOffset:
1440 // 19:55:00+0100
1441 p.set(p.offseti, "-0700")
1442 case timeWsOffset:
1443 p.set(p.offseti, "-0700")
1444 case timeWsOffsetWs:
1445 // 17:57:51 -0700 2009
1446 // 00:12:00 +0000 UTC
1447 case timeWsOffsetColon:
1448 // 17:57:51 -07:00
1449 p.set(p.offseti, "-07:00")
1450 case timeOffsetColon:
1451 // 15:04:05+07:00
1452 p.set(p.offseti, "-07:00")
1453 case timePeriodOffset:
1454 // 19:55:00.799+0100
1455 p.set(p.offseti, "-0700")
1456 case timePeriodOffsetColon:
1457 p.set(p.offseti, "-07:00")
1458 case timePeriodWsOffsetColonAlpha:
1459 p.tzlen = i - p.tzi
1460 switch p.tzlen {
1461 case 3:
1462 p.set(p.tzi, "MST")
1463 case 4:
1464 p.set(p.tzi, "MST ")
1465 }
1466 case timePeriodWsOffset:
1467 p.set(p.offseti, "-0700")
1468 }
1469 p.coalesceTime(i)
1470 }
1471
1472 switch p.stateDate {
1473 case dateDigit:
1474 // unixy timestamps ish
1475 // example ct type
1476 // 1499979655583057426 19 nanoseconds
1477 // 1499979795437000 16 micro-seconds
1478 // 20180722105203 14 yyyyMMddhhmmss
1479 // 1499979795437 13 milliseconds
1480 // 1332151919 10 seconds
1481 // 20140601 8 yyyymmdd
1482 // 2014 4 yyyy
1483 t := time.Time{}
1484 if len(datestr) == len("1499979655583057426") { // 19
1485 // nano-seconds
1486 if nanoSecs, err := strconv.ParseInt(datestr, 10, 64); err == nil {
1487 t = time.Unix(0, nanoSecs)
1488 }
1489 } else if len(datestr) == len("1499979795437000") { // 16
1490 // micro-seconds
1491 if microSecs, err := strconv.ParseInt(datestr, 10, 64); err == nil {
1492 t = time.Unix(0, microSecs*1000)
1493 }
1494 } else if len(datestr) == len("yyyyMMddhhmmss") { // 14
1495 // yyyyMMddhhmmss
1496 p.format = []byte("20060102150405")
1497 return p, nil
1498 } else if len(datestr) == len("1332151919000") { // 13
1499 if miliSecs, err := strconv.ParseInt(datestr, 10, 64); err == nil {
1500 t = time.Unix(0, miliSecs*1000*1000)
1501 }
1502 } else if len(datestr) == len("1332151919") { //10
1503 if secs, err := strconv.ParseInt(datestr, 10, 64); err == nil {
1504 t = time.Unix(secs, 0)
1505 }
1506 } else if len(datestr) == len("20140601") {
1507 p.format = []byte("20060102")
1508 return p, nil
1509 } else if len(datestr) == len("2014") {
1510 p.format = []byte("2006")
1511 return p, nil
1512 } else if len(datestr) < 4 {
1513 return nil, fmt.Errorf("unrecognized format, too short %v", datestr)
1514 }
1515 if !t.IsZero() {
1516 if loc == nil {
1517 p.t = &t
1518 return p, nil
1519 }
1520 t = t.In(loc)
1521 p.t = &t
1522 return p, nil
1523 }
1524
1525 case dateYearDash:
1526 // 2006-01
1527 return p, nil
1528
1529 case dateYearDashDash:
1530 // 2006-01-02
1531 // 2006-1-02
1532 // 2006-1-2
1533 // 2006-01-2
1534 return p, nil
1535
1536 case dateYearDashAlphaDash:
1537 // 2013-Feb-03
1538 // 2013-Feb-3
1539 p.daylen = i - p.dayi
1540 p.setDay()
1541 return p, nil
1542
1543 case dateYearDashDashWs:
1544 // 2013-04-01
1545 return p, nil
1546
1547 case dateYearDashDashT:
1548 return p, nil
1549
1550 case dateDigitDashAlphaDash:
1551 // 13-Feb-03 ambiguous
1552 // 28-Feb-03 ambiguous
1553 // 29-Jun-2016
1554 length := len(datestr) - (p.moi + p.molen + 1)
1555 if length == 4 {
1556 p.yearlen = 4
1557 p.set(p.yeari, "2006")
1558 // We now also know that part1 was the day
1559 p.dayi = 0
1560 p.daylen = p.part1Len
1561 p.setDay()
1562 } else if length == 2 {
1563 // We have no idea if this is
1564 // yy-mon-dd OR dd-mon-yy
1565 //
1566 // We are going to ASSUME (bad, bad) that it is dd-mon-yy which is a horible assumption
1567 p.ambiguousMD = true
1568 p.yearlen = 2
1569 p.set(p.yeari, "06")
1570 // We now also know that part1 was the day
1571 p.dayi = 0
1572 p.daylen = p.part1Len
1573 p.setDay()
1574 }
1575
1576 return p, nil
1577
1578 case dateDigitDot:
1579 // 2014.05
1580 p.molen = i - p.moi
1581 p.setMonth()
1582 return p, nil
1583
1584 case dateDigitDotDot:
1585 // 03.31.1981
1586 // 3.31.2014
1587 // 3.2.1981
1588 // 3.2.81
1589 // 08.21.71
1590 // 2018.09.30
1591 return p, nil
1592
1593 case dateDigitWsMoYear:
1594 // 2 Jan 2018
1595 // 2 Jan 18
1596 // 2 Jan 2018 23:59
1597 // 02 Jan 2018 23:59
1598 // 12 Feb 2006, 19:17
1599 return p, nil
1600
1601 case dateDigitWsMolong:
1602 // 18 January 2018
1603 // 8 January 2018
1604 if p.daylen == 2 {
1605 p.format = []byte("02 January 2006")
1606 return p, nil
1607 }
1608 p.format = []byte("2 January 2006")
1609 return p, nil // parse("2 January 2006", datestr, loc)
1610
1611 case dateAlphaWsMonth:
1612 p.yearlen = i - p.yeari
1613 p.setYear()
1614 return p, nil
1615
1616 case dateAlphaWsMonthMore:
1617 return p, nil
1618
1619 case dateAlphaWsDigitMoreWs:
1620 // oct 1, 1970
1621 p.yearlen = i - p.yeari
1622 p.setYear()
1623 return p, nil
1624
1625 case dateAlphaWsDigitMoreWsYear:
1626 // May 8, 2009 5:57:51 PM
1627 // Jun 7, 2005, 05:57:51
1628 return p, nil
1629
1630 case dateAlphaWsAlpha:
1631 return p, nil
1632
1633 case dateAlphaWsAlphaYearmaybe:
1634 return p, nil
1635
1636 case dateDigitSlash:
1637 // 3/1/2014
1638 // 10/13/2014
1639 // 01/02/2006
1640 // 2014/10/13
1641 return p, nil
1642
1643 case dateDigitChineseYear:
1644 // dateDigitChineseYear
1645 // 2014年04月08日
1646 p.format = []byte("2006年01月02日")
1647 return p, nil
1648
1649 case dateDigitChineseYearWs:
1650 p.format = []byte("2006年01月02日 15:04:05")
1651 return p, nil
1652
1653 case dateWeekdayComma:
1654 // Monday, 02 Jan 2006 15:04:05 -0700
1655 // Monday, 02 Jan 2006 15:04:05 +0100
1656 // Monday, 02-Jan-06 15:04:05 MST
1657 return p, nil
1658
1659 case dateWeekdayAbbrevComma:
1660 // Mon, 02-Jan-06 15:04:05 MST
1661 // Mon, 02 Jan 2006 15:04:05 MST
1662 return p, nil
1663
1664 }
1665
1666 return nil, unknownErr(datestr)
1667}
1668
1669type parser struct {
1670 loc *time.Location
1671 preferMonthFirst bool
1672 ambiguousMD bool
1673 stateDate dateState
1674 stateTime timeState
1675 format []byte
1676 datestr string
1677 fullMonth string
1678 skip int
1679 extra int
1680 part1Len int
1681 yeari int
1682 yearlen int
1683 moi int
1684 molen int
1685 dayi int
1686 daylen int
1687 houri int
1688 hourlen int
1689 mini int
1690 minlen int
1691 seci int
1692 seclen int
1693 msi int
1694 mslen int
1695 offseti int
1696 offsetlen int
1697 tzi int
1698 tzlen int
1699 t *time.Time
1700}
1701
1702func newParser(dateStr string, loc *time.Location) *parser {
1703 p := parser{
1704 stateDate: dateStart,
1705 stateTime: timeIgnore,
1706 datestr: dateStr,
1707 loc: loc,
1708 preferMonthFirst: true,
1709 }
1710 p.format = []byte(dateStr)
1711 return &p
1712}
1713
1714func (p *parser) nextIs(i int, b byte) bool {
1715 if len(p.datestr) > i+1 && p.datestr[i+1] == b {
1716 return true
1717 }
1718 return false
1719}
1720
1721func (p *parser) set(start int, val string) {
1722 if start < 0 {
1723 return
1724 }
1725 if len(p.format) < start+len(val) {
1726 return
1727 }
1728 for i, r := range val {
1729 p.format[start+i] = byte(r)
1730 }
1731}
1732func (p *parser) setMonth() {
1733 if p.molen == 2 {
1734 p.set(p.moi, "01")
1735 } else if p.molen == 1 {
1736 p.set(p.moi, "1")
1737 }
1738}
1739
1740func (p *parser) setDay() {
1741 if p.daylen == 2 {
1742 p.set(p.dayi, "02")
1743 } else if p.daylen == 1 {
1744 p.set(p.dayi, "2")
1745 }
1746}
1747func (p *parser) setYear() {
1748 if p.yearlen == 2 {
1749 p.set(p.yeari, "06")
1750 } else if p.yearlen == 4 {
1751 p.set(p.yeari, "2006")
1752 }
1753}
1754func (p *parser) coalesceDate(end int) {
1755 if p.yeari > 0 {
1756 if p.yearlen == 0 {
1757 p.yearlen = end - p.yeari
1758 }
1759 p.setYear()
1760 }
1761 if p.moi > 0 && p.molen == 0 {
1762 p.molen = end - p.moi
1763 p.setMonth()
1764 }
1765 if p.dayi > 0 && p.daylen == 0 {
1766 p.daylen = end - p.dayi
1767 p.setDay()
1768 }
1769}
1770func (p *parser) ts() string {
1771 return fmt.Sprintf("h:(%d:%d) m:(%d:%d) s:(%d:%d)", p.houri, p.hourlen, p.mini, p.minlen, p.seci, p.seclen)
1772}
1773func (p *parser) ds() string {
1774 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)
1775}
1776func (p *parser) coalesceTime(end int) {
1777 // 03:04:05
1778 // 15:04:05
1779 // 3:04:05
1780 // 3:4:5
1781 // 15:04:05.00
1782 if p.houri > 0 {
1783 if p.hourlen == 2 {
1784 p.set(p.houri, "15")
1785 } else if p.hourlen == 1 {
1786 p.set(p.houri, "3")
1787 }
1788 }
1789 if p.mini > 0 {
1790 if p.minlen == 0 {
1791 p.minlen = end - p.mini
1792 }
1793 if p.minlen == 2 {
1794 p.set(p.mini, "04")
1795 } else {
1796 p.set(p.mini, "4")
1797 }
1798 }
1799 if p.seci > 0 {
1800 if p.seclen == 0 {
1801 p.seclen = end - p.seci
1802 }
1803 if p.seclen == 2 {
1804 p.set(p.seci, "05")
1805 } else {
1806 p.set(p.seci, "5")
1807 }
1808 }
1809
1810 if p.msi > 0 {
1811 for i := 0; i < p.mslen; i++ {
1812 p.format[p.msi+i] = '0'
1813 }
1814 }
1815}
1816func (p *parser) setFullMonth(month string) {
1817 if p.moi == 0 {
1818 p.format = []byte(fmt.Sprintf("%s%s", "January", p.format[len(month):]))
1819 }
1820}
1821
1822func (p *parser) trimExtra() {
1823 if p.extra > 0 && len(p.format) > p.extra {
1824 p.format = p.format[0:p.extra]
1825 p.datestr = p.datestr[0:p.extra]
1826 }
1827}
1828
1829// func (p *parser) remove(i, length int) {
1830// if len(p.format) > i+length {
1831// //append(a[:i], a[j:]...)
1832// p.format = append(p.format[0:i], p.format[i+length:]...)
1833// }
1834// if len(p.datestr) > i+length {
1835// //append(a[:i], a[j:]...)
1836// p.datestr = fmt.Sprintf("%s%s", p.datestr[0:i], p.datestr[i+length:])
1837// }
1838// }
1839
1840func (p *parser) parse() (time.Time, error) {
1841 if p.t != nil {
1842 return *p.t, nil
1843 }
1844 if len(p.fullMonth) > 0 {
1845 p.setFullMonth(p.fullMonth)
1846 }
1847 if p.skip > 0 && len(p.format) > p.skip {
1848 p.format = p.format[p.skip:]
1849 p.datestr = p.datestr[p.skip:]
1850 }
1851 //gou.Debugf("parse %q AS %q", p.datestr, string(p.format))
1852 if p.loc == nil {
1853 return time.Parse(string(p.format), p.datestr)
1854 }
1855 return time.ParseInLocation(string(p.format), p.datestr, p.loc)
1856}
1857func isMonthFull(alpha string) bool {
1858 for _, month := range months {
1859 if alpha == month {
1860 return true
1861 }
1862 }
1863 return false
1864}