1use super::{time::Time, Error};
16
17pub(crate) fn time_from_ymdhms_utc(
18 year: u64,
19 month: u64,
20 day_of_month: u64,
21 hours: u64,
22 minutes: u64,
23 seconds: u64,
24) -> Result<Time, Error> {
25 let days_before_year_since_unix_epoch = days_before_year_since_unix_epoch(year)?;
26
27 const JAN: u64 = 31;
28 let feb = days_in_feb(year);
29 const MAR: u64 = 31;
30 const APR: u64 = 30;
31 const MAY: u64 = 31;
32 const JUN: u64 = 30;
33 const JUL: u64 = 31;
34 const AUG: u64 = 31;
35 const SEP: u64 = 30;
36 const OCT: u64 = 31;
37 const NOV: u64 = 30;
38 let days_before_month_in_year = match month {
39 1 => 0,
40 2 => JAN,
41 3 => JAN + feb,
42 4 => JAN + feb + MAR,
43 5 => JAN + feb + MAR + APR,
44 6 => JAN + feb + MAR + APR + MAY,
45 7 => JAN + feb + MAR + APR + MAY + JUN,
46 8 => JAN + feb + MAR + APR + MAY + JUN + JUL,
47 9 => JAN + feb + MAR + APR + MAY + JUN + JUL + AUG,
48 10 => JAN + feb + MAR + APR + MAY + JUN + JUL + AUG + SEP,
49 11 => JAN + feb + MAR + APR + MAY + JUN + JUL + AUG + SEP + OCT,
50 12 => JAN + feb + MAR + APR + MAY + JUN + JUL + AUG + SEP + OCT + NOV,
51 _ => unreachable!(), };
53
54 let days_before =
55 days_before_year_since_unix_epoch + days_before_month_in_year + day_of_month - 1;
56
57 let seconds_since_unix_epoch =
58 (days_before * 24 * 60 * 60) + (hours * 60 * 60) + (minutes * 60) + seconds;
59
60 Ok(Time::from_seconds_since_unix_epoch(
61 seconds_since_unix_epoch,
62 ))
63}
64
65fn days_before_year_since_unix_epoch(year: u64) -> Result<u64, Error> {
66 if year < UNIX_EPOCH_YEAR {
70 return Err(Error::BadDerTime);
71 }
72 let days_before_year_ad = days_before_year_ad(year);
73 debug_assert!(days_before_year_ad >= DAYS_BEFORE_UNIX_EPOCH_AD);
74 Ok(days_before_year_ad - DAYS_BEFORE_UNIX_EPOCH_AD)
75}
76
77const UNIX_EPOCH_YEAR: u64 = 1970;
78
79fn days_before_year_ad(year: u64) -> u64 {
80 ((year - 1) * 365)
81 + ((year - 1) / 4) - ((year - 1) / 100) + ((year - 1) / 400) }
85
86pub(crate) fn days_in_month(year: u64, month: u64) -> u64 {
87 match month {
88 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
89 4 | 6 | 9 | 11 => 30,
90 2 => days_in_feb(year),
91 _ => unreachable!(), }
93}
94
95fn days_in_feb(year: u64) -> u64 {
96 if (year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0)) {
97 29
98 } else {
99 28
100 }
101}
102
103#[allow(clippy::unreadable_literal)] const DAYS_BEFORE_UNIX_EPOCH_AD: u64 = 719162;
105
106#[cfg(test)]
107mod tests {
108 #[test]
109 fn test_days_before_unix_epoch() {
110 use super::{days_before_year_ad, DAYS_BEFORE_UNIX_EPOCH_AD, UNIX_EPOCH_YEAR};
111 assert_eq!(
112 DAYS_BEFORE_UNIX_EPOCH_AD,
113 days_before_year_ad(UNIX_EPOCH_YEAR)
114 );
115 }
116
117 #[test]
118 fn test_days_before_year_since_unix_epoch() {
119 use super::{days_before_year_since_unix_epoch, Error, UNIX_EPOCH_YEAR};
120 assert_eq!(Ok(0), days_before_year_since_unix_epoch(UNIX_EPOCH_YEAR));
121 assert_eq!(
122 Ok(365),
123 days_before_year_since_unix_epoch(UNIX_EPOCH_YEAR + 1)
124 );
125 assert_eq!(
126 Err(Error::BadDerTime),
127 days_before_year_since_unix_epoch(UNIX_EPOCH_YEAR - 1)
128 );
129 }
130
131 #[test]
132 fn test_days_in_month() {
133 use super::days_in_month;
134 assert_eq!(days_in_month(2017, 1), 31);
135 assert_eq!(days_in_month(2017, 2), 28);
136 assert_eq!(days_in_month(2017, 3), 31);
137 assert_eq!(days_in_month(2017, 4), 30);
138 assert_eq!(days_in_month(2017, 5), 31);
139 assert_eq!(days_in_month(2017, 6), 30);
140 assert_eq!(days_in_month(2017, 7), 31);
141 assert_eq!(days_in_month(2017, 8), 31);
142 assert_eq!(days_in_month(2017, 9), 30);
143 assert_eq!(days_in_month(2017, 10), 31);
144 assert_eq!(days_in_month(2017, 11), 30);
145 assert_eq!(days_in_month(2017, 12), 31);
146
147 assert_eq!(days_in_month(2000, 2), 29);
149 assert_eq!(days_in_month(2004, 2), 29);
150 assert_eq!(days_in_month(2016, 2), 29);
151 assert_eq!(days_in_month(2100, 2), 28);
152 }
153
154 #[allow(clippy::unreadable_literal)] #[test]
156 fn test_time_from_ymdhms_utc() {
157 use super::{time_from_ymdhms_utc, Error, Time, UNIX_EPOCH_YEAR};
158
159 assert_eq!(
161 Err(Error::BadDerTime),
162 time_from_ymdhms_utc(UNIX_EPOCH_YEAR - 1, 1, 1, 0, 0, 0)
163 );
164
165 assert_eq!(
167 Err(Error::BadDerTime),
168 time_from_ymdhms_utc(UNIX_EPOCH_YEAR - 1, 12, 31, 23, 59, 59)
169 );
170
171 assert_eq!(
173 Time::from_seconds_since_unix_epoch(0),
174 time_from_ymdhms_utc(UNIX_EPOCH_YEAR, 1, 1, 0, 0, 0).unwrap()
175 );
176
177 assert_eq!(
179 Time::from_seconds_since_unix_epoch(1),
180 time_from_ymdhms_utc(UNIX_EPOCH_YEAR, 1, 1, 0, 0, 1).unwrap()
181 );
182
183 assert_eq!(
185 Time::from_seconds_since_unix_epoch(365 * 86400),
186 time_from_ymdhms_utc(UNIX_EPOCH_YEAR + 1, 1, 1, 0, 0, 0).unwrap()
187 );
188
189 assert_eq!(
191 Time::from_seconds_since_unix_epoch(1483228799),
192 time_from_ymdhms_utc(2016, 12, 31, 23, 59, 59).unwrap()
193 );
194 assert_eq!(
195 Time::from_seconds_since_unix_epoch(1483228800),
196 time_from_ymdhms_utc(2017, 1, 1, 0, 0, 0).unwrap()
197 );
198
199 assert_eq!(
201 Time::from_seconds_since_unix_epoch(1492449162),
202 time_from_ymdhms_utc(2017, 4, 17, 17, 12, 42).unwrap()
203 );
204
205 assert_eq!(
207 Time::from_seconds_since_unix_epoch(1460913162),
208 time_from_ymdhms_utc(2016, 4, 17, 17, 12, 42).unwrap()
209 );
210 }
211}