///|
/// posix_extrapolate.mbt — POSIX TZ footer 外推(超出内置 transition 后的 DST 计算)
/// 引入 caijiewei295/tzif-engine 的 parse_footer + apply_posix_rule 实现外推。
/// 当 UTC 时间戳超出内置 FixedTimespanSet 最后一个 transition 时,
/// 用 IANA tzdata 的 POSIX TZ footer(如 EST5EDT,M3.2.0,M11.1.0)外推未来 DST 规则。
///|
/// 构造 TZif footer 格式的 Bytes:\n\n(parse_footer 期望的输入格式)。
fn frame_posix_footer(posix_str : String) -> Bytes {
let arr : Array[Byte] = [b'\n']
for byte in @utf8.encode(posix_str) {
arr.push(byte)
}
arr.push(b'\n')
Bytes::from_array(arr)
}
///|
/// 解析 POSIX TZ 字符串为 PosixTzRule,失败返回 None。
fn parse_posix_rule(posix_str : String) -> @types.PosixTzRule? {
let framed = frame_posix_footer(posix_str)
match @parser.parse_footer(framed, offset=0, version=b'2') {
Ok(Some(rule)) => Some(rule)
_ => None
}
}
///|
/// 查找时区对应的 POSIX TZ 规则,失败返回 None。
fn get_posix_rule(tz : Tz) -> @types.PosixTzRule? {
let name = tz.name()
match @data.lookup_posix_tz(name) {
Some(posix_str) => parse_posix_rule(posix_str)
None => None
}
}
///|
/// 判断 UTC 时间戳是否超出内置 FixedTimespanSet 最后一个 transition。
fn is_beyond_last_transition(span : @core.FixedTimespanSet, ts : Int64) -> Bool {
let rest = span.rest_compact
if rest.length() == 0 {
false
} else {
let (last_ts, _, _) = rest[rest.length() - 1]
ts >= last_ts
}
}
///|
/// 给定 UTC 瞬时,返回其在本时区的偏移,超出内置 transition 时用 POSIX TZ footer 外推。
/// 主路径:内置 FixedTimespanSet binary_search(覆盖历史 + 当前)。
/// Fallback:当 ts 超出最后 transition 且有 POSIX TZ 规则时,用 apply_posix_rule 外推未来 DST。
pub fn Tz::offset_from_utc_extrapolated(
self : Tz,
dt : NaiveDateTime,
) -> TzOffset {
let ts = dt.timestamp()
let span = self.timespans()
if is_beyond_last_transition(span, ts) {
match get_posix_rule(self) {
Some(rule) =>
match @posix.apply_posix_rule(rule, ts) {
Ok((offset, _is_dst, abbr)) => {
let ft = @core.FixedTimespan::{ offset, name: abbr, }
return TzOffset::make(self, ft)
}
Err(_) => ()
}
None => ()
}
}
let i = binary_search(0, span.len(), fn(n) { span.utc_span(n).cmp(ts) })
let idx = match i {
Ok(n) => n
Err(n) => n
}
TzOffset::make(self, span.get(idx))
}
///|
/// 给定本地墙钟,返回 LocalResult[TzOffset],超出内置 transition 时用 POSIX TZ footer 外推。
pub fn Tz::offset_from_local_extrapolated(
self : Tz,
dt : NaiveDateTime,
) -> LocalResult[TzOffset] {
let ts = dt.timestamp()
let span = self.timespans()
if is_beyond_last_transition(span, ts) {
match get_posix_rule(self) {
Some(rule) =>
match @posix.apply_posix_rule(rule, ts) {
Ok((offset, _is_dst, abbr)) => {
let ft = @core.FixedTimespan::{ offset, name: abbr, }
return LocalResult::Single(TzOffset::make(self, ft))
}
Err(_) => ()
}
None => ()
}
}
let i = binary_search(0, span.len(), fn(n) { span.local_span(n).cmp(ts) })
match i {
Ok(0) if span.len() == 1 =>
LocalResult::Single(TzOffset::make(self, span.get(0)))
Ok(0) if span.local_span(1).contains(ts) =>
LocalResult::Ambiguous(
TzOffset::make(self, span.get(0)),
TzOffset::make(self, span.get(1)),
)
Ok(0) => LocalResult::Single(TzOffset::make(self, span.get(0)))
Ok(n) if span.local_span(n - 1).contains(ts) =>
LocalResult::Ambiguous(
TzOffset::make(self, span.get(n - 1)),
TzOffset::make(self, span.get(n)),
)
Ok(n) if n == span.len() - 1 =>
LocalResult::Single(TzOffset::make(self, span.get(n)))
Ok(n) if span.local_span(n + 1).contains(ts) =>
LocalResult::Ambiguous(
TzOffset::make(self, span.get(n)),
TzOffset::make(self, span.get(n + 1)),
)
Ok(n) => LocalResult::Single(TzOffset::make(self, span.get(n)))
Err(_) => LocalResult::None
}
}