progscrape_scrapers/types/
date.rs1use chrono::{DateTime, Datelike, Months, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc};
2use serde::{Deserialize, Serialize};
3use std::{fmt::Display, ops::Sub, time::SystemTime};
4
5#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
8pub struct StoryDate {
9 internal_date: DateTime<Utc>,
10}
11
12impl StoryDate {
13 pub const MAX: StoryDate = Self::new(DateTime::<Utc>::MAX_UTC);
14 pub const MIN: StoryDate = Self::new(DateTime::<Utc>::MIN_UTC);
15
16 pub const fn new(internal_date: DateTime<Utc>) -> Self {
17 Self { internal_date }
18 }
19 pub fn year_month_day(year: i32, month: u32, day: u32) -> Option<Self> {
20 match (
21 NaiveDate::from_ymd_opt(year, month, day),
22 NaiveTime::from_hms_opt(0, 0, 0),
23 ) {
24 (Some(d), Some(t)) => {
25 let dt = d.and_time(t);
26 Some(Self::new(Utc.from_utc_datetime(&dt)))
27 }
28 _ => None,
29 }
30 }
31 pub fn now() -> Self {
32 Self::new(DateTime::<Utc>::from(SystemTime::now()))
33 }
34 pub fn from_millis(millis: i64) -> Option<Self> {
35 Utc.timestamp_millis_opt(millis).earliest().map(Self::new)
36 }
37 pub fn from_seconds(seconds: i64) -> Option<Self> {
38 Self::from_millis(seconds * 1_000)
39 }
40 pub fn from_string(date: &str, s: &str) -> Option<Self> {
41 let date = NaiveDateTime::parse_from_str(date, s).ok();
42 date.map(|x| Self::new(Utc.from_utc_datetime(&x)))
43 }
44 pub fn parse_from_rfc3339(date: &str) -> Option<Self> {
45 DateTime::parse_from_rfc3339(date)
46 .ok()
47 .map(|x| Self::new(x.into()))
48 }
49 pub fn to_rfc3339(&self) -> String {
50 self.internal_date.to_rfc3339()
51 }
52 pub fn parse_from_rfc2822(date: &str) -> Option<Self> {
53 DateTime::parse_from_rfc2822(date)
54 .ok()
55 .map(|x| Self::new(x.into()))
56 }
57 pub fn year(&self) -> i32 {
58 self.internal_date.year()
59 }
60 pub fn month(&self) -> u32 {
61 self.internal_date.month()
62 }
63 pub fn month0(&self) -> u32 {
64 self.internal_date.month0()
65 }
66 pub fn day(&self) -> u32 {
67 self.internal_date.day()
68 }
69 pub fn day0(&self) -> u32 {
70 self.internal_date.day0()
71 }
72 pub fn timestamp(&self) -> i64 {
73 self.internal_date.timestamp()
74 }
75 pub fn checked_add_months(&self, months: u32) -> Option<Self> {
76 self.internal_date
77 .checked_add_months(Months::new(months))
78 .map(StoryDate::new)
79 }
80 pub fn checked_sub_months(&self, months: u32) -> Option<Self> {
81 self.internal_date
82 .checked_sub_months(Months::new(months))
83 .map(StoryDate::new)
84 }
85}
86
87impl Sub for StoryDate {
88 type Output = StoryDuration;
89 fn sub(self, rhs: Self) -> Self::Output {
90 StoryDuration {
91 duration: self.internal_date - rhs.internal_date,
92 }
93 }
94}
95
96impl Serialize for StoryDate {
97 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
98 where
99 S: serde::Serializer,
100 {
101 chrono::serde::ts_seconds::serialize(&self.internal_date, serializer)
102 }
103}
104
105impl<'de> Deserialize<'de> for StoryDate {
106 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
107 where
108 D: serde::Deserializer<'de>,
109 {
110 chrono::serde::ts_seconds::deserialize(deserializer).map(Self::new)
111 }
112}
113
114impl Display for StoryDate {
115 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
116 self.internal_date.fmt(f)
117 }
118}
119
120#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
121pub struct StoryDuration {
122 duration: chrono::Duration,
123}
124
125macro_rules! duration_unit {
126 ($unit:ident, $num_unit:ident, $num_unit_f32:ident) => {
127 #[inline(always)]
128 #[allow(dead_code)]
129 pub fn $unit($unit: i64) -> Self {
130 Self {
131 duration: chrono::Duration::$unit($unit),
132 }
133 }
134
135 #[inline(always)]
136 #[allow(dead_code)]
137 pub fn $num_unit(&self) -> i64 {
138 self.duration.$num_unit()
139 }
140
141 #[inline(always)]
142 #[allow(dead_code)]
143 pub fn $num_unit_f32(&self) -> f32 {
144 self.duration.num_milliseconds() as f32
145 / Self::$unit(1).duration.num_milliseconds() as f32
146 }
147 };
148}
149
150impl StoryDuration {
151 duration_unit!(days, num_days, num_days_f32);
152 duration_unit!(hours, num_hours, num_hours_f32);
153 duration_unit!(minutes, num_minutes, num_minutes_f32);
154 duration_unit!(seconds, num_seconds, num_seconds_f32);
155 duration_unit!(milliseconds, num_milliseconds, num_milliseconds_f32);
156}
157
158impl Sub for StoryDuration {
159 type Output = <chrono::Duration as Sub>::Output;
160
161 fn sub(self, rhs: Self) -> Self::Output {
162 self.duration - rhs.duration
163 }
164}
165
166#[cfg(test)]
167mod test {
168 use crate::StoryDate;
169
170 #[test]
171 fn test_serialize() {
172 let date = StoryDate::year_month_day(2000, 1, 1).expect("Date is valid");
173 let json = serde_json::to_string(&date).expect("Serialize");
174 let date2 = serde_json::from_str::<StoryDate>(&json).expect("Deserialize");
175 assert_eq!(date, date2);
176
177 let date_from_seconds = str::parse::<i64>(&json).expect("Parse");
178 assert_eq!(
179 date,
180 StoryDate::from_seconds(date_from_seconds).expect("From seconds")
181 );
182 }
183}