Module: Binxtils::TimeParser

Extended by:
Functionable
Defined in:
lib/binxtils/time_parser.rb

Constant Summary collapse

EARLIEST_YEAR =
1900

Instance Method Summary collapse

Instance Method Details

#default_time_zoneObject



9
10
11
# File 'lib/binxtils/time_parser.rb', line 9

def default_time_zone
  @default_time_zone ||= ActiveSupport::TimeZone[Rails.application.class.config.time_zone].freeze
end

#looks_like_timestamp?(time_str) ⇒ Boolean

Returns:

  • (Boolean)


21
22
23
# File 'lib/binxtils/time_parser.rb', line 21

def looks_like_timestamp?(time_str)
  time_str.is_a?(Integer) || time_str.is_a?(Float) || time_str.to_s.strip.match(/^\d+\z/) # it's only numbers
end

#parse(time_str = nil, time_zone_str = nil, in_time_zone: false, parse_error: :raise) ⇒ Object

parse_error: :raise raises unparseable input errors, :nil swallows them



14
15
16
17
18
19
# File 'lib/binxtils/time_parser.rb', line 14

def parse(time_str = nil, time_zone_str = nil, in_time_zone: false, parse_error: :raise)
  parse!(time_str, time_zone_str, in_time_zone:)
rescue ArgumentError
  raise unless parse_error == :nil
  nil
end

#parse!(time_str = nil, time_zone_str = nil, in_time_zone: false) ⇒ Object

private below here



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/binxtils/time_parser.rb', line 38

def parse!(time_str = nil, time_zone_str = nil, in_time_zone: false)
  return nil unless time_str.present?
  return time_str if time_str.is_a?(Time)

  if looks_like_timestamp?(time_str)
    return parse!("#{time_str}-01-01") if time_str.to_s.length == 4 # Looks like year, valid 8601 format

    # otherwise it's a timestamp
    time = Time.at(time_str.to_i)
  else
    time_zone = Binxtils::TimeZoneParser.parse(time_zone_str)
    time = (time_zone || Time.zone).parse(time_str.to_s) # Assign in time zone
  end
  # Return in time_zone or not
  in_time_zone ? time_in_zone(time, time_str:, time_zone:, time_zone_str:) : time
rescue ArgumentError => e
  # Try to parse some other, unexpected formats -
  paychex_formatted = %r{(?<month>\d+)/(?<day>\d+)/(?<year>\d+) (?<hour>\d\d):(?<minute>\d\d) (?<ampm>\w\w)}.match(time_str)
  ie11_formatted = %r{(?<month>\d+)/(?<day>\d+)/(?<year>\d+)}.match(time_str)
  just_date = %r{(?<year>\d{4})\D(?<month>\d\d?)}.match(time_str)
  just_date_backward = %r{(?<month>\d\d?)\D(?<year>\d{4})}.match(time_str)

  # Get the successful matching regex group, and then reformat it in an expected way
  regex_match = [paychex_formatted, ie11_formatted, just_date, just_date_backward].compact.first
  raise e unless regex_match.present?

  new_str = %w[year month day]
    .map { |component| regex_match[component] if regex_match.names.include?(component) }
    .compact
    .join("-")

  # If we end up with an unreasonable year or month, throw an error
  # (an invalid month would otherwise recurse forever, re-matching and re-appending the day)
  raise e unless new_str.split("-").first.to_i.between?(EARLIEST_YEAR, Time.current.year + 100)
  raise e unless regex_match["month"].to_i.between?(1, 12)

  # Add the day, if there isn't one
  new_str += "-01" unless regex_match.names.include?("day")
  # If it's paychex_formatted there is an hour and minute
  if paychex_formatted.present?
    new_str += " #{regex_match["hour"]}:#{regex_match["minute"]}#{regex_match["ampm"]}"
  end
  # Run it through Binxtils::TimeParser again
  parse!(new_str, time_zone_str, in_time_zone:)
end

#round(time, unit = "minute") ⇒ Object

Accepts a time object, rounds to minutes



26
27
28
29
30
31
32
# File 'lib/binxtils/time_parser.rb', line 26

def round(time, unit = "minute")
  if unit == "second"
    time.change(usec: 0, sec: 0)
  else # Default is minute, nothing is built to manage anything else
    time.change(min: 0, usec: 0, sec: 0)
  end
end

#time_in_zone(time, time_zone_str:, time_str: nil, time_zone: nil) ⇒ Object



84
85
86
87
88
89
90
91
92
93
# File 'lib/binxtils/time_parser.rb', line 84

def time_in_zone(time, time_zone_str:, time_str: nil, time_zone: nil)
  time_zone ||= if time_zone_str.present?
    Binxtils::TimeZoneParser.parse(time_zone_str)
  elsif time_str.present?
    # If no time_zone_str was passed, try to parse it out of the time string
    Binxtils::TimeZoneParser.parse_from_time_string(time_str.to_s)
  end

  time.in_time_zone(time_zone || ActiveSupport::TimeZone["UTC"])
end