defmodule Plausible.Stats.DateTimeRangeTest do use Plausible.DataCase, async: true alias Plausible.Stats.DateTimeRange describe "new!/2" do test "creates a range when datetimes are in correct order" do first = DateTime.new!(~D[2023-01-01], ~T[10:00:00], "UTC") last = DateTime.new!(~D[2023-01-02], ~T[10:00:00], "UTC") range = DateTimeRange.new!(first, last) assert range.first == first assert range.last == last end test "swaps datetimes when in reverse order" do first = DateTime.new!(~D[2023-02-01], ~T[10:00:00], "UTC") last = DateTime.new!(~D[2023-01-01], ~T[10:00:00], "UTC") range = DateTimeRange.new!(first, last) assert range.first == last assert range.last == first end test "truncates microseconds" do first = DateTime.new!(~D[2023-01-01], ~T[10:00:00.123], "UTC") last = DateTime.new!(~D[2023-01-02], ~T[10:00:00.456], "UTC") range = DateTimeRange.new!(first, last) assert range.first == DateTime.truncate(first, :second) assert range.last == DateTime.truncate(last, :second) end end describe "new!/3 with dates and timezone" do test "creates range with start and end of day" do first_date = ~D[2023-01-01] last_date = ~D[2023-01-02] range = DateTimeRange.new!(first_date, last_date, "UTC") assert range.first == DateTime.new!(first_date, ~T[00:00:00], "UTC") assert range.last == DateTime.new!(last_date, ~T[23:59:59], "UTC") end test "creates a range from a mix of date and datetime" do first = DateTime.new!(~D[2023-02-15], ~T[12:30:00], "UTC") last = ~D[2023-02-18] range = DateTimeRange.new!(first, last, "UTC") assert range.first == first assert range.last == DateTime.new!(last, ~T[23:59:59], "UTC") end test "creates a range from a mix of datetime and date" do first = ~D[2023-02-15] last = DateTime.new!(~D[2023-02-18], ~T[12:30:00], "UTC") range = DateTimeRange.new!(first, last, "UTC") assert range.first == DateTime.new!(first, ~T[00:00:00], "UTC") assert range.last == last end test "handles timezone gaps (spring forward)" do # https://stackoverflow.com/questions/18489927/a-day-without-midnight range = DateTimeRange.new!(~D[2020-03-29], ~D[2020-03-29], "Asia/Beirut") assert range.first == DateTime.new!(~D[2020-03-29], ~T[01:00:00], "Asia/Beirut") assert range.last == DateTime.new!(~D[2020-03-29], ~T[23:59:59], "Asia/Beirut") end end describe "to_timezone/2" do test "converts range to specified timezone" do first = DateTime.new!(~D[2023-01-01], ~T[10:00:00], "UTC") last = DateTime.new!(~D[2023-01-02], ~T[10:00:00], "UTC") range = DateTimeRange.new!(first, last) converted = DateTimeRange.to_timezone(range, "America/New_York") assert converted.first == DateTime.shift_zone!(first, "America/New_York") assert converted.last == DateTime.shift_zone!(last, "America/New_York") end end describe "to_date_range/2" do test "converts datetime range to date range" do first = DateTime.new!(~D[2023-01-01], ~T[10:00:00], "UTC") last = DateTime.new!(~D[2023-01-05], ~T[10:00:00], "UTC") range = DateTimeRange.new!(first, last) date_range = DateTimeRange.to_date_range(range, "UTC") assert date_range.first == ~D[2023-01-01] assert date_range.last == ~D[2023-01-05] end test "handles timezone conversions that cross date boundaries" do first = DateTime.new!(~D[2023-01-01], ~T[23:00:00], "UTC") last = DateTime.new!(~D[2023-01-02], ~T[04:59:59], "UTC") range = DateTimeRange.new!(first, last) date_range = DateTimeRange.to_date_range(range, "America/New_York") assert date_range.first == ~D[2023-01-01] assert date_range.last == ~D[2023-01-01] end end end