Class: Slk::Support::TimeParser
- Inherits:
-
Object
- Object
- Slk::Support::TimeParser
- Defined in:
- lib/slk/support/time_parser.rb
Overview
Resolves a single clock time ("1:30p", "2026-08-12 8a") to a Unix timestamp. A bare time at or before now rolls to tomorrow; an explicit YYYY-MM-DD date is honoured as given.
TimeRangeParser builds the two-sided form on the clock arithmetic here.
Constant Summary collapse
- TIME =
/(\d{1,2})(?::(\d{2}))?\s*([ap]m?)?/i- PATTERN =
/\A(?:(\d{4}-\d{2}-\d{2})\s+)?#{TIME}\z/i- EXAMPLE =
'1:30p or 2026-08-12 8:00'- MINUTES_PER_DAY =
24 * 60
- CLOCK_HOURS =
A bare hour of 0 or 13-23 can only be 24-hour notation, so no am/pm was omitted. Anything in 1..12 is genuinely ambiguous without one.
(1..12)
Class Method Summary collapse
-
.parse(input, now: Time.now) ⇒ Integer
Unix timestamp.
Instance Method Summary collapse
-
#ambiguous?(parts) ⇒ Boolean
True when these parts could have meant either am or pm — no meridiem given, and an hour small enough that both readings are plausible.
- #at(date, *parts) ⇒ Object
-
#initialize(now: Time.now) ⇒ TimeParser
constructor
A new instance of TimeParser.
-
#minutes(parts) ⇒ Object
Minutes since midnight, for comparing two times before either has a date.
- #parse(input) ⇒ Object
- #parse_date(text, example: EXAMPLE) ⇒ Object
-
#place(date, *parts) ⇒ Object
Same placement without the existence check, for callers asking a question a nonexistent time still answers.
-
#twenty_four_hour?(parts) ⇒ Boolean
The opposite: a bare hour too large to be a 12-hour clock reading, so the writer was plainly using 24-hour notation.
Constructor Details
#initialize(now: Time.now) ⇒ TimeParser
Returns a new instance of TimeParser.
29 30 31 |
# File 'lib/slk/support/time_parser.rb', line 29 def initialize(now: Time.now) @now = now end |
Class Method Details
.parse(input, now: Time.now) ⇒ Integer
Returns Unix timestamp.
27 |
# File 'lib/slk/support/time_parser.rb', line 27 def self.parse(input, now: Time.now) = new(now: now).parse(input) |
Instance Method Details
#ambiguous?(parts) ⇒ Boolean
True when these parts could have meant either am or pm — no meridiem given, and an hour small enough that both readings are plausible.
80 |
# File 'lib/slk/support/time_parser.rb', line 80 def ambiguous?(parts) = parts[2].nil? && CLOCK_HOURS.cover?(parts[0].to_i) |
#at(date, *parts) ⇒ Object
53 54 55 56 57 58 59 60 61 62 63 64 |
# File 'lib/slk/support/time_parser.rb', line 53 def at(date, *parts) hours, minutes = to_24_hour(*parts) time = build(date, hours, minutes) # Ruby silently shifts a local time that DST skips (2:30a on a # spring-forward date becomes 3:30a), which would move the window # rather than fail. Reject it instead. return time if time.hour == hours && time.min == minutes raise TimeFormatError, format('%<clock>s does not exist on %<date>s (clocks skip forward for DST).', clock: format('%<h>02d:%<m>02d', h: hours, m: minutes), date: date) end |
#minutes(parts) ⇒ Object
Minutes since midnight, for comparing two times before either has a date.
73 74 75 76 |
# File 'lib/slk/support/time_parser.rb', line 73 def minutes(parts) hours, mins = to_24_hour(*parts) (hours * 60) + mins end |
#parse(input) ⇒ Object
33 34 35 36 37 38 39 40 |
# File 'lib/slk/support/time_parser.rb', line 33 def parse(input) match = PATTERN.match(input.to_s.strip) raise TimeFormatError, "Invalid time: #{input}. Use #{EXAMPLE}" unless match parts = match.captures[1..3] date = match[1] ? parse_date(match[1]) : roll_forward(parts) at(date, *parts).to_i end |
#parse_date(text, example: EXAMPLE) ⇒ Object
47 48 49 50 51 |
# File 'lib/slk/support/time_parser.rb', line 47 def parse_date(text, example: EXAMPLE) Date.parse(text) rescue Date::Error raise TimeFormatError, "Invalid date: #{text}. Use #{example}" end |
#place(date, *parts) ⇒ Object
Same placement without the existence check, for callers asking a
question a nonexistent time still answers. at would raise, which for
the roll-forward probe would reject "2:30a" outright on a spring-forward
date instead of rolling it to the next day, where it does exist.
70 |
# File 'lib/slk/support/time_parser.rb', line 70 def place(date, *parts) = build(date, *to_24_hour(*parts)) |
#twenty_four_hour?(parts) ⇒ Boolean
The opposite: a bare hour too large to be a 12-hour clock reading, so the writer was plainly using 24-hour notation. TimeRangeParser treats one of these as settling how to read the other side of a range.
85 |
# File 'lib/slk/support/time_parser.rb', line 85 def twenty_four_hour?(parts) = parts[2].nil? && !CLOCK_HOURS.cover?(parts[0].to_i) |