///|
/// Convert this value to Matplotlib's floating-point day representation.
pub fn DateTime::to_ordinal(self : DateTime) -> Double {
  unix_microseconds_to_ordinal(rruleset_instant(self))
}

///|
/// Convert a Matplotlib UTC day value to a date, rounding to the nearest
/// microsecond and applying an optional fixed offset for the returned fields.
/// `offset_seconds` retains sub-minute history; nonzero `offset_minutes` and
/// `offset_seconds` cannot be supplied together.
pub fn from_ordinal(
  value : Double,
  offset_minutes~ : Int32 = 0,
  offset_seconds? : Int32,
) -> DateTime raise DateError {
  let offset_seconds = resolve_offset_seconds(offset_minutes, offset_seconds)
  decoded_ordinal(value, offset_seconds, false)
}

///|
fn decoded_ordinal(
  value : Double,
  offset_seconds : Int32,
  generated : Bool,
) -> DateTime raise DateError {
  let total_microseconds = ordinal_to_unix_microseconds(value, generated~)
  let mut local_microseconds = total_microseconds +
    Int64::from_int(offset_seconds) * 1_000_000L
  // Matplotlib applies the distant-date 20-microsecond correction after
  // converting to the requested timezone, so the correction follows the
  // local clock's microsecond field across midnight and offset boundaries.
  if Double::abs(value) > 70.0 * 365.0 {
    let second = locator_floor_div64(local_microseconds, 1_000_000L)
    let fraction = local_microseconds - second * 1_000_000L
    local_microseconds = second * 1_000_000L +
      round_even_to_int64(fraction.to_double() / 20.0) * 20L
  }
  datetime_from_microseconds(local_microseconds, offset_seconds)
}

///|
/// Convert an exact local microsecond count to a validated DateTime.
fn datetime_from_microseconds(
  local_microseconds : Int64,
  offset_seconds : Int32,
) -> DateTime raise DateError {
  let day_number = floor_divide_microseconds(local_microseconds)
  let remainder = local_microseconds - day_number * 86_400_000_000L
  let total_seconds = remainder / 1_000_000L
  let microsecond = (remainder % 1_000_000L).to_int()
  let (year, month, day) = civil_from_days(day_number.to_int())
  let hour = (total_seconds / 3600L).to_int()
  let minute = (total_seconds % 3600L / 60L).to_int()
  let second = (total_seconds % 60L).to_int()
  datetime(
    year,
    month,
    day,
    hour~,
    minute~,
    second~,
    microsecond~,
    offset_seconds~,
  )
}

///|
// Convert a nonnegative or negative floating-point value to an Int64 using
// the ties-to-even rule used by NumPy's round.  The supported ordinal range
// is far below the point where the intermediate value can overflow Int64.
fn round_even_to_int64(value : Double) -> Int64 {
  let lower = value.floor()
  let fraction = value - lower
  let lower_integer = lower.to_int64()
  if fraction < 0.5 {
    lower_integer
  } else if fraction > 0.5 {
    lower_integer + 1L
  } else if lower_integer % 2L == 0L {
    lower_integer
  } else {
    lower_integer + 1L
  }
}

///|
// Floor division by the positive number of microseconds in a day.
fn floor_divide_microseconds(value : Int64) -> Int64 {
  let quotient = value / 86_400_000_000L
  if value % 86_400_000_000L < 0L {
    quotient - 1L
  } else {
    quotient
  }
}

///|
/// Matplotlib-compatible name for converting a date to a numeric day value.
pub fn date2num(value : DateTime) -> Double {
  value.to_ordinal()
}

///|
/// Convert a numeric UTC day value using the current timezone rc, unless an
/// explicit tz string, timezone object or fixed offset is supplied. Explicit
/// offset_minutes=0 selects UTC. from_ordinal remains the legacy UTC helper.
pub fn num2date(
  value : Double,
  tz? : String,
  timezone? : DateTimeZone,
  offset_minutes? : Int32,
  offset_seconds? : Int32,
) -> DateTime raise DateError {
  if offset_minutes is Some(_) || offset_seconds is Some(_) {
    if tz is Some(_) || timezone is Some(_) {
      raise DateError::InvalidDate(
        message="use either a timezone or fixed offset",
      )
    }
    return from_ordinal(
      value,
      offset_minutes=offset_minutes.unwrap_or(0),
      offset_seconds?,
    )
  }
  selected_timezone(tz, timezone).to_datetime(value)
}