/// The four shapes a moment can have, side by side.
///
/// A date library has one awkward question to answer: what is a "moment" when the
/// text only gave you a day, or only gave you a clock, or gave you both without
/// saying which time zone? This library answers it by keeping the shape rather
/// than filling in the guess — a bare date stays a date, it does not quietly
/// become midnight UTC.
///
/// moon run src/examples/shapes
///|
/// Write a zone the way an offset is usually written. `Offset` counts minutes
/// east of UTC, which is how RFC 3339 and every zone database spell it.
fn zone_text(zone : @moondate.Zone) -> String {
match zone {
@moondate.Utc => "UTC"
@moondate.Offset(minutes) => {
let sign = if minutes < 0 { "-" } else { "+" }
let abs = if minutes < 0 { -minutes } else { minutes }
let hh = abs / 60
let mm = abs % 60
let pad = if mm < 10 { "0" } else { "" }
"\{sign}0\{hh}:\{pad}\{mm}"
}
}
}
///|
fn main raise {
// `parse` accepts four text shapes. Each one lands in a different constructor of
// `Moment`, and the constructor is what decides which questions you can ask.
let zoned = @moondate.parse("2026-09-22T14:30:00+08:00") // Zoned: date, time, zone
let plain = @moondate.parse("2026-09-22T14:30:00") // Plain: date, time
let day = @moondate.parse("2026-09-22") // Day: date only
let clock = @moondate.parse("14:30:00") // Clock: time only
// `Show` echoes the shape back, which is the quickest proof that nothing was
// invented along the way.
println(zoned)
println(plain)
println(day)
println(clock)
println("")
// Only a zoned moment sits on the universal timeline. `epoch` is `None` for the
// other three, and that is the whole point of having four shapes.
fn epoch_text(m : @moondate.Moment) -> String {
match m.epoch() {
Some(seconds) => seconds.to_string()
None => "off the timeline"
}
}
println(epoch_text(zoned))
println(epoch_text(plain))
println(epoch_text(day))
println(epoch_text(clock))
println("")
// The same split shows up in the zone.
fn zone_of(m : @moondate.Moment) -> String {
match m.zone() {
Some(zone) => zone_text(zone)
None => "carries no zone"
}
}
println("zoned " + zone_of(zoned))
println("plain " + zone_of(plain))
println("")
// A bare time has no date, so `date` is `None` — but it does have its clock.
fn date_of(m : @moondate.Moment) -> String {
match m.date() {
Some(date) => date.to_string()
None => "none"
}
}
println("clock date " + date_of(clock))
println("clock time " + clock.time().to_string())
println("")
// When you know the missing half, say it. `combine` puts a date, a time and a
// zone together without going through text at all.
let date = @moondate.Date::new(2026, 9, 22)
let time = @moondate.Time::new(14, 30, second=0)
let built = @moondate.Moment::combine(date, time, zone=@moondate.Offset(480))
println(built)
println(epoch_text(built))
// Leave the zone off and you get a plain moment, off the timeline on purpose.
let floating = @moondate.Moment::combine(date, time)
println(floating)
println(epoch_text(floating))
}