1 use crate::ext::LitIntExtension;
2 use proc_macro2::TokenStream;
3 use quote::{quote, ToTokens};
4 use syn::{
5     parse::{Parse, ParseStream},
6     LitFloat, LitInt, Result, Token,
7 };
8 
9 #[derive(PartialEq)]
10 enum AmPm {
11     Am,
12     Pm,
13 }
14 
15 impl Parse for AmPm {
parse(input: ParseStream<'_>) -> Result<Self>16     fn parse(input: ParseStream<'_>) -> Result<Self> {
17         use crate::kw::{am, pm, AM, PM};
18         if input.peek(am) {
19             input.parse::<am>()?;
20             Ok(AmPm::Am)
21         } else if input.peek(AM) {
22             input.parse::<AM>()?;
23             Ok(AmPm::Am)
24         } else if input.peek(pm) {
25             input.parse::<pm>()?;
26             Ok(AmPm::Pm)
27         } else if input.peek(PM) {
28             input.parse::<PM>()?;
29             Ok(AmPm::Pm)
30         } else {
31             error!("expected am or pm")
32         }
33     }
34 }
35 
36 pub(crate) struct Time {
37     pub(crate) hour: LitInt,
38     pub(crate) minute: LitInt,
39     pub(crate) second: LitInt,
40     pub(crate) nanosecond: LitInt,
41 }
42 
43 impl Parse for Time {
parse(input: ParseStream<'_>) -> Result<Self>44     fn parse(input: ParseStream<'_>) -> Result<Self> {
45         let mut hour = input.parse::<LitInt>()?;
46         input.parse::<Token![:]>()?;
47         let minute = input.parse::<LitInt>()?;
48 
49         // Seconds and nanoseconds are optional, defaulting to zero.
50         let (second, nanosecond) = if input.peek(Token![:]) {
51             input.parse::<Token![:]>()?;
52 
53             if input.peek(LitFloat) {
54                 let float = input.parse::<LitFloat>()?;
55 
56                 // Temporary value to satisfy the compiler.
57                 let float_str = float.to_string();
58                 let parts: Vec<_> = float_str.splitn(2, '.').collect();
59 
60                 let seconds = LitInt::new(parts[0], float.span());
61                 let nanoseconds = {
62                     // Prepend a zero to avoid having an empty string.
63                     // Strip the suffix to avoid syn thinking it's a float.
64                     let raw_padded = format!("0{}", parts[1].trim_end_matches(float.suffix()));
65 
66                     // Take an extra digit due to the padding.
67                     let digits: String = raw_padded
68                         .chars()
69                         .filter(char::is_ascii_digit)
70                         .take(10)
71                         .collect();
72 
73                     // Scale the value based on how many digits were provided.
74                     let value = LitInt::new(&digits, float.span()).base10_parse::<usize>()?
75                         * 10_usize.pow(10 - digits.len() as u32);
76 
77                     LitInt::create(value).with_span(float.span())
78                 };
79 
80                 (seconds, nanoseconds)
81             } else {
82                 (input.parse::<LitInt>()?, LitInt::create(0))
83             }
84         } else {
85             (LitInt::create(0), LitInt::create(0))
86         };
87 
88         let am_pm = input.parse::<AmPm>().ok();
89 
90         // Ensure none of the components are out of range.
91         match am_pm {
92             Some(am_pm) => {
93                 hour.ensure_in_range(1..=12)?;
94                 // Adjust the hour if necessary.
95                 hour = match (hour.value()?, am_pm) {
96                     (12, AmPm::Am) => LitInt::create(0).with_span(hour.span()),
97                     (value, AmPm::Pm) if value != 12 => {
98                         LitInt::create(value + 12).with_span(hour.span())
99                     }
100                     _ => hour,
101                 }
102             }
103             None => hour.ensure_in_range(0..24)?,
104         }
105         minute.ensure_in_range(0..60)?;
106         second.ensure_in_range(0..60)?;
107         // This likely isn't necessary, but it can't hurt.
108         nanosecond.ensure_in_range(0..1_000_000_000)?;
109 
110         Ok(Self {
111             hour,
112             minute,
113             second,
114             nanosecond,
115         })
116     }
117 }
118 
119 impl ToTokens for Time {
to_tokens(&self, tokens: &mut TokenStream)120     fn to_tokens(&self, tokens: &mut TokenStream) {
121         let Self {
122             hour,
123             minute,
124             second,
125             nanosecond,
126         } = self;
127 
128         tokens.extend(quote! {
129             ::time::internals::Time::from_hms_nanos_unchecked(#hour, #minute, #second, #nanosecond)
130         });
131     }
132 }
133