Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
b8bd803
Replace usages of `Timex.to_unix` with native API
zoldar Sep 9, 2025
f12d185
Wrap call to `Timex.is_valid_timezone?`
zoldar Sep 9, 2025
74e514f
Wrap calls to `Timex.today(tz)`
zoldar Sep 9, 2025
a0cff3f
Replace `Timex.today()` with `Date.utc_today()`
zoldar Sep 9, 2025
5e3254d
Replace `Timex.now()` with `DateTime.utc_now()`
zoldar Sep 9, 2025
b215c34
Replace `Timex.compare` with `Date.compare`
zoldar Sep 9, 2025
1c8c9f0
Wrap `Timex.diff` calls
zoldar Sep 9, 2025
aa09a69
Replace `Timex.Timezone.convert` with `DateTime.shift_zone!`
zoldar Sep 9, 2025
5459fbf
Wrap `Timex.parse!`
zoldar Sep 9, 2025
5020a4b
Replace `Timex.to_date` with native API calls
zoldar Sep 9, 2025
488a61c
Replace `Timex.beginning|end_of...` with native API calls for Date
zoldar Sep 9, 2025
89993bc
Wrap `Timex.beginning|end_of...` for DateTimes and Dates for years
zoldar Sep 9, 2025
e5a9827
Replace `Timex.format(!)` with native API calls
zoldar Sep 9, 2025
98c7fd7
Replace `Timex.to_naive_datetime` with native API calls
zoldar Sep 9, 2025
34254dd
Wrap time humanizing routines using Timex
zoldar Sep 9, 2025
36828e3
Remove unnecessary `use Timex` instances
zoldar Sep 9, 2025
044c16b
Replace `Timex.shift` with native API calls
zoldar Sep 9, 2025
d978010
Make `QueryParser.parse_date` handle gaps and ambiguities gracefully
zoldar Sep 9, 2025
d8c3c99
Replace `Timex.now(tz)` with `DateTime.now!(tz)`
zoldar Sep 9, 2025
4ac9ae2
Use a more suitable Date function for comparison (h/t @aerosol)
zoldar Sep 10, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion lib/plausible/auth/totp.ex
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,9 @@ defmodule Plausible.Auth.TOTP do
|> Repo.one()

if datetime do
Timex.to_unix(datetime)
datetime
|> DateTime.from_naive!("Etc/UTC")
|> DateTime.to_unix()
end
end

Expand Down
2 changes: 1 addition & 1 deletion lib/plausible/billing/subscriptions.ex
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ defmodule Plausible.Billing.Subscriptions do

def expired?(%Subscription{next_bill_date: next_bill_date} = subscription) do
deleted? = Subscription.Status.deleted?(subscription)
expired? = Timex.compare(next_bill_date, Date.utc_today()) < 0
expired? = Date.before?(next_bill_date, Date.utc_today())

deleted? && expired?
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,6 @@ defmodule Plausible.DataMigration.PopulateEventSessionColumns do
end

defp format_duration(seconds) do
seconds
|> Timex.Duration.from_seconds()
|> Timex.format_duration(Timex.Format.Duration.Formatters.Humanized)
Plausible.Times.humanize_seconds(seconds)
end
end
2 changes: 1 addition & 1 deletion lib/plausible/exports.ex
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ defmodule Plausible.Exports do
["expiry-date=", expiry_date, ", rule-id=", _rule_id] =
String.split(x_amz_expiration, "\"", trim: true)

Timex.parse!(expiry_date, "{RFC1123}")
Plausible.Times.parse!(expiry_date, "{RFC1123}")
end

