mirror of
https://github.com/araddon/dateparse.git
synced 2025-07-04 20:33:41 +08:00
exposes monthfirst
This commit is contained in:
parent
0fb0a474d1
commit
23d378dd18
@ -37,7 +37,7 @@ func BenchmarkParseAny(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
for _, dateStr := range testDates {
|
||||
ParseAny(dateStr)
|
||||
ParseAny(dateStr, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
111
parseany.go
111
parseany.go
@ -120,12 +120,24 @@ func unknownErr(datestr string) error {
|
||||
// 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.
|
||||
func ParseAny(datestr string) (time.Time, error) {
|
||||
p, err := parseTime(datestr, nil)
|
||||
func ParseAny(datestr string, preferMonthFirst bool) (time.Time, error) {
|
||||
p, err := parseTime(datestr, nil, preferMonthFirst)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
return p.parse()
|
||||
|
||||
t, err := p.parse()
|
||||
|
||||
if err != nil && strings.Contains(err.Error(), "month out of range") {
|
||||
p, err = parseTime(datestr, nil, !preferMonthFirst)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
|
||||
t, err = p.parse()
|
||||
}
|
||||
|
||||
return t, err
|
||||
}
|
||||
|
||||
// ParseIn with Location, equivalent to time.ParseInLocation() timezone/offset
|
||||
@ -133,12 +145,24 @@ func ParseAny(datestr string) (time.Time, error) {
|
||||
// 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.
|
||||
func ParseIn(datestr string, loc *time.Location) (time.Time, error) {
|
||||
p, err := parseTime(datestr, loc)
|
||||
func ParseIn(datestr string, loc *time.Location, preferMonthFirst bool) (time.Time, error) {
|
||||
p, err := parseTime(datestr, loc, preferMonthFirst)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
return p.parse()
|
||||
|
||||
t, err := p.parse()
|
||||
|
||||
if err != nil && strings.Contains(err.Error(), "month out of range") {
|
||||
p, err = parseTime(datestr, loc, !preferMonthFirst)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
|
||||
t, err = p.parse()
|
||||
}
|
||||
|
||||
return t, err
|
||||
}
|
||||
|
||||
// ParseLocal Given an unknown date format, detect the layout,
|
||||
@ -156,20 +180,31 @@ func ParseIn(datestr string, loc *time.Location) (time.Time, error) {
|
||||
//
|
||||
// t, err := dateparse.ParseIn("3/1/2014", denverLoc)
|
||||
//
|
||||
func ParseLocal(datestr string) (time.Time, error) {
|
||||
p, err := parseTime(datestr, time.Local)
|
||||
func ParseLocal(datestr string, preferMonthFirst bool) (time.Time, error) {
|
||||
p, err := parseTime(datestr, time.Local, preferMonthFirst)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
return p.parse()
|
||||
|
||||
t, err := p.parse()
|
||||
|
||||
if err != nil && strings.Contains(err.Error(), "month out of range") {
|
||||
p, err = parseTime(datestr, time.Local, !preferMonthFirst)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
|
||||
t, err = p.parse()
|
||||
}
|
||||
|
||||
return t, err
|
||||
}
|
||||
|
||||
// MustParse parse a date, and panic if it can't be parsed. Used for testing.
|
||||
// Not recommended for most use-cases.
|
||||
func MustParse(datestr string) time.Time {
|
||||
p, err := parseTime(datestr, nil)
|
||||
func MustParse(datestr string, preferMonthFirst bool) time.Time {
|
||||
p, err := parseTime(datestr, nil, preferMonthFirst)
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
t, err := p.parse()
|
||||
if err != nil {
|
||||
@ -184,8 +219,15 @@ func MustParse(datestr string) time.Time {
|
||||
// layout, err := dateparse.ParseFormat("2013-02-01 00:00:00")
|
||||
// // layout = "2006-01-02 15:04:05"
|
||||
//
|
||||
func ParseFormat(datestr string) (string, error) {
|
||||
p, err := parseTime(datestr, nil)
|
||||
func ParseFormat(datestr string, preferMonthFirst bool) (string, error) {
|
||||
p, err := parseTime(datestr, nil, preferMonthFirst)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
_, err = p.parse()
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "month out of range") {
|
||||
p, err := parseTime(datestr, nil, !preferMonthFirst)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@ -194,12 +236,17 @@ func ParseFormat(datestr string) (string, error) {
|
||||
return "", err
|
||||
}
|
||||
return string(p.format), nil
|
||||
} else {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return string(p.format), nil
|
||||
}
|
||||
|
||||
// ParseStrict parse an unknown date format. IF the date is ambigous
|
||||
// mm/dd vs dd/mm then return an error. These return errors: 3.3.2014 , 8/8/71 etc
|
||||
func ParseStrict(datestr string) (time.Time, error) {
|
||||
p, err := parseTime(datestr, nil)
|
||||
func ParseStrict(datestr string, preferMonthFirst bool) (time.Time, error) {
|
||||
p, err := parseTime(datestr, nil, preferMonthFirst)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
@ -209,9 +256,9 @@ func ParseStrict(datestr string) (time.Time, error) {
|
||||
return p.parse()
|
||||
}
|
||||
|
||||
func parseTime(datestr string, loc *time.Location) (*parser, error) {
|
||||
func parseTime(datestr string, loc *time.Location, preferMonthFirst bool) (*parser, error) {
|
||||
|
||||
p := newParser(datestr, loc)
|
||||
p := newParser(datestr, loc, preferMonthFirst)
|
||||
i := 0
|
||||
|
||||
// General strategy is to read rune by rune through the date looking for
|
||||
@ -269,6 +316,12 @@ iterRunes:
|
||||
p.setMonth()
|
||||
p.dayi = i + 1
|
||||
}
|
||||
} else {
|
||||
if p.daylen == 0 {
|
||||
p.daylen = i
|
||||
p.setDay()
|
||||
p.moi = i + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -446,6 +499,12 @@ iterRunes:
|
||||
p.setDay()
|
||||
p.yeari = i + 1
|
||||
}
|
||||
} else {
|
||||
if p.molen == 0 {
|
||||
p.molen = i - p.moi
|
||||
p.setMonth()
|
||||
p.yeari = i + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -618,7 +677,7 @@ iterRunes:
|
||||
} else if i == 4 {
|
||||
// gross
|
||||
datestr = datestr[0:i-1] + datestr[i:]
|
||||
return parseTime(datestr, loc)
|
||||
return parseTime(datestr, loc, preferMonthFirst)
|
||||
} else {
|
||||
return nil, unknownErr(datestr)
|
||||
}
|
||||
@ -783,25 +842,25 @@ iterRunes:
|
||||
case 't', 'T':
|
||||
if p.nextIs(i, 'h') || p.nextIs(i, 'H') {
|
||||
if len(datestr) > i+2 {
|
||||
return parseTime(fmt.Sprintf("%s%s", p.datestr[0:i], p.datestr[i+2:]), loc)
|
||||
return parseTime(fmt.Sprintf("%s%s", p.datestr[0:i], p.datestr[i+2:]), loc, preferMonthFirst)
|
||||
}
|
||||
}
|
||||
case 'n', 'N':
|
||||
if p.nextIs(i, 'd') || p.nextIs(i, 'D') {
|
||||
if len(datestr) > i+2 {
|
||||
return parseTime(fmt.Sprintf("%s%s", p.datestr[0:i], p.datestr[i+2:]), loc)
|
||||
return parseTime(fmt.Sprintf("%s%s", p.datestr[0:i], p.datestr[i+2:]), loc, preferMonthFirst)
|
||||
}
|
||||
}
|
||||
case 's', 'S':
|
||||
if p.nextIs(i, 't') || p.nextIs(i, 'T') {
|
||||
if len(datestr) > i+2 {
|
||||
return parseTime(fmt.Sprintf("%s%s", p.datestr[0:i], p.datestr[i+2:]), loc)
|
||||
return parseTime(fmt.Sprintf("%s%s", p.datestr[0:i], p.datestr[i+2:]), loc, preferMonthFirst)
|
||||
}
|
||||
}
|
||||
case 'r', 'R':
|
||||
if p.nextIs(i, 'd') || p.nextIs(i, 'D') {
|
||||
if len(datestr) > i+2 {
|
||||
return parseTime(fmt.Sprintf("%s%s", p.datestr[0:i], p.datestr[i+2:]), loc)
|
||||
return parseTime(fmt.Sprintf("%s%s", p.datestr[0:i], p.datestr[i+2:]), loc, preferMonthFirst)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -975,7 +1034,7 @@ iterRunes:
|
||||
// 2014-05-11 08:20:13,787
|
||||
ds := []byte(p.datestr)
|
||||
ds[i] = '.'
|
||||
return parseTime(string(ds), loc)
|
||||
return parseTime(string(ds), loc, preferMonthFirst)
|
||||
case '-', '+':
|
||||
// 03:21:51+00:00
|
||||
p.stateTime = timeOffset
|
||||
@ -1699,13 +1758,13 @@ type parser struct {
|
||||
t *time.Time
|
||||
}
|
||||
|
||||
func newParser(dateStr string, loc *time.Location) *parser {
|
||||
func newParser(dateStr string, loc *time.Location, preferMonthFirst bool) *parser {
|
||||
p := parser{
|
||||
stateDate: dateStart,
|
||||
stateTime: timeIgnore,
|
||||
datestr: dateStr,
|
||||
loc: loc,
|
||||
preferMonthFirst: true,
|
||||
preferMonthFirst: preferMonthFirst,
|
||||
}
|
||||
p.format = []byte(dateStr)
|
||||
return &p
|
||||
|
@ -11,7 +11,7 @@ import (
|
||||
func TestOne(t *testing.T) {
|
||||
time.Local = time.UTC
|
||||
var ts time.Time
|
||||
ts = MustParse("2018.09.30")
|
||||
ts = MustParse("2018.09.30", true)
|
||||
assert.Equal(t, "2018-09-30 00:00:00 +0000 UTC", fmt.Sprintf("%v", ts.In(time.UTC)))
|
||||
}
|
||||
|
||||
@ -386,13 +386,13 @@ func TestParse(t *testing.T) {
|
||||
time.Local = time.UTC
|
||||
|
||||
zeroTime := time.Time{}.Unix()
|
||||
ts, err := ParseAny("INVALID")
|
||||
ts, err := ParseAny("INVALID", true)
|
||||
assert.Equal(t, zeroTime, ts.Unix())
|
||||
assert.NotEqual(t, nil, err)
|
||||
|
||||
assert.Equal(t, true, testDidPanic("NOT GONNA HAPPEN"))
|
||||
// https://github.com/golang/go/issues/5294
|
||||
_, err = ParseAny(time.RFC3339)
|
||||
_, err = ParseAny(time.RFC3339, true)
|
||||
assert.NotEqual(t, nil, err)
|
||||
|
||||
for _, th := range testInputs {
|
||||
@ -401,7 +401,7 @@ func TestParse(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("Expected to load location %q but got %v", th.loc, err)
|
||||
}
|
||||
ts, err = ParseIn(th.in, loc)
|
||||
ts, err = ParseIn(th.in, loc, true)
|
||||
if err != nil {
|
||||
t.Fatalf("expected to parse %q but got %v", th.in, err)
|
||||
}
|
||||
@ -411,7 +411,7 @@ func TestParse(t *testing.T) {
|
||||
panic("whoops")
|
||||
}
|
||||
} else {
|
||||
ts = MustParse(th.in)
|
||||
ts = MustParse(th.in, true)
|
||||
got := fmt.Sprintf("%v", ts.In(time.UTC))
|
||||
assert.Equal(t, th.out, got, "Expected %q but got %q from %q", th.out, got, th.in)
|
||||
if th.out != got {
|
||||
@ -424,13 +424,13 @@ func TestParse(t *testing.T) {
|
||||
|
||||
assert.Equal(t, true, testDidPanic(`{"ts":"now"}`))
|
||||
|
||||
_, err = ParseAny("138421636711122233311111") // too many digits
|
||||
_, err = ParseAny("138421636711122233311111", true) // too many digits
|
||||
assert.NotEqual(t, nil, err)
|
||||
|
||||
_, err = ParseAny("-1314")
|
||||
_, err = ParseAny("-1314", true)
|
||||
assert.NotEqual(t, nil, err)
|
||||
|
||||
_, err = ParseAny("2014-13-13 08:20:13,787") // month 13 doesn't exist so error
|
||||
_, err = ParseAny("2014-13-13 08:20:13,787", true) // month 13 doesn't exist so error
|
||||
assert.NotEqual(t, nil, err)
|
||||
}
|
||||
|
||||
@ -440,7 +440,7 @@ func testDidPanic(datestr string) (paniced bool) {
|
||||
paniced = true
|
||||
}
|
||||
}()
|
||||
MustParse(datestr)
|
||||
MustParse(datestr, true)
|
||||
return false
|
||||
}
|
||||
|
||||
@ -449,7 +449,7 @@ func TestPStruct(t *testing.T) {
|
||||
denverLoc, err := time.LoadLocation("America/Denver")
|
||||
assert.Equal(t, nil, err)
|
||||
|
||||
p := newParser("08.21.71", denverLoc)
|
||||
p := newParser("08.21.71", denverLoc, true)
|
||||
|
||||
p.setMonth()
|
||||
assert.Equal(t, 0, p.moi)
|
||||
@ -479,7 +479,7 @@ var testParseErrors = []dateTest{
|
||||
|
||||
func TestParseErrors(t *testing.T) {
|
||||
for _, th := range testParseErrors {
|
||||
v, err := ParseAny(th.in)
|
||||
v, err := ParseAny(th.in, true)
|
||||
assert.NotEqual(t, nil, err, "%v for %v", v, th.in)
|
||||
}
|
||||
}
|
||||
@ -514,7 +514,7 @@ var testParseFormat = []dateTest{
|
||||
|
||||
func TestParseLayout(t *testing.T) {
|
||||
for _, th := range testParseFormat {
|
||||
l, err := ParseFormat(th.in)
|
||||
l, err := ParseFormat(th.in, true)
|
||||
if th.err {
|
||||
assert.NotEqual(t, nil, err)
|
||||
} else {
|
||||
@ -544,14 +544,14 @@ var testParseStrict = []dateTest{
|
||||
func TestParseStrict(t *testing.T) {
|
||||
|
||||
for _, th := range testParseStrict {
|
||||
_, err := ParseStrict(th.in)
|
||||
_, err := ParseStrict(th.in, true)
|
||||
assert.NotEqual(t, nil, err)
|
||||
}
|
||||
|
||||
_, err := ParseStrict(`{"hello"}`)
|
||||
_, err := ParseStrict(`{"hello"}`, true)
|
||||
assert.NotEqual(t, nil, err)
|
||||
|
||||
_, err = ParseStrict("2009-08-12T22:15Z")
|
||||
_, err = ParseStrict("2009-08-12T22:15Z", true)
|
||||
assert.Equal(t, nil, err)
|
||||
}
|
||||
|
||||
@ -570,7 +570,7 @@ func TestInLocation(t *testing.T) {
|
||||
time.Local = time.UTC
|
||||
|
||||
// Just normal parse to test out zone/offset
|
||||
ts := MustParse("2013-02-01 00:00:00")
|
||||
ts := MustParse("2013-02-01 00:00:00", true)
|
||||
zone, offset := ts.Zone()
|
||||
assert.Equal(t, 0, offset, "Should have found offset = 0 %v", offset)
|
||||
assert.Equal(t, "UTC", zone, "Should have found zone = UTC %v", zone)
|
||||
@ -579,26 +579,26 @@ func TestInLocation(t *testing.T) {
|
||||
// Now lets set to denver (MST/MDT) and re-parse the same time string
|
||||
// and since no timezone info in string, we expect same result
|
||||
time.Local = denverLoc
|
||||
ts = MustParse("2013-02-01 00:00:00")
|
||||
ts = MustParse("2013-02-01 00:00:00", true)
|
||||
zone, offset = ts.Zone()
|
||||
assert.Equal(t, 0, offset, "Should have found offset = 0 %v", offset)
|
||||
assert.Equal(t, "UTC", zone, "Should have found zone = UTC %v", zone)
|
||||
assert.Equal(t, "2013-02-01 00:00:00 +0000 UTC", fmt.Sprintf("%v", ts.In(time.UTC)))
|
||||
|
||||
ts = MustParse("Tue, 5 Jul 2017 16:28:13 -0700 (MST)")
|
||||
ts = MustParse("Tue, 5 Jul 2017 16:28:13 -0700 (MST)", true)
|
||||
assert.Equal(t, "2017-07-05 23:28:13 +0000 UTC", fmt.Sprintf("%v", ts.In(time.UTC)))
|
||||
|
||||
// Now we are going to use ParseIn() and see that it gives different answer
|
||||
// with different zone, offset
|
||||
time.Local = nil
|
||||
ts, err = ParseIn("2013-02-01 00:00:00", denverLoc)
|
||||
ts, err = ParseIn("2013-02-01 00:00:00", denverLoc, true)
|
||||
assert.Equal(t, nil, err)
|
||||
zone, offset = ts.Zone()
|
||||
assert.Equal(t, -25200, offset, "Should have found offset = -25200 %v %v", offset, denverLoc)
|
||||
assert.Equal(t, "MST", zone, "Should have found zone = MST %v", zone)
|
||||
assert.Equal(t, "2013-02-01 07:00:00 +0000 UTC", fmt.Sprintf("%v", ts.In(time.UTC)))
|
||||
|
||||
ts, err = ParseIn("18 January 2018", denverLoc)
|
||||
ts, err = ParseIn("18 January 2018", denverLoc, true)
|
||||
assert.Equal(t, nil, err)
|
||||
zone, offset = ts.Zone()
|
||||
assert.Equal(t, -25200, offset, "Should have found offset = 0 %v", offset)
|
||||
@ -608,7 +608,7 @@ func TestInLocation(t *testing.T) {
|
||||
// Now we are going to use ParseLocal() and see that it gives same
|
||||
// answer as ParseIn when we have time.Local set to a location
|
||||
time.Local = denverLoc
|
||||
ts, err = ParseLocal("2013-02-01 00:00:00")
|
||||
ts, err = ParseLocal("2013-02-01 00:00:00", true)
|
||||
assert.Equal(t, nil, err)
|
||||
zone, offset = ts.Zone()
|
||||
assert.Equal(t, -25200, offset, "Should have found offset = -25200 %v %v", offset, denverLoc)
|
||||
@ -617,7 +617,7 @@ func TestInLocation(t *testing.T) {
|
||||
|
||||
// Lets advance past daylight savings time start
|
||||
// use parseIn and see offset/zone has changed to Daylight Savings Equivalents
|
||||
ts, err = ParseIn("2013-04-01 00:00:00", denverLoc)
|
||||
ts, err = ParseIn("2013-04-01 00:00:00", denverLoc, true)
|
||||
assert.Equal(t, nil, err)
|
||||
zone, offset = ts.Zone()
|
||||
assert.Equal(t, -21600, offset, "Should have found offset = -21600 %v %v", offset, denverLoc)
|
||||
@ -628,7 +628,7 @@ func TestInLocation(t *testing.T) {
|
||||
time.Local = time.UTC
|
||||
|
||||
// UnixDate = "Mon Jan _2 15:04:05 MST 2006"
|
||||
ts = MustParse("Mon Jan 2 15:04:05 MST 2006")
|
||||
ts = MustParse("Mon Jan 2 15:04:05 MST 2006", true)
|
||||
|
||||
_, offset = ts.Zone()
|
||||
assert.Equal(t, 0, offset, "Should have found offset = 0 %v", offset)
|
||||
@ -636,7 +636,7 @@ func TestInLocation(t *testing.T) {
|
||||
|
||||
// Now lets set to denver(mst/mdt)
|
||||
time.Local = denverLoc
|
||||
ts = MustParse("Mon Jan 2 15:04:05 MST 2006")
|
||||
ts = MustParse("Mon Jan 2 15:04:05 MST 2006", true)
|
||||
|
||||
// this time is different from one above parsed with time.Local set to UTC
|
||||
_, offset = ts.Zone()
|
||||
@ -647,25 +647,25 @@ func TestInLocation(t *testing.T) {
|
||||
time.Local = time.UTC
|
||||
|
||||
// RFC850 = "Monday, 02-Jan-06 15:04:05 MST"
|
||||
ts = MustParse("Monday, 02-Jan-06 15:04:05 MST")
|
||||
ts = MustParse("Monday, 02-Jan-06 15:04:05 MST", true)
|
||||
_, offset = ts.Zone()
|
||||
assert.Equal(t, 0, offset, "Should have found offset = 0 %v", offset)
|
||||
assert.Equal(t, "2006-01-02 15:04:05 +0000 UTC", fmt.Sprintf("%v", ts.In(time.UTC)))
|
||||
|
||||
// Now lets set to denver
|
||||
time.Local = denverLoc
|
||||
ts = MustParse("Monday, 02-Jan-06 15:04:05 MST")
|
||||
ts = MustParse("Monday, 02-Jan-06 15:04:05 MST", true)
|
||||
_, offset = ts.Zone()
|
||||
assert.NotEqual(t, 0, offset, "Should have found offset %v", offset)
|
||||
assert.Equal(t, "2006-01-02 22:04:05 +0000 UTC", fmt.Sprintf("%v", ts.In(time.UTC)))
|
||||
|
||||
// Now some errors
|
||||
zeroTime := time.Time{}.Unix()
|
||||
ts, err = ParseIn("INVALID", denverLoc)
|
||||
ts, err = ParseIn("INVALID", denverLoc, true)
|
||||
assert.Equal(t, zeroTime, ts.Unix())
|
||||
assert.NotEqual(t, nil, err)
|
||||
|
||||
ts, err = ParseLocal("INVALID")
|
||||
ts, err = ParseLocal("INVALID", true)
|
||||
assert.Equal(t, zeroTime, ts.Unix())
|
||||
assert.NotEqual(t, nil, err)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user