///|
fn parse_tagged_timestamp(tag : String, raw : String) -> Yaml raise ParseError {
if is_valid_timestamp(raw) {
Yaml::String(raw)
} else {
raise ParseError::InvalidTagValue(tag~, value=raw)
}
}
///|
fn is_valid_timestamp(raw : String) -> Bool {
let chars = raw.to_array()
if chars.length() < 10 {
return false
}
let year = parse_four(chars, 0).unwrap_or(-1)
if year < 0 {
return false
}
if chars[4] != '-' || chars[7] != '-' {
return false
}
let month = parse_two(chars, 5).unwrap_or(0)
let day = parse_two(chars, 8).unwrap_or(0)
if month < 1 || month > 12 {
return false
}
let max_day = days_in_month(year, month)
if day < 1 || day > max_day {
return false
}
if chars.length() == 10 {
return true
}
let sep = chars[10]
if !(sep is 'T' || sep is 't' || sep is ' ') {
return false
}
if chars.length() < 19 {
return false
}
let hour = parse_two(chars, 11).unwrap_or(-1)
let minute = parse_two(chars, 14).unwrap_or(-1)
let second = parse_two(chars, 17).unwrap_or(-1)
if chars[13] != ':' || chars[16] != ':' {
return false
}
if hour < 0 || hour > 23 {
return false
}
if minute < 0 || minute > 59 {
return false
}
if second < 0 || second > 60 {
return false
}
let mut idx = 19
if idx < chars.length() && chars[idx] is '.' {
idx = idx + 1
let start = idx
while idx < chars.length() && chars[idx].is_ascii_digit() {
idx = idx + 1
}
if idx == start {
return false
}
}
if idx == chars.length() {
return true
}
let tz = chars[idx]
if tz is 'Z' || tz is 'z' {
return idx + 1 == chars.length()
}
if tz is '+' || tz is '-' {
idx = idx + 1
if idx + 1 >= chars.length() {
return false
}
let tz_hour = parse_two(chars, idx).unwrap_or(-1)
if tz_hour < 0 || tz_hour > 23 {
return false
}
idx = idx + 2
if idx == chars.length() {
return true
}
if chars[idx] != ':' {
return false
}
idx = idx + 1
if idx + 1 >= chars.length() {
return false
}
let tz_minute = parse_two(chars, idx).unwrap_or(-1)
if tz_minute < 0 || tz_minute > 59 {
return false
}
idx = idx + 2
return idx == chars.length()
}
false
}
///|
fn parse_two(chars : Array[Char], index : Int32) -> Int32? {
if index + 1 >= chars.length() {
return None
}
match digit_value(chars[index]) {
None => None
Some(a) =>
match digit_value(chars[index + 1]) {
None => None
Some(b) => Some(a * 10 + b)
}
}
}
///|
fn parse_four(chars : Array[Char], index : Int32) -> Int32? {
if index + 3 >= chars.length() {
return None
}
match digit_value(chars[index]) {
None => None
Some(a) =>
match digit_value(chars[index + 1]) {
None => None
Some(b) =>
match digit_value(chars[index + 2]) {
None => None
Some(c) =>
match digit_value(chars[index + 3]) {
None => None
Some(d) => Some(((a * 10 + b) * 10 + c) * 10 + d)
}
}
}
}
}
///|
fn digit_value(c : Char) -> Int32? {
if c.is_ascii_digit() {
Some(c.to_int() - '0'.to_int())
} else {
None
}
}
///|
fn is_leap_year(year : Int32) -> Bool {
if year % 400 == 0 {
true
} else if year % 100 == 0 {
false
} else {
year % 4 == 0
}
}
///|
fn days_in_month(year : Int32, month : Int32) -> Int32 {
match month {
1 | 3 | 5 | 7 | 8 | 10 | 12 => 31
4 | 6 | 9 | 11 => 30
2 => if is_leap_year(year) { 29 } else { 28 }
_ => 0
}
}