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