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