mirror of
https://github.com/chronotope/chrono.git
synced 2025-09-28 13:31:35 +00:00
Merge branch '0.4.x'
This commit is contained in:
commit
121f5c7323
3
.github/workflows/test.yml
vendored
3
.github/workflows/test.yml
vendored
@ -93,8 +93,7 @@ jobs:
|
||||
# run using `bash` on all platforms for consistent
|
||||
# line-continuation marks
|
||||
shell: bash
|
||||
- run: cargo test --lib --no-default-features
|
||||
- run: cargo test --doc --no-default-features
|
||||
- run: cargo test --no-default-features
|
||||
|
||||
no_std:
|
||||
strategy:
|
||||
|
@ -29,7 +29,7 @@ __doctest = []
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.0.99", default-features = false, optional = true }
|
||||
pure-rust-locales = { version = "0.5.2", optional = true }
|
||||
pure-rust-locales = { version = "0.6", optional = true }
|
||||
criterion = { version = "0.4.0", optional = true }
|
||||
rkyv = { version = "0.7", optional = true }
|
||||
arbitrary = { version = "1.0.0", features = ["derive"], optional = true }
|
||||
|
@ -3,7 +3,10 @@
|
||||
|
||||
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion};
|
||||
|
||||
use chrono::format::StrftimeItems;
|
||||
use chrono::prelude::*;
|
||||
#[cfg(feature = "unstable-locales")]
|
||||
use chrono::Locale;
|
||||
use chrono::{DateTime, FixedOffset, Local, Utc, __BenchYearFlags};
|
||||
|
||||
fn bench_datetime_parse_from_rfc2822(c: &mut Criterion) {
|
||||
@ -122,6 +125,57 @@ fn bench_num_days_from_ce(c: &mut Criterion) {
|
||||
}
|
||||
}
|
||||
|
||||
fn bench_parse_strftime(c: &mut Criterion) {
|
||||
c.bench_function("bench_parse_strftime", |b| {
|
||||
b.iter(|| {
|
||||
let str = black_box("%a, %d %b %Y %H:%M:%S GMT");
|
||||
let items = StrftimeItems::new(str);
|
||||
black_box(items.collect::<Vec<_>>());
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(feature = "unstable-locales")]
|
||||
fn bench_parse_strftime_localized(c: &mut Criterion) {
|
||||
c.bench_function("bench_parse_strftime_localized", |b| {
|
||||
b.iter(|| {
|
||||
let str = black_box("%a, %d %b %Y %H:%M:%S GMT");
|
||||
let items = StrftimeItems::new_with_locale(str, Locale::nl_NL);
|
||||
black_box(items.collect::<Vec<_>>());
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn bench_format(c: &mut Criterion) {
|
||||
let dt = Local::now();
|
||||
c.bench_function("bench_format", |b| b.iter(|| format!("{}", dt.format("%Y-%m-%d %H-%M-%S"))));
|
||||
}
|
||||
|
||||
fn bench_format_with_items(c: &mut Criterion) {
|
||||
let dt = Local::now();
|
||||
let items: Vec<_> = StrftimeItems::new("%Y-%m-%d %H-%M-%S").collect();
|
||||
c.bench_function("bench_format_with_items", |b| {
|
||||
b.iter(|| format!("{}", dt.format_with_items(items.iter())))
|
||||
});
|
||||
}
|
||||
|
||||
fn bench_format_manual(c: &mut Criterion) {
|
||||
let dt = Local::now();
|
||||
c.bench_function("bench_format_manual", |b| {
|
||||
b.iter(|| {
|
||||
format!(
|
||||
"{}-{:02}-{:02} {:02}:{:02}:{:02}",
|
||||
dt.year(),
|
||||
dt.month(),
|
||||
dt.day(),
|
||||
dt.hour(),
|
||||
dt.minute(),
|
||||
dt.second()
|
||||
)
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
criterion_group!(
|
||||
benches,
|
||||
bench_datetime_parse_from_rfc2822,
|
||||
@ -132,6 +186,16 @@ criterion_group!(
|
||||
bench_year_flags_from_year,
|
||||
bench_num_days_from_ce,
|
||||
bench_get_local_time,
|
||||
bench_parse_strftime,
|
||||
bench_format,
|
||||
bench_format_with_items,
|
||||
bench_format_manual,
|
||||
);
|
||||
|
||||
#[cfg(feature = "unstable-locales")]
|
||||
criterion_group!(unstable_locales, bench_parse_strftime_localized,);
|
||||
|
||||
#[cfg(not(feature = "unstable-locales"))]
|
||||
criterion_main!(benches);
|
||||
#[cfg(feature = "unstable-locales")]
|
||||
criterion_main!(benches, unstable_locales);
|
||||
|
@ -4,7 +4,7 @@
|
||||
//! ISO 8601 calendar date with time zone.
|
||||
#![allow(deprecated)]
|
||||
|
||||
#[cfg(any(feature = "alloc", feature = "std", test))]
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
use core::borrow::Borrow;
|
||||
use core::cmp::Ordering;
|
||||
use core::ops::{Add, AddAssign, Sub, SubAssign};
|
||||
@ -15,7 +15,7 @@ use rkyv::{Archive, Deserialize, Serialize};
|
||||
|
||||
#[cfg(feature = "unstable-locales")]
|
||||
use crate::format::Locale;
|
||||
#[cfg(any(feature = "alloc", feature = "std", test))]
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
use crate::format::{DelayedFormat, Item, StrftimeItems};
|
||||
use crate::naive::{IsoWeek, NaiveDate, NaiveTime};
|
||||
use crate::offset::{TimeZone, Utc};
|
||||
@ -333,7 +333,7 @@ where
|
||||
Tz::Offset: fmt::Display,
|
||||
{
|
||||
/// Formats the date with the specified formatting items.
|
||||
#[cfg(any(feature = "alloc", feature = "std", test))]
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))]
|
||||
#[inline]
|
||||
#[must_use]
|
||||
@ -348,7 +348,7 @@ where
|
||||
/// Formats the date with the specified format string.
|
||||
/// See the [`crate::format::strftime`] module
|
||||
/// on the supported escape sequences.
|
||||
#[cfg(any(feature = "alloc", feature = "std", test))]
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))]
|
||||
#[inline]
|
||||
#[must_use]
|
||||
|
@ -15,13 +15,10 @@ use core::ops::{Add, AddAssign, Sub, SubAssign};
|
||||
use core::{fmt, hash, str};
|
||||
#[cfg(feature = "std")]
|
||||
use std::string::ToString;
|
||||
#[cfg(any(feature = "std", test))]
|
||||
#[cfg(feature = "std")]
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
#[cfg(feature = "rkyv")]
|
||||
use rkyv::{Archive, Deserialize, Serialize};
|
||||
|
||||
#[cfg(any(feature = "alloc", feature = "std", test))]
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
use crate::format::DelayedFormat;
|
||||
#[cfg(feature = "unstable-locales")]
|
||||
use crate::format::Locale;
|
||||
@ -35,6 +32,9 @@ use crate::offset::{FixedOffset, Offset, TimeZone, Utc};
|
||||
use crate::Date;
|
||||
use crate::{Datelike, Months, TimeDelta, Timelike, Weekday};
|
||||
|
||||
#[cfg(feature = "rkyv")]
|
||||
use rkyv::{Archive, Deserialize, Serialize};
|
||||
|
||||
/// documented at re-export site
|
||||
#[cfg(feature = "serde")]
|
||||
pub(super) mod serde;
|
||||
@ -480,6 +480,100 @@ impl<Tz: TimeZone> DateTime<Tz> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an RFC 2822 date and time string such as `Tue, 1 Jul 2003 10:52:37 +0200`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the date can not be represented in this format: the year may not be negative and
|
||||
/// can not have more than 4 digits.
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))]
|
||||
#[must_use]
|
||||
pub fn to_rfc2822(&self) -> String {
|
||||
let mut result = String::with_capacity(32);
|
||||
crate::format::write_rfc2822(&mut result, self.naive_local(), self.offset.fix())
|
||||
.expect("writing rfc2822 datetime to string should never fail");
|
||||
result
|
||||
}
|
||||
|
||||
/// Returns an RFC 3339 and ISO 8601 date and time string such as `1996-12-19T16:39:57-08:00`.
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))]
|
||||
#[must_use]
|
||||
pub fn to_rfc3339(&self) -> String {
|
||||
let mut result = String::with_capacity(32);
|
||||
crate::format::write_rfc3339(&mut result, self.naive_local(), self.offset.fix())
|
||||
.expect("writing rfc3339 datetime to string should never fail");
|
||||
result
|
||||
}
|
||||
|
||||
/// Return an RFC 3339 and ISO 8601 date and time string with subseconds
|
||||
/// formatted as per `SecondsFormat`.
|
||||
///
|
||||
/// If `use_z` is true and the timezone is UTC (offset 0), uses `Z` as
|
||||
/// per [`Fixed::TimezoneOffsetColonZ`]. If `use_z` is false, uses
|
||||
/// [`Fixed::TimezoneOffsetColon`]
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use chrono::{FixedOffset, SecondsFormat, TimeZone, Utc, NaiveDate};
|
||||
/// let dt = NaiveDate::from_ymd_opt(2018, 1, 26).unwrap().and_hms_micro_opt(18, 30, 9, 453_829).unwrap().and_local_timezone(Utc).unwrap();
|
||||
/// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Millis, false),
|
||||
/// "2018-01-26T18:30:09.453+00:00");
|
||||
/// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Millis, true),
|
||||
/// "2018-01-26T18:30:09.453Z");
|
||||
/// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Secs, true),
|
||||
/// "2018-01-26T18:30:09Z");
|
||||
///
|
||||
/// let pst = FixedOffset::east_opt(8 * 60 * 60).unwrap();
|
||||
/// let dt = pst.from_local_datetime(&NaiveDate::from_ymd_opt(2018, 1, 26).unwrap().and_hms_micro_opt(10, 30, 9, 453_829).unwrap()).unwrap();
|
||||
/// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Secs, true),
|
||||
/// "2018-01-26T10:30:09+08:00");
|
||||
/// ```
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))]
|
||||
#[must_use]
|
||||
pub fn to_rfc3339_opts(&self, secform: SecondsFormat, use_z: bool) -> String {
|
||||
use crate::format::Numeric::*;
|
||||
use crate::format::Pad::Zero;
|
||||
use crate::SecondsFormat::*;
|
||||
|
||||
const PREFIX: &[Item<'static>] = &[
|
||||
Item::Numeric(Year, Zero),
|
||||
Item::Literal("-"),
|
||||
Item::Numeric(Month, Zero),
|
||||
Item::Literal("-"),
|
||||
Item::Numeric(Day, Zero),
|
||||
Item::Literal("T"),
|
||||
Item::Numeric(Hour, Zero),
|
||||
Item::Literal(":"),
|
||||
Item::Numeric(Minute, Zero),
|
||||
Item::Literal(":"),
|
||||
Item::Numeric(Second, Zero),
|
||||
];
|
||||
|
||||
let ssitem = match secform {
|
||||
Secs => None,
|
||||
Millis => Some(Item::Fixed(Fixed::Nanosecond3)),
|
||||
Micros => Some(Item::Fixed(Fixed::Nanosecond6)),
|
||||
Nanos => Some(Item::Fixed(Fixed::Nanosecond9)),
|
||||
AutoSi => Some(Item::Fixed(Fixed::Nanosecond)),
|
||||
};
|
||||
|
||||
let tzitem = Item::Fixed(if use_z {
|
||||
Fixed::TimezoneOffsetColonZ
|
||||
} else {
|
||||
Fixed::TimezoneOffsetColon
|
||||
});
|
||||
|
||||
let dt = self.fixed_offset();
|
||||
match ssitem {
|
||||
None => dt.format_with_items(PREFIX.iter().chain([tzitem].iter())).to_string(),
|
||||
Some(s) => dt.format_with_items(PREFIX.iter().chain([s, tzitem].iter())).to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
/// The minimum possible `DateTime<Utc>`.
|
||||
pub const MIN_UTC: DateTime<Utc> = DateTime { datetime: NaiveDateTime::MIN, offset: Utc };
|
||||
/// The maximum possible `DateTime<Utc>`.
|
||||
@ -757,101 +851,8 @@ impl<Tz: TimeZone> DateTime<Tz>
|
||||
where
|
||||
Tz::Offset: fmt::Display,
|
||||
{
|
||||
/// Returns an RFC 2822 date and time string such as `Tue, 1 Jul 2003 10:52:37 +0200`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the date can not be represented in this format: the year may not be negative and
|
||||
/// can not have more than 4 digits.
|
||||
#[cfg(any(feature = "alloc", feature = "std", test))]
|
||||
#[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))]
|
||||
#[must_use]
|
||||
pub fn to_rfc2822(&self) -> String {
|
||||
let mut result = String::with_capacity(32);
|
||||
crate::format::write_rfc2822(&mut result, self.naive_local(), self.offset.fix())
|
||||
.expect("writing rfc2822 datetime to string should never fail");
|
||||
result
|
||||
}
|
||||
|
||||
/// Returns an RFC 3339 and ISO 8601 date and time string such as `1996-12-19T16:39:57-08:00`.
|
||||
#[cfg(any(feature = "alloc", feature = "std", test))]
|
||||
#[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))]
|
||||
#[must_use]
|
||||
pub fn to_rfc3339(&self) -> String {
|
||||
let mut result = String::with_capacity(32);
|
||||
crate::format::write_rfc3339(&mut result, self.naive_local(), self.offset.fix())
|
||||
.expect("writing rfc3339 datetime to string should never fail");
|
||||
result
|
||||
}
|
||||
|
||||
/// Return an RFC 3339 and ISO 8601 date and time string with subseconds
|
||||
/// formatted as per `SecondsFormat`.
|
||||
///
|
||||
/// If `use_z` is true and the timezone is UTC (offset 0), uses `Z` as
|
||||
/// per [`Fixed::TimezoneOffsetColonZ`]. If `use_z` is false, uses
|
||||
/// [`Fixed::TimezoneOffsetColon`]
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use chrono::{FixedOffset, SecondsFormat, TimeZone, Utc, NaiveDate};
|
||||
/// let dt = NaiveDate::from_ymd_opt(2018, 1, 26).unwrap().and_hms_micro_opt(18, 30, 9, 453_829).unwrap().and_local_timezone(Utc).unwrap();
|
||||
/// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Millis, false),
|
||||
/// "2018-01-26T18:30:09.453+00:00");
|
||||
/// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Millis, true),
|
||||
/// "2018-01-26T18:30:09.453Z");
|
||||
/// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Secs, true),
|
||||
/// "2018-01-26T18:30:09Z");
|
||||
///
|
||||
/// let pst = FixedOffset::east_opt(8 * 60 * 60).unwrap();
|
||||
/// let dt = pst.from_local_datetime(&NaiveDate::from_ymd_opt(2018, 1, 26).unwrap().and_hms_micro_opt(10, 30, 9, 453_829).unwrap()).unwrap();
|
||||
/// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Secs, true),
|
||||
/// "2018-01-26T10:30:09+08:00");
|
||||
/// ```
|
||||
#[cfg(any(feature = "alloc", feature = "std", test))]
|
||||
#[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))]
|
||||
#[must_use]
|
||||
pub fn to_rfc3339_opts(&self, secform: SecondsFormat, use_z: bool) -> String {
|
||||
use crate::format::Numeric::*;
|
||||
use crate::format::Pad::Zero;
|
||||
use crate::SecondsFormat::*;
|
||||
|
||||
const PREFIX: &[Item<'static>] = &[
|
||||
Item::Numeric(Year, Zero),
|
||||
Item::Literal("-"),
|
||||
Item::Numeric(Month, Zero),
|
||||
Item::Literal("-"),
|
||||
Item::Numeric(Day, Zero),
|
||||
Item::Literal("T"),
|
||||
Item::Numeric(Hour, Zero),
|
||||
Item::Literal(":"),
|
||||
Item::Numeric(Minute, Zero),
|
||||
Item::Literal(":"),
|
||||
Item::Numeric(Second, Zero),
|
||||
];
|
||||
|
||||
let ssitem = match secform {
|
||||
Secs => None,
|
||||
Millis => Some(Item::Fixed(Fixed::Nanosecond3)),
|
||||
Micros => Some(Item::Fixed(Fixed::Nanosecond6)),
|
||||
Nanos => Some(Item::Fixed(Fixed::Nanosecond9)),
|
||||
AutoSi => Some(Item::Fixed(Fixed::Nanosecond)),
|
||||
};
|
||||
|
||||
let tzitem = Item::Fixed(if use_z {
|
||||
Fixed::TimezoneOffsetColonZ
|
||||
} else {
|
||||
Fixed::TimezoneOffsetColon
|
||||
});
|
||||
|
||||
match ssitem {
|
||||
None => self.format_with_items(PREFIX.iter().chain([tzitem].iter())).to_string(),
|
||||
Some(s) => self.format_with_items(PREFIX.iter().chain([s, tzitem].iter())).to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Formats the combined date and time with the specified formatting items.
|
||||
#[cfg(any(feature = "alloc", feature = "std", test))]
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))]
|
||||
#[inline]
|
||||
#[must_use]
|
||||
@ -876,7 +877,7 @@ where
|
||||
/// let formatted = format!("{}", date_time.format("%d/%m/%Y %H:%M"));
|
||||
/// assert_eq!(formatted, "02/04/2017 12:50");
|
||||
/// ```
|
||||
#[cfg(any(feature = "alloc", feature = "std", test))]
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))]
|
||||
#[inline]
|
||||
#[must_use]
|
||||
@ -1354,7 +1355,7 @@ impl str::FromStr for DateTime<Local> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "std", test))]
|
||||
#[cfg(feature = "std")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
|
||||
impl From<SystemTime> for DateTime<Utc> {
|
||||
fn from(t: SystemTime) -> DateTime<Utc> {
|
||||
@ -1383,7 +1384,7 @@ impl From<SystemTime> for DateTime<Local> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "std", test))]
|
||||
#[cfg(feature = "std")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
|
||||
impl<Tz: TimeZone> From<DateTime<Tz>> for SystemTime {
|
||||
fn from(dt: DateTime<Tz>) -> SystemTime {
|
||||
@ -1548,7 +1549,7 @@ fn test_decodable_json<FUtc, FFixed, FLocal, E>(
|
||||
// we don't know the exact local offset but we can check that
|
||||
// the conversion didn't change the instant itself
|
||||
assert_eq!(
|
||||
local_from_str(r#""2014-07-24T12:34:06Z""#).expect("local shouuld parse"),
|
||||
local_from_str(r#""2014-07-24T12:34:06Z""#).expect("local should parse"),
|
||||
Utc.with_ymd_and_hms(2014, 7, 24, 12, 34, 6).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
|
@ -1122,7 +1122,9 @@ pub mod ts_seconds_option {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::datetime::{test_decodable_json, test_encodable_json};
|
||||
#[cfg(feature = "clock")]
|
||||
use crate::datetime::test_decodable_json;
|
||||
use crate::datetime::test_encodable_json;
|
||||
use crate::{DateTime, TimeZone, Utc};
|
||||
|
||||
#[test]
|
||||
|
@ -1,5 +1,3 @@
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use super::DateTime;
|
||||
use crate::naive::{NaiveDate, NaiveTime};
|
||||
use crate::offset::{FixedOffset, TimeZone, Utc};
|
||||
@ -449,6 +447,7 @@ fn test_datetime_with_timezone() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
fn test_datetime_rfc2822() {
|
||||
let edt = FixedOffset::east_opt(5 * 60 * 60).unwrap();
|
||||
|
||||
@ -553,6 +552,7 @@ fn test_datetime_rfc2822() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
fn test_datetime_rfc3339() {
|
||||
let edt5 = FixedOffset::east_opt(5 * 60 * 60).unwrap();
|
||||
let edt0 = FixedOffset::east_opt(0).unwrap();
|
||||
@ -678,6 +678,7 @@ fn test_datetime_rfc3339() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
fn test_rfc3339_opts() {
|
||||
use crate::SecondsFormat::*;
|
||||
let pst = FixedOffset::east_opt(8 * 60 * 60).unwrap();
|
||||
@ -1221,79 +1222,9 @@ fn test_subsecond_part() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
#[cfg(feature = "std")]
|
||||
fn test_from_system_time() {
|
||||
use std::time::Duration;
|
||||
|
||||
let epoch = Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).unwrap();
|
||||
let nanos = 999_999_999;
|
||||
|
||||
// SystemTime -> DateTime<Utc>
|
||||
assert_eq!(DateTime::<Utc>::from(UNIX_EPOCH), epoch);
|
||||
assert_eq!(
|
||||
DateTime::<Utc>::from(UNIX_EPOCH + Duration::new(999_999_999, nanos)),
|
||||
Utc.from_local_datetime(
|
||||
&NaiveDate::from_ymd_opt(2001, 9, 9)
|
||||
.unwrap()
|
||||
.and_hms_nano_opt(1, 46, 39, nanos)
|
||||
.unwrap()
|
||||
)
|
||||
.unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
DateTime::<Utc>::from(UNIX_EPOCH - Duration::new(999_999_999, nanos)),
|
||||
Utc.from_local_datetime(
|
||||
&NaiveDate::from_ymd_opt(1938, 4, 24).unwrap().and_hms_nano_opt(22, 13, 20, 1).unwrap()
|
||||
)
|
||||
.unwrap()
|
||||
);
|
||||
|
||||
// DateTime<Utc> -> SystemTime
|
||||
assert_eq!(SystemTime::from(epoch), UNIX_EPOCH);
|
||||
assert_eq!(
|
||||
SystemTime::from(
|
||||
Utc.from_local_datetime(
|
||||
&NaiveDate::from_ymd_opt(2001, 9, 9)
|
||||
.unwrap()
|
||||
.and_hms_nano_opt(1, 46, 39, nanos)
|
||||
.unwrap()
|
||||
)
|
||||
.unwrap()
|
||||
),
|
||||
UNIX_EPOCH + Duration::new(999_999_999, nanos)
|
||||
);
|
||||
assert_eq!(
|
||||
SystemTime::from(
|
||||
Utc.from_local_datetime(
|
||||
&NaiveDate::from_ymd_opt(1938, 4, 24)
|
||||
.unwrap()
|
||||
.and_hms_nano_opt(22, 13, 20, 1)
|
||||
.unwrap()
|
||||
)
|
||||
.unwrap()
|
||||
),
|
||||
UNIX_EPOCH - Duration::new(999_999_999, 999_999_999)
|
||||
);
|
||||
|
||||
// DateTime<any tz> -> SystemTime (via `with_timezone`)
|
||||
#[cfg(feature = "clock")]
|
||||
{
|
||||
assert_eq!(SystemTime::from(epoch.with_timezone(&Local)), UNIX_EPOCH);
|
||||
}
|
||||
assert_eq!(
|
||||
SystemTime::from(epoch.with_timezone(&FixedOffset::east_opt(32400).unwrap())),
|
||||
UNIX_EPOCH
|
||||
);
|
||||
assert_eq!(
|
||||
SystemTime::from(epoch.with_timezone(&FixedOffset::west_opt(28800).unwrap())),
|
||||
UNIX_EPOCH
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "windows")]
|
||||
fn test_from_system_time() {
|
||||
use std::time::Duration;
|
||||
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||
|
||||
let nanos = 999_999_000;
|
||||
|
||||
@ -1365,6 +1296,7 @@ fn test_from_system_time() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
fn test_datetime_format_alignment() {
|
||||
let datetime = Utc.with_ymd_and_hms(2007, 1, 2, 0, 0, 0).unwrap();
|
||||
|
||||
|
821
src/format/formatting.rs
Normal file
821
src/format/formatting.rs
Normal file
@ -0,0 +1,821 @@
|
||||
// This is a part of Chrono.
|
||||
// See README.md and LICENSE.txt for details.
|
||||
|
||||
//! Date and time formatting routines.
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
extern crate alloc;
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
use alloc::string::{String, ToString};
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
use core::borrow::Borrow;
|
||||
use core::fmt;
|
||||
use core::fmt::Write;
|
||||
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
use crate::naive::{NaiveDate, NaiveTime};
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
use crate::offset::{FixedOffset, Offset};
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
use crate::{Datelike, Timelike, Weekday};
|
||||
|
||||
#[cfg(feature = "unstable-locales")]
|
||||
use super::locales;
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
use super::{
|
||||
Colons, Fixed, InternalFixed, InternalInternal, Item, Locale, Numeric, OffsetFormat,
|
||||
OffsetPrecision, Pad,
|
||||
};
|
||||
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
struct Locales {
|
||||
short_months: &'static [&'static str],
|
||||
long_months: &'static [&'static str],
|
||||
short_weekdays: &'static [&'static str],
|
||||
long_weekdays: &'static [&'static str],
|
||||
am_pm: &'static [&'static str],
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
impl Locales {
|
||||
fn new(_locale: Option<Locale>) -> Self {
|
||||
#[cfg(feature = "unstable-locales")]
|
||||
{
|
||||
let locale = _locale.unwrap_or(Locale::POSIX);
|
||||
Self {
|
||||
short_months: locales::short_months(locale),
|
||||
long_months: locales::long_months(locale),
|
||||
short_weekdays: locales::short_weekdays(locale),
|
||||
long_weekdays: locales::long_weekdays(locale),
|
||||
am_pm: locales::am_pm(locale),
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "unstable-locales"))]
|
||||
Self {
|
||||
short_months: &[
|
||||
"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
|
||||
],
|
||||
long_months: &[
|
||||
"January",
|
||||
"February",
|
||||
"March",
|
||||
"April",
|
||||
"May",
|
||||
"June",
|
||||
"July",
|
||||
"August",
|
||||
"September",
|
||||
"October",
|
||||
"November",
|
||||
"December",
|
||||
],
|
||||
short_weekdays: &["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
|
||||
long_weekdays: &[
|
||||
"Sunday",
|
||||
"Monday",
|
||||
"Tuesday",
|
||||
"Wednesday",
|
||||
"Thursday",
|
||||
"Friday",
|
||||
"Saturday",
|
||||
],
|
||||
am_pm: &["AM", "PM"],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A *temporary* object which can be used as an argument to `format!` or others.
|
||||
/// This is normally constructed via `format` methods of each date and time type.
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))]
|
||||
#[derive(Debug)]
|
||||
pub struct DelayedFormat<I> {
|
||||
/// The date view, if any.
|
||||
date: Option<NaiveDate>,
|
||||
/// The time view, if any.
|
||||
time: Option<NaiveTime>,
|
||||
/// The name and local-to-UTC difference for the offset (timezone), if any.
|
||||
off: Option<(String, FixedOffset)>,
|
||||
/// An iterator returning formatting items.
|
||||
items: I,
|
||||
/// Locale used for text.
|
||||
// TODO: Only used with the locale feature. We should make this property
|
||||
// only present when the feature is enabled.
|
||||
#[cfg(feature = "unstable-locales")]
|
||||
locale: Option<Locale>,
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
impl<'a, I: Iterator<Item = B> + Clone, B: Borrow<Item<'a>>> DelayedFormat<I> {
|
||||
/// Makes a new `DelayedFormat` value out of local date and time.
|
||||
#[must_use]
|
||||
pub fn new(date: Option<NaiveDate>, time: Option<NaiveTime>, items: I) -> DelayedFormat<I> {
|
||||
DelayedFormat {
|
||||
date,
|
||||
time,
|
||||
off: None,
|
||||
items,
|
||||
#[cfg(feature = "unstable-locales")]
|
||||
locale: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Makes a new `DelayedFormat` value out of local date and time and UTC offset.
|
||||
#[must_use]
|
||||
pub fn new_with_offset<Off>(
|
||||
date: Option<NaiveDate>,
|
||||
time: Option<NaiveTime>,
|
||||
offset: &Off,
|
||||
items: I,
|
||||
) -> DelayedFormat<I>
|
||||
where
|
||||
Off: Offset + fmt::Display,
|
||||
{
|
||||
let name_and_diff = (offset.to_string(), offset.fix());
|
||||
DelayedFormat {
|
||||
date,
|
||||
time,
|
||||
off: Some(name_and_diff),
|
||||
items,
|
||||
#[cfg(feature = "unstable-locales")]
|
||||
locale: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Makes a new `DelayedFormat` value out of local date and time and locale.
|
||||
#[cfg(feature = "unstable-locales")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))]
|
||||
#[must_use]
|
||||
pub fn new_with_locale(
|
||||
date: Option<NaiveDate>,
|
||||
time: Option<NaiveTime>,
|
||||
items: I,
|
||||
locale: Locale,
|
||||
) -> DelayedFormat<I> {
|
||||
DelayedFormat { date, time, off: None, items, locale: Some(locale) }
|
||||
}
|
||||
|
||||
/// Makes a new `DelayedFormat` value out of local date and time, UTC offset and locale.
|
||||
#[cfg(feature = "unstable-locales")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))]
|
||||
#[must_use]
|
||||
pub fn new_with_offset_and_locale<Off>(
|
||||
date: Option<NaiveDate>,
|
||||
time: Option<NaiveTime>,
|
||||
offset: &Off,
|
||||
items: I,
|
||||
locale: Locale,
|
||||
) -> DelayedFormat<I>
|
||||
where
|
||||
Off: Offset + fmt::Display,
|
||||
{
|
||||
let name_and_diff = (offset.to_string(), offset.fix());
|
||||
DelayedFormat { date, time, off: Some(name_and_diff), items, locale: Some(locale) }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
impl<'a, I: Iterator<Item = B> + Clone, B: Borrow<Item<'a>>> fmt::Display for DelayedFormat<I> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
#[cfg(feature = "unstable-locales")]
|
||||
{
|
||||
if let Some(locale) = self.locale {
|
||||
return format_localized(
|
||||
f,
|
||||
self.date.as_ref(),
|
||||
self.time.as_ref(),
|
||||
self.off.as_ref(),
|
||||
self.items.clone(),
|
||||
locale,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
format(f, self.date.as_ref(), self.time.as_ref(), self.off.as_ref(), self.items.clone())
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to format given arguments with given formatting items.
|
||||
/// Internally used by `DelayedFormat`.
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))]
|
||||
pub fn format<'a, I, B>(
|
||||
w: &mut fmt::Formatter,
|
||||
date: Option<&NaiveDate>,
|
||||
time: Option<&NaiveTime>,
|
||||
off: Option<&(String, FixedOffset)>,
|
||||
items: I,
|
||||
) -> fmt::Result
|
||||
where
|
||||
I: Iterator<Item = B> + Clone,
|
||||
B: Borrow<Item<'a>>,
|
||||
{
|
||||
let mut result = String::new();
|
||||
for item in items {
|
||||
format_inner(&mut result, date, time, off, item.borrow(), None)?;
|
||||
}
|
||||
w.pad(&result)
|
||||
}
|
||||
/// Formats single formatting item
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))]
|
||||
pub fn format_item(
|
||||
w: &mut fmt::Formatter,
|
||||
date: Option<&NaiveDate>,
|
||||
time: Option<&NaiveTime>,
|
||||
off: Option<&(String, FixedOffset)>,
|
||||
item: &Item<'_>,
|
||||
) -> fmt::Result {
|
||||
let mut result = String::new();
|
||||
format_inner(&mut result, date, time, off, item, None)?;
|
||||
w.pad(&result)
|
||||
}
|
||||
|
||||
/// Tries to format given arguments with given formatting items.
|
||||
/// Internally used by `DelayedFormat`.
|
||||
#[cfg(feature = "unstable-locales")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))]
|
||||
pub fn format_localized<'a, I, B>(
|
||||
w: &mut fmt::Formatter,
|
||||
date: Option<&NaiveDate>,
|
||||
time: Option<&NaiveTime>,
|
||||
off: Option<&(String, FixedOffset)>,
|
||||
items: I,
|
||||
locale: Locale,
|
||||
) -> fmt::Result
|
||||
where
|
||||
I: Iterator<Item = B> + Clone,
|
||||
B: Borrow<Item<'a>>,
|
||||
{
|
||||
let mut result = String::new();
|
||||
for item in items {
|
||||
format_inner(&mut result, date, time, off, item.borrow(), Some(locale))?;
|
||||
}
|
||||
w.pad(&result)
|
||||
}
|
||||
|
||||
/// Formats single formatting item
|
||||
#[cfg(feature = "unstable-locales")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))]
|
||||
pub fn format_item_localized(
|
||||
w: &mut fmt::Formatter,
|
||||
date: Option<&NaiveDate>,
|
||||
time: Option<&NaiveTime>,
|
||||
off: Option<&(String, FixedOffset)>,
|
||||
item: &Item<'_>,
|
||||
locale: Locale,
|
||||
) -> fmt::Result {
|
||||
let mut result = String::new();
|
||||
format_inner(&mut result, date, time, off, item, Some(locale))?;
|
||||
w.pad(&result)
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
fn format_inner(
|
||||
result: &mut String,
|
||||
date: Option<&NaiveDate>,
|
||||
time: Option<&NaiveTime>,
|
||||
off: Option<&(String, FixedOffset)>,
|
||||
item: &Item<'_>,
|
||||
locale: Option<Locale>,
|
||||
) -> fmt::Result {
|
||||
let locale = Locales::new(locale);
|
||||
|
||||
match *item {
|
||||
Item::Literal(s) | Item::Space(s) => result.push_str(s),
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
Item::OwnedLiteral(ref s) | Item::OwnedSpace(ref s) => result.push_str(s),
|
||||
|
||||
Item::Numeric(ref spec, ref pad) => {
|
||||
use self::Numeric::*;
|
||||
|
||||
let week_from_sun = |d: &NaiveDate| d.weeks_from(Weekday::Sun);
|
||||
let week_from_mon = |d: &NaiveDate| d.weeks_from(Weekday::Mon);
|
||||
|
||||
let (width, v) = match *spec {
|
||||
Year => (4, date.map(|d| i64::from(d.year()))),
|
||||
YearDiv100 => (2, date.map(|d| i64::from(d.year()).div_euclid(100))),
|
||||
YearMod100 => (2, date.map(|d| i64::from(d.year()).rem_euclid(100))),
|
||||
IsoYear => (4, date.map(|d| i64::from(d.iso_week().year()))),
|
||||
IsoYearDiv100 => (2, date.map(|d| i64::from(d.iso_week().year()).div_euclid(100))),
|
||||
IsoYearMod100 => (2, date.map(|d| i64::from(d.iso_week().year()).rem_euclid(100))),
|
||||
Month => (2, date.map(|d| i64::from(d.month()))),
|
||||
Day => (2, date.map(|d| i64::from(d.day()))),
|
||||
WeekFromSun => (2, date.map(|d| i64::from(week_from_sun(d)))),
|
||||
WeekFromMon => (2, date.map(|d| i64::from(week_from_mon(d)))),
|
||||
IsoWeek => (2, date.map(|d| i64::from(d.iso_week().week()))),
|
||||
NumDaysFromSun => (1, date.map(|d| i64::from(d.weekday().num_days_from_sunday()))),
|
||||
WeekdayFromMon => (1, date.map(|d| i64::from(d.weekday().number_from_monday()))),
|
||||
Ordinal => (3, date.map(|d| i64::from(d.ordinal()))),
|
||||
Hour => (2, time.map(|t| i64::from(t.hour()))),
|
||||
Hour12 => (2, time.map(|t| i64::from(t.hour12().1))),
|
||||
Minute => (2, time.map(|t| i64::from(t.minute()))),
|
||||
Second => (2, time.map(|t| i64::from(t.second() + t.nanosecond() / 1_000_000_000))),
|
||||
Nanosecond => (9, time.map(|t| i64::from(t.nanosecond() % 1_000_000_000))),
|
||||
Timestamp => (
|
||||
1,
|
||||
match (date, time, off) {
|
||||
(Some(d), Some(t), None) => Some(d.and_time(*t).timestamp()),
|
||||
(Some(d), Some(t), Some(&(_, off))) => {
|
||||
Some((d.and_time(*t) - off).timestamp())
|
||||
}
|
||||
(_, _, _) => None,
|
||||
},
|
||||
),
|
||||
|
||||
// for the future expansion
|
||||
Internal(ref int) => match int._dummy {},
|
||||
};
|
||||
|
||||
if let Some(v) = v {
|
||||
if (spec == &Year || spec == &IsoYear) && !(0..10_000).contains(&v) {
|
||||
// non-four-digit years require an explicit sign as per ISO 8601
|
||||
match *pad {
|
||||
Pad::None => write!(result, "{:+}", v),
|
||||
Pad::Zero => write!(result, "{:+01$}", v, width + 1),
|
||||
Pad::Space => write!(result, "{:+1$}", v, width + 1),
|
||||
}
|
||||
} else {
|
||||
match *pad {
|
||||
Pad::None => write!(result, "{}", v),
|
||||
Pad::Zero => write!(result, "{:01$}", v, width),
|
||||
Pad::Space => write!(result, "{:1$}", v, width),
|
||||
}
|
||||
}?
|
||||
} else {
|
||||
return Err(fmt::Error); // insufficient arguments for given format
|
||||
}
|
||||
}
|
||||
|
||||
Item::Fixed(ref spec) => {
|
||||
use self::Fixed::*;
|
||||
|
||||
let ret = match *spec {
|
||||
ShortMonthName => date.map(|d| {
|
||||
result.push_str(locale.short_months[d.month0() as usize]);
|
||||
Ok(())
|
||||
}),
|
||||
LongMonthName => date.map(|d| {
|
||||
result.push_str(locale.long_months[d.month0() as usize]);
|
||||
Ok(())
|
||||
}),
|
||||
ShortWeekdayName => date.map(|d| {
|
||||
result.push_str(
|
||||
locale.short_weekdays[d.weekday().num_days_from_sunday() as usize],
|
||||
);
|
||||
Ok(())
|
||||
}),
|
||||
LongWeekdayName => date.map(|d| {
|
||||
result.push_str(
|
||||
locale.long_weekdays[d.weekday().num_days_from_sunday() as usize],
|
||||
);
|
||||
Ok(())
|
||||
}),
|
||||
LowerAmPm => time.map(|t| {
|
||||
let ampm = if t.hour12().0 { locale.am_pm[1] } else { locale.am_pm[0] };
|
||||
for char in ampm.chars() {
|
||||
result.extend(char.to_lowercase())
|
||||
}
|
||||
Ok(())
|
||||
}),
|
||||
UpperAmPm => time.map(|t| {
|
||||
result.push_str(if t.hour12().0 { locale.am_pm[1] } else { locale.am_pm[0] });
|
||||
Ok(())
|
||||
}),
|
||||
Nanosecond => time.map(|t| {
|
||||
let nano = t.nanosecond() % 1_000_000_000;
|
||||
if nano == 0 {
|
||||
Ok(())
|
||||
} else if nano % 1_000_000 == 0 {
|
||||
write!(result, ".{:03}", nano / 1_000_000)
|
||||
} else if nano % 1_000 == 0 {
|
||||
write!(result, ".{:06}", nano / 1_000)
|
||||
} else {
|
||||
write!(result, ".{:09}", nano)
|
||||
}
|
||||
}),
|
||||
Nanosecond3 => time.map(|t| {
|
||||
let nano = t.nanosecond() % 1_000_000_000;
|
||||
write!(result, ".{:03}", nano / 1_000_000)
|
||||
}),
|
||||
Nanosecond6 => time.map(|t| {
|
||||
let nano = t.nanosecond() % 1_000_000_000;
|
||||
write!(result, ".{:06}", nano / 1_000)
|
||||
}),
|
||||
Nanosecond9 => time.map(|t| {
|
||||
let nano = t.nanosecond() % 1_000_000_000;
|
||||
write!(result, ".{:09}", nano)
|
||||
}),
|
||||
Internal(InternalFixed { val: InternalInternal::Nanosecond3NoDot }) => {
|
||||
time.map(|t| {
|
||||
let nano = t.nanosecond() % 1_000_000_000;
|
||||
write!(result, "{:03}", nano / 1_000_000)
|
||||
})
|
||||
}
|
||||
Internal(InternalFixed { val: InternalInternal::Nanosecond6NoDot }) => {
|
||||
time.map(|t| {
|
||||
let nano = t.nanosecond() % 1_000_000_000;
|
||||
write!(result, "{:06}", nano / 1_000)
|
||||
})
|
||||
}
|
||||
Internal(InternalFixed { val: InternalInternal::Nanosecond9NoDot }) => {
|
||||
time.map(|t| {
|
||||
let nano = t.nanosecond() % 1_000_000_000;
|
||||
write!(result, "{:09}", nano)
|
||||
})
|
||||
}
|
||||
TimezoneName => off.map(|(name, _)| {
|
||||
result.push_str(name);
|
||||
Ok(())
|
||||
}),
|
||||
TimezoneOffset | TimezoneOffsetZ => off.map(|&(_, off)| {
|
||||
OffsetFormat {
|
||||
precision: OffsetPrecision::Minutes,
|
||||
colons: Colons::Maybe,
|
||||
allow_zulu: *spec == TimezoneOffsetZ,
|
||||
padding: Pad::Zero,
|
||||
}
|
||||
.format(result, off)
|
||||
}),
|
||||
TimezoneOffsetColon | TimezoneOffsetColonZ => off.map(|&(_, off)| {
|
||||
OffsetFormat {
|
||||
precision: OffsetPrecision::Minutes,
|
||||
colons: Colons::Colon,
|
||||
allow_zulu: *spec == TimezoneOffsetColonZ,
|
||||
padding: Pad::Zero,
|
||||
}
|
||||
.format(result, off)
|
||||
}),
|
||||
TimezoneOffsetDoubleColon => off.map(|&(_, off)| {
|
||||
OffsetFormat {
|
||||
precision: OffsetPrecision::Seconds,
|
||||
colons: Colons::Colon,
|
||||
allow_zulu: false,
|
||||
padding: Pad::Zero,
|
||||
}
|
||||
.format(result, off)
|
||||
}),
|
||||
TimezoneOffsetTripleColon => off.map(|&(_, off)| {
|
||||
OffsetFormat {
|
||||
precision: OffsetPrecision::Hours,
|
||||
colons: Colons::None,
|
||||
allow_zulu: false,
|
||||
padding: Pad::Zero,
|
||||
}
|
||||
.format(result, off)
|
||||
}),
|
||||
Internal(InternalFixed { val: InternalInternal::TimezoneOffsetPermissive }) => {
|
||||
return Err(fmt::Error);
|
||||
}
|
||||
RFC2822 =>
|
||||
// same as `%a, %d %b %Y %H:%M:%S %z`
|
||||
{
|
||||
if let (Some(d), Some(t), Some(&(_, off))) = (date, time, off) {
|
||||
Some(write_rfc2822_inner(result, d, t, off, locale))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
RFC3339 =>
|
||||
// same as `%Y-%m-%dT%H:%M:%S%.f%:z`
|
||||
{
|
||||
if let (Some(d), Some(t), Some(&(_, off))) = (date, time, off) {
|
||||
Some(write_rfc3339(result, crate::NaiveDateTime::new(*d, *t), off))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
match ret {
|
||||
Some(ret) => ret?,
|
||||
None => return Err(fmt::Error), // insufficient arguments for given format
|
||||
}
|
||||
}
|
||||
|
||||
Item::Error => return Err(fmt::Error),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
impl OffsetFormat {
|
||||
/// Writes an offset from UTC to `result` with the format defined by `self`.
|
||||
fn format(&self, result: &mut String, off: FixedOffset) -> fmt::Result {
|
||||
let off = off.local_minus_utc();
|
||||
if self.allow_zulu && off == 0 {
|
||||
result.push('Z');
|
||||
return Ok(());
|
||||
}
|
||||
let (sign, off) = if off < 0 { ('-', -off) } else { ('+', off) };
|
||||
|
||||
let hours;
|
||||
let mut mins = 0;
|
||||
let mut secs = 0;
|
||||
let precision = match self.precision {
|
||||
OffsetPrecision::Hours => {
|
||||
// Minutes and seconds are simply truncated
|
||||
hours = (off / 3600) as u8;
|
||||
OffsetPrecision::Hours
|
||||
}
|
||||
OffsetPrecision::Minutes | OffsetPrecision::OptionalMinutes => {
|
||||
// Round seconds to the nearest minute.
|
||||
let minutes = (off + 30) / 60;
|
||||
mins = (minutes % 60) as u8;
|
||||
hours = (minutes / 60) as u8;
|
||||
if self.precision == OffsetPrecision::OptionalMinutes && mins == 0 {
|
||||
OffsetPrecision::Hours
|
||||
} else {
|
||||
OffsetPrecision::Minutes
|
||||
}
|
||||
}
|
||||
OffsetPrecision::Seconds
|
||||
| OffsetPrecision::OptionalSeconds
|
||||
| OffsetPrecision::OptionalMinutesAndSeconds => {
|
||||
let minutes = off / 60;
|
||||
secs = (off % 60) as u8;
|
||||
mins = (minutes % 60) as u8;
|
||||
hours = (minutes / 60) as u8;
|
||||
if self.precision != OffsetPrecision::Seconds && secs == 0 {
|
||||
if self.precision == OffsetPrecision::OptionalMinutesAndSeconds && mins == 0 {
|
||||
OffsetPrecision::Hours
|
||||
} else {
|
||||
OffsetPrecision::Minutes
|
||||
}
|
||||
} else {
|
||||
OffsetPrecision::Seconds
|
||||
}
|
||||
}
|
||||
};
|
||||
let colons = self.colons == Colons::Colon;
|
||||
|
||||
if hours < 10 {
|
||||
if self.padding == Pad::Space {
|
||||
result.push(' ');
|
||||
}
|
||||
result.push(sign);
|
||||
if self.padding == Pad::Zero {
|
||||
result.push('0');
|
||||
}
|
||||
result.push((b'0' + hours) as char);
|
||||
} else {
|
||||
result.push(sign);
|
||||
write_hundreds(result, hours)?;
|
||||
}
|
||||
if let OffsetPrecision::Minutes | OffsetPrecision::Seconds = precision {
|
||||
if colons {
|
||||
result.push(':');
|
||||
}
|
||||
write_hundreds(result, mins)?;
|
||||
}
|
||||
if let OffsetPrecision::Seconds = precision {
|
||||
if colons {
|
||||
result.push(':');
|
||||
}
|
||||
write_hundreds(result, secs)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes the date, time and offset to the string. same as `%Y-%m-%dT%H:%M:%S%.f%:z`
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
pub(crate) fn write_rfc3339(
|
||||
result: &mut String,
|
||||
dt: crate::NaiveDateTime,
|
||||
off: FixedOffset,
|
||||
) -> fmt::Result {
|
||||
// reuse `Debug` impls which already print ISO 8601 format.
|
||||
// this is faster in this way.
|
||||
write!(result, "{:?}", dt)?;
|
||||
OffsetFormat {
|
||||
precision: OffsetPrecision::Minutes,
|
||||
colons: Colons::Colon,
|
||||
allow_zulu: false,
|
||||
padding: Pad::Zero,
|
||||
}
|
||||
.format(result, off)
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
/// write datetimes like `Tue, 1 Jul 2003 10:52:37 +0200`, same as `%a, %d %b %Y %H:%M:%S %z`
|
||||
pub(crate) fn write_rfc2822(
|
||||
result: &mut String,
|
||||
dt: crate::NaiveDateTime,
|
||||
off: FixedOffset,
|
||||
) -> fmt::Result {
|
||||
write_rfc2822_inner(result, &dt.date(), &dt.time(), off, Locales::new(None))
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
/// write datetimes like `Tue, 1 Jul 2003 10:52:37 +0200`, same as `%a, %d %b %Y %H:%M:%S %z`
|
||||
fn write_rfc2822_inner(
|
||||
result: &mut String,
|
||||
d: &NaiveDate,
|
||||
t: &NaiveTime,
|
||||
off: FixedOffset,
|
||||
locale: Locales,
|
||||
) -> fmt::Result {
|
||||
let year = d.year();
|
||||
// RFC2822 is only defined on years 0 through 9999
|
||||
if !(0..=9999).contains(&year) {
|
||||
return Err(fmt::Error);
|
||||
}
|
||||
|
||||
result.push_str(locale.short_weekdays[d.weekday().num_days_from_sunday() as usize]);
|
||||
result.push_str(", ");
|
||||
write_hundreds(result, d.day() as u8)?;
|
||||
result.push(' ');
|
||||
result.push_str(locale.short_months[d.month0() as usize]);
|
||||
result.push(' ');
|
||||
write_hundreds(result, (year / 100) as u8)?;
|
||||
write_hundreds(result, (year % 100) as u8)?;
|
||||
result.push(' ');
|
||||
write_hundreds(result, t.hour() as u8)?;
|
||||
result.push(':');
|
||||
write_hundreds(result, t.minute() as u8)?;
|
||||
result.push(':');
|
||||
let sec = t.second() + t.nanosecond() / 1_000_000_000;
|
||||
write_hundreds(result, sec as u8)?;
|
||||
result.push(' ');
|
||||
OffsetFormat {
|
||||
precision: OffsetPrecision::Minutes,
|
||||
colons: Colons::None,
|
||||
allow_zulu: false,
|
||||
padding: Pad::Zero,
|
||||
}
|
||||
.format(result, off)
|
||||
}
|
||||
|
||||
/// Equivalent to `{:02}` formatting for n < 100.
|
||||
pub(crate) fn write_hundreds(w: &mut impl Write, n: u8) -> fmt::Result {
|
||||
if n >= 100 {
|
||||
return Err(fmt::Error);
|
||||
}
|
||||
|
||||
let tens = b'0' + n / 10;
|
||||
let ones = b'0' + n % 10;
|
||||
w.write_char(tens as char)?;
|
||||
w.write_char(ones as char)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
mod tests {
|
||||
use super::{Colons, OffsetFormat, OffsetPrecision, Pad};
|
||||
use crate::FixedOffset;
|
||||
|
||||
#[test]
|
||||
fn test_offset_formatting() {
|
||||
fn check_all(precision: OffsetPrecision, expected: [[&str; 7]; 12]) {
|
||||
fn check(
|
||||
precision: OffsetPrecision,
|
||||
colons: Colons,
|
||||
padding: Pad,
|
||||
allow_zulu: bool,
|
||||
offsets: [FixedOffset; 7],
|
||||
expected: [&str; 7],
|
||||
) {
|
||||
let offset_format = OffsetFormat { precision, colons, allow_zulu, padding };
|
||||
for (offset, expected) in offsets.iter().zip(expected.iter()) {
|
||||
let mut output = String::new();
|
||||
offset_format.format(&mut output, *offset).unwrap();
|
||||
assert_eq!(&output, expected);
|
||||
}
|
||||
}
|
||||
// +03:45, -03:30, +11:00, -11:00:22, +02:34:26, -12:34:30, +00:00
|
||||
let offsets = [
|
||||
FixedOffset::east_opt(13_500).unwrap(),
|
||||
FixedOffset::east_opt(-12_600).unwrap(),
|
||||
FixedOffset::east_opt(39_600).unwrap(),
|
||||
FixedOffset::east_opt(-39_622).unwrap(),
|
||||
FixedOffset::east_opt(9266).unwrap(),
|
||||
FixedOffset::east_opt(-45270).unwrap(),
|
||||
FixedOffset::east_opt(0).unwrap(),
|
||||
];
|
||||
check(precision, Colons::Colon, Pad::Zero, false, offsets, expected[0]);
|
||||
check(precision, Colons::Colon, Pad::Zero, true, offsets, expected[1]);
|
||||
check(precision, Colons::Colon, Pad::Space, false, offsets, expected[2]);
|
||||
check(precision, Colons::Colon, Pad::Space, true, offsets, expected[3]);
|
||||
check(precision, Colons::Colon, Pad::None, false, offsets, expected[4]);
|
||||
check(precision, Colons::Colon, Pad::None, true, offsets, expected[5]);
|
||||
check(precision, Colons::None, Pad::Zero, false, offsets, expected[6]);
|
||||
check(precision, Colons::None, Pad::Zero, true, offsets, expected[7]);
|
||||
check(precision, Colons::None, Pad::Space, false, offsets, expected[8]);
|
||||
check(precision, Colons::None, Pad::Space, true, offsets, expected[9]);
|
||||
check(precision, Colons::None, Pad::None, false, offsets, expected[10]);
|
||||
check(precision, Colons::None, Pad::None, true, offsets, expected[11]);
|
||||
// `Colons::Maybe` should format the same as `Colons::None`
|
||||
check(precision, Colons::Maybe, Pad::Zero, false, offsets, expected[6]);
|
||||
check(precision, Colons::Maybe, Pad::Zero, true, offsets, expected[7]);
|
||||
check(precision, Colons::Maybe, Pad::Space, false, offsets, expected[8]);
|
||||
check(precision, Colons::Maybe, Pad::Space, true, offsets, expected[9]);
|
||||
check(precision, Colons::Maybe, Pad::None, false, offsets, expected[10]);
|
||||
check(precision, Colons::Maybe, Pad::None, true, offsets, expected[11]);
|
||||
}
|
||||
check_all(
|
||||
OffsetPrecision::Hours,
|
||||
[
|
||||
["+03", "-03", "+11", "-11", "+02", "-12", "+00"],
|
||||
["+03", "-03", "+11", "-11", "+02", "-12", "Z"],
|
||||
[" +3", " -3", "+11", "-11", " +2", "-12", " +0"],
|
||||
[" +3", " -3", "+11", "-11", " +2", "-12", "Z"],
|
||||
["+3", "-3", "+11", "-11", "+2", "-12", "+0"],
|
||||
["+3", "-3", "+11", "-11", "+2", "-12", "Z"],
|
||||
["+03", "-03", "+11", "-11", "+02", "-12", "+00"],
|
||||
["+03", "-03", "+11", "-11", "+02", "-12", "Z"],
|
||||
[" +3", " -3", "+11", "-11", " +2", "-12", " +0"],
|
||||
[" +3", " -3", "+11", "-11", " +2", "-12", "Z"],
|
||||
["+3", "-3", "+11", "-11", "+2", "-12", "+0"],
|
||||
["+3", "-3", "+11", "-11", "+2", "-12", "Z"],
|
||||
],
|
||||
);
|
||||
check_all(
|
||||
OffsetPrecision::Minutes,
|
||||
[
|
||||
["+03:45", "-03:30", "+11:00", "-11:00", "+02:34", "-12:35", "+00:00"],
|
||||
["+03:45", "-03:30", "+11:00", "-11:00", "+02:34", "-12:35", "Z"],
|
||||
[" +3:45", " -3:30", "+11:00", "-11:00", " +2:34", "-12:35", " +0:00"],
|
||||
[" +3:45", " -3:30", "+11:00", "-11:00", " +2:34", "-12:35", "Z"],
|
||||
["+3:45", "-3:30", "+11:00", "-11:00", "+2:34", "-12:35", "+0:00"],
|
||||
["+3:45", "-3:30", "+11:00", "-11:00", "+2:34", "-12:35", "Z"],
|
||||
["+0345", "-0330", "+1100", "-1100", "+0234", "-1235", "+0000"],
|
||||
["+0345", "-0330", "+1100", "-1100", "+0234", "-1235", "Z"],
|
||||
[" +345", " -330", "+1100", "-1100", " +234", "-1235", " +000"],
|
||||
[" +345", " -330", "+1100", "-1100", " +234", "-1235", "Z"],
|
||||
["+345", "-330", "+1100", "-1100", "+234", "-1235", "+000"],
|
||||
["+345", "-330", "+1100", "-1100", "+234", "-1235", "Z"],
|
||||
],
|
||||
);
|
||||
#[rustfmt::skip]
|
||||
check_all(
|
||||
OffsetPrecision::Seconds,
|
||||
[
|
||||
["+03:45:00", "-03:30:00", "+11:00:00", "-11:00:22", "+02:34:26", "-12:34:30", "+00:00:00"],
|
||||
["+03:45:00", "-03:30:00", "+11:00:00", "-11:00:22", "+02:34:26", "-12:34:30", "Z"],
|
||||
[" +3:45:00", " -3:30:00", "+11:00:00", "-11:00:22", " +2:34:26", "-12:34:30", " +0:00:00"],
|
||||
[" +3:45:00", " -3:30:00", "+11:00:00", "-11:00:22", " +2:34:26", "-12:34:30", "Z"],
|
||||
["+3:45:00", "-3:30:00", "+11:00:00", "-11:00:22", "+2:34:26", "-12:34:30", "+0:00:00"],
|
||||
["+3:45:00", "-3:30:00", "+11:00:00", "-11:00:22", "+2:34:26", "-12:34:30", "Z"],
|
||||
["+034500", "-033000", "+110000", "-110022", "+023426", "-123430", "+000000"],
|
||||
["+034500", "-033000", "+110000", "-110022", "+023426", "-123430", "Z"],
|
||||
[" +34500", " -33000", "+110000", "-110022", " +23426", "-123430", " +00000"],
|
||||
[" +34500", " -33000", "+110000", "-110022", " +23426", "-123430", "Z"],
|
||||
["+34500", "-33000", "+110000", "-110022", "+23426", "-123430", "+00000"],
|
||||
["+34500", "-33000", "+110000", "-110022", "+23426", "-123430", "Z"],
|
||||
],
|
||||
);
|
||||
check_all(
|
||||
OffsetPrecision::OptionalMinutes,
|
||||
[
|
||||
["+03:45", "-03:30", "+11", "-11", "+02:34", "-12:35", "+00"],
|
||||
["+03:45", "-03:30", "+11", "-11", "+02:34", "-12:35", "Z"],
|
||||
[" +3:45", " -3:30", "+11", "-11", " +2:34", "-12:35", " +0"],
|
||||
[" +3:45", " -3:30", "+11", "-11", " +2:34", "-12:35", "Z"],
|
||||
["+3:45", "-3:30", "+11", "-11", "+2:34", "-12:35", "+0"],
|
||||
["+3:45", "-3:30", "+11", "-11", "+2:34", "-12:35", "Z"],
|
||||
["+0345", "-0330", "+11", "-11", "+0234", "-1235", "+00"],
|
||||
["+0345", "-0330", "+11", "-11", "+0234", "-1235", "Z"],
|
||||
[" +345", " -330", "+11", "-11", " +234", "-1235", " +0"],
|
||||
[" +345", " -330", "+11", "-11", " +234", "-1235", "Z"],
|
||||
["+345", "-330", "+11", "-11", "+234", "-1235", "+0"],
|
||||
["+345", "-330", "+11", "-11", "+234", "-1235", "Z"],
|
||||
],
|
||||
);
|
||||
check_all(
|
||||
OffsetPrecision::OptionalSeconds,
|
||||
[
|
||||
["+03:45", "-03:30", "+11:00", "-11:00:22", "+02:34:26", "-12:34:30", "+00:00"],
|
||||
["+03:45", "-03:30", "+11:00", "-11:00:22", "+02:34:26", "-12:34:30", "Z"],
|
||||
[" +3:45", " -3:30", "+11:00", "-11:00:22", " +2:34:26", "-12:34:30", " +0:00"],
|
||||
[" +3:45", " -3:30", "+11:00", "-11:00:22", " +2:34:26", "-12:34:30", "Z"],
|
||||
["+3:45", "-3:30", "+11:00", "-11:00:22", "+2:34:26", "-12:34:30", "+0:00"],
|
||||
["+3:45", "-3:30", "+11:00", "-11:00:22", "+2:34:26", "-12:34:30", "Z"],
|
||||
["+0345", "-0330", "+1100", "-110022", "+023426", "-123430", "+0000"],
|
||||
["+0345", "-0330", "+1100", "-110022", "+023426", "-123430", "Z"],
|
||||
[" +345", " -330", "+1100", "-110022", " +23426", "-123430", " +000"],
|
||||
[" +345", " -330", "+1100", "-110022", " +23426", "-123430", "Z"],
|
||||
["+345", "-330", "+1100", "-110022", "+23426", "-123430", "+000"],
|
||||
["+345", "-330", "+1100", "-110022", "+23426", "-123430", "Z"],
|
||||
],
|
||||
);
|
||||
check_all(
|
||||
OffsetPrecision::OptionalMinutesAndSeconds,
|
||||
[
|
||||
["+03:45", "-03:30", "+11", "-11:00:22", "+02:34:26", "-12:34:30", "+00"],
|
||||
["+03:45", "-03:30", "+11", "-11:00:22", "+02:34:26", "-12:34:30", "Z"],
|
||||
[" +3:45", " -3:30", "+11", "-11:00:22", " +2:34:26", "-12:34:30", " +0"],
|
||||
[" +3:45", " -3:30", "+11", "-11:00:22", " +2:34:26", "-12:34:30", "Z"],
|
||||
["+3:45", "-3:30", "+11", "-11:00:22", "+2:34:26", "-12:34:30", "+0"],
|
||||
["+3:45", "-3:30", "+11", "-11:00:22", "+2:34:26", "-12:34:30", "Z"],
|
||||
["+0345", "-0330", "+11", "-110022", "+023426", "-123430", "+00"],
|
||||
["+0345", "-0330", "+11", "-110022", "+023426", "-123430", "Z"],
|
||||
[" +345", " -330", "+11", "-110022", " +23426", "-123430", " +0"],
|
||||
[" +345", " -330", "+11", "-110022", " +23426", "-123430", "Z"],
|
||||
["+345", "-330", "+11", "-110022", "+23426", "-123430", "+0"],
|
||||
["+345", "-330", "+11", "-110022", "+23426", "-123430", "Z"],
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -31,3 +31,7 @@ pub(crate) const fn d_t_fmt(locale: Locale) -> &'static str {
|
||||
pub(crate) const fn t_fmt(locale: Locale) -> &'static str {
|
||||
locale_match!(locale => LC_TIME::T_FMT)
|
||||
}
|
||||
|
||||
pub(crate) const fn t_fmt_ampm(locale: Locale) -> &'static str {
|
||||
locale_match!(locale => LC_TIME::T_FMT_AMPM)
|
||||
}
|
||||
|
@ -35,27 +35,32 @@ extern crate alloc;
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
use alloc::boxed::Box;
|
||||
#[cfg(feature = "alloc")]
|
||||
use alloc::string::{String, ToString};
|
||||
#[cfg(any(feature = "alloc", feature = "std", test))]
|
||||
use core::borrow::Borrow;
|
||||
use core::fmt;
|
||||
use core::fmt::Write;
|
||||
use core::str::FromStr;
|
||||
#[cfg(any(feature = "std", test))]
|
||||
#[cfg(feature = "std")]
|
||||
use std::error::Error;
|
||||
|
||||
#[cfg(any(feature = "alloc", feature = "std", test))]
|
||||
use crate::naive::{NaiveDate, NaiveTime};
|
||||
#[cfg(any(feature = "alloc", feature = "std", test))]
|
||||
use crate::offset::{FixedOffset, Offset};
|
||||
#[cfg(any(feature = "alloc", feature = "std", test))]
|
||||
use crate::{Datelike, Timelike};
|
||||
use crate::{Month, ParseMonthError, ParseWeekdayError, Weekday};
|
||||
|
||||
mod formatting;
|
||||
mod parsed;
|
||||
|
||||
// due to the size of parsing routines, they are in separate modules.
|
||||
mod parse;
|
||||
pub(crate) mod scan;
|
||||
|
||||
pub mod strftime;
|
||||
|
||||
#[cfg(feature = "unstable-locales")]
|
||||
pub(crate) mod locales;
|
||||
|
||||
pub(crate) use formatting::write_hundreds;
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
pub use formatting::{format, format_item, DelayedFormat};
|
||||
#[cfg(feature = "unstable-locales")]
|
||||
pub use formatting::{format_item_localized, format_localized};
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
pub(crate) use formatting::{write_rfc2822, write_rfc3339};
|
||||
pub use parse::{parse, parse_and_remainder};
|
||||
pub use parsed::Parsed;
|
||||
/// L10n locales.
|
||||
@ -275,13 +280,49 @@ enum InternalInternal {
|
||||
Nanosecond9NoDot,
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "alloc", feature = "std", test))]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
enum Colons {
|
||||
/// Type for specifying the format of UTC offsets.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct OffsetFormat {
|
||||
/// See `OffsetPrecision`.
|
||||
pub precision: OffsetPrecision,
|
||||
/// Separator between hours, minutes and seconds.
|
||||
pub colons: Colons,
|
||||
/// Represent `+00:00` as `Z`.
|
||||
pub allow_zulu: bool,
|
||||
/// Pad the hour value to two digits.
|
||||
pub padding: Pad,
|
||||
}
|
||||
|
||||
/// The precision of an offset from UTC formatting item.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum OffsetPrecision {
|
||||
/// Format offset from UTC as only hours. Not recommended, it is not uncommon for timezones to
|
||||
/// have an offset of 30 minutes, 15 minutes, etc.
|
||||
/// Any minutes and seconds get truncated.
|
||||
Hours,
|
||||
/// Format offset from UTC as hours and minutes.
|
||||
/// Any seconds will be rounded to the nearest minute.
|
||||
Minutes,
|
||||
/// Format offset from UTC as hours, minutes and seconds.
|
||||
Seconds,
|
||||
/// Format offset from UTC as hours, and optionally with minutes.
|
||||
/// Any seconds will be rounded to the nearest minute.
|
||||
OptionalMinutes,
|
||||
/// Format offset from UTC as hours and minutes, and optionally seconds.
|
||||
OptionalSeconds,
|
||||
/// Format offset from UTC as hours and optionally minutes and seconds.
|
||||
OptionalMinutesAndSeconds,
|
||||
}
|
||||
|
||||
/// The separator between hours and minutes in an offset.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum Colons {
|
||||
/// No separator
|
||||
None,
|
||||
Single,
|
||||
Double,
|
||||
Triple,
|
||||
/// Colon (`:`) as separator
|
||||
Colon,
|
||||
/// No separator when formatting, colon allowed when parsing.
|
||||
Maybe,
|
||||
}
|
||||
|
||||
/// A single formatting item. This is used for both formatting and parsing.
|
||||
@ -290,13 +331,13 @@ pub enum Item<'a> {
|
||||
/// A literally printed and parsed text.
|
||||
Literal(&'a str),
|
||||
/// Same as `Literal` but with the string owned by the item.
|
||||
#[cfg(any(feature = "alloc", feature = "std", test))]
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))]
|
||||
OwnedLiteral(Box<str>),
|
||||
/// Whitespace. Prints literally but reads zero or more whitespace.
|
||||
Space(&'a str),
|
||||
/// Same as `Space` but with the string owned by the item.
|
||||
#[cfg(any(feature = "alloc", feature = "std", test))]
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))]
|
||||
OwnedSpace(Box<str>),
|
||||
/// Numeric item. Can be optionally padded to the maximal length (if any) when formatting;
|
||||
@ -308,40 +349,24 @@ pub enum Item<'a> {
|
||||
Error,
|
||||
}
|
||||
|
||||
macro_rules! lit {
|
||||
($x:expr) => {
|
||||
Item::Literal($x)
|
||||
};
|
||||
const fn num(numeric: Numeric) -> Item<'static> {
|
||||
Item::Numeric(numeric, Pad::None)
|
||||
}
|
||||
macro_rules! sp {
|
||||
($x:expr) => {
|
||||
Item::Space($x)
|
||||
};
|
||||
|
||||
const fn num0(numeric: Numeric) -> Item<'static> {
|
||||
Item::Numeric(numeric, Pad::Zero)
|
||||
}
|
||||
macro_rules! num {
|
||||
($x:ident) => {
|
||||
Item::Numeric(Numeric::$x, Pad::None)
|
||||
};
|
||||
|
||||
const fn nums(numeric: Numeric) -> Item<'static> {
|
||||
Item::Numeric(numeric, Pad::Space)
|
||||
}
|
||||
macro_rules! num0 {
|
||||
($x:ident) => {
|
||||
Item::Numeric(Numeric::$x, Pad::Zero)
|
||||
};
|
||||
|
||||
const fn fixed(fixed: Fixed) -> Item<'static> {
|
||||
Item::Fixed(fixed)
|
||||
}
|
||||
macro_rules! nums {
|
||||
($x:ident) => {
|
||||
Item::Numeric(Numeric::$x, Pad::Space)
|
||||
};
|
||||
}
|
||||
macro_rules! fix {
|
||||
($x:ident) => {
|
||||
Item::Fixed(Fixed::$x)
|
||||
};
|
||||
}
|
||||
macro_rules! internal_fix {
|
||||
($x:ident) => {
|
||||
Item::Fixed(Fixed::Internal(InternalFixed { val: InternalInternal::$x }))
|
||||
};
|
||||
|
||||
const fn internal_fixed(val: InternalInternal) -> Item<'static> {
|
||||
Item::Fixed(Fixed::Internal(InternalFixed { val }))
|
||||
}
|
||||
|
||||
/// An error from the `parse` function.
|
||||
@ -405,7 +430,7 @@ impl fmt::Display for ParseError {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "std", test))]
|
||||
#[cfg(feature = "std")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
|
||||
impl Error for ParseError {
|
||||
#[allow(deprecated)]
|
||||
@ -415,7 +440,7 @@ impl Error for ParseError {
|
||||
}
|
||||
|
||||
// to be used in this module and submodules
|
||||
const OUT_OF_RANGE: ParseError = ParseError(ParseErrorKind::OutOfRange);
|
||||
pub(crate) const OUT_OF_RANGE: ParseError = ParseError(ParseErrorKind::OutOfRange);
|
||||
const IMPOSSIBLE: ParseError = ParseError(ParseErrorKind::Impossible);
|
||||
const NOT_ENOUGH: ParseError = ParseError(ParseErrorKind::NotEnough);
|
||||
const INVALID: ParseError = ParseError(ParseErrorKind::Invalid);
|
||||
@ -423,531 +448,6 @@ const TOO_SHORT: ParseError = ParseError(ParseErrorKind::TooShort);
|
||||
const TOO_LONG: ParseError = ParseError(ParseErrorKind::TooLong);
|
||||
const BAD_FORMAT: ParseError = ParseError(ParseErrorKind::BadFormat);
|
||||
|
||||
#[cfg(any(feature = "alloc", feature = "std", test))]
|
||||
struct Locales {
|
||||
short_months: &'static [&'static str],
|
||||
long_months: &'static [&'static str],
|
||||
short_weekdays: &'static [&'static str],
|
||||
long_weekdays: &'static [&'static str],
|
||||
am_pm: &'static [&'static str],
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "alloc", feature = "std", test))]
|
||||
impl Locales {
|
||||
fn new(_locale: Option<Locale>) -> Self {
|
||||
#[cfg(feature = "unstable-locales")]
|
||||
{
|
||||
let locale = _locale.unwrap_or(Locale::POSIX);
|
||||
Self {
|
||||
short_months: locales::short_months(locale),
|
||||
long_months: locales::long_months(locale),
|
||||
short_weekdays: locales::short_weekdays(locale),
|
||||
long_weekdays: locales::long_weekdays(locale),
|
||||
am_pm: locales::am_pm(locale),
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "unstable-locales"))]
|
||||
Self {
|
||||
short_months: &[
|
||||
"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
|
||||
],
|
||||
long_months: &[
|
||||
"January",
|
||||
"February",
|
||||
"March",
|
||||
"April",
|
||||
"May",
|
||||
"June",
|
||||
"July",
|
||||
"August",
|
||||
"September",
|
||||
"October",
|
||||
"November",
|
||||
"December",
|
||||
],
|
||||
short_weekdays: &["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
|
||||
long_weekdays: &[
|
||||
"Sunday",
|
||||
"Monday",
|
||||
"Tuesday",
|
||||
"Wednesday",
|
||||
"Thursday",
|
||||
"Friday",
|
||||
"Saturday",
|
||||
],
|
||||
am_pm: &["AM", "PM"],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Formats single formatting item
|
||||
#[cfg(any(feature = "alloc", feature = "std", test))]
|
||||
#[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))]
|
||||
pub fn format_item(
|
||||
w: &mut fmt::Formatter,
|
||||
date: Option<&NaiveDate>,
|
||||
time: Option<&NaiveTime>,
|
||||
off: Option<&(String, FixedOffset)>,
|
||||
item: &Item<'_>,
|
||||
) -> fmt::Result {
|
||||
let mut result = String::new();
|
||||
format_inner(&mut result, date, time, off, item, None)?;
|
||||
w.pad(&result)
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "alloc", feature = "std", test))]
|
||||
fn format_inner(
|
||||
result: &mut String,
|
||||
date: Option<&NaiveDate>,
|
||||
time: Option<&NaiveTime>,
|
||||
off: Option<&(String, FixedOffset)>,
|
||||
item: &Item<'_>,
|
||||
locale: Option<Locale>,
|
||||
) -> fmt::Result {
|
||||
let locale = Locales::new(locale);
|
||||
|
||||
match *item {
|
||||
Item::Literal(s) | Item::Space(s) => result.push_str(s),
|
||||
#[cfg(any(feature = "alloc", feature = "std", test))]
|
||||
Item::OwnedLiteral(ref s) | Item::OwnedSpace(ref s) => result.push_str(s),
|
||||
|
||||
Item::Numeric(ref spec, ref pad) => {
|
||||
use self::Numeric::*;
|
||||
|
||||
let week_from_sun = |d: &NaiveDate| d.weeks_from(Weekday::Sun);
|
||||
let week_from_mon = |d: &NaiveDate| d.weeks_from(Weekday::Mon);
|
||||
|
||||
let (width, v) = match *spec {
|
||||
Year => (4, date.map(|d| i64::from(d.year()))),
|
||||
YearDiv100 => (2, date.map(|d| i64::from(d.year()).div_euclid(100))),
|
||||
YearMod100 => (2, date.map(|d| i64::from(d.year()).rem_euclid(100))),
|
||||
IsoYear => (4, date.map(|d| i64::from(d.iso_week().year()))),
|
||||
IsoYearDiv100 => (2, date.map(|d| i64::from(d.iso_week().year()).div_euclid(100))),
|
||||
IsoYearMod100 => (2, date.map(|d| i64::from(d.iso_week().year()).rem_euclid(100))),
|
||||
Month => (2, date.map(|d| i64::from(d.month()))),
|
||||
Day => (2, date.map(|d| i64::from(d.day()))),
|
||||
WeekFromSun => (2, date.map(|d| i64::from(week_from_sun(d)))),
|
||||
WeekFromMon => (2, date.map(|d| i64::from(week_from_mon(d)))),
|
||||
IsoWeek => (2, date.map(|d| i64::from(d.iso_week().week()))),
|
||||
NumDaysFromSun => (1, date.map(|d| i64::from(d.weekday().num_days_from_sunday()))),
|
||||
WeekdayFromMon => (1, date.map(|d| i64::from(d.weekday().number_from_monday()))),
|
||||
Ordinal => (3, date.map(|d| i64::from(d.ordinal()))),
|
||||
Hour => (2, time.map(|t| i64::from(t.hour()))),
|
||||
Hour12 => (2, time.map(|t| i64::from(t.hour12().1))),
|
||||
Minute => (2, time.map(|t| i64::from(t.minute()))),
|
||||
Second => (2, time.map(|t| i64::from(t.second() + t.nanosecond() / 1_000_000_000))),
|
||||
Nanosecond => (9, time.map(|t| i64::from(t.nanosecond() % 1_000_000_000))),
|
||||
Timestamp => (
|
||||
1,
|
||||
match (date, time, off) {
|
||||
(Some(d), Some(t), None) => Some(d.and_time(*t).timestamp()),
|
||||
(Some(d), Some(t), Some(&(_, off))) => {
|
||||
Some((d.and_time(*t) - off).timestamp())
|
||||
}
|
||||
(_, _, _) => None,
|
||||
},
|
||||
),
|
||||
|
||||
// for the future expansion
|
||||
Internal(ref int) => match int._dummy {},
|
||||
};
|
||||
|
||||
if let Some(v) = v {
|
||||
if (spec == &Year || spec == &IsoYear) && !(0..10_000).contains(&v) {
|
||||
// non-four-digit years require an explicit sign as per ISO 8601
|
||||
match *pad {
|
||||
Pad::None => write!(result, "{:+}", v),
|
||||
Pad::Zero => write!(result, "{:+01$}", v, width + 1),
|
||||
Pad::Space => write!(result, "{:+1$}", v, width + 1),
|
||||
}
|
||||
} else {
|
||||
match *pad {
|
||||
Pad::None => write!(result, "{}", v),
|
||||
Pad::Zero => write!(result, "{:01$}", v, width),
|
||||
Pad::Space => write!(result, "{:1$}", v, width),
|
||||
}
|
||||
}?
|
||||
} else {
|
||||
return Err(fmt::Error); // insufficient arguments for given format
|
||||
}
|
||||
}
|
||||
|
||||
Item::Fixed(ref spec) => {
|
||||
use self::Fixed::*;
|
||||
|
||||
let ret =
|
||||
match *spec {
|
||||
ShortMonthName => date.map(|d| {
|
||||
result.push_str(locale.short_months[d.month0() as usize]);
|
||||
Ok(())
|
||||
}),
|
||||
LongMonthName => date.map(|d| {
|
||||
result.push_str(locale.long_months[d.month0() as usize]);
|
||||
Ok(())
|
||||
}),
|
||||
ShortWeekdayName => date.map(|d| {
|
||||
result.push_str(
|
||||
locale.short_weekdays[d.weekday().num_days_from_sunday() as usize],
|
||||
);
|
||||
Ok(())
|
||||
}),
|
||||
LongWeekdayName => date.map(|d| {
|
||||
result.push_str(
|
||||
locale.long_weekdays[d.weekday().num_days_from_sunday() as usize],
|
||||
);
|
||||
Ok(())
|
||||
}),
|
||||
LowerAmPm => time.map(|t| {
|
||||
let ampm = if t.hour12().0 { locale.am_pm[1] } else { locale.am_pm[0] };
|
||||
for char in ampm.chars() {
|
||||
result.extend(char.to_lowercase())
|
||||
}
|
||||
Ok(())
|
||||
}),
|
||||
UpperAmPm => time.map(|t| {
|
||||
result.push_str(if t.hour12().0 {
|
||||
locale.am_pm[1]
|
||||
} else {
|
||||
locale.am_pm[0]
|
||||
});
|
||||
Ok(())
|
||||
}),
|
||||
Nanosecond => time.map(|t| {
|
||||
let nano = t.nanosecond() % 1_000_000_000;
|
||||
if nano == 0 {
|
||||
Ok(())
|
||||
} else if nano % 1_000_000 == 0 {
|
||||
write!(result, ".{:03}", nano / 1_000_000)
|
||||
} else if nano % 1_000 == 0 {
|
||||
write!(result, ".{:06}", nano / 1_000)
|
||||
} else {
|
||||
write!(result, ".{:09}", nano)
|
||||
}
|
||||
}),
|
||||
Nanosecond3 => time.map(|t| {
|
||||
let nano = t.nanosecond() % 1_000_000_000;
|
||||
write!(result, ".{:03}", nano / 1_000_000)
|
||||
}),
|
||||
Nanosecond6 => time.map(|t| {
|
||||
let nano = t.nanosecond() % 1_000_000_000;
|
||||
write!(result, ".{:06}", nano / 1_000)
|
||||
}),
|
||||
Nanosecond9 => time.map(|t| {
|
||||
let nano = t.nanosecond() % 1_000_000_000;
|
||||
write!(result, ".{:09}", nano)
|
||||
}),
|
||||
Internal(InternalFixed { val: InternalInternal::Nanosecond3NoDot }) => time
|
||||
.map(|t| {
|
||||
let nano = t.nanosecond() % 1_000_000_000;
|
||||
write!(result, "{:03}", nano / 1_000_000)
|
||||
}),
|
||||
Internal(InternalFixed { val: InternalInternal::Nanosecond6NoDot }) => time
|
||||
.map(|t| {
|
||||
let nano = t.nanosecond() % 1_000_000_000;
|
||||
write!(result, "{:06}", nano / 1_000)
|
||||
}),
|
||||
Internal(InternalFixed { val: InternalInternal::Nanosecond9NoDot }) => time
|
||||
.map(|t| {
|
||||
let nano = t.nanosecond() % 1_000_000_000;
|
||||
write!(result, "{:09}", nano)
|
||||
}),
|
||||
TimezoneName => off.map(|(name, _)| {
|
||||
result.push_str(name);
|
||||
Ok(())
|
||||
}),
|
||||
TimezoneOffsetColon => off
|
||||
.map(|&(_, off)| write_local_minus_utc(result, off, false, Colons::Single)),
|
||||
TimezoneOffsetDoubleColon => off
|
||||
.map(|&(_, off)| write_local_minus_utc(result, off, false, Colons::Double)),
|
||||
TimezoneOffsetTripleColon => off
|
||||
.map(|&(_, off)| write_local_minus_utc(result, off, false, Colons::Triple)),
|
||||
TimezoneOffsetColonZ => off
|
||||
.map(|&(_, off)| write_local_minus_utc(result, off, true, Colons::Single)),
|
||||
TimezoneOffset => {
|
||||
off.map(|&(_, off)| write_local_minus_utc(result, off, false, Colons::None))
|
||||
}
|
||||
TimezoneOffsetZ => {
|
||||
off.map(|&(_, off)| write_local_minus_utc(result, off, true, Colons::None))
|
||||
}
|
||||
Internal(InternalFixed { val: InternalInternal::TimezoneOffsetPermissive }) => {
|
||||
panic!("Do not try to write %#z it is undefined")
|
||||
}
|
||||
RFC2822 =>
|
||||
// same as `%a, %d %b %Y %H:%M:%S %z`
|
||||
{
|
||||
if let (Some(d), Some(t), Some(&(_, off))) = (date, time, off) {
|
||||
Some(write_rfc2822_inner(result, d, t, off, locale))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
RFC3339 =>
|
||||
// same as `%Y-%m-%dT%H:%M:%S%.f%:z`
|
||||
{
|
||||
if let (Some(d), Some(t), Some(&(_, off))) = (date, time, off) {
|
||||
Some(write_rfc3339(result, crate::NaiveDateTime::new(*d, *t), off))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
match ret {
|
||||
Some(ret) => ret?,
|
||||
None => return Err(fmt::Error), // insufficient arguments for given format
|
||||
}
|
||||
}
|
||||
|
||||
Item::Error => return Err(fmt::Error),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Prints an offset from UTC in the format of `+HHMM` or `+HH:MM`.
|
||||
/// `Z` instead of `+00[:]00` is allowed when `allow_zulu` is true.
|
||||
#[cfg(any(feature = "alloc", feature = "std", test))]
|
||||
fn write_local_minus_utc(
|
||||
result: &mut String,
|
||||
off: FixedOffset,
|
||||
allow_zulu: bool,
|
||||
colon_type: Colons,
|
||||
) -> fmt::Result {
|
||||
let off = off.local_minus_utc();
|
||||
if allow_zulu && off == 0 {
|
||||
result.push('Z');
|
||||
return Ok(());
|
||||
}
|
||||
let (sign, off) = if off < 0 { ('-', -off) } else { ('+', off) };
|
||||
result.push(sign);
|
||||
|
||||
write_hundreds(result, (off / 3600) as u8)?;
|
||||
|
||||
match colon_type {
|
||||
Colons::None => write_hundreds(result, (off / 60 % 60) as u8),
|
||||
Colons::Single => {
|
||||
result.push(':');
|
||||
write_hundreds(result, (off / 60 % 60) as u8)
|
||||
}
|
||||
Colons::Double => {
|
||||
result.push(':');
|
||||
write_hundreds(result, (off / 60 % 60) as u8)?;
|
||||
result.push(':');
|
||||
write_hundreds(result, (off % 60) as u8)
|
||||
}
|
||||
Colons::Triple => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes the date, time and offset to the string. same as `%Y-%m-%dT%H:%M:%S%.f%:z`
|
||||
#[cfg(any(feature = "alloc", feature = "std", test))]
|
||||
pub(crate) fn write_rfc3339(
|
||||
result: &mut String,
|
||||
dt: crate::NaiveDateTime,
|
||||
off: FixedOffset,
|
||||
) -> fmt::Result {
|
||||
// reuse `Debug` impls which already print ISO 8601 format.
|
||||
// this is faster in this way.
|
||||
write!(result, "{:?}", dt)?;
|
||||
write_local_minus_utc(result, off, false, Colons::Single)
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "alloc", feature = "std", test))]
|
||||
/// write datetimes like `Tue, 1 Jul 2003 10:52:37 +0200`, same as `%a, %d %b %Y %H:%M:%S %z`
|
||||
pub(crate) fn write_rfc2822(
|
||||
result: &mut String,
|
||||
dt: crate::NaiveDateTime,
|
||||
off: FixedOffset,
|
||||
) -> fmt::Result {
|
||||
write_rfc2822_inner(result, &dt.date(), &dt.time(), off, Locales::new(None))
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "alloc", feature = "std", test))]
|
||||
/// write datetimes like `Tue, 1 Jul 2003 10:52:37 +0200`, same as `%a, %d %b %Y %H:%M:%S %z`
|
||||
fn write_rfc2822_inner(
|
||||
result: &mut String,
|
||||
d: &NaiveDate,
|
||||
t: &NaiveTime,
|
||||
off: FixedOffset,
|
||||
locale: Locales,
|
||||
) -> fmt::Result {
|
||||
let year = d.year();
|
||||
// RFC2822 is only defined on years 0 through 9999
|
||||
if !(0..=9999).contains(&year) {
|
||||
return Err(fmt::Error);
|
||||
}
|
||||
|
||||
result.push_str(locale.short_weekdays[d.weekday().num_days_from_sunday() as usize]);
|
||||
result.push_str(", ");
|
||||
write_hundreds(result, d.day() as u8)?;
|
||||
result.push(' ');
|
||||
result.push_str(locale.short_months[d.month0() as usize]);
|
||||
result.push(' ');
|
||||
write_hundreds(result, (year / 100) as u8)?;
|
||||
write_hundreds(result, (year % 100) as u8)?;
|
||||
result.push(' ');
|
||||
write_hundreds(result, t.hour() as u8)?;
|
||||
result.push(':');
|
||||
write_hundreds(result, t.minute() as u8)?;
|
||||
result.push(':');
|
||||
let sec = t.second() + t.nanosecond() / 1_000_000_000;
|
||||
write_hundreds(result, sec as u8)?;
|
||||
result.push(' ');
|
||||
write_local_minus_utc(result, off, false, Colons::None)
|
||||
}
|
||||
|
||||
/// Equivalent to `{:02}` formatting for n < 100.
|
||||
pub(crate) fn write_hundreds(w: &mut impl Write, n: u8) -> fmt::Result {
|
||||
if n >= 100 {
|
||||
return Err(fmt::Error);
|
||||
}
|
||||
|
||||
let tens = b'0' + n / 10;
|
||||
let ones = b'0' + n % 10;
|
||||
w.write_char(tens as char)?;
|
||||
w.write_char(ones as char)
|
||||
}
|
||||
|
||||
/// Tries to format given arguments with given formatting items.
|
||||
/// Internally used by `DelayedFormat`.
|
||||
#[cfg(any(feature = "alloc", feature = "std", test))]
|
||||
#[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))]
|
||||
pub fn format<'a, I, B>(
|
||||
w: &mut fmt::Formatter,
|
||||
date: Option<&NaiveDate>,
|
||||
time: Option<&NaiveTime>,
|
||||
off: Option<&(String, FixedOffset)>,
|
||||
items: I,
|
||||
) -> fmt::Result
|
||||
where
|
||||
I: Iterator<Item = B> + Clone,
|
||||
B: Borrow<Item<'a>>,
|
||||
{
|
||||
let mut result = String::new();
|
||||
for item in items {
|
||||
format_inner(&mut result, date, time, off, item.borrow(), None)?;
|
||||
}
|
||||
w.pad(&result)
|
||||
}
|
||||
|
||||
mod parsed;
|
||||
|
||||
// due to the size of parsing routines, they are in separate modules.
|
||||
mod parse;
|
||||
mod scan;
|
||||
|
||||
pub mod strftime;
|
||||
|
||||
/// A *temporary* object which can be used as an argument to `format!` or others.
|
||||
/// This is normally constructed via `format` methods of each date and time type.
|
||||
#[cfg(any(feature = "alloc", feature = "std", test))]
|
||||
#[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))]
|
||||
#[derive(Debug)]
|
||||
pub struct DelayedFormat<I> {
|
||||
/// The date view, if any.
|
||||
date: Option<NaiveDate>,
|
||||
/// The time view, if any.
|
||||
time: Option<NaiveTime>,
|
||||
/// The name and local-to-UTC difference for the offset (timezone), if any.
|
||||
off: Option<(String, FixedOffset)>,
|
||||
/// An iterator returning formatting items.
|
||||
items: I,
|
||||
/// Locale used for text.
|
||||
// TODO: Only used with the locale feature. We should make this property
|
||||
// only present when the feature is enabled.
|
||||
#[cfg(feature = "unstable-locales")]
|
||||
locale: Option<Locale>,
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "alloc", feature = "std", test))]
|
||||
impl<'a, I: Iterator<Item = B> + Clone, B: Borrow<Item<'a>>> DelayedFormat<I> {
|
||||
/// Makes a new `DelayedFormat` value out of local date and time.
|
||||
#[must_use]
|
||||
pub fn new(date: Option<NaiveDate>, time: Option<NaiveTime>, items: I) -> DelayedFormat<I> {
|
||||
DelayedFormat {
|
||||
date,
|
||||
time,
|
||||
off: None,
|
||||
items,
|
||||
#[cfg(feature = "unstable-locales")]
|
||||
locale: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Makes a new `DelayedFormat` value out of local date and time and UTC offset.
|
||||
#[must_use]
|
||||
pub fn new_with_offset<Off>(
|
||||
date: Option<NaiveDate>,
|
||||
time: Option<NaiveTime>,
|
||||
offset: &Off,
|
||||
items: I,
|
||||
) -> DelayedFormat<I>
|
||||
where
|
||||
Off: Offset + fmt::Display,
|
||||
{
|
||||
let name_and_diff = (offset.to_string(), offset.fix());
|
||||
DelayedFormat {
|
||||
date,
|
||||
time,
|
||||
off: Some(name_and_diff),
|
||||
items,
|
||||
#[cfg(feature = "unstable-locales")]
|
||||
locale: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Makes a new `DelayedFormat` value out of local date and time and locale.
|
||||
#[cfg(feature = "unstable-locales")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))]
|
||||
#[must_use]
|
||||
pub fn new_with_locale(
|
||||
date: Option<NaiveDate>,
|
||||
time: Option<NaiveTime>,
|
||||
items: I,
|
||||
locale: Locale,
|
||||
) -> DelayedFormat<I> {
|
||||
DelayedFormat { date, time, off: None, items, locale: Some(locale) }
|
||||
}
|
||||
|
||||
/// Makes a new `DelayedFormat` value out of local date and time, UTC offset and locale.
|
||||
#[cfg(feature = "unstable-locales")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))]
|
||||
#[must_use]
|
||||
pub fn new_with_offset_and_locale<Off>(
|
||||
date: Option<NaiveDate>,
|
||||
time: Option<NaiveTime>,
|
||||
offset: &Off,
|
||||
items: I,
|
||||
locale: Locale,
|
||||
) -> DelayedFormat<I>
|
||||
where
|
||||
Off: Offset + fmt::Display,
|
||||
{
|
||||
let name_and_diff = (offset.to_string(), offset.fix());
|
||||
DelayedFormat { date, time, off: Some(name_and_diff), items, locale: Some(locale) }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "alloc", feature = "std", test))]
|
||||
impl<'a, I: Iterator<Item = B> + Clone, B: Borrow<Item<'a>>> fmt::Display for DelayedFormat<I> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
#[cfg(feature = "unstable-locales")]
|
||||
{
|
||||
if let Some(locale) = self.locale {
|
||||
return format_localized(
|
||||
f,
|
||||
self.date.as_ref(),
|
||||
self.time.as_ref(),
|
||||
self.off.as_ref(),
|
||||
self.items.clone(),
|
||||
locale,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
format(f, self.date.as_ref(), self.time.as_ref(), self.off.as_ref(), self.items.clone())
|
||||
}
|
||||
}
|
||||
|
||||
// this implementation is here only because we need some private code from `scan`
|
||||
|
||||
/// Parsing a `str` into a `Weekday` uses the format [`%W`](./format/strftime/index.html).
|
||||
@ -986,45 +486,6 @@ impl FromStr for Weekday {
|
||||
}
|
||||
}
|
||||
|
||||
/// Formats single formatting item
|
||||
#[cfg(feature = "unstable-locales")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))]
|
||||
pub fn format_item_localized(
|
||||
w: &mut fmt::Formatter,
|
||||
date: Option<&NaiveDate>,
|
||||
time: Option<&NaiveTime>,
|
||||
off: Option<&(String, FixedOffset)>,
|
||||
item: &Item<'_>,
|
||||
locale: Locale,
|
||||
) -> fmt::Result {
|
||||
let mut result = String::new();
|
||||
format_inner(&mut result, date, time, off, item, Some(locale))?;
|
||||
w.pad(&result)
|
||||
}
|
||||
|
||||
/// Tries to format given arguments with given formatting items.
|
||||
/// Internally used by `DelayedFormat`.
|
||||
#[cfg(feature = "unstable-locales")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))]
|
||||
pub fn format_localized<'a, I, B>(
|
||||
w: &mut fmt::Formatter,
|
||||
date: Option<&NaiveDate>,
|
||||
time: Option<&NaiveTime>,
|
||||
off: Option<&(String, FixedOffset)>,
|
||||
items: I,
|
||||
locale: Locale,
|
||||
) -> fmt::Result
|
||||
where
|
||||
I: Iterator<Item = B> + Clone,
|
||||
B: Borrow<Item<'a>>,
|
||||
{
|
||||
let mut result = String::new();
|
||||
for item in items {
|
||||
format_inner(&mut result, date, time, off, item.borrow(), Some(locale))?;
|
||||
}
|
||||
w.pad(&result)
|
||||
}
|
||||
|
||||
/// Parsing a `str` into a `Month` uses the format [`%W`](./format/strftime/index.html).
|
||||
///
|
||||
/// # Example
|
||||
|
1584
src/format/parse.rs
1584
src/format/parse.rs
File diff suppressed because it is too large
Load Diff
@ -5,29 +5,9 @@
|
||||
* Various scanning routines for the parser.
|
||||
*/
|
||||
|
||||
#![allow(deprecated)]
|
||||
|
||||
use super::{ParseResult, INVALID, OUT_OF_RANGE, TOO_SHORT};
|
||||
use crate::Weekday;
|
||||
|
||||
/// Returns true when two slices are equal case-insensitively (in ASCII).
|
||||
/// Assumes that the `pattern` is already converted to lower case.
|
||||
fn equals(s: &[u8], pattern: &str) -> bool {
|
||||
let mut xs = s.iter().map(|&c| match c {
|
||||
b'A'..=b'Z' => c + 32,
|
||||
_ => c,
|
||||
});
|
||||
let mut ys = pattern.as_bytes().iter().cloned();
|
||||
loop {
|
||||
match (xs.next(), ys.next()) {
|
||||
(None, None) => return true,
|
||||
(None, _) | (_, None) => return false,
|
||||
(Some(x), Some(y)) if x != y => return false,
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to parse the non-negative number from `min` to `max` digits.
|
||||
///
|
||||
/// The absence of digits at all is an unconditional error.
|
||||
@ -79,7 +59,7 @@ pub(super) fn nanosecond(s: &str) -> ParseResult<(&str, i64)> {
|
||||
let v = v.checked_mul(SCALE[consumed]).ok_or(OUT_OF_RANGE)?;
|
||||
|
||||
// if there are more than 9 digits, skip next digits.
|
||||
let s = s.trim_left_matches(|c: char| c.is_ascii_digit());
|
||||
let s = s.trim_start_matches(|c: char| c.is_ascii_digit());
|
||||
|
||||
Ok((s, v))
|
||||
}
|
||||
@ -145,14 +125,16 @@ pub(super) fn short_weekday(s: &str) -> ParseResult<(&str, Weekday)> {
|
||||
/// It prefers long month names to short month names when both are possible.
|
||||
pub(super) fn short_or_long_month0(s: &str) -> ParseResult<(&str, u8)> {
|
||||
// lowercased month names, minus first three chars
|
||||
static LONG_MONTH_SUFFIXES: [&str; 12] =
|
||||
["uary", "ruary", "ch", "il", "", "e", "y", "ust", "tember", "ober", "ember", "ember"];
|
||||
static LONG_MONTH_SUFFIXES: [&[u8]; 12] = [
|
||||
b"uary", b"ruary", b"ch", b"il", b"", b"e", b"y", b"ust", b"tember", b"ober", b"ember",
|
||||
b"ember",
|
||||
];
|
||||
|
||||
let (mut s, month0) = short_month0(s)?;
|
||||
|
||||
// tries to consume the suffix if possible
|
||||
let suffix = LONG_MONTH_SUFFIXES[month0 as usize];
|
||||
if s.len() >= suffix.len() && equals(&s.as_bytes()[..suffix.len()], suffix) {
|
||||
if s.len() >= suffix.len() && s.as_bytes()[..suffix.len()].eq_ignore_ascii_case(suffix) {
|
||||
s = &s[suffix.len()..];
|
||||
}
|
||||
|
||||
@ -163,14 +145,14 @@ pub(super) fn short_or_long_month0(s: &str) -> ParseResult<(&str, u8)> {
|
||||
/// It prefers long weekday names to short weekday names when both are possible.
|
||||
pub(super) fn short_or_long_weekday(s: &str) -> ParseResult<(&str, Weekday)> {
|
||||
// lowercased weekday names, minus first three chars
|
||||
static LONG_WEEKDAY_SUFFIXES: [&str; 7] =
|
||||
["day", "sday", "nesday", "rsday", "day", "urday", "day"];
|
||||
static LONG_WEEKDAY_SUFFIXES: [&[u8]; 7] =
|
||||
[b"day", b"sday", b"nesday", b"rsday", b"day", b"urday", b"day"];
|
||||
|
||||
let (mut s, weekday) = short_weekday(s)?;
|
||||
|
||||
// tries to consume the suffix if possible
|
||||
let suffix = LONG_WEEKDAY_SUFFIXES[weekday.num_days_from_monday() as usize];
|
||||
if s.len() >= suffix.len() && equals(&s.as_bytes()[..suffix.len()], suffix) {
|
||||
if s.len() >= suffix.len() && s.as_bytes()[..suffix.len()].eq_ignore_ascii_case(suffix) {
|
||||
s = &s[suffix.len()..];
|
||||
}
|
||||
|
||||
@ -188,7 +170,7 @@ pub(super) fn char(s: &str, c1: u8) -> ParseResult<&str> {
|
||||
|
||||
/// Tries to consume one or more whitespace.
|
||||
pub(super) fn space(s: &str) -> ParseResult<&str> {
|
||||
let s_ = s.trim_left();
|
||||
let s_ = s.trim_start();
|
||||
if s_.len() < s.len() {
|
||||
Ok(s_)
|
||||
} else if s.is_empty() {
|
||||
@ -221,7 +203,7 @@ pub(super) fn trim1(s: &str) -> &str {
|
||||
|
||||
/// Consumes one colon char `:` if it is at the front of `s`.
|
||||
/// Always returns `Ok(s)`.
|
||||
pub(super) fn consume_colon_maybe(mut s: &str) -> ParseResult<&str> {
|
||||
pub(crate) fn consume_colon_maybe(mut s: &str) -> ParseResult<&str> {
|
||||
if s.is_empty() {
|
||||
// nothing consumed
|
||||
return Ok(s);
|
||||
@ -235,25 +217,49 @@ pub(super) fn consume_colon_maybe(mut s: &str) -> ParseResult<&str> {
|
||||
Ok(s)
|
||||
}
|
||||
|
||||
/// Tries to parse `[-+]\d\d` continued by `\d\d`. Return an offset in seconds if possible.
|
||||
/// Parse a timezone from `s` and return the offset in seconds.
|
||||
///
|
||||
/// The additional `colon` may be used to parse a mandatory or optional `:`
|
||||
/// between hours and minutes, and should return either a new suffix or `Err` when parsing fails.
|
||||
pub(super) fn timezone_offset<F>(s: &str, consume_colon: F) -> ParseResult<(&str, i32)>
|
||||
where
|
||||
F: FnMut(&str) -> ParseResult<&str>,
|
||||
{
|
||||
timezone_offset_internal(s, consume_colon, false)
|
||||
}
|
||||
|
||||
fn timezone_offset_internal<F>(
|
||||
/// The `consume_colon` function is used to parse a mandatory or optional `:`
|
||||
/// separator between hours offset and minutes offset.
|
||||
///
|
||||
/// The `allow_missing_minutes` flag allows the timezone minutes offset to be
|
||||
/// missing from `s`.
|
||||
///
|
||||
/// The `allow_tz_minus_sign` flag allows the timezone offset negative character
|
||||
/// to also be `−` MINUS SIGN (U+2212) in addition to the typical
|
||||
/// ASCII-compatible `-` HYPHEN-MINUS (U+2D).
|
||||
/// This is part of [RFC 3339 & ISO 8601].
|
||||
///
|
||||
/// [RFC 3339 & ISO 8601]: https://en.wikipedia.org/w/index.php?title=ISO_8601&oldid=1114309368#Time_offsets_from_UTC
|
||||
pub(crate) fn timezone_offset<F>(
|
||||
mut s: &str,
|
||||
mut consume_colon: F,
|
||||
allow_zulu: bool,
|
||||
allow_missing_minutes: bool,
|
||||
allow_tz_minus_sign: bool,
|
||||
) -> ParseResult<(&str, i32)>
|
||||
where
|
||||
F: FnMut(&str) -> ParseResult<&str>,
|
||||
{
|
||||
if allow_zulu {
|
||||
let bytes = s.as_bytes();
|
||||
match bytes.first() {
|
||||
Some(&b'z') | Some(&b'Z') => return Ok((&s[1..], 0)),
|
||||
Some(&b'u') | Some(&b'U') => {
|
||||
if bytes.len() >= 3 {
|
||||
let (b, c) = (bytes[1], bytes[2]);
|
||||
match (b | 32, c | 32) {
|
||||
(b't', b'c') => return Ok((&s[3..], 0)),
|
||||
_ => return Err(INVALID),
|
||||
}
|
||||
} else {
|
||||
return Err(INVALID);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
const fn digits(s: &str) -> ParseResult<(u8, u8)> {
|
||||
let b = s.as_bytes();
|
||||
if b.len() < 2 {
|
||||
@ -262,13 +268,31 @@ where
|
||||
Ok((b[0], b[1]))
|
||||
}
|
||||
}
|
||||
let negative = match s.as_bytes().first() {
|
||||
Some(&b'+') => false,
|
||||
Some(&b'-') => true,
|
||||
let negative = match s.chars().next() {
|
||||
Some('+') => {
|
||||
// PLUS SIGN (U+2B)
|
||||
s = &s['+'.len_utf8()..];
|
||||
|
||||
false
|
||||
}
|
||||
Some('-') => {
|
||||
// HYPHEN-MINUS (U+2D)
|
||||
s = &s['-'.len_utf8()..];
|
||||
|
||||
true
|
||||
}
|
||||
Some('−') => {
|
||||
// MINUS SIGN (U+2212)
|
||||
if !allow_tz_minus_sign {
|
||||
return Err(INVALID);
|
||||
}
|
||||
s = &s['−'.len_utf8()..];
|
||||
|
||||
true
|
||||
}
|
||||
Some(_) => return Err(INVALID),
|
||||
None => return Err(TOO_SHORT),
|
||||
};
|
||||
s = &s[1..];
|
||||
|
||||
// hours (00--99)
|
||||
let hours = match digits(s)? {
|
||||
@ -303,41 +327,6 @@ where
|
||||
Ok((s, if negative { -seconds } else { seconds }))
|
||||
}
|
||||
|
||||
/// Same as `timezone_offset` but also allows for `z`/`Z` which is the same as `+00:00`.
|
||||
pub(super) fn timezone_offset_zulu<F>(s: &str, colon: F) -> ParseResult<(&str, i32)>
|
||||
where
|
||||
F: FnMut(&str) -> ParseResult<&str>,
|
||||
{
|
||||
let bytes = s.as_bytes();
|
||||
match bytes.first() {
|
||||
Some(&b'z') | Some(&b'Z') => Ok((&s[1..], 0)),
|
||||
Some(&b'u') | Some(&b'U') => {
|
||||
if bytes.len() >= 3 {
|
||||
let (b, c) = (bytes[1], bytes[2]);
|
||||
match (b | 32, c | 32) {
|
||||
(b't', b'c') => Ok((&s[3..], 0)),
|
||||
_ => Err(INVALID),
|
||||
}
|
||||
} else {
|
||||
Err(INVALID)
|
||||
}
|
||||
}
|
||||
_ => timezone_offset(s, colon),
|
||||
}
|
||||
}
|
||||
|
||||
/// Same as `timezone_offset` but also allows for `z`/`Z` which is the same as
|
||||
/// `+00:00`, and allows missing minutes entirely.
|
||||
pub(super) fn timezone_offset_permissive<F>(s: &str, colon: F) -> ParseResult<(&str, i32)>
|
||||
where
|
||||
F: FnMut(&str) -> ParseResult<&str>,
|
||||
{
|
||||
match s.as_bytes().first() {
|
||||
Some(&b'z') | Some(&b'Z') => Ok((&s[1..], 0)),
|
||||
_ => timezone_offset_internal(s, colon, true),
|
||||
}
|
||||
}
|
||||
|
||||
/// Same as `timezone_offset` but also allows for RFC 2822 legacy timezones.
|
||||
/// May return `None` which indicates an insufficient offset data (i.e. `-0000`).
|
||||
/// See [RFC 2822 Section 4.3].
|
||||
@ -350,17 +339,17 @@ pub(super) fn timezone_offset_2822(s: &str) -> ParseResult<(&str, Option<i32>)>
|
||||
let name = &s.as_bytes()[..upto];
|
||||
let s = &s[upto..];
|
||||
let offset_hours = |o| Ok((s, Some(o * 3600)));
|
||||
if equals(name, "gmt") || equals(name, "ut") {
|
||||
if name.eq_ignore_ascii_case(b"gmt") || name.eq_ignore_ascii_case(b"ut") {
|
||||
offset_hours(0)
|
||||
} else if equals(name, "edt") {
|
||||
} else if name.eq_ignore_ascii_case(b"edt") {
|
||||
offset_hours(-4)
|
||||
} else if equals(name, "est") || equals(name, "cdt") {
|
||||
} else if name.eq_ignore_ascii_case(b"est") || name.eq_ignore_ascii_case(b"cdt") {
|
||||
offset_hours(-5)
|
||||
} else if equals(name, "cst") || equals(name, "mdt") {
|
||||
} else if name.eq_ignore_ascii_case(b"cst") || name.eq_ignore_ascii_case(b"mdt") {
|
||||
offset_hours(-6)
|
||||
} else if equals(name, "mst") || equals(name, "pdt") {
|
||||
} else if name.eq_ignore_ascii_case(b"mst") || name.eq_ignore_ascii_case(b"pdt") {
|
||||
offset_hours(-7)
|
||||
} else if equals(name, "pst") {
|
||||
} else if name.eq_ignore_ascii_case(b"pst") {
|
||||
offset_hours(-8)
|
||||
} else if name.len() == 1 {
|
||||
match name[0] {
|
||||
@ -372,17 +361,11 @@ pub(super) fn timezone_offset_2822(s: &str) -> ParseResult<(&str, Option<i32>)>
|
||||
Ok((s, None))
|
||||
}
|
||||
} else {
|
||||
let (s_, offset) = timezone_offset(s, |s| Ok(s))?;
|
||||
let (s_, offset) = timezone_offset(s, |s| Ok(s), false, false, false)?;
|
||||
Ok((s_, Some(offset)))
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to consume everything until next whitespace-like symbol.
|
||||
/// Does not provide any offset information from the consumed data.
|
||||
pub(super) fn timezone_name_skip(s: &str) -> ParseResult<(&str, ())> {
|
||||
Ok((s.trim_left_matches(|c: char| !c.is_whitespace()), ()))
|
||||
}
|
||||
|
||||
/// Tries to consume an RFC2822 comment including preceding ` `.
|
||||
///
|
||||
/// Returns the remaining string after the closing parenthesis.
|
||||
|
@ -65,7 +65,7 @@ The following specifiers are available both to formatting and parsing.
|
||||
| `%R` | `00:34` | Hour-minute format. Same as `%H:%M`. |
|
||||
| `%T` | `00:34:60` | Hour-minute-second format. Same as `%H:%M:%S`. |
|
||||
| `%X` | `00:34:60` | Locale's time representation (e.g., 23:13:48). |
|
||||
| `%r` | `12:34:60 AM` | Hour-minute-second format in 12-hour clocks. Same as `%I:%M:%S %p`. |
|
||||
| `%r` | `12:34:60 AM` | Locale's 12 hour clock time. (e.g., 11:11:04 PM). Falls back to `%X` if the locale does not have a 12 hour clock format. |
|
||||
| | | |
|
||||
| | | **TIME ZONE SPECIFIERS:** |
|
||||
| `%Z` | `ACST` | Local time zone name. Skips all non-whitespace characters during parsing. Identical to `%:z` when formatting. [^8] |
|
||||
@ -180,39 +180,10 @@ Notes:
|
||||
China Daylight Time.
|
||||
*/
|
||||
|
||||
#[cfg(feature = "unstable-locales")]
|
||||
extern crate alloc;
|
||||
|
||||
#[cfg(feature = "unstable-locales")]
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use super::{fixed, internal_fixed, num, num0, nums};
|
||||
#[cfg(feature = "unstable-locales")]
|
||||
use super::{locales, Locale};
|
||||
use super::{Fixed, InternalFixed, InternalInternal, Item, Numeric, Pad};
|
||||
|
||||
#[cfg(feature = "unstable-locales")]
|
||||
type Fmt<'a> = Vec<Item<'a>>;
|
||||
#[cfg(not(feature = "unstable-locales"))]
|
||||
type Fmt<'a> = &'static [Item<'static>];
|
||||
|
||||
static D_FMT: &[Item<'static>] =
|
||||
&[num0!(Month), lit!("/"), num0!(Day), lit!("/"), num0!(YearMod100)];
|
||||
static D_T_FMT: &[Item<'static>] = &[
|
||||
fix!(ShortWeekdayName),
|
||||
sp!(" "),
|
||||
fix!(ShortMonthName),
|
||||
sp!(" "),
|
||||
nums!(Day),
|
||||
sp!(" "),
|
||||
num0!(Hour),
|
||||
lit!(":"),
|
||||
num0!(Minute),
|
||||
lit!(":"),
|
||||
num0!(Second),
|
||||
sp!(" "),
|
||||
num0!(Year),
|
||||
];
|
||||
static T_FMT: &[Item<'static>] = &[num0!(Hour), lit!(":"), num0!(Minute), lit!(":"), num0!(Second)];
|
||||
use super::{Fixed, InternalInternal, Item, Numeric, Pad};
|
||||
|
||||
/// Parsing iterator for `strftime`-like format strings.
|
||||
#[derive(Clone, Debug)]
|
||||
@ -220,58 +191,34 @@ pub struct StrftimeItems<'a> {
|
||||
/// Remaining portion of the string.
|
||||
remainder: &'a str,
|
||||
/// If the current specifier is composed of multiple formatting items (e.g. `%+`),
|
||||
/// parser refers to the statically reconstructed slice of them.
|
||||
/// If `recons` is not empty they have to be returned earlier than the `remainder`.
|
||||
recons: Fmt<'a>,
|
||||
/// Date format
|
||||
d_fmt: Fmt<'a>,
|
||||
/// Date and time format
|
||||
d_t_fmt: Fmt<'a>,
|
||||
/// Time format
|
||||
t_fmt: Fmt<'a>,
|
||||
/// `queue` stores a slice of `Item`s that have to be returned one by one.
|
||||
queue: &'static [Item<'static>],
|
||||
#[cfg(feature = "unstable-locales")]
|
||||
locale_str: &'a str,
|
||||
#[cfg(feature = "unstable-locales")]
|
||||
locale: Option<Locale>,
|
||||
}
|
||||
|
||||
impl<'a> StrftimeItems<'a> {
|
||||
/// Creates a new parsing iterator from the `strftime`-like format string.
|
||||
#[must_use]
|
||||
pub fn new(s: &'a str) -> StrftimeItems<'a> {
|
||||
Self::with_remainer(s)
|
||||
pub const fn new(s: &'a str) -> StrftimeItems<'a> {
|
||||
#[cfg(not(feature = "unstable-locales"))]
|
||||
{
|
||||
StrftimeItems { remainder: s, queue: &[] }
|
||||
}
|
||||
#[cfg(feature = "unstable-locales")]
|
||||
{
|
||||
StrftimeItems { remainder: s, queue: &[], locale_str: "", locale: None }
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new parsing iterator from the `strftime`-like format string.
|
||||
#[cfg(feature = "unstable-locales")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))]
|
||||
#[must_use]
|
||||
pub fn new_with_locale(s: &'a str, locale: Locale) -> StrftimeItems<'a> {
|
||||
let d_fmt = StrftimeItems::new(locales::d_fmt(locale)).collect();
|
||||
let d_t_fmt = StrftimeItems::new(locales::d_t_fmt(locale)).collect();
|
||||
let t_fmt = StrftimeItems::new(locales::t_fmt(locale)).collect();
|
||||
|
||||
StrftimeItems { remainder: s, recons: Vec::new(), d_fmt, d_t_fmt, t_fmt }
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "unstable-locales"))]
|
||||
fn with_remainer(s: &'a str) -> StrftimeItems<'a> {
|
||||
static FMT_NONE: &[Item<'static>; 0] = &[];
|
||||
|
||||
StrftimeItems {
|
||||
remainder: s,
|
||||
recons: FMT_NONE,
|
||||
d_fmt: D_FMT,
|
||||
d_t_fmt: D_T_FMT,
|
||||
t_fmt: T_FMT,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "unstable-locales")]
|
||||
fn with_remainer(s: &'a str) -> StrftimeItems<'a> {
|
||||
StrftimeItems {
|
||||
remainder: s,
|
||||
recons: Vec::new(),
|
||||
d_fmt: D_FMT.to_vec(),
|
||||
d_t_fmt: D_T_FMT.to_vec(),
|
||||
t_fmt: T_FMT.to_vec(),
|
||||
}
|
||||
pub const fn new_with_locale(s: &'a str, locale: Locale) -> StrftimeItems<'a> {
|
||||
StrftimeItems { remainder: s, queue: &[], locale_str: "", locale: Some(locale) }
|
||||
}
|
||||
}
|
||||
|
||||
@ -281,37 +228,78 @@ impl<'a> Iterator for StrftimeItems<'a> {
|
||||
type Item = Item<'a>;
|
||||
|
||||
fn next(&mut self) -> Option<Item<'a>> {
|
||||
// we have some reconstructed items to return
|
||||
if !self.recons.is_empty() {
|
||||
let item;
|
||||
#[cfg(feature = "unstable-locales")]
|
||||
{
|
||||
item = self.recons.remove(0);
|
||||
}
|
||||
#[cfg(not(feature = "unstable-locales"))]
|
||||
{
|
||||
item = self.recons[0].clone();
|
||||
self.recons = &self.recons[1..];
|
||||
}
|
||||
// We have items queued to return from a specifier composed of multiple formatting items.
|
||||
if let Some((item, remainder)) = self.queue.split_first() {
|
||||
self.queue = remainder;
|
||||
return Some(item.clone());
|
||||
}
|
||||
|
||||
// We are in the middle of parsing the localized formatting string of a specifier.
|
||||
#[cfg(feature = "unstable-locales")]
|
||||
if !self.locale_str.is_empty() {
|
||||
let (remainder, item) = self.parse_next_item(self.locale_str)?;
|
||||
self.locale_str = remainder;
|
||||
return Some(item);
|
||||
}
|
||||
|
||||
match self.remainder.chars().next() {
|
||||
// Normal: we are parsing the formatting string.
|
||||
let (remainder, item) = self.parse_next_item(self.remainder)?;
|
||||
self.remainder = remainder;
|
||||
Some(item)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> StrftimeItems<'a> {
|
||||
fn parse_next_item(&mut self, mut remainder: &'a str) -> Option<(&'a str, Item<'a>)> {
|
||||
use InternalInternal::*;
|
||||
use Item::{Literal, Space};
|
||||
use Numeric::*;
|
||||
|
||||
static D_FMT: &[Item<'static>] =
|
||||
&[num0(Month), Literal("/"), num0(Day), Literal("/"), num0(YearMod100)];
|
||||
static D_T_FMT: &[Item<'static>] = &[
|
||||
fixed(Fixed::ShortWeekdayName),
|
||||
Space(" "),
|
||||
fixed(Fixed::ShortMonthName),
|
||||
Space(" "),
|
||||
nums(Day),
|
||||
Space(" "),
|
||||
num0(Hour),
|
||||
Literal(":"),
|
||||
num0(Minute),
|
||||
Literal(":"),
|
||||
num0(Second),
|
||||
Space(" "),
|
||||
num0(Year),
|
||||
];
|
||||
static T_FMT: &[Item<'static>] =
|
||||
&[num0(Hour), Literal(":"), num0(Minute), Literal(":"), num0(Second)];
|
||||
static T_FMT_AMPM: &[Item<'static>] = &[
|
||||
num0(Hour12),
|
||||
Literal(":"),
|
||||
num0(Minute),
|
||||
Literal(":"),
|
||||
num0(Second),
|
||||
Space(" "),
|
||||
fixed(Fixed::UpperAmPm),
|
||||
];
|
||||
|
||||
match remainder.chars().next() {
|
||||
// we are done
|
||||
None => None,
|
||||
|
||||
// the next item is a specifier
|
||||
Some('%') => {
|
||||
self.remainder = &self.remainder[1..];
|
||||
remainder = &remainder[1..];
|
||||
|
||||
macro_rules! next {
|
||||
() => {
|
||||
match self.remainder.chars().next() {
|
||||
match remainder.chars().next() {
|
||||
Some(x) => {
|
||||
self.remainder = &self.remainder[x.len_utf8()..];
|
||||
remainder = &remainder[x.len_utf8()..];
|
||||
x
|
||||
}
|
||||
None => return Some(Item::Error), // premature end of string
|
||||
None => return Some((remainder, Item::Error)), // premature end of string
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -326,193 +314,218 @@ impl<'a> Iterator for StrftimeItems<'a> {
|
||||
let is_alternate = spec == '#';
|
||||
let spec = if pad_override.is_some() || is_alternate { next!() } else { spec };
|
||||
if is_alternate && !HAVE_ALTERNATES.contains(spec) {
|
||||
return Some(Item::Error);
|
||||
return Some((remainder, Item::Error));
|
||||
}
|
||||
|
||||
macro_rules! recons {
|
||||
macro_rules! queue {
|
||||
[$head:expr, $($tail:expr),+ $(,)*] => ({
|
||||
#[cfg(feature = "unstable-locales")]
|
||||
{
|
||||
self.recons.clear();
|
||||
$(self.recons.push($tail);)+
|
||||
}
|
||||
#[cfg(not(feature = "unstable-locales"))]
|
||||
{
|
||||
const RECONS: &'static [Item<'static>] = &[$($tail),+];
|
||||
self.recons = RECONS;
|
||||
}
|
||||
const QUEUE: &'static [Item<'static>] = &[$($tail),+];
|
||||
self.queue = QUEUE;
|
||||
$head
|
||||
})
|
||||
}
|
||||
|
||||
macro_rules! recons_from_slice {
|
||||
#[cfg(not(feature = "unstable-locales"))]
|
||||
macro_rules! queue_from_slice {
|
||||
($slice:expr) => {{
|
||||
#[cfg(feature = "unstable-locales")]
|
||||
{
|
||||
self.recons.clear();
|
||||
self.recons.extend_from_slice(&$slice[1..]);
|
||||
}
|
||||
#[cfg(not(feature = "unstable-locales"))]
|
||||
{
|
||||
self.recons = &$slice[1..];
|
||||
}
|
||||
self.queue = &$slice[1..];
|
||||
$slice[0].clone()
|
||||
}};
|
||||
}
|
||||
|
||||
let item = match spec {
|
||||
'A' => fix!(LongWeekdayName),
|
||||
'B' => fix!(LongMonthName),
|
||||
'C' => num0!(YearDiv100),
|
||||
'A' => fixed(Fixed::LongWeekdayName),
|
||||
'B' => fixed(Fixed::LongMonthName),
|
||||
'C' => num0(YearDiv100),
|
||||
'D' => {
|
||||
recons![num0!(Month), lit!("/"), num0!(Day), lit!("/"), num0!(YearMod100)]
|
||||
queue![num0(Month), Literal("/"), num0(Day), Literal("/"), num0(YearMod100)]
|
||||
}
|
||||
'F' => recons![num0!(Year), lit!("-"), num0!(Month), lit!("-"), num0!(Day)],
|
||||
'G' => num0!(IsoYear),
|
||||
'H' => num0!(Hour),
|
||||
'I' => num0!(Hour12),
|
||||
'M' => num0!(Minute),
|
||||
'P' => fix!(LowerAmPm),
|
||||
'R' => recons![num0!(Hour), lit!(":"), num0!(Minute)],
|
||||
'S' => num0!(Second),
|
||||
'T' => recons![num0!(Hour), lit!(":"), num0!(Minute), lit!(":"), num0!(Second)],
|
||||
'U' => num0!(WeekFromSun),
|
||||
'V' => num0!(IsoWeek),
|
||||
'W' => num0!(WeekFromMon),
|
||||
'X' => recons_from_slice!(self.t_fmt),
|
||||
'Y' => num0!(Year),
|
||||
'Z' => fix!(TimezoneName),
|
||||
'a' => fix!(ShortWeekdayName),
|
||||
'b' | 'h' => fix!(ShortMonthName),
|
||||
'c' => recons_from_slice!(self.d_t_fmt),
|
||||
'd' => num0!(Day),
|
||||
'e' => nums!(Day),
|
||||
'f' => num0!(Nanosecond),
|
||||
'g' => num0!(IsoYearMod100),
|
||||
'j' => num0!(Ordinal),
|
||||
'k' => nums!(Hour),
|
||||
'l' => nums!(Hour12),
|
||||
'm' => num0!(Month),
|
||||
'n' => sp!("\n"),
|
||||
'p' => fix!(UpperAmPm),
|
||||
'r' => recons![
|
||||
num0!(Hour12),
|
||||
lit!(":"),
|
||||
num0!(Minute),
|
||||
lit!(":"),
|
||||
num0!(Second),
|
||||
sp!(" "),
|
||||
fix!(UpperAmPm)
|
||||
],
|
||||
's' => num!(Timestamp),
|
||||
't' => sp!("\t"),
|
||||
'u' => num!(WeekdayFromMon),
|
||||
'v' => {
|
||||
recons![nums!(Day), lit!("-"), fix!(ShortMonthName), lit!("-"), num0!(Year)]
|
||||
'F' => queue![num0(Year), Literal("-"), num0(Month), Literal("-"), num0(Day)],
|
||||
'G' => num0(IsoYear),
|
||||
'H' => num0(Hour),
|
||||
'I' => num0(Hour12),
|
||||
'M' => num0(Minute),
|
||||
'P' => fixed(Fixed::LowerAmPm),
|
||||
'R' => queue![num0(Hour), Literal(":"), num0(Minute)],
|
||||
'S' => num0(Second),
|
||||
'T' => {
|
||||
queue![num0(Hour), Literal(":"), num0(Minute), Literal(":"), num0(Second)]
|
||||
}
|
||||
'w' => num!(NumDaysFromSun),
|
||||
'x' => recons_from_slice!(self.d_fmt),
|
||||
'y' => num0!(YearMod100),
|
||||
'z' => {
|
||||
if is_alternate {
|
||||
internal_fix!(TimezoneOffsetPermissive)
|
||||
'U' => num0(WeekFromSun),
|
||||
'V' => num0(IsoWeek),
|
||||
'W' => num0(WeekFromMon),
|
||||
#[cfg(not(feature = "unstable-locales"))]
|
||||
'X' => queue_from_slice!(T_FMT),
|
||||
#[cfg(feature = "unstable-locales")]
|
||||
'X' => self.switch_to_locale_str(locales::t_fmt, T_FMT),
|
||||
'Y' => num0(Year),
|
||||
'Z' => fixed(Fixed::TimezoneName),
|
||||
'a' => fixed(Fixed::ShortWeekdayName),
|
||||
'b' | 'h' => fixed(Fixed::ShortMonthName),
|
||||
#[cfg(not(feature = "unstable-locales"))]
|
||||
'c' => queue_from_slice!(D_T_FMT),
|
||||
#[cfg(feature = "unstable-locales")]
|
||||
'c' => self.switch_to_locale_str(locales::d_t_fmt, D_T_FMT),
|
||||
'd' => num0(Day),
|
||||
'e' => nums(Day),
|
||||
'f' => num0(Nanosecond),
|
||||
'g' => num0(IsoYearMod100),
|
||||
'j' => num0(Ordinal),
|
||||
'k' => nums(Hour),
|
||||
'l' => nums(Hour12),
|
||||
'm' => num0(Month),
|
||||
'n' => Space("\n"),
|
||||
'p' => fixed(Fixed::UpperAmPm),
|
||||
#[cfg(not(feature = "unstable-locales"))]
|
||||
'r' => queue_from_slice!(T_FMT_AMPM),
|
||||
#[cfg(feature = "unstable-locales")]
|
||||
'r' => {
|
||||
if self.locale.is_some()
|
||||
&& locales::t_fmt_ampm(self.locale.unwrap()).is_empty()
|
||||
{
|
||||
// 12-hour clock not supported by this locale. Switch to 24-hour format.
|
||||
self.switch_to_locale_str(locales::t_fmt, T_FMT)
|
||||
} else {
|
||||
fix!(TimezoneOffset)
|
||||
self.switch_to_locale_str(locales::t_fmt_ampm, T_FMT_AMPM)
|
||||
}
|
||||
}
|
||||
'+' => fix!(RFC3339),
|
||||
's' => num(Timestamp),
|
||||
't' => Space("\t"),
|
||||
'u' => num(WeekdayFromMon),
|
||||
'v' => {
|
||||
queue![
|
||||
nums(Day),
|
||||
Literal("-"),
|
||||
fixed(Fixed::ShortMonthName),
|
||||
Literal("-"),
|
||||
num0(Year)
|
||||
]
|
||||
}
|
||||
'w' => num(NumDaysFromSun),
|
||||
#[cfg(not(feature = "unstable-locales"))]
|
||||
'x' => queue_from_slice!(D_FMT),
|
||||
#[cfg(feature = "unstable-locales")]
|
||||
'x' => self.switch_to_locale_str(locales::d_fmt, D_FMT),
|
||||
'y' => num0(YearMod100),
|
||||
'z' => {
|
||||
if is_alternate {
|
||||
internal_fixed(TimezoneOffsetPermissive)
|
||||
} else {
|
||||
fixed(Fixed::TimezoneOffset)
|
||||
}
|
||||
}
|
||||
'+' => fixed(Fixed::RFC3339),
|
||||
':' => {
|
||||
if self.remainder.starts_with("::z") {
|
||||
self.remainder = &self.remainder[3..];
|
||||
fix!(TimezoneOffsetTripleColon)
|
||||
} else if self.remainder.starts_with(":z") {
|
||||
self.remainder = &self.remainder[2..];
|
||||
fix!(TimezoneOffsetDoubleColon)
|
||||
} else if self.remainder.starts_with('z') {
|
||||
self.remainder = &self.remainder[1..];
|
||||
fix!(TimezoneOffsetColon)
|
||||
if remainder.starts_with("::z") {
|
||||
remainder = &remainder[3..];
|
||||
fixed(Fixed::TimezoneOffsetTripleColon)
|
||||
} else if remainder.starts_with(":z") {
|
||||
remainder = &remainder[2..];
|
||||
fixed(Fixed::TimezoneOffsetDoubleColon)
|
||||
} else if remainder.starts_with('z') {
|
||||
remainder = &remainder[1..];
|
||||
fixed(Fixed::TimezoneOffsetColon)
|
||||
} else {
|
||||
Item::Error
|
||||
}
|
||||
}
|
||||
'.' => match next!() {
|
||||
'3' => match next!() {
|
||||
'f' => fix!(Nanosecond3),
|
||||
'f' => fixed(Fixed::Nanosecond3),
|
||||
_ => Item::Error,
|
||||
},
|
||||
'6' => match next!() {
|
||||
'f' => fix!(Nanosecond6),
|
||||
'f' => fixed(Fixed::Nanosecond6),
|
||||
_ => Item::Error,
|
||||
},
|
||||
'9' => match next!() {
|
||||
'f' => fix!(Nanosecond9),
|
||||
'f' => fixed(Fixed::Nanosecond9),
|
||||
_ => Item::Error,
|
||||
},
|
||||
'f' => fix!(Nanosecond),
|
||||
'f' => fixed(Fixed::Nanosecond),
|
||||
_ => Item::Error,
|
||||
},
|
||||
'3' => match next!() {
|
||||
'f' => internal_fix!(Nanosecond3NoDot),
|
||||
'f' => internal_fixed(Nanosecond3NoDot),
|
||||
_ => Item::Error,
|
||||
},
|
||||
'6' => match next!() {
|
||||
'f' => internal_fix!(Nanosecond6NoDot),
|
||||
'f' => internal_fixed(Nanosecond6NoDot),
|
||||
_ => Item::Error,
|
||||
},
|
||||
'9' => match next!() {
|
||||
'f' => internal_fix!(Nanosecond9NoDot),
|
||||
'f' => internal_fixed(Nanosecond9NoDot),
|
||||
_ => Item::Error,
|
||||
},
|
||||
'%' => lit!("%"),
|
||||
'%' => Literal("%"),
|
||||
_ => Item::Error, // no such specifier
|
||||
};
|
||||
|
||||
// adjust `item` if we have any padding modifier
|
||||
// Adjust `item` if we have any padding modifier.
|
||||
// Not allowed on non-numeric items or on specifiers composed out of multiple
|
||||
// formatting items.
|
||||
if let Some(new_pad) = pad_override {
|
||||
match item {
|
||||
Item::Numeric(ref kind, _pad) if self.recons.is_empty() => {
|
||||
Some(Item::Numeric(kind.clone(), new_pad))
|
||||
Item::Numeric(ref kind, _pad) if self.queue.is_empty() => {
|
||||
Some((remainder, Item::Numeric(kind.clone(), new_pad)))
|
||||
}
|
||||
_ => Some(Item::Error), // no reconstructed or non-numeric item allowed
|
||||
_ => Some((remainder, Item::Error)),
|
||||
}
|
||||
} else {
|
||||
Some(item)
|
||||
Some((remainder, item))
|
||||
}
|
||||
}
|
||||
|
||||
// the next item is space
|
||||
Some(c) if c.is_whitespace() => {
|
||||
// `%` is not a whitespace, so `c != '%'` is redundant
|
||||
let nextspec = self
|
||||
.remainder
|
||||
.find(|c: char| !c.is_whitespace())
|
||||
.unwrap_or(self.remainder.len());
|
||||
let nextspec =
|
||||
remainder.find(|c: char| !c.is_whitespace()).unwrap_or(remainder.len());
|
||||
assert!(nextspec > 0);
|
||||
let item = sp!(&self.remainder[..nextspec]);
|
||||
self.remainder = &self.remainder[nextspec..];
|
||||
Some(item)
|
||||
let item = Space(&remainder[..nextspec]);
|
||||
remainder = &remainder[nextspec..];
|
||||
Some((remainder, item))
|
||||
}
|
||||
|
||||
// the next item is literal
|
||||
_ => {
|
||||
let nextspec = self
|
||||
.remainder
|
||||
let nextspec = remainder
|
||||
.find(|c: char| c.is_whitespace() || c == '%')
|
||||
.unwrap_or(self.remainder.len());
|
||||
.unwrap_or(remainder.len());
|
||||
assert!(nextspec > 0);
|
||||
let item = lit!(&self.remainder[..nextspec]);
|
||||
self.remainder = &self.remainder[nextspec..];
|
||||
Some(item)
|
||||
let item = Literal(&remainder[..nextspec]);
|
||||
remainder = &remainder[nextspec..];
|
||||
Some((remainder, item))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "unstable-locales")]
|
||||
fn switch_to_locale_str(
|
||||
&mut self,
|
||||
localized_fmt_str: impl Fn(Locale) -> &'static str,
|
||||
fallback: &'static [Item<'static>],
|
||||
) -> Item<'a> {
|
||||
if let Some(locale) = self.locale {
|
||||
assert!(self.locale_str.is_empty());
|
||||
let (fmt_str, item) = self.parse_next_item(localized_fmt_str(locale)).unwrap();
|
||||
self.locale_str = fmt_str;
|
||||
item
|
||||
} else {
|
||||
self.queue = &fallback[1..];
|
||||
fallback[0].clone()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::StrftimeItems;
|
||||
use crate::format::Item::{self, Literal, Space};
|
||||
#[cfg(feature = "unstable-locales")]
|
||||
use super::Locale;
|
||||
use super::{Fixed, InternalFixed, InternalInternal, Item, Numeric, Pad, StrftimeItems};
|
||||
use crate::format::Locale;
|
||||
use crate::format::{fixed, internal_fixed, num, num0, nums};
|
||||
use crate::format::{Fixed, InternalInternal, Numeric::*};
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
use crate::{DateTime, FixedOffset, NaiveDate, TimeZone, Timelike, Utc};
|
||||
|
||||
#[test]
|
||||
@ -526,97 +539,100 @@ mod tests {
|
||||
}
|
||||
|
||||
assert_eq!(parse_and_collect(""), []);
|
||||
assert_eq!(parse_and_collect(" "), [sp!(" ")]);
|
||||
assert_eq!(parse_and_collect(" "), [sp!(" ")]);
|
||||
assert_eq!(parse_and_collect(" "), [Space(" ")]);
|
||||
assert_eq!(parse_and_collect(" "), [Space(" ")]);
|
||||
// ne!
|
||||
assert_ne!(parse_and_collect(" "), [sp!(" "), sp!(" ")]);
|
||||
assert_ne!(parse_and_collect(" "), [Space(" "), Space(" ")]);
|
||||
// eq!
|
||||
assert_eq!(parse_and_collect(" "), [sp!(" ")]);
|
||||
assert_eq!(parse_and_collect("a"), [lit!("a")]);
|
||||
assert_eq!(parse_and_collect("ab"), [lit!("ab")]);
|
||||
assert_eq!(parse_and_collect("😽"), [lit!("😽")]);
|
||||
assert_eq!(parse_and_collect("a😽"), [lit!("a😽")]);
|
||||
assert_eq!(parse_and_collect("😽a"), [lit!("😽a")]);
|
||||
assert_eq!(parse_and_collect(" 😽"), [sp!(" "), lit!("😽")]);
|
||||
assert_eq!(parse_and_collect("😽 "), [lit!("😽"), sp!(" ")]);
|
||||
assert_eq!(parse_and_collect(" "), [Space(" ")]);
|
||||
assert_eq!(parse_and_collect("a"), [Literal("a")]);
|
||||
assert_eq!(parse_and_collect("ab"), [Literal("ab")]);
|
||||
assert_eq!(parse_and_collect("😽"), [Literal("😽")]);
|
||||
assert_eq!(parse_and_collect("a😽"), [Literal("a😽")]);
|
||||
assert_eq!(parse_and_collect("😽a"), [Literal("😽a")]);
|
||||
assert_eq!(parse_and_collect(" 😽"), [Space(" "), Literal("😽")]);
|
||||
assert_eq!(parse_and_collect("😽 "), [Literal("😽"), Space(" ")]);
|
||||
// ne!
|
||||
assert_ne!(parse_and_collect("😽😽"), [lit!("😽")]);
|
||||
assert_ne!(parse_and_collect("😽"), [lit!("😽😽")]);
|
||||
assert_ne!(parse_and_collect("😽😽"), [lit!("😽😽"), lit!("😽")]);
|
||||
assert_ne!(parse_and_collect("😽😽"), [Literal("😽")]);
|
||||
assert_ne!(parse_and_collect("😽"), [Literal("😽😽")]);
|
||||
assert_ne!(parse_and_collect("😽😽"), [Literal("😽😽"), Literal("😽")]);
|
||||
// eq!
|
||||
assert_eq!(parse_and_collect("😽😽"), [lit!("😽😽")]);
|
||||
assert_eq!(parse_and_collect(" \t\n\r "), [sp!(" \t\n\r ")]);
|
||||
assert_eq!(parse_and_collect("hello?"), [lit!("hello?")]);
|
||||
assert_eq!(parse_and_collect("😽😽"), [Literal("😽😽")]);
|
||||
assert_eq!(parse_and_collect(" \t\n\r "), [Space(" \t\n\r ")]);
|
||||
assert_eq!(parse_and_collect("hello?"), [Literal("hello?")]);
|
||||
assert_eq!(
|
||||
parse_and_collect("a b\t\nc"),
|
||||
[lit!("a"), sp!(" "), lit!("b"), sp!("\t\n"), lit!("c")]
|
||||
[Literal("a"), Space(" "), Literal("b"), Space("\t\n"), Literal("c")]
|
||||
);
|
||||
assert_eq!(parse_and_collect("100%%"), [lit!("100"), lit!("%")]);
|
||||
assert_eq!(parse_and_collect("100%% ok"), [lit!("100"), lit!("%"), sp!(" "), lit!("ok")]);
|
||||
assert_eq!(parse_and_collect("%%PDF-1.0"), [lit!("%"), lit!("PDF-1.0")]);
|
||||
assert_eq!(parse_and_collect("100%%"), [Literal("100"), Literal("%")]);
|
||||
assert_eq!(
|
||||
parse_and_collect("100%% ok"),
|
||||
[Literal("100"), Literal("%"), Space(" "), Literal("ok")]
|
||||
);
|
||||
assert_eq!(parse_and_collect("%%PDF-1.0"), [Literal("%"), Literal("PDF-1.0")]);
|
||||
assert_eq!(
|
||||
parse_and_collect("%Y-%m-%d"),
|
||||
[num0!(Year), lit!("-"), num0!(Month), lit!("-"), num0!(Day)]
|
||||
[num0(Year), Literal("-"), num0(Month), Literal("-"), num0(Day)]
|
||||
);
|
||||
assert_eq!(parse_and_collect("😽 "), [lit!("😽"), sp!(" ")]);
|
||||
assert_eq!(parse_and_collect("😽😽"), [lit!("😽😽")]);
|
||||
assert_eq!(parse_and_collect("😽😽😽"), [lit!("😽😽😽")]);
|
||||
assert_eq!(parse_and_collect("😽😽 😽"), [lit!("😽😽"), sp!(" "), lit!("😽")]);
|
||||
assert_eq!(parse_and_collect("😽😽a 😽"), [lit!("😽😽a"), sp!(" "), lit!("😽")]);
|
||||
assert_eq!(parse_and_collect("😽😽a b😽"), [lit!("😽😽a"), sp!(" "), lit!("b😽")]);
|
||||
assert_eq!(parse_and_collect("😽😽a b😽c"), [lit!("😽😽a"), sp!(" "), lit!("b😽c")]);
|
||||
assert_eq!(parse_and_collect("😽😽 "), [lit!("😽😽"), sp!(" ")]);
|
||||
assert_eq!(parse_and_collect("😽😽 😽"), [lit!("😽😽"), sp!(" "), lit!("😽")]);
|
||||
assert_eq!(parse_and_collect(" 😽"), [sp!(" "), lit!("😽")]);
|
||||
assert_eq!(parse_and_collect(" 😽 "), [sp!(" "), lit!("😽"), sp!(" ")]);
|
||||
assert_eq!(parse_and_collect(" 😽 😽"), [sp!(" "), lit!("😽"), sp!(" "), lit!("😽")]);
|
||||
assert_eq!(parse_and_collect("😽 "), [Literal("😽"), Space(" ")]);
|
||||
assert_eq!(parse_and_collect("😽😽"), [Literal("😽😽")]);
|
||||
assert_eq!(parse_and_collect("😽😽😽"), [Literal("😽😽😽")]);
|
||||
assert_eq!(parse_and_collect("😽😽 😽"), [Literal("😽😽"), Space(" "), Literal("😽")]);
|
||||
assert_eq!(parse_and_collect("😽😽a 😽"), [Literal("😽😽a"), Space(" "), Literal("😽")]);
|
||||
assert_eq!(parse_and_collect("😽😽a b😽"), [Literal("😽😽a"), Space(" "), Literal("b😽")]);
|
||||
assert_eq!(parse_and_collect("😽😽a b😽c"), [Literal("😽😽a"), Space(" "), Literal("b😽c")]);
|
||||
assert_eq!(parse_and_collect("😽😽 "), [Literal("😽😽"), Space(" ")]);
|
||||
assert_eq!(parse_and_collect("😽😽 😽"), [Literal("😽😽"), Space(" "), Literal("😽")]);
|
||||
assert_eq!(parse_and_collect(" 😽"), [Space(" "), Literal("😽")]);
|
||||
assert_eq!(parse_and_collect(" 😽 "), [Space(" "), Literal("😽"), Space(" ")]);
|
||||
assert_eq!(parse_and_collect(" 😽 😽"), [Space(" "), Literal("😽"), Space(" "), Literal("😽")]);
|
||||
assert_eq!(
|
||||
parse_and_collect(" 😽 😽 "),
|
||||
[sp!(" "), lit!("😽"), sp!(" "), lit!("😽"), sp!(" ")]
|
||||
[Space(" "), Literal("😽"), Space(" "), Literal("😽"), Space(" ")]
|
||||
);
|
||||
assert_eq!(
|
||||
parse_and_collect(" 😽 😽 "),
|
||||
[sp!(" "), lit!("😽"), sp!(" "), lit!("😽"), sp!(" ")]
|
||||
[Space(" "), Literal("😽"), Space(" "), Literal("😽"), Space(" ")]
|
||||
);
|
||||
assert_eq!(
|
||||
parse_and_collect(" 😽 😽😽 "),
|
||||
[sp!(" "), lit!("😽"), sp!(" "), lit!("😽😽"), sp!(" ")]
|
||||
[Space(" "), Literal("😽"), Space(" "), Literal("😽😽"), Space(" ")]
|
||||
);
|
||||
assert_eq!(parse_and_collect(" 😽😽"), [sp!(" "), lit!("😽😽")]);
|
||||
assert_eq!(parse_and_collect(" 😽😽 "), [sp!(" "), lit!("😽😽"), sp!(" ")]);
|
||||
assert_eq!(parse_and_collect(" 😽😽 "), [sp!(" "), lit!("😽😽"), sp!(" ")]);
|
||||
assert_eq!(parse_and_collect(" 😽😽 "), [sp!(" "), lit!("😽😽"), sp!(" ")]);
|
||||
assert_eq!(parse_and_collect(" 😽😽 "), [sp!(" "), lit!("😽😽"), sp!(" ")]);
|
||||
assert_eq!(parse_and_collect(" 😽😽"), [Space(" "), Literal("😽😽")]);
|
||||
assert_eq!(parse_and_collect(" 😽😽 "), [Space(" "), Literal("😽😽"), Space(" ")]);
|
||||
assert_eq!(parse_and_collect(" 😽😽 "), [Space(" "), Literal("😽😽"), Space(" ")]);
|
||||
assert_eq!(parse_and_collect(" 😽😽 "), [Space(" "), Literal("😽😽"), Space(" ")]);
|
||||
assert_eq!(parse_and_collect(" 😽😽 "), [Space(" "), Literal("😽😽"), Space(" ")]);
|
||||
assert_eq!(
|
||||
parse_and_collect(" 😽 😽😽 "),
|
||||
[sp!(" "), lit!("😽"), sp!(" "), lit!("😽😽"), sp!(" ")]
|
||||
[Space(" "), Literal("😽"), Space(" "), Literal("😽😽"), Space(" ")]
|
||||
);
|
||||
assert_eq!(
|
||||
parse_and_collect(" 😽 😽はい😽 ハンバーガー"),
|
||||
[sp!(" "), lit!("😽"), sp!(" "), lit!("😽はい😽"), sp!(" "), lit!("ハンバーガー")]
|
||||
[Space(" "), Literal("😽"), Space(" "), Literal("😽はい😽"), Space(" "), Literal("ハンバーガー")]
|
||||
);
|
||||
assert_eq!(parse_and_collect("%%😽%%😽"), [lit!("%"), lit!("😽"), lit!("%"), lit!("😽")]);
|
||||
assert_eq!(parse_and_collect("%Y--%m"), [num0!(Year), lit!("--"), num0!(Month)]);
|
||||
assert_eq!(parse_and_collect("%%😽%%😽"), [Literal("%"), Literal("😽"), Literal("%"), Literal("😽")]);
|
||||
assert_eq!(parse_and_collect("%Y--%m"), [num0(Year), Literal("--"), num0(Month)]);
|
||||
assert_eq!(parse_and_collect("[%F]"), parse_and_collect("[%Y-%m-%d]"));
|
||||
assert_eq!(parse_and_collect("100%%😽"), [lit!("100"), lit!("%"), lit!("😽")]);
|
||||
assert_eq!(parse_and_collect("100%%😽"), [Literal("100"), Literal("%"), Literal("😽")]);
|
||||
assert_eq!(
|
||||
parse_and_collect("100%%😽%%a"),
|
||||
[lit!("100"), lit!("%"), lit!("😽"), lit!("%"), lit!("a")]
|
||||
[Literal("100"), Literal("%"), Literal("😽"), Literal("%"), Literal("a")]
|
||||
);
|
||||
assert_eq!(parse_and_collect("😽100%%"), [lit!("😽100"), lit!("%")]);
|
||||
assert_eq!(parse_and_collect("%m %d"), [num0!(Month), sp!(" "), num0!(Day)]);
|
||||
assert_eq!(parse_and_collect("😽100%%"), [Literal("😽100"), Literal("%")]);
|
||||
assert_eq!(parse_and_collect("%m %d"), [num0(Month), Space(" "), num0(Day)]);
|
||||
assert_eq!(parse_and_collect("%"), [Item::Error]);
|
||||
assert_eq!(parse_and_collect("%%"), [lit!("%")]);
|
||||
assert_eq!(parse_and_collect("%%"), [Literal("%")]);
|
||||
assert_eq!(parse_and_collect("%%%"), [Item::Error]);
|
||||
assert_eq!(parse_and_collect("%a"), [fix!(ShortWeekdayName)]);
|
||||
assert_eq!(parse_and_collect("%aa"), [fix!(ShortWeekdayName), lit!("a")]);
|
||||
assert_eq!(parse_and_collect("%a"), [fixed(Fixed::ShortWeekdayName)]);
|
||||
assert_eq!(parse_and_collect("%aa"), [fixed(Fixed::ShortWeekdayName), Literal("a")]);
|
||||
assert_eq!(parse_and_collect("%%a%"), [Item::Error]);
|
||||
assert_eq!(parse_and_collect("%😽"), [Item::Error]);
|
||||
assert_eq!(parse_and_collect("%😽😽"), [Item::Error]);
|
||||
assert_eq!(parse_and_collect("%%%%"), [lit!("%"), lit!("%")]);
|
||||
assert_eq!(parse_and_collect("%%%%"), [Literal("%"), Literal("%")]);
|
||||
assert_eq!(
|
||||
parse_and_collect("%%%%ハンバーガー"),
|
||||
[lit!("%"), lit!("%"), lit!("ハンバーガー")]
|
||||
[Literal("%"), Literal("%"), Literal("ハンバーガー")]
|
||||
);
|
||||
assert_eq!(parse_and_collect("foo%?"), [Item::Error]);
|
||||
assert_eq!(parse_and_collect("bar%42"), [Item::Error]);
|
||||
@ -628,24 +644,25 @@ mod tests {
|
||||
assert_eq!(parse_and_collect("%_Z"), [Item::Error]);
|
||||
assert_eq!(parse_and_collect("%.j"), [Item::Error]);
|
||||
assert_eq!(parse_and_collect("%:j"), [Item::Error]);
|
||||
assert_eq!(parse_and_collect("%-j"), [num!(Ordinal)]);
|
||||
assert_eq!(parse_and_collect("%0j"), [num0!(Ordinal)]);
|
||||
assert_eq!(parse_and_collect("%_j"), [nums!(Ordinal)]);
|
||||
assert_eq!(parse_and_collect("%-j"), [num(Ordinal)]);
|
||||
assert_eq!(parse_and_collect("%0j"), [num0(Ordinal)]);
|
||||
assert_eq!(parse_and_collect("%_j"), [nums(Ordinal)]);
|
||||
assert_eq!(parse_and_collect("%.e"), [Item::Error]);
|
||||
assert_eq!(parse_and_collect("%:e"), [Item::Error]);
|
||||
assert_eq!(parse_and_collect("%-e"), [num!(Day)]);
|
||||
assert_eq!(parse_and_collect("%0e"), [num0!(Day)]);
|
||||
assert_eq!(parse_and_collect("%_e"), [nums!(Day)]);
|
||||
assert_eq!(parse_and_collect("%z"), [fix!(TimezoneOffset)]);
|
||||
assert_eq!(parse_and_collect("%:z"), [fix!(TimezoneOffsetColon)]);
|
||||
assert_eq!(parse_and_collect("%Z"), [fix!(TimezoneName)]);
|
||||
assert_eq!(parse_and_collect("%ZZZZ"), [fix!(TimezoneName), lit!("ZZZ")]);
|
||||
assert_eq!(parse_and_collect("%Z😽"), [fix!(TimezoneName), lit!("😽")]);
|
||||
assert_eq!(parse_and_collect("%#z"), [internal_fix!(TimezoneOffsetPermissive)]);
|
||||
assert_eq!(parse_and_collect("%-e"), [num(Day)]);
|
||||
assert_eq!(parse_and_collect("%0e"), [num0(Day)]);
|
||||
assert_eq!(parse_and_collect("%_e"), [nums(Day)]);
|
||||
assert_eq!(parse_and_collect("%z"), [fixed(Fixed::TimezoneOffset)]);
|
||||
assert_eq!(parse_and_collect("%:z"), [fixed(Fixed::TimezoneOffsetColon)]);
|
||||
assert_eq!(parse_and_collect("%Z"), [fixed(Fixed::TimezoneName)]);
|
||||
assert_eq!(parse_and_collect("%ZZZZ"), [fixed(Fixed::TimezoneName), Literal("ZZZ")]);
|
||||
assert_eq!(parse_and_collect("%Z😽"), [fixed(Fixed::TimezoneName), Literal("😽")]);
|
||||
assert_eq!(parse_and_collect("%#z"), [internal_fixed(TimezoneOffsetPermissive)]);
|
||||
assert_eq!(parse_and_collect("%#m"), [Item::Error]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
fn test_strftime_docs() {
|
||||
let dt = FixedOffset::east_opt(34200)
|
||||
.unwrap()
|
||||
@ -758,8 +775,8 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "unstable-locales")]
|
||||
#[test]
|
||||
#[cfg(all(feature = "unstable-locales", any(feature = "alloc", feature = "std")))]
|
||||
fn test_strftime_docs_localized() {
|
||||
let dt = FixedOffset::east_opt(34200)
|
||||
.unwrap()
|
||||
@ -785,7 +802,7 @@ mod tests {
|
||||
assert_eq!(dt.format_localized("%R", Locale::fr_BE).to_string(), "00:34");
|
||||
assert_eq!(dt.format_localized("%T", Locale::fr_BE).to_string(), "00:34:60");
|
||||
assert_eq!(dt.format_localized("%X", Locale::fr_BE).to_string(), "00:34:60");
|
||||
assert_eq!(dt.format_localized("%r", Locale::fr_BE).to_string(), "12:34:60 ");
|
||||
assert_eq!(dt.format_localized("%r", Locale::fr_BE).to_string(), "00:34:60");
|
||||
|
||||
// date & time specifiers
|
||||
assert_eq!(
|
||||
@ -806,4 +823,96 @@ mod tests {
|
||||
assert_eq!(nd.format_localized("%F", Locale::de_DE).to_string(), "2001-07-08");
|
||||
assert_eq!(nd.format_localized("%v", Locale::de_DE).to_string(), " 8-Jul-2001");
|
||||
}
|
||||
|
||||
/// Ensure parsing a timestamp with the parse-only stftime formatter "%#z" does
|
||||
/// not cause a panic.
|
||||
///
|
||||
/// See <https://github.com/chronotope/chrono/issues/1139>.
|
||||
#[test]
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
fn test_parse_only_timezone_offset_permissive_no_panic() {
|
||||
use crate::NaiveDate;
|
||||
use crate::{FixedOffset, TimeZone};
|
||||
use std::fmt::Write;
|
||||
|
||||
let dt = FixedOffset::east_opt(34200)
|
||||
.unwrap()
|
||||
.from_local_datetime(
|
||||
&NaiveDate::from_ymd_opt(2001, 7, 8)
|
||||
.unwrap()
|
||||
.and_hms_nano_opt(0, 34, 59, 1_026_490_708)
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut buf = String::new();
|
||||
let _ = write!(buf, "{}", dt.format("%#z")).expect_err("parse-only formatter should fail");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(all(feature = "unstable-locales", any(feature = "alloc", feature = "std")))]
|
||||
fn test_strftime_localized_korean() {
|
||||
let dt = FixedOffset::east_opt(34200)
|
||||
.unwrap()
|
||||
.with_ymd_and_hms(2001, 7, 8, 0, 34, 59)
|
||||
.unwrap()
|
||||
.with_nanosecond(1_026_490_708)
|
||||
.unwrap();
|
||||
|
||||
// date specifiers
|
||||
assert_eq!(dt.format_localized("%b", Locale::ko_KR).to_string(), " 7월");
|
||||
assert_eq!(dt.format_localized("%B", Locale::ko_KR).to_string(), "7월");
|
||||
assert_eq!(dt.format_localized("%h", Locale::ko_KR).to_string(), " 7월");
|
||||
assert_eq!(dt.format_localized("%a", Locale::ko_KR).to_string(), "일");
|
||||
assert_eq!(dt.format_localized("%A", Locale::ko_KR).to_string(), "일요일");
|
||||
assert_eq!(dt.format_localized("%D", Locale::ko_KR).to_string(), "07/08/01");
|
||||
assert_eq!(dt.format_localized("%x", Locale::ko_KR).to_string(), "2001년 07월 08일");
|
||||
assert_eq!(dt.format_localized("%F", Locale::ko_KR).to_string(), "2001-07-08");
|
||||
assert_eq!(dt.format_localized("%v", Locale::ko_KR).to_string(), " 8- 7월-2001");
|
||||
assert_eq!(dt.format_localized("%r", Locale::ko_KR).to_string(), "오전 12시 34분 60초");
|
||||
|
||||
// date & time specifiers
|
||||
assert_eq!(
|
||||
dt.format_localized("%c", Locale::ko_KR).to_string(),
|
||||
"2001년 07월 08일 (일) 오전 12시 34분 60초"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(all(feature = "unstable-locales", any(feature = "alloc", feature = "std")))]
|
||||
fn test_strftime_localized_japanese() {
|
||||
let dt = FixedOffset::east_opt(34200)
|
||||
.unwrap()
|
||||
.with_ymd_and_hms(2001, 7, 8, 0, 34, 59)
|
||||
.unwrap()
|
||||
.with_nanosecond(1_026_490_708)
|
||||
.unwrap();
|
||||
|
||||
// date specifiers
|
||||
assert_eq!(dt.format_localized("%b", Locale::ja_JP).to_string(), " 7月");
|
||||
assert_eq!(dt.format_localized("%B", Locale::ja_JP).to_string(), "7月");
|
||||
assert_eq!(dt.format_localized("%h", Locale::ja_JP).to_string(), " 7月");
|
||||
assert_eq!(dt.format_localized("%a", Locale::ja_JP).to_string(), "日");
|
||||
assert_eq!(dt.format_localized("%A", Locale::ja_JP).to_string(), "日曜日");
|
||||
assert_eq!(dt.format_localized("%D", Locale::ja_JP).to_string(), "07/08/01");
|
||||
assert_eq!(dt.format_localized("%x", Locale::ja_JP).to_string(), "2001年07月08日");
|
||||
assert_eq!(dt.format_localized("%F", Locale::ja_JP).to_string(), "2001-07-08");
|
||||
assert_eq!(dt.format_localized("%v", Locale::ja_JP).to_string(), " 8- 7月-2001");
|
||||
assert_eq!(dt.format_localized("%r", Locale::ja_JP).to_string(), "午前12時34分60秒");
|
||||
|
||||
// date & time specifiers
|
||||
assert_eq!(
|
||||
dt.format_localized("%c", Locale::ja_JP).to_string(),
|
||||
"2001年07月08日 00時34分60秒"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "unstable-locales")]
|
||||
fn test_type_sizes() {
|
||||
use core::mem::size_of;
|
||||
assert_eq!(size_of::<Item>(), 24);
|
||||
assert_eq!(size_of::<StrftimeItems>(), 56);
|
||||
assert_eq!(size_of::<Locale>(), 2);
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
//! ISO 8601 calendar date without timezone.
|
||||
|
||||
#[cfg(any(feature = "alloc", feature = "std", test))]
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
use core::borrow::Borrow;
|
||||
use core::iter::FusedIterator;
|
||||
use core::ops::{Add, AddAssign, RangeInclusive, Sub, SubAssign};
|
||||
@ -16,7 +16,7 @@ use rkyv::{Archive, Deserialize, Serialize};
|
||||
#[cfg(feature = "unstable-locales")]
|
||||
use pure_rust_locales::Locale;
|
||||
|
||||
#[cfg(any(feature = "alloc", feature = "std", test))]
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
use crate::format::DelayedFormat;
|
||||
use crate::format::{
|
||||
parse, parse_and_remainder, write_hundreds, Item, Numeric, Pad, ParseError, ParseResult,
|
||||
@ -1245,7 +1245,7 @@ impl NaiveDate {
|
||||
/// # let d = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap();
|
||||
/// assert_eq!(format!("{}", d.format_with_items(fmt)), "2015-09-05");
|
||||
/// ```
|
||||
#[cfg(any(feature = "alloc", feature = "std", test))]
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))]
|
||||
#[inline]
|
||||
#[must_use]
|
||||
@ -1289,7 +1289,7 @@ impl NaiveDate {
|
||||
/// assert_eq!(format!("{}", d.format("%Y-%m-%d")), "2015-09-05");
|
||||
/// assert_eq!(format!("{}", d.format("%A, %-d %B, %C%y")), "Saturday, 5 September, 2015");
|
||||
/// ```
|
||||
#[cfg(any(feature = "alloc", feature = "std", test))]
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))]
|
||||
#[inline]
|
||||
#[must_use]
|
||||
@ -2250,15 +2250,6 @@ mod serde {
|
||||
formatter.write_str("a formatted date string")
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "std", test))]
|
||||
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: de::Error,
|
||||
{
|
||||
value.parse().map_err(E::custom)
|
||||
}
|
||||
|
||||
#[cfg(not(any(feature = "std", test)))]
|
||||
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: de::Error,
|
||||
@ -3028,6 +3019,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
fn test_date_format() {
|
||||
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");
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
//! ISO 8601 date and time without timezone.
|
||||
|
||||
#[cfg(any(feature = "alloc", feature = "std", test))]
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
use core::borrow::Borrow;
|
||||
use core::fmt::Write;
|
||||
use core::ops::{Add, AddAssign, Sub, SubAssign};
|
||||
@ -12,7 +12,7 @@ use core::{fmt, str};
|
||||
#[cfg(feature = "rkyv")]
|
||||
use rkyv::{Archive, Deserialize, Serialize};
|
||||
|
||||
#[cfg(any(feature = "alloc", feature = "std", test))]
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
use crate::format::DelayedFormat;
|
||||
use crate::format::{parse, parse_and_remainder, ParseError, ParseResult, Parsed, StrftimeItems};
|
||||
use crate::format::{Fixed, Item, Numeric, Pad};
|
||||
@ -851,7 +851,7 @@ impl NaiveDateTime {
|
||||
/// # let dt = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap().and_hms_opt(23, 56, 4).unwrap();
|
||||
/// assert_eq!(format!("{}", dt.format_with_items(fmt)), "2015-09-05 23:56:04");
|
||||
/// ```
|
||||
#[cfg(any(feature = "alloc", feature = "std", test))]
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))]
|
||||
#[inline]
|
||||
#[must_use]
|
||||
@ -895,7 +895,7 @@ impl NaiveDateTime {
|
||||
/// assert_eq!(format!("{}", dt.format("%Y-%m-%d %H:%M:%S")), "2015-09-05 23:56:04");
|
||||
/// assert_eq!(format!("{}", dt.format("around %l %p on %b %-d")), "around 11 PM on Sep 5");
|
||||
/// ```
|
||||
#[cfg(any(feature = "alloc", feature = "std", test))]
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))]
|
||||
#[inline]
|
||||
#[must_use]
|
||||
|
@ -17,10 +17,11 @@ fn test_datetime_from_timestamp_millis() {
|
||||
(2034061609000, "2034-06-16 09:06:49.000000000"),
|
||||
];
|
||||
|
||||
for (timestamp_millis, formatted) in valid_map.iter().copied() {
|
||||
for (timestamp_millis, _formatted) in valid_map.iter().copied() {
|
||||
let naive_datetime = NaiveDateTime::from_timestamp_millis(timestamp_millis);
|
||||
assert_eq!(timestamp_millis, naive_datetime.unwrap().timestamp_millis());
|
||||
assert_eq!(naive_datetime.unwrap().format("%F %T%.9f").to_string(), formatted);
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
assert_eq!(naive_datetime.unwrap().format("%F %T%.9f").to_string(), _formatted);
|
||||
}
|
||||
|
||||
let invalid = [i64::MAX, i64::MIN];
|
||||
@ -54,10 +55,11 @@ fn test_datetime_from_timestamp_micros() {
|
||||
(2034061609000000, "2034-06-16 09:06:49.000000000"),
|
||||
];
|
||||
|
||||
for (timestamp_micros, formatted) in valid_map.iter().copied() {
|
||||
for (timestamp_micros, _formatted) in valid_map.iter().copied() {
|
||||
let naive_datetime = NaiveDateTime::from_timestamp_micros(timestamp_micros);
|
||||
assert_eq!(timestamp_micros, naive_datetime.unwrap().timestamp_micros());
|
||||
assert_eq!(naive_datetime.unwrap().format("%F %T%.9f").to_string(), formatted);
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
assert_eq!(naive_datetime.unwrap().format("%F %T%.9f").to_string(), _formatted);
|
||||
}
|
||||
|
||||
let invalid = [i64::MAX, i64::MIN];
|
||||
@ -327,6 +329,7 @@ fn test_datetime_parse_from_str() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
fn test_datetime_format() {
|
||||
let dt = NaiveDate::from_ymd_opt(2010, 9, 8).unwrap().and_hms_milli_opt(7, 6, 54, 321).unwrap();
|
||||
assert_eq!(dt.format("%c").to_string(), "Wed Sep 8 07:06:54 2010");
|
||||
|
@ -158,11 +158,13 @@ mod tests {
|
||||
assert_eq!(minweek.year(), internals::MIN_YEAR);
|
||||
assert_eq!(minweek.week(), 1);
|
||||
assert_eq!(minweek.week0(), 0);
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
assert_eq!(format!("{:?}", minweek), NaiveDate::MIN.format("%G-W%V").to_string());
|
||||
|
||||
assert_eq!(maxweek.year(), internals::MAX_YEAR + 1);
|
||||
assert_eq!(maxweek.week(), 1);
|
||||
assert_eq!(maxweek.week0(), 0);
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
assert_eq!(format!("{:?}", maxweek), NaiveDate::MAX.format("%G-W%V").to_string());
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
//! ISO 8601 time without timezone.
|
||||
|
||||
#[cfg(any(feature = "alloc", feature = "std", test))]
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
use core::borrow::Borrow;
|
||||
use core::ops::{Add, AddAssign, Sub, SubAssign};
|
||||
use core::{fmt, str};
|
||||
@ -11,7 +11,7 @@ use core::{fmt, str};
|
||||
#[cfg(feature = "rkyv")]
|
||||
use rkyv::{Archive, Deserialize, Serialize};
|
||||
|
||||
#[cfg(any(feature = "alloc", feature = "std", test))]
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
use crate::format::DelayedFormat;
|
||||
use crate::format::{
|
||||
parse, parse_and_remainder, write_hundreds, Fixed, Item, Numeric, Pad, ParseError, ParseResult,
|
||||
@ -96,7 +96,8 @@ mod tests;
|
||||
/// (short for [`NaiveTime::overflowing_add_signed`](#method.overflowing_add_signed)):
|
||||
///
|
||||
/// - `03:00:00 + 1s = 03:00:01`.
|
||||
/// - `03:00:59 + 60s = 03:02:00`.
|
||||
/// - `03:00:59 + 60s = 03:01:59`.
|
||||
/// - `03:00:59 + 61s = 03:02:00`.
|
||||
/// - `03:00:59 + 1s = 03:01:00`.
|
||||
/// - `03:00:60 + 1s = 03:01:00`.
|
||||
/// Note that the sum is identical to the previous.
|
||||
@ -741,7 +742,7 @@ impl NaiveTime {
|
||||
/// # let t = NaiveTime::from_hms_opt(23, 56, 4).unwrap();
|
||||
/// assert_eq!(format!("{}", t.format_with_items(fmt)), "23:56:04");
|
||||
/// ```
|
||||
#[cfg(any(feature = "alloc", feature = "std", test))]
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))]
|
||||
#[inline]
|
||||
#[must_use]
|
||||
@ -787,7 +788,7 @@ impl NaiveTime {
|
||||
/// assert_eq!(format!("{}", t.format("%H:%M:%S%.6f")), "23:56:04.012345");
|
||||
/// assert_eq!(format!("{}", t.format("%-I:%M %p")), "11:56 PM");
|
||||
/// ```
|
||||
#[cfg(any(feature = "alloc", feature = "std", test))]
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))]
|
||||
#[inline]
|
||||
#[must_use]
|
||||
|
@ -346,6 +346,7 @@ fn test_time_parse_from_str() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "alloc", feature = "std"))]
|
||||
fn test_time_format() {
|
||||
let t = NaiveTime::from_hms_nano_opt(3, 5, 7, 98765432).unwrap();
|
||||
assert_eq!(t.format("%H,%k,%I,%l,%P,%p").to_string(), "03, 3,03, 3,am,AM");
|
||||
|
@ -5,15 +5,16 @@
|
||||
|
||||
use core::fmt;
|
||||
use core::ops::{Add, Sub};
|
||||
use core::str::FromStr;
|
||||
|
||||
#[cfg(feature = "rkyv")]
|
||||
use rkyv::{Archive, Deserialize, Serialize};
|
||||
|
||||
use super::{LocalResult, Offset, TimeZone};
|
||||
use crate::format::{scan, OUT_OF_RANGE};
|
||||
use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime};
|
||||
use crate::time_delta::TimeDelta;
|
||||
use crate::DateTime;
|
||||
use crate::Timelike;
|
||||
use crate::{DateTime, ParseError, Timelike};
|
||||
|
||||
/// The time zone with fixed offset, from UTC-23:59:59 to UTC+23:59:59.
|
||||
///
|
||||
@ -113,6 +114,15 @@ impl FixedOffset {
|
||||
}
|
||||
}
|
||||
|
||||
/// Parsing a `str` into a `FixedOffset` uses the format [`%z`](crate::format::strftime).
|
||||
impl FromStr for FixedOffset {
|
||||
type Err = ParseError;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let (_, offset) = scan::timezone_offset(s, scan::consume_colon_maybe, false, false, true)?;
|
||||
Self::east_opt(offset).ok_or(OUT_OF_RANGE)
|
||||
}
|
||||
}
|
||||
|
||||
impl TimeZone for FixedOffset {
|
||||
type Offset = FixedOffset;
|
||||
|
||||
@ -246,6 +256,7 @@ impl<Tz: TimeZone> Sub<FixedOffset> for DateTime<Tz> {
|
||||
mod tests {
|
||||
use super::FixedOffset;
|
||||
use crate::offset::TimeZone;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[test]
|
||||
fn test_date_extreme_offset() {
|
||||
@ -292,4 +303,14 @@ mod tests {
|
||||
"2012-03-04T05:06:07-23:59:59".to_string()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_offset() {
|
||||
let offset = FixedOffset::from_str("-0500").unwrap();
|
||||
assert_eq!(offset.local_minus_utc, -5 * 3600);
|
||||
let offset = FixedOffset::from_str("-08:00").unwrap();
|
||||
assert_eq!(offset.local_minus_utc, -8 * 3600);
|
||||
let offset = FixedOffset::from_str("+06:30").unwrap();
|
||||
assert_eq!(offset.local_minus_utc, (6 * 3600) + 1800);
|
||||
}
|
||||
}
|
||||
|
@ -486,29 +486,49 @@ struct TimeZoneName {
|
||||
|
||||
impl TimeZoneName {
|
||||
/// Construct a time zone name
|
||||
///
|
||||
/// Note: Converts `−` MINUS SIGN (U+2212) to `-` HYPHEN-MINUS (U+2D).
|
||||
/// Multi-byte MINUS SIGN is allowed in [ISO 8601 / RFC 3339]. But
|
||||
/// working with single-byte HYPHEN-MINUS is easier and more common.
|
||||
///
|
||||
/// [ISO 8601 / RFC 3339]: https://en.wikipedia.org/w/index.php?title=ISO_8601&oldid=1114309368#Time_offsets_from_UTC
|
||||
fn new(input: &[u8]) -> Result<Self, Error> {
|
||||
let len = input.len();
|
||||
let s = match str::from_utf8(input) {
|
||||
Ok(s) => s,
|
||||
Err(_err) => return Err(Error::LocalTimeType("invalid UTF-8")),
|
||||
};
|
||||
|
||||
if !(3..=7).contains(&len) {
|
||||
if !(3..=7).contains(&s.chars().count()) {
|
||||
return Err(Error::LocalTimeType(
|
||||
"time zone name must have between 3 and 7 characters",
|
||||
));
|
||||
}
|
||||
|
||||
let mut bytes = [0; 8];
|
||||
bytes[0] = input.len() as u8;
|
||||
|
||||
let mut i = 0;
|
||||
while i < len {
|
||||
let b = input[i];
|
||||
match b {
|
||||
b'0'..=b'9' | b'A'..=b'Z' | b'a'..=b'z' | b'+' | b'-' => {}
|
||||
let mut copied = 0;
|
||||
for (i, c) in s.chars().enumerate() {
|
||||
match c {
|
||||
'0'..='9' | 'A'..='Z' | 'a'..='z'
|
||||
// ISO 8601 / RFC 3339 proscribes use of `+` PLUS SIGN (U+2B)
|
||||
// in timezone
|
||||
| '+'
|
||||
// ISO 8601 / RFC 3339 allows use of `-` HYPHEN-MINUS (U+2D)
|
||||
// in timezone
|
||||
| '-' => {
|
||||
bytes[i + 1] = c as u8;
|
||||
}
|
||||
// ISO 8601 / RFC 3339 recommends the use of
|
||||
// `−` MINUS SIGN (U+2212) in timezone.
|
||||
// But replace with single-byte `-` HYPHEN-MINUS (U+2D) for
|
||||
// easier byte <-> char conversions later on.
|
||||
| '−' => {
|
||||
bytes[i + 1] = b'-';
|
||||
}
|
||||
_ => return Err(Error::LocalTimeType("invalid characters in time zone name")),
|
||||
}
|
||||
|
||||
bytes[i + 1] = b;
|
||||
i += 1;
|
||||
copied += 1;
|
||||
}
|
||||
bytes[0] = copied as u8;
|
||||
|
||||
Ok(Self { bytes })
|
||||
}
|
||||
@ -741,20 +761,63 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tz_ascii_str() -> Result<(), Error> {
|
||||
assert!(matches!(TimeZoneName::new(b""), Err(Error::LocalTimeType(_))));
|
||||
assert!(matches!(TimeZoneName::new(b"1"), Err(Error::LocalTimeType(_))));
|
||||
assert!(matches!(TimeZoneName::new(b"12"), Err(Error::LocalTimeType(_))));
|
||||
assert_eq!(TimeZoneName::new(b"123")?.as_bytes(), b"123");
|
||||
assert_eq!(TimeZoneName::new(b"1234")?.as_bytes(), b"1234");
|
||||
assert_eq!(TimeZoneName::new(b"12345")?.as_bytes(), b"12345");
|
||||
assert_eq!(TimeZoneName::new(b"123456")?.as_bytes(), b"123456");
|
||||
assert_eq!(TimeZoneName::new(b"1234567")?.as_bytes(), b"1234567");
|
||||
assert!(matches!(TimeZoneName::new(b"12345678"), Err(Error::LocalTimeType(_))));
|
||||
assert!(matches!(TimeZoneName::new(b"123456789"), Err(Error::LocalTimeType(_))));
|
||||
assert!(matches!(TimeZoneName::new(b"1234567890"), Err(Error::LocalTimeType(_))));
|
||||
|
||||
assert!(matches!(TimeZoneName::new(b"123\0\0\0"), Err(Error::LocalTimeType(_))));
|
||||
fn test_timezonename_new() -> Result<(), Error> {
|
||||
// expect Error::LocalTimeType()
|
||||
const INPUT_ERR: &[&str] = &[
|
||||
"",
|
||||
"1",
|
||||
"+",
|
||||
"-",
|
||||
"−", // MINUS SIGN (U+2212)
|
||||
"12",
|
||||
"--",
|
||||
"−−", // MINUS SIGN (U+2212)
|
||||
"AB",
|
||||
"ab",
|
||||
"12345678",
|
||||
"ABCDEFGH",
|
||||
"123456789",
|
||||
"1234567890",
|
||||
"--------",
|
||||
"123\0\0\0",
|
||||
"\0\0\0",
|
||||
"\x00123",
|
||||
"123\0",
|
||||
];
|
||||
for input_ in INPUT_ERR.iter() {
|
||||
eprintln!("TimeZoneName::new({:?}) (expect Error::LocalTimeType)", input_);
|
||||
let input_ = input_.as_bytes();
|
||||
let err = TimeZoneName::new(input_);
|
||||
eprintln!("err = {:?}", err);
|
||||
assert!(matches!(err, Err(Error::LocalTimeType(_))));
|
||||
}
|
||||
// expect Ok
|
||||
const INPUT_OK_EXPECT: &[(&str, &str)] = &[
|
||||
("123", "123"),
|
||||
("abc", "abc"),
|
||||
("ABC", "ABC"),
|
||||
("1234", "1234"),
|
||||
("12345", "12345"),
|
||||
("123456", "123456"),
|
||||
("1234567", "1234567"),
|
||||
("+1234", "+1234"),
|
||||
("+1234", "+1234"),
|
||||
("-1234", "-1234"),
|
||||
("−1234", "-1234"), // MINUS SIGN (U+2212) to HYPHEN-MINUS (U+002D)
|
||||
// Ok nonsense
|
||||
("+++", "+++"),
|
||||
("-----", "-----"),
|
||||
("−−−", "---"), // MINUS SIGN (U+2212) to HYPHEN-MINUS (U+002D)
|
||||
("−−−−−−−", "-------"), // MINUS SIGN (U+2212) to HYPHEN-MINUS (U+002D)
|
||||
];
|
||||
for (input_, expect) in INPUT_OK_EXPECT.iter() {
|
||||
eprintln!("TimeZoneName::new({:?})", input_);
|
||||
let output = TimeZoneName::new(input_.as_bytes());
|
||||
match output {
|
||||
Ok(output) => assert_eq!(output.as_bytes(), expect.as_bytes()),
|
||||
Err(error) => panic!("Failed: input {:?}, error {}", input_, error),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -100,11 +100,11 @@ const fn span_for_digits(digits: u16) -> u32 {
|
||||
/// will also fail if the `TimeDelta` is bigger than the timestamp.
|
||||
pub trait DurationRound: Sized {
|
||||
/// Error that can occur in rounding or truncating
|
||||
#[cfg(any(feature = "std", test))]
|
||||
#[cfg(any(feature = "std"))]
|
||||
type Err: std::error::Error;
|
||||
|
||||
/// Error that can occur in rounding or truncating
|
||||
#[cfg(not(any(feature = "std", test)))]
|
||||
#[cfg(not(any(feature = "std")))]
|
||||
type Err: fmt::Debug + fmt::Display;
|
||||
|
||||
/// Return a copy rounded by TimeDelta.
|
||||
@ -299,7 +299,7 @@ impl fmt::Display for RoundingError {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "std", test))]
|
||||
#[cfg(any(feature = "std"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
|
||||
impl std::error::Error for RoundingError {
|
||||
#[allow(deprecated)]
|
||||
|
@ -1,14 +1,8 @@
|
||||
#[cfg(unix)]
|
||||
use chrono::offset::TimeZone;
|
||||
#[cfg(unix)]
|
||||
use chrono::Local;
|
||||
#[cfg(unix)]
|
||||
use chrono::{Datelike, NaiveDate, NaiveDateTime, Timelike};
|
||||
#![cfg(all(unix, feature = "clock", feature = "std"))]
|
||||
|
||||
#[cfg(unix)]
|
||||
use std::{path, process};
|
||||
use chrono::{Datelike, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Timelike};
|
||||
use std::{path, process, thread};
|
||||
|
||||
#[cfg(unix)]
|
||||
fn verify_against_date_command_local(path: &'static str, dt: NaiveDateTime) {
|
||||
let output = process::Command::new(path)
|
||||
.arg("-d")
|
||||
@ -64,7 +58,6 @@ const DATE_PATH: &str = "/usr/bin/date";
|
||||
const DATE_PATH: &str = "/opt/freeware/bin/date";
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg(unix)]
|
||||
/// test helper to sanity check the date command behaves as expected
|
||||
/// asserts the command succeeded
|
||||
fn assert_run_date_version() {
|
||||
@ -82,7 +75,6 @@ fn assert_run_date_version() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn try_verify_against_date_command() {
|
||||
if !path::Path::new(DATE_PATH).exists() {
|
||||
eprintln!("date command {:?} not found, skipping", DATE_PATH);
|
||||
@ -90,31 +82,26 @@ fn try_verify_against_date_command() {
|
||||
}
|
||||
assert_run_date_version();
|
||||
|
||||
let mut date = NaiveDate::from_ymd_opt(1975, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap();
|
||||
|
||||
eprintln!(
|
||||
"Run command {:?} for every hour from {} to 2077, skipping some years...",
|
||||
"Run command {:?} for every hour from 1975 to 2077, skipping some years...",
|
||||
DATE_PATH,
|
||||
date.year()
|
||||
);
|
||||
let mut count: u64 = 0;
|
||||
let mut year_at = date.year();
|
||||
while date.year() < 2078 {
|
||||
if (1975..=1977).contains(&date.year())
|
||||
|| (2020..=2022).contains(&date.year())
|
||||
|| (2073..=2077).contains(&date.year())
|
||||
{
|
||||
if date.year() != year_at {
|
||||
eprintln!("at year {}...", date.year());
|
||||
year_at = date.year();
|
||||
}
|
||||
verify_against_date_command_local(DATE_PATH, date);
|
||||
count += 1;
|
||||
}
|
||||
|
||||
date += chrono::TimeDelta::hours(1);
|
||||
let mut children = vec![];
|
||||
for year in [1975, 1976, 1977, 2020, 2021, 2022, 2073, 2074, 2075, 2076, 2077].iter() {
|
||||
children.push(thread::spawn(|| {
|
||||
let mut date = NaiveDate::from_ymd_opt(*year, 1, 1).unwrap().and_time(NaiveTime::MIN);
|
||||
let end = NaiveDate::from_ymd_opt(*year + 1, 1, 1).unwrap().and_time(NaiveTime::MIN);
|
||||
while date <= end {
|
||||
verify_against_date_command_local(DATE_PATH, date);
|
||||
date += chrono::TimeDelta::hours(1);
|
||||
}
|
||||
}));
|
||||
}
|
||||
for child in children {
|
||||
// Wait for the thread to finish. Returns a result.
|
||||
let _ = child.join();
|
||||
}
|
||||
eprintln!("Command {:?} was run {} times", DATE_PATH, count);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
|
@ -8,6 +8,7 @@
|
||||
#![cfg(all(
|
||||
target_arch = "wasm32",
|
||||
feature = "wasmbind",
|
||||
feature = "clock",
|
||||
not(any(target_os = "emscripten", target_os = "wasi"))
|
||||
))]
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user