| 1 | use std::iter::Peekable; |
| 2 | |
| 3 | use num_conv::Truncate; |
| 4 | use proc_macro::{token_stream, TokenTree}; |
| 5 | use time_core::util::{days_in_year, weeks_in_year}; |
| 6 | |
| 7 | use crate::helpers::{ |
| 8 | consume_any_ident, consume_number, consume_punct, days_in_year_month, ymd_to_yo, ywd_to_yo, |
| 9 | }; |
| 10 | use crate::to_tokens::ToTokenTree; |
| 11 | use crate::Error; |
| 12 | |
| 13 | #[cfg (feature = "large-dates" )] |
| 14 | const MAX_YEAR: i32 = 999_999; |
| 15 | #[cfg (not(feature = "large-dates" ))] |
| 16 | const MAX_YEAR: i32 = 9_999; |
| 17 | |
| 18 | pub(crate) struct Date { |
| 19 | pub(crate) year: i32, |
| 20 | pub(crate) ordinal: u16, |
| 21 | } |
| 22 | |
| 23 | pub(crate) fn parse(chars: &mut Peekable<token_stream::IntoIter>) -> Result<Date, Error> { |
| 24 | let (year_sign_span, year_sign, explicit_sign) = if let Ok(span) = consume_punct('-' , chars) { |
| 25 | (Some(span), -1, true) |
| 26 | } else if let Ok(span) = consume_punct('+' , chars) { |
| 27 | (Some(span), 1, true) |
| 28 | } else { |
| 29 | (None, 1, false) |
| 30 | }; |
| 31 | let (year_span, mut year) = consume_number::<i32>("year" , chars)?; |
| 32 | year *= year_sign; |
| 33 | if year.abs() > MAX_YEAR { |
| 34 | return Err(Error::InvalidComponent { |
| 35 | name: "year" , |
| 36 | value: year.to_string(), |
| 37 | span_start: Some(year_sign_span.unwrap_or(year_span)), |
| 38 | span_end: Some(year_span), |
| 39 | }); |
| 40 | } |
| 41 | if !explicit_sign && year.abs() >= 10_000 { |
| 42 | return Err(Error::Custom { |
| 43 | message: "years with more than four digits must have an explicit sign" .into(), |
| 44 | span_start: Some(year_sign_span.unwrap_or(year_span)), |
| 45 | span_end: Some(year_span), |
| 46 | }); |
| 47 | } |
| 48 | |
| 49 | consume_punct('-' , chars)?; |
| 50 | |
| 51 | // year-week-day |
| 52 | if let Ok(w_span) = consume_any_ident(&["W" ], chars) { |
| 53 | let (week_span, week) = consume_number::<u8>("week" , chars)?; |
| 54 | consume_punct('-' , chars)?; |
| 55 | let (day_span, day) = consume_number::<u8>("day" , chars)?; |
| 56 | |
| 57 | if week > weeks_in_year(year) { |
| 58 | return Err(Error::InvalidComponent { |
| 59 | name: "week" , |
| 60 | value: week.to_string(), |
| 61 | span_start: Some(w_span), |
| 62 | span_end: Some(week_span), |
| 63 | }); |
| 64 | } |
| 65 | if day == 0 || day > 7 { |
| 66 | return Err(Error::InvalidComponent { |
| 67 | name: "day" , |
| 68 | value: day.to_string(), |
| 69 | span_start: Some(day_span), |
| 70 | span_end: Some(day_span), |
| 71 | }); |
| 72 | } |
| 73 | |
| 74 | let (year, ordinal) = ywd_to_yo(year, week, day); |
| 75 | |
| 76 | return Ok(Date { year, ordinal }); |
| 77 | } |
| 78 | |
| 79 | // We don't yet know whether it's year-month-day or year-ordinal. |
| 80 | let (month_or_ordinal_span, month_or_ordinal) = |
| 81 | consume_number::<u16>("month or ordinal" , chars)?; |
| 82 | |
| 83 | // year-month-day |
| 84 | #[allow (clippy::branches_sharing_code)] // clarity |
| 85 | if consume_punct('-' , chars).is_ok() { |
| 86 | let (month_span, month) = (month_or_ordinal_span, month_or_ordinal); |
| 87 | let (day_span, day) = consume_number::<u8>("day" , chars)?; |
| 88 | |
| 89 | if month == 0 || month > 12 { |
| 90 | return Err(Error::InvalidComponent { |
| 91 | name: "month" , |
| 92 | value: month.to_string(), |
| 93 | span_start: Some(month_span), |
| 94 | span_end: Some(month_span), |
| 95 | }); |
| 96 | } |
| 97 | let month = month.truncate(); |
| 98 | if day == 0 || day > days_in_year_month(year, month) { |
| 99 | return Err(Error::InvalidComponent { |
| 100 | name: "day" , |
| 101 | value: day.to_string(), |
| 102 | span_start: Some(day_span), |
| 103 | span_end: Some(day_span), |
| 104 | }); |
| 105 | } |
| 106 | |
| 107 | let (year, ordinal) = ymd_to_yo(year, month, day); |
| 108 | |
| 109 | Ok(Date { year, ordinal }) |
| 110 | } |
| 111 | // year-ordinal |
| 112 | else { |
| 113 | let (ordinal_span, ordinal) = (month_or_ordinal_span, month_or_ordinal); |
| 114 | |
| 115 | if ordinal == 0 || ordinal > days_in_year(year) { |
| 116 | return Err(Error::InvalidComponent { |
| 117 | name: "ordinal" , |
| 118 | value: ordinal.to_string(), |
| 119 | span_start: Some(ordinal_span), |
| 120 | span_end: Some(ordinal_span), |
| 121 | }); |
| 122 | } |
| 123 | |
| 124 | Ok(Date { year, ordinal }) |
| 125 | } |
| 126 | } |
| 127 | |
| 128 | impl ToTokenTree for Date { |
| 129 | fn into_token_tree(self) -> TokenTree { |
| 130 | quote_group! {{ |
| 131 | const DATE: ::time::Date = unsafe { |
| 132 | ::time::Date::__from_ordinal_date_unchecked( |
| 133 | #(self.year), |
| 134 | #(self.ordinal), |
| 135 | ) |
| 136 | }; |
| 137 | DATE |
| 138 | }} |
| 139 | } |
| 140 | } |
| 141 | |