核心结构体详解

1. YearFlags(年份标志)

pub struct YearFlags(pub(super) u8);  // 4位:LWWW

位布局:

  • L(第3位):闰年标志
    • 1 = 平年,0 = 闰年
  • WWW(低3位):上一年最后一天的星期几(1-7)

14种年份类型:

// 平年:A-G
pub(super) const A: YearFlags = YearFlags(COMMON_YEAR | YEAR_STARTS_AFTER_SATURDAY);
// 闰年:AG, BA, CB, ...
pub(super) const AG: YearFlags = YearFlags(LEAP_YEAR | YEAR_STARTS_AFTER_SATURDAY);

2. Mdf(月-日-标志)

pub(super) struct Mdf(u32);  // 位布局:M_MMMD_DDDD_LFFF

位字段:

  • 月份(4位):month << 9
  • 日期(5位):day << 4
  • 年份标志(3位):flags

日期格式转换方法

1. 年月日 → 年日(序数日)转换

创建 Mdf:

impl Mdf {
    pub(super) const fn new(month: u32, day: u32, flags: YearFlags) -> Option<Mdf> {
        match month <= 12 && day <= 31 {
            true => Some(Mdf((month << 9) | (day << 4) | flags as u32)),
            false => None,
        }
    }
}

转换为序数日:

pub(super) const fn ordinal(&self) -> Option<u32> {
    let mdl = self.0 >> 3;  // 移除标志位,得到MDL索引
    match MDL_TO_OL[mdl as usize] {
        XX => None,           // 无效日期
        v => Some((mdl - v as u8 as u32) >> 1),  // 有效日期
    }
}

转换公式: 序数日 = (MDL - 调整值) / 2

2. 年日 → 年月日转换

从序数日创建 Mdf:

pub(super) const fn from_ol(ol: i32, flags: YearFlags) -> Mdf {
    Mdf(((ol as u32 + OL_TO_MDL[ol as usize] as u32) << 3) | flags as u32)
}

提取月份和日期:

pub(super) const fn month(&self) -> u32 {
    let Mdf(mdf) = *self;
    mdf >> 9  // 提取月份
}

pub(super) const fn day(&self) -> u32 {
    let Mdf(mdf) = *self;
    (mdf >> 4) & 0b1_1111  // 提取日期
}

3. 年周日(ISO周日期)转换

ISO周计算:

impl YearFlags {
    pub(super) const fn isoweek_delta(&self) -> u32 {
        let YearFlags(flags) = *self;
        let mut delta = (flags & 0b0111) as u32;  // 取WWW部分
        if delta < 3 { delta += 7; }  // 调整范围
        delta
    }
    
    pub(super) const fn nisoweeks(&self) -> u32 {
        let YearFlags(flags) = *self;
        52 + ((0b0000_0100_0000_0110 >> flags as usize) & 1)  // 52或53周
    }
}

查找表系统

MDL_TO_OL 表(1.5KB)

(月, 日, 闰年标志) 转换为年内序数日:

表结构:

const MDL_TO_OL: &[i8; MAX_MDL as usize + 1] = &[
    XX, XX, 64, 64, ...,  // 1月(无效,1月1日=64)
    XX, XX, 66, 66, ...,  // 2月
    XX, XX, 72, 74, ...,  // 3月(闰年和平年不同)
    // ... 其他月份
];

OL_TO_MDL 表

反向转换表,从序数日计算月和日。

YEAR_TO_FLAGS 表(400年周期)

const YEAR_TO_FLAGS: &[YearFlags; 400] = &[BA, G, F, E, DC, B, A, G, ...];

延迟验证策略

基础验证:

// 只检查月份≤12,日期≤31
pub(super) const fn new(month: u32, day: u32, flags: YearFlags) -> Option<Mdf> {
    match month <= 12 && day <= 31 {
        true => Some(Mdf((month << 9) | (day << 4) | flags as u32)),
        false => None,
    }
}

完整验证延迟到转换时:

// 2月30日可以创建,但转换时会失败
let invalid = Mdf::new(2, 30, flags).unwrap();
assert!(invalid.ordinal().is_none());  // 转换时验证失败

实际转换示例

年月日 → 年日

// 2023年3月15日(平年)→ 年日
let flags = YearFlags::from_year(2023);  // 获取年份标志
let mdf = Mdf::new(3, 15, flags).unwrap();  // 创建Mdf
let ordinal = mdf.ordinal().unwrap();  // 转换为序数日
// 结果:ordinal = 74 (3月15日是一年中的第74天)

年日 → 年月日

// 年日74 → 年月日
let mdf = Mdf::from_ol(74, flags);  // 从序数日创建
let month = mdf.month();  // 3
let day = mdf.day();      // 15

年周日计算

// 计算ISO周信息
let flags = YearFlags::from_year(2023);
let delta = flags.isoweek_delta();  // ISO周计算增量
let weeks = flags.nisoweeks();      // 年份总周数(52或53)

性能优势

  1. 位操作:移位和掩码替代算术运算
  2. 查表优化:1.5KB表适合CPU缓存
  3. 延迟验证:避免不必要的计算
  4. 编译时常量:最大限度利用const fn

这种设计使得日期格式转换在保持数学正确性的同时达到极高的性能。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