Current section
Files
Jump to
Current section
Files
src/tempo@time_zone.erl
-module(tempo@time_zone).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/tempo/time_zone.gleam").
-export([new/1, from_database/2, local_name/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" Functions to provide time zone support for datetimes.\n"
"\n"
" A `tempo.TimeZoneProvider` value is constructed with these functions, and\n"
" then handed to functions like `datetime.to_timezone` to convert datetimes\n"
" between zones. Note that the datetime conversion does not store the\n"
" time zone data in the datetime value itself, so after converting a datetime,\n"
" adding or subtracting time from it may invalidate its correctness in\n"
" the converted time zone.\n"
"\n"
" On the Erlang target running on macOS and other Unix systems, the operating\n"
" system's TZif database is read from `/usr/share/zoneinfo` via the `tzif`\n"
" package. It is parsed once on first use and then memoized in a persistent\n"
" term. A host with no readable database there has no valid zones, so `new` returns\n"
" an error for every name. In this case, use the `from_database` function\n"
" to supply your own `tzif.TzDatabase` database instead.\n"
"\n"
" On the JavaScript target the runtime's native `Intl` API answers both which\n"
" zone names are valid and what offset a zone was at, so any zone the\n"
" JavaScript engine knows about is available with no filesystem access. You\n"
" can also provide your own `tzif.TzDatabase` database to `from_database`\n"
" to supply your own zone data to the JavaScript runtime if you would like.\n"
).
-file("src/tempo/time_zone.gleam", 146).
?DOC(
" Assembles a provider from a name and a function giving the offset in minutes\n"
" at a UTC wall-clock time. Whatever that function closes over — the host, or\n"
" a caller supplied database — is what the provider carries with it.\n"
).
-spec provider(
binary(),
fun((integer(), integer(), integer(), integer(), integer(), integer()) -> integer())
) -> tempo:time_zone_provider().
provider(Name, Offset_minutes) ->
{time_zone_provider,
fun() -> Name end,
fun(Utc_naive_datetime) ->
{{Year, Month, Day}, {Hour, Minute, Second}} = tempo@naive_datetime:to_tuple(
Utc_naive_datetime
),
_pipe = Offset_minutes(Year, Month, Day, Hour, Minute, Second),
_pipe@1 = gleam@time@duration:minutes(_pipe),
tempo:new_offset_unchecked(_pipe@1)
end}.
-file("src/tempo/time_zone.gleam", 52).
?DOC(
" Constructs a TimeZoneProvider type to be used with the rest of this package.\n"
" Returns an error if the time zone is not valid.\n"
"\n"
" Which names are valid, and the offsets they resolve to, come from the host:\n"
" the operating system's TZif database in `/usr/share/zoneinfo` on Erlang\n"
" (macOS and other Unix systems), and the native `Intl` API on JavaScript.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" import tempo/datetime\n"
" import tempo/time_zone\n"
"\n"
" let assert Ok(tz) = time_zone.new(\"America/New_York\")\n"
"\n"
" datetime.literal(\"2024-06-21T06:30:02.334Z\")\n"
" |> datetime.to_timezone(tz)\n"
" |> datetime.to_string\n"
" // -> \"2024-01-03T02:30:02.334-04:00\"\n"
" ```\n"
).
-spec new(binary()) -> {ok, tempo:time_zone_provider()} | {error, nil}.
new(Name) ->
case tempo_time_zone_ffi:is_valid_timezone(Name) of
true ->
{ok,
provider(
Name,
fun(Year, Month, Day, Hour, Minute, Second) ->
tempo_time_zone_ffi:calculate_offset(
Year,
Month,
Day,
Hour,
Minute,
Second,
Name
)
end
)};
false ->
{error, nil}
end.
-file("src/tempo/time_zone.gleam", 100).
?DOC(
" Constructs a TimeZoneProvider backed by a supplied TZif database. Returns\n"
" an error if `db` does not contain `name`.\n"
"\n"
" This is useful for loading a database from a non-standard host location.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" import tempo/datetime\n"
" import tempo/time_zone\n"
" import tzif/database\n"
"\n"
" // Provided by the host at a non-standard location\n"
" let assert Ok(db) = database.load_from_path(\"/custom/path/to/zoneinfo\")\n"
" let assert Ok(tz) = time_zone.from_database(db, \"America/New_York\")\n"
"\n"
" datetime.literal(\"2024-06-21T06:30:02.334Z\")\n"
" |> datetime.to_timezone(tz)\n"
" |> datetime.to_string\n"
" // -> \"2024-06-21T02:30:02.334000-04:00\"\n"
" ```\n"
"\n"
" ```gleam\n"
" import tempo/datetime\n"
" import tempo/time_zone\n"
" import zones\n"
"\n"
" // Self-provided tzif database from the `zones` package\n"
" let db = zones.database()\n"
" let assert Ok(tz) = time_zone.from_database(db, \"America/New_York\")\n"
"\n"
" datetime.literal(\"2024-06-21T06:30:02.334Z\")\n"
" |> datetime.to_timezone(tz)\n"
" |> datetime.to_string\n"
" // -> \"2024-06-21T02:30:02.334000-04:00\"\n"
" ```\n"
).
-spec from_database(tzif@database:tz_database(), binary()) -> {ok,
tempo:time_zone_provider()} |
{error, nil}.
from_database(Db, Name) ->
case begin
_pipe = tzif@database:get_available_timezones(Db),
gleam@list:contains(_pipe, Name)
end of
true ->
{ok,
provider(
Name,
fun(Year, Month, Day, Hour, Minute, Second) ->
Offset_minutes = begin
gleam@result:'try'(
gleam@time@calendar:month_from_int(Month),
fun(Month@1) ->
Timestamp = gleam@time@timestamp:from_calendar(
{date, Year, Month@1, Day},
{time_of_day, Hour, Minute, Second, 0},
{duration, 0, 0}
),
gleam@result:map(
begin
_pipe@1 = tzif@database:get_zone_parameters(
Timestamp,
Name,
Db
),
gleam@result:replace_error(
_pipe@1,
nil
)
end,
fun(Params) ->
{Seconds, _} = gleam@time@duration:to_seconds_and_nanoseconds(
erlang:element(2, Params)
),
Seconds div 60
end
)
end
)
end,
gleam@result:unwrap(Offset_minutes, 0)
end
)};
false ->
{error, nil}
end.
-file("src/tempo/time_zone.gleam", 201).
?DOC(
" Returns the name of the host system's time zone.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" time_zone.local_name()\n"
" // -> \"Europe/London\"\n"
" ```\n"
"\n"
" On the Erlang target the zone is read from the operating system, in order:\n"
" the `TZ` environment variable, the symlink target of `/etc/localtime`, then\n"
" `/etc/timezone` or `/etc/sysconfig/clock`. If none of those yield an IANA\n"
" zone name, `\"UTC\"` is returned. On JavaScript the host's `Intl` API is used.\n"
).
-spec local_name() -> binary().
local_name() ->
tempo_time_zone_ffi:local_timezone().