【printpdf】date.rs 文件详细解析
一、项目背景与设计目标
这是 printpdf 库的日期时间模块,专门为 PDF 文档生成 设计。主要目标是为PDF文件提供符合PDF标准的元数据时间戳支持。
二、核心数据结构
1. 基础结构体
// 日期结构体 - 内存优化设计
pub struct Date {pub year: i32,   // 支持负年份(历史日期)pub month: u8,   // 1-12,最小内存占用pub day: u8,     // 1-31,最小内存占用
}// 时间结构体 - 精确到毫秒
pub struct Time {pub hour: u8,           // 0-23pub minute: u8,         // 0-59  pub second: u8,         // 0-59pub millisecond: u16,   // 0-999
}// 时区偏移结构体
pub struct Offset {pub hours: i8,          // -12 到 +14pub minutes: i8,        // 0-59pub seconds: i8,        // 0-59pub milliseconds: i16,  // 0-999
}// 完整日期时间
pub struct DateTime {pub date: Date,pub time: Time, pub offset: Offset,     // 包含时区信息
}
2. 月份枚举
pub enum Month {January = 1,    // 明确从1开始,符合自然习惯February,       // 自动递增// ... 其他月份December,
}
特征分析:
- 所有结构体都实现了完整的trait集合(
Debug, Clone, Copy等) - 内存布局紧凑,适合PDF文档的序列化需求
 - 支持时区信息,符合国际化PDF标准
 
三、多平台时间获取实现
1. 原生平台实现
#[cfg(not(target_arch = "wasm32"))]
pub fn now() -> Self {use time::OffsetDateTime; // 依赖time库let now = OffsetDateTime::now_utc();Self {date: Date {year: now.year(),month: now.month() as u8,  // 类型转换day: now.day(),},time: Time {hour: now.hour(),minute: now.minute(),second: now.second(),millisecond: now.millisecond(),},offset: Offset {hours: now.offset().whole_hours() as i8,minutes: now.offset().minutes_past_hour() as i8,seconds: 0,milliseconds: 0,},}
}
2. 浏览器WASM实现
#[cfg(all(feature = "js-sys", target_arch = "wasm32"))]
pub fn now() -> Self {use js_sys::Date as JsDate;let js_date = JsDate::new_0();let month = (js_date.get_month() + 1) as u8; // JS月份从0开始let tz_offset_minutes = js_date.get_timezone_offset() as i16;let offset_hours = -(tz_offset_minutes / 60) as i8; // 时区计算let offset_minutes = -(tz_offset_minutes % 60) as i8;// ... 构建DateTime
}
3. 回退实现
#[cfg(其他WASM环境)]
pub fn now() -> Self {Self::epoch() // 返回Unix纪元作为安全值
}
平台兼容性策略:
- 条件编译支持不同目标平台
 - 优雅降级保证基础功能
 - 利用平台原生时间API
 
四、PDF特定功能
1. PDF日期格式解析
pub fn parse_pdf_date(s: &str) -> Result<OffsetDateTime, String> {let s = if s.starts_with("D:") { &s[2..] } else { s };// 解析 "D:20170505150224+02'00'" 格式// 固定位置解析:YYYYMMDDHHMMSSlet year: i32 = s[0..4].parse()?;let month: u8 = s[4..6].parse()?;let day: u8 = s[6..8].parse()?;let hour: u8 = s[8..10].parse()?;let minute: u8 = s[10..12].parse()?;let second: u8 = s[12..14].parse()?;// 时区解析:+02'00' 或 -05'30' 格式if s.len() > 14 {let tz_sign = match &s[14..15] {"+" => 1i8, "-" => -1i8, _ => 0i8,};// 解析时区偏移}
}
2. 时间戳转换
impl DateTime {#[cfg(not(target_arch = "wasm32"))]pub fn unix_timestamp(&self) -> i64 {use time::{Date as TimeDate, Month as TimeMonth, PrimitiveDateTime};// 将自定义格式转换为time库格式进行计算let month = match self.date.month {1 => TimeMonth::January,// ... 月份映射};let date = TimeDate::from_calendar_date(self.date.year, month, self.date.day)?;let time = TimeTime::from_hms_milli(...)?;let primitive_dt = PrimitiveDateTime::new(date, time);primitive_dt.assume_offset(offset).unix_timestamp()}
}
五、序列化与反序列化
1. Serde集成
impl Serialize for DateTime {fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> {// 序列化为字符串格式self.to_string().serialize(serializer)}
}impl<'de> Deserialize<'de> for DateTime {fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> {let s = String::deserialize(deserializer)?;s.parse().map_err(serde::de::Error::custom)}
}
2. 字符串格式
impl std::str::FromStr for DateTime {fn from_str(s: &str) -> Result<Self, Self::Err> {// 解析 "2024-01-15 14:30:00.500 +08:00:00" 格式let parts: Vec<&str> = s.split_whitespace().collect();// 日期部分: "2024-01-15"// 时间部分: "14:30:00.500"  // 时区部分: "+08:00:00"}
}
序列化特点:
- 使用人类可读的字符串格式
 - 支持JSON等格式的序列化
 - 完整的错误处理
 
六、数据验证
1. 日期有效性检查
fn check_date_valid(d: &DateTime) -> Result<(), String> {if !is_valid_date(d.date.year, d.date.month, d.date.day) {return Err(format!("Invalid date: {}-{:02}-{:02}",d.date.year, d.date.month, d.date.day));}// 时间范围验证if d.time.hour > 23 { return Err(format!("Invalid hour: {}", d.time.hour)); }if d.time.minute > 59 { return Err(format!("Invalid minute: {}", d.time.minute)); }if d.time.second > 59 { return Err(format!("Invalid second: {}", d.time.second)); }if d.time.millisecond > 999 { return Err(format!("Invalid millisecond: {}", d.time.millisecond)); }Ok(())
}
2. 闰年计算
fn is_valid_date(year: i32, month: u8, day: u8) -> bool {let days_in_month = match month {1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,4 | 6 | 9 | 11 => 30,2 => {// 闰年判断if (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0) {29} else {28}}_ => unreachable!(),};day >= 1 && day <= days_in_month
}
七、架构设计总结
设计优点:
- PDF标准合规 - 完美支持PDF日期格式
 - 跨平台兼容 - 支持原生、WASM等多平台
 - 内存高效 - 紧凑的数据结构设计
 - 类型安全 - 充分的trait实现和验证
 - 序列化友好 - 完整的Serde支持
 
