1use chrono::prelude::*;
2use chrono::Duration;
3
4#[derive(Debug, Clone, Copy, PartialEq)]
6pub enum Direction {
7 Next,
8 Last,
9 Here,
10}
11
12impl Direction {
13 pub fn from_name(s: &str) -> Option<Direction> {
14 use Direction::*;
15 match s {
16 "next" => Some(Next),
17 "last" => Some(Last),
18 _ => None,
19 }
20 }
21}
22
23#[derive(Debug)]
25pub struct YearDate {
26 pub direct: Direction,
27 pub month: u32,
28 pub day: u32,
29}
30
31#[derive(Debug)]
33pub struct NamedDate {
34 pub direct: Direction,
35 pub unit: u32,
36}
37
38impl NamedDate {
39 pub fn new(direct: Direction, unit: u32) -> NamedDate {
40 NamedDate { direct, unit }
41 }
42}
43
44#[derive(Debug)]
46pub enum ByName {
47 WeekDay(NamedDate),
48 MonthName(NamedDate),
49 DayMonth(YearDate),
50}
51
52fn add_days<Tz: TimeZone>(base: DateTime<Tz>, days: i64) -> Option<DateTime<Tz>> {
53 base.checked_add_signed(Duration::days(days))
54}
55
56fn next_last_direction<T: PartialOrd>(date: &T, base: &T, direct: Direction) -> Option<i32> {
59 let mut res = None;
60 if date > base {
61 if direct == Direction::Last {
62 res = Some(-1);
63 }
64 } else if date < base && direct == Direction::Next {
65 res = Some(1)
66 }
67 res
68}
69
70impl ByName {
71 pub fn from_name(s: &str, direct: Direction) -> Option<ByName> {
72 Some(if let Some(wd) = week_day(s) {
73 ByName::WeekDay(NamedDate::new(direct, wd))
74 } else if let Some(mn) = month_name(s) {
75 ByName::MonthName(NamedDate::new(direct, mn))
76 } else {
77 return None;
78 })
79 }
80
81 pub fn as_month(&self) -> Option<u32> {
82 match *self {
83 ByName::MonthName(ref nd) => Some(nd.unit),
84 _ => None,
85 }
86 }
87
88 pub fn from_day_month(day: u32, month: u32, direct: Direction) -> ByName {
89 ByName::DayMonth(YearDate { direct, day, month })
90 }
91
92 pub fn to_date_time<Tz: TimeZone>(
93 self,
94 base: DateTime<Tz>,
95 ts: TimeSpec,
96 american: bool,
97 ) -> Option<DateTime<Tz>>
98 where
99 <Tz as TimeZone>::Offset: Copy,
100 {
101 let this_year = base.year();
102 match self {
103 ByName::WeekDay(mut nd) => {
104 let mut extra_week = 0;
109 match nd.direct {
110 Direction::Here => nd.direct = Direction::Next,
111 Direction::Next => {
112 if !american {
113 extra_week = 7;
114 }
115 }
116 _ => (),
117 };
118 let this_day = base.weekday().num_days_from_monday() as i64;
119 let that_day = nd.unit as i64;
120 let diff_days = that_day - this_day;
121 let mut date = add_days(base.clone(), diff_days)?;
122 if let Some(correct) = next_last_direction(&date, &base, nd.direct) {
123 date = add_days(date, 7 * correct as i64)?;
124 }
125 if extra_week > 0 {
126 date = add_days(date, extra_week)?;
127 }
128 if diff_days == 0 {
129 let base_time = base.time();
131 let this_time = NaiveTime::from_hms_opt(ts.hour, ts.min, ts.sec)?;
132 if let Some(correct) = next_last_direction(&this_time, &base_time, nd.direct) {
133 date = add_days(date, 7 * correct as i64)?;
134 }
135 }
136 ts.to_date_time(date)
137 }
138 ByName::MonthName(nd) => {
139 let mut date = base.timezone().with_ymd_and_hms(this_year, nd.unit, 1, 0, 0,0 ).single()?;
140 if let Some(correct) = next_last_direction(&date, &base, nd.direct) {
141 date = date.with_year(this_year + correct)?;
142 }
143 ts.to_date_time(date)
144 }
145 ByName::DayMonth(yd) => {
146 let mut date = base
147 .timezone()
148 .with_ymd_and_hms(this_year, yd.month, yd.day, 0, 0, 0)
149 .single()?;
150 if let Some(correct) = next_last_direction(&date, &base, yd.direct) {
151 date = date.with_year(this_year + correct)?;
152 }
153 ts.to_date_time(date)
154 }
155 }
156 }
157}
158
159#[derive(Debug)]
160pub struct AbsDate {
161 pub year: i32,
162 pub month: u32,
163 pub day: u32,
164}
165
166impl AbsDate {
167 pub fn to_date<Tz: TimeZone>(self, base: DateTime<Tz>) -> Option<DateTime<Tz>> {
168 base.timezone()
169 .with_ymd_and_hms(self.year, self.month, self.day,0,0,0)
170 .single()
171 }
172}
173
174#[derive(Debug, Hash, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
186pub enum Interval {
187 Seconds(i32),
188 Days(i32),
189 Months(i32),
190}
191
192#[derive(Debug)]
193pub struct Skip {
194 pub unit: Interval,
195 pub skip: i32,
196}
197
198impl Skip {
199 pub fn to_date_time<Tz: TimeZone>(
200 self,
201 base: DateTime<Tz>,
202 ts: TimeSpec,
203 ) -> Option<DateTime<Tz>> {
204 Some(match self.unit {
205 Interval::Seconds(secs) => {
206 base.checked_add_signed(Duration::seconds((secs as i64) * (self.skip as i64)))
207 .unwrap() }
209 Interval::Days(days) => {
210 let secs = 60 * 60 * 24 * days;
211 let date = base
212 .checked_add_signed(Duration::seconds((secs as i64) * (self.skip as i64)))
213 .unwrap();
214 if !ts.empty() {
215 ts.to_date_time(date)?
216 } else {
217 date
218 }
219 }
220 Interval::Months(mm) => {
221 let (year, month, mut day) = (base.year(), (base.month() - 1) as i32, base.day());
222 let delta = mm * self.skip;
223 let month = month + delta;
225 let (year, month) = if month >= 0 {
227 (year + month / 12, month % 12 + 1)
228 } else {
229 let pmm = 12 - month;
231 (year - pmm / 12, 12 - pmm % 12 + 1)
232 };
233 let mut date = base.timezone().with_ymd_and_hms(year, month as u32, day, 0,0,0).single();
235 while date.is_none() {
237 day -= 1;
238 if day == 0 || day < 28 {
239 eprintln!("fkd date");
241 return None;
242 }
243 date = base.timezone().with_ymd_and_hms(year, month as u32, day,0,0,0).single();
244 }
245 ts.to_date_time(date.unwrap())?
246 }
247 })
248 }
249
250 pub fn to_interval(self) -> Interval {
251 use Interval::*;
252
253 match self.unit {
254 Seconds(s) => Seconds(s * self.skip),
255 Days(d) => Days(d * self.skip),
256 Months(m) => Months(m * self.skip),
257 }
258 }
259}
260
261#[derive(Debug)]
262pub enum DateSpec {
263 Absolute(AbsDate), Relative(Skip), FromName(ByName), }
267
268impl DateSpec {
269 pub fn absolute(year: u32, month: u32, day: u32) -> DateSpec {
270 DateSpec::Absolute(AbsDate {
271 year: year as i32,
272 month,
273 day,
274 })
275 }
276
277 pub fn from_day_month(day: u32, month: u32, direct: Direction) -> DateSpec {
278 DateSpec::FromName(ByName::from_day_month(day, month, direct))
279 }
280
281 pub fn skip(unit: Interval, skip: i32) -> DateSpec {
282 DateSpec::Relative(Skip { unit, skip })
283 }
284
285 pub fn to_date_time<Tz: TimeZone>(
286 self,
287 base: DateTime<Tz>,
288 ts: TimeSpec,
289 american: bool,
290 ) -> Option<DateTime<Tz>>
291 where
292 Tz::Offset: Copy,
293 {
294 use DateSpec::*;
295 match self {
296 Absolute(ad) => ts.to_date_time(ad.to_date(base)?),
297 Relative(skip) => skip.to_date_time(base, ts), FromName(byname) => byname.to_date_time(base, ts, american),
299 }
300 }
301}
302
303#[derive(Debug)]
304pub struct TimeSpec {
305 pub hour: u32,
306 pub min: u32,
307 pub sec: u32,
308 pub empty: bool,
309 pub offset: Option<i64>,
310 pub microsec: u32,
311}
312
313impl TimeSpec {
314 pub fn new(hour: u32, min: u32, sec: u32, microsec: u32) -> TimeSpec {
315 TimeSpec {
316 hour,
317 min,
318 sec,
319 empty: false,
320 offset: None,
321 microsec,
322 }
323 }
324
325 pub fn new_with_offset(hour: u32, min: u32, sec: u32, offset: i64, microsec: u32) -> TimeSpec {
326 TimeSpec {
327 hour,
328 min,
329 sec,
330 empty: false,
331 offset: Some(offset),
332 microsec,
333 }
334 }
335
336 pub fn new_empty() -> TimeSpec {
337 TimeSpec {
338 hour: 0,
339 min: 0,
340 sec: 0,
341 empty: true,
342 offset: None,
343 microsec: 0,
344 }
345 }
346
347 pub fn empty(&self) -> bool {
348 self.empty
349 }
350
351 pub fn to_date_time<Tz: TimeZone>(self, date: DateTime<Tz>) -> Option<DateTime<Tz>> {
352 let dt = date.with_hour(self.hour)?.with_minute(self.min)?.with_second(self.sec)?.with_nanosecond(1000 * self.microsec)?;
353 if let Some(offs) = self.offset {
354 let zoffset = dt.offset().clone();
355 let tstamp = dt.timestamp() - offs + zoffset.fix().local_minus_utc() as i64;
356 dt.timezone().timestamp_opt(tstamp, 1000 * self.microsec).single()
359 } else {
360 Some(dt)
361 }
362 }
363}
364
365#[derive(Debug)]
366pub struct DateTimeSpec {
367 pub date: Option<DateSpec>,
368 pub time: Option<TimeSpec>,
369}
370
371pub fn week_day(s: &str) -> Option<u32> {
373 if s.len() < 3 {
374 return None;
375 }
376 Some(match &s[0..3] {
377 "sun" => 6,
378 "mon" => 0,
379 "tue" => 1,
380 "wed" => 2,
381 "thu" => 3,
382 "fri" => 4,
383 "sat" => 5,
384 _ => return None,
385 })
386}
387
388pub fn month_name(s: &str) -> Option<u32> {
389 if s.len() < 3 {
390 return None;
391 }
392 Some(match &s[0..3] {
393 "jan" => 1,
394 "feb" => 2,
395 "mar" => 3,
396 "apr" => 4,
397 "may" => 5,
398 "jun" => 6,
399 "jul" => 7,
400 "aug" => 8,
401 "sep" => 9,
402 "oct" => 10,
403 "nov" => 11,
404 "dec" => 12,
405 _ => return None,
406 })
407}
408
409pub fn time_unit(s: &str) -> Option<Interval> {
410 use Interval::*;
411 let name = if s.len() < 3 {
412 match &s[0..1] {
413 "s" => "sec",
414 "m" => "min",
415 "h" => "hou",
416 "w" => "wee",
417 "d" => "day",
418 "y" => "yea",
419 _ => return None,
420 }
421 } else {
422 &s[0..3]
423 };
424 Some(match name {
425 "sec" => Seconds(1),
426 "min" => Seconds(60),
427 "hou" => Seconds(60 * 60),
428 "day" => Days(1),
429 "wee" => Days(7),
430 "mon" => Months(1),
431 "yea" => Months(12),
432 _ => return None,
433 })
434}