Module: Flycal::TimeFormat

Defined in:
lib/flycal/time_format.rb

Overview

Named-zone clock for template hours and every datetime Flycal emits.

Wall-clock windows (work 9:30–13, dinner 19–23, template_custom) are local to this zone — never the server process TZ (often UTC in production). JSON start/end/from/to always share that same offset.

Constant Summary collapse

DEFAULT_ZONE =
"Europe/Rome"

Class Method Summary collapse

Class Method Details

.coerce(value) ⇒ Object



108
109
110
111
112
113
114
115
116
# File 'lib/flycal/time_format.rb', line 108

def coerce(value)
  return nil if value.nil?
  return value if value.is_a?(Time)
  return value.to_time if value.respond_to?(:to_time)

  Time.parse(value.to_s)
rescue ArgumentError
  nil
end

.coerce_zone(name) ⇒ Object



34
35
36
37
38
39
40
# File 'lib/flycal/time_format.rb', line 34

def coerce_zone(name)
  return name if name.is_a?(ActiveSupport::TimeZone)

  key = name.to_s.strip
  key = DEFAULT_ZONE if key.empty?
  ActiveSupport::TimeZone[key] || ActiveSupport::TimeZone[DEFAULT_ZONE]
end

.current_zoneObject



30
31
32
# File 'lib/flycal/time_format.rb', line 30

def current_zone
  Thread.current[:flycal_timezone] || coerce_zone(DEFAULT_ZONE)
end

.from_calendars(calendars, calendar_ids = nil) ⇒ Object



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

def from_calendars(calendars, calendar_ids = nil)
  return nil if calendars.nil? || (calendars.respond_to?(:empty?) && calendars.empty?)

  ids = Array(calendar_ids).map(&:to_s)
  list = Array(calendars)
  selected =
    if ids.empty?
      list
    else
      matched = list.select { |cal| cal.respond_to?(:id) && ids.include?(cal.id.to_s) }
      matched.empty? ? list : matched
    end
  cal = selected.find { |c| c.respond_to?(:primary) && c.primary } || selected.first
  tz = cal.respond_to?(:time_zone) ? cal.time_zone : nil
  return nil if tz.to_s.strip.empty?

  zone_name(tz)
end

.iso8601(value, zone: current_zone) ⇒ Object



97
98
99
# File 'lib/flycal/time_format.rb', line 97

def iso8601(value, zone: current_zone)
  unified(value, zone: zone)&.iso8601
end

.local(year, month, day, hour = 0, min = 0, sec = 0, zone: current_zone) ⇒ Object



93
94
95
# File 'lib/flycal/time_format.rb', line 93

def local(year, month, day, hour = 0, min = 0, sec = 0, zone: current_zone)
  coerce_zone(zone).local(year, month, day, hour, min, sec)
end

.nowObject



85
86
87
# File 'lib/flycal/time_format.rb', line 85

def now
  current_zone.now
end

.resolve(explicit: nil, calendars: nil, calendar_ids: nil) ⇒ Object

explicit param → Google calendar timeZone → FLYCAL_TIMEZONE → Europe/Rome. Never uses ENV (Docker/Kamal set that to UTC).



53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/flycal/time_format.rb', line 53

def resolve(explicit: nil, calendars: nil, calendar_ids: nil)
  key = explicit.to_s.strip
  return zone_name(key) unless key.empty?

  from_cal = from_calendars(calendars, calendar_ids)
  return from_cal if from_cal

  env = ENV["FLYCAL_TIMEZONE"].to_s.strip
  return zone_name(env) unless env.empty?

  DEFAULT_ZONE
end

.todayObject



89
90
91
# File 'lib/flycal/time_format.rb', line 89

def today
  current_zone.today
end

.unified(value, zone: current_zone) ⇒ Object



101
102
103
104
105
106
# File 'lib/flycal/time_format.rb', line 101

def unified(value, zone: current_zone)
  time = coerce(value)
  return nil unless time

  coerce_zone(zone).at(time.to_f)
end

.with_zone(name) ⇒ Object



22
23
24
25
26
27
28
# File 'lib/flycal/time_format.rb', line 22

def with_zone(name)
  previous = Thread.current[:flycal_timezone]
  Thread.current[:flycal_timezone] = coerce_zone(name)
  yield current_zone
ensure
  Thread.current[:flycal_timezone] = previous
end

.zone_name(name = nil) ⇒ Object



42
43
44
45
46
47
48
49
# File 'lib/flycal/time_format.rb', line 42

def zone_name(name = nil)
  zone = if name.nil? || name.to_s.strip.empty?
           current_zone
         else
           coerce_zone(name)
         end
  zone.tzinfo&.identifier || zone.name
end