Module: Binxtils::TimeZoneParser

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

Instance Method Summary collapse

Instance Method Details

#full_name(time_zone) ⇒ Object



35
36
37
38
# File 'lib/binxtils/time_zone_parser.rb', line 35

def full_name(time_zone)
  # TODO: figure out an easier way to get this
  ActiveSupport::TimeZone::MAPPING.key(time_zone.tzinfo.name) || time_zone.name
end

#parse(time_zone_str) ⇒ Object



7
8
9
10
11
12
13
14
15
# File 'lib/binxtils/time_zone_parser.rb', line 7

def parse(time_zone_str)
  return nil if time_zone_str.blank?
  return time_zone_str if time_zone_str.is_a?(ActiveSupport::TimeZone) # in case we were given a time_zone obj

  # tzinfo requires non-whitespaced strings.
  # if the normal lookup fails, remove parens, then remove spaces and convert to underscores
  ActiveSupport::TimeZone[time_zone_str] ||
    ActiveSupport::TimeZone[time_zone_str.to_s.gsub(/\(.*?\)/, "").strip.tr("\s", "_")]
end

#parse_from_time_and_offset(time:, offset:) ⇒ Object



28
29
30
31
32
33
# File 'lib/binxtils/time_zone_parser.rb', line 28

def parse_from_time_and_offset(time:, offset:)
  offset_seconds = offset.is_a?(String) ? Time.zone_offset(offset) : offset
  offset_seconds ||= offset.to_i # Fallback parsing of seconds in a string

  prioritized_zones_matching_offset(time, offset_seconds).first
end

#parse_from_time_string(time_str) ⇒ Object



17
18
19
20
21
22
23
24
25
26
# File 'lib/binxtils/time_zone_parser.rb', line 17

def parse_from_time_string(time_str)
  return unless time_str.present? && time_string_has_zone_info?(time_str)

  at = Time.parse(time_str.to_s)
  offset_seconds = at.utc_offset
  # Otherwise this returns casablanca. Guess UTC over London
  return ActiveSupport::TimeZone["UTC"] if offset_seconds == 0

  prioritized_zones_matching_offset(at, offset_seconds).first
end

#prioritize_zones(zones) ⇒ Object



85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/binxtils/time_zone_parser.rb', line 85

def prioritize_zones(zones)
  zones.sort_by do |zone|
    priority = case zone.tzinfo.name
    # Major US zones
    when /New_York/, /Chicago/, /Los_Angeles/ then 1
    # Major European zones
    when /London/, /Paris/, /Berlin/ then 5
    # Major Asian zones
    when /Tokyo/, /Shanghai/, /Singapore/ then 5

    # Major cities generally
    when /Mexico City|Sydney|Hong_Kong|Dubai|Toronto|Vancouver/ then 10
    # State/Provincial capitals
    when /Melbourne|Brisbane|Perth|Montreal|Edmonton/ then 20
    # Smaller cities
    else 100
    end

    [priority, zone]
  end
end

#prioritized_zones_matching_offset(at, offset_seconds) ⇒ Object

Guess possible time zones based on UTC offset at a specific time Returns an array of [timezone_name, city_name] pairs



68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/binxtils/time_zone_parser.rb', line 68

def prioritized_zones_matching_offset(at, offset_seconds)
  # Convert seconds to hours for comparison
  offset_hours = offset_seconds / 3600.0

  # Get all time zones
  possible_zones = ActiveSupport::TimeZone.all.select do |zone|
    # Calculate the offset at the specific time
    zone_time = at.in_time_zone(zone)
    zone_offset = zone_time.utc_offset / 3600.0

    zone_offset == offset_hours
  end

  # Sort zones by priority/popularity
  prioritize_zones(possible_zones)
end

#time_string_has_zone_info?(time_str) ⇒ Boolean

TODO: This might be overly complicated garbage

Returns:

  • (Boolean)


45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/binxtils/time_zone_parser.rb', line 45

def time_string_has_zone_info?(time_str)
  return false if Binxtils::TimeParser.looks_like_timestamp?(time_str)

  timezone_patterns = [
    /[+-]\d{2}:?\d{2}\b/,          # +0900, +09:00, -0500, etc
    /\b(?:UTC|GMT)\b/i,            # UTC or GMT
    /\b[A-Z]{3,4}\b/,              # EST, PDT, AEST, etc
    /[+-]\d{4}\b/,                 # +0900, -0500 without colon
    /Z\b/,                         # UTC (Zulu time)
    /\[[-+A-Za-z0-9\/]+\]/         # Time zone in brackets [America/New_York]
  ]

  # Try parsing to validate it's actually a time string
  Time.parse(time_str)

  # Check if any timezone pattern matches
  timezone_patterns.any? { |pattern| time_str.match?(pattern) }
rescue ArgumentError
  false
end