Module: DateArg

Defined in:
lib/imsg-grep/utils/date.rb

Overview

DateArg - Flexible date/time parser supporting multiple input formats

Supported formats:

Absolute dates:

2024-01-01              => Date object
2024-1-1                => Date object (relaxed format)
2024-12-31              => Date object

Absolute dates with time (inherits timezone from ‘now’ parameter):

2024-01-01 10:30        => Time object
2024-01-01T10:30        => Time object
2024-01-01 10:30:45     => Time object with seconds
2024-1-1 9:5            => Time object (relaxed format)

Absolute dates with explicit timezone:

2024-01-01T10:30Z       => Time object in UTC
2024-01-01T10:30+05:30  => Time object with timezone
2024-01-01T10:30-08:00  => Time object with timezone
2024-01-01T10:30-0800   => Time object (compact timezone)
2024-01-01T10:30-08     => Time object (short timezone)

Time only (uses current date from ‘now’ parameter):

1:34                    => Time object (24-hour format)
23:59                   => Time object (24-hour format)
10a, 10am               => Time object (10 AM)
10p, 10pm               => Time object (10 PM)
12:34a                  => Time object (12:34 AM, converts 12 to 0)
12:45p                  => Time object (12:45 PM, keeps 12)
0:34a                   => Time object (12:34 AM)
0p                      => Time object (12:00 PM, converts 0 to 12)

Relative dates (going back in time from ‘now’):

1d                      => Date 1 day ago
7d                      => Date 7 days ago
1w                      => Date 1 week (7 days) ago
2w                      => Date 2 weeks (14 days) ago
1m                      => Date 1 month ago
6m                      => Date 6 months ago
1y                      => Date 1 year (12 months) ago
2y                      => Date 2 years (24 months) ago

Relative date combinations:

1y6m                    => Date 1 year 6 months ago
1y6m2w                  => Date 1 year 6 months 2 weeks ago
1y6m2w3d                => Date 1 year 6 months 2 weeks 3 days ago
6m1w                    => Date 6 months 1 week ago

Relative time (going back from ‘now’):

1h                      => Time 1 hour ago
30M, 30min              => Time 30 minutes ago
45s                     => Time 45 seconds ago
2h30M                   => Time 2 hours 30 minutes ago
1h30M45s                => Time 1 hour 30 minutes 45 seconds ago

Mixed relative date and time:

3d2h                    => Time 3 days 2 hours ago
1d12h30M                => Time 1 day 12 hours 30 minutes ago
5d3h15M30s              => Time 5 days 3 hours 15 minutes 30 seconds ago
1min2d                  => Time 2 days 1 minute ago (order doesn't matter)
3w2d4h                  => Time 3 weeks 2 days 4 hours ago

Optional minus prefix (treated same as without):

-1d                     => Same as 1d
-3d2h                   => Same as 3d2h

Notes:

  • Case insensitive for timezone (Z/z) and am/pm suffixes

  • ‘M’ = minutes, ‘m’ = months in relative formats

  • 12-hour time: 12am = midnight, 12pm = noon

  • UTC flag converts ‘now’ to UTC before processing

  • Invalid formats raise DateArg::Error

Defined Under Namespace

Classes: Error

Constant Summary collapse

RX_DATE =

mix of iso8601 and rfc3339 with some allowances for lazy typists¯_(ツ)_/¯

/\A(?<year>\d{4})-(?<month>\d{1,2})-(?<day>\d{1,2})(?<time>[ T](?<hour>\d{1,2}):(?<min>\d{1,2})(?::(?<sec>\d{1,2}))?(?<zone>Z|[+-]\d{2}(?::?\d{2})?)?)?\z/i
RX_TIME =
/\A(?:(?<h>0?\d|1[0-2])(?::(?<m>[0-5]\d))?(?<ampm>[ap]m?)|(?<h>[01]?\d|2[0-3]):(?<m>[0-5]\d))\z/i
RX_REL_TIME_PART =
/(\d+)([hMs]|min)/
RX_REL_DATE_PART =
/(\d+)([ywd]|m(?!in))/
RX_REL_DATE =

5y7m4d style (before now); allow optional - in case -23d reads more intuitively than 23d (in the past), but treat both the same

/\A-?(#{RX_REL_DATE_PART}|#{RX_REL_TIME_PART})+\z/

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.parse(str, utc = false, now = Time.now) ⇒ Object



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/imsg-grep/utils/date.rb', line 86

def self.parse(str, utc = false, now = Time.now)
  now = now.utc if utc
  case str
  in RX_DATE if $~[:zone] then Time.new(*$~.values_at(:year, :month, :day, :hour, :min, :sec), $~[:zone].then{|z| z == ?z ? ?Z : z })
  in RX_DATE if $~[:time] then Time.new(*$~.values_at(:year, :month, :day, :hour, :min, :sec), now.strftime("%z"))
  in RX_DATE              then Date.new(*$~.values_at(:year, :month, :day).map(&:to_i))
  in RX_REL_DATE
    date = str.scan(RX_REL_DATE_PART).inject(now.to_date) do |d, (n, u)|
      op = u =~ /[wd]/ ? :- : :<< # - for days, weeks, << for months, years
      n = n.to_i
      n = n * 7  if u == ?w   # week = 7d
      n = n * 12 if u == ?y   # year = 12mo
      d.send(op, n)
    end
    return date unless RX_REL_TIME_PART =~ str
    t = Time.new(date.year, date.month, date.day, now.hour, now.min, now.sec, now.strftime("%z"))
    str.scan(RX_REL_TIME_PART).inject(t){ |t, (n, u)| t - n.to_i * { s:1, m:60, h:60*60 }[u[0].downcase.to_sym] }
  in RX_TIME
    $~ => h:, m:, ampm:
    m  = m.to_i
    h  = h.to_i
    h  =  0 if ampm =~ /a/i && h == 12
    h += 12 if ampm =~ /p/i && h < 12
    Time.new(now.year, now.month, now.day, h, m, 0, now.strftime("%z"))
  else raise ArgumentError
  end
rescue ArgumentError # from Time/Date.new too
  raise Error
end

Instance Method Details

#parse_dateObject



116
# File 'lib/imsg-grep/utils/date.rb', line 116

def parse_date(...) = DateArg.parse(...)