///|
/// tz_format.mbt — strftime/strptime/RFC3339(chrono_tz-mbt MoonBit 移植)
/// strftime 格式化 + strptime 解析 + RFC3339 format/parse。
///|
/// 星期全名(0=Sunday..6=Saturday)。
let weekday_full : Array[String] = [
"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday",
]
///|
/// 星期缩写(0=Sunday..6=Saturday)。
let weekday_abbr : Array[String] = [
"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat",
]
///|
/// 月份全名(1-12)。
let month_full : Array[String] = [
"", "January", "February", "March", "April", "May", "June", "July", "August", "September",
"October", "November", "December",
]
///|
/// 月份缩写(1-12)。
let month_abbr : Array[String] = [
"", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov",
"Dec",
]
///|
/// 左补零到指定宽度。
fn pad_zero(n : Int64, width : Int32) -> String {
let s = n.to_string()
if s.length() >= width {
s
} else {
let mut r = s
while r.length() < width {
r = "0" + r
}
r
}
}
///|
/// 格式化偏移为 ±HHMM(strftime %z 用)。
fn format_offset_hhmm(offset_sec : Int32) -> String {
let total_min = offset_sec / 60
let sign = if total_min < 0 { "-" } else { "+" }
let abs_min = if total_min < 0 { -total_min } else { total_min }
let h = abs_min / 60
let m = abs_min % 60
"\{sign}\{pad_zero(h.to_int64(), 2)}\{pad_zero(m.to_int64(), 2)}"
}
///|
/// strftime:按格式串格式化 DateTime。
/// 支持:%Y %m %d %H %M %S %Z %z %A %a %B %b %j %% 及字面字符。
pub fn strftime(format : String, dt : DateTime) -> String {
let local_dt = dt.naive_local()
let mut result = ""
let chars = format.to_array()
let n = chars.length()
let mut i = 0
while i < n {
let c = chars[i]
if c == '%' && i + 1 < n {
let spec = chars[i + 1]
let part = match spec {
'Y' => local_dt.year().to_string()
'm' => pad_zero(local_dt.month(), 2)
'd' => pad_zero(local_dt.day(), 2)
'H' => pad_zero(local_dt.hour(), 2)
'M' => pad_zero(local_dt.minute(), 2)
'S' => pad_zero(local_dt.second(), 2)
'Z' => dt.abbreviation()
'z' => format_offset_hhmm(dt.offset_sec())
'A' => weekday_full[local_dt.weekday().to_int()]
'a' => weekday_abbr[local_dt.weekday().to_int()]
'B' => month_full[local_dt.month().to_int()]
'b' => month_abbr[local_dt.month().to_int()]
'j' => pad_zero(local_dt.ordinal(), 3)
'%' => "%"
_ => "\{spec}"
}
result = result + part
i = i + 2
} else {
result = result + c.to_string()
i = i + 1
}
}
result
}
///|
///|
/// 解析十进制数字,返回 (值, 新位置),失败返回 None。
fn parse_digits(
s : Array[Char],
start : Int32,
count : Int32,
) -> (Int64, Int32)? {
let mut val = 0L
let mut i = 0
while i < count {
if start + i >= s.length() {
return None
}
let c = s[start + i]
if c < '0' || c > '9' {
return None
}
val = val * 10L + (c.to_int() - '0'.to_int()).to_int64()
i = i + 1
}
Some((val, start + count))
}
///|
/// 解析十进制数字(Int 索引版),返回 (值, 新位置),失败返回 None。
fn parse_digits_at(
s : Array[Char],
start : Int32,
count : Int32,
) -> (Int64, Int32)? {
let mut val = 0L
let mut i = 0
while i < count {
if start + i >= s.length() {
return None
}
let c = s[start + i]
if c < '0' || c > '9' {
return None
}
val = val * 10L + (c.to_int() - '0'.to_int()).to_int64()
i = i + 1
}
Some((val, start + count))
}
///|
/// 解析连续字母,返回 (字符串, 新位置),失败返回 None。
fn parse_alpha(s : Array[Char], start : Int32) -> (String, Int32)? {
if start >= s.length() {
return None
}
let mut buf = ""
let mut i = start
while i < s.length() {
let c = s[i]
if (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') {
buf = buf + c.to_string()
i = i + 1
} else {
break
}
}
if buf.length() == 0 {
None
} else {
Some((buf, i))
}
}
///|
/// 查表匹配名称(大小写敏感),返回索引,失败返回 None。
fn lookup_name(table : Array[String], name : String) -> Int32? {
let mut i = 0
while i < table.length() {
if table[i] == name {
return Some(i)
}
i = i + 1
}
None
}
///|
/// 解析时区偏移 ±HHMM 或 ±HH:MM,返回 (offset_sec, 新位置),失败返回 None。
fn parse_offset(s : Array[Char], start : Int32) -> (Int32, Int32)? {
if start >= s.length() {
return None
}
let sign : Int32 = if s[start] == '+' {
1
} else if s[start] == '-' {
-1
} else {
return None
}
guard parse_digits_at(s, start + 1, 2) is Some((oh, p1)) else { return None }
let p2 = if p1 < s.length() && s[p1] == ':' { p1 + 1 } else { p1 }
guard parse_digits_at(s, p2, 2) is Some((om, p3)) else { return None }
Some((sign * (oh.to_int() * 3600 + om.to_int() * 60), p3))
}
///|
/// RFC3339 解析:`YYYY-MM-DDTHH:MM:SS±HH:MM` 或 `YYYY-MM-DDTHH:MM:SSZ`。
/// 返回 DateTime?(解析失败返回 None,不抛错,ADR-008 查无→T?)。
pub fn DateTime::parse_rfc3339(s : String) -> DateTime? {
let chars = s.to_array()
let n = chars.length()
// 最短格式:YYYY-MM-DDTHH:MM:SSZ = 20 字符
if n < 20 {
return None
}
// 解析日期时间部分 YYYY-MM-DDTHH:MM:SS
guard parse_digits(chars, 0, 4) is Some((year, p1)) else { return None }
if p1 >= n || chars[p1] != '-' {
return None
}
guard parse_digits(chars, p1 + 1, 2) is Some((month, p2)) else { return None }
if p2 >= n || chars[p2] != '-' {
return None
}
guard parse_digits(chars, p2 + 1, 2) is Some((day, p3)) else { return None }
if p3 >= n || (chars[p3] != 'T' && chars[p3] != 't' && chars[p3] != ' ') {
return None
}
guard parse_digits(chars, p3 + 1, 2) is Some((hour, p4)) else { return None }
if p4 >= n || chars[p4] != ':' {
return None
}
guard parse_digits(chars, p4 + 1, 2) is Some((min, p5)) else { return None }
if p5 >= n || chars[p5] != ':' {
return None
}
guard parse_digits(chars, p5 + 1, 2) is Some((sec, p6)) else { return None }
// 解析时区后缀:Z 或 ±HH:MM
let offset_sec : Int32 = if p6 < n && (chars[p6] == 'Z' || chars[p6] == 'z') {
0
} else {
if p6 + 6 > n {
return None
}
let sign : Int32 = if chars[p6] == '+' {
1
} else if chars[p6] == '-' {
-1
} else {
return None
}
guard parse_digits(chars, p6 + 1, 2) is Some((oh, p7)) else { return None }
if p7 >= n || chars[p7] != ':' {
return None
}
guard parse_digits(chars, p7 + 1, 2) is Some((om, _)) else { return None }
sign * (oh.to_int() * 3600 + om.to_int() * 60)
}
// 构造 NaiveDateTime(本地墙钟)
let naive = NaiveDateTime::make(year, month, day, hour, min, sec)
// UTC 时间戳 = 本地时间戳 - offset
let utc_ts = naive.timestamp() - offset_sec.to_int64()
// 查找匹配的时区
let tz = Tz::from_str("UTC")
match tz {
Some(t) => {
let off_span = @core.FixedTimespan::{ offset: offset_sec, name: "UTC", }
let off = TzOffset::make(t, off_span)
Some(DateTime::make(utc_ts, off))
}
None => None
}
}
///|
/// strptime:按格式串解析输入为 DateTime。
/// 支持:%Y(4位年) %m(2位月) %d(2位日) %H(2位时) %M(2位分) %S(2位秒)
/// %y(2位年,00-68→2000s,69-99→1900s) %j(3位年中天数)
/// %B(月份全名) %b(月份缩写) %A(星期全名) %a(星期缩写)
/// %Z(时区缩写,仅消费) %z(偏移±HHMM/±HH:MM,仅消费) %% 及字面字符。
/// 解析失败返回 None(ADR-008 查无→T?)。
pub fn strptime(format : String, input : String, tz : Tz) -> DateTime? {
let fmt_chars = format.to_array()
let in_chars = input.to_array()
let fmt_n = fmt_chars.length()
let in_n = in_chars.length()
let mut fi = 0
let mut ii = 0
let mut year = 1970L
let mut month = 1L
let mut day = 1L
let mut hour = 0L
let mut minute = 0L
let mut second = 0L
let mut has_yday = false
while fi < fmt_n {
let fc = fmt_chars[fi]
if fc == '%' && fi + 1 < fmt_n {
let spec = fmt_chars[fi + 1]
match spec {
'Y' => {
guard parse_digits_at(in_chars, ii, 4) is Some((v, np)) else {
return None
}
year = v
ii = np
}
'm' => {
guard parse_digits_at(in_chars, ii, 2) is Some((v, np)) else {
return None
}
month = v
ii = np
}
'd' => {
guard parse_digits_at(in_chars, ii, 2) is Some((v, np)) else {
return None
}
day = v
ii = np
}
'H' => {
guard parse_digits_at(in_chars, ii, 2) is Some((v, np)) else {
return None
}
hour = v
ii = np
}
'M' => {
guard parse_digits_at(in_chars, ii, 2) is Some((v, np)) else {
return None
}
minute = v
ii = np
}
'S' => {
guard parse_digits_at(in_chars, ii, 2) is Some((v, np)) else {
return None
}
second = v
ii = np
}
'y' => {
guard parse_digits_at(in_chars, ii, 2) is Some((v, np)) else {
return None
}
year = if v <= 68L { 2000L + v } else { 1900L + v }
ii = np
}
'j' => {
guard parse_digits_at(in_chars, ii, 3) is Some((v, np)) else {
return None
}
day = v
has_yday = true
ii = np
}
'B' => {
guard parse_alpha(in_chars, ii) is Some((name, np)) else {
return None
}
guard lookup_name(month_full, name) is Some(m) else { return None }
month = m.to_int64()
ii = np
}
'b' => {
guard parse_alpha(in_chars, ii) is Some((name, np)) else {
return None
}
guard lookup_name(month_abbr, name) is Some(m) else { return None }
month = m.to_int64()
ii = np
}
'A' => {
guard parse_alpha(in_chars, ii) is Some((name, np)) else {
return None
}
guard lookup_name(weekday_full, name) is Some(_) else { return None }
ii = np
}
'a' => {
guard parse_alpha(in_chars, ii) is Some((name, np)) else {
return None
}
guard lookup_name(weekday_abbr, name) is Some(_) else { return None }
ii = np
}
'Z' => {
guard parse_alpha(in_chars, ii) is Some((_, np)) else { return None }
ii = np
}
'z' => {
guard parse_offset(in_chars, ii) is Some((_, np)) else { return None }
ii = np
}
'%' => {
if ii >= in_n || in_chars[ii] != '%' {
return None
}
ii = ii + 1
}
_ => return None
}
fi = fi + 2
} else {
if ii >= in_n || in_chars[ii] != fc {
return None
}
ii = ii + 1
fi = fi + 1
}
}
let naive = if has_yday {
let d = Date::from_ordinal(year, day)
NaiveDateTime::make(d.year(), d.month(), d.day(), hour, minute, second)
} else {
NaiveDateTime::make(year, month, day, hour, minute, second)
}
match tz.from_local_date_time(naive) {
LocalResult::Single(dt) => Some(dt)
_ => None
}
}