timelang/
lib.rs

1//! # 🕗 Timelang
2//!
3//! [![Crates.io](https://img.shields.io/crates/v/timelang)](https://crates.io/crates/timelang)
4//! [![docs.rs](https://img.shields.io/docsrs/timelang?label=docs)](https://docs.rs/timelang/latest/timelang/)
5//! [![Build Status](https://img.shields.io/github/actions/workflow/status/sam0x17/timelang/ci.yaml)](https://github.com/sam0x17/timelang/actions/workflows/ci.yaml?query=branch%3Amain)
6//! [![MIT License](https://img.shields.io/github/license/sam0x17/timelang)](https://github.com/sam0x17/timelang/blob/main/LICENSE)
7//!
8//! Timelang is a simple DSL (Domain Specific Language) for representing human-readable
9//! time-related expressions including specific date/times, relative expressions like "3 hours
10//! from now", time ranges, and durations.
11//!
12//!
13//! ## Getting Started
14//!
15//! To use timelang, you should take a look at [TimeExpression], which is the top-level entry
16//! point of the AST, or some of the more specific types like [Duration], [PointInTime],
17//! and [TimeRange].
18//!
19//! All nodes in timelang impl [FromStr] as well as [syn::parse::Parse] which is used for the
20//! internal parsing logic. The standard [Display] impl is used on all node types as the
21//! preferred means of outputting them to a string.
22//!
23//! Note that for the moment, only years, months, weeks, days, hours, and minutes are supported
24//! in timelang, but seconds and more might be added later. Generally better than minute
25//! resolution is not needed in many of the common use-cases for timelang.
26//!
27//!
28//! ## Examples
29//!
30//! The following are all examples of valid expressions in timelang:
31//! - `now` ([RelativeTime])
32//! - `tomorrow` ([RelativeTime])
33//! - `next tuesday` ([RelativeTime])
34//! - `day after tomorrow` ([RelativeTime])
35//! - `the day before yesterday` ([RelativeTime])
36//! - `20/4/2021` ([Date])
37//! - `11:21 AM` ([Time])
38//! - `15/6/2022 at 3:58 PM` ([DateTime] / [PointInTime::Absolute])
39//! - `2 hours, 37 minutes` ([Duration])
40//! - `5 years, 2 months, 3 weeks and 11 minutes` ([Duration])
41//! - `7 days ago` ([RelativeTime])
42//! - `2 years and 10 minutes from now` ([RelativeTime])
43//! - `5 days, 3 weeks, 6 minutes after 15/4/2025 at 9:27 AM` ([RelativeTime])
44//! - `from 1/1/2023 at 14:07 to 15/1/2023` ([TimeRange])
45//! - `from 19/3/2024 at 10:07 AM to 3 months 2 days after 3/9/2027 at 5:27 PM` ([TimeRange])
46//! - `2 days and 14 hours after the day after tomorrow` ([RelativeTime])
47//!
48//!
49//! ## Context Free Grammar
50//! Here is a rough CFG (Context Free Grammar) for timelang:
51//!
52//! ```cfg
53//! S → TimeExpression
54//! TimeExpression → PointInTime | TimeRange | Duration
55//! PointInTime → AbsoluteTime | RelativeTime
56//! TimeRange → 'from' PointInTime 'to' PointInTime
57//! Duration → Number TimeUnit ((','? 'and')? Number TimeUnit)*
58//! AbsoluteTime → Date | DateTime
59//! RelativeTime → Duration TimeDirection | NamedRelativeTime | 'next' RelativeTimeUnit | 'last' RelativeTimeUnit
60//! NamedRelativeTime → 'now' | 'today' | 'tomorrow' | 'yesterday' | 'day after tomorrow' | 'the day after tomorrow' | 'day before yesterday' | 'the day before yesterday'
61//! Date → DayOfMonth '/' Month '/' Year
62//! DateTime → Date ('at')? Time
63//! Time → Hour ':' Minute AmPm?
64//! Hour → Number
65//! Minute → Number
66//! Month → Number
67//! DayOfMonth → Number
68//! Year → Number
69//! AmPm → 'AM' | 'PM'
70//! TimeUnit → 'minutes' | 'hours' | 'days' | 'weeks' | 'months' | 'years'
71//! TimeDirection → 'after' AbsoluteTime | 'before' AbsoluteTime | 'after' NamedRelativeTime | 'before' NamedRelativeTime | 'before' 'next' RelativeTimeUnit | 'before' 'last' RelativeTimeUnit | 'after' 'next' RelativeTimeUnit | 'after' 'last' RelativeTimeUnit | 'ago' | 'from now'
72//! RelativeTimeUnit → 'week' | 'month' | 'year' | 'monday' | 'tuesday' | 'wednesday' | 'thursday' | 'friday' | 'saturday' | 'sunday'
73//! Number → [Any positive integer value]
74//! ```
75//!
76//! It is worth noting that this CFG is slightly more permissive than the actual timelang
77//! grammar, particularly when it comes to validating the permitted number ranges for various
78//! times.
79//!
80//! ## Notes
81//!
82//! * At the moment [syn](https://crates.io/crates/syn) is used for parsing. This will likely be
83//!   swapped out for a TBD parsing crate, but it was easy to quickly get this off the ground using
84//!   syn. Whatever new crate we use will hopefully allow us to make timelang compatible with no
85//!   std.
86//! * Timelang is unambiguous, meaning there is exactly one tree representation for all possible
87//!   timelang sentences. If you can come up with an ambiguous sentence, please let us know by
88//!   submitting a GitHub issue!
89
90#![deny(missing_docs)]
91
92use std::{
93    fmt::Display,
94    ops::{Add, Div, Mul, Sub},
95    str::FromStr,
96};
97use syn::{
98    parse::{Parse, ParseStream, Result},
99    Error, Ident, LitInt, Token,
100};
101
102#[cfg(test)]
103mod tests;
104
105/// The top-level entry-point for the timelang AST.
106///
107/// Typically you will want to use a more specific type like [Duration], [PointInTime], or
108/// [TimeRange], but this top-level node-type is provided so that we can consider timelang to
109/// be a distinct language.
110///
111/// Note that [TimeExpression] is [Sized], and thus all expressions in timelang have a
112/// predictable memory size and do not require any heap allocations. That said, _parsing_
113/// expressions in timelang does require some temporary allocations that go away when parsing
114/// is complete.
115///
116/// ## Examples
117///
118/// Specific Date:
119/// ```
120/// use timelang::*;
121/// assert_eq!(
122///     "20/4/2021".parse::<TimeExpression>().unwrap(),
123///     TimeExpression::Specific(PointInTime::Absolute(AbsoluteTime::Date(Date(
124///         Month::April,
125///         DayOfMonth(20),
126///         Year(2021)
127///     ))))
128/// );
129/// ```
130///
131/// Specific DateTime:
132/// ```
133/// use timelang::*;
134/// assert_eq!(
135///     "15/6/2022 at 14:00".parse::<AbsoluteTime>().unwrap(),
136///     AbsoluteTime::DateTime(DateTime(
137///         Date(Month::June, DayOfMonth(15), Year(2022)),
138///         Time(Hour::Hour24(14), Minute(0))
139///     ))
140/// );
141/// ```
142///
143/// Time Range:
144/// ```
145/// use timelang::*;
146/// assert_eq!(
147///     "from 1/1/2023 to 15/1/2023"
148///         .parse::<TimeExpression>()
149///         .unwrap(),
150///     TimeExpression::Range(TimeRange(
151///         PointInTime::Absolute(AbsoluteTime::Date(Date(
152///             Month::January,
153///             DayOfMonth(1),
154///             Year(2023)
155///         ))),
156///         PointInTime::Absolute(AbsoluteTime::Date(Date(
157///             Month::January,
158///             DayOfMonth(15),
159///             Year(2023)
160///         )))
161///     ))
162/// );
163/// ```
164///
165/// Duration (multiple units with comma):
166/// ```
167/// use timelang::*;
168/// assert_eq!(
169///     "2 hours, 30 minutes".parse::<TimeExpression>().unwrap(),
170///     TimeExpression::Duration(Duration {
171///         hours: Number(2),
172///         minutes: Number(30),
173///         days: Number(0),
174///         weeks: Number(0),
175///         months: Number(0),
176///         years: Number(0)
177///     })
178/// );
179/// ```
180///
181/// Duration (multiple units with `and`):
182/// ```
183/// use timelang::*;
184/// assert_eq!(
185///     "1 year and 6 months".parse::<TimeExpression>().unwrap(),
186///     TimeExpression::Duration(Duration {
187///         years: Number(1),
188///         months: Number(6),
189///         days: Number(0),
190///         weeks: Number(0),
191///         hours: Number(0),
192///         minutes: Number(0)
193///     })
194/// );
195/// ```
196///
197/// Relative Time (using `ago`):
198/// ```
199/// use timelang::*;
200/// assert_eq!(
201///     "3 days ago".parse::<TimeExpression>().unwrap(),
202///     TimeExpression::Specific(PointInTime::Relative(RelativeTime::Directional {
203///         duration: Duration {
204///             days: Number(3),
205///             minutes: Number(0),
206///             hours: Number(0),
207///             weeks: Number(0),
208///             months: Number(0),
209///             years: Number(0)
210///         },
211///         dir: TimeDirection::Ago
212///     }))
213/// );
214/// ```
215///
216/// Relative Time (using `from now`):
217/// ```
218/// use timelang::*;
219/// assert_eq!(
220///     "5 days, 10 hours, and 35 minutes from now"
221///         .parse::<TimeExpression>()
222///         .unwrap(),
223///     TimeExpression::Specific(PointInTime::Relative(RelativeTime::Directional {
224///         duration: Duration {
225///             minutes: Number(35),
226///             hours: Number(10),
227///             days: Number(5),
228///             weeks: Number(0),
229///             months: Number(0),
230///             years: Number(0)
231///         },
232///         dir: TimeDirection::FromNow
233///     }))
234/// );
235/// ```
236///
237/// Relative Time (`after` a specific date):
238/// ```
239/// use timelang::*;
240/// assert_eq!(
241///     "2 hours, 3 minutes after 10/10/2022"
242///         .parse::<TimeExpression>()
243///         .unwrap(),
244///     TimeExpression::Specific(PointInTime::Relative(RelativeTime::Directional {
245///         duration: Duration {
246///             hours: Number(2),
247///             minutes: Number(3),
248///             days: Number(0),
249///             weeks: Number(0),
250///             months: Number(0),
251///             years: Number(0)
252///         },
253///         dir: TimeDirection::AfterAbsolute(AbsoluteTime::Date(Date(
254///             Month::October,
255///             DayOfMonth(10),
256///             Year(2022)
257///         )))
258///     }))
259/// );
260/// ```
261///
262/// Relative Time (`before` a specific date/time):
263/// ```
264/// use timelang::*;
265/// assert_eq!(
266///     "1 day before 31/12/2023 at 11:13 PM"
267///         .parse::<TimeExpression>()
268///         .unwrap(),
269///     TimeExpression::Specific(PointInTime::Relative(RelativeTime::Directional {
270///         duration: Duration {
271///             days: Number(1),
272///             minutes: Number(0),
273///             hours: Number(0),
274///             weeks: Number(0),
275///             months: Number(0),
276///             years: Number(0)
277///         },
278///         dir: TimeDirection::BeforeAbsolute(AbsoluteTime::DateTime(DateTime(
279///             Date(Month::December, DayOfMonth(31), Year(2023)),
280///             Time(Hour::Hour12(11, AmPm::PM), Minute(13))
281///         )))
282///     }))
283/// );
284/// ```
285///
286/// Time Range (with specific date/times):
287/// ```
288/// use timelang::*;
289/// assert_eq!(
290///     "from 1/1/2024 at 10:00 to 2/1/2024 at 15:30"
291///         .parse::<TimeExpression>()
292///         .unwrap(),
293///     TimeExpression::Range(TimeRange(
294///         PointInTime::Absolute(AbsoluteTime::DateTime(DateTime(
295///             Date(Month::January, DayOfMonth(1), Year(2024)),
296///             Time(Hour::Hour24(10), Minute(0))
297///         ))),
298///         PointInTime::Absolute(AbsoluteTime::DateTime(DateTime(
299///             Date(Month::January, DayOfMonth(2), Year(2024)),
300///             Time(Hour::Hour24(15), Minute(30))
301///         )))
302///     ))
303/// );
304/// ```
305///
306/// Relative Time (Named):
307/// ```
308/// use timelang::*;
309/// assert_eq!("now".parse::<RelativeTime>().unwrap(), RelativeTime::Named(NamedRelativeTime::Now));
310/// assert_eq!(
311///     "tomorrow".parse::<RelativeTime>().unwrap(),
312///     RelativeTime::Named(NamedRelativeTime::Tomorrow)
313/// );
314/// assert_eq!(
315///     "yesterday".parse::<RelativeTime>().unwrap(),
316///     RelativeTime::Named(NamedRelativeTime::Yesterday)
317/// );
318/// assert_eq!(
319///     "day before yesterday".parse::<RelativeTime>().unwrap(),
320///     RelativeTime::Named(NamedRelativeTime::DayBeforeYesterday)
321/// );
322/// // note the optional `the`
323/// assert_eq!(
324///     "the day after tomorrow".parse::<RelativeTime>().unwrap(),
325///     RelativeTime::Named(NamedRelativeTime::DayAfterTomorrow)
326/// );
327/// assert_eq!(
328///     "next tuesday".parse::<RelativeTime>().unwrap(),
329///     RelativeTime::Next(RelativeTimeUnit::Tuesday)
330/// );
331/// assert_eq!(
332///     "last wednesday".parse::<RelativeTime>().unwrap(),
333///     RelativeTime::Last(RelativeTimeUnit::Wednesday)
334/// );
335/// assert_eq!(
336///     "3 days before yesterday".parse::<RelativeTime>().unwrap(),
337///     RelativeTime::Directional {
338///         duration: Duration {
339///             minutes: Number(0),
340///             hours: Number(0),
341///             days: Number(3),
342///             weeks: Number(0),
343///             months: Number(0),
344///             years: Number(0)
345///         },
346///         dir: TimeDirection::BeforeNamed(NamedRelativeTime::Yesterday)
347///     }
348/// );
349/// assert_eq!(
350///     "2 days and 14 hours after the day after tomorrow".parse::<RelativeTime>().unwrap(),
351///     RelativeTime::Directional {
352///         duration: Duration {
353///             minutes: Number(0),
354///             hours: Number(14),
355///             days: Number(2),
356///             weeks: Number(0),
357///             months: Number(0),
358///             years: Number(0)
359///         },
360///         dir: TimeDirection::AfterNamed(NamedRelativeTime::DayAfterTomorrow)
361///     }
362/// );
363/// assert_eq!(
364///     "2 weeks before last sunday".parse::<RelativeTime>().unwrap(),
365///     RelativeTime::Directional {
366///         duration: Duration {
367///             minutes: Number(0),
368///             hours: Number(0),
369///             days: Number(0),
370///             weeks: Number(2),
371///             months: Number(0),
372///             years: Number(0)
373///         },
374///         dir: TimeDirection::BeforeLast(RelativeTimeUnit::Sunday)
375///     }
376/// );
377/// assert_eq!(
378///     "3 years, 2 weeks after next thursday".parse::<RelativeTime>().unwrap(),
379///     RelativeTime::Directional {
380///         duration: Duration {
381///             minutes: Number(0),
382///             hours: Number(0),
383///             days: Number(0),
384///             weeks: Number(2),
385///             months: Number(0),
386///             years: Number(3)
387///         },
388///         dir: TimeDirection::AfterNext(RelativeTimeUnit::Thursday)
389///     }
390/// );
391/// ```
392#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
393pub enum TimeExpression {
394    /// Represents a [PointInTime] expression.
395    Specific(PointInTime), // (LitInt, Ident) or (LitInt, Token![/])
396    /// Represents a [TimeRange] expression.
397    Range(TimeRange), // Ident, LitInt
398    /// Represents a [Duration] expression.
399    Duration(Duration), // LitInt, Ident
400}
401
402impl Parse for TimeExpression {
403    fn parse(input: ParseStream) -> Result<Self> {
404        if !input.peek(Ident) && !input.peek(LitInt) {
405            return Err(Error::new(input.span(), "expected [number] or [keyword]"));
406        }
407        if input.peek(Ident) {
408            let ident = input.fork().parse::<Ident>()?;
409            if ident.to_string().to_lowercase().as_str() == "from" {
410                return Ok(TimeExpression::Range(input.parse()?));
411            }
412            return Ok(TimeExpression::Specific(input.parse()?));
413        }
414        if input.peek(LitInt) && input.peek2(Token![/]) {
415            // case 2 for PointInTime
416            return Ok(TimeExpression::Specific(input.parse()?));
417        }
418        // now we either have a Duration or PointInTime starting with a Duration
419        let fork = input.fork();
420        if fork.parse::<PointInTime>().is_ok() {
421            return Ok(TimeExpression::Specific(input.parse()?));
422        }
423        Ok(TimeExpression::Duration(input.parse()?))
424    }
425}
426
427impl Display for TimeExpression {
428    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
429        match self {
430            TimeExpression::Specific(point) => write!(f, "{point}"),
431            TimeExpression::Range(tr) => write!(f, "{tr}"),
432            TimeExpression::Duration(dur) => write!(f, "{dur}"),
433        }
434    }
435}
436
437/// Represents a range of two valid [PointInTime]s that together define the start and end of
438/// some defined period of time.
439#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
440pub struct TimeRange(pub PointInTime, pub PointInTime);
441
442impl Parse for TimeRange {
443    fn parse(input: ParseStream) -> Result<Self> {
444        let ident = input.parse::<Ident>()?;
445        if ident.to_string().to_lowercase() != "from" {
446            return Err(Error::new(ident.span(), "expected `from`"));
447        }
448        let t1 = input.parse::<PointInTime>()?;
449        let ident = input.parse::<Ident>()?;
450        if ident.to_string().to_lowercase() != "to" {
451            return Err(Error::new(ident.span(), "expected `to`"));
452        }
453        let t2 = input.parse::<PointInTime>()?;
454        Ok(TimeRange(t1, t2))
455    }
456}
457
458impl Display for TimeRange {
459    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
460        write!(f, "from {} to {}", self.0, self.1)
461    }
462}
463
464/// Represents a specific duration of time that is not anchored at any particular point in time.
465///
466/// Note that individual components, if not specified, will be recorded as `0`. Such components
467/// will not appear when the [Duration] is rendered, printed, or displayed.
468#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
469pub struct Duration {
470    /// The number of minutes.
471    pub minutes: Number,
472    /// The number of hours.
473    pub hours: Number,
474    /// The number of days.
475    pub days: Number,
476    /// The number of weeks.
477    pub weeks: Number,
478    /// The number of months.
479    pub months: Number,
480    /// The number of years.
481    pub years: Number,
482}
483
484impl Parse for Duration {
485    fn parse(input: ParseStream) -> Result<Self> {
486        let mut minutes: Option<Number> = None;
487        let mut hours: Option<Number> = None;
488        let mut days: Option<Number> = None;
489        let mut weeks: Option<Number> = None;
490        let mut months: Option<Number> = None;
491        let mut years: Option<Number> = None;
492        while input.peek(LitInt) {
493            let num = input.parse::<Number>()?;
494            let unit = input.parse::<TimeUnit>()?;
495            match unit {
496                TimeUnit::Minutes => minutes = Some(minutes.unwrap_or(Number(0)) + num),
497                TimeUnit::Hours => hours = Some(hours.unwrap_or(Number(0)) + num),
498                TimeUnit::Days => days = Some(days.unwrap_or(Number(0)) + num),
499                TimeUnit::Weeks => weeks = Some(weeks.unwrap_or(Number(0)) + num),
500                TimeUnit::Months => months = Some(months.unwrap_or(Number(0)) + num),
501                TimeUnit::Years => years = Some(years.unwrap_or(Number(0)) + num),
502            }
503            if input.peek(Token![,]) {
504                input.parse::<Token![,]>()?;
505            }
506            if input.peek(Ident) {
507                let ident = input.fork().parse::<Ident>()?; // don't consume if it isn't `and`
508                if ident.to_string().to_lowercase() == "and" {
509                    input.parse::<Ident>()?; // consume the `and`
510                }
511            }
512        }
513        if minutes.is_none()
514            && hours.is_none()
515            && days.is_none()
516            && weeks.is_none()
517            && months.is_none()
518            && years.is_none()
519        {
520            return Err(Error::new(
521                input.span(),
522                "expected [number] followed by one of `minutes`, `hours`, `days`, `years`",
523            ));
524        }
525        Ok(Duration {
526            minutes: minutes.unwrap_or(Number(0)),
527            hours: hours.unwrap_or(Number(0)),
528            days: days.unwrap_or(Number(0)),
529            weeks: weeks.unwrap_or(Number(0)),
530            months: months.unwrap_or(Number(0)),
531            years: years.unwrap_or(Number(0)),
532        })
533    }
534}
535
536impl Display for Duration {
537    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
538        let mut before = false;
539        if self.years > 0 {
540            before = true;
541        }
542        if self.years == 1 {
543            write!(f, "1 year")?;
544        } else if self.years > 1 {
545            write!(f, "{} years", self.years)?;
546        }
547        if self.months > 0 {
548            if before {
549                write!(f, ", ")?;
550            }
551            before = true;
552        }
553        if self.months == 1 {
554            write!(f, "1 month")?;
555        } else if self.months > 1 {
556            write!(f, "{} months", self.months)?;
557        }
558        if self.weeks > 0 {
559            if before {
560                write!(f, ", ")?;
561            }
562            before = true;
563        }
564        if self.weeks == 1 {
565            write!(f, "1 week")?;
566        } else if self.weeks > 1 {
567            write!(f, "{} weeks", self.weeks)?;
568        }
569        if self.days > 0 {
570            if before {
571                write!(f, ", ")?;
572            }
573            before = true;
574        }
575        if self.days == 1 {
576            write!(f, "1 day")?;
577        } else if self.days > 1 {
578            write!(f, "{} days", self.days)?;
579        }
580        if self.hours > 0 {
581            if before {
582                write!(f, ", ")?;
583            }
584            before = true;
585        }
586        if self.hours == 1 {
587            write!(f, "1 hour")?;
588        } else if self.hours > 1 {
589            write!(f, "{} hours", self.hours)?;
590        }
591        if self.minutes > 0 {
592            if before {
593                write!(f, ", ")?;
594            }
595        }
596        if self.minutes == 1 {
597            write!(f, "1 minute")?;
598        } else if self.minutes > 1 {
599            write!(f, "{} minutes", self.minutes)?;
600        }
601        Ok(())
602    }
603}
604
605/// Represents a specific point in time, which could either be an [AbsoluteTime] (corresponding
606/// with a particular [Date] or [DateTime]), or a [RelativeTime] (corresponding with an offset
607/// from some [AbsoluteTime] or "now").
608#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
609pub enum PointInTime {
610    /// Based on a specific [Date] or [DateTime] (fixed point) that involves no relative
611    /// indirection, like "3 days after 18/3/2024".
612    Absolute(AbsoluteTime),
613    /// Based on an offset from some known fixed point in time, like "next tuesday".
614    Relative(RelativeTime),
615}
616
617impl Parse for PointInTime {
618    fn parse(input: ParseStream) -> Result<Self> {
619        if input.peek(LitInt) && input.peek2(Token![/]) {
620            Ok(PointInTime::Absolute(input.parse::<AbsoluteTime>()?))
621        } else {
622            Ok(PointInTime::Relative(input.parse::<RelativeTime>()?))
623        }
624    }
625}
626
627impl Display for PointInTime {
628    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
629        match self {
630            PointInTime::Absolute(abs) => write!(f, "{abs}"),
631            PointInTime::Relative(rel) => write!(f, "{rel}"),
632        }
633    }
634}
635
636/// Represents an absolute/fixed point in time, such as a [Date] or [DateTime].
637#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
638pub enum AbsoluteTime {
639    /// A [Date], such as "23/9/2028".
640    Date(Date),
641    /// A [DateTime], such as "28/1/2025 at 5:23 PM" or "1/1/2019 20:15".
642    DateTime(DateTime),
643}
644
645impl Parse for AbsoluteTime {
646    fn parse(input: ParseStream) -> Result<Self> {
647        let fork = input.fork();
648        fork.parse::<Date>()?;
649        if (fork.peek(LitInt) && fork.peek2(Token![:]) && fork.peek3(LitInt))
650            || (fork.peek(Ident) && fork.peek2(LitInt) && fork.peek3(Token![:]))
651        {
652            return Ok(AbsoluteTime::DateTime(input.parse()?));
653        }
654        Ok(AbsoluteTime::Date(input.parse()?))
655    }
656}
657
658impl Display for AbsoluteTime {
659    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
660        match self {
661            AbsoluteTime::Date(date) => write!(f, "{}", date),
662            AbsoluteTime::DateTime(date_time) => write!(f, "{}", date_time),
663        }
664    }
665}
666
667/// Combined with "next" or "after" to denote specific [RelativeTime]s.
668#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
669pub enum RelativeTimeUnit {
670    /// Week
671    Week,
672    /// Month
673    Month,
674    /// Year
675    Year,
676    /// Monday
677    Monday,
678    /// Tuesday
679    Tuesday,
680    /// Wednesday
681    Wednesday,
682    /// Thursday
683    Thursday,
684    /// Friday
685    Friday,
686    /// Saturday
687    Saturday,
688    /// Sunday
689    Sunday,
690}
691
692impl Parse for RelativeTimeUnit {
693    fn parse(input: ParseStream) -> Result<Self> {
694        let ident = input.parse::<Ident>()?;
695        match ident.to_string().to_lowercase().as_str() {
696            "week" => Ok(RelativeTimeUnit::Week),
697            "month" => Ok(RelativeTimeUnit::Month),
698            "year" => Ok(RelativeTimeUnit::Year),
699            "monday" => Ok(RelativeTimeUnit::Monday),
700            "tuesday" => Ok(RelativeTimeUnit::Tuesday),
701            "wednesday" => Ok(RelativeTimeUnit::Wednesday),
702            "thursday" => Ok(RelativeTimeUnit::Thursday),
703            "friday" => Ok(RelativeTimeUnit::Friday),
704            "saturday" => Ok(RelativeTimeUnit::Saturday),
705            "sunday" => Ok(RelativeTimeUnit::Sunday),
706            _ => Err(Error::new(
707                ident.span(),
708                "expected one of `week`, `month`, `year`, `monday`, `tuesday`, `wednesday`, \
709                `thursday`, `friday`, `saturday` or `sunday`",
710            )),
711        }
712    }
713}
714
715impl Display for RelativeTimeUnit {
716    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
717        match self {
718            RelativeTimeUnit::Week => f.write_str("week"),
719            RelativeTimeUnit::Month => f.write_str("month"),
720            RelativeTimeUnit::Year => f.write_str("year"),
721            RelativeTimeUnit::Monday => f.write_str("Monday"),
722            RelativeTimeUnit::Tuesday => f.write_str("Tuesday"),
723            RelativeTimeUnit::Wednesday => f.write_str("Wednesday"),
724            RelativeTimeUnit::Thursday => f.write_str("Thursday"),
725            RelativeTimeUnit::Friday => f.write_str("Friday"),
726            RelativeTimeUnit::Saturday => f.write_str("Saturday"),
727            RelativeTimeUnit::Sunday => f.write_str("Sunday"),
728        }
729    }
730}
731
732/// Corresponds with a named relative time, such as "now", "today", "tomorrow", etc.
733#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
734pub enum NamedRelativeTime {
735    /// Now
736    Now,
737    /// Today
738    Today,
739    /// Tomorrow
740    Tomorrow,
741    /// Yesterday
742    Yesterday,
743    /// The day after tomorrow
744    DayAfterTomorrow,
745    /// The day before yesterday
746    DayBeforeYesterday,
747}
748
749impl Parse for NamedRelativeTime {
750    fn parse(input: ParseStream) -> Result<Self> {
751        let mut ident1 = input.parse::<Ident>()?;
752        if let Some(variant) = match ident1.to_string().to_lowercase().as_str() {
753            "now" => Some(NamedRelativeTime::Now),
754            "today" => Some(NamedRelativeTime::Today),
755            "tomorrow" => Some(NamedRelativeTime::Tomorrow),
756            "yesterday" => Some(NamedRelativeTime::Yesterday),
757            _ => None,
758        } {
759            // single-ident variants
760            return Ok(variant);
761        }
762        if ident1 == "the" && input.peek(Ident) {
763            // optional "the"
764            ident1 = input.parse::<Ident>()?;
765        }
766        let ident2 = input.parse::<Ident>()?;
767        let ident3 = input.parse::<Ident>()?;
768        let ident1_str = ident1.to_string().to_lowercase();
769        let ident2_str = ident2.to_string().to_lowercase();
770        let ident3_str = ident3.to_string().to_lowercase();
771        match (
772            ident1_str.as_str(),
773            ident2_str.as_str(),
774            ident3_str.as_str(),
775        ) {
776            ("day", "after", "tomorrow") => Ok(NamedRelativeTime::DayAfterTomorrow),
777            ("day", "before", "yesterday") => Ok(NamedRelativeTime::DayBeforeYesterday),
778            _ => {
779                if ident1_str != "day" {
780                    return Err(Error::new(
781                        ident1.span(),
782                        "expected one of `day`, `now`, `today`, `tomorrow`, `yesterday`, `the`",
783                    ));
784                }
785                if ident2_str != "before" && ident2_str != "after" {
786                    return Err(Error::new(ident2.span(), "expected `before` or `after`"));
787                }
788                if ident3_str == "tomorrow" {
789                    Err(Error::new(ident3.span(), "expected `yesterday`"))
790                } else {
791                    Err(Error::new(ident3.span(), "expected `tomorrow`"))
792                }
793            }
794        }
795    }
796}
797
798impl Display for NamedRelativeTime {
799    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
800        match self {
801            NamedRelativeTime::Now => f.write_str("now"),
802            NamedRelativeTime::Today => f.write_str("today"),
803            NamedRelativeTime::Tomorrow => f.write_str("tomorrow"),
804            NamedRelativeTime::Yesterday => f.write_str("yesterday"),
805            NamedRelativeTime::DayAfterTomorrow => f.write_str("the day after tomorrow"),
806            NamedRelativeTime::DayBeforeYesterday => f.write_str("the day before yesterday"),
807        }
808    }
809}
810
811/// Represents a specific point in time offset by some known duration or period, such as
812/// "tomorrow", "now", "next tuesday", "3 days after 2/5/2028 at 7:11 PM" etc..
813#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
814pub enum RelativeTime {
815    /// e.g. "3 hours before 18/9/2024 at 4:32 PM", "7 days and 3 hours after tomorrow", "5
816    /// days ago", "9 years from now".
817    Directional {
818        /// The [Duration] (how long).
819        duration: Duration,
820        /// e.g. "from now", "ago", "after tomorrow".
821        dir: TimeDirection,
822    },
823    /// e.g. "the day before tomorrow", "now", "tomorrow", "yesterday".
824    Named(NamedRelativeTime),
825    /// e.g. "next wednesday", "next friday", "next year".
826    Next(RelativeTimeUnit),
827    /// e.g. "last month", "last tuesday", "last year".
828    Last(RelativeTimeUnit),
829}
830
831impl Parse for RelativeTime {
832    fn parse(input: ParseStream) -> Result<Self> {
833        let fork = input.fork();
834        if fork.peek(Ident) {
835            let ident1 = fork.parse::<Ident>().unwrap().to_string().to_lowercase();
836            match ident1.as_str() {
837                "next" | "last" => {
838                    // next / last [unit]
839                    input.parse::<Ident>()?;
840                    let unit = input.parse::<RelativeTimeUnit>()?;
841                    if ident1 == "next" {
842                        return Ok(RelativeTime::Next(unit));
843                    } else {
844                        return Ok(RelativeTime::Last(unit));
845                    }
846                }
847                "day" | "now" | "today" | "tomorrow" | "yesterday" | "the" => {
848                    return Ok(RelativeTime::Named(input.parse::<NamedRelativeTime>()?))
849                }
850                _ => (),
851            }
852        }
853        let duration = input.parse::<Duration>()?;
854        let dir = input.parse::<TimeDirection>()?;
855        Ok(RelativeTime::Directional { duration, dir })
856    }
857}
858
859impl Display for RelativeTime {
860    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
861        match self {
862            RelativeTime::Directional { duration, dir } => write!(f, "{duration} {dir}"),
863            RelativeTime::Next(unit) => write!(f, "next {unit}"),
864            RelativeTime::Last(unit) => write!(f, "last {unit}"),
865            RelativeTime::Named(named) => write!(f, "{named}"),
866        }
867    }
868}
869
870/// A `dd/mm/yyyy` style date.
871#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
872pub struct Date(pub Month, pub DayOfMonth, pub Year);
873
874impl Parse for Date {
875    fn parse(input: ParseStream) -> Result<Self> {
876        let day = input.parse::<DayOfMonth>()?;
877        input.parse::<Token![/]>()?;
878        let month = input.parse::<Month>()?;
879        input.parse::<Token![/]>()?;
880        let year = input.parse::<Year>()?;
881        Ok(Date(month, day, year))
882    }
883}
884
885impl Display for Date {
886    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
887        f.write_fmt(format_args!("{}/{}/{}", self.1, self.0, self.2))
888    }
889}
890
891/// e.g. `22/4/1991 5:25 PM`, `22/4/1991 at 5:25 PM`, `22/4/1991 15:28`.
892///
893/// Note that "at" is optional and time can either be 12-hour (must have am/pm specified) or
894/// 24-hour.
895#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
896pub struct DateTime(pub Date, pub Time); // 22/4/1991 5:25 PM
897
898impl Parse for DateTime {
899    fn parse(input: ParseStream) -> Result<Self> {
900        let date = input.parse::<Date>()?;
901        if input.peek(Ident) {
902            let ident = input.parse::<Ident>()?;
903            if ident.to_string().to_lowercase().as_str() != "at" {
904                return Err(Error::new(ident.span(), "expected `at`"));
905            }
906        }
907        let time = input.parse::<Time>()?;
908        Ok(DateTime(date, time))
909    }
910}
911
912impl Display for DateTime {
913    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
914        f.write_fmt(format_args!("{} at {}", self.0, self.1))
915    }
916}
917
918/// A simple representation of the time, e.g. `13:07` or `5:07 PM`.
919///
920/// Both 24-hour and 12-hour are supported (must specify `AM` or `PM` when using 12-hour).
921#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
922pub struct Time(pub Hour, pub Minute);
923
924impl Parse for Time {
925    fn parse(input: ParseStream) -> Result<Self> {
926        let hour_lit = input.parse::<LitInt>()?;
927        let hour_val = hour_lit.base10_parse::<u8>()?;
928        input.parse::<Token![:]>()?;
929        let min = input.parse::<Minute>()?;
930        if input.peek(Ident)
931            && ["am", "pm"].contains(
932                &input
933                    .fork()
934                    .parse::<Ident>()
935                    .unwrap()
936                    .to_string()
937                    .to_lowercase()
938                    .as_str(),
939            )
940        {
941            let am_pm = input.parse::<AmPm>()?;
942            if hour_val > 12 || hour_val == 0 {
943                return Err(Error::new(
944                    hour_lit.span(),
945                    "hour must be between 1 and 12 (inclusive)",
946                ));
947            }
948            return Ok(Time(Hour::Hour12(hour_val, am_pm), min));
949        }
950        if hour_val > 24 {
951            return Err(Error::new(
952                hour_lit.span(),
953                "hour must be between 0 and 24 (inclusive)",
954            ));
955        }
956        Ok(Time(Hour::Hour24(hour_val), min))
957    }
958}
959
960impl Display for Time {
961    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
962        match self {
963            Time(Hour::Hour12(hour, am_pm), minute) => {
964                write!(f, "{}:{:02} {}", hour, minute, am_pm)
965            }
966            Time(Hour::Hour24(hour), minute) => write!(f, "{}:{:02}", hour, minute),
967        }
968    }
969}
970
971/// Represents a particular day of the month, which can range from 1 to 31.
972#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
973pub struct DayOfMonth(pub u8);
974
975impl Parse for DayOfMonth {
976    fn parse(input: ParseStream) -> Result<Self> {
977        let lit = input.parse::<LitInt>()?;
978        let int_val = lit.base10_parse::<u8>()?;
979        if int_val > 31 || int_val == 0 {
980            return Err(Error::new(
981                lit.span(),
982                "day must be between 1 and 31 (inclusive)",
983            ));
984        }
985        Ok(DayOfMonth(int_val))
986    }
987}
988
989impl Display for DayOfMonth {
990    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
991        f.write_fmt(format_args!("{}", self.0))
992    }
993}
994
995/// Represents a year, which can be any valid [`u16`].
996#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
997pub struct Year(pub u16);
998
999impl Parse for Year {
1000    fn parse(input: ParseStream) -> Result<Self> {
1001        let lit = input.parse::<LitInt>()?;
1002        let int_val = lit.base10_parse::<u16>()?;
1003        Ok(Year(int_val))
1004    }
1005}
1006
1007impl Display for Year {
1008    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1009        f.write_fmt(format_args!("{}", self.0))
1010    }
1011}
1012
1013/// Represents an hour of the day in either 12-hour or 24-hour format.
1014#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
1015pub enum Hour {
1016    /// 12-hour format, i.e. `5 PM`
1017    Hour12(u8, AmPm),
1018    /// 24-hour format, i.e. `18`
1019    Hour24(u8),
1020}
1021
1022impl Parse for Hour {
1023    fn parse(input: ParseStream) -> Result<Self> {
1024        let lit = input.parse::<LitInt>()?;
1025        let int_val = lit.base10_parse::<u8>()?;
1026        if let Ok(am_pm) = input.parse::<AmPm>() {
1027            if int_val > 12 || int_val == 0 {
1028                return Err(Error::new(
1029                    lit.span(),
1030                    "hour must be between 1 and 12 (inclusive)",
1031                ));
1032            }
1033            return Ok(Hour::Hour12(int_val, am_pm));
1034        }
1035        if int_val > 24 {
1036            return Err(Error::new(
1037                lit.span(),
1038                "hour must be between 0 and 24 (inclusive)",
1039            ));
1040        }
1041        Ok(Hour::Hour24(int_val))
1042    }
1043}
1044
1045impl Display for Hour {
1046    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1047        match self {
1048            Hour::Hour12(hour, am_pm) => f.write_fmt(format_args!("{hour} {am_pm}",)),
1049            Hour::Hour24(hour) => f.write_fmt(format_args!("{hour}")),
1050        }
1051    }
1052}
1053
1054/// Represents a minute of the hour, which can range from 0 to 60.
1055#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
1056pub struct Minute(pub u8);
1057
1058impl Parse for Minute {
1059    fn parse(input: ParseStream) -> Result<Self> {
1060        let lit = input.parse::<LitInt>()?;
1061        let int_val = lit.base10_parse::<u8>()?;
1062        if int_val > 60 {
1063            return Err(Error::new(
1064                lit.span(),
1065                "minute must be between 0 and 60 (inclusive)",
1066            ));
1067        }
1068        Ok(Minute(int_val))
1069    }
1070}
1071
1072impl Display for Minute {
1073    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1074        f.write_fmt(format_args!("{:02}", self.0))
1075    }
1076}
1077
1078/// Represents a particular month of the year, which can range from 1-12
1079#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
1080#[repr(u8)]
1081pub enum Month {
1082    /// January (1)
1083    January = 1,
1084    /// February (2)
1085    February,
1086    /// March (3)
1087    March,
1088    /// April (4)
1089    April,
1090    /// May (5)
1091    May,
1092    /// June (6)
1093    June,
1094    /// July (7)
1095    July,
1096    /// August (8)
1097    August,
1098    /// September (9)
1099    September,
1100    /// October (10)
1101    October,
1102    /// November (11)
1103    November,
1104    /// December (12)
1105    December,
1106}
1107
1108impl Parse for Month {
1109    fn parse(input: ParseStream) -> Result<Self> {
1110        let lit = input.parse::<LitInt>()?;
1111        let int_val = lit.base10_parse::<u8>()?;
1112        if int_val > 12 || int_val == 0 {
1113            return Err(Error::new(
1114                lit.span(),
1115                "month must be between 1 and 12 (inclusive)",
1116            ));
1117        }
1118        use Month::*;
1119        Ok(match int_val {
1120            1 => January,
1121            2 => February,
1122            3 => March,
1123            4 => April,
1124            5 => May,
1125            6 => June,
1126            7 => July,
1127            8 => August,
1128            9 => September,
1129            10 => October,
1130            11 => November,
1131            12 => December,
1132            _ => unreachable!(),
1133        })
1134    }
1135}
1136
1137impl From<Month> for u8 {
1138    fn from(value: Month) -> Self {
1139        use Month::*;
1140        match value {
1141            January => 1,
1142            February => 2,
1143            March => 3,
1144            April => 4,
1145            May => 5,
1146            June => 6,
1147            July => 7,
1148            August => 8,
1149            September => 9,
1150            October => 10,
1151            November => 11,
1152            December => 12,
1153        }
1154    }
1155}
1156
1157impl From<&Month> for u8 {
1158    fn from(value: &Month) -> Self {
1159        (*value).into()
1160    }
1161}
1162
1163impl Display for Month {
1164    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1165        let as_u8: u8 = self.into();
1166        f.write_fmt(format_args!("{}", as_u8))
1167    }
1168}
1169
1170/// Represents either AM or PM
1171#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
1172pub enum AmPm {
1173    /// AM
1174    AM,
1175    /// PM
1176    PM,
1177}
1178
1179impl Parse for AmPm {
1180    fn parse(input: ParseStream) -> Result<Self> {
1181        let ident = input.parse::<Ident>()?;
1182        match ident.to_string().to_lowercase().as_str() {
1183            "am" => Ok(AmPm::AM),
1184            "pm" => Ok(AmPm::PM),
1185            _ => Err(Error::new(ident.span(), "expected `AM` or `PM`")),
1186        }
1187    }
1188}
1189
1190impl Display for AmPm {
1191    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1192        match self {
1193            AmPm::AM => f.write_str("AM"),
1194            AmPm::PM => f.write_str("PM"),
1195        }
1196    }
1197}
1198
1199impl AsRef<str> for AmPm {
1200    fn as_ref(&self) -> &str {
1201        match self {
1202            AmPm::AM => "AM",
1203            AmPm::PM => "PM",
1204        }
1205    }
1206}
1207
1208/// Represents particular units of time, such as hours, minutes, etc.
1209#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
1210pub enum TimeUnit {
1211    /// Minutes
1212    Minutes,
1213    /// Hours
1214    Hours,
1215    /// Days
1216    Days,
1217    /// Weeks
1218    Weeks,
1219    /// Months
1220    Months,
1221    /// Years
1222    Years,
1223}
1224
1225impl Parse for TimeUnit {
1226    fn parse(input: ParseStream) -> Result<Self> {
1227        let ident = input.parse::<Ident>()?;
1228        use TimeUnit::*;
1229        Ok(match ident.to_string().to_lowercase().as_str() {
1230            "mins" | "minutes" | "minute" | "min" => Minutes,
1231            "hours" | "hrs" | "hour" | "hr" => Hours,
1232            "days" | "day" => Days,
1233            "weeks" | "week" => Weeks,
1234            "months" | "month" => Months,
1235            "years" | "yr" | "year" => Years,
1236            _ => {
1237                return Err(Error::new(
1238                    ident.span(),
1239                    "expected one of `minutes`, `hours`, `days`, `weeks`, `months`, and `years`",
1240                ))
1241            }
1242        })
1243    }
1244}
1245
1246impl AsRef<str> for TimeUnit {
1247    fn as_ref(&self) -> &str {
1248        match self {
1249            TimeUnit::Minutes => "minutes",
1250            TimeUnit::Hours => "hours",
1251            TimeUnit::Days => "days",
1252            TimeUnit::Weeks => "minutes",
1253            TimeUnit::Months => "months",
1254            TimeUnit::Years => "years",
1255        }
1256    }
1257}
1258
1259impl Display for TimeUnit {
1260    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1261        f.write_str(self.as_ref())
1262    }
1263}
1264
1265/// Enumerates the various types of relative times that can be paired with a [Duration].
1266#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
1267pub enum TimeDirection {
1268    /// e.g. `after 18/7/2025 at 3:22 PM`
1269    AfterAbsolute(AbsoluteTime),
1270    /// e.g. `before 18/7/2025 at 3:22 PM`
1271    BeforeAbsolute(AbsoluteTime),
1272    /// e.g. `after tomorrow`
1273    AfterNamed(NamedRelativeTime),
1274    /// e.g. `before yesterday`
1275    BeforeNamed(NamedRelativeTime),
1276    /// e.g. `before next tuesday`
1277    BeforeNext(RelativeTimeUnit),
1278    /// e.g. `before last week`
1279    BeforeLast(RelativeTimeUnit),
1280    /// e.g. `after next thursday`
1281    AfterNext(RelativeTimeUnit),
1282    /// e.g. `after last month`
1283    AfterLast(RelativeTimeUnit),
1284    /// Ago
1285    Ago,
1286    /// From now
1287    FromNow,
1288}
1289
1290impl Parse for TimeDirection {
1291    fn parse(input: ParseStream) -> Result<Self> {
1292        let ident1 = input.parse::<Ident>()?;
1293        match ident1.to_string().to_lowercase().as_str() {
1294            "after" => {
1295                if input.peek(LitInt) {
1296                    Ok(TimeDirection::AfterAbsolute(input.parse()?))
1297                } else {
1298                    let ident2 = input.fork().parse::<Ident>()?.to_string().to_lowercase();
1299                    match ident2.as_str() {
1300                        "next" => {
1301                            input.parse::<Ident>()?;
1302                            Ok(TimeDirection::AfterNext(input.parse()?))
1303                        }
1304                        "last" => {
1305                            input.parse::<Ident>()?;
1306                            Ok(TimeDirection::AfterLast(input.parse()?))
1307                        }
1308                        _ => Ok(TimeDirection::AfterNamed(input.parse()?)),
1309                    }
1310                }
1311            }
1312            "before" => {
1313                if input.peek(LitInt) {
1314                    Ok(TimeDirection::BeforeAbsolute(input.parse()?))
1315                } else {
1316                    let ident2 = input.fork().parse::<Ident>()?.to_string().to_lowercase();
1317                    match ident2.as_str() {
1318                        "next" => {
1319                            input.parse::<Ident>()?;
1320                            Ok(TimeDirection::BeforeNext(input.parse()?))
1321                        }
1322                        "last" => {
1323                            input.parse::<Ident>()?;
1324                            Ok(TimeDirection::BeforeLast(input.parse()?))
1325                        }
1326                        _ => Ok(TimeDirection::BeforeNamed(input.parse()?)),
1327                    }
1328                }
1329            }
1330            "ago" => Ok(TimeDirection::Ago),
1331            "from" => {
1332                let ident2 = input.parse::<Ident>()?;
1333                if ident2.to_string().to_lowercase().as_str() != "now" {
1334                    return Err(Error::new(ident2.span(), "expected `now`"));
1335                }
1336                Ok(TimeDirection::FromNow)
1337            }
1338            _ => Err(Error::new(
1339                ident1.span(),
1340                "expected one of `after`, `before`, `ago`, `from`",
1341            )),
1342        }
1343    }
1344}
1345
1346impl Display for TimeDirection {
1347    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1348        match self {
1349            TimeDirection::AfterAbsolute(abs_time) => write!(f, "after {abs_time}"),
1350            TimeDirection::BeforeAbsolute(abs_time) => write!(f, "before {abs_time}"),
1351            TimeDirection::Ago => f.write_str("ago"),
1352            TimeDirection::FromNow => f.write_str("from now"),
1353            TimeDirection::AfterNamed(named) => write!(f, "after {named}"),
1354            TimeDirection::BeforeNamed(named) => write!(f, "before {named}"),
1355            TimeDirection::BeforeNext(unit) => write!(f, "before next {unit}"),
1356            TimeDirection::BeforeLast(unit) => write!(f, "before last {unit}"),
1357            TimeDirection::AfterNext(unit) => write!(f, "after next {unit}"),
1358            TimeDirection::AfterLast(unit) => write!(f, "after last {unit}"),
1359        }
1360    }
1361}
1362
1363/// Represents a positive integer, stored as a [`u64`].
1364#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
1365pub struct Number(pub u64);
1366
1367impl From<u64> for Number {
1368    fn from(value: u64) -> Self {
1369        Number(value)
1370    }
1371}
1372
1373impl From<Number> for u64 {
1374    fn from(value: Number) -> Self {
1375        value.0
1376    }
1377}
1378
1379impl Add for Number {
1380    type Output = Number;
1381
1382    fn add(self, rhs: Self) -> Self::Output {
1383        Number(self.0 + rhs.0)
1384    }
1385}
1386
1387impl Sub for Number {
1388    type Output = Number;
1389
1390    fn sub(self, rhs: Self) -> Self::Output {
1391        Number(self.0 - rhs.0)
1392    }
1393}
1394
1395impl Mul for Number {
1396    type Output = Number;
1397
1398    fn mul(self, rhs: Self) -> Self::Output {
1399        Number(self.0 * rhs.0)
1400    }
1401}
1402
1403impl Div for Number {
1404    type Output = Number;
1405
1406    fn div(self, rhs: Self) -> Self::Output {
1407        Number(self.0 / rhs.0)
1408    }
1409}
1410
1411impl PartialEq<u64> for Number {
1412    fn eq(&self, other: &u64) -> bool {
1413        self.0 == *other
1414    }
1415}
1416
1417impl PartialOrd<u64> for Number {
1418    fn partial_cmp(&self, other: &u64) -> Option<std::cmp::Ordering> {
1419        self.0.partial_cmp(other)
1420    }
1421}
1422
1423impl Parse for Number {
1424    fn parse(input: ParseStream) -> Result<Self> {
1425        let lit = input.parse::<LitInt>()?;
1426        let int_val = lit.base10_parse::<u64>()?;
1427        Ok(Number(int_val))
1428    }
1429}
1430
1431impl Display for Number {
1432    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1433        f.write_fmt(format_args!("{}", self.0))
1434    }
1435}
1436
1437macro_rules! impl_parse_str {
1438    ($ident:ident) => {
1439        impl FromStr for $ident {
1440            type Err = syn::Error;
1441
1442            fn from_str(s: &str) -> std::prelude::v1::Result<Self, Self::Err> {
1443                syn::parse_str(s)
1444            }
1445        }
1446    };
1447}
1448
1449impl_parse_str!(TimeExpression);
1450impl_parse_str!(TimeDirection);
1451impl_parse_str!(TimeUnit);
1452impl_parse_str!(TimeRange);
1453impl_parse_str!(AmPm);
1454impl_parse_str!(DayOfMonth);
1455impl_parse_str!(Minute);
1456impl_parse_str!(Month);
1457impl_parse_str!(Hour);
1458impl_parse_str!(AbsoluteTime);
1459impl_parse_str!(Duration);
1460impl_parse_str!(RelativeTime);
1461impl_parse_str!(PointInTime);
1462impl_parse_str!(Time);
1463impl_parse_str!(DateTime);
1464impl_parse_str!(RelativeTimeUnit);
1465impl_parse_str!(NamedRelativeTime);
1466
1467#[cfg(test)]
1468macro_rules! assert_impl_all {
1469    ($($typ:ty),* : $($tt:tt)*) => {{
1470        const fn _assert_impl<T>() where T: $($tt)*, {}
1471        $(_assert_impl::<$typ>();)*
1472    }};
1473}
1474
1475#[test]
1476fn test_traits() {
1477    assert_impl_all!(
1478        TimeDirection,
1479        TimeUnit,
1480        AmPm,
1481        DayOfMonth,
1482        Minute,
1483        Month,
1484        Hour,
1485        AbsoluteTime,
1486        Duration,
1487        RelativeTime,
1488        PointInTime,
1489        Time,
1490        DateTime,
1491        RelativeTimeUnit,
1492        NamedRelativeTime,
1493        TimeRange,
1494        TimeExpression : Copy
1495        + Clone
1496        + PartialEq
1497        + Eq
1498        + PartialOrd
1499        + Ord
1500        + core::fmt::Debug
1501        + core::fmt::Display
1502        + Parse
1503        + core::hash::Hash
1504        + FromStr
1505    );
1506}