Skip to content

Commit

Permalink
Merge branch '0.4.x'
Browse files Browse the repository at this point in the history
  • Loading branch information
pitdicker committed Jul 17, 2023
2 parents 121f5c7 + 3993c02 commit 3df0a0d
Show file tree
Hide file tree
Showing 7 changed files with 1,086 additions and 939 deletions.
2 changes: 2 additions & 0 deletions src/datetime/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,7 @@ fn ymdhms_milli(

// local helper function to easily create a DateTime<FixedOffset>
#[allow(clippy::too_many_arguments)]
#[cfg(any(feature = "alloc", feature = "std"))]
fn ymdhms_micro(
fixedoffset: &FixedOffset,
year: i32,
Expand All @@ -291,6 +292,7 @@ fn ymdhms_micro(

// local helper function to easily create a DateTime<FixedOffset>
#[allow(clippy::too_many_arguments)]
#[cfg(any(feature = "alloc", feature = "std"))]
fn ymdhms_nano(
fixedoffset: &FixedOffset,
year: i32,
Expand Down
1,801 changes: 1,000 additions & 801 deletions src/format/parse.rs

Large diffs are not rendered by default.

89 changes: 45 additions & 44 deletions src/format/strftime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,14 @@ The following specifiers are available both to formatting and parsing.
| | | |
| `%M` | `34` | Minute number (00--59), zero-padded to 2 digits. |
| `%S` | `60` | Second number (00--60), zero-padded to 2 digits. [^4] |
| `%f` | `026490000` | The fractional seconds (in nanoseconds) since last whole second. [^7] |
| `%.f` | `.026490`| Similar to `.%f` but left-aligned. These all consume the leading dot. [^7] |
| `%.3f`| `.026` | Similar to `.%f` but left-aligned but fixed to a length of 3. [^7] |
| `%.6f`| `.026490` | Similar to `.%f` but left-aligned but fixed to a length of 6. [^7] |
| `%.9f`| `.026490000` | Similar to `.%f` but left-aligned but fixed to a length of 9. [^7] |
| `%3f` | `026` | Similar to `%.3f` but without the leading dot. [^7] |
| `%6f` | `026490` | Similar to `%.6f` but without the leading dot. [^7] |
| `%9f` | `026490000` | Similar to `%.9f` but without the leading dot. [^7] |
| `%f` | `26490000` | Number of nanoseconds since last whole second. [^7] |
| `%.f` | `.026490`| Decimal fraction of a second. Consumes the leading dot. [^7] |
| `%.3f`| `.026` | Decimal fraction of a second with a fixed length of 3. |
| `%.6f`| `.026490` | Decimal fraction of a second with a fixed length of 6. |
| `%.9f`| `.026490000` | Decimal fraction of a second with a fixed length of 9. |
| `%3f` | `026` | Decimal fraction of a second like `%.3f` but without the leading dot. |
| `%6f` | `026490` | Decimal fraction of a second like `%.6f` but without the leading dot. |
| `%9f` | `026490000` | Decimal fraction of a second like `%.9f` but without the leading dot. |
| | | |
| `%R` | `00:34` | Hour-minute format. Same as `%H:%M`. |
| `%T` | `00:34:60` | Hour-minute-second format. Same as `%H:%M:%S`. |
Expand Down Expand Up @@ -132,36 +132,12 @@ Notes:
For the purpose of Chrono, it only accounts for non-leap seconds
so it slightly differs from ISO C `strftime` behavior.
[^7]: `%f`, `%.f`, `%.3f`, `%.6f`, `%.9f`, `%3f`, `%6f`, `%9f`:
[^7]: `%f`, `%.f`:
<br>
The default `%f` is right-aligned and always zero-padded to 9 digits
for the compatibility with glibc and others,
so it always counts the number of nanoseconds since the last whole second.
E.g. 7ms after the last second will print `007000000`,
and parsing `7000000` will yield the same.
<br>
<br>
The variant `%.f` is left-aligned and print 0, 3, 6 or 9 fractional digits
according to the precision.
E.g. 70ms after the last second under `%.f` will print `.070` (note: not `.07`),
and parsing `.07`, `.070000` etc. will yield the same.
Note that they can print or read nothing if the fractional part is zero or
the next character is not `.`.
<br>
<br>
The variant `%.3f`, `%.6f` and `%.9f` are left-aligned and print 3, 6 or 9 fractional digits
according to the number preceding `f`.
E.g. 70ms after the last second under `%.3f` will print `.070` (note: not `.07`),
and parsing `.07`, `.070000` etc. will yield the same.
Note that they can read nothing if the fractional part is zero or
the next character is not `.` however will print with the specified length.
<br>
<br>
The variant `%3f`, `%6f` and `%9f` are left-aligned and print 3, 6 or 9 fractional digits
according to the number preceding `f`, but without the leading dot.
E.g. 70ms after the last second under `%3f` will print `070` (note: not `07`),
and parsing `07`, `070000` etc. will yield the same.
Note that they can read nothing if the fractional part is zero.
`%f` and `%.f` are notably different formatting specifiers.<br>
`%f` counts the number of nanoseconds since the last whole second, while `%.f` is a fraction of a
second.<br>
Example: 7μs is formatted as `7000` with `%f`, and formatted as `.000007` with `%.f`.
[^8]: `%Z`:
Since `chrono` is not aware of timezones beyond their offsets, this specifier
Expand Down Expand Up @@ -580,12 +556,18 @@ mod tests {
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("😽😽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(" 😽 😽"),
[Space(" "), Literal("😽"), Space(" "), Literal("😽")]
);
assert_eq!(
parse_and_collect(" 😽 😽 "),
[Space(" "), Literal("😽"), Space(" "), Literal("😽"), Space(" ")]
Expand All @@ -600,18 +582,34 @@ mod tests {
);
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(" 😽😽 "),
[Space(" "), Literal("😽😽"), Space(" ")]
);
assert_eq!(parse_and_collect(" 😽😽 "), [Space(" "), Literal("😽😽"), Space(" ")]);
assert_eq!(
parse_and_collect(" 😽 😽😽 "),
[Space(" "), Literal("😽"), Space(" "), Literal("😽😽"), Space(" ")]
);
assert_eq!(
parse_and_collect(" 😽 😽はい😽 ハンバーガー"),
[Space(" "), Literal("😽"), Space(" "), Literal("😽はい😽"), Space(" "), Literal("ハンバーガー")]
[
Space(" "),
Literal("😽"),
Space(" "),
Literal("😽はい😽"),
Space(" "),
Literal("ハンバーガー")
]
);
assert_eq!(
parse_and_collect("%%😽%%😽"),
[Literal("%"), Literal("😽"), Literal("%"), Literal("😽")]
);
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%%😽"), [Literal("100"), Literal("%"), Literal("😽")]);
Expand Down Expand Up @@ -657,7 +655,10 @@ mod tests {
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("%#z"),
[internal_fixed(InternalInternal::TimezoneOffsetPermissive)]
);
assert_eq!(parse_and_collect("%#m"), [Item::Error]);
}

Expand Down
1 change: 0 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,6 @@
#![deny(missing_docs)]
#![deny(missing_debug_implementations)]
#![warn(unreachable_pub)]
#![deny(dead_code)]
#![deny(clippy::tests_outside_test_module)]
#![cfg_attr(not(any(feature = "std", test)), no_std)]
#![cfg_attr(docsrs, feature(doc_cfg))]
Expand Down
9 changes: 7 additions & 2 deletions src/naive/date.rs
Original file line number Diff line number Diff line change
Expand Up @@ -284,9 +284,14 @@ impl NaiveDate {
/// assert!(from_ymd_opt(-400000, 1, 1).is_none());
/// ```
#[must_use]
pub fn from_ymd_opt(year: i32, month: u32, day: u32) -> Option<NaiveDate> {
pub const fn from_ymd_opt(year: i32, month: u32, day: u32) -> Option<NaiveDate> {
let flags = YearFlags::from_year(year);
NaiveDate::from_mdf(year, Mdf::new(month, day, flags)?)

if let Some(mdf) = Mdf::new(month, day, flags) {
NaiveDate::from_mdf(year, mdf)
} else {
None
}
}

/// Makes a new `NaiveDate` from the [ordinal date](#ordinal-date)
Expand Down
117 changes: 29 additions & 88 deletions src/offset/local/tz_info/timezone.rs
Original file line number Diff line number Diff line change
Expand Up @@ -487,48 +487,33 @@ 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
/// man tzfile(5):
/// Time zone designations should consist of at least three (3) and no more than six (6) ASCII
/// characters from the set of alphanumerics, “-”, and “+”. This is for compatibility with
/// POSIX requirements for time zone abbreviations.
fn new(input: &[u8]) -> Result<Self, Error> {
let s = match str::from_utf8(input) {
Ok(s) => s,
Err(_err) => return Err(Error::LocalTimeType("invalid UTF-8")),
};
let len = input.len();

if !(3..=7).contains(&s.chars().count()) {
if !(3..=7).contains(&len) {
return Err(Error::LocalTimeType(
"time zone name must have between 3 and 7 characters",
));
}

let mut bytes = [0; 8];
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'-';
}
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'-' => {}
_ => return Err(Error::LocalTimeType("invalid characters in time zone name")),
}
copied += 1;

bytes[i + 1] = b;
i += 1;
}
bytes[0] = copied as u8;

Ok(Self { bytes })
}
Expand Down Expand Up @@ -761,63 +746,19 @@ mod tests {
}

#[test]
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),
}
}
fn test_tz_ascii_str() -> Result<(), Error> {
assert!(matches!(TimeZoneName::new(b""), Err(Error::LocalTimeType(_))));
assert!(matches!(TimeZoneName::new(b"A"), Err(Error::LocalTimeType(_))));
assert!(matches!(TimeZoneName::new(b"AB"), Err(Error::LocalTimeType(_))));
assert_eq!(TimeZoneName::new(b"CET")?.as_bytes(), b"CET");
assert_eq!(TimeZoneName::new(b"CHADT")?.as_bytes(), b"CHADT");
assert_eq!(TimeZoneName::new(b"abcdefg")?.as_bytes(), b"abcdefg");
assert_eq!(TimeZoneName::new(b"UTC+02")?.as_bytes(), b"UTC+02");
assert_eq!(TimeZoneName::new(b"-1230")?.as_bytes(), b"-1230");
assert!(matches!(TimeZoneName::new("−0330".as_bytes()), Err(Error::LocalTimeType(_)))); // MINUS SIGN (U+2212)
assert!(matches!(TimeZoneName::new(b"\x00123"), Err(Error::LocalTimeType(_))));
assert!(matches!(TimeZoneName::new(b"12345678"), Err(Error::LocalTimeType(_))));
assert!(matches!(TimeZoneName::new(b"GMT\0\0\0"), Err(Error::LocalTimeType(_))));

Ok(())
}
Expand Down
6 changes: 3 additions & 3 deletions src/round.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"))]
#[cfg(feature = "std")]
type Err: std::error::Error;

/// Error that can occur in rounding or truncating
#[cfg(not(any(feature = "std")))]
#[cfg(not(feature = "std"))]
type Err: fmt::Debug + fmt::Display;

/// Return a copy rounded by TimeDelta.
Expand Down Expand Up @@ -299,7 +299,7 @@ impl fmt::Display for RoundingError {
}
}

#[cfg(any(feature = "std"))]
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
impl std::error::Error for RoundingError {
#[allow(deprecated)]
Expand Down

0 comments on commit 3df0a0d

Please sign in to comment.