towerops/test/towerops_web/helpers/time_helpers_test.exs
2026-06-13 18:20:20 -05:00

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