///|
/// String or callable entries in AutoDateFormatter.scaled. Scalar callbacks
/// receive the original numeric value and optional position; they own TeX output.
/// Array callbacks are used by the explicit format_array/format_dates methods.
pub(all) enum DateScaleFormat {
Text(String)
Func((Double, Int32?) -> String)
ValuesFunc((Array[Double], Int32?) -> Array[String])
DatesFunc((Array[DateTime], Int32?) -> Array[String])
}
///|
priv struct AutoSettings {
scaled : Array[(Double, DateScaleFormat)]
unit : Double
locator : DateLocator?
}
///|
pub struct AutoDateFormatter {
priv formatter : DateFormatter
}
///|
/// Construct a locator-unit formatter and capture the rc format table. Text
/// entries resolve the original tz argument at each call (the current timezone
/// rc if omitted); custom callbacks receive original inputs without resolution.
/// The first scaled key >= the base unit wins, regardless of locator interval.
/// An omitted locator uses the upstream missing-unit fallback of one day.
///
/// ```mbt check
/// test {
/// let formatter = @date.AutoDateFormatter::new(
/// locator=@date.minute_locator(interval=30),
/// )
/// assert_eq(formatter.unit_scale(), 1.0 / 1440.0)
/// assert_eq(formatter.format_value(0.0), "01 00:00")
/// }
/// ```
pub fn AutoDateFormatter::new(
locator? : DateLocator,
tz? : String,
timezone? : DateTimeZone,
default_format~ : String = "%Y-%m-%d",
usetex~ : Bool = false,
) -> AutoDateFormatter raise DateError {
let zone = selected_timezone(tz, timezone)
let timezone_source = formatter_timezone_source(tz, timezone)
let formats = @config.date_autoformats()
let units = [
365.0,
30.0,
1.0,
1.0 / 24.0,
1.0 / 1440.0,
1.0 / 86400.0,
1.0 / 86_400_000_000.0,
]
let scaled : Array[(Double, DateScaleFormat)] = []
for index in 0.. entry.0)
{
formatter: {
format: default_format,
offset_minutes: zone.initial_offset_minutes(),
timezone: Some(zone),
timezone_source,
mode: Auto({
scaled,
unit: locator.map(value => value.unit_scale()).unwrap_or(1.0),
locator,
}),
usetex,
},
}
}
///|
fn DateFormatter::auto_settings(self : DateFormatter) -> AutoSettings {
match self.mode {
Auto(settings) => settings
_ => abort("automatic formatter invariant")
}
}
///|
fn AutoSettings::selected(
self : AutoSettings,
default_format : String,
) -> DateScaleFormat {
for entry in self.scaled {
if entry.0 >=
self.locator.map(value => value.unit_scale()).unwrap_or(self.unit) {
return entry.1
}
}
Text(default_format)
}
///|
pub fn AutoDateFormatter::as_formatter(
self : AutoDateFormatter,
) -> DateFormatter {
self.formatter.copy()
}
///|
pub fn AutoDateFormatter::unit_scale(self : AutoDateFormatter) -> Double {
let settings = self.formatter.auto_settings()
settings.locator.map(value => value.unit_scale()).unwrap_or(settings.unit)
}
///|
pub fn AutoDateFormatter::default_format(self : AutoDateFormatter) -> String {
self.formatter.format
}
///|
pub fn AutoDateFormatter::scaled(
self : AutoDateFormatter,
) -> Array[(Double, DateScaleFormat)] {
self.formatter.auto_settings().scaled.copy()
}
///|
/// Replace the scale dictionary. Repeated keys take the last value. Negative
/// and infinite keys are supported; NaN keys are rejected as unordered.
pub fn AutoDateFormatter::with_scaled(
self : AutoDateFormatter,
entries : Array[(Double, DateScaleFormat)],
) -> AutoDateFormatter raise DateError {
let scaled : Array[(Double, DateScaleFormat)] = []
for entry in entries {
if entry.0.is_nan() {
raise DateError::InvalidFormat(message="date scaled keys must not be NaN")
}
let mut replaced = false
for index in 0.. entry.0)
let settings = self.formatter.auto_settings()
{
formatter: { ..self.formatter.copy(), mode: Auto({ ..settings, scaled, }), },
}
}
///|
/// Set one scale entry while retaining the remaining dictionary.
pub fn AutoDateFormatter::with_scale(
self : AutoDateFormatter,
scale : Double,
format : DateScaleFormat,
) -> AutoDateFormatter raise DateError {
let entries = self.scaled()
entries.push((scale, format))
self.with_scaled(entries)
}
///|
/// Copy the formatter for a new concrete locator.
pub fn AutoDateFormatter::with_locator(
self : AutoDateFormatter,
locator : DateLocator,
) -> AutoDateFormatter {
let settings = self.formatter.auto_settings()
{
formatter: {
..self.formatter.copy(),
mode: Auto({
..settings,
scaled: settings.scaled.copy(),
locator: Some(locator),
}),
},
}
}
///|
/// Bind a custom locator's base unit. Standard axes derive this automatically
/// from the actual major/minor locator, including after replacement or zoom.
pub fn AutoDateFormatter::with_locator_unit(
self : AutoDateFormatter,
unit : Double,
) -> AutoDateFormatter {
{ formatter: self.formatter.with_locator_unit(unit), }
}
///|
pub fn AutoDateFormatter::with_default_format(
self : AutoDateFormatter,
format : String,
) -> AutoDateFormatter {
{ formatter: { ..self.formatter.copy(), format, }, }
}
///|
pub fn AutoDateFormatter::with_offset(
self : AutoDateFormatter,
offset_minutes : Int32,
) -> AutoDateFormatter raise DateError {
{ formatter: self.formatter.with_offset(offset_minutes), }
}
///|
pub fn AutoDateFormatter::with_timezone(
self : AutoDateFormatter,
timezone : DateTimeZone,
) -> AutoDateFormatter {
{ formatter: self.formatter.with_timezone(timezone), }
}
///|
pub fn AutoDateFormatter::with_usetex(
self : AutoDateFormatter,
usetex : Bool,
) -> AutoDateFormatter {
{ formatter: self.formatter.with_usetex(usetex), }
}
///|
pub fn AutoDateFormatter::format_value(
self : AutoDateFormatter,
value : Double,
pos? : Int32,
) -> String raise DateError {
self.formatter.auto_value(value, pos)
}
///|
/// Standard Formatter.format_ticks semantics: call each scalar with its index.
pub fn AutoDateFormatter::format_ticks(
self : AutoDateFormatter,
values : Array[Double],
) -> Array[String] raise DateError {
let (labels, _) = self.formatter.tick_layout(values, false)
labels
}
///|
/// Pass an entire numeric array unchanged to the selected ValuesFunc callback.
pub fn AutoDateFormatter::format_array(
self : AutoDateFormatter,
values : Array[Double],
pos? : Int32,
) -> Array[String] raise DateError {
match self.formatter.auto_settings().selected(self.formatter.format) {
ValuesFunc(function) => function(values, pos)
_ =>
raise DateError::InvalidFormat(
message="array date input requires a ValuesFunc scale entry",
)
}
}
///|
/// Pass original date objects to a DatesFunc callback without numeric conversion.
pub fn AutoDateFormatter::format_dates(
self : AutoDateFormatter,
values : Array[DateTime],
pos? : Int32,
) -> Array[String] raise DateError {
match self.formatter.auto_settings().selected(self.formatter.format) {
DatesFunc(function) => function(values, pos)
_ =>
raise DateError::InvalidFormat(
message="date-array input requires a DatesFunc scale entry",
)
}
}