Skip to content

Commit

Permalink
Fix year-over-year comparisons for leap years
Browse files Browse the repository at this point in the history
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
  • Loading branch information
macobo committed Jan 6, 2025
1 parent c29b93f commit 6bf7b12
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 2 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ All notable changes to this project will be documented in this file.
- Fix returning filter suggestions for multiple custom property values in the dashboard Filter modal
- Fix typo on login screen
- Fix Direct / None details modal not opening
- Fix year over year comparisons being offset by a day for leap years

## v2.1.4 - 2024-10-08

Expand Down
5 changes: 3 additions & 2 deletions lib/plausible/stats/comparisons.ex
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,9 @@ defmodule Plausible.Stats.Comparisons do
defp get_comparison_date_range(source_query, %{mode: "year_over_year"} = options) do
source_date_range = Query.date_range(source_query, trim_trailing: true)

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

Date.range(start_date, end_date)
|> maybe_match_day_of_week(source_date_range, options)
Expand Down
36 changes: 36 additions & 0 deletions test/plausible/stats/comparisons_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,36 @@ defmodule Plausible.Stats.ComparisonsTest do
end
end

describe "year_over_year, exact dates behavior with leap years" do
test "start of the year matching", %{site: site} do
query = Query.from(site, %{"period" => "7d", "date" => "2021-01-05"})
comparison_query = Comparisons.get_comparison_query(query, %{mode: "year_over_year"})

assert comparison_query.utc_time_range.first == ~U[2019-12-30 00:00:00Z]
assert comparison_query.utc_time_range.last == ~U[2020-01-05 23:59:59Z]
assert date_range_length(comparison_query) == 7
end

test "leap day matching", %{site: site} do
query = Query.from(site, %{"period" => "7d", "date" => "2021-03-03"})
comparison_query = Comparisons.get_comparison_query(query, %{mode: "year_over_year"})

assert comparison_query.utc_time_range.first == ~U[2020-02-25 00:00:00Z]
# :TRICKY: Since dates of the two months don't match precisely we cut off earlier
assert comparison_query.utc_time_range.last == ~U[2020-03-02 23:59:59Z]
assert date_range_length(comparison_query) == 7
end

test "end of the year matching", %{site: site} do
query = Query.from(site, %{"period" => "7d", "date" => "2021-11-25"})
comparison_query = Comparisons.get_comparison_query(query, %{mode: "year_over_year"})

assert comparison_query.utc_time_range.first == ~U[2020-11-19 00:00:00Z]
assert comparison_query.utc_time_range.last == ~U[2020-11-25 23:59:59Z]
assert date_range_length(comparison_query) == 7
end
end

describe "with period set to year to date" do
test "shifts back by the same number of days when mode is previous_period", %{site: site} do
query =
Expand Down Expand Up @@ -369,4 +399,10 @@ defmodule Plausible.Stats.ComparisonsTest do

query
end

def date_range_length(query) do
query
|> Query.date_range()
|> Enum.count()
end
end

0 comments on commit 6bf7b12

Please sign in to comment.