%{
Expand Down
2 changes: 0 additions & 2 deletions lib/plausible/google/api.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ defmodule Plausible.Google.API do
API to Google services.
"""

use Timex

alias Plausible.Google.HTTP
alias Plausible.Google.SearchConsole
alias Plausible.Stats.Query
Expand Down
2 changes: 1 addition & 1 deletion lib/plausible/google/ga4/http.ex
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ defmodule Plausible.Google.GA4.HTTP do
date =
case report["rows"] do
[%{"dimensionValues" => [%{"value" => date_str}]}] ->
Timex.parse!(date_str, "%Y%m%d", :strftime) |> NaiveDateTime.to_date()
Plausible.Times.parse!(date_str, "%Y%m%d", :strftime) |> NaiveDateTime.to_date()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image

The news outlet we need

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image


_ ->
nil
Expand Down
2 changes: 1 addition & 1 deletion lib/plausible/imported/google_analytics4.ex
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,7 @@ defmodule Plausible.Imported.GoogleAnalytics4 do

defp get_date(%{dimensions: %{"date" => date}}) do
date
|> Timex.parse!("%Y%m%d", :strftime)
|> Plausible.Times.parse!("%Y%m%d", :strftime)
|> NaiveDateTime.to_date()
end

Expand Down
2 changes: 1 addition & 1 deletion lib/plausible/site.ex
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ defmodule Plausible.Site do
defp validate_timezone(changeset) do
tz = get_field(changeset, :timezone)

if Timex.is_valid_timezone?(tz) do
if Plausible.Timezones.valid?(tz) do
changeset
else
add_error(changeset, :timezone, "is invalid")
Expand Down
12 changes: 8 additions & 4 deletions lib/plausible/stats/filters/query_parser.ex
Original file line number Diff line number Diff line change
Expand Up @@ -197,8 +197,12 @@ defmodule Plausible.Stats.Filters.QueryParser do
end

defp parse_date(site, date_string, _date, _now) when is_binary(date_string) do
case Date.from_iso8601(date_string) do
{:ok, date} -> {:ok, date, DateTime.new!(date, ~T[00:00:00], site.timezone)}
with {:ok, date} <- Date.from_iso8601(date_string),
{:ok, datetime} <- DateTime.new(date, ~T[00:00:00], site.timezone) do
{:ok, date, datetime}
else
{:gap, just_before, _just_after} -> just_before
{:ambiguous, first_datetime, _second_datetime} -> first_datetime
_ -> {:error, "Invalid date '#{date_string}'."}
end
end
Expand Down Expand Up @@ -231,8 +235,8 @@ defmodule Plausible.Stats.Filters.QueryParser do
end

defp parse_time_range(site, "year", date, _now) do
last = date |> Timex.end_of_year()
first = last |> Timex.beginning_of_year()
last = date |> Plausible.Times.end_of_year()
first = last |> Plausible.Times.beginning_of_year()
{:ok, DateTimeRange.new!(first, last, site.timezone)}
end

Expand Down
6 changes: 3 additions & 3 deletions lib/plausible/stats/interval.ex
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ defmodule Plausible.Stats.Interval do
"""
def default_for_date_range(%DateTimeRange{first: first, last: last}) do
cond do
Timex.diff(last, first, :months) > 0 ->
Plausible.Times.diff(last, first, :month) > 0 ->
"month"

DateTime.diff(last, first, :day) > 0 ->
Expand Down Expand Up @@ -75,15 +75,15 @@ defmodule Plausible.Stats.Interval do
table =
with %Date{} = from <- Keyword.get(opts, :from),
%Date{} = to <- Keyword.get(opts, :to),
true <- abs(Timex.diff(from, to, :months)) > 12 do
true <- abs(Plausible.Times.diff(from, to, :month)) > 12 do
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wtf, DateTime.diff has no :month? :(

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, unfortunately day is the highest possible unit when diffing.

Map.replace(@valid_by_period, "custom", ["week", "month"])
else
_ ->
@valid_by_period
end

with %Date{} = stats_start <- Plausible.Sites.stats_start_date(site),
true <- abs(Timex.diff(Date.utc_today(), stats_start, :months)) > 12 do
true <- abs(Plausible.Times.diff(Date.utc_today(), stats_start, :month)) > 12 do
Map.replace(table, "all", ["week", "month"])
else
_ ->
Expand Down
8 changes: 4 additions & 4 deletions lib/plausible/stats/legacy/legacy_query_builder.ex
Original file line number Diff line number Diff line change
Expand Up @@ -157,9 +157,9 @@ defmodule Plausible.Stats.Legacy.QueryBuilder do
defp put_input_date_range(query, site, %{"period" => "year"} = params) do
end_date =
parse_single_date(query, params)
|> Timex.end_of_year()
|> Plausible.Times.end_of_year()

start_date = Timex.beginning_of_year(end_date)
start_date = Plausible.Times.beginning_of_year(end_date)

datetime_range =
DateTimeRange.new!(start_date, end_date, site.timezone)
Expand Down Expand Up @@ -304,12 +304,12 @@ defmodule Plausible.Stats.Legacy.QueryBuilder do
end

defp today(query) do
query.now |> Timex.to_date()
query.now |> DateTime.to_date()
end

defp parse_single_date(query, params) do
case params["date"] do
"today" -> query.now |> Timex.to_date()
"today" -> query.now |> DateTime.to_date()
date when is_binary(date) -> Date.from_iso8601!(date)
_ -> today(query)
end
Expand Down
2 changes: 1 addition & 1 deletion lib/plausible/stats/query_optimizer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ defmodule Plausible.Stats.QueryOptimizer do
cond do
DateTime.diff(last, first, :hour) <= 48 -> "time:hour"
DateTime.diff(last, first, :day) <= 40 -> "time:day"
Timex.diff(last, first, :weeks) <= 52 -> "time:week"
Plausible.Times.diff(last, first, :week) <= 52 -> "time:week"
true -> "time:month"
end
end
Expand Down
8 changes: 4 additions & 4 deletions lib/plausible/stats/time.ex
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,10 @@ defmodule Plausible.Stats.Time do
date_range = Query.date_range(query)

n_buckets =
Timex.diff(
Plausible.Times.diff(
date_range.last,
Date.beginning_of_month(date_range.first),
:months
:month
)

Enum.map(n_buckets..0//-1, fn shift ->
Expand All @@ -71,10 +71,10 @@ defmodule Plausible.Stats.Time do
date_range = Query.date_range(query)

n_buckets =
Timex.diff(
Plausible.Times.diff(
date_range.last,
Date.beginning_of_week(date_range.first),
:weeks
:week
)

Enum.map(0..n_buckets, fn shift ->
Expand Down
2 changes: 1 addition & 1 deletion lib/plausible/teams/billing.ex
Original file line number Diff line number Diff line change
Expand Up @@ -435,7 +435,7 @@ defmodule Plausible.Teams.Billing do
last_bill_date = team.subscription.last_bill_date

normalized_last_bill_date =
Date.shift(last_bill_date, month: Timex.diff(today, last_bill_date, :months))
Date.shift(last_bill_date, month: Plausible.Times.diff(today, last_bill_date, :month))

date_range =
case cycle do
Expand Down
76 changes: 76 additions & 0 deletions lib/plausible/times.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
defmodule Plausible.Times do
@moduledoc """
API for working with time wrapping around external libraries where necessary.
"""

@spec today(String.t()) :: Date.t()
def today(tz) do
tz
|> DateTime.now!()
|> DateTime.to_date()
end

@spec diff(Date.t() | DateTime.t(), Date.t() | DateTime.t(), :month | :week) :: integer()
def diff(a, b, unit) do
unit =
case unit do
:week -> :weeks
:month -> :months
end

Timex.diff(a, b, unit)
end

@spec parse!(String.t(), String.t(), :default | :strftime) :: DateTime.t() | NaiveDateTime.t()
def parse!(str, format, tokenizer \\ :default)

def parse!(str, "{RFC1123}" = format, :default) do
Timex.parse!(str, format)
end

def parse!(str, format, :strftime) do
Timex.parse!(str, format, :strftime)
end

@spec beginning_of_week(DateTime.t()) :: DateTime.t()
def beginning_of_week(dt) do
Timex.beginning_of_week(dt)
end

@spec beginning_of_month(DateTime.t()) :: DateTime.t()
def beginning_of_month(dt) do
Timex.beginning_of_month(dt)
end

@spec beginning_of_year(Date.t()) :: Date.t()
def beginning_of_year(d) do
Timex.beginning_of_year(d)
end

@spec end_of_week(DateTime.t()) :: DateTime.t()
def end_of_week(dt) do
Timex.end_of_week(dt)
end

@spec end_of_month(DateTime.t()) :: DateTime.t()
def end_of_month(dt) do
Timex.end_of_month(dt)
end

@spec end_of_year(Date.t()) :: Date.t()
def end_of_year(t) do
Timex.end_of_year(t)
end

@spec humanize(DateTime.t()) :: String.t()
def humanize(%DateTime{} = dt) do
Timex.Format.DateTime.Formatters.Relative.format!(dt, "{relative}")
end

@spec humanize_seconds(pos_integer()) :: String.t()
def humanize_seconds(seconds) do
seconds
|> Timex.Duration.from_seconds()
|> Timex.format_duration(Timex.Format.Duration.Formatters.Humanized)
end
end
9 changes: 9 additions & 0 deletions lib/plausible/timezones.ex
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
defmodule Plausible.Timezones do
@moduledoc """
API for working with timezones wrapping around external libraries where necessary.
"""

@spec valid?(String.t()) :: boolean()
def valid?(tz) do
Timex.is_valid_timezone?(tz)
end

@spec options(DateTime.t()) :: [{:key, String.t()}, {:value, String.t()}, {:offset, integer()}]
def options(now \\ DateTime.utc_now()) do
Tzdata.zone_list()
Expand Down
12 changes: 6 additions & 6 deletions lib/plausible_web/controllers/api/stats_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -148,15 +148,15 @@ defmodule PlausibleWeb.Api.StatsController do
labels
) do
date_range = Query.date_range(query)
build_intervals(labels, date_range, &Timex.beginning_of_week/1, &Timex.end_of_week/1)
build_intervals(labels, date_range, &Date.beginning_of_week/1, &Date.end_of_week/1)
end

defp build_full_intervals(
%Query{interval: "month"} = query,
labels
) do
date_range = Query.date_range(query)
build_intervals(labels, date_range, &Timex.beginning_of_month/1, &Timex.end_of_month/1)
build_intervals(labels, date_range, &Date.beginning_of_month/1, &Date.end_of_month/1)
end

defp build_full_intervals(_query, _labels) do
Expand Down Expand Up @@ -243,7 +243,7 @@ defmodule PlausibleWeb.Api.StatsController do
"day" ->
current_date =
DateTime.now!(site.timezone)
|> Timex.to_date()
|> DateTime.to_date()
|> Date.to_string()

Enum.find_index(dates, &(&1 == current_date))
Expand All @@ -253,7 +253,7 @@ defmodule PlausibleWeb.Api.StatsController do

current_date =
DateTime.now!(site.timezone)
|> Timex.to_date()
|> DateTime.to_date()
|> Time.date_or_weekstart(date_range)
|> Date.to_string()

Expand All @@ -262,8 +262,8 @@ defmodule PlausibleWeb.Api.StatsController do
"month" ->
current_date =
DateTime.now!(site.timezone)
|> Timex.to_date()
|> Timex.beginning_of_month()
|> DateTime.to_date()
|> Date.beginning_of_month()
|> Date.to_string()

Enum.find_index(dates, &(&1 == current_date))
Expand Down
5 changes: 1 addition & 4 deletions lib/plausible_web/email.ex
Original file line number Diff line number Diff line change
Expand Up @@ -487,10 +487,7 @@ defmodule PlausibleWeb.Email do
def export_success(user, site, expires_at) do
expires_in =
if expires_at do
Timex.Format.DateTime.Formatters.Relative.format!(
expires_at,
"{relative}"
)
Plausible.Times.humanize(expires_at)
end

download_url =
Expand Down
2 changes: 1 addition & 1 deletion lib/plausible_web/live/csv_export.ex
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ defmodule PlausibleWeb.Live.CSVExport do
<p :if={@export.expires_at} class="text-sm">
Note: this file will expire
<.hint message={@export.expires_at}>
{Timex.Format.DateTime.Formatters.Relative.format!(@export.expires_at, "{relative}")}.
{Plausible.Times.humanize(@export.expires_at)}.
</.hint>
</p>

Expand Down
2 changes: 1 addition & 1 deletion lib/plausible_web/live/csv_import.ex
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ defmodule PlausibleWeb.Live.CSVImport do
native_stats_start_date: native_stats_start_date
} = socket.assigns

cutoff_date = native_stats_start_date || Timex.today(site.timezone)
cutoff_date = native_stats_start_date || Plausible.Times.today(site.timezone)

case Imported.clamp_dates(occupied_ranges, cutoff_date, start_date, end_date) do
{:ok, start_date, end_date} -> Date.range(start_date, end_date)
Expand Down
4 changes: 2 additions & 2 deletions lib/workers/schedule_email_reports.ex
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ defmodule Plausible.Workers.ScheduleEmailReports do
def monday_9am(timezone) do
DateTime.now!(timezone)
|> DateTime.shift(week: 1)
|> Timex.beginning_of_week()
|> Plausible.Times.beginning_of_week()
|> DateTime.shift(hour: 9)
end

Expand Down Expand Up @@ -93,7 +93,7 @@ defmodule Plausible.Workers.ScheduleEmailReports do
def first_of_month_9am(timezone) do
DateTime.now!(timezone)
|> DateTime.shift(month: 1)
|> Timex.beginning_of_month()
|> Plausible.Times.beginning_of_month()
|> DateTime.shift(hour: 9)
end
end
7 changes: 6 additions & 1 deletion test/plausible/auth/totp_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,12 @@ defmodule Plausible.Auth.TOTPTest do

assert {:ok, user} = TOTP.validate_code(user, new_code, allow_reuse?: true)

assert_in_delta Timex.to_unix(user.totp_last_used_at), System.os_time(:second), 2
totp_last_used_at =
user.totp_last_used_at
|> DateTime.from_naive!("Etc/UTC")
|> DateTime.to_unix()

assert_in_delta totp_last_used_at, System.os_time(:second), 2
end

test "fails when trying to reuse the same code twice" do
Expand Down
Loading
Loading