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 ( const (
dateStart dateState = iota dateStart dateState = iota
dateDigit dateDigit
dateYearDash
dateYearDashAlphaDash
dateYearDashDash
dateYearDashDashWs
dateYearDashDashT
dateDigitDash dateDigitDash
dateDigitDashDash dateDigitDashAlpha
dateDigitDashDashWs dateDigitDashAlphaDash
dateDigitDashDashT
dateDigitDashDashAlpha
dateDigitDot dateDigitDot
dateDigitDotDot dateDigitDotDot
dateDigitSlash dateDigitSlash
@ -202,167 +205,6 @@ func ParseStrict(datestr string) (time.Time, error) {
return p.parse() 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) { func parseTime(datestr string, loc *time.Location) (*parser, error) {
p := newParser(datestr, loc) p := newParser(datestr, loc)
@ -395,15 +237,17 @@ iterRunes:
switch r { switch r {
case '-', '\u2212': case '-', '\u2212':
// 2006-01-02 // 2006-01-02
// 2006-01-02T15:04:05Z07:00
// 13-Feb-03
// 2013-Feb-03 // 2013-Feb-03
p.stateDate = dateDigitDash // 13-Feb-03
p.yeari = 0 // 29-Jun-2016
p.yearlen = i
p.moi = i + 1
if i == 4 { if i == 4 {
p.stateDate = dateYearDash
p.yeari = 0
p.yearlen = i
p.moi = i + 1
p.set(0, "2006") p.set(0, "2006")
} else {
p.stateDate = dateDigitDash
} }
case '/': case '/':
// 03/31/2005 // 03/31/2005
@ -458,83 +302,110 @@ iterRunes:
case ',': case ',':
return nil, unknownErr(datestr) return nil, unknownErr(datestr)
default: default:
//if unicode.IsDigit(r) {
continue continue
} }
p.part1Len = i p.part1Len = i
case dateDigitDash: case dateYearDash:
// 2006-01 // dateYearDashDashT
// 2006-01-02
// dateDigitDashDashT
// 2006-01-02T15:04:05Z07:00 // 2006-01-02T15:04:05Z07:00
// 2017-06-25T17:46:57.45706582-07:00 // dateYearDashDashWs
// 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
// 2013-04-01 22:43:22 // 2013-04-01 22:43:22
// 2014-04-26 05:24:37 PM // dateYearDashAlphaDash
// dateDigitDashDashAlpha // 2013-Feb-03
// 2013-Feb-03
// 13-Feb-03
switch r { switch r {
case '-': case '-':
p.molen = i - p.moi p.molen = i - p.moi
p.dayi = i + 1 p.dayi = i + 1
p.stateDate = dateDigitDashDash p.stateDate = dateYearDashDash
p.setMonth() p.setMonth()
default: default:
if unicode.IsDigit(r) { if unicode.IsLetter(r) {
//continue p.stateDate = dateYearDashAlphaDash
} else if unicode.IsLetter(r) {
p.stateDate = dateDigitDashDashAlpha
} }
} }
case dateDigitDashDash:
// 2006-01-02 case dateYearDashDash:
// dateDigitDashDashT // dateYearDashDashT
// 2006-01-02T15:04:05Z07:00 // 2006-01-02T15:04:05Z07:00
// 2017-06-25T17:46:57.45706582-07:00 // dateYearDashDashWs
// 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
// 2013-04-01 22:43:22 // 2013-04-01 22:43:22
// 2014-04-26 05:24:37 PM
switch r { switch r {
case ' ': case ' ':
p.daylen = i - p.dayi p.daylen = i - p.dayi
p.stateDate = dateDigitDashDashWs p.stateDate = dateYearDashDashWs
p.stateTime = timeStart p.stateTime = timeStart
p.setDay() p.setDay()
break iterRunes break iterRunes
case 'T': case 'T':
p.daylen = i - p.dayi p.daylen = i - p.dayi
p.stateDate = dateDigitDashDashT p.stateDate = dateYearDashDashT
p.stateTime = timeStart p.stateTime = timeStart
p.setDay() p.setDay()
break iterRunes break iterRunes
} }
case dateDigitDashDashAlpha: case dateYearDashAlphaDash:
// 2013-Feb-03 // 2013-Feb-03
// 13-Feb-03
switch r { switch r {
case '-': case '-':
p.molen = i - p.moi p.molen = i - p.moi
p.set(p.moi, "Jan") p.set(p.moi, "Jan")
p.dayi = i + 1 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: case dateDigitSlash:
// 2014/07/10 06:55:38.156283 // 2014/07/10 06:55:38.156283
// 03/19/2012 10:11:59 // 03/19/2012 10:11:59
@ -1462,30 +1333,35 @@ iterRunes:
return p, nil return p, nil
} }
case dateDigitDash: case dateYearDash:
// 2006-01 // 2006-01
return p, nil return p, nil
case dateDigitDashDash: case dateYearDashDash:
// 2006-01-02 // 2006-01-02
// 2006-1-02 // 2006-1-02
// 2006-1-2 // 2006-1-2
// 2006-01-2 // 2006-01-2
return p, nil return p, nil
case dateDigitDashDashAlpha: case dateYearDashAlphaDash:
// 2013-Feb-03 // 2013-Feb-03
// 2013-Feb-3 // 2013-Feb-3
p.daylen = i - p.dayi p.daylen = i - p.dayi
p.setDay() p.setDay()
return p, nil return p, nil
case dateDigitDashDashWs: // starts digit then dash 02- then whitespace 1 << 2 << 5 + 3 case dateYearDashDashWs:
// 2013-04-01 22:43:22 // 2013-04-01
// 2013-04-01 22:43
return p, nil 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 return p, nil
case dateDigitDot: case dateDigitDot:
@ -1509,9 +1385,7 @@ iterRunes:
// 2 Jan 18 // 2 Jan 18
// 2 Jan 2018 23:59 // 2 Jan 2018 23:59
// 02 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
// 12 Feb 2006, 19:17:22
return p, nil return p, nil
case dateDigitWsMolong: case dateDigitWsMolong:
@ -1572,3 +1446,164 @@ iterRunes:
return nil, unknownErr(datestr) 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) { func TestOne(t *testing.T) {
time.Local = time.UTC time.Local = time.UTC
var ts time.Time var ts time.Time
ts = MustParse("sept. 28, 2017") ts = MustParse("11-Jun-11 18:09:59")
assert.Equal(t, "2017-09-28 00:00:00 +0000 UTC", fmt.Sprintf("%v", ts.In(time.UTC))) // "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 { 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 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 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"}, {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", 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: "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: "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"}, {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: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: "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: "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"}, {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"}, {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"}, {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"}, {in: "2013-Feb-03", out: "2013-02-03 00:00:00 +0000 UTC"},
// 03 February 2013 // 03 February 2013
{in: "03 February 2013", out: "2013-02-03 00:00:00 +0000 UTC"}, {in: "03 February 2013", out: "2013-02-03 00:00:00 +0000 UTC"},
@ -426,6 +431,9 @@ var testParseErrors = []dateTest{
{in: "xyzq-baad"}, {in: "xyzq-baad"},
{in: "oct.-7-1970", err: true}, {in: "oct.-7-1970", err: true},
{in: "septe. 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) { func TestParseErrors(t *testing.T) {
@ -475,6 +483,8 @@ func TestParseLayout(t *testing.T) {
} }
var testParseStrict = []dateTest{ var testParseStrict = []dateTest{
// dd-mon-yy 13-Feb-03
{in: "03-03-14"},
// mm.dd.yyyy // mm.dd.yyyy
{in: "3.3.2014"}, {in: "3.3.2014"},
// mm.dd.yy // mm.dd.yy