// Copyright (c) 2026 colmugx
// SPDX-License-Identifier: Apache-2.0
///|
/// `full-date` (RFC 3339 5.6): four-digit year, `01`-`12` month and a day that
/// exists in that month, including the Gregorian leap-year rule.
fn format_date(value : String) -> Bool {
format_full_date(value[:])
}
///|
fn format_full_date(text : StringView) -> Bool {
if text.length() != 10 {
return false
}
guard text.get_char(4) == Some('-') && text.get_char(7) == Some('-') else {
return false
}
guard format_uint(text[:4]) is Some(year) else { return false }
guard format_two_digits(text, 5) is Some(month) else { return false }
guard format_two_digits(text, 8) is Some(day) else { return false }
format_calendar_day(year, month, day)
}
///|
fn format_calendar_day(year : Int32, month : Int32, day : Int32) -> Bool {
if month < 1 || month > 12 || day < 1 {
return false
}
let limit = match month {
1 | 3 | 5 | 7 | 8 | 10 | 12 => 31
4 | 6 | 9 | 11 => 30
_ => if format_is_leap_year(year) { 29 } else { 28 }
}
day <= limit
}
///|
/// Proleptic Gregorian leap rule: divisible by 4, not by 100, unless by 400.
fn format_is_leap_year(year : Int32) -> Bool {
year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)
}
///|
/// `full-time` (RFC 3339 5.6): `partial-time` with a mandatory UTC offset.
fn format_time(value : String) -> Bool {
format_full_time(value[:])
}
///|
fn format_full_time(text : StringView) -> Bool {
if text.length() < 9 {
return false
}
guard text.get_char(2) == Some(':') && text.get_char(5) == Some(':') else {
return false
}
guard format_two_digits(text, 0) is Some(hour) else { return false }
guard format_two_digits(text, 3) is Some(minute) else { return false }
guard format_two_digits(text, 6) is Some(second) else { return false }
let mut index = 8
if text.get_char(index) == Some('.') {
index = index + 1
let fraction_start = index
while index < text.length() {
let next = text.get_char(index)
if next is Some(ch) && format_is_digit(ch) {
index = index + 1
} else {
break
}
}
if index == fraction_start {
return false
}
}
let mut offset = 0
let marker = text.get_char(index)
if marker is Some(ch) && (ch == 'Z' || ch == 'z') {
index = index + 1
} else if marker is Some(ch) && (ch == '+' || ch == '-') {
guard format_two_digits(text, index + 1) is Some(offset_hour) else {
return false
}
guard text.get_char(index + 3) == Some(':') else { return false }
guard format_two_digits(text, index + 4) is Some(offset_minute) else {
return false
}
if offset_hour > 23 || offset_minute > 59 {
return false
}
let magnitude = offset_hour * 60 + offset_minute
offset = if ch == '+' { magnitude } else { -magnitude }
index = index + 6
} else {
return false
}
if index != text.length() {
return false
}
if hour > 23 || minute > 59 || second > 60 {
return false
}
if second == 60 {
format_leap_second(hour, minute, offset)
} else {
true
}
}
///|
/// A leap second may only be `:60` when the instant it names is 23:59:60 UTC,
/// so the local hour/minute and the numeric offset must agree.
fn format_leap_second(hour : Int32, minute : Int32, offset : Int32) -> Bool {
let mut utc = (hour * 60 + minute - offset) % 1440
if utc < 0 {
utc = utc + 1440
}
utc == 23 * 60 + 59
}
///|
/// `date-time` (RFC 3339 5.6): `full-date "T" full-time`, where the `T` and
/// the zone designator are case-insensitive.
fn format_date_time(value : String) -> Bool {
let mut split = -1
for i in 0..
if ch == 'T' || ch == 't' {
if split >= 0 {
return false
}
split = i
}
None => return false
}
}
if split != 10 {
return false
}
format_full_date(value[:split]) && format_full_time(value[split + 1:])
}