├── go.mod ├── .travis.yml ├── LICENSE ├── go.sum ├── bench_test.go ├── dateparse ├── main.go └── README.md ├── example └── main.go ├── README.md ├── parseany_test.go └── parseany.go /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/araddon/dateparse 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/mattn/go-runewidth v0.0.10 // indirect 7 | github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4 8 | github.com/stretchr/testify v1.7.0 9 | ) 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.13.x 5 | 6 | before_install: 7 | - go get -t -v ./... 8 | 9 | script: 10 | - go test -race -coverprofile=coverage.txt -covermode=atomic 11 | 12 | after_success: 13 | - bash <(curl -s https://codecov.io/bash) 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-2017 Aaron Raddon 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg= 4 | github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= 5 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 6 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 7 | github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY= 8 | github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 9 | github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4 h1:8qmTC5ByIXO3GP/IzBkxcZ/99VITvnIETDhdFz/om7A= 10 | github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg= 11 | github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= 12 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 13 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 14 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 15 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 16 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 17 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 18 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 19 | -------------------------------------------------------------------------------- /bench_test.go: -------------------------------------------------------------------------------- 1 | package dateparse 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | /* 10 | 11 | go test -bench Parse 12 | 13 | BenchmarkShotgunParse 50000 37588 ns/op 13258 B/op 167 allocs/op 14 | BenchmarkDateparseParseAny 500000 5752 ns/op 0 B/op 0 allocs/op 15 | 16 | // Aarons Laptop Lenovo 900 Feb 2018 17 | BenchmarkShotgunParse-4 50000 30045 ns/op 13136 B/op 169 allocs/op 18 | BenchmarkParseAny-4 200000 8627 ns/op 144 B/op 3 allocs/op 19 | 20 | // ifreddyrondon Laptop MacBook Pro (Retina, Mid 2012) March 2018 21 | BenchmarkShotgunParse-8 50000 33940 ns/op 13136 B/op 169 allocs/op 22 | BenchmarkParseAny-8 200000 10146 ns/op 912 B/op 29 allocs/op 23 | BenchmarkParseDateString-8 10000 123077 ns/op 208 B/op 13 allocs/op 24 | 25 | */ 26 | func BenchmarkShotgunParse(b *testing.B) { 27 | b.ReportAllocs() 28 | for i := 0; i < b.N; i++ { 29 | for _, dateStr := range testDates { 30 | // This is the non dateparse traditional approach 31 | parseShotgunStyle(dateStr) 32 | } 33 | } 34 | } 35 | 36 | func BenchmarkParseAny(b *testing.B) { 37 | b.ReportAllocs() 38 | for i := 0; i < b.N; i++ { 39 | for _, dateStr := range testDates { 40 | ParseAny(dateStr) 41 | } 42 | } 43 | } 44 | 45 | /* 46 | func BenchmarkParseDateString(b *testing.B) { 47 | b.ReportAllocs() 48 | for i := 0; i < b.N; i++ { 49 | for _, dateStr := range testDates { 50 | timeutils.ParseDateString(dateStr) 51 | } 52 | } 53 | } 54 | */ 55 | 56 | var ( 57 | testDates = []string{ 58 | "2012/03/19 10:11:59", 59 | "2012/03/19 10:11:59.3186369", 60 | "2009-08-12T22:15:09-07:00", 61 | "2014-04-26 17:24:37.3186369", 62 | "2012-08-03 18:31:59.257000000", 63 | "2013-04-01 22:43:22", 64 | "2014-04-26 17:24:37.123", 65 | "2014-12-16 06:20:00 UTC", 66 | "1384216367189", 67 | "1332151919", 68 | "2014-05-11 08:20:13,787", 69 | "2014-04-26 05:24:37 PM", 70 | "2014-04-26", 71 | } 72 | 73 | ErrDateFormat = fmt.Errorf("Invalid Date Format") 74 | 75 | timeFormats = []string{ 76 | // ISO 8601ish formats 77 | time.RFC3339Nano, 78 | time.RFC3339, 79 | 80 | // Unusual formats, prefer formats with timezones 81 | time.RFC1123Z, 82 | time.RFC1123, 83 | time.RFC822Z, 84 | time.RFC822, 85 | time.UnixDate, 86 | time.RubyDate, 87 | time.ANSIC, 88 | 89 | // Hilariously, Go doesn't have a const for it's own time layout. 90 | // See: https://code.google.com/p/go/issues/detail?id=6587 91 | "2006-01-02 15:04:05.999999999 -0700 MST", 92 | 93 | // No timezone information 94 | "2006-01-02T15:04:05.999999999", 95 | "2006-01-02T15:04:05", 96 | "2006-01-02 15:04:05.999999999", 97 | "2006-01-02 15:04:05", 98 | } 99 | ) 100 | 101 | func parseShotgunStyle(raw string) (time.Time, error) { 102 | 103 | for _, format := range timeFormats { 104 | t, err := time.Parse(format, raw) 105 | if err == nil { 106 | // Parsed successfully 107 | return t, nil 108 | } 109 | } 110 | return time.Time{}, ErrDateFormat 111 | } 112 | -------------------------------------------------------------------------------- /dateparse/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | "time" 8 | 9 | "github.com/scylladb/termtables" 10 | "github.com/araddon/dateparse" 11 | ) 12 | 13 | var ( 14 | timezone = "" 15 | datestr = "" 16 | ) 17 | 18 | func main() { 19 | flag.StringVar(&timezone, "timezone", "", "Timezone aka `America/Los_Angeles` formatted time-zone") 20 | flag.Parse() 21 | 22 | if len(flag.Args()) == 0 { 23 | fmt.Println(`Must pass a time, and optional location: 24 | 25 | ./dateparse "2009-08-12T22:15:09.99Z" 26 | 27 | ./dateparse --timezone="America/Denver" "2017-07-19 03:21:51+00:00" 28 | `) 29 | return 30 | } 31 | 32 | datestr = flag.Args()[0] 33 | 34 | layout, err := dateparse.ParseFormat(datestr) 35 | if err != nil { 36 | fatal(err) 37 | } 38 | 39 | zonename, _ := time.Now().In(time.Local).Zone() 40 | fmt.Printf("\nYour Current time.Local zone is %v\n", zonename) 41 | fmt.Printf("\nLayout String: dateparse.ParseFormat() => %v\n", layout) 42 | var loc *time.Location 43 | if timezone != "" { 44 | // NOTE: This is very, very important to understand 45 | // time-parsing in go 46 | l, err := time.LoadLocation(timezone) 47 | if err != nil { 48 | fatal(err) 49 | } 50 | loc = l 51 | zonename, _ := time.Now().In(l).Zone() 52 | fmt.Printf("\nYour Using time.Local set to location=%s %v \n", timezone, zonename) 53 | } 54 | fmt.Printf("\n") 55 | 56 | table := termtables.CreateTable() 57 | 58 | table.AddHeaders("method", "Zone Source", "Parsed", "Parsed: t.In(time.UTC)") 59 | 60 | parsers := map[string]parser{ 61 | "ParseAny": parseAny, 62 | "ParseIn": parseIn, 63 | "ParseLocal": parseLocal, 64 | "ParseStrict": parseStrict, 65 | } 66 | 67 | for name, parser := range parsers { 68 | time.Local = nil 69 | table.AddRow(name, "time.Local = nil", parser(datestr, nil, false), parser(datestr, nil, true)) 70 | if timezone != "" { 71 | time.Local = loc 72 | table.AddRow(name, "time.Local = timezone arg", parser(datestr, loc, false), parser(datestr, loc, true)) 73 | } 74 | time.Local = time.UTC 75 | table.AddRow(name, "time.Local = time.UTC", parser(datestr, time.UTC, false), parser(datestr, time.UTC, true)) 76 | } 77 | 78 | fmt.Println(table.Render()) 79 | } 80 | 81 | type parser func(datestr string, loc *time.Location, utc bool) string 82 | 83 | func parseLocal(datestr string, loc *time.Location, utc bool) string { 84 | time.Local = loc 85 | t, err := dateparse.ParseLocal(datestr) 86 | if err != nil { 87 | return err.Error() 88 | } 89 | if utc { 90 | return t.In(time.UTC).String() 91 | } 92 | return t.String() 93 | } 94 | 95 | func parseIn(datestr string, loc *time.Location, utc bool) string { 96 | t, err := dateparse.ParseIn(datestr, loc) 97 | if err != nil { 98 | return err.Error() 99 | } 100 | if utc { 101 | return t.In(time.UTC).String() 102 | } 103 | return t.String() 104 | } 105 | 106 | func parseAny(datestr string, loc *time.Location, utc bool) string { 107 | t, err := dateparse.ParseAny(datestr) 108 | if err != nil { 109 | return err.Error() 110 | } 111 | if utc { 112 | return fmt.Sprintf("%s day=%d", t.In(time.UTC), t.In(time.UTC).Weekday()) 113 | } 114 | return t.String() 115 | } 116 | 117 | func parseStrict(datestr string, loc *time.Location, utc bool) string { 118 | t, err := dateparse.ParseStrict(datestr) 119 | if err != nil { 120 | return err.Error() 121 | } 122 | if utc { 123 | return t.In(time.UTC).String() 124 | } 125 | return t.String() 126 | } 127 | 128 | func fatal(err error) { 129 | fmt.Printf("fatal: %s\n", err) 130 | os.Exit(1) 131 | } 132 | -------------------------------------------------------------------------------- /dateparse/README.md: -------------------------------------------------------------------------------- 1 | DateParse CLI 2 | ---------------------- 3 | 4 | Simple CLI to test out dateparse. 5 | 6 | 7 | ```sh 8 | 9 | # Since this date string has no timezone/offset so is more effected by 10 | # which method you use to parse 11 | 12 | $ dateparse --timezone="America/Denver" "2017-07-19 03:21:00" 13 | 14 | Your Current time.Local zone is PDT 15 | 16 | Layout String: dateparse.ParseFormat() => 2006-01-02 15:04:05 17 | 18 | Your Using time.Local set to location=America/Denver MDT 19 | 20 | +-------------+---------------------------+-------------------------------+-------------------------------------+ 21 | | method | Zone Source | Parsed | Parsed: t.In(time.UTC) | 22 | +-------------+---------------------------+-------------------------------+-------------------------------------+ 23 | | ParseAny | time.Local = nil | 2017-07-19 03:21:00 +0000 UTC | 2017-07-19 03:21:00 +0000 UTC day=3 | 24 | | ParseAny | time.Local = timezone arg | 2017-07-19 03:21:00 +0000 UTC | 2017-07-19 03:21:00 +0000 UTC day=3 | 25 | | ParseAny | time.Local = time.UTC | 2017-07-19 03:21:00 +0000 UTC | 2017-07-19 03:21:00 +0000 UTC day=3 | 26 | | ParseIn | time.Local = nil | 2017-07-19 03:21:00 +0000 UTC | 2017-07-19 03:21:00 +0000 UTC | 27 | | ParseIn | time.Local = timezone arg | 2017-07-19 03:21:00 -0600 MDT | 2017-07-19 09:21:00 +0000 UTC | 28 | | ParseIn | time.Local = time.UTC | 2017-07-19 03:21:00 +0000 UTC | 2017-07-19 03:21:00 +0000 UTC | 29 | | ParseLocal | time.Local = nil | 2017-07-19 03:21:00 +0000 UTC | 2017-07-19 03:21:00 +0000 UTC | 30 | | ParseLocal | time.Local = timezone arg | 2017-07-19 03:21:00 -0600 MDT | 2017-07-19 09:21:00 +0000 UTC | 31 | | ParseLocal | time.Local = time.UTC | 2017-07-19 03:21:00 +0000 UTC | 2017-07-19 03:21:00 +0000 UTC | 32 | | ParseStrict | time.Local = nil | 2017-07-19 03:21:00 +0000 UTC | 2017-07-19 03:21:00 +0000 UTC | 33 | | ParseStrict | time.Local = timezone arg | 2017-07-19 03:21:00 +0000 UTC | 2017-07-19 03:21:00 +0000 UTC | 34 | | ParseStrict | time.Local = time.UTC | 2017-07-19 03:21:00 +0000 UTC | 2017-07-19 03:21:00 +0000 UTC | 35 | +-------------+---------------------------+-------------------------------+-------------------------------------+ 36 | 37 | 38 | # Note on this one that the outputed zone is always UTC/0 offset as opposed to above 39 | 40 | $ dateparse --timezone="America/Denver" "2017-07-19 03:21:51+00:00" 41 | 42 | Your Current time.Local zone is PDT 43 | 44 | Layout String: dateparse.ParseFormat() => 2006-01-02 15:04:05-07:00 45 | 46 | Your Using time.Local set to location=America/Denver MDT 47 | 48 | +-------------+---------------------------+---------------------------------+-------------------------------------+ 49 | | method | Zone Source | Parsed | Parsed: t.In(time.UTC) | 50 | +-------------+---------------------------+---------------------------------+-------------------------------------+ 51 | | ParseAny | time.Local = nil | 2017-07-19 03:21:51 +0000 UTC | 2017-07-19 03:21:51 +0000 UTC day=3 | 52 | | ParseAny | time.Local = timezone arg | 2017-07-19 03:21:51 +0000 +0000 | 2017-07-19 03:21:51 +0000 UTC day=3 | 53 | | ParseAny | time.Local = time.UTC | 2017-07-19 03:21:51 +0000 UTC | 2017-07-19 03:21:51 +0000 UTC day=3 | 54 | | ParseIn | time.Local = nil | 2017-07-19 03:21:51 +0000 UTC | 2017-07-19 03:21:51 +0000 UTC | 55 | | ParseIn | time.Local = timezone arg | 2017-07-19 03:21:51 +0000 +0000 | 2017-07-19 03:21:51 +0000 UTC | 56 | | ParseIn | time.Local = time.UTC | 2017-07-19 03:21:51 +0000 UTC | 2017-07-19 03:21:51 +0000 UTC | 57 | | ParseLocal | time.Local = nil | 2017-07-19 03:21:51 +0000 UTC | 2017-07-19 03:21:51 +0000 UTC | 58 | | ParseLocal | time.Local = timezone arg | 2017-07-19 03:21:51 +0000 +0000 | 2017-07-19 03:21:51 +0000 UTC | 59 | | ParseLocal | time.Local = time.UTC | 2017-07-19 03:21:51 +0000 UTC | 2017-07-19 03:21:51 +0000 UTC | 60 | | ParseStrict | time.Local = nil | 2017-07-19 03:21:51 +0000 UTC | 2017-07-19 03:21:51 +0000 UTC | 61 | | ParseStrict | time.Local = timezone arg | 2017-07-19 03:21:51 +0000 +0000 | 2017-07-19 03:21:51 +0000 UTC | 62 | | ParseStrict | time.Local = time.UTC | 2017-07-19 03:21:51 +0000 UTC | 2017-07-19 03:21:51 +0000 UTC | 63 | +-------------+---------------------------+---------------------------------+-------------------------------------+ 64 | 65 | 66 | $ dateparse --timezone="America/Denver" "Monday, 19-Jul-17 03:21:00 MDT" 67 | 68 | Your Current time.Local zone is PDT 69 | 70 | Layout String: dateparse.ParseFormat() => 02-Jan-06 15:04:05 MDT 71 | 72 | Your Using time.Local set to location=America/Denver MDT 73 | 74 | +-------------+---------------------------+-------------------------------+-------------------------------------+ 75 | | method | Zone Source | Parsed | Parsed: t.In(time.UTC) | 76 | +-------------+---------------------------+-------------------------------+-------------------------------------+ 77 | | ParseStrict | time.Local = nil | 2017-07-19 03:21:00 +0000 UTC | 2017-07-19 03:21:00 +0000 UTC | 78 | | ParseStrict | time.Local = timezone arg | 2017-07-19 03:21:00 +0000 UTC | 2017-07-19 03:21:00 +0000 UTC | 79 | | ParseStrict | time.Local = time.UTC | 2017-07-19 03:21:00 +0000 UTC | 2017-07-19 03:21:00 +0000 UTC | 80 | | ParseAny | time.Local = nil | 2017-07-19 03:21:00 +0000 UTC | 2017-07-19 03:21:00 +0000 UTC day=3 | 81 | | ParseAny | time.Local = timezone arg | 2017-07-19 03:21:00 +0000 UTC | 2017-07-19 03:21:00 +0000 UTC day=3 | 82 | | ParseAny | time.Local = time.UTC | 2017-07-19 03:21:00 +0000 UTC | 2017-07-19 03:21:00 +0000 UTC day=3 | 83 | | ParseIn | time.Local = nil | 2017-07-19 03:21:00 +0000 UTC | 2017-07-19 03:21:00 +0000 UTC | 84 | | ParseIn | time.Local = timezone arg | 2017-07-19 03:21:00 -0600 MDT | 2017-07-19 09:21:00 +0000 UTC | 85 | | ParseIn | time.Local = time.UTC | 2017-07-19 03:21:00 +0000 UTC | 2017-07-19 03:21:00 +0000 UTC | 86 | | ParseLocal | time.Local = nil | 2017-07-19 03:21:00 +0000 UTC | 2017-07-19 03:21:00 +0000 UTC | 87 | | ParseLocal | time.Local = timezone arg | 2017-07-19 03:21:00 -0600 MDT | 2017-07-19 09:21:00 +0000 UTC | 88 | | ParseLocal | time.Local = time.UTC | 2017-07-19 03:21:00 +0000 UTC | 2017-07-19 03:21:00 +0000 UTC | 89 | +-------------+---------------------------+-------------------------------+-------------------------------------+ 90 | 91 | 92 | 93 | # pass in a wrong timezone "MST" (should be MDT) 94 | $ dateparse --timezone="America/Denver" "Monday, 19-Jul-17 03:21:00 MST" 95 | 96 | Your Current time.Local zone is PDT 97 | 98 | Layout String: dateparse.ParseFormat() => 02-Jan-06 15:04:05 MST 99 | 100 | Your Using time.Local set to location=America/Denver MDT 101 | 102 | +-------------+---------------------------+-------------------------------+-------------------------------------+ 103 | | method | Zone Source | Parsed | Parsed: t.In(time.UTC) | 104 | +-------------+---------------------------+-------------------------------+-------------------------------------+ 105 | | ParseAny | time.Local = nil | 2017-07-19 03:21:00 +0000 MST | 2017-07-19 03:21:00 +0000 UTC day=3 | 106 | | ParseAny | time.Local = timezone arg | 2017-07-19 04:21:00 -0600 MDT | 2017-07-19 10:21:00 +0000 UTC day=3 | 107 | | ParseAny | time.Local = time.UTC | 2017-07-19 03:21:00 +0000 MST | 2017-07-19 03:21:00 +0000 UTC day=3 | 108 | | ParseIn | time.Local = nil | 2017-07-19 03:21:00 +0000 MST | 2017-07-19 03:21:00 +0000 UTC | 109 | | ParseIn | time.Local = timezone arg | 2017-07-19 04:21:00 -0600 MDT | 2017-07-19 10:21:00 +0000 UTC | 110 | | ParseIn | time.Local = time.UTC | 2017-07-19 03:21:00 +0000 MST | 2017-07-19 03:21:00 +0000 UTC | 111 | | ParseLocal | time.Local = nil | 2017-07-19 03:21:00 +0000 MST | 2017-07-19 03:21:00 +0000 UTC | 112 | | ParseLocal | time.Local = timezone arg | 2017-07-19 04:21:00 -0600 MDT | 2017-07-19 10:21:00 +0000 UTC | 113 | | ParseLocal | time.Local = time.UTC | 2017-07-19 03:21:00 +0000 MST | 2017-07-19 03:21:00 +0000 UTC | 114 | | ParseStrict | time.Local = nil | 2017-07-19 03:21:00 +0000 MST | 2017-07-19 03:21:00 +0000 UTC | 115 | | ParseStrict | time.Local = timezone arg | 2017-07-19 04:21:00 -0600 MDT | 2017-07-19 10:21:00 +0000 UTC | 116 | | ParseStrict | time.Local = time.UTC | 2017-07-19 03:21:00 +0000 MST | 2017-07-19 03:21:00 +0000 UTC | 117 | +-------------+---------------------------+-------------------------------+-------------------------------------+ 118 | 119 | 120 | 121 | # note, we are using America/New_York which doesn't recognize MDT so essentially ignores it 122 | $ dateparse --timezone="America/New_York" "Monday, 19-Jul-17 03:21:00 MDT" 123 | 124 | Your Current time.Local zone is PDT 125 | 126 | Layout String: dateparse.ParseFormat() => 02-Jan-06 15:04:05 MDT 127 | 128 | Your Using time.Local set to location=America/New_York EDT 129 | 130 | +-------------+---------------------------+-------------------------------+-------------------------------------+ 131 | | method | Zone Source | Parsed | Parsed: t.In(time.UTC) | 132 | +-------------+---------------------------+-------------------------------+-------------------------------------+ 133 | | ParseAny | time.Local = nil | 2017-07-19 03:21:00 +0000 UTC | 2017-07-19 03:21:00 +0000 UTC day=3 | 134 | | ParseAny | time.Local = timezone arg | 2017-07-19 03:21:00 +0000 UTC | 2017-07-19 03:21:00 +0000 UTC day=3 | 135 | | ParseAny | time.Local = time.UTC | 2017-07-19 03:21:00 +0000 UTC | 2017-07-19 03:21:00 +0000 UTC day=3 | 136 | | ParseIn | time.Local = nil | 2017-07-19 03:21:00 +0000 UTC | 2017-07-19 03:21:00 +0000 UTC | 137 | | ParseIn | time.Local = timezone arg | 2017-07-19 03:21:00 -0400 EDT | 2017-07-19 07:21:00 +0000 UTC | 138 | | ParseIn | time.Local = time.UTC | 2017-07-19 03:21:00 +0000 UTC | 2017-07-19 03:21:00 +0000 UTC | 139 | | ParseLocal | time.Local = nil | 2017-07-19 03:21:00 +0000 UTC | 2017-07-19 03:21:00 +0000 UTC | 140 | | ParseLocal | time.Local = timezone arg | 2017-07-19 03:21:00 -0400 EDT | 2017-07-19 07:21:00 +0000 UTC | 141 | | ParseLocal | time.Local = time.UTC | 2017-07-19 03:21:00 +0000 UTC | 2017-07-19 03:21:00 +0000 UTC | 142 | | ParseStrict | time.Local = nil | 2017-07-19 03:21:00 +0000 UTC | 2017-07-19 03:21:00 +0000 UTC | 143 | | ParseStrict | time.Local = timezone arg | 2017-07-19 03:21:00 +0000 UTC | 2017-07-19 03:21:00 +0000 UTC | 144 | | ParseStrict | time.Local = time.UTC | 2017-07-19 03:21:00 +0000 UTC | 2017-07-19 03:21:00 +0000 UTC | 145 | +-------------+---------------------------+-------------------------------+-------------------------------------+ 146 | 147 | $ dateparse --timezone="America/New_York" "03/03/2017" 148 | 149 | Your Current time.Local zone is PDT 150 | 151 | Layout String: dateparse.ParseFormat() => 01/02/2006 152 | 153 | Your Using time.Local set to location=America/New_York EDT 154 | 155 | +-------------+---------------------------+----------------------------------------------------+----------------------------------------------------+ 156 | | method | Zone Source | Parsed | Parsed: t.In(time.UTC) | 157 | +-------------+---------------------------+----------------------------------------------------+----------------------------------------------------+ 158 | | ParseIn | time.Local = nil | 2017-03-03 00:00:00 +0000 UTC | 2017-03-03 00:00:00 +0000 UTC | 159 | | ParseIn | time.Local = timezone arg | 2017-03-03 00:00:00 -0500 EST | 2017-03-03 05:00:00 +0000 UTC | 160 | | ParseIn | time.Local = time.UTC | 2017-03-03 00:00:00 +0000 UTC | 2017-03-03 00:00:00 +0000 UTC | 161 | | ParseLocal | time.Local = nil | 2017-03-03 00:00:00 +0000 UTC | 2017-03-03 00:00:00 +0000 UTC | 162 | | ParseLocal | time.Local = timezone arg | 2017-03-03 00:00:00 -0500 EST | 2017-03-03 05:00:00 +0000 UTC | 163 | | ParseLocal | time.Local = time.UTC | 2017-03-03 00:00:00 +0000 UTC | 2017-03-03 00:00:00 +0000 UTC | 164 | | ParseStrict | time.Local = nil | This date has ambiguous mm/dd vs dd/mm type format | This date has ambiguous mm/dd vs dd/mm type format | 165 | | ParseStrict | time.Local = timezone arg | This date has ambiguous mm/dd vs dd/mm type format | This date has ambiguous mm/dd vs dd/mm type format | 166 | | ParseStrict | time.Local = time.UTC | This date has ambiguous mm/dd vs dd/mm type format | This date has ambiguous mm/dd vs dd/mm type format | 167 | | ParseAny | time.Local = nil | 2017-03-03 00:00:00 +0000 UTC | 2017-03-03 00:00:00 +0000 UTC day=5 | 168 | | ParseAny | time.Local = timezone arg | 2017-03-03 00:00:00 +0000 UTC | 2017-03-03 00:00:00 +0000 UTC day=5 | 169 | | ParseAny | time.Local = time.UTC | 2017-03-03 00:00:00 +0000 UTC | 2017-03-03 00:00:00 +0000 UTC day=5 | 170 | +-------------+---------------------------+----------------------------------------------------+----------------------------------------------------+ 171 | 172 | ``` -------------------------------------------------------------------------------- /example/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/araddon/dateparse" 9 | "github.com/scylladb/termtables" 10 | ) 11 | 12 | var examples = []string{ 13 | "May 8, 2009 5:57:51 PM", 14 | "oct 7, 1970", 15 | "oct 7, '70", 16 | "oct. 7, 1970", 17 | "oct. 7, 70", 18 | "Mon Jan 2 15:04:05 2006", 19 | "Mon Jan 2 15:04:05 MST 2006", 20 | "Mon Jan 02 15:04:05 -0700 2006", 21 | "Monday, 02-Jan-06 15:04:05 MST", 22 | "Mon, 02 Jan 2006 15:04:05 MST", 23 | "Tue, 11 Jul 2017 16:28:13 +0200 (CEST)", 24 | "Mon, 02 Jan 2006 15:04:05 -0700", 25 | "Mon 30 Sep 2018 09:09:09 PM UTC", 26 | "Mon Aug 10 15:44:11 UTC+0100 2015", 27 | "Thu, 4 Jan 2018 17:53:36 +0000", 28 | "Fri Jul 03 2015 18:04:07 GMT+0100 (GMT Daylight Time)", 29 | "Sun, 3 Jan 2021 00:12:23 +0800 (GMT+08:00)", 30 | "September 17, 2012 10:09am", 31 | "September 17, 2012 at 10:09am PST-08", 32 | "September 17, 2012, 10:10:09", 33 | "October 7, 1970", 34 | "October 7th, 1970", 35 | "12 Feb 2006, 19:17", 36 | "12 Feb 2006 19:17", 37 | "14 May 2019 19:11:40.164", 38 | "7 oct 70", 39 | "7 oct 1970", 40 | "03 February 2013", 41 | "1 July 2013", 42 | "2013-Feb-03", 43 | // dd/Mon/yyy alpha Months 44 | "06/Jan/2008:15:04:05 -0700", 45 | "06/Jan/2008 15:04:05 -0700", 46 | // mm/dd/yy 47 | "3/31/2014", 48 | "03/31/2014", 49 | "08/21/71", 50 | "8/1/71", 51 | "4/8/2014 22:05", 52 | "04/08/2014 22:05", 53 | "4/8/14 22:05", 54 | "04/2/2014 03:00:51", 55 | "8/8/1965 12:00:00 AM", 56 | "8/8/1965 01:00:01 PM", 57 | "8/8/1965 01:00 PM", 58 | "8/8/1965 1:00 PM", 59 | "8/8/1965 12:00 AM", 60 | "4/02/2014 03:00:51", 61 | "03/19/2012 10:11:59", 62 | "03/19/2012 10:11:59.3186369", 63 | // yyyy/mm/dd 64 | "2014/3/31", 65 | "2014/03/31", 66 | "2014/4/8 22:05", 67 | "2014/04/08 22:05", 68 | "2014/04/2 03:00:51", 69 | "2014/4/02 03:00:51", 70 | "2012/03/19 10:11:59", 71 | "2012/03/19 10:11:59.3186369", 72 | // yyyy:mm:dd 73 | "2014:3:31", 74 | "2014:03:31", 75 | "2014:4:8 22:05", 76 | "2014:04:08 22:05", 77 | "2014:04:2 03:00:51", 78 | "2014:4:02 03:00:51", 79 | "2012:03:19 10:11:59", 80 | "2012:03:19 10:11:59.3186369", 81 | // Chinese 82 | "2014年04月08日", 83 | // yyyy-mm-ddThh 84 | "2006-01-02T15:04:05+0000", 85 | "2009-08-12T22:15:09-07:00", 86 | "2009-08-12T22:15:09", 87 | "2009-08-12T22:15:09.988", 88 | "2009-08-12T22:15:09Z", 89 | "2017-07-19T03:21:51:897+0100", 90 | "2019-05-29T08:41-04", // no seconds, 2 digit TZ offset 91 | // yyyy-mm-dd hh:mm:ss 92 | "2014-04-26 17:24:37.3186369", 93 | "2012-08-03 18:31:59.257000000", 94 | "2014-04-26 17:24:37.123", 95 | "2013-04-01 22:43", 96 | "2013-04-01 22:43:22", 97 | "2014-12-16 06:20:00 UTC", 98 | "2014-12-16 06:20:00 GMT", 99 | "2014-04-26 05:24:37 PM", 100 | "2014-04-26 13:13:43 +0800", 101 | "2014-04-26 13:13:43 +0800 +08", 102 | "2014-04-26 13:13:44 +09:00", 103 | "2012-08-03 18:31:59.257000000 +0000 UTC", 104 | "2015-09-30 18:48:56.35272715 +0000 UTC", 105 | "2015-02-18 00:12:00 +0000 GMT", 106 | "2015-02-18 00:12:00 +0000 UTC", 107 | "2015-02-08 03:02:00 +0300 MSK m=+0.000000001", 108 | "2015-02-08 03:02:00.001 +0300 MSK m=+0.000000001", 109 | "2017-07-19 03:21:51+00:00", 110 | "2014-04-26", 111 | "2014-04", 112 | "2014", 113 | "2014-05-11 08:20:13,787", 114 | // yyyy-mm-dd-07:00 115 | "2020-07-20+08:00", 116 | // mm.dd.yy 117 | "3.31.2014", 118 | "03.31.2014", 119 | "08.21.71", 120 | "2014.03", 121 | "2014.03.30", 122 | // yyyymmdd and similar 123 | "20140601", 124 | "20140722105203", 125 | // yymmdd hh:mm:yy mysql log 126 | // 080313 05:21:55 mysqld started 127 | "171113 14:14:20", 128 | // unix seconds, ms, micro, nano 129 | "1332151919", 130 | "1384216367189", 131 | "1384216367111222", 132 | "1384216367111222333", 133 | } 134 | 135 | var ( 136 | timezone = "" 137 | ) 138 | 139 | func main() { 140 | flag.StringVar(&timezone, "timezone", "UTC", "Timezone aka `America/Los_Angeles` formatted time-zone") 141 | flag.Parse() 142 | 143 | if timezone != "" { 144 | // NOTE: This is very, very important to understand 145 | // time-parsing in go 146 | loc, err := time.LoadLocation(timezone) 147 | if err != nil { 148 | panic(err.Error()) 149 | } 150 | time.Local = loc 151 | } 152 | 153 | table := termtables.CreateTable() 154 | 155 | table.AddHeaders("Input", "Parsed, and Output as %v") 156 | for _, dateExample := range examples { 157 | t, err := dateparse.ParseLocal(dateExample) 158 | if err != nil { 159 | panic(err.Error()) 160 | } 161 | table.AddRow(dateExample, fmt.Sprintf("%v", t)) 162 | } 163 | fmt.Println(table.Render()) 164 | } 165 | 166 | /* 167 | +-------------------------------------------------------+-----------------------------------------+ 168 | | Input | Parsed, and Output as %v | 169 | +-------------------------------------------------------+-----------------------------------------+ 170 | | May 8, 2009 5:57:51 PM | 2009-05-08 17:57:51 +0000 UTC | 171 | | oct 7, 1970 | 1970-10-07 00:00:00 +0000 UTC | 172 | | oct 7, '70 | 1970-10-07 00:00:00 +0000 UTC | 173 | | oct. 7, 1970 | 1970-10-07 00:00:00 +0000 UTC | 174 | | oct. 7, 70 | 1970-10-07 00:00:00 +0000 UTC | 175 | | Mon Jan 2 15:04:05 2006 | 2006-01-02 15:04:05 +0000 UTC | 176 | | Mon Jan 2 15:04:05 MST 2006 | 2006-01-02 15:04:05 +0000 MST | 177 | | Mon Jan 02 15:04:05 -0700 2006 | 2006-01-02 15:04:05 -0700 -0700 | 178 | | Monday, 02-Jan-06 15:04:05 MST | 2006-01-02 15:04:05 +0000 MST | 179 | | Mon, 02 Jan 2006 15:04:05 MST | 2006-01-02 15:04:05 +0000 MST | 180 | | Tue, 11 Jul 2017 16:28:13 +0200 (CEST) | 2017-07-11 16:28:13 +0200 +0200 | 181 | | Mon, 02 Jan 2006 15:04:05 -0700 | 2006-01-02 15:04:05 -0700 -0700 | 182 | | Mon 30 Sep 2018 09:09:09 PM UTC | 2018-09-30 21:09:09 +0000 UTC | 183 | | Mon Aug 10 15:44:11 UTC+0100 2015 | 2015-08-10 15:44:11 +0000 UTC | 184 | | Thu, 4 Jan 2018 17:53:36 +0000 | 2018-01-04 17:53:36 +0000 UTC | 185 | | Fri Jul 03 2015 18:04:07 GMT+0100 (GMT Daylight Time) | 2015-07-03 18:04:07 +0100 GMT | 186 | | Sun, 3 Jan 2021 00:12:23 +0800 (GMT+08:00) | 2021-01-03 00:12:23 +0800 +0800 | 187 | | September 17, 2012 10:09am | 2012-09-17 10:09:00 +0000 UTC | 188 | | September 17, 2012 at 10:09am PST-08 | 2012-09-17 10:09:00 -0800 PST | 189 | | September 17, 2012, 10:10:09 | 2012-09-17 10:10:09 +0000 UTC | 190 | | October 7, 1970 | 1970-10-07 00:00:00 +0000 UTC | 191 | | October 7th, 1970 | 1970-10-07 00:00:00 +0000 UTC | 192 | | 12 Feb 2006, 19:17 | 2006-02-12 19:17:00 +0000 UTC | 193 | | 12 Feb 2006 19:17 | 2006-02-12 19:17:00 +0000 UTC | 194 | | 14 May 2019 19:11:40.164 | 2019-05-14 19:11:40.164 +0000 UTC | 195 | | 7 oct 70 | 1970-10-07 00:00:00 +0000 UTC | 196 | | 7 oct 1970 | 1970-10-07 00:00:00 +0000 UTC | 197 | | 03 February 2013 | 2013-02-03 00:00:00 +0000 UTC | 198 | | 1 July 2013 | 2013-07-01 00:00:00 +0000 UTC | 199 | | 2013-Feb-03 | 2013-02-03 00:00:00 +0000 UTC | 200 | | 06/Jan/2008:15:04:05 -0700 | 2008-01-06 15:04:05 -0700 -0700 | 201 | | 06/Jan/2008 15:04:05 -0700 | 2008-01-06 15:04:05 -0700 -0700 | 202 | | 3/31/2014 | 2014-03-31 00:00:00 +0000 UTC | 203 | | 03/31/2014 | 2014-03-31 00:00:00 +0000 UTC | 204 | | 08/21/71 | 1971-08-21 00:00:00 +0000 UTC | 205 | | 8/1/71 | 1971-08-01 00:00:00 +0000 UTC | 206 | | 4/8/2014 22:05 | 2014-04-08 22:05:00 +0000 UTC | 207 | | 04/08/2014 22:05 | 2014-04-08 22:05:00 +0000 UTC | 208 | | 4/8/14 22:05 | 2014-04-08 22:05:00 +0000 UTC | 209 | | 04/2/2014 03:00:51 | 2014-04-02 03:00:51 +0000 UTC | 210 | | 8/8/1965 12:00:00 AM | 1965-08-08 00:00:00 +0000 UTC | 211 | | 8/8/1965 01:00:01 PM | 1965-08-08 13:00:01 +0000 UTC | 212 | | 8/8/1965 01:00 PM | 1965-08-08 13:00:00 +0000 UTC | 213 | | 8/8/1965 1:00 PM | 1965-08-08 13:00:00 +0000 UTC | 214 | | 8/8/1965 12:00 AM | 1965-08-08 00:00:00 +0000 UTC | 215 | | 4/02/2014 03:00:51 | 2014-04-02 03:00:51 +0000 UTC | 216 | | 03/19/2012 10:11:59 | 2012-03-19 10:11:59 +0000 UTC | 217 | | 03/19/2012 10:11:59.3186369 | 2012-03-19 10:11:59.3186369 +0000 UTC | 218 | | 2014/3/31 | 2014-03-31 00:00:00 +0000 UTC | 219 | | 2014/03/31 | 2014-03-31 00:00:00 +0000 UTC | 220 | | 2014/4/8 22:05 | 2014-04-08 22:05:00 +0000 UTC | 221 | | 2014/04/08 22:05 | 2014-04-08 22:05:00 +0000 UTC | 222 | | 2014/04/2 03:00:51 | 2014-04-02 03:00:51 +0000 UTC | 223 | | 2014/4/02 03:00:51 | 2014-04-02 03:00:51 +0000 UTC | 224 | | 2012/03/19 10:11:59 | 2012-03-19 10:11:59 +0000 UTC | 225 | | 2012/03/19 10:11:59.3186369 | 2012-03-19 10:11:59.3186369 +0000 UTC | 226 | | 2014:3:31 | 2014-03-31 00:00:00 +0000 UTC | 227 | | 2014:03:31 | 2014-03-31 00:00:00 +0000 UTC | 228 | | 2014:4:8 22:05 | 2014-04-08 22:05:00 +0000 UTC | 229 | | 2014:04:08 22:05 | 2014-04-08 22:05:00 +0000 UTC | 230 | | 2014:04:2 03:00:51 | 2014-04-02 03:00:51 +0000 UTC | 231 | | 2014:4:02 03:00:51 | 2014-04-02 03:00:51 +0000 UTC | 232 | | 2012:03:19 10:11:59 | 2012-03-19 10:11:59 +0000 UTC | 233 | | 2012:03:19 10:11:59.3186369 | 2012-03-19 10:11:59.3186369 +0000 UTC | 234 | | 2014年04月08日 | 2014-04-08 00:00:00 +0000 UTC | 235 | | 2006-01-02T15:04:05+0000 | 2006-01-02 15:04:05 +0000 UTC | 236 | | 2009-08-12T22:15:09-07:00 | 2009-08-12 22:15:09 -0700 -0700 | 237 | | 2009-08-12T22:15:09 | 2009-08-12 22:15:09 +0000 UTC | 238 | | 2009-08-12T22:15:09.988 | 2009-08-12 22:15:09.988 +0000 UTC | 239 | | 2009-08-12T22:15:09Z | 2009-08-12 22:15:09 +0000 UTC | 240 | | 2017-07-19T03:21:51:897+0100 | 2017-07-19 03:21:51.897 +0100 +0100 | 241 | | 2019-05-29T08:41-04 | 2019-05-29 08:41:00 -0400 -0400 | 242 | | 2014-04-26 17:24:37.3186369 | 2014-04-26 17:24:37.3186369 +0000 UTC | 243 | | 2012-08-03 18:31:59.257000000 | 2012-08-03 18:31:59.257 +0000 UTC | 244 | | 2014-04-26 17:24:37.123 | 2014-04-26 17:24:37.123 +0000 UTC | 245 | | 2013-04-01 22:43 | 2013-04-01 22:43:00 +0000 UTC | 246 | | 2013-04-01 22:43:22 | 2013-04-01 22:43:22 +0000 UTC | 247 | | 2014-12-16 06:20:00 UTC | 2014-12-16 06:20:00 +0000 UTC | 248 | | 2014-12-16 06:20:00 GMT | 2014-12-16 06:20:00 +0000 UTC | 249 | | 2014-04-26 05:24:37 PM | 2014-04-26 17:24:37 +0000 UTC | 250 | | 2014-04-26 13:13:43 +0800 | 2014-04-26 13:13:43 +0800 +0800 | 251 | | 2014-04-26 13:13:43 +0800 +08 | 2014-04-26 13:13:43 +0800 +0800 | 252 | | 2014-04-26 13:13:44 +09:00 | 2014-04-26 13:13:44 +0900 +0900 | 253 | | 2012-08-03 18:31:59.257000000 +0000 UTC | 2012-08-03 18:31:59.257 +0000 UTC | 254 | | 2015-09-30 18:48:56.35272715 +0000 UTC | 2015-09-30 18:48:56.35272715 +0000 UTC | 255 | | 2015-02-18 00:12:00 +0000 GMT | 2015-02-18 00:12:00 +0000 UTC | 256 | | 2015-02-18 00:12:00 +0000 UTC | 2015-02-18 00:12:00 +0000 UTC | 257 | | 2015-02-08 03:02:00 +0300 MSK m=+0.000000001 | 2015-02-08 03:02:00 +0300 +0300 | 258 | | 2015-02-08 03:02:00.001 +0300 MSK m=+0.000000001 | 2015-02-08 03:02:00.001 +0300 +0300 | 259 | | 2017-07-19 03:21:51+00:00 | 2017-07-19 03:21:51 +0000 UTC | 260 | | 2014-04-26 | 2014-04-26 00:00:00 +0000 UTC | 261 | | 2014-04 | 2014-04-01 00:00:00 +0000 UTC | 262 | | 2014 | 2014-01-01 00:00:00 +0000 UTC | 263 | | 2014-05-11 08:20:13,787 | 2014-05-11 08:20:13.787 +0000 UTC | 264 | | 2020-07-20+08:00 | 2020-07-20 00:00:00 +0800 +0800 | 265 | | 3.31.2014 | 2014-03-31 00:00:00 +0000 UTC | 266 | | 03.31.2014 | 2014-03-31 00:00:00 +0000 UTC | 267 | | 08.21.71 | 1971-08-21 00:00:00 +0000 UTC | 268 | | 2014.03 | 2014-03-01 00:00:00 +0000 UTC | 269 | | 2014.03.30 | 2014-03-30 00:00:00 +0000 UTC | 270 | | 20140601 | 2014-06-01 00:00:00 +0000 UTC | 271 | | 20140722105203 | 2014-07-22 10:52:03 +0000 UTC | 272 | | 171113 14:14:20 | 2017-11-13 14:14:20 +0000 UTC | 273 | | 1332151919 | 2012-03-19 10:11:59 +0000 UTC | 274 | | 1384216367189 | 2013-11-12 00:32:47.189 +0000 UTC | 275 | | 1384216367111222 | 2013-11-12 00:32:47.111222 +0000 UTC | 276 | | 1384216367111222333 | 2013-11-12 00:32:47.111222333 +0000 UTC | 277 | +-------------------------------------------------------+-----------------------------------------+ 278 | */ 279 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Go Date Parser 2 | --------------------------- 3 | 4 | Parse many date strings without knowing format in advance. Uses a scanner to read bytes and use a state machine to find format. Much faster than shotgun based parse methods. See [bench_test.go](https://github.com/araddon/dateparse/blob/master/bench_test.go) for performance comparison. 5 | 6 | 7 | [![Code Coverage](https://codecov.io/gh/araddon/dateparse/branch/master/graph/badge.svg)](https://codecov.io/gh/araddon/dateparse) 8 | [![GoDoc](https://godoc.org/github.com/araddon/dateparse?status.svg)](http://godoc.org/github.com/araddon/dateparse) 9 | [![Build Status](https://travis-ci.org/araddon/dateparse.svg?branch=master)](https://travis-ci.org/araddon/dateparse) 10 | [![Go ReportCard](https://goreportcard.com/badge/araddon/dateparse)](https://goreportcard.com/report/araddon/dateparse) 11 | 12 | **MM/DD/YYYY VS DD/MM/YYYY** Right now this uses mm/dd/yyyy WHEN ambiguous if this is not desired behavior, use `ParseStrict` which will fail on ambiguous date strings. 13 | 14 | **Timezones** The location your server is configured affects the results! See example or https://play.golang.org/p/IDHRalIyXh and last paragraph here https://golang.org/pkg/time/#Parse. 15 | 16 | 17 | ```go 18 | 19 | // Normal parse. Equivalent Timezone rules as time.Parse() 20 | t, err := dateparse.ParseAny("3/1/2014") 21 | 22 | // Parse Strict, error on ambigous mm/dd vs dd/mm dates 23 | t, err := dateparse.ParseStrict("3/1/2014") 24 | > returns error 25 | 26 | // Return a string that represents the layout to parse the given date-time. 27 | layout, err := dateparse.ParseFormat("May 8, 2009 5:57:51 PM") 28 | > "Jan 2, 2006 3:04:05 PM" 29 | 30 | ``` 31 | 32 | cli tool for testing dateformats 33 | ---------------------------------- 34 | 35 | [Date Parse CLI](https://github.com/araddon/dateparse/blob/master/dateparse) 36 | 37 | 38 | Extended example 39 | ------------------- 40 | 41 | https://github.com/araddon/dateparse/blob/master/example/main.go 42 | 43 | ```go 44 | package main 45 | 46 | import ( 47 | "flag" 48 | "fmt" 49 | "time" 50 | 51 | "github.com/scylladb/termtables" 52 | "github.com/araddon/dateparse" 53 | ) 54 | 55 | var examples = []string{ 56 | "May 8, 2009 5:57:51 PM", 57 | "oct 7, 1970", 58 | "oct 7, '70", 59 | "oct. 7, 1970", 60 | "oct. 7, 70", 61 | "Mon Jan 2 15:04:05 2006", 62 | "Mon Jan 2 15:04:05 MST 2006", 63 | "Mon Jan 02 15:04:05 -0700 2006", 64 | "Monday, 02-Jan-06 15:04:05 MST", 65 | "Mon, 02 Jan 2006 15:04:05 MST", 66 | "Tue, 11 Jul 2017 16:28:13 +0200 (CEST)", 67 | "Mon, 02 Jan 2006 15:04:05 -0700", 68 | "Mon 30 Sep 2018 09:09:09 PM UTC", 69 | "Mon Aug 10 15:44:11 UTC+0100 2015", 70 | "Thu, 4 Jan 2018 17:53:36 +0000", 71 | "Fri Jul 03 2015 18:04:07 GMT+0100 (GMT Daylight Time)", 72 | "Sun, 3 Jan 2021 00:12:23 +0800 (GMT+08:00)", 73 | "September 17, 2012 10:09am", 74 | "September 17, 2012 at 10:09am PST-08", 75 | "September 17, 2012, 10:10:09", 76 | "October 7, 1970", 77 | "October 7th, 1970", 78 | "12 Feb 2006, 19:17", 79 | "12 Feb 2006 19:17", 80 | "14 May 2019 19:11:40.164", 81 | "7 oct 70", 82 | "7 oct 1970", 83 | "03 February 2013", 84 | "1 July 2013", 85 | "2013-Feb-03", 86 | // dd/Mon/yyy alpha Months 87 | "06/Jan/2008:15:04:05 -0700", 88 | "06/Jan/2008 15:04:05 -0700", 89 | // mm/dd/yy 90 | "3/31/2014", 91 | "03/31/2014", 92 | "08/21/71", 93 | "8/1/71", 94 | "4/8/2014 22:05", 95 | "04/08/2014 22:05", 96 | "4/8/14 22:05", 97 | "04/2/2014 03:00:51", 98 | "8/8/1965 12:00:00 AM", 99 | "8/8/1965 01:00:01 PM", 100 | "8/8/1965 01:00 PM", 101 | "8/8/1965 1:00 PM", 102 | "8/8/1965 12:00 AM", 103 | "4/02/2014 03:00:51", 104 | "03/19/2012 10:11:59", 105 | "03/19/2012 10:11:59.3186369", 106 | // yyyy/mm/dd 107 | "2014/3/31", 108 | "2014/03/31", 109 | "2014/4/8 22:05", 110 | "2014/04/08 22:05", 111 | "2014/04/2 03:00:51", 112 | "2014/4/02 03:00:51", 113 | "2012/03/19 10:11:59", 114 | "2012/03/19 10:11:59.3186369", 115 | // yyyy:mm:dd 116 | "2014:3:31", 117 | "2014:03:31", 118 | "2014:4:8 22:05", 119 | "2014:04:08 22:05", 120 | "2014:04:2 03:00:51", 121 | "2014:4:02 03:00:51", 122 | "2012:03:19 10:11:59", 123 | "2012:03:19 10:11:59.3186369", 124 | // Chinese 125 | "2014年04月08日", 126 | // yyyy-mm-ddThh 127 | "2006-01-02T15:04:05+0000", 128 | "2009-08-12T22:15:09-07:00", 129 | "2009-08-12T22:15:09", 130 | "2009-08-12T22:15:09.988", 131 | "2009-08-12T22:15:09Z", 132 | "2017-07-19T03:21:51:897+0100", 133 | "2019-05-29T08:41-04", // no seconds, 2 digit TZ offset 134 | // yyyy-mm-dd hh:mm:ss 135 | "2014-04-26 17:24:37.3186369", 136 | "2012-08-03 18:31:59.257000000", 137 | "2014-04-26 17:24:37.123", 138 | "2013-04-01 22:43", 139 | "2013-04-01 22:43:22", 140 | "2014-12-16 06:20:00 UTC", 141 | "2014-12-16 06:20:00 GMT", 142 | "2014-04-26 05:24:37 PM", 143 | "2014-04-26 13:13:43 +0800", 144 | "2014-04-26 13:13:43 +0800 +08", 145 | "2014-04-26 13:13:44 +09:00", 146 | "2012-08-03 18:31:59.257000000 +0000 UTC", 147 | "2015-09-30 18:48:56.35272715 +0000 UTC", 148 | "2015-02-18 00:12:00 +0000 GMT", 149 | "2015-02-18 00:12:00 +0000 UTC", 150 | "2015-02-08 03:02:00 +0300 MSK m=+0.000000001", 151 | "2015-02-08 03:02:00.001 +0300 MSK m=+0.000000001", 152 | "2017-07-19 03:21:51+00:00", 153 | "2014-04-26", 154 | "2014-04", 155 | "2014", 156 | "2014-05-11 08:20:13,787", 157 | // yyyy-mm-dd-07:00 158 | "2020-07-20+08:00", 159 | // mm.dd.yy 160 | "3.31.2014", 161 | "03.31.2014", 162 | "08.21.71", 163 | "2014.03", 164 | "2014.03.30", 165 | // yyyymmdd and similar 166 | "20140601", 167 | "20140722105203", 168 | // yymmdd hh:mm:yy mysql log 169 | // 080313 05:21:55 mysqld started 170 | "171113 14:14:20", 171 | // unix seconds, ms, micro, nano 172 | "1332151919", 173 | "1384216367189", 174 | "1384216367111222", 175 | "1384216367111222333", 176 | } 177 | 178 | var ( 179 | timezone = "" 180 | ) 181 | 182 | func main() { 183 | flag.StringVar(&timezone, "timezone", "UTC", "Timezone aka `America/Los_Angeles` formatted time-zone") 184 | flag.Parse() 185 | 186 | if timezone != "" { 187 | // NOTE: This is very, very important to understand 188 | // time-parsing in go 189 | loc, err := time.LoadLocation(timezone) 190 | if err != nil { 191 | panic(err.Error()) 192 | } 193 | time.Local = loc 194 | } 195 | 196 | table := termtables.CreateTable() 197 | 198 | table.AddHeaders("Input", "Parsed, and Output as %v") 199 | for _, dateExample := range examples { 200 | t, err := dateparse.ParseLocal(dateExample) 201 | if err != nil { 202 | panic(err.Error()) 203 | } 204 | table.AddRow(dateExample, fmt.Sprintf("%v", t)) 205 | } 206 | fmt.Println(table.Render()) 207 | } 208 | 209 | /* 210 | +-------------------------------------------------------+-----------------------------------------+ 211 | | Input | Parsed, and Output as %v | 212 | +-------------------------------------------------------+-----------------------------------------+ 213 | | May 8, 2009 5:57:51 PM | 2009-05-08 17:57:51 +0000 UTC | 214 | | oct 7, 1970 | 1970-10-07 00:00:00 +0000 UTC | 215 | | oct 7, '70 | 1970-10-07 00:00:00 +0000 UTC | 216 | | oct. 7, 1970 | 1970-10-07 00:00:00 +0000 UTC | 217 | | oct. 7, 70 | 1970-10-07 00:00:00 +0000 UTC | 218 | | Mon Jan 2 15:04:05 2006 | 2006-01-02 15:04:05 +0000 UTC | 219 | | Mon Jan 2 15:04:05 MST 2006 | 2006-01-02 15:04:05 +0000 MST | 220 | | Mon Jan 02 15:04:05 -0700 2006 | 2006-01-02 15:04:05 -0700 -0700 | 221 | | Monday, 02-Jan-06 15:04:05 MST | 2006-01-02 15:04:05 +0000 MST | 222 | | Mon, 02 Jan 2006 15:04:05 MST | 2006-01-02 15:04:05 +0000 MST | 223 | | Tue, 11 Jul 2017 16:28:13 +0200 (CEST) | 2017-07-11 16:28:13 +0200 +0200 | 224 | | Mon, 02 Jan 2006 15:04:05 -0700 | 2006-01-02 15:04:05 -0700 -0700 | 225 | | Mon 30 Sep 2018 09:09:09 PM UTC | 2018-09-30 21:09:09 +0000 UTC | 226 | | Mon Aug 10 15:44:11 UTC+0100 2015 | 2015-08-10 15:44:11 +0000 UTC | 227 | | Thu, 4 Jan 2018 17:53:36 +0000 | 2018-01-04 17:53:36 +0000 UTC | 228 | | Fri Jul 03 2015 18:04:07 GMT+0100 (GMT Daylight Time) | 2015-07-03 18:04:07 +0100 GMT | 229 | | Sun, 3 Jan 2021 00:12:23 +0800 (GMT+08:00) | 2021-01-03 00:12:23 +0800 +0800 | 230 | | September 17, 2012 10:09am | 2012-09-17 10:09:00 +0000 UTC | 231 | | September 17, 2012 at 10:09am PST-08 | 2012-09-17 10:09:00 -0800 PST | 232 | | September 17, 2012, 10:10:09 | 2012-09-17 10:10:09 +0000 UTC | 233 | | October 7, 1970 | 1970-10-07 00:00:00 +0000 UTC | 234 | | October 7th, 1970 | 1970-10-07 00:00:00 +0000 UTC | 235 | | 12 Feb 2006, 19:17 | 2006-02-12 19:17:00 +0000 UTC | 236 | | 12 Feb 2006 19:17 | 2006-02-12 19:17:00 +0000 UTC | 237 | | 14 May 2019 19:11:40.164 | 2019-05-14 19:11:40.164 +0000 UTC | 238 | | 7 oct 70 | 1970-10-07 00:00:00 +0000 UTC | 239 | | 7 oct 1970 | 1970-10-07 00:00:00 +0000 UTC | 240 | | 03 February 2013 | 2013-02-03 00:00:00 +0000 UTC | 241 | | 1 July 2013 | 2013-07-01 00:00:00 +0000 UTC | 242 | | 2013-Feb-03 | 2013-02-03 00:00:00 +0000 UTC | 243 | | 06/Jan/2008:15:04:05 -0700 | 2008-01-06 15:04:05 -0700 -0700 | 244 | | 06/Jan/2008 15:04:05 -0700 | 2008-01-06 15:04:05 -0700 -0700 | 245 | | 3/31/2014 | 2014-03-31 00:00:00 +0000 UTC | 246 | | 03/31/2014 | 2014-03-31 00:00:00 +0000 UTC | 247 | | 08/21/71 | 1971-08-21 00:00:00 +0000 UTC | 248 | | 8/1/71 | 1971-08-01 00:00:00 +0000 UTC | 249 | | 4/8/2014 22:05 | 2014-04-08 22:05:00 +0000 UTC | 250 | | 04/08/2014 22:05 | 2014-04-08 22:05:00 +0000 UTC | 251 | | 4/8/14 22:05 | 2014-04-08 22:05:00 +0000 UTC | 252 | | 04/2/2014 03:00:51 | 2014-04-02 03:00:51 +0000 UTC | 253 | | 8/8/1965 12:00:00 AM | 1965-08-08 00:00:00 +0000 UTC | 254 | | 8/8/1965 01:00:01 PM | 1965-08-08 13:00:01 +0000 UTC | 255 | | 8/8/1965 01:00 PM | 1965-08-08 13:00:00 +0000 UTC | 256 | | 8/8/1965 1:00 PM | 1965-08-08 13:00:00 +0000 UTC | 257 | | 8/8/1965 12:00 AM | 1965-08-08 00:00:00 +0000 UTC | 258 | | 4/02/2014 03:00:51 | 2014-04-02 03:00:51 +0000 UTC | 259 | | 03/19/2012 10:11:59 | 2012-03-19 10:11:59 +0000 UTC | 260 | | 03/19/2012 10:11:59.3186369 | 2012-03-19 10:11:59.3186369 +0000 UTC | 261 | | 2014/3/31 | 2014-03-31 00:00:00 +0000 UTC | 262 | | 2014/03/31 | 2014-03-31 00:00:00 +0000 UTC | 263 | | 2014/4/8 22:05 | 2014-04-08 22:05:00 +0000 UTC | 264 | | 2014/04/08 22:05 | 2014-04-08 22:05:00 +0000 UTC | 265 | | 2014/04/2 03:00:51 | 2014-04-02 03:00:51 +0000 UTC | 266 | | 2014/4/02 03:00:51 | 2014-04-02 03:00:51 +0000 UTC | 267 | | 2012/03/19 10:11:59 | 2012-03-19 10:11:59 +0000 UTC | 268 | | 2012/03/19 10:11:59.3186369 | 2012-03-19 10:11:59.3186369 +0000 UTC | 269 | | 2014:3:31 | 2014-03-31 00:00:00 +0000 UTC | 270 | | 2014:03:31 | 2014-03-31 00:00:00 +0000 UTC | 271 | | 2014:4:8 22:05 | 2014-04-08 22:05:00 +0000 UTC | 272 | | 2014:04:08 22:05 | 2014-04-08 22:05:00 +0000 UTC | 273 | | 2014:04:2 03:00:51 | 2014-04-02 03:00:51 +0000 UTC | 274 | | 2014:4:02 03:00:51 | 2014-04-02 03:00:51 +0000 UTC | 275 | | 2012:03:19 10:11:59 | 2012-03-19 10:11:59 +0000 UTC | 276 | | 2012:03:19 10:11:59.3186369 | 2012-03-19 10:11:59.3186369 +0000 UTC | 277 | | 2014年04月08日 | 2014-04-08 00:00:00 +0000 UTC | 278 | | 2006-01-02T15:04:05+0000 | 2006-01-02 15:04:05 +0000 UTC | 279 | | 2009-08-12T22:15:09-07:00 | 2009-08-12 22:15:09 -0700 -0700 | 280 | | 2009-08-12T22:15:09 | 2009-08-12 22:15:09 +0000 UTC | 281 | | 2009-08-12T22:15:09.988 | 2009-08-12 22:15:09.988 +0000 UTC | 282 | | 2009-08-12T22:15:09Z | 2009-08-12 22:15:09 +0000 UTC | 283 | | 2017-07-19T03:21:51:897+0100 | 2017-07-19 03:21:51.897 +0100 +0100 | 284 | | 2019-05-29T08:41-04 | 2019-05-29 08:41:00 -0400 -0400 | 285 | | 2014-04-26 17:24:37.3186369 | 2014-04-26 17:24:37.3186369 +0000 UTC | 286 | | 2012-08-03 18:31:59.257000000 | 2012-08-03 18:31:59.257 +0000 UTC | 287 | | 2014-04-26 17:24:37.123 | 2014-04-26 17:24:37.123 +0000 UTC | 288 | | 2013-04-01 22:43 | 2013-04-01 22:43:00 +0000 UTC | 289 | | 2013-04-01 22:43:22 | 2013-04-01 22:43:22 +0000 UTC | 290 | | 2014-12-16 06:20:00 UTC | 2014-12-16 06:20:00 +0000 UTC | 291 | | 2014-12-16 06:20:00 GMT | 2014-12-16 06:20:00 +0000 UTC | 292 | | 2014-04-26 05:24:37 PM | 2014-04-26 17:24:37 +0000 UTC | 293 | | 2014-04-26 13:13:43 +0800 | 2014-04-26 13:13:43 +0800 +0800 | 294 | | 2014-04-26 13:13:43 +0800 +08 | 2014-04-26 13:13:43 +0800 +0800 | 295 | | 2014-04-26 13:13:44 +09:00 | 2014-04-26 13:13:44 +0900 +0900 | 296 | | 2012-08-03 18:31:59.257000000 +0000 UTC | 2012-08-03 18:31:59.257 +0000 UTC | 297 | | 2015-09-30 18:48:56.35272715 +0000 UTC | 2015-09-30 18:48:56.35272715 +0000 UTC | 298 | | 2015-02-18 00:12:00 +0000 GMT | 2015-02-18 00:12:00 +0000 UTC | 299 | | 2015-02-18 00:12:00 +0000 UTC | 2015-02-18 00:12:00 +0000 UTC | 300 | | 2015-02-08 03:02:00 +0300 MSK m=+0.000000001 | 2015-02-08 03:02:00 +0300 +0300 | 301 | | 2015-02-08 03:02:00.001 +0300 MSK m=+0.000000001 | 2015-02-08 03:02:00.001 +0300 +0300 | 302 | | 2017-07-19 03:21:51+00:00 | 2017-07-19 03:21:51 +0000 UTC | 303 | | 2014-04-26 | 2014-04-26 00:00:00 +0000 UTC | 304 | | 2014-04 | 2014-04-01 00:00:00 +0000 UTC | 305 | | 2014 | 2014-01-01 00:00:00 +0000 UTC | 306 | | 2014-05-11 08:20:13,787 | 2014-05-11 08:20:13.787 +0000 UTC | 307 | | 2020-07-20+08:00 | 2020-07-20 00:00:00 +0800 +0800 | 308 | | 3.31.2014 | 2014-03-31 00:00:00 +0000 UTC | 309 | | 03.31.2014 | 2014-03-31 00:00:00 +0000 UTC | 310 | | 08.21.71 | 1971-08-21 00:00:00 +0000 UTC | 311 | | 2014.03 | 2014-03-01 00:00:00 +0000 UTC | 312 | | 2014.03.30 | 2014-03-30 00:00:00 +0000 UTC | 313 | | 20140601 | 2014-06-01 00:00:00 +0000 UTC | 314 | | 20140722105203 | 2014-07-22 10:52:03 +0000 UTC | 315 | | 171113 14:14:20 | 2017-11-13 14:14:20 +0000 UTC | 316 | | 1332151919 | 2012-03-19 10:11:59 +0000 UTC | 317 | | 1384216367189 | 2013-11-12 00:32:47.189 +0000 UTC | 318 | | 1384216367111222 | 2013-11-12 00:32:47.111222 +0000 UTC | 319 | | 1384216367111222333 | 2013-11-12 00:32:47.111222333 +0000 UTC | 320 | +-------------------------------------------------------+-----------------------------------------+ 321 | */ 322 | 323 | ``` 324 | -------------------------------------------------------------------------------- /parseany_test.go: -------------------------------------------------------------------------------- 1 | package dateparse 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | "time" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestOne(t *testing.T) { 12 | time.Local = time.UTC 13 | var ts time.Time 14 | ts = MustParse("2020-07-20+08:00") 15 | assert.Equal(t, "2020-07-19 16:00:00 +0000 UTC", fmt.Sprintf("%v", ts.In(time.UTC))) 16 | } 17 | 18 | type dateTest struct { 19 | in, out, loc string 20 | err bool 21 | } 22 | 23 | var testInputs = []dateTest{ 24 | {in: "oct 7, 1970", out: "1970-10-07 00:00:00 +0000 UTC"}, 25 | {in: "oct 7, '70", out: "1970-10-07 00:00:00 +0000 UTC"}, 26 | {in: "Oct 7, '70", out: "1970-10-07 00:00:00 +0000 UTC"}, 27 | {in: "Oct. 7, '70", out: "1970-10-07 00:00:00 +0000 UTC"}, 28 | {in: "oct. 7, '70", out: "1970-10-07 00:00:00 +0000 UTC"}, 29 | {in: "oct. 7, 1970", out: "1970-10-07 00:00:00 +0000 UTC"}, 30 | {in: "Sept. 7, '70", out: "1970-09-07 00:00:00 +0000 UTC"}, 31 | {in: "sept. 7, 1970", out: "1970-09-07 00:00:00 +0000 UTC"}, 32 | {in: "Feb 8, 2009 5:57:51 AM", out: "2009-02-08 05:57:51 +0000 UTC"}, 33 | {in: "May 8, 2009 5:57:51 PM", out: "2009-05-08 17:57:51 +0000 UTC"}, 34 | {in: "May 8, 2009 5:57:1 PM", out: "2009-05-08 17:57:01 +0000 UTC"}, 35 | {in: "May 8, 2009 5:7:51 PM", out: "2009-05-08 17:07:51 +0000 UTC"}, 36 | {in: "May 8, 2009, 5:7:51 PM", out: "2009-05-08 17:07:51 +0000 UTC"}, 37 | {in: "7 oct 70", out: "1970-10-07 00:00:00 +0000 UTC"}, 38 | {in: "7 oct 1970", out: "1970-10-07 00:00:00 +0000 UTC"}, 39 | {in: "7 May 1970", out: "1970-05-07 00:00:00 +0000 UTC"}, 40 | {in: "7 Sep 1970", out: "1970-09-07 00:00:00 +0000 UTC"}, 41 | {in: "7 June 1970", out: "1970-06-07 00:00:00 +0000 UTC"}, 42 | {in: "7 September 1970", out: "1970-09-07 00:00:00 +0000 UTC"}, 43 | // ANSIC = "Mon Jan _2 15:04:05 2006" 44 | {in: "Mon Jan 2 15:04:05 2006", out: "2006-01-02 15:04:05 +0000 UTC"}, 45 | {in: "Thu May 8 17:57:51 2009", out: "2009-05-08 17:57:51 +0000 UTC"}, 46 | {in: "Thu May 8 17:57:51 2009", out: "2009-05-08 17:57:51 +0000 UTC"}, 47 | // ANSIC_GLIBC = "Mon 02 Jan 2006 03:04:05 PM UTC" 48 | {in: "Mon 02 Jan 2006 03:04:05 PM UTC", out: "2006-01-02 15:04:05 +0000 UTC"}, 49 | {in: "Mon 30 Sep 2018 09:09:09 PM UTC", out: "2018-09-30 21:09:09 +0000 UTC"}, 50 | // RubyDate = "Mon Jan 02 15:04:05 -0700 2006" 51 | {in: "Mon Jan 02 15:04:05 -0700 2006", out: "2006-01-02 22:04:05 +0000 UTC"}, 52 | {in: "Thu May 08 11:57:51 -0700 2009", out: "2009-05-08 18:57:51 +0000 UTC"}, 53 | // UnixDate = "Mon Jan _2 15:04:05 MST 2006" 54 | {in: "Mon Jan 2 15:04:05 MST 2006", out: "2006-01-02 15:04:05 +0000 UTC"}, 55 | {in: "Thu May 8 17:57:51 MST 2009", out: "2009-05-08 17:57:51 +0000 UTC"}, 56 | {in: "Thu May 8 17:57:51 PST 2009", out: "2009-05-08 17:57:51 +0000 UTC"}, 57 | {in: "Thu May 08 17:57:51 PST 2009", out: "2009-05-08 17:57:51 +0000 UTC"}, 58 | {in: "Thu May 08 17:57:51 CEST 2009", out: "2009-05-08 17:57:51 +0000 UTC"}, 59 | {in: "Thu May 08 05:05:07 PST 2009", out: "2009-05-08 05:05:07 +0000 UTC"}, 60 | {in: "Thu May 08 5:5:7 PST 2009", out: "2009-05-08 05:05:07 +0000 UTC"}, 61 | // Day Month dd time 62 | {in: "Mon Aug 10 15:44:11 UTC+0000 2015", out: "2015-08-10 15:44:11 +0000 UTC"}, 63 | {in: "Mon Aug 10 15:44:11 PST-0700 2015", out: "2015-08-10 22:44:11 +0000 UTC"}, 64 | {in: "Mon Aug 10 15:44:11 CEST+0200 2015", out: "2015-08-10 13:44:11 +0000 UTC"}, 65 | {in: "Mon Aug 1 15:44:11 CEST+0200 2015", out: "2015-08-01 13:44:11 +0000 UTC"}, 66 | {in: "Mon Aug 1 5:44:11 CEST+0200 2015", out: "2015-08-01 03:44:11 +0000 UTC"}, 67 | // ?? 68 | {in: "Fri Jul 03 2015 18:04:07 GMT+0100 (GMT Daylight Time)", out: "2015-07-03 17:04:07 +0000 UTC"}, 69 | {in: "Fri Jul 3 2015 06:04:07 GMT+0100 (GMT Daylight Time)", out: "2015-07-03 05:04:07 +0000 UTC"}, 70 | {in: "Fri Jul 3 2015 06:04:07 PST-0700 (Pacific Daylight Time)", out: "2015-07-03 13:04:07 +0000 UTC"}, 71 | // Month dd, yyyy at time 72 | {in: "September 17, 2012 at 5:00pm UTC-05", out: "2012-09-17 17:00:00 +0000 UTC"}, 73 | {in: "September 17, 2012 at 10:09am PST-08", out: "2012-09-17 18:09:00 +0000 UTC"}, 74 | {in: "September 17, 2012, 10:10:09", out: "2012-09-17 10:10:09 +0000 UTC"}, 75 | {in: "May 17, 2012 at 10:09am PST-08", out: "2012-05-17 18:09:00 +0000 UTC"}, 76 | {in: "May 17, 2012 AT 10:09am PST-08", out: "2012-05-17 18:09:00 +0000 UTC"}, 77 | // Month dd, yyyy time 78 | {in: "September 17, 2012 5:00pm UTC-05", out: "2012-09-17 17:00:00 +0000 UTC"}, 79 | {in: "September 17, 2012 10:09am PST-08", out: "2012-09-17 18:09:00 +0000 UTC"}, 80 | {in: "September 17, 2012 09:01:00", out: "2012-09-17 09:01:00 +0000 UTC"}, 81 | // Month dd yyyy time 82 | {in: "September 17 2012 5:00pm UTC-05", out: "2012-09-17 17:00:00 +0000 UTC"}, 83 | {in: "September 17 2012 5:00pm UTC-0500", out: "2012-09-17 17:00:00 +0000 UTC"}, 84 | {in: "September 17 2012 10:09am PST-08", out: "2012-09-17 18:09:00 +0000 UTC"}, 85 | {in: "September 17 2012 5:00PM UTC-05", out: "2012-09-17 17:00:00 +0000 UTC"}, 86 | {in: "September 17 2012 10:09AM PST-08", out: "2012-09-17 18:09:00 +0000 UTC"}, 87 | {in: "September 17 2012 09:01:00", out: "2012-09-17 09:01:00 +0000 UTC"}, 88 | {in: "May 17, 2012 10:10:09", out: "2012-05-17 10:10:09 +0000 UTC"}, 89 | // Month dd, yyyy 90 | {in: "September 17, 2012", out: "2012-09-17 00:00:00 +0000 UTC"}, 91 | {in: "May 7, 2012", out: "2012-05-07 00:00:00 +0000 UTC"}, 92 | {in: "June 7, 2012", out: "2012-06-07 00:00:00 +0000 UTC"}, 93 | {in: "June 7 2012", out: "2012-06-07 00:00:00 +0000 UTC"}, 94 | // Month dd[th,nd,st,rd] yyyy 95 | {in: "September 17th, 2012", out: "2012-09-17 00:00:00 +0000 UTC"}, 96 | {in: "September 17th 2012", out: "2012-09-17 00:00:00 +0000 UTC"}, 97 | {in: "September 7th, 2012", out: "2012-09-07 00:00:00 +0000 UTC"}, 98 | {in: "September 7th 2012", out: "2012-09-07 00:00:00 +0000 UTC"}, 99 | {in: "September 7tH 2012", out: "2012-09-07 00:00:00 +0000 UTC"}, 100 | {in: "May 1st 2012", out: "2012-05-01 00:00:00 +0000 UTC"}, 101 | {in: "May 1st, 2012", out: "2012-05-01 00:00:00 +0000 UTC"}, 102 | {in: "May 21st 2012", out: "2012-05-21 00:00:00 +0000 UTC"}, 103 | {in: "May 21st, 2012", out: "2012-05-21 00:00:00 +0000 UTC"}, 104 | {in: "May 23rd 2012", out: "2012-05-23 00:00:00 +0000 UTC"}, 105 | {in: "May 23rd, 2012", out: "2012-05-23 00:00:00 +0000 UTC"}, 106 | {in: "June 2nd, 2012", out: "2012-06-02 00:00:00 +0000 UTC"}, 107 | {in: "June 2nd 2012", out: "2012-06-02 00:00:00 +0000 UTC"}, 108 | {in: "June 22nd, 2012", out: "2012-06-22 00:00:00 +0000 UTC"}, 109 | {in: "June 22nd 2012", out: "2012-06-22 00:00:00 +0000 UTC"}, 110 | // RFC1123 = "Mon, 02 Jan 2006 15:04:05 MST" 111 | {in: "Fri, 03 Jul 2015 08:08:08 MST", out: "2015-07-03 08:08:08 +0000 UTC"}, 112 | //{in: "Fri, 03 Jul 2015 08:08:08 CET", out: "2015-07-03 08:08:08 +0000 UTC"}, 113 | {in: "Fri, 03 Jul 2015 08:08:08 PST", out: "2015-07-03 16:08:08 +0000 UTC", loc: "America/Los_Angeles"}, 114 | {in: "Fri, 03 Jul 2015 08:08:08 PST", out: "2015-07-03 08:08:08 +0000 UTC"}, 115 | {in: "Fri, 3 Jul 2015 08:08:08 MST", out: "2015-07-03 08:08:08 +0000 UTC"}, 116 | {in: "Fri, 03 Jul 2015 8:08:08 MST", out: "2015-07-03 08:08:08 +0000 UTC"}, 117 | {in: "Fri, 03 Jul 2015 8:8:8 MST", out: "2015-07-03 08:08:08 +0000 UTC"}, 118 | // ? 119 | {in: "Thu, 03 Jul 2017 08:08:04 +0100", out: "2017-07-03 07:08:04 +0000 UTC"}, 120 | {in: "Thu, 03 Jul 2017 08:08:04 -0100", out: "2017-07-03 09:08:04 +0000 UTC"}, 121 | {in: "Thu, 3 Jul 2017 08:08:04 +0100", out: "2017-07-03 07:08:04 +0000 UTC"}, 122 | {in: "Thu, 03 Jul 2017 8:08:04 +0100", out: "2017-07-03 07:08:04 +0000 UTC"}, 123 | {in: "Thu, 03 Jul 2017 8:8:4 +0100", out: "2017-07-03 07:08:04 +0000 UTC"}, 124 | // 125 | {in: "Tue, 11 Jul 2017 04:08:03 +0200 (CEST)", out: "2017-07-11 02:08:03 +0000 UTC"}, 126 | {in: "Tue, 5 Jul 2017 04:08:03 -0700 (CEST)", out: "2017-07-05 11:08:03 +0000 UTC"}, 127 | {in: "Tue, 11 Jul 2017 04:08:03 +0200 (CEST)", out: "2017-07-11 02:08:03 +0000 UTC", loc: "Europe/Berlin"}, 128 | // day, dd-Mon-yy hh:mm:zz TZ 129 | {in: "Fri, 03-Jul-15 08:08:08 MST", out: "2015-07-03 08:08:08 +0000 UTC"}, 130 | {in: "Fri, 03-Jul-15 08:08:08 PST", out: "2015-07-03 16:08:08 +0000 UTC", loc: "America/Los_Angeles"}, 131 | {in: "Fri, 03-Jul 2015 08:08:08 PST", out: "2015-07-03 08:08:08 +0000 UTC"}, 132 | {in: "Fri, 3-Jul-15 08:08:08 MST", out: "2015-07-03 08:08:08 +0000 UTC"}, 133 | {in: "Fri, 03-Jul-15 8:08:08 MST", out: "2015-07-03 08:08:08 +0000 UTC"}, 134 | {in: "Fri, 03-Jul-15 8:8:8 MST", out: "2015-07-03 08:08:08 +0000 UTC"}, 135 | // day, dd-Mon-yy hh:mm:zz TZ (text) https://github.com/araddon/dateparse/issues/116 136 | {in: "Sun, 3 Jan 2021 00:12:23 +0800 (GMT+08:00)", out: "2021-01-02 16:12:23 +0000 UTC"}, 137 | // RFC850 = "Monday, 02-Jan-06 15:04:05 MST" 138 | {in: "Wednesday, 07-May-09 08:00:43 MST", out: "2009-05-07 08:00:43 +0000 UTC"}, 139 | {in: "Wednesday, 28-Feb-18 09:01:00 MST", out: "2018-02-28 09:01:00 +0000 UTC"}, 140 | {in: "Wednesday, 28-Feb-18 09:01:00 MST", out: "2018-02-28 16:01:00 +0000 UTC", loc: "America/Denver"}, 141 | // with offset then with variations on non-zero filled stuff 142 | {in: "Monday, 02 Jan 2006 15:04:05 +0100", out: "2006-01-02 14:04:05 +0000 UTC"}, 143 | {in: "Wednesday, 28 Feb 2018 09:01:00 -0300", out: "2018-02-28 12:01:00 +0000 UTC"}, 144 | {in: "Wednesday, 2 Feb 2018 09:01:00 -0300", out: "2018-02-02 12:01:00 +0000 UTC"}, 145 | {in: "Wednesday, 2 Feb 2018 9:01:00 -0300", out: "2018-02-02 12:01:00 +0000 UTC"}, 146 | {in: "Wednesday, 2 Feb 2018 09:1:00 -0300", out: "2018-02-02 12:01:00 +0000 UTC"}, 147 | // dd mon yyyy 12 Feb 2006, 19:17:08 148 | {in: "07 Feb 2004, 09:07", out: "2004-02-07 09:07:00 +0000 UTC"}, 149 | {in: "07 Feb 2004, 09:07:07", out: "2004-02-07 09:07:07 +0000 UTC"}, 150 | {in: "7 Feb 2004, 09:07:07", out: "2004-02-07 09:07:07 +0000 UTC"}, 151 | {in: "07 Feb 2004, 9:7:7", out: "2004-02-07 09:07:07 +0000 UTC"}, 152 | // dd Mon yyyy hh:mm:ss 153 | {in: "07 Feb 2004 09:07:08", out: "2004-02-07 09:07:08 +0000 UTC"}, 154 | {in: "07 Feb 2004 09:07", out: "2004-02-07 09:07:00 +0000 UTC"}, 155 | {in: "7 Feb 2004 9:7:8", out: "2004-02-07 09:07:08 +0000 UTC"}, 156 | {in: "07 Feb 2004 09:07:08.123", out: "2004-02-07 09:07:08.123 +0000 UTC"}, 157 | // dd-mon-yyyy 12 Feb 2006, 19:17:08 GMT 158 | {in: "07 Feb 2004, 09:07:07 GMT", out: "2004-02-07 09:07:07 +0000 UTC"}, 159 | // dd-mon-yyyy 12 Feb 2006, 19:17:08 +0100 160 | {in: "07 Feb 2004, 09:07:07 +0100", out: "2004-02-07 08:07:07 +0000 UTC"}, 161 | // dd-mon-yyyy 12-Feb-2006 19:17:08 162 | {in: "07-Feb-2004 09:07:07 +0100", out: "2004-02-07 08:07:07 +0000 UTC"}, 163 | // dd-mon-yy 12-Feb-2006 19:17:08 164 | {in: "07-Feb-04 09:07:07 +0100", out: "2004-02-07 08:07:07 +0000 UTC"}, 165 | // yyyy-mon-dd 2013-Feb-03 166 | {in: "2013-Feb-03", out: "2013-02-03 00:00:00 +0000 UTC"}, 167 | // 03 February 2013 168 | {in: "03 February 2013", out: "2013-02-03 00:00:00 +0000 UTC"}, 169 | {in: "3 February 2013", out: "2013-02-03 00:00:00 +0000 UTC"}, 170 | // Chinese 2014年04月18日 171 | {in: "2014年04月08日", out: "2014-04-08 00:00:00 +0000 UTC"}, 172 | {in: "2014年04月08日 19:17:22", out: "2014-04-08 19:17:22 +0000 UTC"}, 173 | // mm/dd/yyyy 174 | {in: "03/31/2014", out: "2014-03-31 00:00:00 +0000 UTC"}, 175 | {in: "3/31/2014", out: "2014-03-31 00:00:00 +0000 UTC"}, 176 | {in: "3/5/2014", out: "2014-03-05 00:00:00 +0000 UTC"}, 177 | // mm/dd/yy 178 | {in: "08/08/71", out: "1971-08-08 00:00:00 +0000 UTC"}, 179 | {in: "8/8/71", out: "1971-08-08 00:00:00 +0000 UTC"}, 180 | // mm/dd/yy hh:mm:ss 181 | {in: "04/02/2014 04:08:09", out: "2014-04-02 04:08:09 +0000 UTC"}, 182 | {in: "4/2/2014 04:08:09", out: "2014-04-02 04:08:09 +0000 UTC"}, 183 | {in: "04/02/2014 4:08:09", out: "2014-04-02 04:08:09 +0000 UTC"}, 184 | {in: "04/02/2014 4:8:9", out: "2014-04-02 04:08:09 +0000 UTC"}, 185 | {in: "04/02/2014 04:08", out: "2014-04-02 04:08:00 +0000 UTC"}, 186 | {in: "04/02/2014 4:8", out: "2014-04-02 04:08:00 +0000 UTC"}, 187 | {in: "04/02/2014 04:08:09.123", out: "2014-04-02 04:08:09.123 +0000 UTC"}, 188 | {in: "04/02/2014 04:08:09.12312", out: "2014-04-02 04:08:09.12312 +0000 UTC"}, 189 | {in: "04/02/2014 04:08:09.123123", out: "2014-04-02 04:08:09.123123 +0000 UTC"}, 190 | // mm:dd:yy hh:mm:ss 191 | {in: "04:02:2014 04:08:09", out: "2014-04-02 04:08:09 +0000 UTC"}, 192 | {in: "4:2:2014 04:08:09", out: "2014-04-02 04:08:09 +0000 UTC"}, 193 | {in: "04:02:2014 4:08:09", out: "2014-04-02 04:08:09 +0000 UTC"}, 194 | {in: "04:02:2014 4:8:9", out: "2014-04-02 04:08:09 +0000 UTC"}, 195 | {in: "04:02:2014 04:08", out: "2014-04-02 04:08:00 +0000 UTC"}, 196 | {in: "04:02:2014 4:8", out: "2014-04-02 04:08:00 +0000 UTC"}, 197 | {in: "04:02:2014 04:08:09.123", out: "2014-04-02 04:08:09.123 +0000 UTC"}, 198 | {in: "04:02:2014 04:08:09.12312", out: "2014-04-02 04:08:09.12312 +0000 UTC"}, 199 | {in: "04:02:2014 04:08:09.123123", out: "2014-04-02 04:08:09.123123 +0000 UTC"}, 200 | // mm/dd/yy hh:mm:ss AM 201 | {in: "04/02/2014 04:08:09 AM", out: "2014-04-02 04:08:09 +0000 UTC"}, 202 | {in: "04/02/2014 04:08:09 PM", out: "2014-04-02 16:08:09 +0000 UTC"}, 203 | {in: "04/02/2014 04:08 AM", out: "2014-04-02 04:08:00 +0000 UTC"}, 204 | {in: "04/02/2014 04:08 PM", out: "2014-04-02 16:08:00 +0000 UTC"}, 205 | {in: "04/02/2014 4:8 AM", out: "2014-04-02 04:08:00 +0000 UTC"}, 206 | {in: "04/02/2014 4:8 PM", out: "2014-04-02 16:08:00 +0000 UTC"}, 207 | {in: "04/02/2014 04:08:09.123 AM", out: "2014-04-02 04:08:09.123 +0000 UTC"}, 208 | {in: "04/02/2014 04:08:09.123 PM", out: "2014-04-02 16:08:09.123 +0000 UTC"}, 209 | // yyyy/mm/dd 210 | {in: "2014/04/02", out: "2014-04-02 00:00:00 +0000 UTC"}, 211 | {in: "2014/03/31", out: "2014-03-31 00:00:00 +0000 UTC"}, 212 | {in: "2014/4/2", out: "2014-04-02 00:00:00 +0000 UTC"}, 213 | // yyyy/mm/dd hh:mm:ss AM 214 | {in: "2014/04/02 04:08", out: "2014-04-02 04:08:00 +0000 UTC"}, 215 | {in: "2014/03/31 04:08", out: "2014-03-31 04:08:00 +0000 UTC"}, 216 | {in: "2014/4/2 04:08", out: "2014-04-02 04:08:00 +0000 UTC"}, 217 | {in: "2014/04/02 4:8", out: "2014-04-02 04:08:00 +0000 UTC"}, 218 | {in: "2014/04/02 04:08:09", out: "2014-04-02 04:08:09 +0000 UTC"}, 219 | {in: "2014/03/31 04:08:09", out: "2014-03-31 04:08:09 +0000 UTC"}, 220 | {in: "2014/4/2 04:08:09", out: "2014-04-02 04:08:09 +0000 UTC"}, 221 | {in: "2014/04/02 04:08:09.123", out: "2014-04-02 04:08:09.123 +0000 UTC"}, 222 | {in: "2014/04/02 04:08:09.123123", out: "2014-04-02 04:08:09.123123 +0000 UTC"}, 223 | {in: "2014/04/02 04:08:09 AM", out: "2014-04-02 04:08:09 +0000 UTC"}, 224 | {in: "2014/03/31 04:08:09 AM", out: "2014-03-31 04:08:09 +0000 UTC"}, 225 | {in: "2014/4/2 04:08:09 AM", out: "2014-04-02 04:08:09 +0000 UTC"}, 226 | {in: "2014/04/02 04:08:09.123 AM", out: "2014-04-02 04:08:09.123 +0000 UTC"}, 227 | {in: "2014/04/02 04:08:09.123 PM", out: "2014-04-02 16:08:09.123 +0000 UTC"}, 228 | // dd/mon/yyyy:hh:mm:ss tz nginx-log? https://github.com/araddon/dateparse/issues/118 229 | // 112.195.209.90 - - [20/Feb/2018:12:12:14 +0800] "GET / HTTP/1.1" 200 190 "-" "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Mobile Safari/537.36" "-" 230 | {in: "06/May/2008:08:11:17 -0700", out: "2008-05-06 15:11:17 +0000 UTC"}, 231 | {in: "30/May/2008:08:11:17 -0700", out: "2008-05-30 15:11:17 +0000 UTC"}, 232 | // dd/mon/yyy hh:mm:ss tz 233 | {in: "06/May/2008:08:11:17 -0700", out: "2008-05-06 15:11:17 +0000 UTC"}, 234 | {in: "30/May/2008:08:11:17 -0700", out: "2008-05-30 15:11:17 +0000 UTC"}, 235 | // yyyy-mm-dd 236 | {in: "2014-04-02", out: "2014-04-02 00:00:00 +0000 UTC"}, 237 | {in: "2014-03-31", out: "2014-03-31 00:00:00 +0000 UTC"}, 238 | {in: "2014-4-2", out: "2014-04-02 00:00:00 +0000 UTC"}, 239 | // yyyy-mm-dd-07:00 240 | {in: "2020-07-20+08:00", out: "2020-07-19 16:00:00 +0000 UTC"}, 241 | {in: "2020-07-20+0800", out: "2020-07-19 16:00:00 +0000 UTC"}, 242 | // dd-mmm-yy 243 | {in: "28-Feb-02", out: "2002-02-28 00:00:00 +0000 UTC"}, 244 | {in: "15-Jan-18", out: "2018-01-15 00:00:00 +0000 UTC"}, 245 | {in: "15-Jan-2017", out: "2017-01-15 00:00:00 +0000 UTC"}, 246 | // yyyy-mm 247 | {in: "2014-04", out: "2014-04-01 00:00:00 +0000 UTC"}, 248 | // yyyy-mm-dd hh:mm:ss AM 249 | {in: "2014-04-02 04:08", out: "2014-04-02 04:08:00 +0000 UTC"}, 250 | {in: "2014-03-31 04:08", out: "2014-03-31 04:08:00 +0000 UTC"}, 251 | {in: "2014-4-2 04:08", out: "2014-04-02 04:08:00 +0000 UTC"}, 252 | {in: "2014-04-02 4:8", out: "2014-04-02 04:08:00 +0000 UTC"}, 253 | {in: "2014-04-02 04:08:09", out: "2014-04-02 04:08:09 +0000 UTC"}, 254 | {in: "2014-03-31 04:08:09", out: "2014-03-31 04:08:09 +0000 UTC"}, 255 | {in: "2014-4-2 04:08:09", out: "2014-04-02 04:08:09 +0000 UTC"}, 256 | {in: "2014-04-02 04:08:09.123", out: "2014-04-02 04:08:09.123 +0000 UTC"}, 257 | {in: "2014-04-02 04:08:09.123123", out: "2014-04-02 04:08:09.123123 +0000 UTC"}, 258 | {in: "2014-04-02 04:08:09.12312312", out: "2014-04-02 04:08:09.12312312 +0000 UTC"}, 259 | {in: "2014-04-02 04:08:09 AM", out: "2014-04-02 04:08:09 +0000 UTC"}, 260 | {in: "2014-03-31 04:08:09 AM", out: "2014-03-31 04:08:09 +0000 UTC"}, 261 | {in: "2014-04-26 05:24:37 PM", out: "2014-04-26 17:24:37 +0000 UTC"}, 262 | {in: "2014-4-2 04:08:09 AM", out: "2014-04-02 04:08:09 +0000 UTC"}, 263 | {in: "2014-04-02 04:08:09.123 AM", out: "2014-04-02 04:08:09.123 +0000 UTC"}, 264 | {in: "2014-04-02 04:08:09.123 PM", out: "2014-04-02 16:08:09.123 +0000 UTC"}, 265 | // yyyy-mm-dd hh:mm:ss,000 266 | {in: "2014-05-11 08:20:13,787", out: "2014-05-11 08:20:13.787 +0000 UTC"}, 267 | // yyyy-mm-dd hh:mm:ss +0000 268 | {in: "2012-08-03 18:31:59 +0000", out: "2012-08-03 18:31:59 +0000 UTC"}, 269 | {in: "2012-08-03 13:31:59 -0600", out: "2012-08-03 19:31:59 +0000 UTC"}, 270 | {in: "2012-08-03 18:31:59.257000000 +0000", out: "2012-08-03 18:31:59.257 +0000 UTC"}, 271 | {in: "2012-08-03 8:1:59.257000000 +0000", out: "2012-08-03 08:01:59.257 +0000 UTC"}, 272 | {in: "2012-8-03 18:31:59.257000000 +0000", out: "2012-08-03 18:31:59.257 +0000 UTC"}, 273 | {in: "2012-8-3 18:31:59.257000000 +0000", out: "2012-08-03 18:31:59.257 +0000 UTC"}, 274 | {in: "2014-04-26 17:24:37.123456 +0000", out: "2014-04-26 17:24:37.123456 +0000 UTC"}, 275 | {in: "2014-04-26 17:24:37.12 +0000", out: "2014-04-26 17:24:37.12 +0000 UTC"}, 276 | {in: "2014-04-26 17:24:37.1 +0000", out: "2014-04-26 17:24:37.1 +0000 UTC"}, 277 | {in: "2014-05-11 08:20:13 +0000", out: "2014-05-11 08:20:13 +0000 UTC"}, 278 | {in: "2014-05-11 08:20:13 +0530", out: "2014-05-11 02:50:13 +0000 UTC"}, 279 | // yyyy-mm-dd hh:mm:ss +0300 +03 ?? issue author said this is from golang? 280 | {in: "2018-06-29 19:09:57.77297118 +0300 +03", out: "2018-06-29 16:09:57.77297118 +0000 UTC"}, 281 | {in: "2018-06-29 19:09:57.77297118 +0300 +0300", out: "2018-06-29 16:09:57.77297118 +0000 UTC"}, 282 | {in: "2018-06-29 19:09:57 +0300 +03", out: "2018-06-29 16:09:57 +0000 UTC"}, 283 | {in: "2018-06-29 19:09:57 +0300 +0300", out: "2018-06-29 16:09:57 +0000 UTC"}, 284 | 285 | // 13:31:51.999 -07:00 MST 286 | // yyyy-mm-dd hh:mm:ss +00:00 287 | {in: "2012-08-03 18:31:59 +00:00", out: "2012-08-03 18:31:59 +0000 UTC"}, 288 | {in: "2014-05-01 08:02:13 +00:00", out: "2014-05-01 08:02:13 +0000 UTC"}, 289 | {in: "2014-5-01 08:02:13 +00:00", out: "2014-05-01 08:02:13 +0000 UTC"}, 290 | {in: "2014-05-1 08:02:13 +00:00", out: "2014-05-01 08:02:13 +0000 UTC"}, 291 | {in: "2012-08-03 13:31:59 -06:00", out: "2012-08-03 19:31:59 +0000 UTC"}, 292 | {in: "2012-08-03 18:31:59.257000000 +00:00", out: "2012-08-03 18:31:59.257 +0000 UTC"}, 293 | {in: "2012-08-03 8:1:59.257000000 +00:00", out: "2012-08-03 08:01:59.257 +0000 UTC"}, 294 | {in: "2012-8-03 18:31:59.257000000 +00:00", out: "2012-08-03 18:31:59.257 +0000 UTC"}, 295 | {in: "2012-8-3 18:31:59.257000000 +00:00", out: "2012-08-03 18:31:59.257 +0000 UTC"}, 296 | {in: "2014-04-26 17:24:37.123456 +00:00", out: "2014-04-26 17:24:37.123456 +0000 UTC"}, 297 | {in: "2014-04-26 17:24:37.12 +00:00", out: "2014-04-26 17:24:37.12 +0000 UTC"}, 298 | {in: "2014-04-26 17:24:37.1 +00:00", out: "2014-04-26 17:24:37.1 +0000 UTC"}, 299 | // yyyy-mm-dd hh:mm:ss +0000 TZ 300 | // Golang Native Format 301 | {in: "2012-08-03 18:31:59 +0000 UTC", out: "2012-08-03 18:31:59 +0000 UTC"}, 302 | {in: "2012-08-03 13:31:59 -0600 MST", out: "2012-08-03 19:31:59 +0000 UTC", loc: "America/Denver"}, 303 | {in: "2015-02-18 00:12:00 +0000 UTC", out: "2015-02-18 00:12:00 +0000 UTC"}, 304 | {in: "2015-02-18 00:12:00 +0000 GMT", out: "2015-02-18 00:12:00 +0000 UTC"}, 305 | {in: "2015-02-08 03:02:00 +0200 CEST", out: "2015-02-08 01:02:00 +0000 UTC", loc: "Europe/Berlin"}, 306 | {in: "2015-02-08 03:02:00 +0300 MSK", out: "2015-02-08 00:02:00 +0000 UTC"}, 307 | {in: "2015-2-08 03:02:00 +0300 MSK", out: "2015-02-08 00:02:00 +0000 UTC"}, 308 | {in: "2015-02-8 03:02:00 +0300 MSK", out: "2015-02-08 00:02:00 +0000 UTC"}, 309 | {in: "2015-2-8 03:02:00 +0300 MSK", out: "2015-02-08 00:02:00 +0000 UTC"}, 310 | {in: "2012-08-03 18:31:59.257000000 +0000 UTC", out: "2012-08-03 18:31:59.257 +0000 UTC"}, 311 | {in: "2012-08-03 8:1:59.257000000 +0000 UTC", out: "2012-08-03 08:01:59.257 +0000 UTC"}, 312 | {in: "2012-8-03 18:31:59.257000000 +0000 UTC", out: "2012-08-03 18:31:59.257 +0000 UTC"}, 313 | {in: "2012-8-3 18:31:59.257000000 +0000 UTC", out: "2012-08-03 18:31:59.257 +0000 UTC"}, 314 | {in: "2014-04-26 17:24:37.123456 +0000 UTC", out: "2014-04-26 17:24:37.123456 +0000 UTC"}, 315 | {in: "2014-04-26 17:24:37.12 +0000 UTC", out: "2014-04-26 17:24:37.12 +0000 UTC"}, 316 | {in: "2014-04-26 17:24:37.1 +0000 UTC", out: "2014-04-26 17:24:37.1 +0000 UTC"}, 317 | {in: "2015-02-08 03:02:00 +0200 CEST m=+0.000000001", out: "2015-02-08 01:02:00 +0000 UTC", loc: "Europe/Berlin"}, 318 | {in: "2015-02-08 03:02:00 +0300 MSK m=+0.000000001", out: "2015-02-08 00:02:00 +0000 UTC"}, 319 | {in: "2015-02-08 03:02:00.001 +0300 MSK m=+0.000000001", out: "2015-02-08 00:02:00.001 +0000 UTC"}, 320 | // yyyy-mm-dd hh:mm:ss TZ 321 | {in: "2012-08-03 18:31:59 UTC", out: "2012-08-03 18:31:59 +0000 UTC"}, 322 | {in: "2014-12-16 06:20:00 GMT", out: "2014-12-16 06:20:00 +0000 UTC"}, 323 | {in: "2012-08-03 13:31:59 MST", out: "2012-08-03 20:31:59 +0000 UTC", loc: "America/Denver"}, 324 | {in: "2012-08-03 18:31:59.257000000 UTC", out: "2012-08-03 18:31:59.257 +0000 UTC"}, 325 | {in: "2012-08-03 8:1:59.257000000 UTC", out: "2012-08-03 08:01:59.257 +0000 UTC"}, 326 | {in: "2012-8-03 18:31:59.257000000 UTC", out: "2012-08-03 18:31:59.257 +0000 UTC"}, 327 | {in: "2012-8-3 18:31:59.257000000 UTC", out: "2012-08-03 18:31:59.257 +0000 UTC"}, 328 | {in: "2014-04-26 17:24:37.123456 UTC", out: "2014-04-26 17:24:37.123456 +0000 UTC"}, 329 | {in: "2014-04-26 17:24:37.12 UTC", out: "2014-04-26 17:24:37.12 +0000 UTC"}, 330 | {in: "2014-04-26 17:24:37.1 UTC", out: "2014-04-26 17:24:37.1 +0000 UTC"}, 331 | // This one is pretty special, it is TIMEZONE based but starts with P to emulate collions with PM 332 | {in: "2014-04-26 05:24:37 PST", out: "2014-04-26 05:24:37 +0000 UTC"}, 333 | {in: "2014-04-26 05:24:37 PST", out: "2014-04-26 13:24:37 +0000 UTC", loc: "America/Los_Angeles"}, 334 | // yyyy-mm-dd hh:mm:ss+00:00 335 | {in: "2012-08-03 18:31:59+00:00", out: "2012-08-03 18:31:59 +0000 UTC"}, 336 | {in: "2017-07-19 03:21:51+00:00", out: "2017-07-19 03:21:51 +0000 UTC"}, 337 | // yyyy:mm:dd hh:mm:ss+00:00 338 | {in: "2012:08:03 18:31:59+00:00", out: "2012-08-03 18:31:59 +0000 UTC"}, 339 | // dd:mm:yyyy hh:mm:ss+00:00 340 | {in: "08:03:2012 18:31:59+00:00", out: "2012-08-03 18:31:59 +0000 UTC"}, 341 | // yyyy-mm-dd hh:mm:ss.000+00:00 PST 342 | {in: "2012-08-03 18:31:59.000+00:00 PST", out: "2012-08-03 18:31:59 +0000 UTC", loc: "America/Los_Angeles"}, 343 | // yyyy-mm-dd hh:mm:ss +00:00 TZ 344 | {in: "2012-08-03 18:31:59 +00:00 UTC", out: "2012-08-03 18:31:59 +0000 UTC"}, 345 | {in: "2012-08-03 13:31:51 -07:00 MST", out: "2012-08-03 20:31:51 +0000 UTC", loc: "America/Denver"}, 346 | {in: "2012-08-03 18:31:59.257000000 +00:00 UTC", out: "2012-08-03 18:31:59.257 +0000 UTC"}, 347 | {in: "2012-08-03 13:31:51.123 -08:00 PST", out: "2012-08-03 21:31:51.123 +0000 UTC", loc: "America/Los_Angeles"}, 348 | {in: "2012-08-03 13:31:51.123 +02:00 CEST", out: "2012-08-03 11:31:51.123 +0000 UTC", loc: "Europe/Berlin"}, 349 | {in: "2012-08-03 8:1:59.257000000 +00:00 UTC", out: "2012-08-03 08:01:59.257 +0000 UTC"}, 350 | {in: "2012-8-03 18:31:59.257000000 +00:00 UTC", out: "2012-08-03 18:31:59.257 +0000 UTC"}, 351 | {in: "2012-8-3 18:31:59.257000000 +00:00 UTC", out: "2012-08-03 18:31:59.257 +0000 UTC"}, 352 | {in: "2014-04-26 17:24:37.123456 +00:00 UTC", out: "2014-04-26 17:24:37.123456 +0000 UTC"}, 353 | {in: "2014-04-26 17:24:37.12 +00:00 UTC", out: "2014-04-26 17:24:37.12 +0000 UTC"}, 354 | {in: "2014-04-26 17:24:37.1 +00:00 UTC", out: "2014-04-26 17:24:37.1 +0000 UTC"}, 355 | // yyyy-mm-ddThh:mm:ss 356 | {in: "2009-08-12T22:15:09", out: "2009-08-12 22:15:09 +0000 UTC"}, 357 | {in: "2009-08-08T02:08:08", out: "2009-08-08 02:08:08 +0000 UTC"}, 358 | {in: "2009-08-08T2:8:8", out: "2009-08-08 02:08:08 +0000 UTC"}, 359 | {in: "2009-08-12T22:15:09.123", out: "2009-08-12 22:15:09.123 +0000 UTC"}, 360 | {in: "2009-08-12T22:15:09.123456", out: "2009-08-12 22:15:09.123456 +0000 UTC"}, 361 | {in: "2009-08-12T22:15:09.12", out: "2009-08-12 22:15:09.12 +0000 UTC"}, 362 | {in: "2009-08-12T22:15:09.1", out: "2009-08-12 22:15:09.1 +0000 UTC"}, 363 | {in: "2014-04-26 17:24:37.3186369", out: "2014-04-26 17:24:37.3186369 +0000 UTC"}, 364 | // yyyy-mm-ddThh:mm:ss-07:00 365 | {in: "2009-08-12T22:15:09-07:00", out: "2009-08-13 05:15:09 +0000 UTC"}, 366 | {in: "2009-08-12T22:15:09-03:00", out: "2009-08-13 01:15:09 +0000 UTC"}, 367 | {in: "2009-08-12T22:15:9-07:00", out: "2009-08-13 05:15:09 +0000 UTC"}, 368 | {in: "2009-08-12T22:15:09.123-07:00", out: "2009-08-13 05:15:09.123 +0000 UTC"}, 369 | {in: "2016-06-21T19:55:00+01:00", out: "2016-06-21 18:55:00 +0000 UTC"}, 370 | {in: "2016-06-21T19:55:00.799+01:00", out: "2016-06-21 18:55:00.799 +0000 UTC"}, 371 | // yyyy-mm-ddThh:mm:ss-07 TZ truncated to 2 digits instead of 4 372 | {in: "2019-05-29T08:41-04", out: "2019-05-29 12:41:00 +0000 UTC"}, 373 | // yyyy-mm-ddThh:mm:ss-0700 374 | {in: "2009-08-12T22:15:09-0700", out: "2009-08-13 05:15:09 +0000 UTC"}, 375 | {in: "2009-08-12T22:15:09-0300", out: "2009-08-13 01:15:09 +0000 UTC"}, 376 | {in: "2009-08-12T22:15:9-0700", out: "2009-08-13 05:15:09 +0000 UTC"}, 377 | {in: "2009-08-12T22:15:09.123-0700", out: "2009-08-13 05:15:09.123 +0000 UTC"}, 378 | {in: "2016-06-21T19:55:00+0100", out: "2016-06-21 18:55:00 +0000 UTC"}, 379 | {in: "2016-06-21T19:55:00.799+0100", out: "2016-06-21 18:55:00.799 +0000 UTC"}, 380 | {in: "2016-06-21T19:55:00+0100", out: "2016-06-21 18:55:00 +0000 UTC"}, 381 | {in: "2016-06-21T19:55:00-0700", out: "2016-06-22 02:55:00 +0000 UTC"}, 382 | {in: "2016-06-21T19:55:00.799+0100", out: "2016-06-21 18:55:00.799 +0000 UTC"}, 383 | {in: "2016-06-21T19:55+0100", out: "2016-06-21 18:55:00 +0000 UTC"}, 384 | {in: "2016-06-21T19:55+0130", out: "2016-06-21 18:25:00 +0000 UTC"}, 385 | // yyyy-mm-ddThh:mm:ss:000+0000 - weird format with additional colon in front of milliseconds 386 | {in: "2012-08-17T18:31:59:257+0100", out: "2012-08-17 17:31:59.257 +0000 UTC"}, // https://github.com/araddon/dateparse/issues/117 387 | 388 | // yyyy-mm-ddThh:mm:ssZ 389 | {in: "2009-08-12T22:15Z", out: "2009-08-12 22:15:00 +0000 UTC"}, 390 | {in: "2009-08-12T22:15:09Z", out: "2009-08-12 22:15:09 +0000 UTC"}, 391 | {in: "2009-08-12T22:15:09.99Z", out: "2009-08-12 22:15:09.99 +0000 UTC"}, 392 | {in: "2009-08-12T22:15:09.9999Z", out: "2009-08-12 22:15:09.9999 +0000 UTC"}, 393 | {in: "2009-08-12T22:15:09.99999999Z", out: "2009-08-12 22:15:09.99999999 +0000 UTC"}, 394 | {in: "2009-08-12T22:15:9.99999999Z", out: "2009-08-12 22:15:09.99999999 +0000 UTC"}, 395 | // yyyy.mm 396 | {in: "2014.05", out: "2014-05-01 00:00:00 +0000 UTC"}, 397 | {in: "2018.09.30", out: "2018-09-30 00:00:00 +0000 UTC"}, 398 | 399 | // mm.dd.yyyy 400 | {in: "3.31.2014", out: "2014-03-31 00:00:00 +0000 UTC"}, 401 | {in: "3.3.2014", out: "2014-03-03 00:00:00 +0000 UTC"}, 402 | {in: "03.31.2014", out: "2014-03-31 00:00:00 +0000 UTC"}, 403 | // mm.dd.yy 404 | {in: "08.21.71", out: "1971-08-21 00:00:00 +0000 UTC"}, 405 | // yyyymmdd and similar 406 | {in: "2014", out: "2014-01-01 00:00:00 +0000 UTC"}, 407 | {in: "20140601", out: "2014-06-01 00:00:00 +0000 UTC"}, 408 | {in: "20140722105203", out: "2014-07-22 10:52:03 +0000 UTC"}, 409 | // yymmdd hh:mm:yy mysql log https://github.com/araddon/dateparse/issues/119 410 | // 080313 05:21:55 mysqld started 411 | // 080313 5:21:55 InnoDB: Started; log sequence number 0 43655 412 | {in: "171113 14:14:20", out: "2017-11-13 14:14:20 +0000 UTC"}, 413 | 414 | // all digits: unix secs, ms etc 415 | {in: "1332151919", out: "2012-03-19 10:11:59 +0000 UTC"}, 416 | {in: "1332151919", out: "2012-03-19 10:11:59 +0000 UTC", loc: "America/Denver"}, 417 | {in: "1384216367111", out: "2013-11-12 00:32:47.111 +0000 UTC"}, 418 | {in: "1384216367111222", out: "2013-11-12 00:32:47.111222 +0000 UTC"}, 419 | {in: "1384216367111222333", out: "2013-11-12 00:32:47.111222333 +0000 UTC"}, 420 | } 421 | 422 | func TestParse(t *testing.T) { 423 | 424 | // Lets ensure we are operating on UTC 425 | time.Local = time.UTC 426 | 427 | zeroTime := time.Time{}.Unix() 428 | ts, err := ParseAny("INVALID") 429 | assert.Equal(t, zeroTime, ts.Unix()) 430 | assert.NotEqual(t, nil, err) 431 | 432 | assert.Equal(t, true, testDidPanic("NOT GONNA HAPPEN")) 433 | // https://github.com/golang/go/issues/5294 434 | _, err = ParseAny(time.RFC3339) 435 | assert.NotEqual(t, nil, err) 436 | 437 | for _, th := range testInputs { 438 | if len(th.loc) > 0 { 439 | loc, err := time.LoadLocation(th.loc) 440 | if err != nil { 441 | t.Fatalf("Expected to load location %q but got %v", th.loc, err) 442 | } 443 | ts, err = ParseIn(th.in, loc) 444 | if err != nil { 445 | t.Fatalf("expected to parse %q but got %v", th.in, err) 446 | } 447 | got := fmt.Sprintf("%v", ts.In(time.UTC)) 448 | assert.Equal(t, th.out, got, "Expected %q but got %q from %q", th.out, got, th.in) 449 | if th.out != got { 450 | panic("whoops") 451 | } 452 | } else { 453 | ts = MustParse(th.in) 454 | got := fmt.Sprintf("%v", ts.In(time.UTC)) 455 | assert.Equal(t, th.out, got, "Expected %q but got %q from %q", th.out, got, th.in) 456 | if th.out != got { 457 | panic("whoops") 458 | } 459 | } 460 | } 461 | 462 | // some errors 463 | 464 | assert.Equal(t, true, testDidPanic(`{"ts":"now"}`)) 465 | 466 | _, err = ParseAny("138421636711122233311111") // too many digits 467 | assert.NotEqual(t, nil, err) 468 | 469 | _, err = ParseAny("-1314") 470 | assert.NotEqual(t, nil, err) 471 | 472 | _, err = ParseAny("2014-13-13 08:20:13,787") // month 13 doesn't exist so error 473 | assert.NotEqual(t, nil, err) 474 | } 475 | 476 | func testDidPanic(datestr string) (paniced bool) { 477 | defer func() { 478 | if r := recover(); r != nil { 479 | paniced = true 480 | } 481 | }() 482 | MustParse(datestr) 483 | return false 484 | } 485 | 486 | func TestPStruct(t *testing.T) { 487 | 488 | denverLoc, err := time.LoadLocation("America/Denver") 489 | assert.Equal(t, nil, err) 490 | 491 | p := newParser("08.21.71", denverLoc) 492 | 493 | p.setMonth() 494 | assert.Equal(t, 0, p.moi) 495 | p.setDay() 496 | assert.Equal(t, 0, p.dayi) 497 | p.set(-1, "not") 498 | p.set(15, "not") 499 | assert.Equal(t, "08.21.71", p.datestr) 500 | assert.Equal(t, "08.21.71", string(p.format)) 501 | assert.True(t, len(p.ds()) > 0) 502 | assert.True(t, len(p.ts()) > 0) 503 | } 504 | 505 | var testParseErrors = []dateTest{ 506 | {in: "3", err: true}, 507 | {in: `{"hello"}`, err: true}, 508 | {in: "2009-15-12T22:15Z", err: true}, 509 | {in: "5,000-9,999", err: true}, 510 | {in: "xyzq-baad"}, 511 | {in: "oct.-7-1970", err: true}, 512 | {in: "septe. 7, 1970", err: true}, 513 | {in: "SeptemberRR 7th, 1970", err: true}, 514 | {in: "29-06-2016", err: true}, 515 | // this is just testing the empty space up front 516 | {in: " 2018-01-02 17:08:09 -07:00", err: true}, 517 | } 518 | 519 | func TestParseErrors(t *testing.T) { 520 | for _, th := range testParseErrors { 521 | v, err := ParseAny(th.in) 522 | assert.NotEqual(t, nil, err, "%v for %v", v, th.in) 523 | 524 | v, err = ParseAny(th.in, RetryAmbiguousDateWithSwap(true)) 525 | assert.NotEqual(t, nil, err, "%v for %v", v, th.in) 526 | } 527 | } 528 | 529 | func TestParseLayout(t *testing.T) { 530 | 531 | time.Local = time.UTC 532 | // These tests are verifying that the layout returned by ParseFormat 533 | // are correct. Above tests correct parsing, this tests correct 534 | // re-usable formatting string 535 | var testParseFormat = []dateTest{ 536 | // errors 537 | {in: "3", err: true}, 538 | {in: `{"hello"}`, err: true}, 539 | {in: "2009-15-12T22:15Z", err: true}, 540 | {in: "5,000-9,999", err: true}, 541 | // This 3 digit TZ offset (should be 2 or 4? is 3 a thing?) 542 | {in: "2019-05-29T08:41-047", err: true}, 543 | // 544 | {in: "06/May/2008 15:04:05 -0700", out: "02/Jan/2006 15:04:05 -0700"}, 545 | {in: "06/May/2008:15:04:05 -0700", out: "02/Jan/2006:15:04:05 -0700"}, 546 | {in: "14 May 2019 19:11:40.164", out: "02 Jan 2006 15:04:05.000"}, 547 | {in: "171113 14:14:20", out: "060102 15:04:05"}, 548 | 549 | {in: "oct 7, 1970", out: "Jan 2, 2006"}, 550 | {in: "sept. 7, 1970", out: "Jan. 2, 2006"}, 551 | {in: "May 05, 2015, 05:05:07", out: "Jan 02, 2006, 15:04:05"}, 552 | // 03 February 2013 553 | {in: "03 February 2013", out: "02 January 2006"}, 554 | // 13:31:51.999 -07:00 MST 555 | // yyyy-mm-dd hh:mm:ss +00:00 556 | {in: "2012-08-03 18:31:59 +00:00", out: "2006-01-02 15:04:05 -07:00"}, 557 | // yyyy-mm-dd hh:mm:ss +0000 TZ 558 | // Golang Native Format = "2006-01-02 15:04:05.999999999 -0700 MST" 559 | {in: "2012-08-03 18:31:59 +0000 UTC", out: "2006-01-02 15:04:05 -0700 MST"}, 560 | // yyyy-mm-dd hh:mm:ss TZ 561 | {in: "2012-08-03 18:31:59 UTC", out: "2006-01-02 15:04:05 MST"}, 562 | {in: "2012-08-03 18:31:59 CEST", out: "2006-01-02 15:04:05 MST"}, 563 | // yyyy-mm-ddThh:mm:ss-07:00 564 | {in: "2009-08-12T22:15:09-07:00", out: "2006-01-02T15:04:05-07:00"}, 565 | // yyyy-mm-ddThh:mm:ss-0700 566 | {in: "2009-08-12T22:15:09-0700", out: "2006-01-02T15:04:05-0700"}, 567 | // yyyy-mm-ddThh:mm:ssZ 568 | {in: "2009-08-12T22:15Z", out: "2006-01-02T15:04Z"}, 569 | } 570 | 571 | for _, th := range testParseFormat { 572 | l, err := ParseFormat(th.in) 573 | if th.err { 574 | assert.NotEqual(t, nil, err) 575 | } else { 576 | assert.Equal(t, nil, err) 577 | assert.Equal(t, th.out, l, "for in=%v", th.in) 578 | } 579 | } 580 | } 581 | 582 | var testParseStrict = []dateTest{ 583 | // dd-mon-yy 13-Feb-03 584 | {in: "03-03-14"}, 585 | // mm.dd.yyyy 586 | {in: "3.3.2014"}, 587 | // mm.dd.yy 588 | {in: "08.09.71"}, 589 | // mm/dd/yyyy 590 | {in: "3/5/2014"}, 591 | // mm/dd/yy 592 | {in: "08/08/71"}, 593 | {in: "8/8/71"}, 594 | // mm/dd/yy hh:mm:ss 595 | {in: "04/02/2014 04:08:09"}, 596 | {in: "4/2/2014 04:08:09"}, 597 | } 598 | 599 | func TestParseStrict(t *testing.T) { 600 | 601 | for _, th := range testParseStrict { 602 | _, err := ParseStrict(th.in) 603 | assert.NotEqual(t, nil, err) 604 | } 605 | 606 | _, err := ParseStrict(`{"hello"}`) 607 | assert.NotEqual(t, nil, err) 608 | 609 | _, err = ParseStrict("2009-08-12T22:15Z") 610 | assert.Equal(t, nil, err) 611 | } 612 | 613 | // Lets test to see how this performs using different Timezones/Locations 614 | // Also of note, try changing your server/machine timezones and repeat 615 | // 616 | // !!!!! The time-zone of local machine effects the results! 617 | // https://play.golang.org/p/IDHRalIyXh 618 | // https://github.com/golang/go/issues/18012 619 | func TestInLocation(t *testing.T) { 620 | 621 | denverLoc, err := time.LoadLocation("America/Denver") 622 | assert.Equal(t, nil, err) 623 | 624 | // Start out with time.UTC 625 | time.Local = time.UTC 626 | 627 | // Just normal parse to test out zone/offset 628 | ts := MustParse("2013-02-01 00:00:00") 629 | zone, offset := ts.Zone() 630 | assert.Equal(t, 0, offset, "Should have found offset = 0 %v", offset) 631 | assert.Equal(t, "UTC", zone, "Should have found zone = UTC %v", zone) 632 | assert.Equal(t, "2013-02-01 00:00:00 +0000 UTC", fmt.Sprintf("%v", ts.In(time.UTC))) 633 | 634 | // Now lets set to denver (MST/MDT) and re-parse the same time string 635 | // and since no timezone info in string, we expect same result 636 | time.Local = denverLoc 637 | ts = MustParse("2013-02-01 00:00:00") 638 | zone, offset = ts.Zone() 639 | assert.Equal(t, 0, offset, "Should have found offset = 0 %v", offset) 640 | assert.Equal(t, "UTC", zone, "Should have found zone = UTC %v", zone) 641 | assert.Equal(t, "2013-02-01 00:00:00 +0000 UTC", fmt.Sprintf("%v", ts.In(time.UTC))) 642 | 643 | ts = MustParse("Tue, 5 Jul 2017 16:28:13 -0700 (MST)") 644 | assert.Equal(t, "2017-07-05 23:28:13 +0000 UTC", fmt.Sprintf("%v", ts.In(time.UTC))) 645 | 646 | // Now we are going to use ParseIn() and see that it gives different answer 647 | // with different zone, offset 648 | time.Local = nil 649 | ts, err = ParseIn("2013-02-01 00:00:00", denverLoc) 650 | assert.Equal(t, nil, err) 651 | zone, offset = ts.Zone() 652 | assert.Equal(t, -25200, offset, "Should have found offset = -25200 %v %v", offset, denverLoc) 653 | assert.Equal(t, "MST", zone, "Should have found zone = MST %v", zone) 654 | assert.Equal(t, "2013-02-01 07:00:00 +0000 UTC", fmt.Sprintf("%v", ts.In(time.UTC))) 655 | 656 | ts, err = ParseIn("18 January 2018", denverLoc) 657 | assert.Equal(t, nil, err) 658 | zone, offset = ts.Zone() 659 | assert.Equal(t, -25200, offset, "Should have found offset = 0 %v", offset) 660 | assert.Equal(t, "MST", zone, "Should have found zone = UTC %v", zone) 661 | assert.Equal(t, "2018-01-18 07:00:00 +0000 UTC", fmt.Sprintf("%v", ts.In(time.UTC))) 662 | 663 | // Now we are going to use ParseLocal() and see that it gives same 664 | // answer as ParseIn when we have time.Local set to a location 665 | time.Local = denverLoc 666 | ts, err = ParseLocal("2013-02-01 00:00:00") 667 | assert.Equal(t, nil, err) 668 | zone, offset = ts.Zone() 669 | assert.Equal(t, -25200, offset, "Should have found offset = -25200 %v %v", offset, denverLoc) 670 | assert.Equal(t, "MST", zone, "Should have found zone = MST %v", zone) 671 | assert.Equal(t, "2013-02-01 07:00:00 +0000 UTC", fmt.Sprintf("%v", ts.In(time.UTC))) 672 | 673 | // Lets advance past daylight savings time start 674 | // use parseIn and see offset/zone has changed to Daylight Savings Equivalents 675 | ts, err = ParseIn("2013-04-01 00:00:00", denverLoc) 676 | assert.Equal(t, nil, err) 677 | zone, offset = ts.Zone() 678 | assert.Equal(t, -21600, offset, "Should have found offset = -21600 %v %v", offset, denverLoc) 679 | assert.Equal(t, "MDT", zone, "Should have found zone = MDT %v", zone) 680 | assert.Equal(t, "2013-04-01 06:00:00 +0000 UTC", fmt.Sprintf("%v", ts.In(time.UTC))) 681 | 682 | // reset to UTC 683 | time.Local = time.UTC 684 | 685 | // UnixDate = "Mon Jan _2 15:04:05 MST 2006" 686 | ts = MustParse("Mon Jan 2 15:04:05 MST 2006") 687 | 688 | _, offset = ts.Zone() 689 | assert.Equal(t, 0, offset, "Should have found offset = 0 %v", offset) 690 | assert.Equal(t, "2006-01-02 15:04:05 +0000 UTC", fmt.Sprintf("%v", ts.In(time.UTC))) 691 | 692 | // Now lets set to denver(mst/mdt) 693 | time.Local = denverLoc 694 | ts = MustParse("Mon Jan 2 15:04:05 MST 2006") 695 | 696 | // this time is different from one above parsed with time.Local set to UTC 697 | _, offset = ts.Zone() 698 | assert.Equal(t, -25200, offset, "Should have found offset = -25200 %v", offset) 699 | assert.Equal(t, "2006-01-02 22:04:05 +0000 UTC", fmt.Sprintf("%v", ts.In(time.UTC))) 700 | 701 | // Now Reset To UTC 702 | time.Local = time.UTC 703 | 704 | // RFC850 = "Monday, 02-Jan-06 15:04:05 MST" 705 | ts = MustParse("Monday, 02-Jan-06 15:04:05 MST") 706 | _, offset = ts.Zone() 707 | assert.Equal(t, 0, offset, "Should have found offset = 0 %v", offset) 708 | assert.Equal(t, "2006-01-02 15:04:05 +0000 UTC", fmt.Sprintf("%v", ts.In(time.UTC))) 709 | 710 | // Now lets set to denver 711 | time.Local = denverLoc 712 | ts = MustParse("Monday, 02-Jan-06 15:04:05 MST") 713 | _, offset = ts.Zone() 714 | assert.NotEqual(t, 0, offset, "Should have found offset %v", offset) 715 | assert.Equal(t, "2006-01-02 22:04:05 +0000 UTC", fmt.Sprintf("%v", ts.In(time.UTC))) 716 | 717 | // Now some errors 718 | zeroTime := time.Time{}.Unix() 719 | ts, err = ParseIn("INVALID", denverLoc) 720 | assert.Equal(t, zeroTime, ts.Unix()) 721 | assert.NotEqual(t, nil, err) 722 | 723 | ts, err = ParseLocal("INVALID") 724 | assert.Equal(t, zeroTime, ts.Unix()) 725 | assert.NotEqual(t, nil, err) 726 | } 727 | 728 | func TestPreferMonthFirst(t *testing.T) { 729 | // default case is true 730 | ts, err := ParseAny("04/02/2014 04:08:09 +0000 UTC") 731 | assert.Equal(t, nil, err) 732 | assert.Equal(t, "2014-04-02 04:08:09 +0000 UTC", fmt.Sprintf("%v", ts.In(time.UTC))) 733 | 734 | preferMonthFirstTrue := PreferMonthFirst(true) 735 | ts, err = ParseAny("04/02/2014 04:08:09 +0000 UTC", preferMonthFirstTrue) 736 | assert.Equal(t, nil, err) 737 | assert.Equal(t, "2014-04-02 04:08:09 +0000 UTC", fmt.Sprintf("%v", ts.In(time.UTC))) 738 | 739 | // allows the day to be preferred before the month, when completely ambiguous 740 | preferMonthFirstFalse := PreferMonthFirst(false) 741 | ts, err = ParseAny("04/02/2014 04:08:09 +0000 UTC", preferMonthFirstFalse) 742 | assert.Equal(t, nil, err) 743 | assert.Equal(t, "2014-02-04 04:08:09 +0000 UTC", fmt.Sprintf("%v", ts.In(time.UTC))) 744 | } 745 | 746 | func TestRetryAmbiguousDateWithSwap(t *testing.T) { 747 | // default is false 748 | _, err := ParseAny("13/02/2014 04:08:09 +0000 UTC") 749 | assert.NotEqual(t, nil, err) 750 | 751 | // will fail error if the month preference cannot work due to the value being larger than 12 752 | retryAmbiguousDateWithSwapFalse := RetryAmbiguousDateWithSwap(false) 753 | _, err = ParseAny("13/02/2014 04:08:09 +0000 UTC", retryAmbiguousDateWithSwapFalse) 754 | assert.NotEqual(t, nil, err) 755 | 756 | // will retry with the other month preference if this error is detected 757 | retryAmbiguousDateWithSwapTrue := RetryAmbiguousDateWithSwap(true) 758 | ts, err := ParseAny("13/02/2014 04:08:09 +0000 UTC", retryAmbiguousDateWithSwapTrue) 759 | assert.Equal(t, nil, err) 760 | assert.Equal(t, "2014-02-13 04:08:09 +0000 UTC", fmt.Sprintf("%v", ts.In(time.UTC))) 761 | } 762 | -------------------------------------------------------------------------------- /parseany.go: -------------------------------------------------------------------------------- 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. 4 | package dateparse 5 | 6 | import ( 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 | 20 | var days = []string{ 21 | "mon", 22 | "tue", 23 | "wed", 24 | "thu", 25 | "fri", 26 | "sat", 27 | "sun", 28 | "monday", 29 | "tuesday", 30 | "wednesday", 31 | "thursday", 32 | "friday", 33 | "saturday", 34 | "sunday", 35 | } 36 | 37 | var months = []string{ 38 | "january", 39 | "february", 40 | "march", 41 | "april", 42 | "may", 43 | "june", 44 | "july", 45 | "august", 46 | "september", 47 | "october", 48 | "november", 49 | "december", 50 | } 51 | 52 | type dateState uint8 53 | type timeState uint8 54 | 55 | const ( 56 | dateStart dateState = iota // 0 57 | dateDigit 58 | dateDigitSt 59 | dateYearDash 60 | dateYearDashAlphaDash 61 | dateYearDashDash 62 | dateYearDashDashWs // 5 63 | dateYearDashDashT 64 | dateYearDashDashOffset 65 | dateDigitDash 66 | dateDigitDashAlpha 67 | dateDigitDashAlphaDash // 10 68 | dateDigitDot 69 | dateDigitDotDot 70 | dateDigitSlash 71 | dateDigitYearSlash 72 | dateDigitSlashAlpha // 15 73 | dateDigitColon 74 | dateDigitChineseYear 75 | dateDigitChineseYearWs 76 | dateDigitWs 77 | dateDigitWsMoYear // 20 78 | dateDigitWsMolong 79 | dateAlpha 80 | dateAlphaWs 81 | dateAlphaWsDigit 82 | dateAlphaWsDigitMore // 25 83 | dateAlphaWsDigitMoreWs 84 | dateAlphaWsDigitMoreWsYear 85 | dateAlphaWsMonth 86 | dateAlphaWsDigitYearmaybe 87 | dateAlphaWsMonthMore 88 | dateAlphaWsMonthSuffix 89 | dateAlphaWsMore 90 | dateAlphaWsAtTime 91 | dateAlphaWsAlpha 92 | dateAlphaWsAlphaYearmaybe // 35 93 | dateAlphaPeriodWsDigit 94 | dateWeekdayComma 95 | dateWeekdayAbbrevComma 96 | ) 97 | const ( 98 | // Time state 99 | timeIgnore timeState = iota // 0 100 | timeStart 101 | timeWs 102 | timeWsAlpha 103 | timeWsAlphaWs 104 | timeWsAlphaZoneOffset // 5 105 | timeWsAlphaZoneOffsetWs 106 | timeWsAlphaZoneOffsetWsYear 107 | timeWsAlphaZoneOffsetWsExtra 108 | timeWsAMPMMaybe 109 | timeWsAMPM // 10 110 | timeWsOffset 111 | timeWsOffsetWs // 12 112 | timeWsOffsetColonAlpha 113 | timeWsOffsetColon 114 | timeWsYear // 15 115 | timeOffset 116 | timeOffsetColon 117 | timeAlpha 118 | timePeriod 119 | timePeriodOffset // 20 120 | timePeriodOffsetColon 121 | timePeriodOffsetColonWs 122 | timePeriodWs 123 | timePeriodWsAlpha 124 | timePeriodWsOffset // 25 125 | timePeriodWsOffsetWs 126 | timePeriodWsOffsetWsAlpha 127 | timePeriodWsOffsetColon 128 | timePeriodWsOffsetColonAlpha 129 | timeZ 130 | timeZDigit 131 | ) 132 | 133 | var ( 134 | // ErrAmbiguousMMDD for date formats such as 04/02/2014 the mm/dd vs dd/mm are 135 | // ambiguous, so it is an error for strict parse rules. 136 | ErrAmbiguousMMDD = fmt.Errorf("This date has ambiguous mm/dd vs dd/mm type format") 137 | ) 138 | 139 | func unknownErr(datestr string) error { 140 | return fmt.Errorf("Could not find format for %q", datestr) 141 | } 142 | 143 | // ParseAny parse an unknown date format, detect the layout. 144 | // Normal parse. Equivalent Timezone rules as time.Parse(). 145 | // NOTE: please see readme on mmdd vs ddmm ambiguous dates. 146 | func ParseAny(datestr string, opts ...ParserOption) (time.Time, error) { 147 | p, err := parseTime(datestr, nil, opts...) 148 | if err != nil { 149 | return time.Time{}, err 150 | } 151 | return p.parse() 152 | } 153 | 154 | // ParseIn with Location, equivalent to time.ParseInLocation() timezone/offset 155 | // rules. Using location arg, if timezone/offset info exists in the 156 | // datestring, it uses the given location rules for any zone interpretation. 157 | // That is, MST means one thing when using America/Denver and something else 158 | // in other locations. 159 | func ParseIn(datestr string, loc *time.Location, opts ...ParserOption) (time.Time, error) { 160 | p, err := parseTime(datestr, loc, opts...) 161 | if err != nil { 162 | return time.Time{}, err 163 | } 164 | return p.parse() 165 | } 166 | 167 | // ParseLocal Given an unknown date format, detect the layout, 168 | // using time.Local, parse. 169 | // 170 | // Set Location to time.Local. Same as ParseIn Location but lazily uses 171 | // the global time.Local variable for Location argument. 172 | // 173 | // denverLoc, _ := time.LoadLocation("America/Denver") 174 | // time.Local = denverLoc 175 | // 176 | // t, err := dateparse.ParseLocal("3/1/2014") 177 | // 178 | // Equivalent to: 179 | // 180 | // t, err := dateparse.ParseIn("3/1/2014", denverLoc) 181 | // 182 | func ParseLocal(datestr string, opts ...ParserOption) (time.Time, error) { 183 | p, err := parseTime(datestr, time.Local, opts...) 184 | if err != nil { 185 | return time.Time{}, err 186 | } 187 | return p.parse() 188 | } 189 | 190 | // MustParse parse a date, and panic if it can't be parsed. Used for testing. 191 | // Not recommended for most use-cases. 192 | func MustParse(datestr string, opts ...ParserOption) time.Time { 193 | p, err := parseTime(datestr, nil, opts...) 194 | if err != nil { 195 | panic(err.Error()) 196 | } 197 | t, err := p.parse() 198 | if err != nil { 199 | panic(err.Error()) 200 | } 201 | return t 202 | } 203 | 204 | // ParseFormat parse's an unknown date-time string and returns a layout 205 | // string that can parse this (and exact same format) other date-time strings. 206 | // 207 | // layout, err := dateparse.ParseFormat("2013-02-01 00:00:00") 208 | // // layout = "2006-01-02 15:04:05" 209 | // 210 | func ParseFormat(datestr string, opts ...ParserOption) (string, error) { 211 | p, err := parseTime(datestr, nil, opts...) 212 | if err != nil { 213 | return "", err 214 | } 215 | _, err = p.parse() 216 | if err != nil { 217 | return "", err 218 | } 219 | return string(p.format), nil 220 | } 221 | 222 | // ParseStrict parse an unknown date format. IF the date is ambigous 223 | // mm/dd vs dd/mm then return an error. These return errors: 3.3.2014 , 8/8/71 etc 224 | func ParseStrict(datestr string, opts ...ParserOption) (time.Time, error) { 225 | p, err := parseTime(datestr, nil, opts...) 226 | if err != nil { 227 | return time.Time{}, err 228 | } 229 | if p.ambiguousMD { 230 | return time.Time{}, ErrAmbiguousMMDD 231 | } 232 | return p.parse() 233 | } 234 | 235 | func parseTime(datestr string, loc *time.Location, opts ...ParserOption) (p *parser, err error) { 236 | 237 | p = newParser(datestr, loc, opts...) 238 | if p.retryAmbiguousDateWithSwap { 239 | // month out of range signifies that a day/month swap is the correct solution to an ambiguous date 240 | // this is because it means that a day is being interpreted as a month and overflowing the valid value for that 241 | // by retrying in this case, we can fix a common situation with no assumptions 242 | defer func() { 243 | if p != nil && p.ambiguousMD { 244 | // if it errors out with the following error, swap before we 245 | // get out of this function to reduce scope it needs to be applied on 246 | _, err := p.parse() 247 | if err != nil && strings.Contains(err.Error(), "month out of range") { 248 | // create the option to reverse the preference 249 | preferMonthFirst := PreferMonthFirst(!p.preferMonthFirst) 250 | // turn off the retry to avoid endless recursion 251 | retryAmbiguousDateWithSwap := RetryAmbiguousDateWithSwap(false) 252 | modifiedOpts := append(opts, preferMonthFirst, retryAmbiguousDateWithSwap) 253 | p, err = parseTime(datestr, time.Local, modifiedOpts...) 254 | } 255 | } 256 | 257 | }() 258 | } 259 | 260 | i := 0 261 | 262 | // General strategy is to read rune by rune through the date looking for 263 | // certain hints of what type of date we are dealing with. 264 | // Hopefully we only need to read about 5 or 6 bytes before 265 | // we figure it out and then attempt a parse 266 | iterRunes: 267 | for ; i < len(datestr); i++ { 268 | //r := rune(datestr[i]) 269 | r, bytesConsumed := utf8.DecodeRuneInString(datestr[i:]) 270 | if bytesConsumed > 1 { 271 | i += (bytesConsumed - 1) 272 | } 273 | 274 | // gou.Debugf("i=%d r=%s state=%d %s", i, string(r), p.stateDate, datestr) 275 | switch p.stateDate { 276 | case dateStart: 277 | if unicode.IsDigit(r) { 278 | p.stateDate = dateDigit 279 | } else if unicode.IsLetter(r) { 280 | p.stateDate = dateAlpha 281 | } else { 282 | return nil, unknownErr(datestr) 283 | } 284 | case dateDigit: 285 | 286 | switch r { 287 | case '-', '\u2212': 288 | // 2006-01-02 289 | // 2013-Feb-03 290 | // 13-Feb-03 291 | // 29-Jun-2016 292 | if i == 4 { 293 | p.stateDate = dateYearDash 294 | p.yeari = 0 295 | p.yearlen = i 296 | p.moi = i + 1 297 | p.set(0, "2006") 298 | } else { 299 | p.stateDate = dateDigitDash 300 | } 301 | case '/': 302 | // 08/May/2005 303 | // 03/31/2005 304 | // 2014/02/24 305 | p.stateDate = dateDigitSlash 306 | if i == 4 { 307 | // 2014/02/24 - Year first / 308 | p.yearlen = i // since it was start of datestr, i=len 309 | p.moi = i + 1 310 | p.setYear() 311 | p.stateDate = dateDigitYearSlash 312 | } else { 313 | // Either Ambiguous dd/mm vs mm/dd OR dd/month/yy 314 | // 08/May/2005 315 | // 03/31/2005 316 | // 31/03/2005 317 | if i+2 < len(p.datestr) && unicode.IsLetter(rune(datestr[i+1])) { 318 | // 08/May/2005 319 | p.stateDate = dateDigitSlashAlpha 320 | p.moi = i + 1 321 | p.daylen = 2 322 | p.dayi = 0 323 | p.setDay() 324 | continue 325 | } 326 | // Ambiguous dd/mm vs mm/dd the bane of date-parsing 327 | // 03/31/2005 328 | // 31/03/2005 329 | p.ambiguousMD = true 330 | if p.preferMonthFirst { 331 | if p.molen == 0 { 332 | // 03/31/2005 333 | p.molen = i 334 | p.setMonth() 335 | p.dayi = i + 1 336 | } 337 | } else { 338 | if p.daylen == 0 { 339 | p.daylen = i 340 | p.setDay() 341 | p.moi = i + 1 342 | } 343 | } 344 | 345 | } 346 | 347 | case ':': 348 | // 03/31/2005 349 | // 2014/02/24 350 | p.stateDate = dateDigitColon 351 | if i == 4 { 352 | p.yearlen = i 353 | p.moi = i + 1 354 | p.setYear() 355 | } else { 356 | p.ambiguousMD = true 357 | if p.preferMonthFirst { 358 | if p.molen == 0 { 359 | p.molen = i 360 | p.setMonth() 361 | p.dayi = i + 1 362 | } 363 | } 364 | } 365 | 366 | case '.': 367 | // 3.31.2014 368 | // 08.21.71 369 | // 2014.05 370 | p.stateDate = dateDigitDot 371 | if i == 4 { 372 | p.yearlen = i 373 | p.moi = i + 1 374 | p.setYear() 375 | } else { 376 | p.ambiguousMD = true 377 | p.moi = 0 378 | p.molen = i 379 | p.setMonth() 380 | p.dayi = i + 1 381 | } 382 | 383 | case ' ': 384 | // 18 January 2018 385 | // 8 January 2018 386 | // 8 jan 2018 387 | // 02 Jan 2018 23:59 388 | // 02 Jan 2018 23:59:34 389 | // 12 Feb 2006, 19:17 390 | // 12 Feb 2006, 19:17:22 391 | if i == 6 { 392 | p.stateDate = dateDigitSt 393 | } else { 394 | p.stateDate = dateDigitWs 395 | p.dayi = 0 396 | p.daylen = i 397 | } 398 | case '年': 399 | // Chinese Year 400 | p.stateDate = dateDigitChineseYear 401 | case ',': 402 | return nil, unknownErr(datestr) 403 | default: 404 | continue 405 | } 406 | p.part1Len = i 407 | 408 | case dateDigitSt: 409 | p.set(0, "060102") 410 | i = i - 1 411 | p.stateTime = timeStart 412 | break iterRunes 413 | case dateYearDash: 414 | // dateYearDashDashT 415 | // 2006-01-02T15:04:05Z07:00 416 | // 2020-08-17T17:00:00:000+0100 417 | // dateYearDashDashWs 418 | // 2013-04-01 22:43:22 419 | // dateYearDashAlphaDash 420 | // 2013-Feb-03 421 | switch r { 422 | case '-': 423 | p.molen = i - p.moi 424 | p.dayi = i + 1 425 | p.stateDate = dateYearDashDash 426 | p.setMonth() 427 | default: 428 | if unicode.IsLetter(r) { 429 | p.stateDate = dateYearDashAlphaDash 430 | } 431 | } 432 | 433 | case dateYearDashDash: 434 | // dateYearDashDashT 435 | // 2006-01-02T15:04:05Z07:00 436 | // dateYearDashDashWs 437 | // 2013-04-01 22:43:22 438 | // dateYearDashDashOffset 439 | // 2020-07-20+00:00 440 | switch r { 441 | case '+', '-': 442 | p.offseti = i 443 | p.daylen = i - p.dayi 444 | p.stateDate = dateYearDashDashOffset 445 | p.setDay() 446 | case ' ': 447 | p.daylen = i - p.dayi 448 | p.stateDate = dateYearDashDashWs 449 | p.stateTime = timeStart 450 | p.setDay() 451 | break iterRunes 452 | case 'T': 453 | p.daylen = i - p.dayi 454 | p.stateDate = dateYearDashDashT 455 | p.stateTime = timeStart 456 | p.setDay() 457 | break iterRunes 458 | } 459 | 460 | case dateYearDashDashT: 461 | // dateYearDashDashT 462 | // 2006-01-02T15:04:05Z07:00 463 | // 2020-08-17T17:00:00:000+0100 464 | 465 | case dateYearDashDashOffset: 466 | // 2020-07-20+00:00 467 | switch r { 468 | case ':': 469 | p.set(p.offseti, "-07:00") 470 | // case ' ': 471 | // return nil, unknownErr(datestr) 472 | } 473 | 474 | case dateYearDashAlphaDash: 475 | // 2013-Feb-03 476 | switch r { 477 | case '-': 478 | p.molen = i - p.moi 479 | p.set(p.moi, "Jan") 480 | p.dayi = i + 1 481 | } 482 | case dateDigitDash: 483 | // 13-Feb-03 484 | // 29-Jun-2016 485 | if unicode.IsLetter(r) { 486 | p.stateDate = dateDigitDashAlpha 487 | p.moi = i 488 | } else { 489 | return nil, unknownErr(datestr) 490 | } 491 | case dateDigitDashAlpha: 492 | // 13-Feb-03 493 | // 28-Feb-03 494 | // 29-Jun-2016 495 | switch r { 496 | case '-': 497 | p.molen = i - p.moi 498 | p.set(p.moi, "Jan") 499 | p.yeari = i + 1 500 | p.stateDate = dateDigitDashAlphaDash 501 | } 502 | 503 | case dateDigitDashAlphaDash: 504 | // 13-Feb-03 ambiguous 505 | // 28-Feb-03 ambiguous 506 | // 29-Jun-2016 dd-month(alpha)-yyyy 507 | switch r { 508 | case ' ': 509 | // we need to find if this was 4 digits, aka year 510 | // or 2 digits which makes it ambiguous year/day 511 | length := i - (p.moi + p.molen + 1) 512 | if length == 4 { 513 | p.yearlen = 4 514 | p.set(p.yeari, "2006") 515 | // We now also know that part1 was the day 516 | p.dayi = 0 517 | p.daylen = p.part1Len 518 | p.setDay() 519 | } else if length == 2 { 520 | // We have no idea if this is 521 | // yy-mon-dd OR dd-mon-yy 522 | // 523 | // We are going to ASSUME (bad, bad) that it is dd-mon-yy which is a horible assumption 524 | p.ambiguousMD = true 525 | p.yearlen = 2 526 | p.set(p.yeari, "06") 527 | // We now also know that part1 was the day 528 | p.dayi = 0 529 | p.daylen = p.part1Len 530 | p.setDay() 531 | } 532 | p.stateTime = timeStart 533 | break iterRunes 534 | } 535 | 536 | case dateDigitYearSlash: 537 | // 2014/07/10 06:55:38.156283 538 | // I honestly don't know if this format ever shows up as yyyy/ 539 | 540 | switch r { 541 | case ' ', ':': 542 | p.stateTime = timeStart 543 | if p.daylen == 0 { 544 | p.daylen = i - p.dayi 545 | p.setDay() 546 | } 547 | break iterRunes 548 | case '/': 549 | if p.molen == 0 { 550 | p.molen = i - p.moi 551 | p.setMonth() 552 | p.dayi = i + 1 553 | } 554 | } 555 | 556 | case dateDigitSlashAlpha: 557 | // 06/May/2008 558 | 559 | switch r { 560 | case '/': 561 | // | 562 | // 06/May/2008 563 | if p.molen == 0 { 564 | p.set(p.moi, "Jan") 565 | p.yeari = i + 1 566 | } 567 | // We aren't breaking because we are going to re-use this case 568 | // to find where the date starts, and possible time begins 569 | case ' ', ':': 570 | p.stateTime = timeStart 571 | if p.yearlen == 0 { 572 | p.yearlen = i - p.yeari 573 | p.setYear() 574 | } 575 | break iterRunes 576 | } 577 | 578 | case dateDigitSlash: 579 | // 03/19/2012 10:11:59 580 | // 04/2/2014 03:00:37 581 | // 3/1/2012 10:11:59 582 | // 4/8/2014 22:05 583 | // 3/1/2014 584 | // 10/13/2014 585 | // 01/02/2006 586 | // 1/2/06 587 | 588 | switch r { 589 | case '/': 590 | // This is the 2nd / so now we should know start pts of all of the dd, mm, yy 591 | if p.preferMonthFirst { 592 | if p.daylen == 0 { 593 | p.daylen = i - p.dayi 594 | p.setDay() 595 | p.yeari = i + 1 596 | } 597 | } else { 598 | if p.molen == 0 { 599 | p.molen = i - p.moi 600 | p.setMonth() 601 | p.yeari = i + 1 602 | } 603 | } 604 | // Note no break, we are going to pass by and re-enter this dateDigitSlash 605 | // and look for ending (space) or not (just date) 606 | case ' ': 607 | p.stateTime = timeStart 608 | if p.yearlen == 0 { 609 | p.yearlen = i - p.yeari 610 | p.setYear() 611 | } 612 | break iterRunes 613 | } 614 | 615 | case dateDigitColon: 616 | // 2014:07:10 06:55:38.156283 617 | // 03:19:2012 10:11:59 618 | // 04:2:2014 03:00:37 619 | // 3:1:2012 10:11:59 620 | // 4:8:2014 22:05 621 | // 3:1:2014 622 | // 10:13:2014 623 | // 01:02:2006 624 | // 1:2:06 625 | 626 | switch r { 627 | case ' ': 628 | p.stateTime = timeStart 629 | if p.yearlen == 0 { 630 | p.yearlen = i - p.yeari 631 | p.setYear() 632 | } else if p.daylen == 0 { 633 | p.daylen = i - p.dayi 634 | p.setDay() 635 | } 636 | break iterRunes 637 | case ':': 638 | if p.yearlen > 0 { 639 | // 2014:07:10 06:55:38.156283 640 | if p.molen == 0 { 641 | p.molen = i - p.moi 642 | p.setMonth() 643 | p.dayi = i + 1 644 | } 645 | } else if p.preferMonthFirst { 646 | if p.daylen == 0 { 647 | p.daylen = i - p.dayi 648 | p.setDay() 649 | p.yeari = i + 1 650 | } 651 | } 652 | } 653 | 654 | case dateDigitWs: 655 | // 18 January 2018 656 | // 8 January 2018 657 | // 8 jan 2018 658 | // 1 jan 18 659 | // 02 Jan 2018 23:59 660 | // 02 Jan 2018 23:59:34 661 | // 12 Feb 2006, 19:17 662 | // 12 Feb 2006, 19:17:22 663 | switch r { 664 | case ' ': 665 | p.yeari = i + 1 666 | //p.yearlen = 4 667 | p.dayi = 0 668 | p.daylen = p.part1Len 669 | p.setDay() 670 | p.stateTime = timeStart 671 | if i > p.daylen+len(" Sep") { // November etc 672 | // If len greather than space + 3 it must be full month 673 | p.stateDate = dateDigitWsMolong 674 | } else { 675 | // If len=3, the might be Feb or May? Ie ambigous abbreviated but 676 | // we can parse may with either. BUT, that means the 677 | // format may not be correct? 678 | // mo := strings.ToLower(datestr[p.daylen+1 : i]) 679 | p.moi = p.daylen + 1 680 | p.molen = i - p.moi 681 | p.set(p.moi, "Jan") 682 | p.stateDate = dateDigitWsMoYear 683 | } 684 | } 685 | 686 | case dateDigitWsMoYear: 687 | // 8 jan 2018 688 | // 02 Jan 2018 23:59 689 | // 02 Jan 2018 23:59:34 690 | // 12 Feb 2006, 19:17 691 | // 12 Feb 2006, 19:17:22 692 | switch r { 693 | case ',': 694 | p.yearlen = i - p.yeari 695 | p.setYear() 696 | i++ 697 | break iterRunes 698 | case ' ': 699 | p.yearlen = i - p.yeari 700 | p.setYear() 701 | break iterRunes 702 | } 703 | case dateDigitWsMolong: 704 | // 18 January 2018 705 | // 8 January 2018 706 | 707 | case dateDigitChineseYear: 708 | // dateDigitChineseYear 709 | // 2014年04月08日 710 | // weekday %Y年%m月%e日 %A %I:%M %p 711 | // 2013年07月18日 星期四 10:27 上午 712 | if r == ' ' { 713 | p.stateDate = dateDigitChineseYearWs 714 | break 715 | } 716 | case dateDigitDot: 717 | // This is the 2nd period 718 | // 3.31.2014 719 | // 08.21.71 720 | // 2014.05 721 | // 2018.09.30 722 | if r == '.' { 723 | if p.moi == 0 { 724 | // 3.31.2014 725 | p.daylen = i - p.dayi 726 | p.yeari = i + 1 727 | p.setDay() 728 | p.stateDate = dateDigitDotDot 729 | } else { 730 | // 2018.09.30 731 | //p.molen = 2 732 | p.molen = i - p.moi 733 | p.dayi = i + 1 734 | p.setMonth() 735 | p.stateDate = dateDigitDotDot 736 | } 737 | } 738 | case dateDigitDotDot: 739 | // iterate all the way through 740 | case dateAlpha: 741 | // dateAlphaWS 742 | // Mon Jan _2 15:04:05 2006 743 | // Mon Jan _2 15:04:05 MST 2006 744 | // Mon Jan 02 15:04:05 -0700 2006 745 | // Mon Aug 10 15:44:11 UTC+0100 2015 746 | // Fri Jul 03 2015 18:04:07 GMT+0100 (GMT Daylight Time) 747 | // dateAlphaWSDigit 748 | // May 8, 2009 5:57:51 PM 749 | // oct 1, 1970 750 | // dateAlphaWsMonth 751 | // April 8, 2009 752 | // dateAlphaWsMore 753 | // dateAlphaWsAtTime 754 | // January 02, 2006 at 3:04pm MST-07 755 | // 756 | // dateAlphaPeriodWsDigit 757 | // oct. 1, 1970 758 | // dateWeekdayComma 759 | // Monday, 02 Jan 2006 15:04:05 MST 760 | // Monday, 02-Jan-06 15:04:05 MST 761 | // Monday, 02 Jan 2006 15:04:05 -0700 762 | // Monday, 02 Jan 2006 15:04:05 +0100 763 | // dateWeekdayAbbrevComma 764 | // Mon, 02 Jan 2006 15:04:05 MST 765 | // Mon, 02 Jan 2006 15:04:05 -0700 766 | // Thu, 13 Jul 2017 08:58:40 +0100 767 | // Tue, 11 Jul 2017 16:28:13 +0200 (CEST) 768 | // Mon, 02-Jan-06 15:04:05 MST 769 | switch { 770 | case r == ' ': 771 | // X 772 | // April 8, 2009 773 | if i > 3 { 774 | // Check to see if the alpha is name of month? or Day? 775 | month := strings.ToLower(datestr[0:i]) 776 | if isMonthFull(month) { 777 | p.fullMonth = month 778 | // len(" 31, 2018") = 9 779 | if len(datestr[i:]) < 10 { 780 | // April 8, 2009 781 | p.stateDate = dateAlphaWsMonth 782 | } else { 783 | p.stateDate = dateAlphaWsMore 784 | } 785 | p.dayi = i + 1 786 | break 787 | } 788 | 789 | } else { 790 | // This is possibly ambiguous? May will parse as either though. 791 | // So, it could return in-correct format. 792 | // dateAlphaWs 793 | // May 05, 2005, 05:05:05 794 | // May 05 2005, 05:05:05 795 | // Jul 05, 2005, 05:05:05 796 | // May 8 17:57:51 2009 797 | // May 8 17:57:51 2009 798 | // skip & return to dateStart 799 | // Tue 05 May 2020, 05:05:05 800 | // Mon Jan 2 15:04:05 2006 801 | 802 | maybeDay := strings.ToLower(datestr[0:i]) 803 | if isDay(maybeDay) { 804 | // using skip throws off indices used by other code; saner to restart 805 | return parseTime(datestr[i+1:], loc) 806 | } 807 | p.stateDate = dateAlphaWs 808 | } 809 | 810 | case r == ',': 811 | // Mon, 02 Jan 2006 812 | 813 | if i == 3 { 814 | p.stateDate = dateWeekdayAbbrevComma 815 | p.set(0, "Mon") 816 | } else { 817 | p.stateDate = dateWeekdayComma 818 | p.skip = i + 2 819 | i++ 820 | // TODO: lets just make this "skip" as we don't need 821 | // the mon, monday, they are all superfelous and not needed 822 | // just lay down the skip, no need to fill and then skip 823 | } 824 | case r == '.': 825 | // sept. 28, 2017 826 | // jan. 28, 2017 827 | p.stateDate = dateAlphaPeriodWsDigit 828 | if i == 3 { 829 | p.molen = i 830 | p.set(0, "Jan") 831 | } else if i == 4 { 832 | // gross 833 | datestr = datestr[0:i-1] + datestr[i:] 834 | return parseTime(datestr, loc, opts...) 835 | } else { 836 | return nil, unknownErr(datestr) 837 | } 838 | } 839 | 840 | case dateAlphaWs: 841 | // dateAlphaWsAlpha 842 | // Mon Jan _2 15:04:05 2006 843 | // Mon Jan _2 15:04:05 MST 2006 844 | // Mon Jan 02 15:04:05 -0700 2006 845 | // Fri Jul 03 2015 18:04:07 GMT+0100 (GMT Daylight Time) 846 | // Mon Aug 10 15:44:11 UTC+0100 2015 847 | // dateAlphaWsDigit 848 | // May 8, 2009 5:57:51 PM 849 | // May 8 2009 5:57:51 PM 850 | // May 8 17:57:51 2009 851 | // May 8 17:57:51 2009 852 | // May 08 17:57:51 2009 853 | // oct 1, 1970 854 | // oct 7, '70 855 | switch { 856 | case unicode.IsLetter(r): 857 | p.set(0, "Mon") 858 | p.stateDate = dateAlphaWsAlpha 859 | p.set(i, "Jan") 860 | case unicode.IsDigit(r): 861 | p.set(0, "Jan") 862 | p.stateDate = dateAlphaWsDigit 863 | p.dayi = i 864 | } 865 | 866 | case dateAlphaWsDigit: 867 | // May 8, 2009 5:57:51 PM 868 | // May 8 2009 5:57:51 PM 869 | // oct 1, 1970 870 | // oct 7, '70 871 | // oct. 7, 1970 872 | // May 8 17:57:51 2009 873 | // May 8 17:57:51 2009 874 | // May 08 17:57:51 2009 875 | if r == ',' { 876 | p.daylen = i - p.dayi 877 | p.setDay() 878 | p.stateDate = dateAlphaWsDigitMore 879 | } else if r == ' ' { 880 | p.daylen = i - p.dayi 881 | p.setDay() 882 | p.yeari = i + 1 883 | p.stateDate = dateAlphaWsDigitYearmaybe 884 | p.stateTime = timeStart 885 | } else if unicode.IsLetter(r) { 886 | p.stateDate = dateAlphaWsMonthSuffix 887 | i-- 888 | } 889 | case dateAlphaWsDigitYearmaybe: 890 | // x 891 | // May 8 2009 5:57:51 PM 892 | // May 8 17:57:51 2009 893 | // May 8 17:57:51 2009 894 | // May 08 17:57:51 2009 895 | // Jul 03 2015 18:04:07 GMT+0100 (GMT Daylight Time) 896 | if r == ':' { 897 | // Guessed wrong; was not a year 898 | i = i - 3 899 | p.stateDate = dateAlphaWsDigit 900 | p.yeari = 0 901 | break iterRunes 902 | } else if r == ' ' { 903 | // must be year format, not 15:04 904 | p.yearlen = i - p.yeari 905 | p.setYear() 906 | break iterRunes 907 | } 908 | case dateAlphaWsDigitMore: 909 | // x 910 | // May 8, 2009 5:57:51 PM 911 | // May 05, 2005, 05:05:05 912 | // May 05 2005, 05:05:05 913 | // oct 1, 1970 914 | // oct 7, '70 915 | if r == ' ' { 916 | p.yeari = i + 1 917 | p.stateDate = dateAlphaWsDigitMoreWs 918 | } 919 | case dateAlphaWsDigitMoreWs: 920 | // x 921 | // May 8, 2009 5:57:51 PM 922 | // May 05, 2005, 05:05:05 923 | // oct 1, 1970 924 | // oct 7, '70 925 | switch r { 926 | case '\'': 927 | p.yeari = i + 1 928 | case ' ', ',': 929 | // x 930 | // May 8, 2009 5:57:51 PM 931 | // x 932 | // May 8, 2009, 5:57:51 PM 933 | p.stateDate = dateAlphaWsDigitMoreWsYear 934 | p.yearlen = i - p.yeari 935 | p.setYear() 936 | p.stateTime = timeStart 937 | break iterRunes 938 | } 939 | 940 | case dateAlphaWsMonth: 941 | // April 8, 2009 942 | // April 8 2009 943 | switch r { 944 | case ' ', ',': 945 | // x 946 | // June 8, 2009 947 | // x 948 | // June 8 2009 949 | if p.daylen == 0 { 950 | p.daylen = i - p.dayi 951 | p.setDay() 952 | } 953 | case 's', 'S', 'r', 'R', 't', 'T', 'n', 'N': 954 | // st, rd, nd, st 955 | i-- 956 | p.stateDate = dateAlphaWsMonthSuffix 957 | default: 958 | if p.daylen > 0 && p.yeari == 0 { 959 | p.yeari = i 960 | } 961 | } 962 | case dateAlphaWsMonthMore: 963 | // X 964 | // January 02, 2006, 15:04:05 965 | // January 02 2006, 15:04:05 966 | // January 02, 2006 15:04:05 967 | // January 02 2006 15:04:05 968 | switch r { 969 | case ',': 970 | p.yearlen = i - p.yeari 971 | p.setYear() 972 | p.stateTime = timeStart 973 | i++ 974 | break iterRunes 975 | case ' ': 976 | p.yearlen = i - p.yeari 977 | p.setYear() 978 | p.stateTime = timeStart 979 | break iterRunes 980 | } 981 | case dateAlphaWsMonthSuffix: 982 | // x 983 | // April 8th, 2009 984 | // April 8th 2009 985 | switch r { 986 | case 't', 'T': 987 | if p.nextIs(i, 'h') || p.nextIs(i, 'H') { 988 | if len(datestr) > i+2 { 989 | return parseTime(fmt.Sprintf("%s%s", p.datestr[0:i], p.datestr[i+2:]), loc, opts...) 990 | } 991 | } 992 | case 'n', 'N': 993 | if p.nextIs(i, 'd') || p.nextIs(i, 'D') { 994 | if len(datestr) > i+2 { 995 | return parseTime(fmt.Sprintf("%s%s", p.datestr[0:i], p.datestr[i+2:]), loc, opts...) 996 | } 997 | } 998 | case 's', 'S': 999 | if p.nextIs(i, 't') || p.nextIs(i, 'T') { 1000 | if len(datestr) > i+2 { 1001 | return parseTime(fmt.Sprintf("%s%s", p.datestr[0:i], p.datestr[i+2:]), loc, opts...) 1002 | } 1003 | } 1004 | case 'r', 'R': 1005 | if p.nextIs(i, 'd') || p.nextIs(i, 'D') { 1006 | if len(datestr) > i+2 { 1007 | return parseTime(fmt.Sprintf("%s%s", p.datestr[0:i], p.datestr[i+2:]), loc, opts...) 1008 | } 1009 | } 1010 | } 1011 | case dateAlphaWsMore: 1012 | // January 02, 2006, 15:04:05 1013 | // January 02 2006, 15:04:05 1014 | // January 2nd, 2006, 15:04:05 1015 | // January 2nd 2006, 15:04:05 1016 | // September 17, 2012 at 5:00pm UTC-05 1017 | switch { 1018 | case r == ',': 1019 | // x 1020 | // January 02, 2006, 15:04:05 1021 | if p.nextIs(i, ' ') { 1022 | p.daylen = i - p.dayi 1023 | p.setDay() 1024 | p.yeari = i + 2 1025 | p.stateDate = dateAlphaWsMonthMore 1026 | i++ 1027 | } 1028 | 1029 | case r == ' ': 1030 | // x 1031 | // January 02 2006, 15:04:05 1032 | p.daylen = i - p.dayi 1033 | p.setDay() 1034 | p.yeari = i + 1 1035 | p.stateDate = dateAlphaWsMonthMore 1036 | case unicode.IsDigit(r): 1037 | // XX 1038 | // January 02, 2006, 15:04:05 1039 | continue 1040 | case unicode.IsLetter(r): 1041 | // X 1042 | // January 2nd, 2006, 15:04:05 1043 | p.daylen = i - p.dayi 1044 | p.setDay() 1045 | p.stateDate = dateAlphaWsMonthSuffix 1046 | i-- 1047 | } 1048 | 1049 | case dateAlphaPeriodWsDigit: 1050 | // oct. 7, '70 1051 | switch { 1052 | case r == ' ': 1053 | // continue 1054 | case unicode.IsDigit(r): 1055 | p.stateDate = dateAlphaWsDigit 1056 | p.dayi = i 1057 | default: 1058 | return p, unknownErr(datestr) 1059 | } 1060 | case dateWeekdayComma: 1061 | // Monday, 02 Jan 2006 15:04:05 MST 1062 | // Monday, 02 Jan 2006 15:04:05 -0700 1063 | // Monday, 02 Jan 2006 15:04:05 +0100 1064 | // Monday, 02-Jan-06 15:04:05 MST 1065 | if p.dayi == 0 { 1066 | p.dayi = i 1067 | } 1068 | switch r { 1069 | case ' ', '-': 1070 | if p.moi == 0 { 1071 | p.moi = i + 1 1072 | p.daylen = i - p.dayi 1073 | p.setDay() 1074 | } else if p.yeari == 0 { 1075 | p.yeari = i + 1 1076 | p.molen = i - p.moi 1077 | p.set(p.moi, "Jan") 1078 | } else { 1079 | p.stateTime = timeStart 1080 | break iterRunes 1081 | } 1082 | } 1083 | case dateWeekdayAbbrevComma: 1084 | // Mon, 02 Jan 2006 15:04:05 MST 1085 | // Mon, 02 Jan 2006 15:04:05 -0700 1086 | // Thu, 13 Jul 2017 08:58:40 +0100 1087 | // Thu, 4 Jan 2018 17:53:36 +0000 1088 | // Tue, 11 Jul 2017 16:28:13 +0200 (CEST) 1089 | // Mon, 02-Jan-06 15:04:05 MST 1090 | switch r { 1091 | case ' ', '-': 1092 | if p.dayi == 0 { 1093 | p.dayi = i + 1 1094 | } else if p.moi == 0 { 1095 | p.daylen = i - p.dayi 1096 | p.setDay() 1097 | p.moi = i + 1 1098 | } else if p.yeari == 0 { 1099 | p.molen = i - p.moi 1100 | p.set(p.moi, "Jan") 1101 | p.yeari = i + 1 1102 | } else { 1103 | p.yearlen = i - p.yeari 1104 | p.setYear() 1105 | p.stateTime = timeStart 1106 | break iterRunes 1107 | } 1108 | } 1109 | 1110 | default: 1111 | break iterRunes 1112 | } 1113 | } 1114 | p.coalesceDate(i) 1115 | if p.stateTime == timeStart { 1116 | // increment first one, since the i++ occurs at end of loop 1117 | if i < len(p.datestr) { 1118 | i++ 1119 | } 1120 | // ensure we skip any whitespace prefix 1121 | for ; i < len(datestr); i++ { 1122 | r := rune(datestr[i]) 1123 | if r != ' ' { 1124 | break 1125 | } 1126 | } 1127 | 1128 | iterTimeRunes: 1129 | for ; i < len(datestr); i++ { 1130 | r := rune(datestr[i]) 1131 | 1132 | // gou.Debugf("i=%d r=%s state=%d iterTimeRunes %s %s", i, string(r), p.stateTime, p.ds(), p.ts()) 1133 | 1134 | switch p.stateTime { 1135 | case timeStart: 1136 | // 22:43:22 1137 | // 22:43 1138 | // timeComma 1139 | // 08:20:13,787 1140 | // timeWs 1141 | // 05:24:37 PM 1142 | // 06:20:00 UTC 1143 | // 06:20:00 UTC-05 1144 | // 00:12:00 +0000 UTC 1145 | // 22:18:00 +0000 UTC m=+0.000000001 1146 | // 15:04:05 -0700 1147 | // 15:04:05 -07:00 1148 | // 15:04:05 2008 1149 | // timeOffset 1150 | // 03:21:51+00:00 1151 | // 19:55:00+0100 1152 | // timePeriod 1153 | // 17:24:37.3186369 1154 | // 00:07:31.945167 1155 | // 18:31:59.257000000 1156 | // 00:00:00.000 1157 | // timePeriodOffset 1158 | // 19:55:00.799+0100 1159 | // timePeriodOffsetColon 1160 | // 15:04:05.999-07:00 1161 | // timePeriodWs 1162 | // timePeriodWsOffset 1163 | // 00:07:31.945167 +0000 1164 | // 00:00:00.000 +0000 1165 | // timePeriodWsOffsetAlpha 1166 | // 00:07:31.945167 +0000 UTC 1167 | // 22:18:00.001 +0000 UTC m=+0.000000001 1168 | // 00:00:00.000 +0000 UTC 1169 | // timePeriodWsAlpha 1170 | // 06:20:00.000 UTC 1171 | if p.houri == 0 { 1172 | p.houri = i 1173 | } 1174 | switch r { 1175 | case ',': 1176 | // hm, lets just swap out comma for period. for some reason go 1177 | // won't parse it. 1178 | // 2014-05-11 08:20:13,787 1179 | ds := []byte(p.datestr) 1180 | ds[i] = '.' 1181 | return parseTime(string(ds), loc, opts...) 1182 | case '-', '+': 1183 | // 03:21:51+00:00 1184 | p.stateTime = timeOffset 1185 | if p.seci == 0 { 1186 | // 22:18+0530 1187 | p.minlen = i - p.mini 1188 | } else { 1189 | if p.seclen == 0 { 1190 | p.seclen = i - p.seci 1191 | } 1192 | if p.msi > 0 && p.mslen == 0 { 1193 | p.mslen = i - p.msi 1194 | } 1195 | } 1196 | p.offseti = i 1197 | case '.': 1198 | p.stateTime = timePeriod 1199 | p.seclen = i - p.seci 1200 | p.msi = i + 1 1201 | case 'Z': 1202 | p.stateTime = timeZ 1203 | if p.seci == 0 { 1204 | p.minlen = i - p.mini 1205 | } else { 1206 | p.seclen = i - p.seci 1207 | } 1208 | // (Z)ulu time 1209 | p.loc = time.UTC 1210 | case 'a', 'A': 1211 | if p.nextIs(i, 't') || p.nextIs(i, 'T') { 1212 | // x 1213 | // September 17, 2012 at 5:00pm UTC-05 1214 | i++ // skip t 1215 | if p.nextIs(i, ' ') { 1216 | // x 1217 | // September 17, 2012 at 5:00pm UTC-05 1218 | i++ // skip ' 1219 | p.houri = 0 // reset hour 1220 | } 1221 | } else { 1222 | switch { 1223 | case r == 'a' && p.nextIs(i, 'm'): 1224 | p.coalesceTime(i) 1225 | p.set(i, "am") 1226 | case r == 'A' && p.nextIs(i, 'M'): 1227 | p.coalesceTime(i) 1228 | p.set(i, "PM") 1229 | } 1230 | } 1231 | 1232 | case 'p', 'P': 1233 | // Could be AM/PM 1234 | switch { 1235 | case r == 'p' && p.nextIs(i, 'm'): 1236 | p.coalesceTime(i) 1237 | p.set(i, "pm") 1238 | case r == 'P' && p.nextIs(i, 'M'): 1239 | p.coalesceTime(i) 1240 | p.set(i, "PM") 1241 | } 1242 | case ' ': 1243 | p.coalesceTime(i) 1244 | p.stateTime = timeWs 1245 | case ':': 1246 | if p.mini == 0 { 1247 | p.mini = i + 1 1248 | p.hourlen = i - p.houri 1249 | } else if p.seci == 0 { 1250 | p.seci = i + 1 1251 | p.minlen = i - p.mini 1252 | } else if p.seci > 0 { 1253 | // 18:31:59:257 ms uses colon, wtf 1254 | p.seclen = i - p.seci 1255 | p.set(p.seci, "05") 1256 | p.msi = i + 1 1257 | 1258 | // gross, gross, gross. manipulating the datestr is horrible. 1259 | // https://github.com/araddon/dateparse/issues/117 1260 | // Could not get the parsing to work using golang time.Parse() without 1261 | // replacing that colon with period. 1262 | p.set(i, ".") 1263 | datestr = datestr[0:i] + "." + datestr[i+1:] 1264 | p.datestr = datestr 1265 | } 1266 | } 1267 | case timeOffset: 1268 | // 19:55:00+0100 1269 | // timeOffsetColon 1270 | // 15:04:05+07:00 1271 | // 15:04:05-07:00 1272 | if r == ':' { 1273 | p.stateTime = timeOffsetColon 1274 | } 1275 | case timeWs: 1276 | // timeWsAlpha 1277 | // 06:20:00 UTC 1278 | // 06:20:00 UTC-05 1279 | // 15:44:11 UTC+0100 2015 1280 | // 18:04:07 GMT+0100 (GMT Daylight Time) 1281 | // 17:57:51 MST 2009 1282 | // timeWsAMPMMaybe 1283 | // 05:24:37 PM 1284 | // timeWsOffset 1285 | // 15:04:05 -0700 1286 | // 00:12:00 +0000 UTC 1287 | // timeWsOffsetColon 1288 | // 15:04:05 -07:00 1289 | // 17:57:51 -0700 2009 1290 | // timeWsOffsetColonAlpha 1291 | // 00:12:00 +00:00 UTC 1292 | // timeWsYear 1293 | // 00:12:00 2008 1294 | // timeZ 1295 | // 15:04:05.99Z 1296 | switch r { 1297 | case 'A', 'P': 1298 | // Could be AM/PM or could be PST or similar 1299 | p.tzi = i 1300 | p.stateTime = timeWsAMPMMaybe 1301 | case '+', '-': 1302 | p.offseti = i 1303 | p.stateTime = timeWsOffset 1304 | default: 1305 | if unicode.IsLetter(r) { 1306 | // 06:20:00 UTC 1307 | // 06:20:00 UTC-05 1308 | // 15:44:11 UTC+0100 2015 1309 | // 17:57:51 MST 2009 1310 | p.tzi = i 1311 | p.stateTime = timeWsAlpha 1312 | } else if unicode.IsDigit(r) { 1313 | // 00:12:00 2008 1314 | p.stateTime = timeWsYear 1315 | p.yeari = i 1316 | } 1317 | } 1318 | case timeWsAlpha: 1319 | // 06:20:00 UTC 1320 | // 06:20:00 UTC-05 1321 | // timeWsAlphaWs 1322 | // 17:57:51 MST 2009 1323 | // timeWsAlphaZoneOffset 1324 | // timeWsAlphaZoneOffsetWs 1325 | // timeWsAlphaZoneOffsetWsExtra 1326 | // 18:04:07 GMT+0100 (GMT Daylight Time) 1327 | // timeWsAlphaZoneOffsetWsYear 1328 | // 15:44:11 UTC+0100 2015 1329 | switch r { 1330 | case '+', '-': 1331 | p.tzlen = i - p.tzi 1332 | if p.tzlen == 4 { 1333 | p.set(p.tzi, " MST") 1334 | } else if p.tzlen == 3 { 1335 | p.set(p.tzi, "MST") 1336 | } 1337 | p.stateTime = timeWsAlphaZoneOffset 1338 | p.offseti = i 1339 | case ' ': 1340 | // 17:57:51 MST 2009 1341 | // 17:57:51 MST 1342 | p.tzlen = i - p.tzi 1343 | if p.tzlen == 4 { 1344 | p.set(p.tzi, " MST") 1345 | } else if p.tzlen == 3 { 1346 | p.set(p.tzi, "MST") 1347 | } 1348 | p.stateTime = timeWsAlphaWs 1349 | p.yeari = i + 1 1350 | } 1351 | case timeWsAlphaWs: 1352 | // 17:57:51 MST 2009 1353 | 1354 | case timeWsAlphaZoneOffset: 1355 | // 06:20:00 UTC-05 1356 | // timeWsAlphaZoneOffset 1357 | // timeWsAlphaZoneOffsetWs 1358 | // timeWsAlphaZoneOffsetWsExtra 1359 | // 18:04:07 GMT+0100 (GMT Daylight Time) 1360 | // timeWsAlphaZoneOffsetWsYear 1361 | // 15:44:11 UTC+0100 2015 1362 | switch r { 1363 | case ' ': 1364 | p.set(p.offseti, "-0700") 1365 | if p.yeari == 0 { 1366 | p.yeari = i + 1 1367 | } 1368 | p.stateTime = timeWsAlphaZoneOffsetWs 1369 | } 1370 | case timeWsAlphaZoneOffsetWs: 1371 | // timeWsAlphaZoneOffsetWs 1372 | // timeWsAlphaZoneOffsetWsExtra 1373 | // 18:04:07 GMT+0100 (GMT Daylight Time) 1374 | // timeWsAlphaZoneOffsetWsYear 1375 | // 15:44:11 UTC+0100 2015 1376 | if unicode.IsDigit(r) { 1377 | p.stateTime = timeWsAlphaZoneOffsetWsYear 1378 | } else { 1379 | p.extra = i - 1 1380 | p.stateTime = timeWsAlphaZoneOffsetWsExtra 1381 | } 1382 | case timeWsAlphaZoneOffsetWsYear: 1383 | // 15:44:11 UTC+0100 2015 1384 | if unicode.IsDigit(r) { 1385 | p.yearlen = i - p.yeari + 1 1386 | if p.yearlen == 4 { 1387 | p.setYear() 1388 | } 1389 | } 1390 | case timeWsAMPMMaybe: 1391 | // timeWsAMPMMaybe 1392 | // timeWsAMPM 1393 | // 05:24:37 PM 1394 | // timeWsAlpha 1395 | // 00:12:00 PST 1396 | // 15:44:11 UTC+0100 2015 1397 | if r == 'M' { 1398 | //return parse("2006-01-02 03:04:05 PM", datestr, loc) 1399 | p.stateTime = timeWsAMPM 1400 | p.set(i-1, "PM") 1401 | if p.hourlen == 2 { 1402 | p.set(p.houri, "03") 1403 | } else if p.hourlen == 1 { 1404 | p.set(p.houri, "3") 1405 | } 1406 | } else { 1407 | p.stateTime = timeWsAlpha 1408 | } 1409 | 1410 | case timeWsOffset: 1411 | // timeWsOffset 1412 | // 15:04:05 -0700 1413 | // timeWsOffsetWsOffset 1414 | // 17:57:51 -0700 -07 1415 | // timeWsOffsetWs 1416 | // 17:57:51 -0700 2009 1417 | // 00:12:00 +0000 UTC 1418 | // timeWsOffsetColon 1419 | // 15:04:05 -07:00 1420 | // timeWsOffsetColonAlpha 1421 | // 00:12:00 +00:00 UTC 1422 | switch r { 1423 | case ':': 1424 | p.stateTime = timeWsOffsetColon 1425 | case ' ': 1426 | p.set(p.offseti, "-0700") 1427 | p.yeari = i + 1 1428 | p.stateTime = timeWsOffsetWs 1429 | } 1430 | case timeWsOffsetWs: 1431 | // 17:57:51 -0700 2009 1432 | // 00:12:00 +0000 UTC 1433 | // 22:18:00.001 +0000 UTC m=+0.000000001 1434 | // w Extra 1435 | // 17:57:51 -0700 -07 1436 | switch r { 1437 | case '=': 1438 | // eff you golang 1439 | if datestr[i-1] == 'm' { 1440 | p.extra = i - 2 1441 | p.trimExtra() 1442 | break 1443 | } 1444 | case '+', '-', '(': 1445 | // This really doesn't seem valid, but for some reason when round-tripping a go date 1446 | // their is an extra +03 printed out. seems like go bug to me, but, parsing anyway. 1447 | // 00:00:00 +0300 +03 1448 | // 00:00:00 +0300 +0300 1449 | p.extra = i - 1 1450 | p.stateTime = timeWsOffset 1451 | p.trimExtra() 1452 | break 1453 | default: 1454 | switch { 1455 | case unicode.IsDigit(r): 1456 | p.yearlen = i - p.yeari + 1 1457 | if p.yearlen == 4 { 1458 | p.setYear() 1459 | } 1460 | case unicode.IsLetter(r): 1461 | // 15:04:05 -0700 MST 1462 | if p.tzi == 0 { 1463 | p.tzi = i 1464 | } 1465 | } 1466 | } 1467 | 1468 | case timeWsOffsetColon: 1469 | // timeWsOffsetColon 1470 | // 15:04:05 -07:00 1471 | // timeWsOffsetColonAlpha 1472 | // 2015-02-18 00:12:00 +00:00 UTC 1473 | if unicode.IsLetter(r) { 1474 | // 2015-02-18 00:12:00 +00:00 UTC 1475 | p.stateTime = timeWsOffsetColonAlpha 1476 | break iterTimeRunes 1477 | } 1478 | case timePeriod: 1479 | // 15:04:05.999999999+07:00 1480 | // 15:04:05.999999999-07:00 1481 | // 15:04:05.999999+07:00 1482 | // 15:04:05.999999-07:00 1483 | // 15:04:05.999+07:00 1484 | // 15:04:05.999-07:00 1485 | // timePeriod 1486 | // 17:24:37.3186369 1487 | // 00:07:31.945167 1488 | // 18:31:59.257000000 1489 | // 00:00:00.000 1490 | // timePeriodOffset 1491 | // 19:55:00.799+0100 1492 | // timePeriodOffsetColon 1493 | // 15:04:05.999-07:00 1494 | // timePeriodWs 1495 | // timePeriodWsOffset 1496 | // 00:07:31.945167 +0000 1497 | // 00:00:00.000 +0000 1498 | // With Extra 1499 | // 00:00:00.000 +0300 +03 1500 | // timePeriodWsOffsetAlpha 1501 | // 00:07:31.945167 +0000 UTC 1502 | // 00:00:00.000 +0000 UTC 1503 | // 22:18:00.001 +0000 UTC m=+0.000000001 1504 | // timePeriodWsAlpha 1505 | // 06:20:00.000 UTC 1506 | switch r { 1507 | case ' ': 1508 | p.mslen = i - p.msi 1509 | p.stateTime = timePeriodWs 1510 | case '+', '-': 1511 | // This really shouldn't happen 1512 | p.mslen = i - p.msi 1513 | p.offseti = i 1514 | p.stateTime = timePeriodOffset 1515 | default: 1516 | if unicode.IsLetter(r) { 1517 | // 06:20:00.000 UTC 1518 | p.mslen = i - p.msi 1519 | p.stateTime = timePeriodWsAlpha 1520 | } 1521 | } 1522 | case timePeriodOffset: 1523 | // timePeriodOffset 1524 | // 19:55:00.799+0100 1525 | // timePeriodOffsetColon 1526 | // 15:04:05.999-07:00 1527 | // 13:31:51.999-07:00 MST 1528 | if r == ':' { 1529 | p.stateTime = timePeriodOffsetColon 1530 | } 1531 | case timePeriodOffsetColon: 1532 | // timePeriodOffset 1533 | // timePeriodOffsetColon 1534 | // 15:04:05.999-07:00 1535 | // 13:31:51.999 -07:00 MST 1536 | switch r { 1537 | case ' ': 1538 | p.set(p.offseti, "-07:00") 1539 | p.stateTime = timePeriodOffsetColonWs 1540 | p.tzi = i + 1 1541 | } 1542 | case timePeriodOffsetColonWs: 1543 | // continue 1544 | case timePeriodWs: 1545 | // timePeriodWs 1546 | // timePeriodWsOffset 1547 | // 00:07:31.945167 +0000 1548 | // 00:00:00.000 +0000 1549 | // timePeriodWsOffsetAlpha 1550 | // 00:07:31.945167 +0000 UTC 1551 | // 00:00:00.000 +0000 UTC 1552 | // timePeriodWsOffsetColon 1553 | // 13:31:51.999 -07:00 MST 1554 | // timePeriodWsAlpha 1555 | // 06:20:00.000 UTC 1556 | if p.offseti == 0 { 1557 | p.offseti = i 1558 | } 1559 | switch r { 1560 | case '+', '-': 1561 | p.mslen = i - p.msi - 1 1562 | p.stateTime = timePeriodWsOffset 1563 | default: 1564 | if unicode.IsLetter(r) { 1565 | // 00:07:31.945167 +0000 UTC 1566 | // 00:00:00.000 +0000 UTC 1567 | p.stateTime = timePeriodWsOffsetWsAlpha 1568 | break iterTimeRunes 1569 | } 1570 | } 1571 | 1572 | case timePeriodWsOffset: 1573 | // timePeriodWs 1574 | // timePeriodWsOffset 1575 | // 00:07:31.945167 +0000 1576 | // 00:00:00.000 +0000 1577 | // With Extra 1578 | // 00:00:00.000 +0300 +03 1579 | // timePeriodWsOffsetAlpha 1580 | // 00:07:31.945167 +0000 UTC 1581 | // 00:00:00.000 +0000 UTC 1582 | // 03:02:00.001 +0300 MSK m=+0.000000001 1583 | // timePeriodWsOffsetColon 1584 | // 13:31:51.999 -07:00 MST 1585 | // timePeriodWsAlpha 1586 | // 06:20:00.000 UTC 1587 | switch r { 1588 | case ':': 1589 | p.stateTime = timePeriodWsOffsetColon 1590 | case ' ': 1591 | p.set(p.offseti, "-0700") 1592 | case '+', '-': 1593 | // This really doesn't seem valid, but for some reason when round-tripping a go date 1594 | // their is an extra +03 printed out. seems like go bug to me, but, parsing anyway. 1595 | // 00:00:00.000 +0300 +03 1596 | // 00:00:00.000 +0300 +0300 1597 | p.extra = i - 1 1598 | p.trimExtra() 1599 | break 1600 | default: 1601 | if unicode.IsLetter(r) { 1602 | // 00:07:31.945167 +0000 UTC 1603 | // 00:00:00.000 +0000 UTC 1604 | // 03:02:00.001 +0300 MSK m=+0.000000001 1605 | p.stateTime = timePeriodWsOffsetWsAlpha 1606 | } 1607 | } 1608 | case timePeriodWsOffsetWsAlpha: 1609 | // 03:02:00.001 +0300 MSK m=+0.000000001 1610 | // eff you golang 1611 | if r == '=' && datestr[i-1] == 'm' { 1612 | p.extra = i - 2 1613 | p.trimExtra() 1614 | break 1615 | } 1616 | 1617 | case timePeriodWsOffsetColon: 1618 | // 13:31:51.999 -07:00 MST 1619 | switch r { 1620 | case ' ': 1621 | p.set(p.offseti, "-07:00") 1622 | default: 1623 | if unicode.IsLetter(r) { 1624 | // 13:31:51.999 -07:00 MST 1625 | p.tzi = i 1626 | p.stateTime = timePeriodWsOffsetColonAlpha 1627 | } 1628 | } 1629 | case timePeriodWsOffsetColonAlpha: 1630 | // continue 1631 | case timeZ: 1632 | // timeZ 1633 | // 15:04:05.99Z 1634 | // With a time-zone at end after Z 1635 | // 2006-01-02T15:04:05.999999999Z07:00 1636 | // 2006-01-02T15:04:05Z07:00 1637 | // RFC3339 = "2006-01-02T15:04:05Z07:00" 1638 | // RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00" 1639 | if unicode.IsDigit(r) { 1640 | p.stateTime = timeZDigit 1641 | } 1642 | 1643 | } 1644 | } 1645 | 1646 | switch p.stateTime { 1647 | case timeWsAlpha: 1648 | switch len(p.datestr) - p.tzi { 1649 | case 3: 1650 | // 13:31:51.999 +01:00 CET 1651 | p.set(p.tzi, "MST") 1652 | case 4: 1653 | p.set(p.tzi, "MST") 1654 | p.extra = len(p.datestr) - 1 1655 | p.trimExtra() 1656 | } 1657 | 1658 | case timeWsAlphaWs: 1659 | p.yearlen = i - p.yeari 1660 | p.setYear() 1661 | case timeWsYear: 1662 | p.yearlen = i - p.yeari 1663 | p.setYear() 1664 | case timeWsAlphaZoneOffsetWsExtra: 1665 | p.trimExtra() 1666 | case timeWsAlphaZoneOffset: 1667 | // 06:20:00 UTC-05 1668 | if i-p.offseti < 4 { 1669 | p.set(p.offseti, "-07") 1670 | } else { 1671 | p.set(p.offseti, "-0700") 1672 | } 1673 | 1674 | case timePeriod: 1675 | p.mslen = i - p.msi 1676 | case timeOffset: 1677 | 1678 | switch len(p.datestr) - p.offseti { 1679 | case 0, 1, 2, 4: 1680 | return p, fmt.Errorf("TZ offset not recognized %q near %q (must be 2 or 4 digits optional colon)", datestr, string(datestr[p.offseti:])) 1681 | case 3: 1682 | // 19:55:00+01 1683 | p.set(p.offseti, "-07") 1684 | case 5: 1685 | // 19:55:00+0100 1686 | p.set(p.offseti, "-0700") 1687 | } 1688 | 1689 | case timeWsOffset: 1690 | p.set(p.offseti, "-0700") 1691 | case timeWsOffsetWs: 1692 | // 17:57:51 -0700 2009 1693 | // 00:12:00 +0000 UTC 1694 | if p.tzi > 0 { 1695 | switch len(p.datestr) - p.tzi { 1696 | case 3: 1697 | // 13:31:51.999 +01:00 CET 1698 | p.set(p.tzi, "MST") 1699 | case 4: 1700 | // 13:31:51.999 +01:00 CEST 1701 | p.set(p.tzi, "MST ") 1702 | } 1703 | 1704 | } 1705 | case timeWsOffsetColon: 1706 | // 17:57:51 -07:00 1707 | p.set(p.offseti, "-07:00") 1708 | case timeOffsetColon: 1709 | // 15:04:05+07:00 1710 | p.set(p.offseti, "-07:00") 1711 | case timePeriodOffset: 1712 | // 19:55:00.799+0100 1713 | p.set(p.offseti, "-0700") 1714 | case timePeriodOffsetColon: 1715 | p.set(p.offseti, "-07:00") 1716 | case timePeriodWsOffsetColonAlpha: 1717 | p.tzlen = i - p.tzi 1718 | switch p.tzlen { 1719 | case 3: 1720 | p.set(p.tzi, "MST") 1721 | case 4: 1722 | p.set(p.tzi, "MST ") 1723 | } 1724 | case timePeriodWsOffset: 1725 | p.set(p.offseti, "-0700") 1726 | } 1727 | p.coalesceTime(i) 1728 | } 1729 | 1730 | switch p.stateDate { 1731 | case dateDigit: 1732 | // unixy timestamps ish 1733 | // example ct type 1734 | // 1499979655583057426 19 nanoseconds 1735 | // 1499979795437000 16 micro-seconds 1736 | // 20180722105203 14 yyyyMMddhhmmss 1737 | // 1499979795437 13 milliseconds 1738 | // 1332151919 10 seconds 1739 | // 20140601 8 yyyymmdd 1740 | // 2014 4 yyyy 1741 | t := time.Time{} 1742 | if len(datestr) == len("1499979655583057426") { // 19 1743 | // nano-seconds 1744 | if nanoSecs, err := strconv.ParseInt(datestr, 10, 64); err == nil { 1745 | t = time.Unix(0, nanoSecs) 1746 | } 1747 | } else if len(datestr) == len("1499979795437000") { // 16 1748 | // micro-seconds 1749 | if microSecs, err := strconv.ParseInt(datestr, 10, 64); err == nil { 1750 | t = time.Unix(0, microSecs*1000) 1751 | } 1752 | } else if len(datestr) == len("yyyyMMddhhmmss") { // 14 1753 | // yyyyMMddhhmmss 1754 | p.format = []byte("20060102150405") 1755 | return p, nil 1756 | } else if len(datestr) == len("1332151919000") { // 13 1757 | if miliSecs, err := strconv.ParseInt(datestr, 10, 64); err == nil { 1758 | t = time.Unix(0, miliSecs*1000*1000) 1759 | } 1760 | } else if len(datestr) == len("1332151919") { //10 1761 | if secs, err := strconv.ParseInt(datestr, 10, 64); err == nil { 1762 | t = time.Unix(secs, 0) 1763 | } 1764 | } else if len(datestr) == len("20140601") { 1765 | p.format = []byte("20060102") 1766 | return p, nil 1767 | } else if len(datestr) == len("2014") { 1768 | p.format = []byte("2006") 1769 | return p, nil 1770 | } else if len(datestr) < 4 { 1771 | return nil, fmt.Errorf("unrecognized format, too short %v", datestr) 1772 | } 1773 | if !t.IsZero() { 1774 | if loc == nil { 1775 | p.t = &t 1776 | return p, nil 1777 | } 1778 | t = t.In(loc) 1779 | p.t = &t 1780 | return p, nil 1781 | } 1782 | case dateDigitSt: 1783 | // 171113 14:14:20 1784 | return p, nil 1785 | 1786 | case dateYearDash: 1787 | // 2006-01 1788 | return p, nil 1789 | 1790 | case dateYearDashDash: 1791 | // 2006-01-02 1792 | // 2006-1-02 1793 | // 2006-1-2 1794 | // 2006-01-2 1795 | return p, nil 1796 | 1797 | case dateYearDashDashOffset: 1798 | /// 2020-07-20+00:00 1799 | switch len(p.datestr) - p.offseti { 1800 | case 5: 1801 | p.set(p.offseti, "-0700") 1802 | case 6: 1803 | p.set(p.offseti, "-07:00") 1804 | } 1805 | return p, nil 1806 | 1807 | case dateYearDashAlphaDash: 1808 | // 2013-Feb-03 1809 | // 2013-Feb-3 1810 | p.daylen = i - p.dayi 1811 | p.setDay() 1812 | return p, nil 1813 | 1814 | case dateYearDashDashWs: 1815 | // 2013-04-01 1816 | return p, nil 1817 | 1818 | case dateYearDashDashT: 1819 | return p, nil 1820 | 1821 | case dateDigitDashAlphaDash: 1822 | // 13-Feb-03 ambiguous 1823 | // 28-Feb-03 ambiguous 1824 | // 29-Jun-2016 1825 | length := len(datestr) - (p.moi + p.molen + 1) 1826 | if length == 4 { 1827 | p.yearlen = 4 1828 | p.set(p.yeari, "2006") 1829 | // We now also know that part1 was the day 1830 | p.dayi = 0 1831 | p.daylen = p.part1Len 1832 | p.setDay() 1833 | } else if length == 2 { 1834 | // We have no idea if this is 1835 | // yy-mon-dd OR dd-mon-yy 1836 | // 1837 | // We are going to ASSUME (bad, bad) that it is dd-mon-yy which is a horible assumption 1838 | p.ambiguousMD = true 1839 | p.yearlen = 2 1840 | p.set(p.yeari, "06") 1841 | // We now also know that part1 was the day 1842 | p.dayi = 0 1843 | p.daylen = p.part1Len 1844 | p.setDay() 1845 | } 1846 | 1847 | return p, nil 1848 | 1849 | case dateDigitDot: 1850 | // 2014.05 1851 | p.molen = i - p.moi 1852 | p.setMonth() 1853 | return p, nil 1854 | 1855 | case dateDigitDotDot: 1856 | // 03.31.1981 1857 | // 3.31.2014 1858 | // 3.2.1981 1859 | // 3.2.81 1860 | // 08.21.71 1861 | // 2018.09.30 1862 | return p, nil 1863 | 1864 | case dateDigitWsMoYear: 1865 | // 2 Jan 2018 1866 | // 2 Jan 18 1867 | // 2 Jan 2018 23:59 1868 | // 02 Jan 2018 23:59 1869 | // 12 Feb 2006, 19:17 1870 | return p, nil 1871 | 1872 | case dateDigitWsMolong: 1873 | // 18 January 2018 1874 | // 8 January 2018 1875 | if p.daylen == 2 { 1876 | p.format = []byte("02 January 2006") 1877 | return p, nil 1878 | } 1879 | p.format = []byte("2 January 2006") 1880 | return p, nil // parse("2 January 2006", datestr, loc) 1881 | 1882 | case dateAlphaWsMonth: 1883 | p.yearlen = i - p.yeari 1884 | p.setYear() 1885 | return p, nil 1886 | 1887 | case dateAlphaWsMonthMore: 1888 | return p, nil 1889 | 1890 | case dateAlphaWsDigitMoreWs: 1891 | // oct 1, 1970 1892 | p.yearlen = i - p.yeari 1893 | p.setYear() 1894 | return p, nil 1895 | 1896 | case dateAlphaWsDigitMoreWsYear: 1897 | // May 8, 2009 5:57:51 PM 1898 | // Jun 7, 2005, 05:57:51 1899 | return p, nil 1900 | 1901 | case dateAlphaWsAlpha: 1902 | return p, nil 1903 | 1904 | case dateAlphaWsDigit: 1905 | return p, nil 1906 | 1907 | case dateAlphaWsDigitYearmaybe: 1908 | return p, nil 1909 | 1910 | case dateDigitSlash: 1911 | // 3/1/2014 1912 | // 10/13/2014 1913 | // 01/02/2006 1914 | return p, nil 1915 | 1916 | case dateDigitSlashAlpha: 1917 | // 03/Jun/2014 1918 | return p, nil 1919 | 1920 | case dateDigitYearSlash: 1921 | // 2014/10/13 1922 | return p, nil 1923 | 1924 | case dateDigitColon: 1925 | // 3:1:2014 1926 | // 10:13:2014 1927 | // 01:02:2006 1928 | // 2014:10:13 1929 | return p, nil 1930 | 1931 | case dateDigitChineseYear: 1932 | // dateDigitChineseYear 1933 | // 2014年04月08日 1934 | p.format = []byte("2006年01月02日") 1935 | return p, nil 1936 | 1937 | case dateDigitChineseYearWs: 1938 | p.format = []byte("2006年01月02日 15:04:05") 1939 | return p, nil 1940 | 1941 | case dateWeekdayComma: 1942 | // Monday, 02 Jan 2006 15:04:05 -0700 1943 | // Monday, 02 Jan 2006 15:04:05 +0100 1944 | // Monday, 02-Jan-06 15:04:05 MST 1945 | return p, nil 1946 | 1947 | case dateWeekdayAbbrevComma: 1948 | // Mon, 02-Jan-06 15:04:05 MST 1949 | // Mon, 02 Jan 2006 15:04:05 MST 1950 | return p, nil 1951 | 1952 | } 1953 | 1954 | return nil, unknownErr(datestr) 1955 | } 1956 | 1957 | type parser struct { 1958 | loc *time.Location 1959 | preferMonthFirst bool 1960 | retryAmbiguousDateWithSwap bool 1961 | ambiguousMD bool 1962 | stateDate dateState 1963 | stateTime timeState 1964 | format []byte 1965 | datestr string 1966 | fullMonth string 1967 | skip int 1968 | extra int 1969 | part1Len int 1970 | yeari int 1971 | yearlen int 1972 | moi int 1973 | molen int 1974 | dayi int 1975 | daylen int 1976 | houri int 1977 | hourlen int 1978 | mini int 1979 | minlen int 1980 | seci int 1981 | seclen int 1982 | msi int 1983 | mslen int 1984 | offseti int 1985 | offsetlen int 1986 | tzi int 1987 | tzlen int 1988 | t *time.Time 1989 | } 1990 | 1991 | // ParserOption defines a function signature implemented by options 1992 | // Options defined like this accept the parser and operate on the data within 1993 | type ParserOption func(*parser) error 1994 | 1995 | // PreferMonthFirst is an option that allows preferMonthFirst to be changed from its default 1996 | func PreferMonthFirst(preferMonthFirst bool) ParserOption { 1997 | return func(p *parser) error { 1998 | p.preferMonthFirst = preferMonthFirst 1999 | return nil 2000 | } 2001 | } 2002 | 2003 | // RetryAmbiguousDateWithSwap is an option that allows retryAmbiguousDateWithSwap to be changed from its default 2004 | func RetryAmbiguousDateWithSwap(retryAmbiguousDateWithSwap bool) ParserOption { 2005 | return func(p *parser) error { 2006 | p.retryAmbiguousDateWithSwap = retryAmbiguousDateWithSwap 2007 | return nil 2008 | } 2009 | } 2010 | 2011 | func newParser(dateStr string, loc *time.Location, opts ...ParserOption) *parser { 2012 | p := &parser{ 2013 | stateDate: dateStart, 2014 | stateTime: timeIgnore, 2015 | datestr: dateStr, 2016 | loc: loc, 2017 | preferMonthFirst: true, 2018 | retryAmbiguousDateWithSwap: false, 2019 | } 2020 | p.format = []byte(dateStr) 2021 | 2022 | // allow the options to mutate the parser fields from their defaults 2023 | for _, option := range opts { 2024 | option(p) 2025 | } 2026 | return p 2027 | } 2028 | 2029 | func (p *parser) nextIs(i int, b byte) bool { 2030 | if len(p.datestr) > i+1 && p.datestr[i+1] == b { 2031 | return true 2032 | } 2033 | return false 2034 | } 2035 | 2036 | func (p *parser) set(start int, val string) { 2037 | if start < 0 { 2038 | return 2039 | } 2040 | if len(p.format) < start+len(val) { 2041 | return 2042 | } 2043 | for i, r := range val { 2044 | p.format[start+i] = byte(r) 2045 | } 2046 | } 2047 | func (p *parser) setMonth() { 2048 | if p.molen == 2 { 2049 | p.set(p.moi, "01") 2050 | } else if p.molen == 1 { 2051 | p.set(p.moi, "1") 2052 | } 2053 | } 2054 | 2055 | func (p *parser) setDay() { 2056 | if p.daylen == 2 { 2057 | p.set(p.dayi, "02") 2058 | } else if p.daylen == 1 { 2059 | p.set(p.dayi, "2") 2060 | } 2061 | } 2062 | func (p *parser) setYear() { 2063 | if p.yearlen == 2 { 2064 | p.set(p.yeari, "06") 2065 | } else if p.yearlen == 4 { 2066 | p.set(p.yeari, "2006") 2067 | } 2068 | } 2069 | func (p *parser) coalesceDate(end int) { 2070 | if p.yeari > 0 { 2071 | if p.yearlen == 0 { 2072 | p.yearlen = end - p.yeari 2073 | } 2074 | p.setYear() 2075 | } 2076 | if p.moi > 0 && p.molen == 0 { 2077 | p.molen = end - p.moi 2078 | p.setMonth() 2079 | } 2080 | if p.dayi > 0 && p.daylen == 0 { 2081 | p.daylen = end - p.dayi 2082 | p.setDay() 2083 | } 2084 | } 2085 | func (p *parser) ts() string { 2086 | return fmt.Sprintf("h:(%d:%d) m:(%d:%d) s:(%d:%d)", p.houri, p.hourlen, p.mini, p.minlen, p.seci, p.seclen) 2087 | } 2088 | func (p *parser) ds() string { 2089 | 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) 2090 | } 2091 | func (p *parser) coalesceTime(end int) { 2092 | // 03:04:05 2093 | // 15:04:05 2094 | // 3:04:05 2095 | // 3:4:5 2096 | // 15:04:05.00 2097 | if p.houri > 0 { 2098 | if p.hourlen == 2 { 2099 | p.set(p.houri, "15") 2100 | } else if p.hourlen == 1 { 2101 | p.set(p.houri, "3") 2102 | } 2103 | } 2104 | if p.mini > 0 { 2105 | if p.minlen == 0 { 2106 | p.minlen = end - p.mini 2107 | } 2108 | if p.minlen == 2 { 2109 | p.set(p.mini, "04") 2110 | } else { 2111 | p.set(p.mini, "4") 2112 | } 2113 | } 2114 | if p.seci > 0 { 2115 | if p.seclen == 0 { 2116 | p.seclen = end - p.seci 2117 | } 2118 | if p.seclen == 2 { 2119 | p.set(p.seci, "05") 2120 | } else { 2121 | p.set(p.seci, "5") 2122 | } 2123 | } 2124 | 2125 | if p.msi > 0 { 2126 | for i := 0; i < p.mslen; i++ { 2127 | p.format[p.msi+i] = '0' 2128 | } 2129 | } 2130 | } 2131 | func (p *parser) setFullMonth(month string) { 2132 | if p.moi == 0 { 2133 | p.format = []byte(fmt.Sprintf("%s%s", "January", p.format[len(month):])) 2134 | } 2135 | } 2136 | 2137 | func (p *parser) trimExtra() { 2138 | if p.extra > 0 && len(p.format) > p.extra { 2139 | p.format = p.format[0:p.extra] 2140 | p.datestr = p.datestr[0:p.extra] 2141 | } 2142 | } 2143 | 2144 | // func (p *parser) remove(i, length int) { 2145 | // if len(p.format) > i+length { 2146 | // //append(a[:i], a[j:]...) 2147 | // p.format = append(p.format[0:i], p.format[i+length:]...) 2148 | // } 2149 | // if len(p.datestr) > i+length { 2150 | // //append(a[:i], a[j:]...) 2151 | // p.datestr = fmt.Sprintf("%s%s", p.datestr[0:i], p.datestr[i+length:]) 2152 | // } 2153 | // } 2154 | 2155 | func (p *parser) parse() (time.Time, error) { 2156 | if p.t != nil { 2157 | return *p.t, nil 2158 | } 2159 | if len(p.fullMonth) > 0 { 2160 | p.setFullMonth(p.fullMonth) 2161 | } 2162 | if p.skip > 0 && len(p.format) > p.skip { 2163 | p.format = p.format[p.skip:] 2164 | p.datestr = p.datestr[p.skip:] 2165 | } 2166 | 2167 | if p.loc == nil { 2168 | // gou.Debugf("parse layout=%q input=%q \ntx, err := time.Parse(%q, %q)", string(p.format), p.datestr, string(p.format), p.datestr) 2169 | return time.Parse(string(p.format), p.datestr) 2170 | } 2171 | //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) 2172 | return time.ParseInLocation(string(p.format), p.datestr, p.loc) 2173 | } 2174 | func isDay(alpha string) bool { 2175 | for _, day := range days { 2176 | if alpha == day { 2177 | return true 2178 | } 2179 | } 2180 | return false 2181 | } 2182 | func isMonthFull(alpha string) bool { 2183 | for _, month := range months { 2184 | if alpha == month { 2185 | return true 2186 | } 2187 | } 2188 | return false 2189 | } 2190 | --------------------------------------------------------------------------------