Current section
Files
Jump to
Current section
Files
src/gtz.gleam
//// Functions to provide simple timezone support for other Gleam datetime libraries.
import gleam/time/calendar
import gleam/time/duration
import gleam/time/timestamp
import tempo
import tempo/naive_datetime
import tempo/offset
/// Constructs a TimeZoneProvider type to be used with the Tempo package.
/// Returns an error if the timezone is not valid.
///
/// ## Examples
///
/// ```gleam
/// import tempo/datetime
///
/// let assert Ok(tz) = gtz.timezone("America/New_York")
///
/// datetime.literal("2024-06-21T06:30:02.334Z")
/// |> datetime.to_timezone(tz)
/// |> datetime.to_string
/// // -> "2024-01-03T02:30:02.334-04:00"
/// ```
pub fn timezone(name: String) -> Result(tempo.TimeZoneProvider, Nil) {
case is_valid_timezone(name) {
True ->
Ok(
tempo.TimeZoneProvider(
get_name: fn() { name },
calculate_offset: fn(utc_naive_datetime) {
let #(#(year, month, day), #(hour, minute, second)) =
naive_datetime.to_tuple(utc_naive_datetime)
let assert Ok(offset) =
calculate_offset_ffi(year, month, day, hour, minute, second, name)
|> duration.minutes
|> offset.from_duration
offset
},
),
)
False -> Error(Nil)
}
}
/// Calculates the offset of a given timestamp in a specific time zone. Returns
/// an error if the time zone is invalid.
///
/// This can be combined with the `gleam_time` package to convert timestamps to
/// calendar dates in a given time zone.
///
/// ## Example
///
/// ```gleam
/// import gtz
/// import gleam/time/timestamp
///
/// let my_ts =
/// 1_729_257_776
/// |> timestamp.from_unix_seconds
///
/// let assert Ok(offset) =
/// gtz.calculate_offset(my_ts, in: "America/New_York")
///
/// timestamp.to_calendar(my_ts, offset)
/// // -> #(
/// // calendar.Date(2024, calendar.October, 18),
/// // calendar.TimeOfDay(9, 22, 56, 0)
/// // )
/// ```
pub fn calculate_offset(
timestamp: timestamp.Timestamp,
in time_zone: String,
) -> Result(duration.Duration, Nil) {
case is_valid_timezone(time_zone) {
True -> {
let #(
calendar.Date(year:, month:, day:),
calendar.TimeOfDay(hours:, minutes:, seconds:, nanoseconds: _),
) = timestamp.to_calendar(timestamp, calendar.utc_offset)
calculate_offset_ffi(
year,
month |> calendar.month_to_int,
day,
hours,
minutes,
seconds,
time_zone,
)
|> duration.minutes
|> Ok
}
False -> Error(Nil)
}
}
@external(erlang, "Elixir.GTZ_FFI", "calculate_offset")
@external(javascript, "./gtz_ffi.mjs", "calculate_offset")
fn calculate_offset_ffi(
year: Int,
month: Int,
day: Int,
hour: Int,
minute: Int,
second: Int,
timezone: String,
) -> Int
@external(erlang, "Elixir.GTZ_FFI", "is_valid_timezone")
@external(javascript, "./gtz_ffi.mjs", "is_valid_timezone")
fn is_valid_timezone(timezone: String) -> Bool
/// Returns the name of the host system's timezone.
///
/// ## Examples
///
/// ```gleam
/// gtz.local_name()
/// // -> "Europe/London"
/// ```
@external(erlang, "Elixir.Timex.Timezone.Local", "lookup")
@external(javascript, "./gtz_ffi.mjs", "local_timezone")
pub fn local_name() -> String