///|
priv struct ConciseSettings {
formats : Array[String]
zero_formats : Array[String]
offset_formats : Array[String]
show_offset : Bool
}
///|
/// A batch date formatter with Matplotlib's six-level concise label rules.
/// `format_ticks` updates the last offset; configuration copies are independent.
pub struct ConciseDateFormatter {
priv formatter : DateFormatter
priv mut offset_string : String
}
///|
fn concise_formats(
values : Array[String]?,
defaults : Array[String],
name : String,
) -> Array[String] raise DateError {
match values {
None | Some([]) => defaults.copy()
Some(values) if values.length() == 6 => values.copy()
_ =>
raise DateError::InvalidFormat(
message=name + " must contain six date formats",
)
}
}
///|
/// Construct a concise formatter. tz is resolved when formatting; an omitted
/// tz follows the current timezone rc. The optional format arrays have six entries
/// (years through seconds); empty arrays select defaults. Inputs are copied.
/// `usetex` returns upstream TeX strings; rendering TeX requires backend support.
///
/// ```mbt check
/// test {
/// let formatter = @date.concise_date_formatter()
/// let ticks = [
/// @date.datetime(2024, 1, 1).to_ordinal(),
/// @date.datetime(2024, 1, 2).to_ordinal(),
/// ]
/// assert_eq(formatter.format_ticks(ticks), ["Jan", "02"])
/// assert_eq(formatter.get_offset(), "2024-Jan")
/// }
/// ```
pub fn concise_date_formatter(
tz? : String,
timezone? : DateTimeZone,
formats? : Array[String],
zero_formats? : Array[String],
offset_formats? : Array[String],
show_offset~ : Bool = true,
usetex~ : Bool = false,
) -> ConciseDateFormatter raise DateError {
let timezone_source = formatter_timezone_source(tz, timezone)
let selected_formats = concise_formats(
formats,
["%Y", "%b", "%d", "%H:%M", "%H:%M", "%S.%f"],
"formats",
)
let default_zeros = [
"",
selected_formats[0],
selected_formats[1],
selected_formats[2],
selected_formats[3],
selected_formats[4],
]
if formats is None || formats is Some([]) {
default_zeros[3] = "%b-%d"
}
let settings = {
formats: selected_formats,
zero_formats: concise_formats(zero_formats, default_zeros, "zero_formats"),
offset_formats: concise_formats(
offset_formats,
["", "%Y", "%Y-%b", "%Y-%b-%d", "%Y-%b-%d", "%Y-%b-%d %H:%M"],
"offset_formats",
),
show_offset,
}
{
formatter: {
format: "%Y",
offset_minutes: timezone
.map(zone => zone.initial_offset_minutes())
.unwrap_or(0),
timezone,
timezone_source,
mode: Concise(settings),
usetex,
},
offset_string: "",
}
}
///|
/// Return a standalone date formatter suitable for Axis::set_date_formatter.
pub fn ConciseDateFormatter::as_formatter(
self : ConciseDateFormatter,
) -> DateFormatter {
self.formatter.copy()
}
///|
/// Return the last successful batch's shared date text, initially empty.
pub fn ConciseDateFormatter::get_offset(self : ConciseDateFormatter) -> String {
self.offset_string
}
///|
/// Format a batch and update its shared date text. `inverted` selects the first
/// supplied tick for the offset instead of the last, matching an inverted axis.
/// Empty batches clear the offset; invalid dates raise without changing it.
pub fn ConciseDateFormatter::format_ticks(
self : ConciseDateFormatter,
values : Array[Double],
inverted~ : Bool = false,
) -> Array[String] raise DateError {
let (labels, offset) = self.formatter.tick_layout(values, inverted)
self.offset_string = offset
labels
}
///|
/// Format one value using the default year format, as upstream __call__ does.
pub fn ConciseDateFormatter::format_value(
self : ConciseDateFormatter,
value : Double,
) -> String {
self.formatter.format_value(value)
}
///|
/// Return the format used for individual values (upstream defaultfmt).
pub fn ConciseDateFormatter::default_format(
self : ConciseDateFormatter,
) -> String {
self.formatter.format
}
///|
/// Return a copy with a different individual-value format. Batch labels and
/// format_data_short retain their own formats.
pub fn ConciseDateFormatter::with_default_format(
self : ConciseDateFormatter,
format : String,
) -> ConciseDateFormatter {
{ formatter: { ..self.formatter.copy(), format, }, offset_string: "", }
}
///|
/// Return a configuration copy controlling upstream TeX string output.
pub fn ConciseDateFormatter::with_usetex(
self : ConciseDateFormatter,
usetex : Bool,
) -> ConciseDateFormatter {
{ formatter: self.formatter.with_usetex(usetex), offset_string: "", }
}
///|
/// Format a cursor/status value with a complete date and second-resolution time.
pub fn ConciseDateFormatter::format_data_short(
self : ConciseDateFormatter,
value : Double,
) -> String raise DateError {
format_date(self.formatter.converted_date(value), "%Y-%m-%d %H:%M:%S")
}
///|
/// Return a configuration copy using a fixed label offset, with no prior batch.
pub fn ConciseDateFormatter::with_offset(
self : ConciseDateFormatter,
offset_minutes : Int32,
) -> ConciseDateFormatter raise DateError {
{ formatter: self.formatter.with_offset(offset_minutes), offset_string: "", }
}
///|
/// Return a configuration copy using exact transition-aware label conversion.
pub fn ConciseDateFormatter::with_timezone(
self : ConciseDateFormatter,
timezone : DateTimeZone,
) -> ConciseDateFormatter {
{ formatter: self.formatter.with_timezone(timezone), offset_string: "", }
}
///|
/// Return a configuration copy controlling shared date text visibility.
pub fn ConciseDateFormatter::with_show_offset(
self : ConciseDateFormatter,
show_offset : Bool,
) -> ConciseDateFormatter {
let formatter = self.formatter.copy()
let settings = formatter.concise_settings()
{
formatter: { ..formatter, mode: Concise({ ..settings, show_offset, }), },
offset_string: "",
}
}
///|
pub fn ConciseDateFormatter::formats(
self : ConciseDateFormatter,
) -> Array[String] {
self.formatter.concise_settings().formats.copy()
}
///|
pub fn ConciseDateFormatter::zero_formats(
self : ConciseDateFormatter,
) -> Array[String] {
self.formatter.concise_settings().zero_formats.copy()
}
///|
pub fn ConciseDateFormatter::offset_formats(
self : ConciseDateFormatter,
) -> Array[String] {
self.formatter.concise_settings().offset_formats.copy()
}
///|
pub fn ConciseDateFormatter::show_offset(self : ConciseDateFormatter) -> Bool {
self.formatter.concise_settings().show_offset
}