@@ -144,8 +144,8 @@ func unknownErr(datestr string) error {
144144// ParseAny parse an unknown date format, detect the layout.
145145// Normal parse. Equivalent Timezone rules as time.Parse().
146146// NOTE: please see readme on mmdd vs ddmm ambiguous dates.
147- func ParseAny (datestr string ) (time.Time , error ) {
148- p , err := parseTime (datestr , nil )
147+ func ParseAny (datestr string , opts ... ParserOption ) (time.Time , error ) {
148+ p , err := parseTime (datestr , nil , opts ... )
149149 if err != nil {
150150 return time.Time {}, err
151151 }
@@ -157,8 +157,8 @@ func ParseAny(datestr string) (time.Time, error) {
157157// datestring, it uses the given location rules for any zone interpretation.
158158// That is, MST means one thing when using America/Denver and something else
159159// in other locations.
160- func ParseIn (datestr string , loc * time.Location ) (time.Time , error ) {
161- p , err := parseTime (datestr , loc )
160+ func ParseIn (datestr string , loc * time.Location , opts ... ParserOption ) (time.Time , error ) {
161+ p , err := parseTime (datestr , loc , opts ... )
162162 if err != nil {
163163 return time.Time {}, err
164164 }
@@ -180,8 +180,8 @@ func ParseIn(datestr string, loc *time.Location) (time.Time, error) {
180180//
181181// t, err := dateparse.ParseIn("3/1/2014", denverLoc)
182182//
183- func ParseLocal (datestr string ) (time.Time , error ) {
184- p , err := parseTime (datestr , time .Local )
183+ func ParseLocal (datestr string , opts ... ParserOption ) (time.Time , error ) {
184+ p , err := parseTime (datestr , time .Local , opts ... )
185185 if err != nil {
186186 return time.Time {}, err
187187 }
@@ -190,8 +190,8 @@ func ParseLocal(datestr string) (time.Time, error) {
190190
191191// MustParse parse a date, and panic if it can't be parsed. Used for testing.
192192// Not recommended for most use-cases.
193- func MustParse (datestr string ) time.Time {
194- p , err := parseTime (datestr , nil )
193+ func MustParse (datestr string , opts ... ParserOption ) time.Time {
194+ p , err := parseTime (datestr , nil , opts ... )
195195 if err != nil {
196196 panic (err .Error ())
197197 }
@@ -208,8 +208,8 @@ func MustParse(datestr string) time.Time {
208208// layout, err := dateparse.ParseFormat("2013-02-01 00:00:00")
209209// // layout = "2006-01-02 15:04:05"
210210//
211- func ParseFormat (datestr string ) (string , error ) {
212- p , err := parseTime (datestr , nil )
211+ func ParseFormat (datestr string , opts ... ParserOption ) (string , error ) {
212+ p , err := parseTime (datestr , nil , opts ... )
213213 if err != nil {
214214 return "" , err
215215 }
@@ -222,8 +222,8 @@ func ParseFormat(datestr string) (string, error) {
222222
223223// ParseStrict parse an unknown date format. IF the date is ambigous
224224// mm/dd vs dd/mm then return an error. These return errors: 3.3.2014 , 8/8/71 etc
225- func ParseStrict (datestr string ) (time.Time , error ) {
226- p , err := parseTime (datestr , nil )
225+ func ParseStrict (datestr string , opts ... ParserOption ) (time.Time , error ) {
226+ p , err := parseTime (datestr , nil , opts ... )
227227 if err != nil {
228228 return time.Time {}, err
229229 }
@@ -233,9 +233,31 @@ func ParseStrict(datestr string) (time.Time, error) {
233233 return p .parse ()
234234}
235235
236- func parseTime (datestr string , loc * time.Location ) (* parser , error ) {
236+ func parseTime (datestr string , loc * time.Location , opts ... ParserOption ) (p * parser , err error ) {
237+
238+ p = newParser (datestr , loc , opts ... )
239+ if p .retryAmbiguousDateWithSwap {
240+ // month out of range signifies that a day/month swap is the correct solution to an ambiguous date
241+ // this is because it means that a day is being interpreted as a month and overflowing the valid value for that
242+ // by retrying in this case, we can fix a common situation with no assumptions
243+ defer func () {
244+ if p .ambiguousMD {
245+ // if it errors out with the following error, swap before we
246+ // get out of this function to reduce scope it needs to be applied on
247+ _ , err := p .parse ()
248+ if err != nil && strings .Contains (err .Error (), "month out of range" ) {
249+ // create the option to reverse the preference
250+ preferMonthFirst := PreferMonthFirst (! p .preferMonthFirst )
251+ // turn off the retry to avoid endless recursion
252+ retryAmbiguousDateWithSwap := RetryAmbiguousDateWithSwap (false )
253+ modifiedOpts := append (opts , preferMonthFirst , retryAmbiguousDateWithSwap )
254+ p , err = parseTime (datestr , time .Local , modifiedOpts ... )
255+ }
256+ }
257+
258+ }()
259+ }
237260
238- p := newParser (datestr , loc )
239261 i := 0
240262
241263 // General strategy is to read rune by rune through the date looking for
@@ -293,6 +315,12 @@ iterRunes:
293315 p .setMonth ()
294316 p .dayi = i + 1
295317 }
318+ } else {
319+ if p .daylen == 0 {
320+ p .daylen = i
321+ p .setDay ()
322+ p .moi = i + 1
323+ }
296324 }
297325 }
298326
@@ -489,6 +517,12 @@ iterRunes:
489517 p .setDay ()
490518 p .yeari = i + 1
491519 }
520+ } else {
521+ if p .molen == 0 {
522+ p .molen = i - p .moi
523+ p .setMonth ()
524+ p .yeari = i + 1
525+ }
492526 }
493527 }
494528
@@ -712,7 +746,7 @@ iterRunes:
712746 } else if i == 4 {
713747 // gross
714748 datestr = datestr [0 :i - 1 ] + datestr [i :]
715- return parseTime (datestr , loc )
749+ return parseTime (datestr , loc , opts ... )
716750 } else {
717751 return nil , unknownErr (datestr )
718752 }
@@ -867,25 +901,25 @@ iterRunes:
867901 case 't' , 'T' :
868902 if p .nextIs (i , 'h' ) || p .nextIs (i , 'H' ) {
869903 if len (datestr ) > i + 2 {
870- return parseTime (fmt .Sprintf ("%s%s" , p .datestr [0 :i ], p .datestr [i + 2 :]), loc )
904+ return parseTime (fmt .Sprintf ("%s%s" , p .datestr [0 :i ], p .datestr [i + 2 :]), loc , opts ... )
871905 }
872906 }
873907 case 'n' , 'N' :
874908 if p .nextIs (i , 'd' ) || p .nextIs (i , 'D' ) {
875909 if len (datestr ) > i + 2 {
876- return parseTime (fmt .Sprintf ("%s%s" , p .datestr [0 :i ], p .datestr [i + 2 :]), loc )
910+ return parseTime (fmt .Sprintf ("%s%s" , p .datestr [0 :i ], p .datestr [i + 2 :]), loc , opts ... )
877911 }
878912 }
879913 case 's' , 'S' :
880914 if p .nextIs (i , 't' ) || p .nextIs (i , 'T' ) {
881915 if len (datestr ) > i + 2 {
882- return parseTime (fmt .Sprintf ("%s%s" , p .datestr [0 :i ], p .datestr [i + 2 :]), loc )
916+ return parseTime (fmt .Sprintf ("%s%s" , p .datestr [0 :i ], p .datestr [i + 2 :]), loc , opts ... )
883917 }
884918 }
885919 case 'r' , 'R' :
886920 if p .nextIs (i , 'd' ) || p .nextIs (i , 'D' ) {
887921 if len (datestr ) > i + 2 {
888- return parseTime (fmt .Sprintf ("%s%s" , p .datestr [0 :i ], p .datestr [i + 2 :]), loc )
922+ return parseTime (fmt .Sprintf ("%s%s" , p .datestr [0 :i ], p .datestr [i + 2 :]), loc , opts ... )
889923 }
890924 }
891925 }
@@ -1059,7 +1093,7 @@ iterRunes:
10591093 // 2014-05-11 08:20:13,787
10601094 ds := []byte (p .datestr )
10611095 ds [i ] = '.'
1062- return parseTime (string (ds ), loc )
1096+ return parseTime (string (ds ), loc , opts ... )
10631097 case '-' , '+' :
10641098 // 03:21:51+00:00
10651099 p .stateTime = timeOffset
@@ -1763,48 +1797,75 @@ iterRunes:
17631797}
17641798
17651799type parser struct {
1766- loc * time.Location
1767- preferMonthFirst bool
1768- ambiguousMD bool
1769- stateDate dateState
1770- stateTime timeState
1771- format []byte
1772- datestr string
1773- fullMonth string
1774- skip int
1775- extra int
1776- part1Len int
1777- yeari int
1778- yearlen int
1779- moi int
1780- molen int
1781- dayi int
1782- daylen int
1783- houri int
1784- hourlen int
1785- mini int
1786- minlen int
1787- seci int
1788- seclen int
1789- msi int
1790- mslen int
1791- offseti int
1792- offsetlen int
1793- tzi int
1794- tzlen int
1795- t * time.Time
1800+ loc * time.Location
1801+ preferMonthFirst bool
1802+ retryAmbiguousDateWithSwap bool
1803+ ambiguousMD bool
1804+ stateDate dateState
1805+ stateTime timeState
1806+ format []byte
1807+ datestr string
1808+ fullMonth string
1809+ skip int
1810+ extra int
1811+ part1Len int
1812+ yeari int
1813+ yearlen int
1814+ moi int
1815+ molen int
1816+ dayi int
1817+ daylen int
1818+ houri int
1819+ hourlen int
1820+ mini int
1821+ minlen int
1822+ seci int
1823+ seclen int
1824+ msi int
1825+ mslen int
1826+ offseti int
1827+ offsetlen int
1828+ tzi int
1829+ tzlen int
1830+ t * time.Time
1831+ }
1832+
1833+ // ParserOption defines a function signature implemented by options
1834+ // Options defined like this accept the parser and operate on the data within
1835+ type ParserOption func (* parser ) error
1836+
1837+ // PreferMonthFirst is an option that allows preferMonthFirst to be changed from its default
1838+ func PreferMonthFirst (preferMonthFirst bool ) ParserOption {
1839+ return func (p * parser ) error {
1840+ p .preferMonthFirst = preferMonthFirst
1841+ return nil
1842+ }
17961843}
17971844
1798- func newParser (dateStr string , loc * time.Location ) * parser {
1799- p := parser {
1800- stateDate : dateStart ,
1801- stateTime : timeIgnore ,
1802- datestr : dateStr ,
1803- loc : loc ,
1804- preferMonthFirst : true ,
1845+ // RetryAmbiguousDateWithSwap is an option that allows retryAmbiguousDateWithSwap to be changed from its default
1846+ func RetryAmbiguousDateWithSwap (retryAmbiguousDateWithSwap bool ) ParserOption {
1847+ return func (p * parser ) error {
1848+ p .retryAmbiguousDateWithSwap = retryAmbiguousDateWithSwap
1849+ return nil
1850+ }
1851+ }
1852+
1853+ func newParser (dateStr string , loc * time.Location , opts ... ParserOption ) * parser {
1854+ p := & parser {
1855+ stateDate : dateStart ,
1856+ stateTime : timeIgnore ,
1857+ datestr : dateStr ,
1858+ loc : loc ,
1859+ preferMonthFirst : true ,
1860+ retryAmbiguousDateWithSwap : false ,
18051861 }
18061862 p .format = []byte (dateStr )
1807- return & p
1863+
1864+ // allow the options to mutate the parser fields from their defaults
1865+ for _ , option := range opts {
1866+ option (p )
1867+ }
1868+ return p
18081869}
18091870
18101871func (p * parser ) nextIs (i int , b byte ) bool {
0 commit comments