Module: Codepulse::TimeHelpers

Included in:
CLI, MetricsCalculator
Defined in:
lib/codepulse/time_helpers.rb

Overview

Time calculation helpers for business days and holidays.

Constant Summary collapse

SECONDS_PER_DAY =
86_400

Instance Method Summary collapse

Instance Method Details

#build_us_holidays(year) ⇒ Object



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
# File 'lib/codepulse/time_helpers.rb', line 44

def build_us_holidays(year)
  holidays = []

  # New Year's Day (Jan 1)
  holidays << [year, 1, 1]

  # MLK Day (3rd Monday in January)
  holidays << nth_weekday(year, 1, 1, 3)

  # Presidents Day (3rd Monday in February)
  holidays << nth_weekday(year, 2, 1, 3)

  # Memorial Day (last Monday in May)
  holidays << last_weekday(year, 5, 1)

  # Juneteenth (June 19)
  holidays << [year, 6, 19]

  # Independence Day (July 4)
  holidays << [year, 7, 4]

  # Labor Day (1st Monday in September)
  holidays << nth_weekday(year, 9, 1, 1)

  # Columbus Day (2nd Monday in October)
  holidays << nth_weekday(year, 10, 1, 2)

  # Veterans Day (Nov 11)
  holidays << [year, 11, 11]

  # Thanksgiving (4th Thursday in November)
  holidays << nth_weekday(year, 11, 4, 4)

  # Christmas Day (Dec 25)
  holidays << [year, 12, 25]

  holidays
end

#business_day?(time_value) ⇒ Boolean

Returns true if the time falls on a weekday and is not a US holiday.

Returns:

  • (Boolean)


18
19
20
21
22
23
# File 'lib/codepulse/time_helpers.rb', line 18

def business_day?(time_value)
  return false unless weekday?(time_value)
  return false if us_holiday?(time_value)

  true
end

#business_days_cutoff(business_days) ⇒ Object

Returns the cutoff time for N business days ago.



130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/codepulse/time_helpers.rb', line 130

def business_days_cutoff(business_days)
  return nil unless business_days

  current_time = Time.now
  remaining_days = business_days

  while remaining_days.positive?
    current_time -= SECONDS_PER_DAY
    remaining_days -= 1 if business_day?(current_time)
  end

  start_of_day(current_time)
end

#business_seconds_between(start_time, end_time) ⇒ Object

Calculates seconds between two times, counting only business day hours. Skips weekends and US holidays entirely.



110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/codepulse/time_helpers.rb', line 110

def business_seconds_between(start_time, end_time)
  return nil unless start_time && end_time
  return 0 if end_time <= start_time

  total = 0
  current_start = start_time

  while current_start < end_time
    day_end = end_of_day(current_start)
    segment_end = [day_end, end_time].min

    total += (segment_end - current_start) if business_day?(current_start)

    current_start = start_of_day(current_start + SECONDS_PER_DAY)
  end

  total.to_i
end

#date_key(time_value) ⇒ Object



35
36
37
# File 'lib/codepulse/time_helpers.rb', line 35

def date_key(time_value)
  [time_value.year, time_value.month, time_value.day]
end

#end_of_day(time_value) ⇒ Object



104
105
106
# File 'lib/codepulse/time_helpers.rb', line 104

def end_of_day(time_value)
  Time.new(time_value.year, time_value.month, time_value.day, 23, 59, 59, time_value.utc_offset)
end

#last_weekday(year, month, target_wday) ⇒ Object

Finds the last occurrence of a weekday in a month (e.g., last Monday of May).



93
94
95
96
97
98
# File 'lib/codepulse/time_helpers.rb', line 93

def last_weekday(year, month, target_wday)
  next_month = month == 12 ? Time.new(year + 1, 1, 1) : Time.new(year, month + 1, 1)
  last_day = next_month - SECONDS_PER_DAY
  days_back = (last_day.wday - target_wday + 7) % 7
  [year, month, last_day.day - days_back]
end

#nth_weekday(year, month, target_wday, occurrence) ⇒ Object

Finds the nth occurrence of a weekday in a month (e.g., 3rd Monday). target_wday: 0=Sun, 1=Mon, ..., 6=Sat



85
86
87
88
89
90
# File 'lib/codepulse/time_helpers.rb', line 85

def nth_weekday(year, month, target_wday, occurrence)
  first_day = Time.new(year, month, 1)
  days_until = (target_wday - first_day.wday + 7) % 7
  day = 1 + days_until + (7 * (occurrence - 1))
  [year, month, day]
end

#parse_time(value) ⇒ Object

Parses a time string, returns nil if invalid.



11
12
13
14
15
# File 'lib/codepulse/time_helpers.rb', line 11

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

#start_of_day(time_value) ⇒ Object



100
101
102
# File 'lib/codepulse/time_helpers.rb', line 100

def start_of_day(time_value)
  Time.new(time_value.year, time_value.month, time_value.day, 0, 0, 0, time_value.utc_offset)
end

#us_holiday?(time_value) ⇒ Boolean

Returns true if the date is a US federal holiday.

Returns:

  • (Boolean)


31
32
33
# File 'lib/codepulse/time_helpers.rb', line 31

def us_holiday?(time_value)
  us_holidays(time_value.year).include?(date_key(time_value))
end

#us_holidays(year) ⇒ Object



39
40
41
42
# File 'lib/codepulse/time_helpers.rb', line 39

def us_holidays(year)
  @us_holidays_cache ||= {}
  @us_holidays_cache[year] ||= build_us_holidays(year)
end

#weekday?(time_value) ⇒ Boolean

Returns true for Monday through Friday.

Returns:

  • (Boolean)


26
27
28
# File 'lib/codepulse/time_helpers.rb', line 26

def weekday?(time_value)
  time_value.wday.between?(1, 5)
end