Current section

Files

Jump to
gtempo src tempo datetime.gleam
Raw

src/tempo/datetime.gleam

//// Functions to use with the `DateTime` type in Tempo.
////
//// ## Examples
////
//// ```gleam
//// import tempo/datetime
//// import snag
////
//// pub fn main() {
//// datetime.literal("2024-12-25T06:00:00+05:00")
//// |> datetime.format("ddd @ h:mm A, Z")
//// // -> "Fri @ 6:00 AM, +05:00"
////
//// datetime.parse("06:21:2024 23:17:07.123Z", "MM:DD:YYYY HH:mm:ss.SSSZ")
//// |> snag.map_error(datetime.describe_parse_error)
//// |> result.map(datetime.to_string)
//// // -> Ok("2024-06-21T23:17:07.123Z")
//// }
//// ```
////
//// ```gleam
//// import gleam/list
//// import tempo/datetime
//// import tempo/period
////
//// pub fn get_every_friday_between(datetime1, datetime2) {
//// period.new(datetime1, datetime2)
//// |> period.comprising_dates
//// |> list.filter(fn(date) {
//// date |> date.to_day_of_week == date.Fri
//// })
//// // -> ["2024-06-21", "2024-06-28", "2024-07-05"]
//// }
//// ```
import gleam/dynamic
import gleam/dynamic/decode
import gleam/list
import gleam/option.{None, Some}
import gleam/result
import gleam/string
import gleam/time/calendar
import gleam/time/duration
import gleam/time/timestamp
import tempo
import tempo/date
import tempo/error as tempo_error
import tempo/naive_datetime
import tempo/offset
import tempo/time
/// Starting point of unix timestamps
pub const unix_epoch = tempo.unix_epoch
/// Create a new datetime from a date, time, and offset.
///
/// ## Examples
///
/// ```gleam
/// datetime.new(
/// date.literal("2024-06-13"),
/// time.literal("23:04:00.009"),
/// offset.literal("+10:00"),
/// )
/// // -> datetime.literal("2024-06-13T23:04:00.009+10:00")
/// ```
pub fn new(
date date: tempo.Date,
time time: tempo.Time,
offset offset: tempo.Offset,
) -> tempo.DateTime {
tempo.datetime(date:, time:, offset:)
}
/// Create a new datetime value from a string literal, but will panic if
/// the string is invalid. Accepted formats are `YYYY-MM-DDThh:mm:ss.sTZD` or
/// `YYYYMMDDThhmmss.sTZD`
///
/// Useful for declaring datetime literals that you know are valid within your
/// program.
///
/// ## Examples
///
/// ```gleam
/// datetime.literal("2024-06-13T23:04:00.009+10:00")
/// |> datetime.to_string
/// // -> "2024-06-13T23:04:00.009+10:00"
/// ```
pub fn literal(datetime: String) -> tempo.DateTime {
case from_string(datetime) {
Ok(datetime) -> datetime
Error(tempo_error.DateTimeInvalidFormat(..)) ->
panic as "Invalid datetime literal format"
Error(tempo_error.DateTimeDateParseError(..)) ->
panic as "Invalid date in datetime literal value"
Error(tempo_error.DateTimeTimeParseError(..)) ->
panic as "Invalid time in datetime literal value"
Error(_) -> panic as "Invalid datetime literal"
}
}
/// Parses a datetime string in the format `YYYY-MM-DDThh:mm:ss.sTZD`,
/// `YYYYMMDDThhmmss.sTZD`, `YYYY-MM-DD hh:mm:ss.sTZD`,
/// `YYYYMMDD hhmmss.sTZD`, `YYYY-MM-DD`, `YYYY-M-D`, `YYYY/MM/DD`,
/// `YYYY/M/D`, `YYYY.MM.DD`, `YYYY.M.D`, `YYYY_MM_DD`, `YYYY_M_D`,
/// `YYYY MM DD`, `YYYY M D`, or `YYYYMMDD`.
///
/// ## Examples
///
/// ```gleam
/// datetime.from_string("20240613T230400.009+00:00")
/// // -> datetime.literal("2024-06-13T23:04:00.009Z")
/// ```
pub fn from_string(
datetime: String,
) -> Result(tempo.DateTime, tempo_error.DateTimeParseError) {
let split_dt =
string.split_once(datetime, "T")
|> result.try_recover(fn(_) { string.split_once(datetime, "t") })
|> result.try_recover(fn(_) { string.split_once(datetime, "_") })
|> result.try_recover(fn(_) { string.split_once(datetime, " ") })
case split_dt {
Ok(#(date, time)) -> {
use date: tempo.Date <- result.try(
date.from_string(date)
|> result.map_error(tempo_error.DateTimeDateParseError(datetime, _)),
)
use #(time, offset): #(String, String) <- result.try(
split_time_and_offset(time)
|> result.replace_error(tempo_error.DateTimeInvalidFormat(datetime)),
)
use time: tempo.Time <- result.try(
time.from_string(time)
|> result.map_error(tempo_error.DateTimeTimeParseError(datetime, _)),
)
use offset: tempo.Offset <- result.map(
offset.from_string(offset)
|> result.map_error(tempo_error.DateTimeOffsetParseError(datetime, _)),
)
new(date, time, offset)
}
_ -> Error(tempo_error.DateTimeInvalidFormat(datetime))
}
}
pub fn from_string_fast(datetime: String) {
timestamp.parse_rfc3339(datetime)
|> result.map(from_timestamp)
|> result.replace_error(tempo_error.DateTimeInvalidFormat(datetime))
}
fn split_time_and_offset(time_with_offset: String) {
case string.slice(time_with_offset, at_index: -1, length: 1) {
"Z" -> #(string.drop_end(time_with_offset, 1), "Z") |> Ok
"z" -> #(string.drop_end(time_with_offset, 1), "Z") |> Ok
_ ->
case string.split_once(time_with_offset, "-") {
Ok(#(time, offset)) -> #(time, "-" <> offset) |> Ok
_ ->
case string.split_once(time_with_offset, "+") {
Ok(#(time, offset)) -> #(time, "+" <> offset) |> Ok
_ -> Error(Nil)
}
}
}
}
/// Returns a string representation of a datetime value in the ISO 8601
/// format with millisecond precision. If a different precision is needed,
/// use the `format` function. If serializing to send outside of Gleam and then
/// parse back into a datetime value, use the `serialize` function.
///
/// ## Examples
///
/// ```gleam
/// datetime.to_string(my_datetime)
/// // -> "2024-06-21T05:22:22.009534Z"
/// ```
pub fn to_string(datetime: tempo.DateTime) -> String {
tempo.datetime_to_string(datetime)
}
/// Returns the date, time, and offset parts of a datetime as a tuple of
/// `gleam_time` types. This is useful for interop and accessing the underlying
/// values of a datetime.
///
/// ## Example
///
/// ```gleam
/// let #(
/// calendar.Date(year:, month:, day:),
/// calendar.TimeOfDay(hour:, minute:, second:, nanosecond:),
/// offset,
/// ) = datetime.to_calendar_parts(my_datetime)
/// ```
pub fn to_calendar_parts(
datetime: tempo.DateTime,
) -> #(calendar.Date, calendar.TimeOfDay, duration.Duration) {
#(
tempo.date_to_calendar_date(datetime.date),
tempo.time_to_calendar_time_of_day(datetime.time),
tempo.offset_to_duration(datetime.offset),
)
}
/// Parses a datetime string in the provided format. Always prefer using
/// this over `parse_any`. All parsed formats must have all parts of a
/// datetime (date, time, offset). Use the other modules for parsing lesser
/// date time values.
///
/// Values can be escaped by putting brackets around them, like "[Hello!] YYYY".
///
/// Available directives: YY (two-digit year), YYYY (four-digit year), M (month),
/// MM (two-digit month), MMM (short month name), MMMM (full month name),
/// D (day of the month), DD (two-digit day of the month),
/// H (hour), HH (two-digit hour), h (12-hour clock hour), hh
/// (two-digit 12-hour clock hour), m (minute), mm (two-digit minute),
/// s (second), ss (two-digit second), SSS (millisecond), SSSS (microsecond),
/// Z (offset from UTC), ZZ (offset from UTC with no ":"),
/// z (short offset from UTC "-04", "Z"), zz (full offset from UTC as "-04:00"
/// or "Z" if UTC), A (AM/PM), a (am/pm).
///
/// ## Example
///
/// ```gleam
/// datetime.parse("2024/06/08, 13:42:11, -04:00", "YYYY/MM/DD, HH:mm:ss, Z")
/// // -> Ok(datetime.literal("2024-06-08T13:42:11-04"))
/// ```
///
/// ```gleam
/// datetime.parse("January 13, 2024. 3:42:11Z", "MMMM DD, YYYY. H:mm:ssz")
/// // -> Ok(datetime.literal("2024-01-13T03:42:11Z"))
/// ```
///
/// ```gleam
/// datetime.parse("Hi! 2024 11 13 12 2 am Z", "[Hi!] YYYY M D h m a z")
/// // -> Ok(datetime.literal("2024-11-13T00:02:00Z"))
/// ```
pub fn parse(
str: String,
in format: tempo.DateTimeFormat,
) -> Result(tempo.DateTime, tempo_error.DateTimeParseError) {
let format_str = tempo.get_datetime_format_str(format)
use #(parts, _) <- result.try(
tempo.consume_format(str, in: format_str)
|> result.map_error(tempo_error.DateTimeInvalidFormat),
)
use date <- result.try(
tempo.find_date(in: parts)
|> result.map_error(tempo_error.DateTimeDateParseError(str, _)),
)
use time <- result.try(
tempo.find_time(in: parts)
|> result.map_error(tempo_error.DateTimeTimeParseError(str, _)),
)
use offset <- result.try(
tempo.find_offset(in: parts)
|> result.map_error(tempo_error.DateTimeOffsetParseError(str, _)),
)
Ok(new(date, time, offset))
}
/// Tries to parse a given date string without a known format. It will not
/// parse two digit years and will assume the month always comes before the
/// day in a date.
///
/// ## Example
///
/// ```gleam
/// parse_any.parse_any("2024.06.21 01:32 PM -0400")
/// // -> Ok(datetime.literal("2024-06-21T13:32:00-04:00"))
/// ```
///
/// ```gleam
/// parse_any.parse_any("2024.06.21 01:32 PM")
/// // -> Error(tempo.ParseMissingOffset)
/// ```
pub fn parse_any(
str: String,
) -> Result(tempo.DateTime, tempo_error.DateTimeParseError) {
case tempo.parse_any(str) {
#(Some(date), Some(time), Some(offset)) -> Ok(new(date, time, offset))
#(_, _, None) ->
Error(tempo_error.DateTimeInvalidFormat(
"Unable to find offset in " <> str,
))
#(_, None, _) ->
Error(tempo_error.DateTimeInvalidFormat("Unable to find time in " <> str))
#(None, _, _) ->
Error(tempo_error.DateTimeInvalidFormat("Unable to find date in " <> str))
}
}
/// Converts a datetime parse error to a human readable error message.
///
/// ## Example
///
/// ```gleam
/// datetime.parse("13:42:11.314-04:00", "YYYY-MM-DDTHH:mm:ss.SSSZ")
/// |> snag.map_error(with: datetime.describe_parse_error)
/// // -> snag.error("Invalid date format in datetime: 13:42:11.314-04:00")
pub fn describe_parse_error(error: tempo_error.DateTimeParseError) {
tempo_error.describe_datetime_parse_error(error)
}
/// Formats a datetime value into a string using the provided format.
///
/// ## Examples
///
/// ```gleam
/// datetime.literal(tempo.Custom("2024-06-21T13:42:11.314-04:00"))
/// |> datetime.format("ddd @ h:mm A (z)")
/// // -> "Fri @ 1:42 PM (-04)"
/// ```
///
/// ```gleam
/// datetime.literal("2024-06-03T09:02:01-04:00")
/// |> datetime.format(tempo.Custom("YY YYYY M MM MMM MMMM D DD d dd ddd"))
/// // -----------:---------------> "24 2024 6 06 Jun June 3 03 1 Mo Mon"
/// ```
///
/// ```gleam
/// datetime.literal("2024-06-03T09:02:01.014920202-00:00")
/// |> datetime.format(tempo.Custom("dddd SSS SSSS SSSSS Z ZZ z"))
/// // -> "Monday 014 014920 014920202 -00:00 -0000 Z"
/// ```
///
/// ```gleam
/// datetime.literal("2024-06-03T13:02:01-04:00")
/// |> datetime.format(tempo.Custom("H HH h hh m mm s ss a A [An ant]"))
/// // --------------------------> "13 13 1 01 2 02 1 01 pm PM An ant"
/// ```
pub fn format(
datetime: tempo.DateTime,
in format: tempo.DateTimeFormat,
) -> String {
case format {
tempo.HTTP -> to_utc(datetime)
_ -> datetime
}
|> tempo.datetime_format(in: format)
}
/// Converts a core gleam time timestamp type to a datetime.
pub fn from_timestamp(timestamp: timestamp.Timestamp) -> tempo.DateTime {
let #(seconds, nanoseconds) =
timestamp.to_unix_seconds_and_nanoseconds(timestamp)
from_unix_micro({ seconds * 1_000_000 } + { nanoseconds / 1000 })
}
/// Converts a datetime to a core gleam time timestamp type.
pub fn to_timestamp(datetime: tempo.DateTime) -> timestamp.Timestamp {
let unix_us = to_unix_micro(datetime)
let seconds = unix_us / 1_000_000
let nanoseconds = { unix_us % 1_000_000 } * 1000
timestamp.from_unix_seconds_and_nanoseconds(seconds, nanoseconds)
}
/// Returns the UTC datetime of a unix timestamp.
///
/// ## Examples
///
/// ```gleam
/// datetime.from_unix_seconds(1_718_829_191)
/// // -> datetime.literal("2024-06-17T12:59:51Z")
/// ```
pub fn from_unix_seconds(unix_ts: Int) -> tempo.DateTime {
new(
date.from_unix_seconds(unix_ts),
time.from_unix_seconds(unix_ts),
tempo.utc,
)
}
/// Returns the UTC unix timestamp of a datetime.
///
/// ## Examples
///
/// ```gleam
/// datetime.literal("2024-06-17T12:59:51Z")
/// |> datetime.to_unix_seconds
/// // -> 1_718_829_191
/// ```
pub fn to_unix_seconds(datetime: tempo.DateTime) -> Int {
let utc_dt = datetime |> apply_offset
date.to_unix_seconds(utc_dt |> tempo.naive_datetime_get_date)
+ {
tempo.time_to_microseconds(utc_dt |> tempo.naive_datetime_get_time)
/ 1_000_000
}
}
/// Returns the UTC datetime of a unix timestamp in milliseconds.
///
/// ## Examples
///
/// ```gleam
/// datetime.from_unix_milli(1_718_629_314_334)
/// // -> datetime.literal("2024-06-17T13:01:54.334Z")
/// ```
pub fn from_unix_milli(unix_ts: Int) -> tempo.DateTime {
new(date.from_unix_milli(unix_ts), time.from_unix_milli(unix_ts), tempo.utc)
}
/// Returns the UTC unix timestamp in milliseconds of a datetime.
///
/// ## Examples
///
/// ```gleam
/// datetime.literal("2024-06-17T13:01:54.334Z")
/// |> datetime.to_unix_milli
/// // -> 1_718_629_314_334
/// ```
pub fn to_unix_milli(datetime: tempo.DateTime) -> Int {
let utc_dt = datetime |> apply_offset
date.to_unix_milli(utc_dt |> tempo.naive_datetime_get_date)
+ {
tempo.time_to_microseconds(utc_dt |> tempo.naive_datetime_get_time) / 1000
}
}
/// Returns the UTC datetime of a unix timestamp in microseconds.
///
/// ## Examples
///
/// ```gleam
/// datetime.from_unix_micro(1_718_629_314_334_734)
/// // -> datetime.literal("2024-06-17T13:01:54.334734Z")
/// ```
pub fn from_unix_micro(unix_ts: Int) -> tempo.DateTime {
new(date.from_unix_micro(unix_ts), time.from_unix_micro(unix_ts), tempo.utc)
}
/// Returns the UTC unix timestamp in microseconds of a datetime.
///
/// ## Examples
///
/// ```gleam
/// datetime.literal("2024-06-17T13:01:54.334734Z")
/// |> datetime.to_unix_micro
/// // -> 1_718_629_314_334_734
/// ```
pub fn to_unix_micro(datetime: tempo.DateTime) -> Int {
tempo.datetime_to_unix_micro(datetime)
}
/// Checks if a dynamic value is a valid datetime string, and returns the
/// datetime if it is.
///
/// ## Examples
///
/// ```gleam
/// dynamic.string("2024-06-13T13:42:11.195Z")
/// |> datetime.from_dynamic_string
/// // -> Ok(datetime.literal("2024-06-13T13:42:11.195Z"))
/// ```
///
/// ```gleam
/// dynamic.string("24-06-13,13:42:11.195")
/// |> datetime.from_dynamic_string
/// // -> Error([
/// // decode.DecodeError(
/// // expected: "tempo.DateTime",
/// // found: "Invalid format: 24-06-13,13:42:11.195",
/// // path: [],
/// // ),
/// // ])
/// ```
pub fn from_dynamic_string(
dynamic_string: dynamic.Dynamic,
) -> Result(tempo.DateTime, List(decode.DecodeError)) {
use datetime: String <- result.try(
// Uses the decode.string function but maintains the decode.DecodeError
// return type to maintain API compatibility.
decode.run(dynamic_string, decode.string)
|> result.map_error(fn(errs) {
list.map(errs, fn(err) {
decode.DecodeError(err.expected, err.found, err.path)
})
}),
)
case from_string(datetime) {
Ok(datetime) -> Ok(datetime)
Error(tempo_error) ->
Error([
decode.DecodeError(
expected: "tempo.DateTime",
found: case tempo_error {
tempo_error.DateTimeInvalidFormat(msg) -> msg
tempo_error.DateTimeTimeParseError(msg, _) -> msg
tempo_error.DateTimeDateParseError(msg, _) -> msg
tempo_error.DateTimeOffsetParseError(msg, _) -> msg
},
path: [],
),
])
}
}
/// Checks if a dynamic value is a valid unix timestamp in seconds, and
/// returns the datetime representation if it is.
///
/// ## Examples
///
/// ```gleam
/// dynamic.int(1_718_629_314)
/// |> datetime.from_dynamic_unix_utc
/// // -> Ok(datetime.literal("2024-06-17T13:01:54Z"))
/// ```
///
/// ```gleam
/// dynamic.string("hello")
/// |> datetime.from_dynamic_unix_utc
/// // -> Error([
/// // decode.DecodeError(
/// // expected: "Int",
/// // found: "String",
/// // path: [],
/// // ),
/// // ])
/// ```
pub fn from_dynamic_unix_utc(
dynamic_ts: dynamic.Dynamic,
) -> Result(tempo.DateTime, List(decode.DecodeError)) {
use unix_seconds: Int <- result.map(
// Uses the decode.int function but maintains the decode.DecodeError
// return type to maintain API compatibility.
decode.run(dynamic_ts, decode.int)
|> result.map_error(fn(errs) {
list.map(errs, fn(err) {
decode.DecodeError(err.expected, err.found, err.path)
})
}),
)
from_unix_seconds(unix_seconds)
}
/// Checks if a dynamic value is a valid unix timestamp in milliseconds, and
/// returns the datetime if it is.
///
/// ## Examples
///
/// ```gleam
/// dynamic.int(1_718_629_314_334)
/// |> datetime.from_dynamic_unix_milli_utc
/// // -> Ok(datetime.literal("2024-06-17T13:01:54.334Z"))
/// ```
///
/// ```gleam
/// dynamic.string("hello")
/// |> datetime.from_dynamic_unix_milli_utc
/// // -> Error([
/// // decode.DecodeError(
/// // expected: "Int",
/// // found: "String",
/// // path: [],
/// // ),
/// // ])
/// ```
pub fn from_dynamic_unix_milli_utc(
dynamic_ts: dynamic.Dynamic,
) -> Result(tempo.DateTime, List(decode.DecodeError)) {
use unix_milli: Int <- result.map(
// Uses the decode.int function but maintains the decode.DecodeError
// return type to maintain API compatibility.
decode.run(dynamic_ts, decode.int)
|> result.map_error(fn(errs) {
list.map(errs, fn(err) {
decode.DecodeError(err.expected, err.found, err.path)
})
}),
)
from_unix_milli(unix_milli)
}
/// Checks if a dynamic value is a valid unix timestamp in microseconds, and
/// returns the datetime if it is.
///
/// ## Examples
///
/// ```gleam
/// dynamic.int(1_718_629_314_334_734)
/// |> datetime.from_dynamic_unix_micro_utc
/// // -> Ok(datetime.literal("2024-06-17T13:01:54.334734Z"))
/// ```
///
/// ```gleam
/// dynamic.string("hello")
/// |> datetime.from_dynamic_unix_micro_utc
/// // -> Error([
/// // decode.DecodeError(
/// // expected: "Int",
/// // found: "String",
/// // path: [],
/// // ),
/// // ])
/// ```
pub fn from_dynamic_unix_micro_utc(
dynamic_ts: dynamic.Dynamic,
) -> Result(tempo.DateTime, List(decode.DecodeError)) {
use unix_micro: Int <- result.map(
// Uses the decode.int function but maintains the decode.DecodeError
// return type to maintain API compatibility.
decode.run(dynamic_ts, decode.int)
|> result.map_error(fn(errs) {
list.map(errs, fn(err) {
decode.DecodeError(err.expected, err.found, err.path)
})
}),
)
from_unix_micro(unix_micro)
}
/// Gets the date of a datetime.
///
/// ## Examples
///
/// ```gleam
/// datetime.literal("2024-06-21T13:42:11.195Z")
/// |> datetime.get_date
/// // -> date.literal("2024-06-21")
/// ```
pub fn get_date(datetime: tempo.DateTime) -> tempo.Date {
datetime.date
}
/// Gets the core gleam time package calendar date of a datetime.
///
/// ## Examples
///
/// ```gleam
/// datetime.literal("2024-06-21T13:42:11.195Z")
/// |> datetime.get_calendar_date
/// // -> calendar.Date(2024, calendar.June, 21)
/// ```
pub fn get_calendar_date(datetime: tempo.DateTime) -> calendar.Date {
datetime.date |> date.to_calendar_date
}
/// Gets the time of a datetime.
///
/// ## Examples
///
/// ```gleam
/// datetime.literal("2024-06-21T13:42:11.195Z")
/// |> datetime.get_time
/// // -> time.literal("13:42:11.195")
/// ```
pub fn get_time(datetime: tempo.DateTime) -> tempo.Time {
datetime.time
}
/// Gets the core gleam time package calendar time of day of a datetime.
///
/// ## Examples
///
/// ```gleam
/// datetime.literal("2024-06-21T13:42:11.195Z")
/// |> datetime.get_calendar_time_of_day
/// // -> calendar.TimeOfDay(13, 42, 11, 195_000_000)
/// ```
pub fn get_calendar_time_of_day(datetime: tempo.DateTime) -> calendar.TimeOfDay {
datetime.time |> time.to_calendar_time_of_day
}
/// Gets the offset of a datetime.
///
/// ## Examples
///
/// ```gleam
/// datetime.literal("2024-06-12T13:42:11.195-04:00")
/// |> datetime.get_offset
/// // -> offset.literal("+04:00")
/// ```
pub fn get_offset(datetime: tempo.DateTime) -> tempo.Offset {
datetime |> tempo.datetime_get_offset
}
/// Drops the time of a datetime, leaving the date and time values unchanged.
///
/// ## Examples
///
/// ```gleam
/// datetime.literal("2024-06-13T13:42:11.195Z")
/// |> datetime.drop_offset
/// // -> naive_datetime.literal("2024-06-13T13:42:11")
/// ```
pub fn drop_offset(datetime: tempo.DateTime) -> tempo.NaiveDateTime {
tempo.datetime_drop_offset(datetime)
}
/// Drops the time of a datetime, leaving the date value unchanged.
///
/// ## Examples
///
/// ```gleam
/// datetime.literal("2024-06-18T13:42:11.195Z")
/// |> datetime.drop_time
/// // -> naive_datetime.literal("2024-06-18T00:00:00Z")
/// ```
pub fn drop_time(datetime: tempo.DateTime) -> tempo.DateTime {
let naive = naive_datetime.drop_time(datetime |> tempo.datetime_get_naive)
tempo.datetime(
date: naive.date,
time: naive.time,
offset: datetime |> tempo.datetime_get_offset,
)
}
/// Applies the offset of a datetime to the date and time values, resulting
/// in a new naive datetime value that represents the original datetime in
/// UTC time.
///
/// ## Examples
///
/// ```gleam
/// datetime.literal("2024-06-21T05:36:11.195-04:00")
/// |> datetime.apply_offset
/// // -> naive_datetime.literal("2024-06-21T09:36:11.195")
/// ```
pub fn apply_offset(datetime: tempo.DateTime) -> tempo.NaiveDateTime {
tempo.datetime_apply_offset(datetime)
}
/// Converts a datetime to the equivalent UTC time.
///
/// ## Examples
///
/// ```gleam
/// datetime.literal("2024-06-21T05:36:11.195-04:00")
/// |> datetime.to_utc
/// // -> datetime.literal("2024-06-21T09:36:11.195Z")
/// ```
pub fn to_utc(datetime: tempo.DateTime) -> tempo.DateTime {
tempo.datetime_to_utc(datetime)
}
/// Converts a datetime to the equivalent time in an offset.
///
/// ## Examples
///
/// ```gleam
/// datetime.literal("2024-06-21T05:36:11.195-04:00")
/// |> datetime.to_offset(offset.literal("+10:00"))
/// // -> datetime.literal("2024-06-21T19:36:11.195+10:00")
/// ```
pub fn to_offset(
datetime: tempo.DateTime,
offset: tempo.Offset,
) -> tempo.DateTime {
tempo.datetime_to_offset(datetime, offset)
}
/// Converts a datetime to the equivalent local datetime. Prefer to either
/// design your application to not need this, or add an external timezone
/// provider to use with the `to_timezone` function.
///
/// Conversion is based on the host's current offset. We can not be
/// sure the current host offset is applicable to the given datetime, and so
/// an imprecise conversion will be performed. The imprecise conversion can be
/// inaccurate to the degree the local offset changes throughout the year.
/// For example, in North America where Daylight Savings Time is observed with
/// a one-hour time shift, the imprecise conversion can be off by up to an hour,
/// depending on the time of year.
///
/// If the date of the given datetime matches the date of the host, then the
/// conversion will actually be precise all but during the hour(s) when the
/// time zone offset is shifting.
///
/// ## Examples
///
/// ```gleam
/// datetime.literal("2024-06-21T09:57:11.195Z")
/// |> datetime.to_local_imprecise
/// // -> tempo.Precise(datetime.literal("2024-06-21T05:57:11.195-04:00"))
/// ```
///
/// ```gleam
/// datetime.literal("1998-08-23T09:57:11.195Z")
/// |> datetime.to_local_imprecise
/// // -> tempo.Imprecise(datetime.literal("1998-08-23T05:57:11.195-04:00"))
/// ```
pub fn to_local_imprecise(datetime: tempo.DateTime) -> tempo.DateTime {
datetime |> to_offset(offset.local())
}
/// Converts a datetime to the equivalent local time. Prefer to either
/// design your application to not need this, or add an external timezone
/// provider to use with the `to_timezone` function.
///
/// Conversion is based on the host's current offset. We can not be
/// sure the current host offset is applicable to the given datetime, and so
/// an imprecise conversion will be performed. The imprecise conversion can be
/// inaccurate to the degree the local offset changes throughout the year.
/// For example, in North America where Daylight Savings Time is observed with
/// a one-hour time shift, the imprecise conversion can be off by up to an hour,
/// depending on the time of year.
///
/// If the date of the given datetime matches the date of the host, then the
/// conversion will actually be precise all but during the hour(s) when the
/// time zone offset is shifting.
///
/// ## Examples
///
/// ```gleam
/// datetime.literal("2024-06-21T09:57:11.195Z")
/// |> datetime.to_local_time_imprecise
/// // -> time.literal("05:57:11.195")
/// ```
///
/// ```gleam
/// datetime.literal("1998-08-23T09:57:11.195Z")
/// |> datetime.to_local_time_imprecise
/// // -> time.literal("05:57:11.195")
/// ```
///
/// Making internal because users can now just call
/// `datetime.to_local_imprecise(dt).time`. It was harder in prior versions
@internal
pub fn to_local_time_imprecise(datetime: tempo.DateTime) -> tempo.Time {
to_local_imprecise(datetime).time
}
/// Converts a datetime to the equivalent local time imprecisely. Prefer to either
/// design your application to not need this, or add an external timezone
/// provider to use with the `to_timezone` function.
///
/// Conversion is based on the host's current offset. We can not be
/// sure the current host offset is applicable to the given datetime, and so
/// an imprecise conversion will be performed. The imprecise conversion can be
/// inaccurate to the degree the local offset changes throughout the year.
/// For example, in North America where Daylight Savings Time is observed with
/// a one-hour time shift, the imprecise conversion can be off by up to an hour,
/// depending on the time of year.
///
/// If the date of the given datetime matches the date of the host, then the
/// conversion will actually be precise all but during the hour(s) when the
/// time zone offset is shifting.
///
/// ## Examples
///
/// ```gleam
/// datetime.literal("2024-06-19T01:35:11.195Z")
/// |> datetime.to_local_date_imprecise
/// // -> date.literal("2024-06-18")
/// ```
///
/// ```gleam
/// datetime.literal("1998-08-23T01:57:11.195Z")
/// |> datetime.to_local_date_imprecise
/// // -> date.literal("1998-08-22")
/// ```
///
/// Making internal because users can now just call
/// `datetime.to_local_imprecise(dt).date`. It was harder in prior versions
@internal
pub fn to_local_date_imprecise(datetime: tempo.DateTime) -> tempo.Date {
to_local_imprecise(datetime).date
}
/// Converts a datetime to the specified timezone. Relies on an external
/// package like `gtz` to provide timezone information.
///
/// ## Example
///
/// ```gleam
/// import gtz
/// 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"
/// ```
///
/// ```gleam
/// import gtz
/// let assert Ok(local_tz) = gtz.local_name() |> gtz.timezone
/// datetime.from_unix_seconds(1_729_257_776)
/// |> datetime.to_timezone(local_tz)
/// |> datetime.to_string
/// // -> "2024-10-18T14:22:56.000+01:00"
/// ```
pub fn to_timezone(
datetime: tempo.DateTime,
tz: tempo.TimeZoneProvider,
) -> tempo.DateTime {
tempo.datetime_to_tz(datetime, tz)
}
/// Gets the name of the timezone the datetime is in.
///
/// ## Example
///
/// ```gleam
/// datetime.literal("2024-06-21T06:30:02.334Z")
/// |> datetime.get_timezone_name
/// // -> None
/// ```
///
/// ```gleam
/// import gtz
/// let assert Ok(tz) = gtz.timezone("Europe/London")
/// datetime.to_timezone(my_datetime, tz)
/// |> datetime.get_timezone_name
/// // -> Some("Europe/London")
/// ```
pub fn get_timezone_name(datetime: tempo.DateTime) -> option.Option(String) {
tempo.datetime_get_tz(datetime)
}
/// Compares two datetimes.
///
/// ## Examples
///
/// ```gleam
/// datetime.literal("2024-06-21T23:47:00+09:05")
/// |> datetime.compare(to: datetime.literal("2024-06-21T23:47:00+09:05"))
/// // -> order.Eq
/// ```
///
/// ```gleam
/// datetime.literal("2023-05-11T13:30:00-04:00")
/// |> datetime.compare(to: datetime.literal("2023-05-11T13:15:00Z"))
/// // -> order.Lt
/// ```
///
/// ```gleam
/// datetime.literal("2024-06-12T23:47:00+09:05")
/// |> datetime.compare(to: datetime.literal("2022-04-12T00:00:00"))
/// // -> order.Gt
/// ```
pub fn compare(a: tempo.DateTime, to b: tempo.DateTime) {
tempo.datetime_compare(a, to: b)
}
/// Checks if the first datetime is earlier than the second datetime.
///
/// ## Examples
///
/// ```gleam
/// datetime.literal("2024-06-21T23:47:00+09:05")
/// |> datetime.is_earlier(
/// than: datetime.literal("2024-06-21T23:47:00+09:05"),
/// )
/// // -> False
/// ```
///
/// ```gleam
/// datetime.literal("2023-05-11T13:30:00-04:00")
/// |> datetime.is_earlier(
/// than: datetime.literal("2023-05-11T13:15:00Z"),
/// )
/// // -> True
/// ```
pub fn is_earlier(a: tempo.DateTime, than b: tempo.DateTime) -> Bool {
tempo.datetime_is_earlier(a, than: b)
}
/// Checks if the first datetime is earlier or equal to the second datetime.
///
/// ## Examples
/// ```gleam
/// datetime.literal("2024-06-21T23:47:00+09:05")
/// |> datetime.is_earlier_or_equal(
/// to: datetime.literal("2024-06-21T23:47:00+09:05"),
/// )
/// // -> True
/// ```
///
/// ```gleam
/// datetime.literal("2024-07-15T23:40:00-04:00")
/// |> datetime.is_earlier_or_equal(
/// to: datetime.literal("2023-05-11T13:15:00Z"),
/// )
/// // -> False
/// ```
pub fn is_earlier_or_equal(a: tempo.DateTime, to b: tempo.DateTime) -> Bool {
tempo.datetime_is_earlier_or_equal(a, b)
}
/// Checks if the first datetime is equal to the second datetime.
///
/// ## Examples
/// ```gleam
/// datetime.literal("2024-06-21T09:44:00Z")
/// |> datetime.is_equal(
/// to: datetime.literal("2024-06-21T05:44:00-04:00"),
/// )
/// // -> True
/// ```
///
/// ```gleam
/// datetime.literal("2024-06-21T09:44:00Z")
/// |> datetime.is_equal(
/// to: datetime.literal("2024-06-21T09:44:00.045Z"),
/// )
/// // -> False
/// ```
pub fn is_equal(a: tempo.DateTime, to b: tempo.DateTime) -> Bool {
tempo.datetime_is_equal(a, to: b)
}
/// Checks if the first datetime is later than the second datetime.
///
/// ## Examples
///
/// ```gleam
/// datetime.literal("2024-06-21T23:47:00+09:05")
/// |> datetime.is_later(
/// than: datetime.literal("2024-06-21T23:47:00+09:05"),
/// )
/// // -> False
/// ```
///
/// ```gleam
/// datetime.literal("2023-05-11T13:00:00+04:00")
/// |> datetime.is_later(
/// than: datetime.literal("2023-05-11T13:15:00.534Z"),
/// )
/// // -> True
/// ```
pub fn is_later(a: tempo.DateTime, than b: tempo.DateTime) -> Bool {
tempo.datetime_is_later(a, than: b)
}
/// Checks if the first datetime is later or equal to the second datetime.
///
/// ## Examples
/// ```gleam
/// datetime.literal("2016-01-11T03:47:00+09:05")
/// |> datetime.is_later_or_equal(
/// to: datetime.literal("2024-06-21T23:47:00+09:05"),
/// )
/// // -> False
/// ```
///
/// ```gleam
/// datetime.literal("2024-07-15T23:40:00-04:00")
/// |> datetime.is_later_or_equal(
/// to: datetime.literal("2023-05-11T13:15:00Z"),
/// )
/// // -> True
/// ```
pub fn is_later_or_equal(a: tempo.DateTime, to b: tempo.DateTime) -> Bool {
tempo.datetime_is_later_or_equal(a, b)
}
/// Returns the difference between two datetimes as a duration between their
/// equivalent UTC times.
///
/// ## Examples
///
/// ```gleam
/// datetime.literal("2024-06-12T23:17:00Z")
/// |> datetime.difference(
/// from: datetime.literal("2024-06-16T01:16:12Z"),
/// )
/// |> duration.as_days
/// // -> 3
/// ```
///
/// ```gleam
/// datetime.literal("2024-06-12T23:17:00Z")
/// |> datetime.difference(
/// from: datetime.literal("2024-06-16T01:18:12Z"),
/// )
/// |> duration.format
/// // -> "3 days, 2 hours, and 1 minute"
/// ```
pub fn difference(
from a: tempo.DateTime,
to b: tempo.DateTime,
) -> duration.Duration {
naive_datetime.difference(
from: tempo.datetime_apply_offset(a),
to: tempo.datetime_apply_offset(b),
)
}
/// Creates a period between two datetimes, where the start and end times are
/// the equivalent UTC times of the provided datetimes. The specified start
/// and end datetimes will be swapped if the start datetime is later than the
/// end datetime.
///
/// ## Examples
///
/// ```gleam
/// datetime.to_period(
/// start: datetime.literal("2024-06-12T23:17:00Z")
/// end: datetime.literal("2024-06-16T01:16:12Z"),
/// )
/// |> period.as_days
/// // -> 3
/// ```
///
/// ```gleam
/// datetime.to_period(
/// start: datetime.literal("2024-06-12T23:17:00Z")
/// end: datetime.literal("2024-06-16T01:18:12Z"),
/// )
/// |> period.format
/// // -> "3 days, 2 hours, and 1 minute"
/// ```
pub fn as_period(
start start: tempo.DateTime,
end end: tempo.DateTime,
) -> tempo.Period {
tempo.period_new(start:, end:)
}
/// Adds a duration to a datetime.
///
/// ## Examples
///
/// ```gleam
/// datetime.literal("2024-06-12T23:17:00Z")
/// |> datetime.add(duration |> tempo.offset_get_minutes(3))
/// // -> datetime.literal("2024-06-12T23:20:00Z")
/// ```
pub fn add(
datetime: tempo.DateTime,
duration duration_to_add: duration.Duration,
) -> tempo.DateTime {
tempo.datetime_add(datetime, duration_to_add)
}
/// Subtracts a duration from a datetime.
///
/// ## Examples
///
/// ```gleam
/// datetime.literal("2024-06-21T23:17:00Z")
/// |> datetime.subtract(duration.days(3))
/// // -> datetime.literal("2024-06-18T23:17:00Z")
/// ```
pub fn subtract(
datetime: tempo.DateTime,
duration duration_to_subtract: duration.Duration,
) -> tempo.DateTime {
tempo.datetime_subtract(datetime, duration: duration_to_subtract)
}
/// Gets the time left in the day.
///
/// Does **not** account for leap seconds like the rest of the package.
///
/// ## Examples
///
/// ```gleam
/// naive_datetime.literal("2015-06-30T23:59:03Z")
/// |> naive_datetime.time_left_in_day
/// // -> time.literal("00:00:57")
/// ```
///
/// ```gleam
/// naive_datetime.literal("2024-06-18T08:05:20-04:00")
/// |> naive_datetime.time_left_in_day
/// // -> time.literal("15:54:40")
/// ```
pub fn time_left_in_day(datetime: tempo.DateTime) -> tempo.Time {
datetime
|> tempo.datetime_get_naive
|> tempo.naive_datetime_get_time
|> time.left_in_day
}