new format closes #69

This commit is contained in:
Aaron Raddon 2018-10-09 20:50:09 -07:00
parent 089f77b1d9
commit 700250988b
2 changed files with 274 additions and 229 deletions

View File

@ -38,11 +38,14 @@ type timeState uint8
const (
dateStart dateState = iota
dateDigit
dateYearDash
dateYearDashAlphaDash
dateYearDashDash
dateYearDashDashWs
dateYearDashDashT
dateDigitDash
dateDigitDashDash
dateDigitDashDashWs
dateDigitDashDashT
dateDigitDashDashAlpha
dateDigitDashAlpha
dateDigitDashAlphaDash
dateDigitDot
dateDigitDotDot
dateDigitSlash
@ -202,167 +205,6 @@ func ParseStrict(datestr string) (time.Time, error) {
return p.parse()
}
type parser struct {
loc *time.Location
preferMonthFirst bool
ambiguousMD bool
stateDate dateState
stateTime timeState
format []byte
datestr string
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
offsetlen int
tzi int
tzlen int
t *time.Time
}
func newParser(dateStr string, loc *time.Location) *parser {
p := parser{
stateDate: dateStart,
stateTime: timeIgnore,
datestr: dateStr,
loc: loc,
preferMonthFirst: true,
}
p.format = []byte(dateStr)
return &p
}
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)
}
}
func (p *parser) setMonth() {
if p.molen == 2 {
p.set(p.moi, "01")
} else if p.molen == 1 {
p.set(p.moi, "1")
}
}
func (p *parser) setDay() {
if p.daylen == 2 {
p.set(p.dayi, "02")
} else if p.daylen == 1 {
p.set(p.dayi, "2")
}
}
func (p *parser) setYear() {
if p.yearlen == 2 {
p.set(p.yeari, "06")
} else if p.yearlen == 4 {
p.set(p.yeari, "2006")
}
}
func (p *parser) coalesceDate(end int) {
if p.yeari > 0 {
if p.yearlen == 0 {
p.yearlen = end - p.yeari
}
p.setYear()
}
if p.moi > 0 && p.molen == 0 {
p.molen = end - p.moi
p.setMonth()
}
if p.dayi > 0 && p.daylen == 0 {
p.daylen = end - p.dayi
p.setDay()
}
}
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'
}
}
}
func (p *parser) trimExtra() {
if p.extra > 0 && len(p.format) > p.extra {
p.format = p.format[0:p.extra]
p.datestr = p.datestr[0:p.extra]
}
}
func (p *parser) parse() (time.Time, error) {
if p.t != nil {
return *p.t, nil
}
if p.skip > 0 && len(p.format) > p.skip {
p.format = p.format[p.skip:]
p.datestr = p.datestr[p.skip:]
}
//gou.Debugf("parse %q AS %s", p.datestr, string(p.format))
if p.loc == nil {
return time.Parse(string(p.format), p.datestr)
}
return time.ParseInLocation(string(p.format), p.datestr, p.loc)
}
func parseTime(datestr string, loc *time.Location) (*parser, error) {
p := newParser(datestr, loc)
@ -395,15 +237,17 @@ iterRunes:
switch r {
case '-', '\u2212':
// 2006-01-02
// 2006-01-02T15:04:05Z07:00
// 13-Feb-03
// 2013-Feb-03
p.stateDate = dateDigitDash
// 13-Feb-03
// 29-Jun-2016
if i == 4 {
p.stateDate = dateYearDash
p.yeari = 0
p.yearlen = i
p.moi = i + 1
if i == 4 {
p.set(0, "2006")
} else {
p.stateDate = dateDigitDash
}
case '/':
// 03/31/2005
@ -458,83 +302,110 @@ iterRunes:
case ',':
return nil, unknownErr(datestr)
default:
//if unicode.IsDigit(r) {
continue
}
p.part1Len = i
case dateDigitDash:
// 2006-01
// 2006-01-02
// dateDigitDashDashT
case dateYearDash:
// dateYearDashDashT
// 2006-01-02T15:04:05Z07:00
// 2017-06-25T17:46:57.45706582-07:00
// 2006-01-02T15:04:05.999999999Z07:00
// 2006-01-02T15:04:05+0000
// dateDigitDashDashWs
// 2012-08-03 18:31:59.257000000
// 2014-04-26 17:24:37.3186369
// 2017-01-27 00:07:31.945167
// 2016-03-14 00:00:00.000
// 2014-05-11 08:20:13,787
// 2017-07-19 03:21:51+00:00
// dateYearDashDashWs
// 2013-04-01 22:43:22
// 2014-04-26 05:24:37 PM
// dateDigitDashDashAlpha
// dateYearDashAlphaDash
// 2013-Feb-03
// 13-Feb-03
switch r {
case '-':
p.molen = i - p.moi
p.dayi = i + 1
p.stateDate = dateDigitDashDash
p.stateDate = dateYearDashDash
p.setMonth()
default:
if unicode.IsDigit(r) {
//continue
} else if unicode.IsLetter(r) {
p.stateDate = dateDigitDashDashAlpha
if unicode.IsLetter(r) {
p.stateDate = dateYearDashAlphaDash
}
}
case dateDigitDashDash:
// 2006-01-02
// dateDigitDashDashT
case dateYearDashDash:
// dateYearDashDashT
// 2006-01-02T15:04:05Z07:00
// 2017-06-25T17:46:57.45706582-07:00
// 2006-01-02T15:04:05.999999999Z07:00
// 2006-01-02T15:04:05+0000
// dateDigitDashDashWs
// 2012-08-03 18:31:59.257000000
// 2014-04-26 17:24:37.3186369
// 2017-01-27 00:07:31.945167
// 2016-03-14 00:00:00.000
// 2014-05-11 08:20:13,787
// 2017-07-19 03:21:51+00:00
// dateYearDashDashWs
// 2013-04-01 22:43:22
// 2014-04-26 05:24:37 PM
switch r {
case ' ':
p.daylen = i - p.dayi
p.stateDate = dateDigitDashDashWs
p.stateDate = dateYearDashDashWs
p.stateTime = timeStart
p.setDay()
break iterRunes
case 'T':
p.daylen = i - p.dayi
p.stateDate = dateDigitDashDashT
p.stateDate = dateYearDashDashT
p.stateTime = timeStart
p.setDay()
break iterRunes
}
case dateDigitDashDashAlpha:
case dateYearDashAlphaDash:
// 2013-Feb-03
// 13-Feb-03
switch r {
case '-':
p.molen = i - p.moi
p.set(p.moi, "Jan")
p.dayi = i + 1
}
case dateDigitDash:
// 13-Feb-03
// 29-Jun-2016
if unicode.IsLetter(r) {
p.stateDate = dateDigitDashAlpha
p.moi = i
} else {
return nil, unknownErr(datestr)
}
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
}
case dateDigitDashAlphaDash:
// 13-Feb-03 ambiguous
// 28-Feb-03 ambiguous
// 29-Jun-2016
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
p.setDay()
} else if length == 2 {
// We have no idea if this is
// yy-mon-dd OR dd-mon-yy
//
// We are going to ASSUME (bad, bad) that it is dd-mon-yy which is a horible assumption
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
p.setDay()
}
p.stateTime = timeStart
break iterRunes
}
case dateDigitSlash:
// 2014/07/10 06:55:38.156283
// 03/19/2012 10:11:59
@ -1462,30 +1333,35 @@ iterRunes:
return p, nil
}
case dateDigitDash:
case dateYearDash:
// 2006-01
return p, nil
case dateDigitDashDash:
case dateYearDashDash:
// 2006-01-02
// 2006-1-02
// 2006-1-2
// 2006-01-2
return p, nil
case dateDigitDashDashAlpha:
case dateYearDashAlphaDash:
// 2013-Feb-03
// 2013-Feb-3
p.daylen = i - p.dayi
p.setDay()
return p, nil
case dateDigitDashDashWs: // starts digit then dash 02- then whitespace 1 << 2 << 5 + 3
// 2013-04-01 22:43:22
// 2013-04-01 22:43
case dateYearDashDashWs:
// 2013-04-01
return p, nil
case dateDigitDashDashT:
case dateYearDashDashT:
return p, nil
case dateDigitDashAlphaDash:
// 13-Feb-03 ambiguous
// 28-Feb-03 ambiguous
// 29-Jun-2016
return p, nil
case dateDigitDot:
@ -1509,9 +1385,7 @@ iterRunes:
// 2 Jan 18
// 2 Jan 2018 23:59
// 02 Jan 2018 23:59
// 02 Jan 2018 23:59:45
// 12 Feb 2006, 19:17
// 12 Feb 2006, 19:17:22
return p, nil
case dateDigitWsMolong:
@ -1572,3 +1446,164 @@ iterRunes:
return nil, unknownErr(datestr)
}
type parser struct {
loc *time.Location
preferMonthFirst bool
ambiguousMD bool
stateDate dateState
stateTime timeState
format []byte
datestr string
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
offsetlen int
tzi int
tzlen int
t *time.Time
}
func newParser(dateStr string, loc *time.Location) *parser {
p := parser{
stateDate: dateStart,
stateTime: timeIgnore,
datestr: dateStr,
loc: loc,
preferMonthFirst: true,
}
p.format = []byte(dateStr)
return &p
}
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)
}
}
func (p *parser) setMonth() {
if p.molen == 2 {
p.set(p.moi, "01")
} else if p.molen == 1 {
p.set(p.moi, "1")
}
}
func (p *parser) setDay() {
if p.daylen == 2 {
p.set(p.dayi, "02")
} else if p.daylen == 1 {
p.set(p.dayi, "2")
}
}
func (p *parser) setYear() {
if p.yearlen == 2 {
p.set(p.yeari, "06")
} else if p.yearlen == 4 {
p.set(p.yeari, "2006")
}
}
func (p *parser) coalesceDate(end int) {
if p.yeari > 0 {
if p.yearlen == 0 {
p.yearlen = end - p.yeari
}
p.setYear()
}
if p.moi > 0 && p.molen == 0 {
p.molen = end - p.moi
p.setMonth()
}
if p.dayi > 0 && p.daylen == 0 {
p.daylen = end - p.dayi
p.setDay()
}
}
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'
}
}
}
func (p *parser) trimExtra() {
if p.extra > 0 && len(p.format) > p.extra {
p.format = p.format[0:p.extra]
p.datestr = p.datestr[0:p.extra]
}
}
func (p *parser) parse() (time.Time, error) {
if p.t != nil {
return *p.t, nil
}
if p.skip > 0 && len(p.format) > p.skip {
p.format = p.format[p.skip:]
p.datestr = p.datestr[p.skip:]
}
//gou.Debugf("parse %q AS %s", p.datestr, string(p.format))
if p.loc == nil {
return time.Parse(string(p.format), p.datestr)
}
return time.ParseInLocation(string(p.format), p.datestr, p.loc)
}

