| 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 |  | 
|---|