pdf_rs/date.rs
1use std::ops::Range;
2use std::str::FromStr;
3use crate::error::PDFError;
4
5/// Represents a date and time value used in PDF documents.
6///
7/// This struct stores time information with millisecond precision,
8/// following the PDF specification for date/time representation.
9pub struct Date {
10 /// Time zone offset from UTC in hours.
11 pub(crate) time_zero: i8,
12 /// Milliseconds component of the time.
13 pub(crate) millisecond: u64,
14}
15
16
17
18impl Date {
19 /// Creates a new Date instance with the specified date and time components.
20 ///
21 /// # Arguments
22 ///
23 /// * `year` - The year (e.g., 2024)
24 /// * `month` - The month (1-12)
25 /// * `day` - The day of the month (1-31)
26 /// * `hour` - The hour (0-23)
27 /// * `minute` - The minute (0-59)
28 /// * `second` - The second (0-59)
29 /// * `time_zero` - Time zone offset from UTC in hours (-12 to +12)
30 /// * `utm` - shall be the absolute value of the offset from UT in minutes (00–59)
31 ///
32 /// # Returns
33 ///
34 /// A new Date instance with the calculated Unix timestamp in milliseconds
35 pub fn new(year: i32, month: u8, day: u8, hour: u8, minute: u8, second: u8, time_zero: i8, utm: u8) -> Self {
36 let millisecond = Self::calculate_unix_timestamp_millis(year, month, day, hour, minute, second, time_zero, utm);
37
38 Date {
39 time_zero,
40 millisecond,
41 }
42 }
43
44 /// Calculates the Unix timestamp in milliseconds from 1970-01-01 00:00:00 UTC.
45 ///
46 /// This function computes the number of milliseconds elapsed since the Unix epoch
47 /// (January 1, 1970, 00:00:00 UTC) for the given date and time, taking into account
48 /// the time zone offset.
49 ///
50 /// # Arguments
51 ///
52 /// * `year` - The year (e.g., 2024)
53 /// * `month` - The month (1-12)
54 /// * `day` - The day of the month (1-31)
55 /// * `hour` - The hour (0-23)
56 /// * `minute` - The minute (0-59)
57 /// * `second` - The second (0-59)
58 /// * `time_zero` - Time zone offset from UTC in hours (-12 to +12)
59 /// * `utm` - Milliseconds component (0-999)
60 ///
61 /// # Returns
62 ///
63 /// The Unix timestamp in milliseconds
64 fn calculate_unix_timestamp_millis(year: i32, month: u8, day: u8, hour: u8, minute: u8, second: u8, time_zero: i8, utm: u8) -> u64 {
65 // Days in each month for non-leap years
66 static DAYS_IN_MONTH: [u64; 12] = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
67
68 // Calculate total days from 1970 to the given year
69 let mut total_days: u64 = 0;
70
71 // Add days for full years from 1970 to year-1
72 for y in 1970..year {
73 total_days += if Self::is_leap_year(y) { 366 } else { 365 };
74 }
75
76 // Add days for full months in the current year
77 for m in 1..month {
78 let month_idx = (m - 1) as usize;
79 total_days += DAYS_IN_MONTH[month_idx];
80 // Add leap day for February in leap years
81 if m == 2 && Self::is_leap_year(year) {
82 total_days += 1;
83 }
84 }
85
86 // Add days in the current month
87 total_days += (day - 1) as u64;
88
89 // Convert days to seconds
90 let mut total_seconds = total_days * 86400;
91
92 // Add hours, minutes, and seconds
93 total_seconds += hour as u64 * 3600;
94 total_seconds += minute as u64 * 60;
95 total_seconds += second as u64;
96
97 // Adjust for time zone offset (convert hours to seconds)
98 let tz_offset_seconds = (time_zero as i64) * 3600;
99 total_seconds = (total_seconds as i64 - tz_offset_seconds) as u64;
100
101 // Convert to milliseconds and add the milliseconds component
102 total_seconds * 1000 + (utm as u64) * 60 * 1000
103 }
104
105 /// Determines if a given year is a leap year.
106 ///
107 /// A year is a leap year if:
108 /// - It is divisible by 4, and
109 /// - It is not divisible by 100, unless it is also divisible by 400
110 ///
111 /// # Arguments
112 ///
113 /// * `year` - The year to check
114 ///
115 /// # Returns
116 ///
117 /// true if the year is a leap year, false otherwise
118 fn is_leap_year(year: i32) -> bool {
119 (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)
120 }
121
122 /// Returns the millisecond component of the date and time.
123 ///
124 /// This represents the number of milliseconds elapsed since the Unix epoch
125 /// (January 1, 1970, 00:00:00 UTC), adjusted for the time zone offset.
126 ///
127 /// # Returns
128 ///
129 /// The millisecond value as a `u64`.
130 pub fn get_millisecond(&self) -> u64 {
131 self.millisecond
132 }
133
134 /// Returns the time zone offset from UTC in hours.
135 ///
136 /// This represents the time zone offset that was used when creating this
137 /// `Date` instance. The value is in the range of -12 to +12 hours.
138 ///
139 /// # Returns
140 ///
141 /// The time zone offset in hours as an `i8`.
142 pub fn get_time_zero(&self) -> i8 {
143 self.time_zero
144 }
145}
146
147fn parse_part(text: &str, range: Range<usize>) -> u8 {
148 text.get(range)
149 .and_then(|s| s.parse::<u8>().ok())
150 .unwrap_or(0)
151}
152
153impl FromStr for Date {
154 type Err = PDFError;
155
156 fn from_str(text: &str) -> Result<Self, Self::Err> {
157 let length = text.len();
158 if !text.starts_with("D:") || length < 6 {
159 return Err(PDFError::IllegalDateFormat(text.to_string()));
160 }
161 let year = text[2..6].parse::<i32>().unwrap_or(0);
162 let month = parse_part(text, 6..8);
163 let day = parse_part(text, 8..10);
164 let hour = parse_part(text, 10..12);
165 let minute = parse_part(text, 12..14);
166 let second = parse_part(text, 14..16);
167 let (tz, utm) = if length >= 17 {
168 let tmp = &text[16..17];
169 let mut index = 17;
170 let time_zero = if tmp == "Z" {
171 0
172 } else {
173 let plus_sign = tmp == "+";
174 let minus_sign = tmp == "-";
175 if !plus_sign || !minus_sign || length < 19 {
176 return Err(PDFError::IllegalDateFormat(text.to_string()));
177 }
178 let tz = parse_part(text, 17..19) as i8;
179 index = 19;
180 if minus_sign {
181 -tz
182 } else {
183 tz
184 }
185 };
186 if length > index && index + 3 != length {
187 return Err(PDFError::IllegalDateFormat(text.to_string()));
188 }
189 let utm = parse_part(text, index + 1..length);
190 (time_zero, utm)
191 } else {
192 (0, 0)
193 };
194 Ok(Self::new(year, month, day, hour, minute, second, tz, utm))
195 }
196
197}