///|
priv struct EpochState {
mut selected : @epoch_data.Stamp?
}
///|
let epoch_state : EpochState = { selected: None, }
///|
fn selected_epoch() -> @epoch_data.Stamp {
match epoch_state.selected {
Some(value) => value
None => {
// The config setter and default validate this invariant before storage.
let value = match @epoch_data.parse(@config.date_epoch()) {
Ok(value) => value
Err(_) => abort("invalid configured date epoch")
}
epoch_state.selected = Some(value)
value
}
}
}
///|
/// Select and return the date origin. Reading this value locks the selection.
pub fn get_epoch() -> String {
selected_epoch().source
}
///|
/// Select a UTC ISO origin before any numeric date conversion/get_epoch call.
/// Supports signed Int32 astronomical years (including 0), partial dates and
/// times through microseconds. Invalid input leaves the selection unchanged;
/// timezone suffixes and finer-than-microsecond precision are not accepted.
pub fn set_epoch(value : String) -> Unit raise DateError {
if epoch_state.selected is Some(_) {
raise DateError::InvalidDate(
message="set_epoch must be called before dates are converted or epoch is read",
)
}
match @epoch_data.parse(value) {
Ok(value) => epoch_state.selected = Some(value)
Err(message) => raise DateError::InvalidDate(message~)
}
}
///|
fn unix_seconds_to_ordinal(seconds : Int64) -> Double {
(seconds - selected_epoch().seconds).to_double() / 86400.0
}
///|
fn unix_microseconds_to_ordinal(microseconds : Int64) -> Double {
let (seconds, fraction) = delta_split(microseconds, 1_000_000L)
(
(seconds - selected_epoch().seconds).to_double() +
fraction.to_double() / 1_000_000.0
) /
86400.0
}
///|
// Decode a numeric day before timezone conversion or distant-date correction.
// Internal generated calendar coordinates use the forward (whole-second) origin.
fn ordinal_to_unix_microseconds(
value : Double,
generated~ : Bool = false,
) -> Int64 raise DateError {
let epoch = selected_epoch()
if value.is_nan() || value.is_inf() {
raise DateError::InvalidDate(message="ordinal must be finite")
}
let scaled = value * 86_400_000_000.0
if scaled <= -9.223372036854776e18 || scaled >= 9.223372036854776e18 {
raise DateError::InvalidDate(
message="relative ordinal microseconds exceed Int64 range",
)
}
let (seconds, fraction) = delta_split(round_even_to_int64(scaled), 1_000_000L)
let subsecond = fraction +
(if generated { 0L } else { Int64::from_int(epoch.microsecond) })
let absolute_seconds = epoch.seconds + seconds + subsecond / 1_000_000L
if absolute_seconds < -62_135_596_800L || absolute_seconds >= 253_402_300_800L {
raise DateError::InvalidDate(message="ordinal is outside years 1..9999")
}
absolute_seconds * 1_000_000L + subsecond % 1_000_000L
}
///|
fn supported_ordinal(value : Double) -> Bool {
try {
ignore(ordinal_to_unix_microseconds(value))
true
} catch {
_ => false
}
}
///|
fn forward_calendar_date(value : Double) -> DateTime raise DateError {
decoded_ordinal(value, 0, true)
}
///|
fn DateTimeZone::footer_reached(self : DateTimeZone, value : Double) -> Bool {
match self.footer_start_seconds {
None => true
Some(seconds) =>
// TZif permits raw Int64 endpoints far beyond any Gregorian date.
if seconds < -62_135_596_800L {
true
} else if seconds >= 253_402_300_800L {
false
} else {
value >= unix_seconds_to_ordinal(seconds)
}
}
}