Skip to main content

time_macros/helpers/
mod.rs

1#[cfg(any(feature = "formatting", feature = "parsing"))]
2mod string;
3
4use std::iter::Peekable;
5use std::str::FromStr;
6
7use num_conv::prelude::*;
8use proc_macro::{Span, TokenTree, token_stream};
9use time_core::util::{days_in_year, is_leap_year};
10
11use crate::Error;
12
13#[cfg(any(feature = "formatting", feature = "parsing"))]
14pub(crate) fn get_string_literal(
15    permit_byte_strings: bool,
16    mut tokens: impl Iterator<Item = TokenTree>,
17) -> Result<(Span, Vec<u8>), Error> {
18    match (tokens.next(), tokens.next()) {
19        (Some(TokenTree::Literal(literal)), None) => string::parse(permit_byte_strings, &literal),
20        (Some(tree), None) => Err(Error::ExpectedString {
21            span_start: Some(tree.span()),
22            span_end: Some(tree.span()),
23        }),
24        (_, Some(tree)) => Err(Error::UnexpectedToken { tree }),
25        (None, None) => Err(Error::ExpectedString {
26            span_start: None,
27            span_end: None,
28        }),
29    }
30}
31
32pub(crate) fn parse_number<T: FromStr>(
33    component_name: &'static str,
34    chars: &str,
35) -> Result<T, Error> {
36    chars
37        .replace('_', "")
38        .parse()
39        .map_err(|_| Error::InvalidComponent {
40            name: component_name,
41            value: chars.to_string(),
42            span_start: None,
43            span_end: None,
44        })
45}
46
47pub(crate) fn consume_number<T: FromStr>(
48    component_name: &'static str,
49    chars: &mut Peekable<token_stream::IntoIter>,
50) -> Result<(Span, T), Error> {
51    match chars.next() {
52        Some(TokenTree::Literal(literal)) => {
53            let span = literal.span();
54            match parse_number(component_name, &literal.to_string()) {
55                Ok(val) => Ok((span, val)),
56                Err(Error::InvalidComponent {
57                    name,
58                    value,
59                    span_start: _,
60                    span_end: _,
61                }) => Err(Error::InvalidComponent {
62                    name,
63                    value,
64                    span_start: Some(span.start()),
65                    span_end: Some(span.end()),
66                }),
67                Err(e) => Err(e),
68            }
69        }
70        Some(tree) => Err(Error::UnexpectedToken { tree }),
71        None => Err(Error::UnexpectedEndOfInput),
72    }
73}
74
75pub(crate) fn consume_any_ident(
76    idents: &[&str],
77    chars: &mut Peekable<token_stream::IntoIter>,
78) -> Result<Span, Error> {
79    match chars.peek() {
80        Some(TokenTree::Ident(char)) if idents.contains(&char.to_string().as_str()) => {
81            let ret = Ok(char.span());
82            drop(chars.next());
83            ret
84        }
85        Some(tree) => Err(Error::UnexpectedToken { tree: tree.clone() }),
86        None => Err(Error::UnexpectedEndOfInput),
87    }
88}
89
90pub(crate) fn consume_punct(
91    c: char,
92    chars: &mut Peekable<token_stream::IntoIter>,
93) -> Result<Span, Error> {
94    match chars.peek() {
95        Some(TokenTree::Punct(punct)) if *punct == c => {
96            let ret = Ok(punct.span());
97            drop(chars.next());
98            ret
99        }
100        Some(tree) => Err(Error::UnexpectedToken { tree: tree.clone() }),
101        None => Err(Error::UnexpectedEndOfInput),
102    }
103}
104
105fn jan_weekday(year: i32, ordinal: i32) -> u8 {
106    macro_rules! div_floor {
107        ($a:expr, $b:expr) => {{
108            let (_quotient, _remainder) = ($a / $b, $a % $b);
109            if (_remainder > 0 && $b < 0) || (_remainder < 0 && $b > 0) {
110                _quotient - 1
111            } else {
112                _quotient
113            }
114        }};
115    }
116
117    let adj_year = year - 1;
118    (ordinal + adj_year + div_floor!(adj_year, 4) - div_floor!(adj_year, 100)
119        + div_floor!(adj_year, 400)
120        + 6)
121    .rem_euclid(7)
122    .cast_unsigned()
123    .truncate()
124}
125
126pub(crate) fn days_in_year_month(year: i32, month: u8) -> u8 {
127    [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month.widen::<usize>() - 1]
128        + u8::from(month == 2 && is_leap_year(year))
129}
130
131pub(crate) fn ywd_to_yo(year: i32, week: u8, iso_weekday_number: u8) -> (i32, u16) {
132    let (ordinal, overflow) = (u16::from(week) * 7 + u16::from(iso_weekday_number))
133        .overflowing_sub(u16::from(jan_weekday(year, 4)) + 4);
134
135    if overflow || ordinal == 0 {
136        return (year - 1, (ordinal.wrapping_add(days_in_year(year - 1))));
137    }
138
139    let days_in_cur_year = days_in_year(year);
140    if ordinal > days_in_cur_year {
141        (year + 1, ordinal - days_in_cur_year)
142    } else {
143        (year, ordinal)
144    }
145}
146
147pub(crate) fn ymd_to_yo(year: i32, month: u8, day: u8) -> (i32, u16) {
148    let ordinal = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334]
149        [month.widen::<usize>() - 1]
150        + u16::from(month > 2 && is_leap_year(year));
151
152    (year, ordinal + u16::from(day))
153}