From 57dca25b74cef396385c0e77e92d644583c23a31 Mon Sep 17 00:00:00 2001 From: John Bachir Date: Mon, 15 Apr 2024 16:59:18 -0400 Subject: [PATCH] exact date spans >= AND <= is bad >= AND < is good https://wiki.postgresql.org/wiki/Don't_Do_This#Don.27t_use_BETWEEN_.28especially_with_timestamps.29 --- .../core_ext/date/calculations.rb | 4 +++ .../core_ext/date_and_time/calculations.rb | 28 +++++++++++++++---- .../core_ext/date_time/calculations.rb | 4 +++ .../core_ext/time/calculations.rb | 9 ++++++ activesupport/test/core_ext/date_ext_test.rb | 18 ++++++------ activesupport/test/core_ext/time_ext_test.rb | 16 +++++------ 6 files changed, 57 insertions(+), 22 deletions(-) diff --git a/activesupport/lib/active_support/core_ext/date/calculations.rb b/activesupport/lib/active_support/core_ext/date/calculations.rb index 7dea469756255..c8d9a8a741b1b 100644 --- a/activesupport/lib/active_support/core_ext/date/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date/calculations.rb @@ -87,6 +87,10 @@ def end_of_day end alias :at_end_of_day :end_of_day + def end_of_day_threshold + beginning_of_day.advance(days: 1) + end + def plus_with_duration(other) # :nodoc: if ActiveSupport::Duration === other other.since(self) diff --git a/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb b/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb index e713cee1fed96..e0e245b9307a3 100644 --- a/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb @@ -157,6 +157,11 @@ def end_of_quarter end alias :at_end_of_quarter :end_of_quarter + def end_of_quarter_threshold + last_quarter_month = month + (12 - month) % 3 + beginning_of_month.change(month: last_quarter_month).end_of_month_threshold + end + # Returns the quarter for a date/time. # # Date.new(2010, 1, 31).quarter # => 1 @@ -285,6 +290,10 @@ def end_of_week(start_day = Date.beginning_of_week) end alias :at_end_of_week :end_of_week + def end_of_week_threshold(start_day = Date.beginning_of_week) + days_since(6 - days_to_week_start(start_day)).advance(days: 1).beginning_of_day + end + # Returns Sunday of this week assuming that week starts on Monday. # +DateTime+ objects have their time set to 23:59:59. def sunday @@ -299,6 +308,11 @@ def end_of_month end alias :at_end_of_month :end_of_month + def end_of_month_threshold + last_day = ::Time.days_in_month(month, year) + days_since(last_day - day).advance(days: 1).beginning_of_day + end + # Returns a new date/time representing the end of the year. # DateTime objects will have a time set to 23:59:59. def end_of_year @@ -306,30 +320,34 @@ def end_of_year end alias :at_end_of_year :end_of_year + def end_of_year_threshold + change(month: 12).end_of_month_threshold + end + # Returns a Range representing the whole day of the current date/time. def all_day - beginning_of_day..end_of_day + beginning_of_day...end_of_day_threshold end # Returns a Range representing the whole week of the current date/time. # Week starts on start_day, default is Date.beginning_of_week or config.beginning_of_week when set. def all_week(start_day = Date.beginning_of_week) - beginning_of_week(start_day)..end_of_week(start_day) + beginning_of_week(start_day)...end_of_week_threshold(start_day) end # Returns a Range representing the whole month of the current date/time. def all_month - beginning_of_month..end_of_month + beginning_of_month...end_of_month_threshold end # Returns a Range representing the whole quarter of the current date/time. def all_quarter - beginning_of_quarter..end_of_quarter + beginning_of_quarter...end_of_quarter_threshold end # Returns a Range representing the whole year of the current date/time. def all_year - beginning_of_year..end_of_year + beginning_of_year...end_of_year_threshold end # Returns a new date/time representing the next occurrence of the specified day of week. diff --git a/activesupport/lib/active_support/core_ext/date_time/calculations.rb b/activesupport/lib/active_support/core_ext/date_time/calculations.rb index a63506809a98e..feee937defc63 100644 --- a/activesupport/lib/active_support/core_ext/date_time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date_time/calculations.rb @@ -142,6 +142,10 @@ def end_of_day end alias :at_end_of_day :end_of_day + def end_of_day_threshold + change(hour: 0, min: 0, sec: 0, usec: 0).advance(days: 1) + end + # Returns a new DateTime representing the start of the hour (hh:00:00). def beginning_of_hour change(min: 0) diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb index 67973f7f43ace..e13c45ed4c36f 100644 --- a/activesupport/lib/active_support/core_ext/time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/time/calculations.rb @@ -251,6 +251,15 @@ def end_of_day end alias :at_end_of_day :end_of_day + def end_of_day_threshold + change( + hour: 0, + min: 0, + sec: 0, + usec: 0 + ).advance(days: 1) + end + # Returns a new Time representing the start of the hour (x:00) def beginning_of_hour change(min: 0) diff --git a/activesupport/test/core_ext/date_ext_test.rb b/activesupport/test/core_ext/date_ext_test.rb index cfd24d7bd1c19..d2a25a10857b8 100644 --- a/activesupport/test/core_ext/date_ext_test.rb +++ b/activesupport/test/core_ext/date_ext_test.rb @@ -284,8 +284,8 @@ def test_end_of_day_when_zone_is_set def test_all_day beginning_of_day = Time.local(2011, 6, 7, 0, 0, 0) - end_of_day = Time.local(2011, 6, 7, 23, 59, 59, Rational(999999999, 1000)) - assert_equal beginning_of_day..end_of_day, Date.new(2011, 6, 7).all_day + beginning_of_next_day = Time.local(2011, 6, 8, 0, 0, 0) + assert_equal beginning_of_day...beginning_of_next_day, Date.new(2011, 6, 7).all_day end def test_all_day_when_zone_is_set @@ -293,27 +293,27 @@ def test_all_day_when_zone_is_set with_env_tz "UTC" do with_tz_default zone do beginning_of_day = zone.local(2011, 6, 7, 0, 0, 0) - end_of_day = zone.local(2011, 6, 7, 23, 59, 59, Rational(999999999, 1000)) - assert_equal beginning_of_day..end_of_day, Date.new(2011, 6, 7).all_day + beginning_of_next_day = zone.local(2011, 6, 8, 0, 0, 0) + assert_equal beginning_of_day...beginning_of_next_day, Date.new(2011, 6, 7).all_day end end end def test_all_week - assert_equal Date.new(2011, 6, 6)..Date.new(2011, 6, 12), Date.new(2011, 6, 7).all_week - assert_equal Date.new(2011, 6, 5)..Date.new(2011, 6, 11), Date.new(2011, 6, 7).all_week(:sunday) + assert_equal Date.new(2011, 6, 6)...Date.new(2011, 6, 13), Date.new(2011, 6, 7).all_week + assert_equal Date.new(2011, 6, 5)...Date.new(2011, 6, 12), Date.new(2011, 6, 7).all_week(:sunday) end def test_all_month - assert_equal Date.new(2011, 6, 1)..Date.new(2011, 6, 30), Date.new(2011, 6, 7).all_month + assert_equal Date.new(2011, 6, 1)...Date.new(2011, 7, 1), Date.new(2011, 6, 7).all_month end def test_all_quarter - assert_equal Date.new(2011, 4, 1)..Date.new(2011, 6, 30), Date.new(2011, 6, 7).all_quarter + assert_equal Date.new(2011, 4, 1)...Date.new(2011, 7, 1), Date.new(2011, 6, 7).all_quarter end def test_all_year - assert_equal Date.new(2011, 1, 1)..Date.new(2011, 12, 31), Date.new(2011, 6, 7).all_year + assert_equal Date.new(2011, 1, 1)...Date.new(2012, 1, 1), Date.new(2011, 6, 7).all_year end def test_xmlschema diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb index 817a11874bb29..effab4b802eb7 100644 --- a/activesupport/test/core_ext/time_ext_test.rb +++ b/activesupport/test/core_ext/time_ext_test.rb @@ -1252,32 +1252,32 @@ def test_case_equality end def test_all_day - assert_equal Time.local(2011, 6, 7, 0, 0, 0)..Time.local(2011, 6, 7, 23, 59, 59, Rational(999999999, 1000)), Time.local(2011, 6, 7, 10, 10, 10).all_day + assert_equal Time.local(2011, 6, 7, 0, 0, 0)...Time.local(2011, 6, 8, 0, 0, 0), Time.local(2011, 6, 7, 10, 10, 10).all_day end def test_all_day_with_timezone beginning_of_day = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Hawaii"], Time.local(2011, 6, 7, 0, 0, 0)) - end_of_day = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Hawaii"], Time.local(2011, 6, 7, 23, 59, 59, Rational(999999999, 1000))) + beginning_of_next_day = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Hawaii"], Time.local(2011, 6, 8, 0, 0, 0)) assert_equal beginning_of_day, ActiveSupport::TimeWithZone.new(Time.local(2011, 6, 7, 10, 10, 10), ActiveSupport::TimeZone["Hawaii"]).all_day.begin - assert_equal end_of_day, ActiveSupport::TimeWithZone.new(Time.local(2011, 6, 7, 10, 10, 10), ActiveSupport::TimeZone["Hawaii"]).all_day.end + assert_equal beginning_of_next_day, ActiveSupport::TimeWithZone.new(Time.local(2011, 6, 7, 10, 10, 10), ActiveSupport::TimeZone["Hawaii"]).all_day.end end def test_all_week - assert_equal Time.local(2011, 6, 6, 0, 0, 0)..Time.local(2011, 6, 12, 23, 59, 59, Rational(999999999, 1000)), Time.local(2011, 6, 7, 10, 10, 10).all_week - assert_equal Time.local(2011, 6, 5, 0, 0, 0)..Time.local(2011, 6, 11, 23, 59, 59, Rational(999999999, 1000)), Time.local(2011, 6, 7, 10, 10, 10).all_week(:sunday) + assert_equal Time.local(2011, 6, 6, 0, 0, 0)...Time.local(2011, 6, 13, 0, 0, 0), Time.local(2011, 6, 7, 10, 10, 10).all_week + assert_equal Time.local(2011, 6, 5, 0, 0, 0)...Time.local(2011, 6, 12, 0, 0, 0), Time.local(2011, 6, 7, 10, 10, 10).all_week(:sunday) end def test_all_month - assert_equal Time.local(2011, 6, 1, 0, 0, 0)..Time.local(2011, 6, 30, 23, 59, 59, Rational(999999999, 1000)), Time.local(2011, 6, 7, 10, 10, 10).all_month + assert_equal Time.local(2011, 6, 1, 0, 0, 0)...Time.local(2011, 7, 1, 0, 0, 0), Time.local(2011, 6, 7, 10, 10, 10).all_month end def test_all_quarter - assert_equal Time.local(2011, 4, 1, 0, 0, 0)..Time.local(2011, 6, 30, 23, 59, 59, Rational(999999999, 1000)), Time.local(2011, 6, 7, 10, 10, 10).all_quarter + assert_equal Time.local(2011, 4, 1, 0, 0, 0)...Time.local(2011, 7, 1, 0, 0, 0), Time.local(2011, 6, 7, 10, 10, 10).all_quarter end def test_all_year - assert_equal Time.local(2011, 1, 1, 0, 0, 0)..Time.local(2011, 12, 31, 23, 59, 59, Rational(999999999, 1000)), Time.local(2011, 6, 7, 10, 10, 10).all_year + assert_equal Time.local(2011, 1, 1, 0, 0, 0)...Time.local(2012, 1, 1, 0, 0, 0), Time.local(2011, 6, 7, 10, 10, 10).all_year end def test_rfc3339_parse