// Copyright (c) 2026 colmugx
// SPDX-License-Identifier: Apache-2.0
///|
/// `duration` (RFC 3339 appendix A). The ABNF nests the components, so a later
/// unit may only appear when the unit before it is present as well: `P1Y2D` and
/// `PT1H2S` are not derivable, while `P1M2D` and `PT1M2S` are. Weeks are
/// exclusive, fractional values are not allowed and a time part needs at least
/// one component (`P1YT`, `PT`).
fn format_duration(value : String) -> Bool {
let text = value[:]
if text.length() < 2 || !text.has_prefix("P") {
return false
}
let mut index = 1
// 1 = years, 2 = months, 3 = days; 0 means "no date component yet".
let mut date_rank = 0
// 1 = hours, 2 = minutes, 3 = seconds; 0 means "no time component yet".
let mut time_rank = 0
let mut in_time = false
let mut saw_component = false
while index < text.length() {
if !in_time && text.get_char(index) == Some('T') {
in_time = true
index = index + 1
continue
}
let digits_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 == digits_start || index >= text.length() {
return false
}
let unit = text.get_char(index)
index = index + 1
if in_time {
// dur-hour = hours "H" [dur-minute]; dur-minute = minutes "M" [dur-second]
let rank = match unit {
Some('H') => 1
Some('M') => 2
Some('S') => 3
_ => return false
}
if !(time_rank == 0 ||
(rank == 2 && time_rank == 1) ||
(rank == 3 && time_rank == 2)) {
return false
}
time_rank = rank
} else if unit == Some('W') {
// dur-week = weeks "W" is its own branch of the duration production.
if saw_component || index != text.length() {
return false
}
return true
} else {
// dur-year = years "Y" [dur-month]; dur-month = months "M" [dur-day];
// dur-day = days "D" is also allowed on its own.
let rank = match unit {
Some('Y') => 1
Some('M') => 2
Some('D') => 3
_ => return false
}
if !((date_rank == 0 && rank == 1) ||
(rank == 2 && (date_rank == 0 || date_rank == 1)) ||
(rank == 3 && (date_rank == 0 || date_rank == 2))) {
return false
}
date_rank = rank
}
saw_component = true
}
saw_component && !(in_time && time_rank == 0)
}