///|
/// A calendar date and a wall-clock time, with no time zone.
///
/// Reference:
pub struct PlainDateTime {
iso : IsoDateTime
calendar : Calendar
} derive(Eq)
///|
pub impl Show for PlainDateTime with fn output(self, logger) {
logger.write_string(
try! self.to_string_with_options(
ToStringRoundingOptions::default(),
DisplayCalendar::default(),
),
)
}
///|
pub impl Debug for PlainDateTime with fn to_repr(self) {
Repr::Repr(self.to_string())
}
///|
/// Creates a date-time without validating it.
fn PlainDateTime::new_unchecked(
iso : IsoDateTime,
calendar : Calendar,
) -> PlainDateTime {
{ iso, calendar }
}
///|
/// Creates a date-time in the ISO calendar, rejecting invalid fields.
///
/// ```mbt check
/// test {
/// inspect(
/// @temporal.PlainDateTime::try_new(2025, 3, 1, hour=11, minute=16, second=10),
/// content="2025-03-01T11:16:10",
/// )
/// }
/// ```
pub fn PlainDateTime::try_new(
year : Int,
month : Int,
day : Int,
hour? : Int = 0,
minute? : Int = 0,
second? : Int = 0,
millisecond? : Int = 0,
microsecond? : Int = 0,
nanosecond? : Int = 0,
) -> PlainDateTime raise TemporalError {
PlainDateTime::new_with_overflow(
year,
month,
day,
hour,
minute,
second,
millisecond,
microsecond,
nanosecond,
Reject,
Calendar::ISO,
)
}
///|
/// Creates a date-time, handling out-of-range fields according to `overflow`.
pub fn PlainDateTime::new_with_overflow(
year : Int,
month : Int,
day : Int,
hour : Int,
minute : Int,
second : Int,
millisecond : Int,
microsecond : Int,
nanosecond : Int,
overflow : Overflow,
calendar : Calendar,
) -> PlainDateTime raise TemporalError {
let date = IsoDate::new_with_overflow(year, month, day, overflow)
let time = IsoTime::new_with_overflow(
hour, minute, second, millisecond, microsecond, nanosecond, overflow,
)
{ iso: IsoDateTime::new(date, time), calendar }
}
///|
/// Creates a date-time from an already-validated ISO date-time.
pub fn PlainDateTime::from_iso(
iso : IsoDateTime,
calendar : Calendar,
) -> PlainDateTime {
{ iso, calendar }
}
///|
/// Returns the underlying ISO date-time.
pub fn PlainDateTime::iso(self : PlainDateTime) -> IsoDateTime {
self.iso
}
///|
/// Returns the calendar.
pub fn PlainDateTime::calendar(self : PlainDateTime) -> Calendar {
self.calendar
}
///|
/// Returns the year.
pub fn PlainDateTime::year(self : PlainDateTime) -> Int {
self.iso.date.year
}
///|
/// Returns the month, 1-based.
pub fn PlainDateTime::month(self : PlainDateTime) -> Int {
self.iso.date.month
}
///|
/// Returns the calendar-independent month code.
pub fn PlainDateTime::month_code(self : PlainDateTime) -> String {
month_code_of(self.iso.date.month)
}
///|
/// Returns the day of the month.
pub fn PlainDateTime::day(self : PlainDateTime) -> Int {
self.iso.date.day
}
///|
/// Returns the hour.
pub fn PlainDateTime::hour(self : PlainDateTime) -> Int {
self.iso.time.hour
}
///|
/// Returns the minute.
pub fn PlainDateTime::minute(self : PlainDateTime) -> Int {
self.iso.time.minute
}
///|
/// Returns the second.
pub fn PlainDateTime::second(self : PlainDateTime) -> Int {
self.iso.time.second
}
///|
/// Returns the millisecond.
pub fn PlainDateTime::millisecond(self : PlainDateTime) -> Int {
self.iso.time.millisecond
}
///|
/// Returns the microsecond within the millisecond.
pub fn PlainDateTime::microsecond(self : PlainDateTime) -> Int {
self.iso.time.microsecond
}
///|
/// Returns the nanosecond within the microsecond.
pub fn PlainDateTime::nanosecond(self : PlainDateTime) -> Int {
self.iso.time.nanosecond
}
///|
/// Returns the ISO day of the week, Monday = 1 through Sunday = 7.
pub fn PlainDateTime::day_of_week(self : PlainDateTime) -> Int {
iso_day_of_week(self.iso.date.year, self.iso.date.month, self.iso.date.day)
}
///|
/// Returns the 1-based day of the year.
pub fn PlainDateTime::day_of_year(self : PlainDateTime) -> Int {
iso_day_of_year(self.iso.date.year, self.iso.date.month, self.iso.date.day)
}
///|
/// Returns the ISO week number.
pub fn PlainDateTime::week_of_year(self : PlainDateTime) -> Int {
iso_week_of_year(self.iso.date.year, self.iso.date.month, self.iso.date.day).0
}
///|
/// Returns the year the ISO week belongs to.
pub fn PlainDateTime::year_of_week(self : PlainDateTime) -> Int {
iso_week_of_year(self.iso.date.year, self.iso.date.month, self.iso.date.day).1
}
///|
/// Returns the number of days in this month.
pub fn PlainDateTime::days_in_month(self : PlainDateTime) -> Int {
iso_days_in_month(self.iso.date.year, self.iso.date.month)
}
///|
/// Returns the number of days in this year.
pub fn PlainDateTime::days_in_year(self : PlainDateTime) -> Int {
iso_days_in_year(self.iso.date.year)
}
///|
/// Returns the number of months in this year, always 12 in the ISO calendar.
pub fn PlainDateTime::months_in_year(_self : PlainDateTime) -> Int {
12
}
///|
/// Returns whether this year is a leap year.
pub fn PlainDateTime::in_leap_year(self : PlainDateTime) -> Bool {
is_leap_year(self.iso.date.year)
}
///|
/// Compares two date-times chronologically.
pub impl Compare for PlainDateTime with fn compare(self, other) {
self.iso.compare(other.iso)
}
///|
/// Returns whether two date-times are the same instant in the same calendar.
pub fn PlainDateTime::equals(
self : PlainDateTime,
other : PlainDateTime,
) -> Bool {
self.iso == other.iso && self.calendar == other.calendar
}
///|
/// Returns the date part.
pub fn PlainDateTime::to_plain_date(self : PlainDateTime) -> PlainDate {
PlainDate::new_unchecked(self.iso.date, self.calendar)
}
///|
/// Returns the time part.
pub fn PlainDateTime::to_plain_time(self : PlainDateTime) -> PlainTime {
PlainTime::from_iso(self.iso.time)
}
///|
/// Returns a copy with the given fields replaced.
pub fn PlainDateTime::with_fields(
self : PlainDateTime,
year? : Int,
month? : Int,
day? : Int,
hour? : Int,
minute? : Int,
second? : Int,
millisecond? : Int,
microsecond? : Int,
nanosecond? : Int,
overflow? : Overflow = Constrain,
) -> PlainDateTime raise TemporalError {
PlainDateTime::new_with_overflow(
year.unwrap_or(self.iso.date.year),
month.unwrap_or(self.iso.date.month),
day.unwrap_or(self.iso.date.day),
hour.unwrap_or(self.iso.time.hour),
minute.unwrap_or(self.iso.time.minute),
second.unwrap_or(self.iso.time.second),
millisecond.unwrap_or(self.iso.time.millisecond),
microsecond.unwrap_or(self.iso.time.microsecond),
nanosecond.unwrap_or(self.iso.time.nanosecond),
overflow,
self.calendar,
)
}
///|
/// `AddDurationToDateTime`: adds a duration, treating each of its days as
/// exactly 24 hours.
///
/// ```mbt check
/// test {
/// let dt = @temporal.PlainDateTime::try_new(2024, 1, 31, hour=23)
/// // The time is added first: 23:00 + 2h rolls into the next day, so the
/// // date arithmetic sees one month and one day.
/// inspect(
/// dt.add(@temporal.Duration::of(months=1, hours=2)),
/// content="2024-03-01T01:00:00",
/// )
/// }
/// ```
pub fn PlainDateTime::add(
self : PlainDateTime,
duration : Duration,
overflow? : Overflow = Constrain,
) -> PlainDateTime raise TemporalError {
let internal = InternalDurationRecord::from_duration_with_24_hour_days(
duration,
)
// The time part is added first; any whole days it produces join the date
// part before the calendar arithmetic runs.
let (days, time_result) = self.iso.time.add(internal.time)
let date_duration = internal.date.adjust(internal.date.days + days)
let added_date = PlainDate::new_unchecked(self.iso.date, self.calendar).calendar_date_add(
date_duration, overflow,
)
{
iso: IsoDateTime::new(added_date.iso, time_result),
calendar: self.calendar,
}
}
///|
/// Subtracts a duration.
pub fn PlainDateTime::subtract(
self : PlainDateTime,
duration : Duration,
overflow? : Overflow = Constrain,
) -> PlainDateTime raise TemporalError {
self.add(duration.negated(), overflow~)
}
///|
/// `DifferencePlainDateTimeWithRounding`.
fn PlainDateTime::diff_with_rounding(
self : PlainDateTime,
other : PlainDateTime,
options : ResolvedRoundingOptions,
) -> InternalDurationRecord raise TemporalError {
if self.iso == other.iso {
return internal_duration_zero
}
self.iso.check_within_limits()
other.iso.check_within_limits()
let diff = self.iso.diff(other.iso, options.largest_unit)
if options.smallest_unit is Nanosecond && options.increment.get() == 1 {
return diff
}
diff.round_relative_duration(
self.iso.as_nanoseconds(),
other.iso.as_nanoseconds(),
self,
None,
utc_only_provider,
options,
)
}
///|
/// `DifferencePlainDateTimeWithTotal`.
fn PlainDateTime::diff_with_total(
self : PlainDateTime,
other : PlainDateTime,
unit : DateTimeUnit,
) -> Double raise TemporalError {
if self.iso == other.iso {
return 0.0
}
self.iso.check_within_limits()
other.iso.check_within_limits()
let diff = self.iso.diff(other.iso, unit)
if unit is Nanosecond {
return diff.time.nanoseconds().to_double()
}
diff.total_relative_duration(
self.iso.as_nanoseconds(),
other.iso.as_nanoseconds(),
self,
None,
utc_only_provider,
unit,
)
}
///|
/// `DifferenceTemporalPlainDateTime`.
fn PlainDateTime::diff(
self : PlainDateTime,
operation : DifferenceOperation,
other : PlainDateTime,
settings : DifferenceSettings,
) -> Duration raise TemporalError {
if self.calendar != other.calendar {
raise RangeError("cannot compare date-times in different calendars")
}
let options = ResolvedRoundingOptions::from_diff_settings(
settings,
operation,
UnitGroup::DateTime,
Day,
Nanosecond,
)
if self.iso == other.iso {
return duration_zero
}
let record = self.diff_with_rounding(other, options)
let result = Duration::from_internal(record, options.largest_unit)
match operation {
Until => result
Since => result.negated()
}
}
///|
/// Returns the duration from this date-time until `other`.
pub fn PlainDateTime::until(
self : PlainDateTime,
other : PlainDateTime,
settings? : DifferenceSettings = DifferenceSettings::default(),
) -> Duration raise TemporalError {
self.diff(Until, other, settings)
}
///|
/// Returns the duration from `other` until this date-time.
pub fn PlainDateTime::since(
self : PlainDateTime,
other : PlainDateTime,
settings? : DifferenceSettings = DifferenceSettings::default(),
) -> Duration raise TemporalError {
self.diff(Since, other, settings)
}
///|
/// Rounds to a multiple of the given unit, which may be as large as a day.
pub fn PlainDateTime::round(
self : PlainDateTime,
options : RoundingOptions,
) -> PlainDateTime raise TemporalError {
let resolved = ResolvedRoundingOptions::from_datetime_options(options)
if resolved.is_noop() {
return self
}
{ iso: self.iso.round(resolved), calendar: self.calendar }
}
///|
/// Renders the date-time in RFC 9557 form.
pub fn PlainDateTime::to_string_with_options(
self : PlainDateTime,
options : ToStringRoundingOptions,
display_calendar : DisplayCalendar,
) -> String raise TemporalError {
let resolved = options.resolve()
let rounding = ResolvedRoundingOptions::from_to_string_options(resolved)
let iso = self.iso.round(rounding)
let buf = StringBuilder::new()
write_date(buf, iso.date.year, iso.date.month, iso.date.day)
buf.write_string("T")
write_time(
buf,
iso.time.hour,
iso.time.minute,
iso.time.second,
iso.time.subsecond_nanoseconds(),
resolved.precision,
true,
)
write_calendar_annotation(buf, self.calendar, display_calendar)
buf.to_string()
}
///|
/// Renders the date-time as `YYYY-MM-DDTHH:MM:SS`.
pub fn PlainDateTime::to_string(self : PlainDateTime) -> String {
try! self.to_string_with_options(
ToStringRoundingOptions::default(),
DisplayCalendar::default(),
)
}