514 lines
18 KiB
Elixir
514 lines
18 KiB
Elixir
defmodule ToweropsWeb.TimeHelpersTest do
|
|
use ExUnit.Case, async: true
|
|
|
|
alias ToweropsWeb.TimeHelpers
|
|
|
|
doctest TimeHelpers
|
|
|
|
describe "to_user_timezone/2" do
|
|
test "returns shifted datetime and abbreviation for known timezone" do
|
|
datetime = ~U[2026-01-15 14:34:00Z]
|
|
assert {:ok, shifted, "EST"} = TimeHelpers.to_user_timezone(datetime, "America/New_York")
|
|
# UTC-5 => 09:34
|
|
assert shifted.hour == 9
|
|
assert shifted.minute == 34
|
|
end
|
|
|
|
test "returns unchanged datetime for UTC" do
|
|
datetime = ~U[2026-01-15 14:34:00Z]
|
|
assert {:ok, shifted, "UTC"} = TimeHelpers.to_user_timezone(datetime, "UTC")
|
|
assert shifted.hour == 14
|
|
assert shifted.minute == 34
|
|
end
|
|
|
|
test "handles positive offset timezone (Asia/Tokyo, UTC+9)" do
|
|
datetime = ~U[2026-01-15 14:00:00Z]
|
|
assert {:ok, shifted, "JST"} = TimeHelpers.to_user_timezone(datetime, "Asia/Tokyo")
|
|
assert shifted.hour == 23
|
|
end
|
|
|
|
test "handles date rollover forward (Asia/Tokyo near midnight)" do
|
|
datetime = ~U[2026-01-15 20:00:00Z]
|
|
assert {:ok, shifted, "JST"} = TimeHelpers.to_user_timezone(datetime, "Asia/Tokyo")
|
|
# 20:00 + 9h = 05:00 next day
|
|
assert shifted.hour == 5
|
|
assert shifted.day == 16
|
|
end
|
|
|
|
test "handles date rollover backward (America/Los_Angeles early UTC)" do
|
|
datetime = ~U[2026-01-15 02:00:00Z]
|
|
assert {:ok, shifted, "PST"} = TimeHelpers.to_user_timezone(datetime, "America/Los_Angeles")
|
|
# 02:00 - 8h = 18:00 previous day
|
|
assert shifted.hour == 18
|
|
assert shifted.day == 14
|
|
end
|
|
|
|
test "returns error for unknown timezone" do
|
|
datetime = ~U[2026-01-15 14:34:00Z]
|
|
assert {:error, :unknown_timezone} = TimeHelpers.to_user_timezone(datetime, "Invalid/Zone")
|
|
end
|
|
|
|
test "handles all supported timezones without error" do
|
|
datetime = ~U[2026-01-15 12:00:00Z]
|
|
|
|
timezones = [
|
|
"UTC",
|
|
"America/New_York",
|
|
"America/Chicago",
|
|
"America/Denver",
|
|
"America/Los_Angeles",
|
|
"America/Phoenix",
|
|
"Europe/London",
|
|
"Europe/Paris",
|
|
"Europe/Berlin",
|
|
"Asia/Tokyo",
|
|
"Asia/Shanghai",
|
|
"Asia/Dubai",
|
|
"Australia/Sydney"
|
|
]
|
|
|
|
for tz <- timezones do
|
|
assert {:ok, _shifted, _abbr} = TimeHelpers.to_user_timezone(datetime, tz)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "format_time_ago/1" do
|
|
test "returns 'Never' for nil datetime" do
|
|
assert TimeHelpers.format_time_ago(nil) == "Never"
|
|
end
|
|
|
|
test "formats seconds ago" do
|
|
datetime = DateTime.add(DateTime.utc_now(), -30, :second)
|
|
result = TimeHelpers.format_time_ago(datetime)
|
|
assert result =~ "s ago"
|
|
end
|
|
|
|
test "formats 0 seconds ago for very recent datetime" do
|
|
datetime = DateTime.utc_now()
|
|
result = TimeHelpers.format_time_ago(datetime)
|
|
assert result =~ "s ago"
|
|
end
|
|
|
|
test "formats minutes ago" do
|
|
datetime = DateTime.add(DateTime.utc_now(), -120, :second)
|
|
result = TimeHelpers.format_time_ago(datetime)
|
|
assert result == "2m ago"
|
|
end
|
|
|
|
test "formats 1 minute ago at 60 seconds" do
|
|
datetime = DateTime.add(DateTime.utc_now(), -65, :second)
|
|
result = TimeHelpers.format_time_ago(datetime)
|
|
assert result == "1m ago"
|
|
end
|
|
|
|
test "formats hours ago" do
|
|
datetime = DateTime.add(DateTime.utc_now(), -7200, :second)
|
|
result = TimeHelpers.format_time_ago(datetime)
|
|
assert result == "2h ago"
|
|
end
|
|
|
|
test "formats 1 hour ago at 3600 seconds" do
|
|
datetime = DateTime.add(DateTime.utc_now(), -3600, :second)
|
|
result = TimeHelpers.format_time_ago(datetime)
|
|
assert result == "1h ago"
|
|
end
|
|
|
|
test "formats days ago" do
|
|
datetime = DateTime.add(DateTime.utc_now(), -172_800, :second)
|
|
result = TimeHelpers.format_time_ago(datetime)
|
|
assert result == "2d ago"
|
|
end
|
|
|
|
test "formats 1 day ago at 86400 seconds" do
|
|
datetime = DateTime.add(DateTime.utc_now(), -86_400, :second)
|
|
result = TimeHelpers.format_time_ago(datetime)
|
|
assert result == "1d ago"
|
|
end
|
|
|
|
test "formats months ago" do
|
|
# 60 days = 5,184,000 seconds
|
|
datetime = DateTime.add(DateTime.utc_now(), -5_184_000, :second)
|
|
result = TimeHelpers.format_time_ago(datetime)
|
|
assert result == "2mo ago"
|
|
end
|
|
|
|
test "formats 1 month ago at 30 days" do
|
|
# 30 days = 2,592,000 seconds
|
|
datetime = DateTime.add(DateTime.utc_now(), -2_592_000, :second)
|
|
result = TimeHelpers.format_time_ago(datetime)
|
|
assert result == "1mo ago"
|
|
end
|
|
|
|
test "formats years ago" do
|
|
# 2 years = 63,072,000 seconds
|
|
datetime = DateTime.add(DateTime.utc_now(), -63_072_000, :second)
|
|
result = TimeHelpers.format_time_ago(datetime)
|
|
assert result == "2y ago"
|
|
end
|
|
|
|
test "formats 1 year ago at 365 days" do
|
|
# 365 days = 31,536,000 seconds
|
|
datetime = DateTime.add(DateTime.utc_now(), -31_536_000, :second)
|
|
result = TimeHelpers.format_time_ago(datetime)
|
|
assert result == "1y ago"
|
|
end
|
|
|
|
test "handles future datetime by treating it as just now (clock skew)" do
|
|
# Simulate clock skew where datetime is in the future
|
|
datetime = DateTime.add(DateTime.utc_now(), 5, :second)
|
|
result = TimeHelpers.format_time_ago(datetime)
|
|
# Should not crash and should show "0s ago" or similar
|
|
assert result =~ "s ago"
|
|
end
|
|
|
|
test "handles future datetime by many seconds (edge case)" do
|
|
# Extreme clock skew - datetime 30 seconds in the future
|
|
datetime = DateTime.add(DateTime.utc_now(), 30, :second)
|
|
result = TimeHelpers.format_time_ago(datetime)
|
|
# Should clamp to 0 and show as recent
|
|
assert result =~ "s ago"
|
|
end
|
|
end
|
|
|
|
describe "format_datetime/1" do
|
|
test "formats datetime in UTC with 24h format by default" do
|
|
datetime = ~U[2026-01-15 14:34:00Z]
|
|
assert TimeHelpers.format_datetime(datetime) == "Jan 15, 2026 at 14:34:00 UTC"
|
|
end
|
|
|
|
test "returns 'Never' for nil" do
|
|
assert TimeHelpers.format_datetime(nil) == "Never"
|
|
end
|
|
end
|
|
|
|
describe "format_datetime/2" do
|
|
test "formats datetime in UTC when timezone is nil" do
|
|
datetime = ~U[2026-01-15 14:34:00Z]
|
|
assert TimeHelpers.format_datetime(datetime, nil) == "Jan 15, 2026 at 14:34:00 UTC"
|
|
end
|
|
|
|
test "formats datetime in UTC when timezone is explicitly UTC" do
|
|
datetime = ~U[2026-01-15 14:34:00Z]
|
|
assert TimeHelpers.format_datetime(datetime, "UTC") == "Jan 15, 2026 at 14:34:00 UTC"
|
|
end
|
|
|
|
test "converts datetime to America/New_York timezone" do
|
|
datetime = ~U[2026-01-15 14:34:00Z]
|
|
result = TimeHelpers.format_datetime(datetime, "America/New_York")
|
|
assert result == "Jan 15, 2026 at 09:34:00 EST"
|
|
end
|
|
|
|
test "converts datetime to America/Los_Angeles timezone" do
|
|
datetime = ~U[2026-01-15 14:34:00Z]
|
|
result = TimeHelpers.format_datetime(datetime, "America/Los_Angeles")
|
|
assert result == "Jan 15, 2026 at 06:34:00 PST"
|
|
end
|
|
|
|
test "falls back to UTC for invalid timezone" do
|
|
datetime = ~U[2026-01-15 14:34:00Z]
|
|
assert TimeHelpers.format_datetime(datetime, "Invalid/Timezone") == "Jan 15, 2026 at 14:34:00 UTC"
|
|
end
|
|
|
|
test "returns 'Never' for nil datetime" do
|
|
assert TimeHelpers.format_datetime(nil, "America/New_York") == "Never"
|
|
assert TimeHelpers.format_datetime(nil, nil) == "Never"
|
|
end
|
|
end
|
|
|
|
describe "format_datetime/3" do
|
|
test "formats datetime in 12h format" do
|
|
datetime = ~U[2026-01-15 14:34:00Z]
|
|
assert TimeHelpers.format_datetime(datetime, "UTC", "12h") == "Jan 15, 2026 at 02:34 PM UTC"
|
|
end
|
|
|
|
test "formats datetime in 24h format" do
|
|
datetime = ~U[2026-01-15 14:34:00Z]
|
|
assert TimeHelpers.format_datetime(datetime, "UTC", "24h") == "Jan 15, 2026 at 14:34:00 UTC"
|
|
end
|
|
|
|
test "formats datetime in 24h format with timezone conversion" do
|
|
datetime = ~U[2026-01-15 14:34:00Z]
|
|
result = TimeHelpers.format_datetime(datetime, "America/New_York", "24h")
|
|
assert result == "Jan 15, 2026 at 09:34:00 EST"
|
|
end
|
|
|
|
test "formats datetime in 12h format with timezone conversion" do
|
|
datetime = ~U[2026-01-15 14:34:00Z]
|
|
result = TimeHelpers.format_datetime(datetime, "America/New_York", "12h")
|
|
assert result == "Jan 15, 2026 at 09:34 AM EST"
|
|
end
|
|
|
|
test "returns 'Never' for nil datetime" do
|
|
assert TimeHelpers.format_datetime(nil, "UTC", "24h") == "Never"
|
|
assert TimeHelpers.format_datetime(nil, "UTC", "12h") == "Never"
|
|
end
|
|
|
|
test "handles nil timezone by defaulting to UTC" do
|
|
datetime = ~U[2026-01-15 14:34:00Z]
|
|
assert TimeHelpers.format_datetime(datetime, nil, "24h") == "Jan 15, 2026 at 14:34:00 UTC"
|
|
end
|
|
|
|
test "falls back to UTC for invalid timezone in 24h format" do
|
|
datetime = ~U[2026-01-15 14:34:00Z]
|
|
assert TimeHelpers.format_datetime(datetime, "Fake/Zone", "24h") == "Jan 15, 2026 at 14:34:00 UTC"
|
|
end
|
|
|
|
test "formats midnight correctly in 12h format" do
|
|
datetime = ~U[2026-01-15 00:00:00Z]
|
|
assert TimeHelpers.format_datetime(datetime, "UTC", "12h") == "Jan 15, 2026 at 12:00 AM UTC"
|
|
end
|
|
|
|
test "formats noon correctly in 12h format" do
|
|
datetime = ~U[2026-01-15 12:00:00Z]
|
|
assert TimeHelpers.format_datetime(datetime, "UTC", "12h") == "Jan 15, 2026 at 12:00 PM UTC"
|
|
end
|
|
end
|
|
|
|
describe "format_date/1" do
|
|
test "formats date in UTC by default" do
|
|
datetime = ~U[2026-01-15 14:34:00Z]
|
|
assert TimeHelpers.format_date(datetime) == "Jan 15, 2026"
|
|
end
|
|
|
|
test "returns 'Never' for nil" do
|
|
assert TimeHelpers.format_date(nil) == "Never"
|
|
end
|
|
end
|
|
|
|
describe "format_date/2" do
|
|
test "formats date in UTC when timezone is nil" do
|
|
datetime = ~U[2026-01-15 14:34:00Z]
|
|
assert TimeHelpers.format_date(datetime, nil) == "Jan 15, 2026"
|
|
end
|
|
|
|
test "formats date in UTC when timezone is explicitly UTC" do
|
|
datetime = ~U[2026-01-15 14:34:00Z]
|
|
assert TimeHelpers.format_date(datetime, "UTC") == "Jan 15, 2026"
|
|
end
|
|
|
|
test "converts to different date in America/Los_Angeles for early UTC times" do
|
|
datetime = ~U[2026-01-15 02:00:00Z]
|
|
assert TimeHelpers.format_date(datetime, "America/Los_Angeles") == "Jan 14, 2026"
|
|
end
|
|
|
|
test "keeps same date for late UTC times in America/Los_Angeles" do
|
|
datetime = ~U[2026-01-15 20:00:00Z]
|
|
assert TimeHelpers.format_date(datetime, "America/Los_Angeles") == "Jan 15, 2026"
|
|
end
|
|
|
|
test "rolls date forward for positive offset timezones near midnight" do
|
|
# 23:00 UTC on Jan 15 + 9h (Tokyo) = 08:00 Jan 16
|
|
datetime = ~U[2026-01-15 23:00:00Z]
|
|
assert TimeHelpers.format_date(datetime, "Asia/Tokyo") == "Jan 16, 2026"
|
|
end
|
|
|
|
test "falls back to UTC for invalid timezone" do
|
|
datetime = ~U[2026-01-15 14:34:00Z]
|
|
assert TimeHelpers.format_date(datetime, "Invalid/Timezone") == "Jan 15, 2026"
|
|
end
|
|
|
|
test "returns 'Never' for nil datetime" do
|
|
assert TimeHelpers.format_date(nil, "America/New_York") == "Never"
|
|
assert TimeHelpers.format_date(nil, nil) == "Never"
|
|
end
|
|
end
|
|
|
|
describe "format_iso8601/1" do
|
|
test "formats datetime in UTC with 24h format by default" do
|
|
datetime = ~U[2026-01-15 14:34:00Z]
|
|
assert TimeHelpers.format_iso8601(datetime) == "2026-01-15 14:34:00 UTC"
|
|
end
|
|
|
|
test "returns 'Never' for nil" do
|
|
assert TimeHelpers.format_iso8601(nil) == "Never"
|
|
end
|
|
end
|
|
|
|
describe "format_iso8601/2" do
|
|
test "formats datetime in UTC when timezone is nil" do
|
|
datetime = ~U[2026-01-15 14:34:00Z]
|
|
assert TimeHelpers.format_iso8601(datetime, nil) == "2026-01-15 14:34:00 UTC"
|
|
end
|
|
|
|
test "formats datetime in UTC when timezone is explicitly UTC" do
|
|
datetime = ~U[2026-01-15 14:34:00Z]
|
|
assert TimeHelpers.format_iso8601(datetime, "UTC") == "2026-01-15 14:34:00 UTC"
|
|
end
|
|
|
|
test "converts datetime to America/New_York timezone" do
|
|
datetime = ~U[2026-01-15 14:34:00Z]
|
|
result = TimeHelpers.format_iso8601(datetime, "America/New_York")
|
|
assert result == "2026-01-15 09:34:00 EST"
|
|
end
|
|
|
|
test "converts datetime to America/Los_Angeles timezone" do
|
|
datetime = ~U[2026-01-15 14:34:00Z]
|
|
result = TimeHelpers.format_iso8601(datetime, "America/Los_Angeles")
|
|
assert result == "2026-01-15 06:34:00 PST"
|
|
end
|
|
|
|
test "falls back to UTC for invalid timezone" do
|
|
datetime = ~U[2026-01-15 14:34:00Z]
|
|
assert TimeHelpers.format_iso8601(datetime, "Invalid/Timezone") == "2026-01-15 14:34:00 UTC"
|
|
end
|
|
|
|
test "returns 'Never' for nil datetime" do
|
|
assert TimeHelpers.format_iso8601(nil, "America/New_York") == "Never"
|
|
assert TimeHelpers.format_iso8601(nil, nil) == "Never"
|
|
end
|
|
end
|
|
|
|
describe "format_iso8601/3" do
|
|
test "formats datetime in 24h format" do
|
|
datetime = ~U[2026-01-15 14:34:00Z]
|
|
assert TimeHelpers.format_iso8601(datetime, "UTC", "24h") == "2026-01-15 14:34:00 UTC"
|
|
end
|
|
|
|
test "formats datetime in 12h format" do
|
|
datetime = ~U[2026-01-15 14:34:00Z]
|
|
assert TimeHelpers.format_iso8601(datetime, "UTC", "12h") == "2026-01-15 02:34 PM UTC"
|
|
end
|
|
|
|
test "formats datetime in 12h format with timezone conversion" do
|
|
datetime = ~U[2026-01-15 14:34:00Z]
|
|
result = TimeHelpers.format_iso8601(datetime, "America/New_York", "12h")
|
|
assert result == "2026-01-15 09:34 AM EST"
|
|
end
|
|
|
|
test "formats datetime in 24h format with timezone conversion" do
|
|
datetime = ~U[2026-01-15 14:34:00Z]
|
|
result = TimeHelpers.format_iso8601(datetime, "America/New_York", "24h")
|
|
assert result == "2026-01-15 09:34:00 EST"
|
|
end
|
|
|
|
test "returns 'Never' for nil datetime" do
|
|
assert TimeHelpers.format_iso8601(nil, "UTC", "24h") == "Never"
|
|
assert TimeHelpers.format_iso8601(nil, "UTC", "12h") == "Never"
|
|
end
|
|
|
|
test "handles nil timezone by defaulting to UTC" do
|
|
datetime = ~U[2026-01-15 14:34:00Z]
|
|
assert TimeHelpers.format_iso8601(datetime, nil, "12h") == "2026-01-15 02:34 PM UTC"
|
|
end
|
|
|
|
test "falls back to UTC for invalid timezone in 12h format" do
|
|
datetime = ~U[2026-01-15 14:34:00Z]
|
|
assert TimeHelpers.format_iso8601(datetime, "Fake/Zone", "12h") == "2026-01-15 02:34 PM UTC"
|
|
end
|
|
end
|
|
|
|
describe "format_utc_hour/1" do
|
|
test "formats hour in UTC with AM" do
|
|
result = TimeHelpers.format_utc_hour(7)
|
|
assert result == "7:00 AM UTC"
|
|
end
|
|
|
|
test "formats midnight as 12 AM" do
|
|
result = TimeHelpers.format_utc_hour(0)
|
|
assert result == "12:00 AM UTC"
|
|
end
|
|
|
|
test "formats noon as 12 PM" do
|
|
result = TimeHelpers.format_utc_hour(12)
|
|
assert result == "12:00 PM UTC"
|
|
end
|
|
|
|
test "formats afternoon hours with PM" do
|
|
result = TimeHelpers.format_utc_hour(15)
|
|
assert result == "3:00 PM UTC"
|
|
end
|
|
|
|
test "formats hour 23 correctly" do
|
|
result = TimeHelpers.format_utc_hour(23)
|
|
assert result == "11:00 PM UTC"
|
|
end
|
|
end
|
|
|
|
describe "format_utc_hour/2" do
|
|
test "converts UTC hour to America/New_York" do
|
|
# 7 UTC - 5h = 2 AM EST
|
|
result = TimeHelpers.format_utc_hour(7, "America/New_York")
|
|
assert result == "2:00 AM EST"
|
|
end
|
|
|
|
test "converts UTC hour to Asia/Tokyo" do
|
|
# 7 UTC + 9h = 16:00 JST = 4 PM
|
|
result = TimeHelpers.format_utc_hour(7, "Asia/Tokyo")
|
|
assert result == "4:00 PM JST"
|
|
end
|
|
|
|
test "converts midnight UTC to America/Los_Angeles" do
|
|
# 0 UTC - 8h = 16:00 previous day PST = 4 PM
|
|
result = TimeHelpers.format_utc_hour(0, "America/Los_Angeles")
|
|
assert result == "4:00 PM PST"
|
|
end
|
|
|
|
test "converts noon UTC to Europe/Paris" do
|
|
# 12 UTC + 1h = 13:00 CET = 1 PM
|
|
result = TimeHelpers.format_utc_hour(12, "Europe/Paris")
|
|
assert result == "1:00 PM CET"
|
|
end
|
|
|
|
test "converts UTC hour to Australia/Sydney" do
|
|
# 14 UTC + 10h = 24 => 0 next day = 12 AM
|
|
result = TimeHelpers.format_utc_hour(14, "Australia/Sydney")
|
|
assert result == "12:00 AM AEST"
|
|
end
|
|
|
|
test "falls back to UTC for invalid timezone" do
|
|
result = TimeHelpers.format_utc_hour(7, "Invalid/Zone")
|
|
assert result == "7:00 AM UTC"
|
|
end
|
|
|
|
test "handles UTC explicitly" do
|
|
result = TimeHelpers.format_utc_hour(15, "UTC")
|
|
assert result == "3:00 PM UTC"
|
|
end
|
|
end
|
|
|
|
describe "format_utc_hour/3" do
|
|
test "formats hour in 24h format" do
|
|
result = TimeHelpers.format_utc_hour(15, "UTC", "24h")
|
|
assert result == "15:00 UTC"
|
|
end
|
|
|
|
test "formats midnight in 24h format" do
|
|
result = TimeHelpers.format_utc_hour(0, "UTC", "24h")
|
|
assert result == "0:00 UTC"
|
|
end
|
|
|
|
test "formats hour in 12h format" do
|
|
result = TimeHelpers.format_utc_hour(15, "UTC", "12h")
|
|
assert result == "3:00 PM UTC"
|
|
end
|
|
|
|
test "formats hour in 24h format with timezone conversion" do
|
|
result = TimeHelpers.format_utc_hour(7, "America/New_York", "24h")
|
|
assert result == "2:00 EST"
|
|
end
|
|
end
|
|
|
|
describe "format_datetime_for_scope/2" do
|
|
test "formats datetime using scope timezone and time_format" do
|
|
scope = %{timezone: "America/New_York", time_format: "12h"}
|
|
datetime = ~U[2026-01-15 14:34:00Z]
|
|
|
|
result = TimeHelpers.format_datetime_for_scope(datetime, scope)
|
|
assert result == "Jan 15, 2026 at 09:34 AM EST"
|
|
end
|
|
|
|
test "formats datetime with 24h scope" do
|
|
scope = %{timezone: "UTC", time_format: "24h"}
|
|
datetime = ~U[2026-01-15 14:34:00Z]
|
|
|
|
result = TimeHelpers.format_datetime_for_scope(datetime, scope)
|
|
assert result == "Jan 15, 2026 at 14:34:00 UTC"
|
|
end
|
|
|
|
test "returns 'Never' for nil datetime" do
|
|
scope = %{timezone: "UTC", time_format: "24h"}
|
|
assert TimeHelpers.format_datetime_for_scope(nil, scope) == "Never"
|
|
end
|
|
end
|
|
end
|