diff --git a/app/models/concerns/time_range_filterable.rb b/app/models/concerns/time_range_filterable.rb index d10d962bc..f577f64a3 100644 --- a/app/models/concerns/time_range_filterable.rb +++ b/app/models/concerns/time_range_filterable.rb @@ -4,7 +4,7 @@ module TimeRangeFilterable RANGES = { today: { human_name: "Today", - calculate: -> { Time.current.beginning_of_day..Time.current.end_of_day } + calculate: -> { Time.current.beginning_of_day...Time.current.beginning_of_day.next_day } }, yesterday: { human_name: "Yesterday", @@ -73,8 +73,11 @@ def filter_by_time_range(interval, from = nil, to = nil) interval = interval&.to_sym if interval == :custom from_time = from.present? ? Time.zone.parse(from).beginning_of_day.to_i : 0 - to_time = to.present? ? Time.zone.parse(to).end_of_day.to_i : 253402300799 - where(time: from_time..to_time) + if to.present? + where(time: from_time...Time.zone.parse(to).beginning_of_day.next_day.to_i) + else + where(time: from_time..253402300799) + end elsif RANGES.key?(interval) public_send(interval) else diff --git a/app/models/heartbeat.rb b/app/models/heartbeat.rb index d7e2aaf10..23f0ef18e 100644 --- a/app/models/heartbeat.rb +++ b/app/models/heartbeat.rb @@ -11,7 +11,6 @@ class Heartbeat < ApplicationRecord # Default scope to exclude deleted records default_scope { where(deleted_at: nil) } - scope :today, -> { where(time: Time.current.beginning_of_day.to_i..Time.current.end_of_day.to_i) } scope :recent, -> { where("time > ?", 24.hours.ago.to_i) } scope :with_deleted, -> { unscope(where: :deleted_at) } scope :only_deleted, -> { with_deleted.where.not(deleted_at: nil) } diff --git a/test/models/heartbeat_test.rb b/test/models/heartbeat_test.rb index 1de8d0224..65317ba06 100644 --- a/test/models/heartbeat_test.rb +++ b/test/models/heartbeat_test.rb @@ -16,6 +16,26 @@ class HeartbeatTest < ActiveSupport::TestCase ActiveJob::Base.queue_adapter = @original_queue_adapter end + test "today and custom days include the final fractional second across DST" do + Time.use_zone("Europe/London") do + [ "2026-03-29", "2026-10-25" ].each do |date| + travel_to Time.zone.parse("#{date} 12:00:00") do + user = create(:user) + start = Time.current.beginning_of_day + finish = start.next_day + rows = [ start.to_f - 0.5, finish.to_f - 1.5, finish.to_f - 0.5, finish.to_f ].map do |time| + create(:heartbeat, user:, time:, source_type: :test_entry) + end + [ user.heartbeats.today, user.heartbeats.filter_by_time_range("custom", date, date) ].each do |scope| + assert_equal rows[1..2].map(&:id), scope.order(:time, :id).pluck(:id) + assert_equal 1, scope.duration_seconds + end + assert_includes [ 23.hours, 25.hours ], finish - start + end + end + end + end + test "soft delete hides record from default scope and restore brings it back" do user = create(:user) heartbeat = create(:heartbeat, user: user,