@@ -18,6 +18,10 @@ M.get_timestamp = function(date_string, opts)
1818 return nil
1919 end
2020 local date_table = os.date (" *t" , timestamp )
21+ -- let awkward dates like Feb 21 be interpreted as days, not months
22+ if period == " month" and (format == " %B %Y" or format == " %b %Y" ) and date_table .year <= 31 then
23+ return nil
24+ end
2125 if (period == " month" or period == " year" ) and date_table .day ~= 1 then
2226 -- correct strange off-by-one error from strptime
2327 timestamp = timestamp + (24 * 60 * 60 )
@@ -195,6 +199,31 @@ M.weekstamp = function(date_string, opts)
195199 return token
196200end
197201
202+ M .number = function (min_digits , max_digits )
203+ local closure = function (date_string , opts )
204+ local digit_part = " "
205+ if max_digits <= 0 then
206+ digit_part = " %d+"
207+ else
208+ for i = 1 , max_digits do
209+ if i <= min_digits then
210+ digit_part = digit_part .. " %d"
211+ else
212+ digit_part = digit_part .. " %d?"
213+ end
214+ end
215+ end
216+ local matched = M .match (digit_part .. " %s*" )(date_string )
217+ if matched == nil then
218+ return nil
219+ end
220+ local stripped = string.gsub (matched .captured , " %s" , " " )
221+ local num = tonumber (stripped )
222+ return { type = " number" , number = num , str = matched .str }
223+ end
224+ return closure
225+ end
226+
198227M .number_offset = function (inverse )
199228 local closure = function (date_string , opts )
200229 local joiner = function (tokens )
@@ -414,6 +443,43 @@ M.weekday_number_offset = function(date_string, opts)
414443 return parser (date_string , opts )
415444end
416445
446+ M .join_day_of_month = function (tokens , opts )
447+ local month_name = nil
448+ for _ , token in pairs (tokens ) do
449+ if token .type == " match" then
450+ month_name = string.gsub (token .captured , " %s" , " " )
451+ break
452+ end
453+ end
454+ if month_name == nil then
455+ return nil
456+ end
457+ local day_num = 0
458+ for _ , token in pairs (tokens ) do
459+ if token .type == " number" then
460+ day_num = token .number
461+ end
462+ end
463+ if day_num > 31 or day_num < 1 then
464+ return nil
465+ end
466+ local this_dt = datetime .get_month_from_today (month_name , opts .parsing .resolve_strategy .month .this , opts )
467+ if this_dt == nil then
468+ return nil
469+ end
470+ this_dt .day = day_num
471+ -- check that this isn't a nonsense date e.g. "Feb 31"
472+ local normalised = os.date (" *t" , os.time (this_dt ))
473+ if normalised .day ~= this_dt .day then
474+ return nil
475+ end
476+ return {
477+ type = " period" ,
478+ period = { this_dt , " day" },
479+ str = tokens [# tokens ].str
480+ }
481+ end
482+
417483
418484M .join_month_numerical = function (tokens , opts )
419485 local month_name = nil
@@ -482,6 +548,14 @@ M.single_token_month = function(date_string, opts)
482548 return { type = " period" , period = { dt , " month" }, str = " " }
483549end
484550
551+ M .day_of_month = function (date_string , opts )
552+ local parser = M .select ({
553+ M .join ({ M .number (1 , 2 ), M .match_month }, M .join_day_of_month ),
554+ M .join ({ M .match_month , M .number (1 , 2 ) }, M .join_day_of_month )
555+ })
556+ return parser (date_string , opts )
557+ end
558+
485559M .month_verbal_offset = function (date_string , opts )
486560 local parser = M .join ({
487561 M .word_offset ,
@@ -504,6 +578,7 @@ M.get_ambiguous_period = function(date_string, opts)
504578 M .weekday_verbal_offset ,
505579 M .weekday_number_offset ,
506580 M .single_token_month ,
581+ M .day_of_month ,
507582 M .month_verbal_offset ,
508583 M .month_number_offset ,
509584 })
0 commit comments