development
rs 45 lines 1.56 KB
Raw
1 //! Timestamp helpers for comparing against App Store Connect's expiry
2 //! strings without a date-parsing dependency.
3
4 /// Whether an ISO-8601 UTC timestamp is in the past.
5 ///
6 /// Lexicographic comparison is sound for the fixed-width UTC timestamps
7 /// App Store Connect returns, and avoids a date-parsing dependency for a
8 /// field that is only ever compared and displayed.
9 pub fn is_expired(expiration_date: &str, now: &str) -> bool {
10 expiration_date < now
11 }
12
13 /// Current UTC time as `YYYY-MM-DDTHH:MM:SS`, for comparison against
14 /// Apple's expiry strings.
15 pub fn now_iso8601() -> String {
16 let secs = std::time::SystemTime::now()
17 .duration_since(std::time::UNIX_EPOCH)
18 .unwrap_or_default()
19 .as_secs() as i64;
20
21 // days_from_civil, inverted. Shifting the era to March makes the leap
22 // day the last day of the year, which removes every special case.
23 let days = secs.div_euclid(86_400);
24 let secs_of_day = secs.rem_euclid(86_400);
25 let z = days + 719_468;
26 let era = z.div_euclid(146_097);
27 let doe = z.rem_euclid(146_097);
28 let yoe = (doe - doe / 1460 + doe / 36_524 - doe / 146_096) / 365;
29 let y = yoe + era * 400;
30 let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
31 let mp = (5 * doy + 2) / 153;
32 let d = doy - (153 * mp + 2) / 5 + 1;
33 let m = if mp < 10 { mp + 3 } else { mp - 9 };
34 let y = if m <= 2 { y + 1 } else { y };
35
36 format!(
37 "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}",
38 y,
39 m,
40 d,
41 secs_of_day / 3600,
42 (secs_of_day % 3600) / 60,
43 secs_of_day % 60
44 )
45 }