Skip to content

Commit 827bd17

Browse files
authored
Fix year-over-year comparisons for leap years (#4940)
When comparing dates year-over-year, the previous logic always shifted by 365 days, which caused issues with leap years. This means when comparing e.g. last 7 days vs a year ago in 2025, you would compare Jan 6th and Jan 7th. The new logic uses `Date.shift` while ensuring that the compared date range stays the same length as original date range. This can cause some oddities around Feb 29th where dates can get offset by 1 at the end of the range (see tests) Helpscout ref: https://secure.helpscout.net/conversation/2809180400/22224?viewId=6980900
1 parent 27fac66 commit 827bd17

File tree

3 files changed

+40
-2
lines changed

3 files changed

+40
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ All notable changes to this project will be documented in this file.
2323
- Fix returning filter suggestions for multiple custom property values in the dashboard Filter modal
2424
- Fix typo on login screen
2525
- Fix Direct / None details modal not opening
26+
- Fix year over year comparisons being offset by a day for leap years
2627

2728
## v2.1.4 - 2024-10-08
2829

lib/plausible/stats/comparisons.ex

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,9 @@ defmodule Plausible.Stats.Comparisons do
9999
defp get_comparison_date_range(source_query, %{mode: "year_over_year"} = options) do
100100
source_date_range = Query.date_range(source_query, trim_trailing: true)
101101

102-
start_date = Date.add(source_date_range.first, -365)
103-
end_date = source_date_range.last |> Date.add(-365)
102+
start_date = source_date_range.first |> Date.shift(year: -1)
103+
diff_in_days = Date.diff(source_date_range.last, source_date_range.first)
104+
end_date = Date.add(start_date, diff_in_days)
104105

105106
Date.range(start_date, end_date)
106107
|> maybe_match_day_of_week(source_date_range, options)

test/plausible/stats/comparisons_test.exs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,36 @@ defmodule Plausible.Stats.ComparisonsTest do
161161
end
162162
end
163163

164+
describe "year_over_year, exact dates behavior with leap years" do
165+
test "start of the year matching", %{site: site} do
166+
query = Query.from(site, %{"period" => "7d", "date" => "2021-01-05"})
167+
comparison_query = Comparisons.get_comparison_query(query, %{mode: "year_over_year"})
168+
169+
assert comparison_query.utc_time_range.first == ~U[2019-12-30 00:00:00Z]
170+
assert comparison_query.utc_time_range.last == ~U[2020-01-05 23:59:59Z]
171+
assert date_range_length(comparison_query) == 7
172+
end
173+
174+
test "leap day matching", %{site: site} do
175+
query = Query.from(site, %{"period" => "7d", "date" => "2021-03-03"})
176+
comparison_query = Comparisons.get_comparison_query(query, %{mode: "year_over_year"})
177+
178+
assert comparison_query.utc_time_range.first == ~U[2020-02-25 00:00:00Z]
179+
# :TRICKY: Since dates of the two months don't match precisely we cut off earlier
180+
assert comparison_query.utc_time_range.last == ~U[2020-03-02 23:59:59Z]
181+
assert date_range_length(comparison_query) == 7
182+
end
183+
184+
test "end of the year matching", %{site: site} do
185+
query = Query.from(site, %{"period" => "7d", "date" => "2021-11-25"})
186+
comparison_query = Comparisons.get_comparison_query(query, %{mode: "year_over_year"})
187+
188+
assert comparison_query.utc_time_range.first == ~U[2020-11-19 00:00:00Z]
189+
assert comparison_query.utc_time_range.last == ~U[2020-11-25 23:59:59Z]
190+
assert date_range_length(comparison_query) == 7
191+
end
192+
end
193+
164194
describe "with period set to year to date" do
165195
test "shifts back by the same number of days when mode is previous_period", %{site: site} do
166196
query =
@@ -369,4 +399,10 @@ defmodule Plausible.Stats.ComparisonsTest do
369399

370400
query
371401
end
402+
403+
def date_range_length(query) do
404+
query
405+
|> Query.date_range()
406+
|> Enum.count()
407+
end
372408
end

0 commit comments

Comments
 (0)