mirror of
https://github.com/chronotope/chrono.git
synced 2025-09-27 04:50:52 +00:00
Add quarter (%q) date string specifier
GNU date supports %q as a date string specifier. This adds support for that in chrono. This is needed by uutils/coreutils for compability.
This commit is contained in:
parent
07216ae8fd
commit
6d29c8abe7
@ -185,6 +185,7 @@ impl<'a, I: Iterator<Item = B> + Clone, B: Borrow<Item<'a>>> DelayedFormat<I> {
|
||||
(IsoYearMod100, Some(d), _) => {
|
||||
write_two(w, d.iso_week().year().rem_euclid(100) as u8, pad)
|
||||
}
|
||||
(Quarter, Some(d), _) => write_one(w, d.quarter() as u8),
|
||||
(Month, Some(d), _) => write_two(w, d.month() as u8, pad),
|
||||
(Day, Some(d), _) => write_two(w, d.day() as u8, pad),
|
||||
(WeekFromSun, Some(d), _) => write_two(w, d.weeks_from(Weekday::Sun) as u8, pad),
|
||||
@ -657,6 +658,7 @@ mod tests {
|
||||
let d = NaiveDate::from_ymd_opt(2012, 3, 4).unwrap();
|
||||
assert_eq!(d.format("%Y,%C,%y,%G,%g").to_string(), "2012,20,12,2012,12");
|
||||
assert_eq!(d.format("%m,%b,%h,%B").to_string(), "03,Mar,Mar,March");
|
||||
assert_eq!(d.format("%q").to_string(), "1");
|
||||
assert_eq!(d.format("%d,%e").to_string(), "04, 4");
|
||||
assert_eq!(d.format("%U,%W,%V").to_string(), "10,09,09");
|
||||
assert_eq!(d.format("%a,%A,%w,%u").to_string(), "Sun,Sunday,0,7");
|
||||
|
@ -115,6 +115,8 @@ pub enum Numeric {
|
||||
IsoYearDiv100,
|
||||
/// Year in the ISO week date, modulo 100 (FW=PW=2). Cannot be negative.
|
||||
IsoYearMod100,
|
||||
/// Quarter (FW=PW=1).
|
||||
Quarter,
|
||||
/// Month (FW=PW=2).
|
||||
Month,
|
||||
/// Day of the month (FW=PW=2).
|
||||
|
@ -343,6 +343,7 @@ where
|
||||
IsoYear => (4, true, Parsed::set_isoyear),
|
||||
IsoYearDiv100 => (2, false, Parsed::set_isoyear_div_100),
|
||||
IsoYearMod100 => (2, false, Parsed::set_isoyear_mod_100),
|
||||
Quarter => (1, false, Parsed::set_quarter),
|
||||
Month => (2, false, Parsed::set_month),
|
||||
Day => (2, false, Parsed::set_day),
|
||||
WeekFromSun => (2, false, Parsed::set_week_from_sun),
|
||||
@ -819,9 +820,16 @@ mod tests {
|
||||
parsed!(year_div_100: 12, year_mod_100: 34, isoyear_div_100: 56, isoyear_mod_100: 78),
|
||||
);
|
||||
check(
|
||||
"1 2 3 4 5",
|
||||
&[num(Month), num(Day), num(WeekFromSun), num(NumDaysFromSun), num(IsoWeek)],
|
||||
parsed!(month: 1, day: 2, week_from_sun: 3, weekday: Weekday::Thu, isoweek: 5),
|
||||
"1 1 2 3 4 5",
|
||||
&[
|
||||
num(Quarter),
|
||||
num(Month),
|
||||
num(Day),
|
||||
num(WeekFromSun),
|
||||
num(NumDaysFromSun),
|
||||
num(IsoWeek),
|
||||
],
|
||||
parsed!(quarter: 1, month: 1, day: 2, week_from_sun: 3, weekday: Weekday::Thu, isoweek: 5),
|
||||
);
|
||||
check(
|
||||
"6 7 89 01",
|
||||
|
@ -140,6 +140,8 @@ pub struct Parsed {
|
||||
#[doc(hidden)]
|
||||
pub isoyear_mod_100: Option<i32>,
|
||||
#[doc(hidden)]
|
||||
pub quarter: Option<u32>,
|
||||
#[doc(hidden)]
|
||||
pub month: Option<u32>,
|
||||
#[doc(hidden)]
|
||||
pub week_from_sun: Option<u32>,
|
||||
@ -304,6 +306,23 @@ impl Parsed {
|
||||
set_if_consistent(&mut self.isoyear_mod_100, value as i32)
|
||||
}
|
||||
|
||||
/// Set the [`quarter`](Parsed::quarter) field to the given value.
|
||||
///
|
||||
/// Quarter 1 starts in January.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns `OUT_OF_RANGE` if `value` is not in the range 1-4.
|
||||
///
|
||||
/// Returns `IMPOSSIBLE` if this field was already set to a different value.
|
||||
#[inline]
|
||||
pub fn set_quarter(&mut self, value: i64) -> ParseResult<()> {
|
||||
if !(1..=4).contains(&value) {
|
||||
return Err(OUT_OF_RANGE);
|
||||
}
|
||||
set_if_consistent(&mut self.quarter, value as u32)
|
||||
}
|
||||
|
||||
/// Set the [`month`](Parsed::month) field to the given value.
|
||||
///
|
||||
/// # Errors
|
||||
@ -698,7 +717,15 @@ impl Parsed {
|
||||
(_, _, _) => return Err(NOT_ENOUGH),
|
||||
};
|
||||
|
||||
if verified { Ok(parsed_date) } else { Err(IMPOSSIBLE) }
|
||||
if !verified {
|
||||
return Err(IMPOSSIBLE);
|
||||
} else if let Some(parsed) = self.quarter {
|
||||
if parsed != parsed_date.quarter() {
|
||||
return Err(IMPOSSIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(parsed_date)
|
||||
}
|
||||
|
||||
/// Returns a parsed naive time out of given fields.
|
||||
@ -1013,6 +1040,14 @@ impl Parsed {
|
||||
self.isoyear_mod_100
|
||||
}
|
||||
|
||||
/// Get the `quarter` field if set.
|
||||
///
|
||||
/// See also [`set_quarter()`](Parsed::set_quarter).
|
||||
#[inline]
|
||||
pub fn quarter(&self) -> Option<u32> {
|
||||
self.quarter
|
||||
}
|
||||
|
||||
/// Get the `month` field if set.
|
||||
///
|
||||
/// See also [`set_month()`](Parsed::set_month).
|
||||
@ -1267,6 +1302,11 @@ mod tests {
|
||||
assert!(Parsed::new().set_isoyear_mod_100(99).is_ok());
|
||||
assert_eq!(Parsed::new().set_isoyear_mod_100(100), Err(OUT_OF_RANGE));
|
||||
|
||||
assert_eq!(Parsed::new().set_quarter(0), Err(OUT_OF_RANGE));
|
||||
assert!(Parsed::new().set_quarter(1).is_ok());
|
||||
assert!(Parsed::new().set_quarter(4).is_ok());
|
||||
assert_eq!(Parsed::new().set_quarter(5), Err(OUT_OF_RANGE));
|
||||
|
||||
assert_eq!(Parsed::new().set_month(0), Err(OUT_OF_RANGE));
|
||||
assert!(Parsed::new().set_month(1).is_ok());
|
||||
assert!(Parsed::new().set_month(12).is_ok());
|
||||
@ -1425,6 +1465,17 @@ mod tests {
|
||||
assert_eq!(parse!(year: -1, year_div_100: 0, month: 1, day: 1), Err(IMPOSSIBLE));
|
||||
assert_eq!(parse!(year: -1, year_mod_100: 99, month: 1, day: 1), Err(IMPOSSIBLE));
|
||||
|
||||
// quarters
|
||||
assert_eq!(parse!(year: 2000, quarter: 1), Err(NOT_ENOUGH));
|
||||
assert_eq!(parse!(year: 2000, quarter: 1, month: 1, day: 1), ymd(2000, 1, 1));
|
||||
assert_eq!(parse!(year: 2000, quarter: 2, month: 4, day: 1), ymd(2000, 4, 1));
|
||||
assert_eq!(parse!(year: 2000, quarter: 3, month: 7, day: 1), ymd(2000, 7, 1));
|
||||
assert_eq!(parse!(year: 2000, quarter: 4, month: 10, day: 1), ymd(2000, 10, 1));
|
||||
|
||||
// quarter: conflicting inputs
|
||||
assert_eq!(parse!(year: 2000, quarter: 2, month: 3, day: 31), Err(IMPOSSIBLE));
|
||||
assert_eq!(parse!(year: 2000, quarter: 4, month: 3, day: 31), Err(IMPOSSIBLE));
|
||||
|
||||
// weekdates
|
||||
assert_eq!(parse!(year: 2000, week_from_mon: 0), Err(NOT_ENOUGH));
|
||||
assert_eq!(parse!(year: 2000, week_from_sun: 0), Err(NOT_ENOUGH));
|
||||
|
@ -15,6 +15,7 @@ The following specifiers are available both to formatting and parsing.
|
||||
| `%C` | `20` | The proleptic Gregorian year divided by 100, zero-padded to 2 digits. [^1] |
|
||||
| `%y` | `01` | The proleptic Gregorian year modulo 100, zero-padded to 2 digits. [^1] |
|
||||
| | | |
|
||||
| `%q` | `1` | Quarter of year (1-4) |
|
||||
| `%m` | `07` | Month number (01--12), zero-padded to 2 digits. |
|
||||
| `%b` | `Jul` | Abbreviated month name. Always 3 letters. |
|
||||
| `%B` | `July` | Full month name. Also accepts corresponding abbreviation in parsing. |
|
||||
@ -538,6 +539,7 @@ impl<'a> StrftimeItems<'a> {
|
||||
'm' => num0(Month),
|
||||
'n' => Space("\n"),
|
||||
'p' => fixed(Fixed::UpperAmPm),
|
||||
'q' => num(Quarter),
|
||||
#[cfg(not(feature = "unstable-locales"))]
|
||||
'r' => queue_from_slice!(T_FMT_AMPM),
|
||||
#[cfg(feature = "unstable-locales")]
|
||||
@ -866,6 +868,7 @@ mod tests {
|
||||
assert_eq!(dt.format("%Y").to_string(), "2001");
|
||||
assert_eq!(dt.format("%C").to_string(), "20");
|
||||
assert_eq!(dt.format("%y").to_string(), "01");
|
||||
assert_eq!(dt.format("%q").to_string(), "3");
|
||||
assert_eq!(dt.format("%m").to_string(), "07");
|
||||
assert_eq!(dt.format("%b").to_string(), "Jul");
|
||||
assert_eq!(dt.format("%B").to_string(), "July");
|
||||
|
@ -666,7 +666,7 @@ fn test_date_parse_from_str() {
|
||||
Ok(ymd(2014, 5, 7))
|
||||
); // ignore time and offset
|
||||
assert_eq!(
|
||||
NaiveDate::parse_from_str("2015-W06-1=2015-033", "%G-W%V-%u = %Y-%j"),
|
||||
NaiveDate::parse_from_str("2015-W06-1=2015-033 Q1", "%G-W%V-%u = %Y-%j Q%q"),
|
||||
Ok(ymd(2015, 2, 2))
|
||||
);
|
||||
assert_eq!(NaiveDate::parse_from_str("Fri, 09 Aug 13", "%a, %d %b %y"), Ok(ymd(2013, 8, 9)));
|
||||
@ -674,6 +674,8 @@ fn test_date_parse_from_str() {
|
||||
assert!(NaiveDate::parse_from_str("2014-57", "%Y-%m-%d").is_err());
|
||||
assert!(NaiveDate::parse_from_str("2014", "%Y").is_err()); // insufficient
|
||||
|
||||
assert!(NaiveDate::parse_from_str("2014-5-7 Q3", "%Y-%m-%d Q%q").is_err()); // mismatched quarter
|
||||
|
||||
assert_eq!(
|
||||
NaiveDate::parse_from_str("2020-01-0", "%Y-%W-%w").ok(),
|
||||
NaiveDate::from_ymd_opt(2020, 1, 12),
|
||||
|
@ -203,7 +203,7 @@ fn test_datetime_parse_from_str() {
|
||||
NaiveDateTime::parse_from_str("Sat, 09 Aug 2013 23:54:35 GMT", "%a, %d %b %Y %H:%M:%S GMT")
|
||||
.is_err()
|
||||
);
|
||||
assert!(NaiveDateTime::parse_from_str("2014-5-7 12:3456", "%Y-%m-%d %H:%M:%S").is_err());
|
||||
assert!(NaiveDateTime::parse_from_str("2014-5-7 Q2 12:3456", "%Y-%m-%d Q%q %H:%M:%S").is_err());
|
||||
assert!(NaiveDateTime::parse_from_str("12:34:56", "%H:%M:%S").is_err()); // insufficient
|
||||
assert_eq!(
|
||||
NaiveDateTime::parse_from_str("1441497364", "%s"),
|
||||
|
@ -40,6 +40,14 @@ pub trait Datelike: Sized {
|
||||
if year < 1 { (false, (1 - year) as u32) } else { (true, year as u32) }
|
||||
}
|
||||
|
||||
/// Returns the quarter number starting from 1.
|
||||
///
|
||||
/// The return value ranges from 1 to 4.
|
||||
#[inline]
|
||||
fn quarter(&self) -> u32 {
|
||||
(self.month() - 1).div_euclid(3) + 1
|
||||
}
|
||||
|
||||
/// Returns the month number starting from 1.
|
||||
///
|
||||
/// The return value ranges from 1 to 12.
|
||||
|
@ -110,7 +110,7 @@ fn try_verify_against_date_command() {
|
||||
#[cfg(target_os = "linux")]
|
||||
fn verify_against_date_command_format_local(path: &'static str, dt: NaiveDateTime) {
|
||||
let required_format =
|
||||
"d%d D%D F%F H%H I%I j%j k%k l%l m%m M%M S%S T%T u%u U%U w%w W%W X%X y%y Y%Y z%:z";
|
||||
"d%d D%D F%F H%H I%I j%j k%k l%l m%m M%M q%q S%S T%T u%u U%U w%w W%W X%X y%y Y%Y z%:z";
|
||||
// a%a - depends from localization
|
||||
// A%A - depends from localization
|
||||
// b%b - depends from localization
|
||||
|
Loading…
x
Reference in New Issue
Block a user