///|
/// datetime.mbt — NaiveDateTime / Date / Duration(chrono_tz-mbt MoonBit 移植)
/// 移植自仓颉 src/base/tz/datetime.cj,集成 core2 core/hinnant.mbt 算法。
///|
/// 秒级时长,用于 NaiveDateTime 的加减运算。
pub struct Duration {
secs : Int64
} derive(Eq, Compare, Debug)
///|
pub extend Duration with Eq::{not_equal, equal}
///|
pub extend Duration with Compare::{op_lt, op_le, op_ge, compare, op_gt}
///|
pub extend Duration with Debug::{to_repr}
///|
/// 由秒数直接构造。
pub fn Duration::make(secs : Int64) -> Duration {
Duration::{ secs, }
}
///|
/// 构造 `h` 小时的时长。
pub fn Duration::hours(h : Int64) -> Duration {
Duration::{ secs: h * 3600L, }
}
///|
/// 构造 `m` 分钟的时长。
pub fn Duration::minutes(m : Int64) -> Duration {
Duration::{ secs: m * 60L, }
}
///|
/// 构造 `s` 秒的时长。
pub fn Duration::of_seconds(s : Int64) -> Duration {
Duration::{ secs: s, }
}
///|
/// 零时长。
pub fn Duration::zero() -> Duration {
Duration::{ secs: 0L, }
}
///|
/// 时长加法。
pub fn Duration::op_add(self : Duration, rhs : Duration) -> Duration {
Duration::{ secs: self.secs + rhs.secs, }
}
///|
/// 时长减法。
pub fn Duration::op_sub(self : Duration, rhs : Duration) -> Duration {
Duration::{ secs: self.secs - rhs.secs, }
}
///|
/// 无时区墙钟时刻(year/month/day/hour/min/sec),不携带任何 UTC 或偏移信息。
pub struct NaiveDateTime {
year : Int64
month : Int64
day : Int64
hour : Int64
min : Int64
sec : Int64
} derive(Eq, Compare, Debug)
///|
pub extend NaiveDateTime with Eq::{not_equal, equal}
///|
pub extend NaiveDateTime with Compare::{op_lt, op_le, op_ge, compare, op_gt}
///|
pub extend NaiveDateTime with Debug::{to_repr}
///|
/// 向负无穷取整的整数除法。
fn floor_div(a : Int64, b : Int64) -> Int64 {
let q = a / b
if a % b != 0L && (a < 0L) != (b < 0L) {
q - 1L
} else {
q
}
}
///|
/// 判断是否闰年。
pub fn NaiveDateTime::is_leap_year(year : Int64) -> Bool {
(year % 4L == 0L && year % 100L != 0L) || year % 400L == 0L
}
///|
/// 返回某年某月的天数。
pub fn NaiveDateTime::days_in_month(year : Int64, month : Int64) -> Int64 {
let days : Array[Int64] = [
31L, 28L, 31L, 30L, 31L, 30L, 31L, 31L, 30L, 31L, 30L, 31L,
]
if month == 2L && NaiveDateTime::is_leap_year(year) {
29L
} else if month >= 1L && month <= 12L {
days[month.to_int() - 1]
} else {
0L
}
}
///|
/// 构造 NaiveDateTime,非法日期/时间用 fail! 抛错(S4 改为 raise TzError)。
pub fn NaiveDateTime::make(
year : Int64,
month : Int64,
day : Int64,
hour : Int64,
min : Int64,
sec : Int64,
) -> NaiveDateTime {
if month < 1L || month > 12L {
abort("Invalid month: \{month}")
}
let dim = NaiveDateTime::days_in_month(year, month)
if day < 1L || day > dim {
abort("Invalid day: \{day} for \{year}-\{month} (max \{dim})")
}
if hour < 0L || hour > 23L {
abort("Invalid hour: \{hour}")
}
if min < 0L || min > 59L {
abort("Invalid minute: \{min}")
}
if sec < 0L || sec > 59L {
abort("Invalid second: \{sec}")
}
NaiveDateTime::{ year, month, day, hour, min, sec, }
}
///|
/// 由 Unix 时间戳构造 NaiveDateTime(UTC,Hinnant days→civil 算法)。
pub fn NaiveDateTime::from_timestamp(unix_secs : Int64) -> NaiveDateTime {
let days = floor_div(unix_secs, 86400L)
let rem = unix_secs - days * 86400L
let hour = rem / 3600L
let min = rem % 3600L / 60L
let sec = rem % 60L
let (year, month, day) = @core.civil_from_days(days)
NaiveDateTime::{ year, month, day, hour, min, sec, }
}
///|
/// 转为 Unix 时间戳(Hinnant civil→days 算法,支持负时间戳)。
pub fn NaiveDateTime::timestamp(self : NaiveDateTime) -> Int64 {
let days = @core.days_from_civil(self.year, self.month, self.day)
days * 86400L + self.hour * 3600L + self.min * 60L + self.sec
}
///|
/// 年分量。
pub fn NaiveDateTime::year(self : NaiveDateTime) -> Int64 {
self.year
}
///|
/// 月分量。
pub fn NaiveDateTime::month(self : NaiveDateTime) -> Int64 {
self.month
}
///|
/// 日分量。
pub fn NaiveDateTime::day(self : NaiveDateTime) -> Int64 {
self.day
}
///|
/// 时分量。
pub fn NaiveDateTime::hour(self : NaiveDateTime) -> Int64 {
self.hour
}
///|
/// 分分量。
pub fn NaiveDateTime::minute(self : NaiveDateTime) -> Int64 {
self.min
}
///|
/// 秒分量。
pub fn NaiveDateTime::second(self : NaiveDateTime) -> Int64 {
self.sec
}
///|
/// 星期(0=周日..6=周六)。
pub fn NaiveDateTime::weekday(self : NaiveDateTime) -> Int64 {
let days = @core.days_from_civil(self.year, self.month, self.day)
let wd = (days + 4L) % 7L
(wd + 7L) % 7L
}
///|
/// 年内序日(1..366)。
pub fn NaiveDateTime::ordinal(self : NaiveDateTime) -> Int64 {
let days = @core.days_from_civil(self.year, self.month, self.day)
let year_start = @core.days_from_civil(self.year, 1L, 1L)
days - year_start + 1L
}
///|
/// 加秒数。
pub fn NaiveDateTime::add_seconds(
self : NaiveDateTime,
secs : Int64,
) -> NaiveDateTime {
NaiveDateTime::from_timestamp(self.timestamp() + secs)
}
///|
/// 减秒数。
pub fn NaiveDateTime::sub_seconds(
self : NaiveDateTime,
secs : Int64,
) -> NaiveDateTime {
NaiveDateTime::from_timestamp(self.timestamp() - secs)
}
///|
/// 加天数。
pub fn NaiveDateTime::add_days(
self : NaiveDateTime,
days : Int64,
) -> NaiveDateTime {
NaiveDateTime::add_seconds(self, days * 86400L)
}
///|
/// 格式化为 "YYYY-MM-DDTHH:MM:SS"。
pub fn NaiveDateTime::to_rfc_string(self : NaiveDateTime) -> String {
let pad4 = fn(n : Int64) -> String {
let s = n.to_string()
if s.length() >= 4 {
s
} else {
let mut r = s
while r.length() < 4 {
r = "0" + r
}
r
}
}
let pad2 = fn(n : Int64) -> String {
let s = n.to_string()
if s.length() >= 2 {
s
} else {
"0" + s
}
}
"\{pad4(self.year)}-\{pad2(self.month)}-\{pad2(self.day)}T\{pad2(self.hour)}:\{pad2(self.min)}:\{pad2(self.sec)}"
}
///|
/// 格式化为 "YYYY-MM-DD HH:MM:SS"。
pub fn NaiveDateTime::to_date_time_string(self : NaiveDateTime) -> String {
let pad4 = fn(n : Int64) -> String {
let s = n.to_string()
if s.length() >= 4 {
s
} else {
let mut r = s
while r.length() < 4 {
r = "0" + r
}
r
}
}
let pad2 = fn(n : Int64) -> String {
let s = n.to_string()
if s.length() >= 2 {
s
} else {
"0" + s
}
}
"\{pad4(self.year)}-\{pad2(self.month)}-\{pad2(self.day)} \{pad2(self.hour)}:\{pad2(self.min)}:\{pad2(self.sec)}"
}
///|
/// 仅有年月日的日期,不携带时区信息。
pub struct Date {
year : Int64
month : Int64
day : Int64
} derive(Eq, Compare, Debug)
///|
pub extend Date with Eq::{not_equal, equal}
///|
pub extend Date with Compare::{op_lt, op_le, op_ge, compare, op_gt}
///|
pub extend Date with Debug::{to_repr}
///|
/// 构造 Date,非法日期用 fail! 抛错。
pub fn Date::make(year : Int64, month : Int64, day : Int64) -> Date {
if month < 1L || month > 12L {
abort("Invalid month: \{month}")
}
let dim = NaiveDateTime::days_in_month(year, month)
if day < 1L || day > dim {
abort("Invalid day: \{day} for \{year}-\{month}")
}
Date::{ year, month, day, }
}
///|
/// 由年 + 年内序日构造 Date。
pub fn Date::from_ordinal(year : Int64, ordinal : Int64) -> Date {
let year_start = @core.days_from_civil(year, 1L, 1L)
let days = year_start + ordinal - 1L
let (y, m, d) = @core.civil_from_days(days)
Date::{ year: y, month: m, day: d, }
}
///|
/// 年分量。
pub fn Date::year(self : Date) -> Int64 {
self.year
}
///|
/// 月分量。
pub fn Date::month(self : Date) -> Int64 {
self.month
}
///|
/// 日分量。
pub fn Date::day(self : Date) -> Int64 {
self.day
}
///|
/// 转为 NaiveDateTime(时分秒为零)。
pub fn Date::and_hms(
self : Date,
h : Int64,
m : Int64,
s : Int64,
) -> NaiveDateTime {
NaiveDateTime::make(self.year, self.month, self.day, h, m, s)
}
///|
/// 星期(0=周日..6=周六)。
pub fn Date::weekday(self : Date) -> Int64 {
let days = @core.days_from_civil(self.year, self.month, self.day)
let wd = (days + 4L) % 7L
(wd + 7L) % 7L
}
///|
/// 年内序日(1..366)。
pub fn Date::ordinal(self : Date) -> Int64 {
let days = @core.days_from_civil(self.year, self.month, self.day)
let year_start = @core.days_from_civil(self.year, 1L, 1L)
days - year_start + 1L
}
///|
/// UTC 瞬时 + 时区偏移(TzOffset),表示一个时区感知的绝对时刻。
pub struct DateTime {
naive_utc : Int64
off : TzOffset
} derive(Eq, Debug)
///|
pub extend DateTime with Eq::{not_equal, equal}
///|
pub extend DateTime with Debug::{to_repr}
///|
/// 由 UTC 时间戳和偏移构造。
pub fn DateTime::make(naive_utc : Int64, offset : TzOffset) -> DateTime {
DateTime::{ naive_utc, off: offset, }
}
///|
/// 返回 UTC 时间戳。
pub fn DateTime::timestamp(self : DateTime) -> Int64 {
self.naive_utc
}
///|
/// 返回偏移。
pub fn DateTime::offset(self : DateTime) -> TzOffset {
self.off
}
///|
/// 返回偏移秒数。
pub fn DateTime::offset_sec(self : DateTime) -> Int32 {
self.off.offset_sec()
}
///|
/// 返回偏移缩写。
pub fn DateTime::abbreviation(self : DateTime) -> String {
self.off.abbreviation()
}
///|
/// 返回 IANA 时区 ID。
pub fn DateTime::tz_id(self : DateTime) -> String {
self.off.tz_id()
}
///|
/// 重新绑定到新时区(UTC 瞬时不变,偏移重算)。
pub fn DateTime::with_timezone(self : DateTime, tz : Tz) -> DateTime {
let naive = NaiveDateTime::from_timestamp(self.naive_utc)
let off = tz.offset_from_utc_date_time(naive)
DateTime::{ naive_utc: self.naive_utc, off, }
}
///|
/// 返回本地墙钟时刻(UTC + offset)。
pub fn DateTime::naive_local(self : DateTime) -> NaiveDateTime {
NaiveDateTime::from_timestamp(
self.naive_utc + self.off.offset_sec().to_int64(),
)
}
///|
/// 格式化为 "YYYY-MM-DD HH:MM:SS ABBR"。
pub fn DateTime::to_string(self : DateTime) -> String {
let local_dt = self.naive_local()
"\{local_dt.to_date_time_string()} \{self.abbreviation()}"
}
///|
/// 格式化偏移为 ±HH:MM(用于 RFC3339)。
fn format_offset(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
let pad2 = fn(n : Int32) -> String {
let s = n.to_string()
if s.length() >= 2 {
s
} else {
"0" + s
}
}
"\{sign}\{pad2(h)}:\{pad2(m)}"
}
///|
/// 格式化为 RFC3339 "YYYY-MM-DDTHH:MM:SS±HH:MM"。
pub fn DateTime::to_rfc3339(self : DateTime) -> String {
let local_dt = self.naive_local()
"\{local_dt.to_rfc_string()}\{format_offset(self.off.offset_sec())}"
}
///|
/// 把本地墙钟 `dt` 解析为 UTC 瞬时,返回 LocalResult[DateTime]。
pub fn Tz::from_local_date_time(
self : Tz,
dt : NaiveDateTime,
) -> LocalResult[DateTime] {
let lr = self.offset_from_local_date_time(dt)
match lr {
LocalResult::Single(o) =>
LocalResult::Single(DateTime::{
naive_utc: dt.timestamp() - o.offset_sec().to_int64(),
off: o,
})
LocalResult::Ambiguous(a, b) =>
LocalResult::Ambiguous(
DateTime::{
naive_utc: dt.timestamp() - a.offset_sec().to_int64(),
off: a,
},
DateTime::{
naive_utc: dt.timestamp() - b.offset_sec().to_int64(),
off: b,
},
)
LocalResult::None => LocalResult::None
}
}
///|
/// 由 Date + 时分秒 + 时区构造,返回 LocalResult[DateTime](含 DST 歧义/缺口)。
pub fn Date::and_hms_opt(
self : Date,
tz : Tz,
h : Int64,
m : Int64,
s : Int64,
) -> LocalResult[DateTime] {
let naive = NaiveDateTime::make(self.year, self.month, self.day, h, m, s)
tz.from_local_date_time(naive)
}
///|
/// 由 Date + 时分秒 + 时区构造,DST 缺口/歧义时 abort。
pub fn Date::and_hms_tz(
self : Date,
tz : Tz,
h : Int64,
m : Int64,
s : Int64,
) -> DateTime {
let naive = NaiveDateTime::make(self.year, self.month, self.day, h, m, s)
match tz.from_local_date_time(naive) {
LocalResult::Single(dt) => dt
_ =>
abort("Date::and_hms_tz: local time falls in a DST gap or is ambiguous")
}
}