From 3ac8b171b352d665bdc925dbe919128399823ef1 Mon Sep 17 00:00:00 2001 From: John W Higgins Date: Sun, 5 Jan 2025 21:14:08 -0800 Subject: [PATCH 1/2] Add tests for daylight savings time switches --- test/logger/test_logperiod.rb | 132 ++++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) diff --git a/test/logger/test_logperiod.rb b/test/logger/test_logperiod.rb index ee38d87..945306b 100644 --- a/test/logger/test_logperiod.rb +++ b/test/logger/test_logperiod.rb @@ -14,6 +14,72 @@ def test_next_rotate_time assert_raise(ArgumentError) { Logger::Period.next_rotate_time(time, "invalid") } end + def test_next_rotate_time_dst_begin + tz = ENV['TZ'] + ENV['TZ'] = 'America/New_York' # 1 hour shift + time = Time.new("2025-03-09 00:52:02") + + assert_next_rotate_time_words(time, "2025-03-10 00:00:00", ["daily", :daily]) + assert_next_rotate_time_words(time, "2025-03-16 00:00:00", ["weekly", :weekly]) + assert_next_rotate_time_words(time, "2025-04-01 00:00:00", ["monthly", :monthly]) + + ENV['TZ'] = 'Antarctica/Troll' # 2 hour shift + time = Time.new("2025-03-30 00:52:02") + + assert_next_rotate_time_words(time, "2025-03-31 00:00:00", ["daily", :daily]) + assert_next_rotate_time_words(time, "2025-04-06 00:00:00", ["weekly", :weekly]) + assert_next_rotate_time_words(time, "2025-04-01 00:00:00", ["monthly", :monthly]) + + ENV['TZ'] = 'Australia/Lord_Howe' # 30 minute shift + time = Time.new("2025-04-06 00:52:02") + + assert_next_rotate_time_words(time, "2025-04-07 00:00:00", ["daily", :daily]) + assert_next_rotate_time_words(time, "2025-04-13 00:00:00", ["weekly", :weekly]) + assert_next_rotate_time_words(time, "2025-05-01 00:00:00", ["monthly", :monthly]) + + ENV['TZ'] = 'Asia/Gaza' # 1 hour shift on Saturday + time = Time.new("2025-04-12 00:52:02") + + assert_next_rotate_time_words(time, "2025-04-13 00:00:00", ["daily", :daily]) + assert_next_rotate_time_words(time, "2025-04-13 00:00:00", ["weekly", :weekly]) + assert_next_rotate_time_words(time, "2025-05-01 00:00:00", ["monthly", :monthly]) + + ENV['TZ'] = tz + end + + def test_next_rotate_time_dst_end + tz = ENV['TZ'] + ENV['TZ'] = 'America/New_York' # 1 hour shift + time = Time.parse("2025-11-02 13:52:02") + + assert_next_rotate_time_words(time, "2025-11-03 00:00:00", ["daily", :daily]) + assert_next_rotate_time_words(time, "2025-11-09 00:00:00", ["weekly", :weekly]) + assert_next_rotate_time_words(time, "2025-12-01 00:00:00", ["monthly", :monthly]) + + ENV['TZ'] = 'Antarctica/Troll' # 2 hour shift + time = Time.parse("2025-10-26 13:52:02") + + assert_next_rotate_time_words(time, "2025-10-27 00:00:00", ["daily", :daily]) + assert_next_rotate_time_words(time, "2025-11-02 00:00:00", ["weekly", :weekly]) + assert_next_rotate_time_words(time, "2025-11-01 00:00:00", ["monthly", :monthly]) + + ENV['TZ'] = 'Australia/Lord_Howe' # 30 minute shift + time = Time.parse("2025-10-05 13:52:02") + + assert_next_rotate_time_words(time, "2025-10-06 00:00:00", ["daily", :daily]) + assert_next_rotate_time_words(time, "2025-10-12 00:00:00", ["weekly", :weekly]) + assert_next_rotate_time_words(time, "2025-11-01 00:00:00", ["monthly", :monthly]) + + ENV['TZ'] = 'Asia/Gaza' # 1 hour shift on Saturday + time = Time.new("2025-10-25 13:52:02") + + assert_next_rotate_time_words(time, "2025-10-26 00:00:00", ["daily", :daily]) + assert_next_rotate_time_words(time, "2025-10-26 00:00:00", ["weekly", :weekly]) + assert_next_rotate_time_words(time, "2025-11-01 00:00:00", ["monthly", :monthly]) + + ENV['TZ'] = tz + end + def test_next_rotate_time_extreme_cases # First day of Month and Saturday time = Time.parse("2018-07-01 00:00:00") @@ -35,6 +101,72 @@ def test_previous_period_end assert_raise(ArgumentError) { Logger::Period.previous_period_end(time, "invalid") } end + def test_previous_period_end_dst_begin + tz = ENV['TZ'] + ENV['TZ'] = 'America/New_York' # 1 hour shift + time = Time.new("2025-03-09 00:52:02") + + assert_previous_period_end_words(time, "2025-03-08 23:59:59", ["daily", :daily]) + assert_previous_period_end_words(time, "2025-03-08 23:59:59", ["weekly", :weekly]) + assert_previous_period_end_words(time, "2025-02-28 23:59:59", ["monthly", :monthly]) + + ENV['TZ'] = 'Antarctica/Troll' # 2 hour shift + time = Time.new("2025-03-30 00:52:02") + + assert_previous_period_end_words(time, "2025-03-29 23:59:59", ["daily", :daily]) + assert_previous_period_end_words(time, "2025-03-29 23:59:59", ["weekly", :weekly]) + assert_previous_period_end_words(time, "2025-02-28 23:59:59", ["monthly", :monthly]) + + ENV['TZ'] = 'Australia/Lord_Howe' # 30 minute shift + time = Time.new("2025-04-06 00:52:02") + + assert_previous_period_end_words(time, "2025-04-05 23:59:59", ["daily", :daily]) + assert_previous_period_end_words(time, "2025-04-05 23:59:59", ["weekly", :weekly]) + assert_previous_period_end_words(time, "2025-03-31 23:59:59", ["monthly", :monthly]) + + ENV['TZ'] = 'Asia/Gaza' # 1 hour shift on Saturday + time = Time.new("2025-04-12 00:52:02") + + assert_previous_period_end_words(time, "2025-04-11 23:59:59", ["daily", :daily]) + assert_previous_period_end_words(time, "2025-04-05 23:59:59", ["weekly", :weekly]) + assert_previous_period_end_words(time, "2025-03-31 23:59:59", ["monthly", :monthly]) + + ENV['TZ'] = tz + end + + def test_previous_period_end_dst_end + tz = ENV['TZ'] + ENV['TZ'] = 'America/New_York' # 1 hour shift + time = Time.parse("2025-11-02 13:52:02") + + assert_previous_period_end_words(time, "2025-11-01 23:59:59", ["daily", :daily]) + assert_previous_period_end_words(time, "2025-11-01 23:59:59", ["weekly", :weekly]) + assert_previous_period_end_words(time, "2025-10-31 23:59:59", ["monthly", :monthly]) + + ENV['TZ'] = 'Antarctica/Troll' # 2 hour shift + time = Time.parse("2025-10-26 13:52:02") + + assert_previous_period_end_words(time, "2025-10-25 23:59:59", ["daily", :daily]) + assert_previous_period_end_words(time, "2025-10-25 23:59:59", ["weekly", :weekly]) + assert_previous_period_end_words(time, "2025-09-30 23:59:59", ["monthly", :monthly]) + + ENV['TZ'] = 'Australia/Lord_Howe' # 30 minute shift + time = Time.parse("2025-10-05 13:52:02") + + assert_previous_period_end_words(time, "2025-10-04 23:59:59", ["daily", :daily]) + assert_previous_period_end_words(time, "2025-10-04 23:59:59", ["weekly", :weekly]) + assert_previous_period_end_words(time, "2025-09-30 23:59:59", ["monthly", :monthly]) + + + ENV['TZ'] = 'Asia/Gaza' # 1 hour shift on Saturday + time = Time.new("2025-10-25 13:52:02") + + assert_previous_period_end_words(time, "2025-10-24 23:59:59", ["daily", :daily]) + assert_previous_period_end_words(time, "2025-10-18 23:59:59", ["weekly", :weekly]) + assert_previous_period_end_words(time, "2025-09-30 23:59:59", ["monthly", :monthly]) + ENV['TZ'] = tz + end + def test_previous_period_end_extreme_cases # First day of Month and Saturday time = Time.parse("2018-07-01 00:00:00") From cc46a5067105ef5dad02859dc9adf61ecdd9c276 Mon Sep 17 00:00:00 2001 From: John W Higgins Date: Fri, 10 Jan 2025 14:04:32 -0800 Subject: [PATCH 2/2] Simplify the next/previous rotate calculations --- lib/logger/period.rb | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/lib/logger/period.rb b/lib/logger/period.rb index a0359de..b68d64a 100644 --- a/lib/logger/period.rb +++ b/lib/logger/period.rb @@ -1,47 +1,43 @@ # frozen_string_literal: true +require 'date' + class Logger module Period module_function - SiD = 24 * 60 * 60 - def next_rotate_time(now, shift_age) case shift_age when 'daily', :daily - t = Time.mktime(now.year, now.month, now.mday) + SiD + (now.to_date + 1).to_time when 'weekly', :weekly - t = Time.mktime(now.year, now.month, now.mday) + SiD * (7 - now.wday) + (now.to_date + 7 - now.wday).to_time when 'monthly', :monthly - t = Time.mktime(now.year, now.month, 1) + SiD * 32 - return Time.mktime(t.year, t.month, 1) + if now.month == 12 + Time.mktime(now.year + 1, 1, 1) + else + Time.mktime(now.year, now.month + 1, 1) + end when 'now', 'everytime', :now, :everytime - return now + now else raise ArgumentError, "invalid :shift_age #{shift_age.inspect}, should be daily, weekly, monthly, or everytime" end - if t.hour.nonzero? or t.min.nonzero? or t.sec.nonzero? - hour = t.hour - t = Time.mktime(t.year, t.month, t.mday) - t += SiD if hour > 12 - end - t end def previous_period_end(now, shift_age) case shift_age when 'daily', :daily - t = Time.mktime(now.year, now.month, now.mday) - SiD / 2 + now.to_date.to_time - 1 when 'weekly', :weekly - t = Time.mktime(now.year, now.month, now.mday) - (SiD * now.wday + SiD / 2) + (now.to_date - now.wday).to_time - 1 when 'monthly', :monthly - t = Time.mktime(now.year, now.month, 1) - SiD / 2 + Time.mktime(now.year, now.month, 1) - 1 when 'now', 'everytime', :now, :everytime - return now + now else raise ArgumentError, "invalid :shift_age #{shift_age.inspect}, should be daily, weekly, monthly, or everytime" end - Time.mktime(t.year, t.month, t.mday, 23, 59, 59) end end end