View File

@ -11,8 +11,9 @@ import (
func TestOne(t *testing.T) {
time.Local = time.UTC
var ts time.Time
ts = MustParse("sept. 28, 2017")
assert.Equal(t, "2017-09-28 00:00:00 +0000 UTC", fmt.Sprintf("%v", ts.In(time.UTC)))
ts = MustParse("11-Jun-11 18:09:59")
// "2016-06-29 18:09:59 +0000 UTC"
assert.Equal(t, "2011-06-11 18:09:59 +0000 UTC", fmt.Sprintf("%v", ts.In(time.UTC)))
}
type dateTest struct {
@ -100,21 +101,25 @@ var testInputs = []dateTest{
{in: "Wednesday, 2 Feb 2018 09:01:00 -0300", out: "2018-02-02 12:01:00 +0000 UTC"},
{in: "Wednesday, 2 Feb 2018 9:01:00 -0300", out: "2018-02-02 12:01:00 +0000 UTC"},
{in: "Wednesday, 2 Feb 2018 09:1:00 -0300", out: "2018-02-02 12:01:00 +0000 UTC"},
// 12 Feb 2006, 19:17:08
// dd mon yyyy 12 Feb 2006, 19:17:08
{in: "07 Feb 2004, 09:07", out: "2004-02-07 09:07:00 +0000 UTC"},
{in: "07 Feb 2004, 09:07:07", out: "2004-02-07 09:07:07 +0000 UTC"},
{in: "7 Feb 2004, 09:07:07", out: "2004-02-07 09:07:07 +0000 UTC"},
{in: "07 Feb 2004, 9:7:7", out: "2004-02-07 09:07:07 +0000 UTC"},
// DD Mon yyyy hh:mm:ss
// dd Mon yyyy hh:mm:ss
{in: "07 Feb 2004 09:07:08", out: "2004-02-07 09:07:08 +0000 UTC"},
{in: "07 Feb 2004 09:07", out: "2004-02-07 09:07:00 +0000 UTC"},
{in: "7 Feb 2004 9:7:8", out: "2004-02-07 09:07:08 +0000 UTC"},
{in: "07 Feb 2004 09:07:08.123", out: "2004-02-07 09:07:08.123 +0000 UTC"},
// 12 Feb 2006, 19:17:08 GMT
// dd-mon-yyyy 12 Feb 2006, 19:17:08 GMT
{in: "07 Feb 2004, 09:07:07 GMT", out: "2004-02-07 09:07:07 +0000 UTC"},
// 12 Feb 2006, 19:17:08 +0100
// dd-mon-yyyy 12 Feb 2006, 19:17:08 +0100
{in: "07 Feb 2004, 09:07:07 +0100", out: "2004-02-07 08:07:07 +0000 UTC"},
// 2013-Feb-03
// dd-mon-yyyy 12-Feb-2006 19:17:08
{in: "07-Feb-2004 09:07:07 +0100", out: "2004-02-07 08:07:07 +0000 UTC"},
// dd-mon-yy 12-Feb-2006 19:17:08
{in: "07-Feb-04 09:07:07 +0100", out: "2004-02-07 08:07:07 +0000 UTC"},
// yyyy-mon-dd 2013-Feb-03
{in: "2013-Feb-03", out: "2013-02-03 00:00:00 +0000 UTC"},
// 03 February 2013
{in: "03 February 2013", out: "2013-02-03 00:00:00 +0000 UTC"},
@ -426,6 +431,9 @@ var testParseErrors = []dateTest{
{in: "xyzq-baad"},
{in: "oct.-7-1970", err: true},
{in: "septe. 7, 1970", err: true},
{in: "29-06-2016", err: true},
// this is just testing the empty space up front
{in: " 2018-01-02 17:08:09 -07:00", err: true},
}
func TestParseErrors(t *testing.T) {
@ -475,6 +483,8 @@ func TestParseLayout(t *testing.T) {
}
var testParseStrict = []dateTest{
// dd-mon-yy 13-Feb-03
{in: "03-03-14"},
// mm.dd.yyyy
{in: "3.3.2014"},
// mm.dd.yy