зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1806766 - Update time to 0.3.17. r=emilio,supply-chain-reviewers
Differential Revision: https://phabricator.services.mozilla.com/D165570
This commit is contained in:
Родитель
db3417cdda
Коммит
c2e9fb5416
|
@ -848,7 +848,7 @@ version = "0.16.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb"
|
||||
dependencies = [
|
||||
"time 0.3.9",
|
||||
"time 0.3.17",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
|
@ -3852,15 +3852,6 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_threads"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc"
|
||||
version = "0.2.7"
|
||||
|
@ -4198,7 +4189,7 @@ dependencies = [
|
|||
"indexmap",
|
||||
"line-wrap",
|
||||
"serde",
|
||||
"time 0.3.9",
|
||||
"time 0.3.17",
|
||||
"xml-rs",
|
||||
]
|
||||
|
||||
|
@ -5318,21 +5309,30 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.9"
|
||||
version = "0.3.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2702e08a7a860f005826c6815dcac101b19b5eb330c27fe4a5928fec1d20ddd"
|
||||
checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"libc",
|
||||
"num_threads",
|
||||
"serde",
|
||||
"time-core",
|
||||
"time-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.4"
|
||||
name = "time-core"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792"
|
||||
checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd"
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2"
|
||||
dependencies = [
|
||||
"time-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinystr"
|
||||
|
@ -6107,7 +6107,7 @@ dependencies = [
|
|||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"time 0.3.9",
|
||||
"time 0.3.17",
|
||||
"tokio 1.17.0",
|
||||
"tokio-stream",
|
||||
"unicode-segmentation",
|
||||
|
|
|
@ -1798,6 +1798,21 @@ who = "Mike Hommey <mh+mozilla@glandium.org>"
|
|||
criteria = "safe-to-deploy"
|
||||
delta = "0.1.44 -> 0.1.45"
|
||||
|
||||
[[audits.time]]
|
||||
who = "Mike Hommey <mh+mozilla@glandium.org>"
|
||||
criteria = "safe-to-run"
|
||||
delta = "0.3.9 -> 0.3.17"
|
||||
|
||||
[[audits.time-core]]
|
||||
who = "Mike Hommey <mh+mozilla@glandium.org>"
|
||||
criteria = "safe-to-run"
|
||||
version = "0.1.0"
|
||||
|
||||
[[audits.time-macros]]
|
||||
who = "Mike Hommey <mh+mozilla@glandium.org>"
|
||||
criteria = "safe-to-run"
|
||||
delta = "0.2.4 -> 0.2.6"
|
||||
|
||||
[[audits.tinystr]]
|
||||
who = "Zibi Braniecki <zibi@unicode.org>"
|
||||
criteria = "safe-to-deploy"
|
||||
|
|
|
@ -889,10 +889,6 @@ criteria = "safe-to-deploy"
|
|||
version = "1.13.1"
|
||||
criteria = "safe-to-deploy"
|
||||
|
||||
[[exemptions.num_threads]]
|
||||
version = "0.1.6"
|
||||
criteria = "safe-to-run"
|
||||
|
||||
[[exemptions.objc]]
|
||||
version = "0.2.7"
|
||||
criteria = "safe-to-deploy"
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
{"files":{"Cargo.toml":"ed1dbfb8f9eb836549858fb0ca704eddaa480cbe576e8dd50ebca1529dca21b3","LICENSE-Apache":"c69b72788ec261765e460532016b4bb78372f238e449b5e70e3268ec1e18fd15","LICENSE-MIT":"b4bf94a9fceb8846320fda938ee53fc16b506572609da5cf1d2d289a5597a8f8","src/apple.rs":"018e729ea67e8e17428d2e8d93f28ed0f7e7c506fcf78b56f56113235ce7dfcf","src/freebsd.rs":"683636294a62d6b958a5de800b52ddfea609234921e0583906272aacc71e18e5","src/imp.rs":"8cc9d07f05b0aa70e9997648abce7f79bd758c6fc76040d1c8f7beb7bf551e9d","src/lib.rs":"c53382612069b9552846414d1508cbb1401c500a4f34e21addd336001ebd8b7e","src/linux.rs":"67e02ecd105b8a421227bf72814e8388a4f77df1d8a44b8902bc8926f7e6698c"},"package":"2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44"}
|
|
@ -1,36 +0,0 @@
|
|||
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
|
||||
#
|
||||
# When uploading crates to the registry Cargo will automatically
|
||||
# "normalize" Cargo.toml files for maximal compatibility
|
||||
# with all versions of Cargo and also rewrite `path` dependencies
|
||||
# to registry (e.g., crates.io) dependencies.
|
||||
#
|
||||
# If you are reading this file be aware that the original Cargo.toml
|
||||
# will likely look very different (and much more reasonable).
|
||||
# See Cargo.toml.orig for the original contents.
|
||||
|
||||
[package]
|
||||
name = "num_threads"
|
||||
version = "0.1.6"
|
||||
authors = ["Jacob Pratt <open-source@jhpratt.dev>"]
|
||||
include = [
|
||||
"src/**/*",
|
||||
"LICENSE-*",
|
||||
]
|
||||
description = "A minimal library that determines the number of running threads for the current process."
|
||||
categories = [
|
||||
"api-bindings",
|
||||
"hardware-support",
|
||||
"os",
|
||||
]
|
||||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://github.com/jhpratt/num_threads"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
|
||||
[target."cfg(any(target_os = \"macos\", target_os = \"ios\", target_os = \"freebsd\"))".dependencies.libc]
|
||||
version = "0.2.107"
|
|
@ -1,45 +0,0 @@
|
|||
extern crate libc;
|
||||
|
||||
use std::num::NonZeroUsize;
|
||||
|
||||
use self::libc::{kern_return_t, mach_msg_type_number_t, mach_port_t, thread_t};
|
||||
|
||||
// This constant is from
|
||||
// /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/
|
||||
// usr/include/mach/machine/thread_state.h.
|
||||
//
|
||||
// It has not been updated since Apple devices started to support 64-bit ARM (iOS), so it
|
||||
// should be very stable.
|
||||
const THREAD_STATE_MAX: i32 = 1296;
|
||||
#[allow(non_camel_case_types)]
|
||||
// https://github.com/apple/darwin-xnu/blob/a1babec6b135d1f35b2590a1990af3c5c5393479/osfmk/mach/mach_types.defs#L155
|
||||
type task_inspect_t = mach_port_t;
|
||||
#[allow(non_camel_case_types)]
|
||||
// https://github.com/apple/darwin-xnu/blob/a1babec6b135d1f35b2590a1990af3c5c5393479/osfmk/mach/mach_types.defs#L238
|
||||
type thread_array_t = [thread_t; THREAD_STATE_MAX as usize];
|
||||
|
||||
extern "C" {
|
||||
// https://developer.apple.com/documentation/kernel/1537751-task_threads/
|
||||
fn task_threads(
|
||||
target_task: task_inspect_t,
|
||||
act_list: *mut thread_array_t,
|
||||
act_listCnt: *mut mach_msg_type_number_t,
|
||||
) -> kern_return_t;
|
||||
}
|
||||
|
||||
pub(crate) fn num_threads() -> Option<NonZeroUsize> {
|
||||
// http://web.mit.edu/darwin/src/modules/xnu/osfmk/man/task_threads.html
|
||||
let mut thread_state = [0u32; THREAD_STATE_MAX as usize];
|
||||
let mut thread_count = 0;
|
||||
|
||||
// Safety: `mach_task_self` always returns a valid value, `thread_state` is large enough, and
|
||||
// both it and `thread_count` are writable.
|
||||
let result =
|
||||
unsafe { task_threads(libc::mach_task_self(), &mut thread_state, &mut thread_count) };
|
||||
|
||||
if result == libc::KERN_SUCCESS {
|
||||
NonZeroUsize::new(thread_count as usize)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
extern crate libc;
|
||||
|
||||
use std::num::NonZeroUsize;
|
||||
use std::{mem, ptr};
|
||||
|
||||
pub(crate) fn num_threads() -> Option<NonZeroUsize> {
|
||||
// Safety: `sysctl` and `getpid` are both thread-safe.
|
||||
// `kip` is only accessed if sysctl() succeeds and agrees with the expected size,
|
||||
// and the data only trusted if both its embedded size and pid match expectations
|
||||
unsafe {
|
||||
let pid = libc::getpid();
|
||||
let mib: [libc::c_int; 4] = [libc::CTL_KERN, libc::KERN_PROC, libc::KERN_PROC_PID, pid];
|
||||
let mut kip: libc::kinfo_proc = mem::zeroed();
|
||||
let expected_kip_len = mem::size_of_val(&kip);
|
||||
let mut kip_len = expected_kip_len;
|
||||
|
||||
let ret = libc::sysctl(
|
||||
mib.as_ptr(),
|
||||
mib.len() as u32,
|
||||
&mut kip as *mut _ as *mut libc::c_void,
|
||||
&mut kip_len,
|
||||
ptr::null(),
|
||||
0,
|
||||
);
|
||||
|
||||
if ret == 0
|
||||
&& kip_len == expected_kip_len
|
||||
&& kip.ki_structsize == expected_kip_len as i32
|
||||
&& kip.ki_pid == pid
|
||||
{
|
||||
NonZeroUsize::new(kip.ki_numthreads as usize)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
//! Fallback if no OS matches.
|
||||
|
||||
use std::num::NonZeroUsize;
|
||||
|
||||
pub(crate) fn num_threads() -> Option<NonZeroUsize> {
|
||||
None
|
||||
}
|
|
@ -1,64 +0,0 @@
|
|||
//! Minimum supported Rust version: 1.28
|
||||
|
||||
use std::num::NonZeroUsize;
|
||||
|
||||
#[cfg_attr(any(target_os = "linux", target_os = "android"), path = "linux.rs")]
|
||||
#[cfg_attr(target_os = "freebsd", path = "freebsd.rs")]
|
||||
#[cfg_attr(any(target_os = "macos", target_os = "ios"), path = "apple.rs")]
|
||||
mod imp;
|
||||
|
||||
/// Obtain the number of threads currently part of the active process. Returns `None` if the number
|
||||
/// of threads cannot be determined.
|
||||
pub fn num_threads() -> Option<NonZeroUsize> {
|
||||
imp::num_threads()
|
||||
}
|
||||
|
||||
/// Determine if the current process is single-threaded. Returns `None` if the number of threads
|
||||
/// cannot be determined.
|
||||
pub fn is_single_threaded() -> Option<bool> {
|
||||
num_threads().map(|n| n.get() == 1)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::num::NonZeroUsize;
|
||||
|
||||
// Run each expression in its own thread.
|
||||
macro_rules! threaded {
|
||||
($first:expr;) => {
|
||||
$first;
|
||||
};
|
||||
($first:expr; $($rest:expr;)*) => {
|
||||
$first;
|
||||
::std::thread::spawn(|| {
|
||||
threaded!($($rest;)*);
|
||||
})
|
||||
.join()
|
||||
.unwrap();
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn num_threads() {
|
||||
threaded! {
|
||||
assert_eq!(super::num_threads().map(NonZeroUsize::get), Some(1));
|
||||
assert_eq!(super::num_threads().map(NonZeroUsize::get), Some(2));
|
||||
assert_eq!(super::num_threads().map(NonZeroUsize::get), Some(3));
|
||||
assert_eq!(super::num_threads().map(NonZeroUsize::get), Some(4));
|
||||
assert_eq!(super::num_threads().map(NonZeroUsize::get), Some(5));
|
||||
assert_eq!(super::num_threads().map(NonZeroUsize::get), Some(6));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_single_threaded() {
|
||||
threaded! {
|
||||
assert_eq!(super::is_single_threaded(), Some(true));
|
||||
assert_eq!(super::is_single_threaded(), Some(false));
|
||||
assert_eq!(super::is_single_threaded(), Some(false));
|
||||
assert_eq!(super::is_single_threaded(), Some(false));
|
||||
assert_eq!(super::is_single_threaded(), Some(false));
|
||||
assert_eq!(super::is_single_threaded(), Some(false));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
use std::fs;
|
||||
use std::num::NonZeroUsize;
|
||||
|
||||
pub(crate) fn num_threads() -> Option<NonZeroUsize> {
|
||||
fs::read_to_string("/proc/self/stat")
|
||||
.ok()
|
||||
.as_ref()
|
||||
// Skip past the pid and (process name) fields
|
||||
.and_then(|stat| stat.rsplit(')').next())
|
||||
// 20th field, less the two we skipped
|
||||
.and_then(|rstat| rstat.split_whitespace().nth(17))
|
||||
.and_then(|num_threads| num_threads.parse::<usize>().ok())
|
||||
.and_then(NonZeroUsize::new)
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
{"files":{"Cargo.toml":"d20a28a1ed2c35aff8a60f495a0ed440e022051d3909b372bf5e262fd62d7b29","LICENSE-Apache":"b8929fea28678da67251fb2daf9438f67503814211051861612441806d8edb05","LICENSE-MIT":"04620bf27e4a643dd47bf27652320c205acdb776c1f9f24bb8c3bfaba10804c5","src/lib.rs":"247b6ac4c2acc97e51552fd7ca1ef7cb2cbc30f8bcbbf5d029553a83a7fe2cc4","src/util.rs":"52c1fbf68b71c3582caf0d9a8255378c6c14a737e2df8d7e6d6603b0eb12ca06"},"package":"2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd"}
|
|
@ -0,0 +1,32 @@
|
|||
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
|
||||
#
|
||||
# When uploading crates to the registry Cargo will automatically
|
||||
# "normalize" Cargo.toml files for maximal compatibility
|
||||
# with all versions of Cargo and also rewrite `path` dependencies
|
||||
# to registry (e.g., crates.io) dependencies.
|
||||
#
|
||||
# If you are reading this file be aware that the original Cargo.toml
|
||||
# will likely look very different (and much more reasonable).
|
||||
# See Cargo.toml.orig for the original contents.
|
||||
|
||||
[package]
|
||||
edition = "2021"
|
||||
rust-version = "1.60.0"
|
||||
name = "time-core"
|
||||
version = "0.1.0"
|
||||
authors = [
|
||||
"Jacob Pratt <open-source@jhpratt.dev>",
|
||||
"Time contributors",
|
||||
]
|
||||
description = "This crate is an implementation detail and should not be relied upon directly."
|
||||
keywords = [
|
||||
"date",
|
||||
"time",
|
||||
"calendar",
|
||||
"duration",
|
||||
]
|
||||
categories = ["date-and-time"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://github.com/time-rs/time"
|
||||
|
||||
[dependencies]
|
|
@ -187,7 +187,7 @@
|
|||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2021 Jacob Pratt
|
||||
Copyright 2022 Jacob Pratt et al.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
|
@ -1,4 +1,4 @@
|
|||
Copyright (c) 2021 Jacob Pratt
|
||||
Copyright (c) 2022 Jacob Pratt et al.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
|
@ -0,0 +1,52 @@
|
|||
//! Core items for `time`.
|
||||
//!
|
||||
//! This crate is an implementation detail of `time` and should not be relied upon directly.
|
||||
|
||||
#![no_std]
|
||||
#![deny(
|
||||
anonymous_parameters,
|
||||
clippy::all,
|
||||
clippy::alloc_instead_of_core,
|
||||
clippy::explicit_auto_deref,
|
||||
clippy::obfuscated_if_else,
|
||||
clippy::std_instead_of_core,
|
||||
clippy::undocumented_unsafe_blocks,
|
||||
const_err,
|
||||
illegal_floating_point_literal_pattern,
|
||||
late_bound_lifetime_arguments,
|
||||
path_statements,
|
||||
patterns_in_fns_without_body,
|
||||
rust_2018_idioms,
|
||||
trivial_casts,
|
||||
trivial_numeric_casts,
|
||||
unreachable_pub,
|
||||
unsafe_op_in_unsafe_fn,
|
||||
unused_extern_crates,
|
||||
rustdoc::broken_intra_doc_links,
|
||||
rustdoc::private_intra_doc_links
|
||||
)]
|
||||
#![warn(
|
||||
clippy::dbg_macro,
|
||||
clippy::decimal_literal_representation,
|
||||
clippy::get_unwrap,
|
||||
clippy::missing_docs_in_private_items,
|
||||
clippy::nursery,
|
||||
clippy::print_stdout,
|
||||
clippy::todo,
|
||||
clippy::unimplemented,
|
||||
clippy::unnested_or_patterns,
|
||||
clippy::unwrap_in_result,
|
||||
clippy::unwrap_used,
|
||||
clippy::use_debug,
|
||||
deprecated_in_future,
|
||||
missing_copy_implementations,
|
||||
missing_debug_implementations,
|
||||
unused_qualifications,
|
||||
variant_size_differences
|
||||
)]
|
||||
#![allow(clippy::redundant_pub_crate)]
|
||||
#![doc(html_favicon_url = "https://avatars0.githubusercontent.com/u/55999857")]
|
||||
#![doc(html_logo_url = "https://avatars0.githubusercontent.com/u/55999857")]
|
||||
#![doc(test(attr(deny(warnings))))]
|
||||
|
||||
pub mod util;
|
|
@ -0,0 +1,52 @@
|
|||
//! Utility functions.
|
||||
|
||||
/// Returns if the provided year is a leap year in the proleptic Gregorian calendar. Uses
|
||||
/// [astronomical year numbering](https://en.wikipedia.org/wiki/Astronomical_year_numbering).
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::util::is_leap_year;
|
||||
/// assert!(!is_leap_year(1900));
|
||||
/// assert!(is_leap_year(2000));
|
||||
/// assert!(is_leap_year(2004));
|
||||
/// assert!(!is_leap_year(2005));
|
||||
/// assert!(!is_leap_year(2100));
|
||||
/// ```
|
||||
pub const fn is_leap_year(year: i32) -> bool {
|
||||
year % 4 == 0 && (year % 25 != 0 || year % 16 == 0)
|
||||
}
|
||||
|
||||
/// Get the number of calendar days in a given year.
|
||||
///
|
||||
/// The returned value will always be either 365 or 366.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::util::days_in_year;
|
||||
/// assert_eq!(days_in_year(1900), 365);
|
||||
/// assert_eq!(days_in_year(2000), 366);
|
||||
/// assert_eq!(days_in_year(2004), 366);
|
||||
/// assert_eq!(days_in_year(2005), 365);
|
||||
/// assert_eq!(days_in_year(2100), 365);
|
||||
/// ```
|
||||
pub const fn days_in_year(year: i32) -> u16 {
|
||||
if is_leap_year(year) { 366 } else { 365 }
|
||||
}
|
||||
|
||||
/// Get the number of weeks in the ISO year.
|
||||
///
|
||||
/// The returned value will always be either 52 or 53.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::util::weeks_in_year;
|
||||
/// assert_eq!(weeks_in_year(2019), 52);
|
||||
/// assert_eq!(weeks_in_year(2020), 53);
|
||||
/// ```
|
||||
pub const fn weeks_in_year(year: i32) -> u8 {
|
||||
match year.rem_euclid(400) {
|
||||
4 | 9 | 15 | 20 | 26 | 32 | 37 | 43 | 48 | 54 | 60 | 65 | 71 | 76 | 82 | 88 | 93 | 99
|
||||
| 105 | 111 | 116 | 122 | 128 | 133 | 139 | 144 | 150 | 156 | 161 | 167 | 172 | 178
|
||||
| 184 | 189 | 195 | 201 | 207 | 212 | 218 | 224 | 229 | 235 | 240 | 246 | 252 | 257
|
||||
| 263 | 268 | 274 | 280 | 285 | 291 | 296 | 303 | 308 | 314 | 320 | 325 | 331 | 336
|
||||
| 342 | 348 | 353 | 359 | 364 | 370 | 376 | 381 | 387 | 392 | 398 => 53,
|
||||
_ => 52,
|
||||
}
|
||||
}
|
|
@ -1 +1 @@
|
|||
{"files":{"Cargo.toml":"c993889735eed89f265010c2ff35ffdd33c95077f53b012678d7330b7662ba9e","LICENSE-Apache":"b8929fea28678da67251fb2daf9438f67503814211051861612441806d8edb05","LICENSE-MIT":"04620bf27e4a643dd47bf27652320c205acdb776c1f9f24bb8c3bfaba10804c5","src/date.rs":"ef9432de8c7250b6ffe82fe7cd4332018ce22e32b0a414d3a7d60eb18d9b6eca","src/datetime.rs":"5c7f6e07dc2f0dcfcd86216664df53bc008dbc86f346df57a9ff57f52fe43bc6","src/error.rs":"a1de0680c8a51a05f341352e9707fe50d2c29b00c36600c2b642879cea1e89c6","src/format_description/component.rs":"a05e7549db9bab4f3836f5fd5af18cacbfa6b323d0106b027e21bf438a5885e5","src/format_description/error.rs":"f2085c5914c21bce09e8eb73b2b96584ac28a829f151e339b3f4ca7b5d5405a3","src/format_description/mod.rs":"da47af329408e9428753ad98ce433eaf026cfdd6e73e3142b23285251d32d0dd","src/format_description/modifier.rs":"c252c8a7d6608b594a6f715210ff67e804ae2f308025f62c8dd99d707627e4a9","src/format_description/parse.rs":"e131ccb7e7ad28e851a01f1b4ce8a6febcc2093e21c0a18126f15e8c17044625","src/helpers/mod.rs":"d3283903b66388efced79a4b1eca3a6b4644031d1ae211034443df5b6b78dc51","src/helpers/string.rs":"ba5699a4df344cbd71c4143f642f6bc07591f53978a9800d4b49ca1f461f87d9","src/lib.rs":"fecf82d1c8b1287fda03c3728741e477731534cf9c2609def2b525ad29683119","src/offset.rs":"fc9341648e091b4d8f7bec47006c01c21cb038c7ef98bd36a492cf78e7533023","src/quote.rs":"058ae621d7c6951c6289a3e855c74d924d9434c1f3031438587aedeb718be283","src/serde_format_description.rs":"5750a1ecf75ae80dc88d4242a6e56b39f3e25c77fdbcc2e88ec3a91ad8511434","src/time.rs":"3c06562358aed7ef624319c96e3f9c150a069606ab930de98ac379ef16b08100","src/to_tokens.rs":"825150a92396a019fee44f21da0bd257349e276d5e75a23ff86cfc625bef6f10"},"package":"42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792"}
|
||||
{"files":{"Cargo.toml":"eb16c06efbfbf2ff5f48260785d4ecefbae6873d9d55c0ba2d388c6762e69b1f","LICENSE-Apache":"b8929fea28678da67251fb2daf9438f67503814211051861612441806d8edb05","LICENSE-MIT":"04620bf27e4a643dd47bf27652320c205acdb776c1f9f24bb8c3bfaba10804c5","src/date.rs":"ffcd3d0998ec67abb43a3f8eccc6104172f5061b855312b89d53bb82fece2460","src/datetime.rs":"5c7f6e07dc2f0dcfcd86216664df53bc008dbc86f346df57a9ff57f52fe43bc6","src/error.rs":"b597f98f425f1628b93ffea19f5f32163aa204e4cd25351bc114853a798e14b0","src/format_description/component.rs":"a05e7549db9bab4f3836f5fd5af18cacbfa6b323d0106b027e21bf438a5885e5","src/format_description/error.rs":"41253d7a02e14597915cf588811a272a90d1ce0f857f7769914e076dd5a66774","src/format_description/mod.rs":"da47af329408e9428753ad98ce433eaf026cfdd6e73e3142b23285251d32d0dd","src/format_description/modifier.rs":"c252c8a7d6608b594a6f715210ff67e804ae2f308025f62c8dd99d707627e4a9","src/format_description/parse.rs":"d65d6e7008030414ce6a860ff37c462c07ed89176a3f1462eeb46468a38fce7e","src/helpers/mod.rs":"54ce8e93512e18ef8761687eaac898a8227852a732f92aa5e80c28e23315bd0c","src/helpers/string.rs":"ba5699a4df344cbd71c4143f642f6bc07591f53978a9800d4b49ca1f461f87d9","src/lib.rs":"f99bded51bb861be5d708a3f756407f5b936a5febb719760c253a15113687e0d","src/offset.rs":"fc9341648e091b4d8f7bec47006c01c21cb038c7ef98bd36a492cf78e7533023","src/quote.rs":"b40251b0ca68e2362aff4297b87a027e48053f1a419113d3d0f7fe089a845a9c","src/serde_format_description.rs":"aa279c8005005fc87c52fa5e8be8ef8fc13ef456a18e3cd5d702ae81194ba4d9","src/time.rs":"3c06562358aed7ef624319c96e3f9c150a069606ab930de98ac379ef16b08100","src/to_tokens.rs":"825150a92396a019fee44f21da0bd257349e276d5e75a23ff86cfc625bef6f10"},"package":"d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2"}
|
|
@ -10,20 +10,36 @@
|
|||
# See Cargo.toml.orig for the original contents.
|
||||
|
||||
[package]
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
rust-version = "1.60.0"
|
||||
name = "time-macros"
|
||||
version = "0.2.4"
|
||||
authors = ["Jacob Pratt <open-source@jhpratt.dev>", "Time contributors"]
|
||||
description = "Procedural macros for the time crate."
|
||||
readme = "../README.md"
|
||||
keywords = ["date", "time", "calendar", "duration"]
|
||||
version = "0.2.6"
|
||||
authors = [
|
||||
"Jacob Pratt <open-source@jhpratt.dev>",
|
||||
"Time contributors",
|
||||
]
|
||||
description = """
|
||||
Procedural macros for the time crate.
|
||||
This crate is an implementation detail and should not be relied upon directly.
|
||||
"""
|
||||
keywords = [
|
||||
"date",
|
||||
"time",
|
||||
"calendar",
|
||||
"duration",
|
||||
]
|
||||
categories = ["date-and-time"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://github.com/time-rs/time"
|
||||
resolver = "2"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies.time-core]
|
||||
version = "=0.1.0"
|
||||
|
||||
[features]
|
||||
formatting = []
|
||||
large-dates = []
|
||||
parsing = []
|
||||
serde = []
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
use std::iter::Peekable;
|
||||
|
||||
use proc_macro::{token_stream, TokenTree};
|
||||
use time_core::util::{days_in_year, weeks_in_year};
|
||||
|
||||
use crate::helpers::{
|
||||
consume_any_ident, consume_number, consume_punct, days_in_year, days_in_year_month,
|
||||
weeks_in_year, ymd_to_yo, ywd_to_yo,
|
||||
consume_any_ident, consume_number, consume_punct, days_in_year_month, ymd_to_yo, ywd_to_yo,
|
||||
};
|
||||
use crate::to_tokens::ToTokenTree;
|
||||
use crate::Error;
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use std::borrow::Cow;
|
||||
use std::fmt;
|
||||
use std::iter::once;
|
||||
|
||||
use proc_macro::{Delimiter, Group, Ident, Literal, Punct, Spacing, Span, TokenStream, TokenTree};
|
||||
|
||||
#[cfg(any(feature = "formatting", feature = "parsing"))]
|
||||
use crate::format_description::error::InvalidFormatDescription;
|
||||
|
||||
trait WithSpan {
|
||||
|
@ -29,6 +29,7 @@ pub(crate) enum Error {
|
|||
span_start: Option<Span>,
|
||||
span_end: Option<Span>,
|
||||
},
|
||||
#[cfg(any(feature = "formatting", feature = "parsing"))]
|
||||
ExpectedString {
|
||||
span_start: Option<Span>,
|
||||
span_end: Option<Span>,
|
||||
|
@ -37,6 +38,7 @@ pub(crate) enum Error {
|
|||
tree: TokenTree,
|
||||
},
|
||||
UnexpectedEndOfInput,
|
||||
#[cfg(any(feature = "formatting", feature = "parsing"))]
|
||||
InvalidFormatDescription {
|
||||
error: InvalidFormatDescription,
|
||||
span_start: Option<Span>,
|
||||
|
@ -52,13 +54,15 @@ pub(crate) enum Error {
|
|||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::MissingComponent { name, .. } => write!(f, "missing component: {}", name),
|
||||
Self::MissingComponent { name, .. } => write!(f, "missing component: {name}"),
|
||||
Self::InvalidComponent { name, value, .. } => {
|
||||
write!(f, "invalid component: {} was {}", name, value)
|
||||
write!(f, "invalid component: {name} was {value}")
|
||||
}
|
||||
#[cfg(any(feature = "formatting", feature = "parsing"))]
|
||||
Self::ExpectedString { .. } => f.write_str("expected string"),
|
||||
Self::UnexpectedToken { tree } => write!(f, "unexpected token: {}", tree),
|
||||
Self::UnexpectedToken { tree } => write!(f, "unexpected token: {tree}"),
|
||||
Self::UnexpectedEndOfInput => f.write_str("unexpected end of input"),
|
||||
#[cfg(any(feature = "formatting", feature = "parsing"))]
|
||||
Self::InvalidFormatDescription { error, .. } => error.fmt(f),
|
||||
Self::Custom { message, .. } => f.write_str(message),
|
||||
}
|
||||
|
@ -70,9 +74,10 @@ impl Error {
|
|||
match self {
|
||||
Self::MissingComponent { span_start, .. }
|
||||
| Self::InvalidComponent { span_start, .. }
|
||||
| Self::ExpectedString { span_start, .. }
|
||||
| Self::InvalidFormatDescription { span_start, .. }
|
||||
| Self::Custom { span_start, .. } => *span_start,
|
||||
#[cfg(any(feature = "formatting", feature = "parsing"))]
|
||||
Self::ExpectedString { span_start, .. }
|
||||
| Self::InvalidFormatDescription { span_start, .. } => *span_start,
|
||||
Self::UnexpectedToken { tree } => Some(tree.span()),
|
||||
Self::UnexpectedEndOfInput => Some(Span::mixed_site()),
|
||||
}
|
||||
|
@ -83,9 +88,10 @@ impl Error {
|
|||
match self {
|
||||
Self::MissingComponent { span_end, .. }
|
||||
| Self::InvalidComponent { span_end, .. }
|
||||
| Self::ExpectedString { span_end, .. }
|
||||
| Self::InvalidFormatDescription { span_end, .. }
|
||||
| Self::Custom { span_end, .. } => *span_end,
|
||||
#[cfg(any(feature = "formatting", feature = "parsing"))]
|
||||
Self::ExpectedString { span_end, .. }
|
||||
| Self::InvalidFormatDescription { span_end, .. } => *span_end,
|
||||
Self::UnexpectedToken { tree, .. } => Some(tree.span()),
|
||||
Self::UnexpectedEndOfInput => Some(Span::mixed_site()),
|
||||
}
|
||||
|
@ -117,11 +123,12 @@ impl Error {
|
|||
}
|
||||
|
||||
/// Like `to_compile_error`, but for use in macros that produce items.
|
||||
#[cfg(all(feature = "serde", any(feature = "formatting", feature = "parsing")))]
|
||||
pub(crate) fn to_compile_error_standalone(&self) -> TokenStream {
|
||||
let end = self.span_end();
|
||||
self.to_compile_error()
|
||||
.into_iter()
|
||||
.chain(once(
|
||||
.chain(std::iter::once(
|
||||
TokenTree::from(Punct::new(';', Spacing::Alone)).with_span(end),
|
||||
))
|
||||
.collect()
|
||||
|
|
|
@ -13,18 +13,16 @@ impl fmt::Display for InvalidFormatDescription {
|
|||
use InvalidFormatDescription::*;
|
||||
match self {
|
||||
UnclosedOpeningBracket { index } => {
|
||||
write!(f, "unclosed opening bracket at byte index {}", index)
|
||||
write!(f, "unclosed opening bracket at byte index {index}")
|
||||
}
|
||||
InvalidComponentName { name, index } => {
|
||||
write!(f, "invalid component name `{name}` at byte index {index}",)
|
||||
}
|
||||
InvalidComponentName { name, index } => write!(
|
||||
f,
|
||||
"invalid component name `{}` at byte index {}",
|
||||
name, index
|
||||
),
|
||||
InvalidModifier { value, index } => {
|
||||
write!(f, "invalid modifier `{}` at byte index {}", value, index)
|
||||
write!(f, "invalid modifier `{value}` at byte index {index}")
|
||||
}
|
||||
MissingComponentName { index } => {
|
||||
write!(f, "missing component name at byte index {}", index)
|
||||
write!(f, "missing component name at byte index {index}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,12 +43,12 @@ fn parse_item<'a>(
|
|||
if let [b'[', b'[', remaining @ ..] = s {
|
||||
*index += 2;
|
||||
return Ok(ParsedItem {
|
||||
item: FormatItem::Literal(&[b'[']),
|
||||
item: FormatItem::Literal(b"["),
|
||||
remaining,
|
||||
});
|
||||
};
|
||||
|
||||
if s.starts_with(&[b'[']) {
|
||||
if s.starts_with(b"[") {
|
||||
if let Some(bracket_index) = s.iter().position(|&c| c == b']') {
|
||||
*index += 1; // opening bracket
|
||||
let ret_val = ParsedItem {
|
||||
|
|
|
@ -1,12 +1,17 @@
|
|||
#[cfg(any(feature = "formatting", feature = "parsing"))]
|
||||
mod string;
|
||||
|
||||
use std::iter::Peekable;
|
||||
use std::str::FromStr;
|
||||
|
||||
use proc_macro::{token_stream, Span, TokenStream, TokenTree};
|
||||
#[cfg(any(feature = "formatting", feature = "parsing"))]
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro::{token_stream, Span, TokenTree};
|
||||
use time_core::util::{days_in_year, is_leap_year};
|
||||
|
||||
use crate::Error;
|
||||
|
||||
#[cfg(any(feature = "formatting", feature = "parsing"))]
|
||||
pub(crate) fn get_string_literal(tokens: TokenStream) -> Result<(Span, Vec<u8>), Error> {
|
||||
let mut tokens = tokens.into_iter();
|
||||
|
||||
|
@ -76,10 +81,6 @@ pub(crate) fn consume_punct(
|
|||
}
|
||||
}
|
||||
|
||||
fn is_leap_year(year: i32) -> bool {
|
||||
(year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0))
|
||||
}
|
||||
|
||||
fn jan_weekday(year: i32, ordinal: i32) -> u8 {
|
||||
macro_rules! div_floor {
|
||||
($a:expr, $b:expr) => {{
|
||||
|
@ -99,19 +100,11 @@ fn jan_weekday(year: i32, ordinal: i32) -> u8 {
|
|||
.rem_euclid(7)) as _
|
||||
}
|
||||
|
||||
pub(crate) fn days_in_year(year: i32) -> u16 {
|
||||
365 + is_leap_year(year) as u16
|
||||
}
|
||||
|
||||
pub(crate) fn days_in_year_month(year: i32, month: u8) -> u8 {
|
||||
[31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month as usize - 1]
|
||||
+ (month == 2 && is_leap_year(year)) as u8
|
||||
}
|
||||
|
||||
pub(crate) fn weeks_in_year(year: i32) -> u8 {
|
||||
52 + (jan_weekday(year, 1) + is_leap_year(year) as u8 == 3) as u8
|
||||
}
|
||||
|
||||
pub(crate) fn ywd_to_yo(year: i32, week: u8, iso_weekday_number: u8) -> (i32, u16) {
|
||||
let (ordinal, overflow) = (u16::from(week) * 7 + u16::from(iso_weekday_number))
|
||||
.overflowing_sub(u16::from(jan_weekday(year, 4)) + 4);
|
||||
|
|
|
@ -28,7 +28,11 @@
|
|||
unused_qualifications,
|
||||
variant_size_differences
|
||||
)]
|
||||
#![allow(clippy::missing_const_for_fn, clippy::redundant_pub_crate)]
|
||||
#![allow(
|
||||
clippy::missing_const_for_fn, // useless in proc macro
|
||||
clippy::redundant_pub_crate, // suggests bad style
|
||||
clippy::option_if_let_else, // suggests terrible code
|
||||
)]
|
||||
|
||||
#[macro_use]
|
||||
mod quote;
|
||||
|
@ -36,14 +40,18 @@ mod quote;
|
|||
mod date;
|
||||
mod datetime;
|
||||
mod error;
|
||||
#[cfg(any(feature = "formatting", feature = "parsing"))]
|
||||
mod format_description;
|
||||
mod helpers;
|
||||
mod offset;
|
||||
#[cfg(all(feature = "serde", any(feature = "formatting", feature = "parsing")))]
|
||||
mod serde_format_description;
|
||||
mod time;
|
||||
mod to_tokens;
|
||||
|
||||
use proc_macro::{TokenStream, TokenTree};
|
||||
use proc_macro::TokenStream;
|
||||
#[cfg(all(feature = "serde", any(feature = "formatting", feature = "parsing")))]
|
||||
use proc_macro::TokenTree;
|
||||
|
||||
use self::error::Error;
|
||||
|
||||
|
@ -67,8 +75,7 @@ macro_rules! impl_macros {
|
|||
|
||||
impl_macros![date datetime offset time];
|
||||
|
||||
// TODO Gate this behind the the `formatting` or `parsing` feature flag when weak dependency
|
||||
// features land.
|
||||
#[cfg(any(feature = "formatting", feature = "parsing"))]
|
||||
#[proc_macro]
|
||||
pub fn format_description(input: TokenStream) -> TokenStream {
|
||||
(|| {
|
||||
|
@ -88,6 +95,7 @@ pub fn format_description(input: TokenStream) -> TokenStream {
|
|||
.unwrap_or_else(|err: Error| err.to_compile_error())
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "serde", any(feature = "formatting", feature = "parsing")))]
|
||||
#[proc_macro]
|
||||
pub fn serde_format_description(input: TokenStream) -> TokenStream {
|
||||
(|| {
|
||||
|
@ -112,17 +120,47 @@ pub fn serde_format_description(input: TokenStream) -> TokenStream {
|
|||
// Another comma
|
||||
helpers::consume_punct(',', &mut tokens)?;
|
||||
|
||||
// Then, a string literal.
|
||||
let (span, format_string) = helpers::get_string_literal(tokens.collect())?;
|
||||
// We now have two options. The user can either provide a format description as a string or
|
||||
// they can provide a path to a format description. If the latter, all remaining tokens are
|
||||
// assumed to be part of the path.
|
||||
let (format, raw_format_string) = match tokens.peek() {
|
||||
// string literal
|
||||
Some(TokenTree::Literal(_)) => {
|
||||
let (span, format_string) = helpers::get_string_literal(tokens.collect())?;
|
||||
let items = format_description::parse(&format_string, span)?;
|
||||
let items: TokenStream =
|
||||
items.into_iter().map(|item| quote! { #S(item), }).collect();
|
||||
let items = quote! { &[#S(items)] };
|
||||
|
||||
let items = format_description::parse(&format_string, span)?;
|
||||
let items: TokenStream = items.into_iter().map(|item| quote! { #S(item), }).collect();
|
||||
(
|
||||
items,
|
||||
Some(String::from_utf8_lossy(&format_string).into_owned()),
|
||||
)
|
||||
}
|
||||
// path
|
||||
Some(_) => (
|
||||
quote! {{
|
||||
// We can't just do `super::path` because the path could be an absolute path. In
|
||||
// that case, we'd be generating `super::::path`, which is invalid. Even if we
|
||||
// took that into account, it's not possible to know if it's an external crate,
|
||||
// which would just require emitting `path` directly. By taking this approach,
|
||||
// we can leave it to the compiler to do the actual resolution.
|
||||
mod __path_hack {
|
||||
pub(super) use super::super::*;
|
||||
pub(super) use #S(tokens.collect::<TokenStream>()) as FORMAT;
|
||||
}
|
||||
__path_hack::FORMAT
|
||||
}},
|
||||
None,
|
||||
),
|
||||
None => return Err(Error::UnexpectedEndOfInput),
|
||||
};
|
||||
|
||||
Ok(serde_format_description::build(
|
||||
mod_name,
|
||||
items,
|
||||
formattable,
|
||||
&String::from_utf8_lossy(&format_string),
|
||||
format,
|
||||
raw_format_string,
|
||||
))
|
||||
})()
|
||||
.unwrap_or_else(|err: Error| err.to_compile_error_standalone())
|
||||
|
|
|
@ -8,6 +8,7 @@ macro_rules! quote {
|
|||
}};
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "formatting", feature = "parsing"))]
|
||||
macro_rules! quote_append {
|
||||
($ts:ident $($x:tt)*) => {{
|
||||
quote_inner!($ts $($x)*);
|
||||
|
@ -65,6 +66,7 @@ macro_rules! quote_inner {
|
|||
($ts:ident ? $($tail:tt)*) => { sym!($ts '?'); quote_inner!($ts $($tail)*); };
|
||||
($ts:ident ! $($tail:tt)*) => { sym!($ts '!'); quote_inner!($ts $($tail)*); };
|
||||
($ts:ident | $($tail:tt)*) => { sym!($ts '|'); quote_inner!($ts $($tail)*); };
|
||||
($ts:ident * $($tail:tt)*) => { sym!($ts '*'); quote_inner!($ts $($tail)*); };
|
||||
|
||||
// Identifier
|
||||
($ts:ident $i:ident $($tail:tt)*) => {
|
||||
|
|
|
@ -1,130 +1,162 @@
|
|||
use proc_macro::{Ident, TokenStream, TokenTree};
|
||||
|
||||
use crate::to_tokens;
|
||||
|
||||
pub(crate) fn build(
|
||||
mod_name: Ident,
|
||||
items: impl to_tokens::ToTokenStream,
|
||||
ty: TokenTree,
|
||||
format_string: &str,
|
||||
format: TokenStream,
|
||||
raw_format_string: Option<String>,
|
||||
) -> TokenStream {
|
||||
let ty_s = &*ty.to_string();
|
||||
|
||||
let visitor = quote! {
|
||||
struct Visitor;
|
||||
struct OptionVisitor;
|
||||
let format_description_display = raw_format_string.unwrap_or_else(|| format.to_string());
|
||||
|
||||
impl<'a> ::serde::de::Visitor<'a> for Visitor {
|
||||
type Value = __TimeSerdeType;
|
||||
let visitor = if cfg!(feature = "parsing") {
|
||||
quote! {
|
||||
struct Visitor;
|
||||
struct OptionVisitor;
|
||||
|
||||
fn expecting(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
concat!(
|
||||
"a(n) `",
|
||||
#(ty_s),
|
||||
"` in the format \"",
|
||||
#(format_string),
|
||||
"\"",
|
||||
impl<'a> ::serde::de::Visitor<'a> for Visitor {
|
||||
type Value = __TimeSerdeType;
|
||||
|
||||
fn expecting(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
concat!(
|
||||
"a(n) `",
|
||||
#(ty_s),
|
||||
"` in the format \"{}\"",
|
||||
),
|
||||
#(format_description_display.as_str())
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fn visit_str<E: ::serde::de::Error>(
|
||||
self,
|
||||
value: &str
|
||||
) -> Result<__TimeSerdeType, E> {
|
||||
__TimeSerdeType::parse(value, &DESCRIPTION).map_err(E::custom)
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_str<E: ::serde::de::Error>(
|
||||
self,
|
||||
value: &str
|
||||
) -> Result<__TimeSerdeType, E> {
|
||||
__TimeSerdeType::parse(value, &DESCRIPTION).map_err(E::custom)
|
||||
impl<'a> ::serde::de::Visitor<'a> for OptionVisitor {
|
||||
type Value = Option<__TimeSerdeType>;
|
||||
|
||||
fn expecting(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
concat!(
|
||||
"an `Option<",
|
||||
#(ty_s),
|
||||
">` in the format \"{}\"",
|
||||
),
|
||||
#(format_description_display.as_str())
|
||||
)
|
||||
}
|
||||
|
||||
fn visit_some<D: ::serde::de::Deserializer<'a>>(
|
||||
self,
|
||||
deserializer: D
|
||||
) -> Result<Option<__TimeSerdeType>, D::Error> {
|
||||
deserializer
|
||||
.deserialize_any(Visitor)
|
||||
.map(Some)
|
||||
}
|
||||
|
||||
fn visit_none<E: ::serde::de::Error>(
|
||||
self
|
||||
) -> Result<Option<__TimeSerdeType>, E> {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quote!()
|
||||
};
|
||||
|
||||
impl<'a> ::serde::de::Visitor<'a> for OptionVisitor {
|
||||
type Value = Option<__TimeSerdeType>;
|
||||
|
||||
fn expecting(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
concat!(
|
||||
"an `Option<",
|
||||
#(ty_s),
|
||||
">` in the format \"",
|
||||
#(format_string),
|
||||
"\"",
|
||||
)
|
||||
)
|
||||
let serialize_primary = if cfg!(feature = "formatting") {
|
||||
quote! {
|
||||
pub fn serialize<S: ::serde::Serializer>(
|
||||
datetime: &__TimeSerdeType,
|
||||
serializer: S,
|
||||
) -> Result<S::Ok, S::Error> {
|
||||
use ::serde::Serialize;
|
||||
datetime
|
||||
.format(&DESCRIPTION)
|
||||
.map_err(::time::error::Format::into_invalid_serde_value::<S>)?
|
||||
.serialize(serializer)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quote!()
|
||||
};
|
||||
|
||||
fn visit_some<D: ::serde::de::Deserializer<'a>>(
|
||||
self,
|
||||
let deserialize_primary = if cfg!(feature = "parsing") {
|
||||
quote! {
|
||||
pub fn deserialize<'a, D: ::serde::Deserializer<'a>>(
|
||||
deserializer: D
|
||||
) -> Result<__TimeSerdeType, D::Error> {
|
||||
use ::serde::Deserialize;
|
||||
deserializer.deserialize_any(Visitor)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quote!()
|
||||
};
|
||||
|
||||
let serialize_option = if cfg!(feature = "formatting") {
|
||||
quote! {
|
||||
pub fn serialize<S: ::serde::Serializer>(
|
||||
option: &Option<__TimeSerdeType>,
|
||||
serializer: S,
|
||||
) -> Result<S::Ok, S::Error> {
|
||||
use ::serde::Serialize;
|
||||
option.map(|datetime| datetime.format(&DESCRIPTION))
|
||||
.transpose()
|
||||
.map_err(::time::error::Format::into_invalid_serde_value::<S>)?
|
||||
.serialize(serializer)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quote!()
|
||||
};
|
||||
|
||||
let deserialize_option = if cfg!(feature = "parsing") {
|
||||
quote! {
|
||||
pub fn deserialize<'a, D: ::serde::Deserializer<'a>>(
|
||||
deserializer: D
|
||||
) -> Result<Option<__TimeSerdeType>, D::Error> {
|
||||
deserializer
|
||||
.deserialize_any(Visitor)
|
||||
.map(Some)
|
||||
}
|
||||
|
||||
fn visit_none<E: ::serde::de::Error>(
|
||||
self
|
||||
) -> Result<Option<__TimeSerdeType>, E> {
|
||||
Ok(None)
|
||||
use ::serde::Deserialize;
|
||||
deserializer.deserialize_option(OptionVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
quote!()
|
||||
};
|
||||
|
||||
let primary_fns = quote! {
|
||||
pub fn serialize<S: ::serde::Serializer>(
|
||||
datetime: &__TimeSerdeType,
|
||||
serializer: S,
|
||||
) -> Result<S::Ok, S::Error> {
|
||||
use ::serde::Serialize;
|
||||
datetime
|
||||
.format(&DESCRIPTION)
|
||||
.map_err(::time::error::Format::into_invalid_serde_value::<S>)?
|
||||
.serialize(serializer)
|
||||
}
|
||||
|
||||
pub fn deserialize<'a, D: ::serde::Deserializer<'a>>(
|
||||
deserializer: D
|
||||
) -> Result<__TimeSerdeType, D::Error> {
|
||||
use ::serde::Deserialize;
|
||||
deserializer.deserialize_any(Visitor)
|
||||
}
|
||||
};
|
||||
|
||||
let options_fns = quote! {
|
||||
pub fn serialize<S: ::serde::Serializer>(
|
||||
option: &Option<__TimeSerdeType>,
|
||||
serializer: S,
|
||||
) -> Result<S::Ok, S::Error> {
|
||||
use ::serde::Serialize;
|
||||
option.map(|datetime| datetime.format(&DESCRIPTION))
|
||||
.transpose()
|
||||
.map_err(::time::error::Format::into_invalid_serde_value::<S>)?
|
||||
.serialize(serializer)
|
||||
}
|
||||
|
||||
pub fn deserialize<'a, D: ::serde::Deserializer<'a>>(
|
||||
deserializer: D
|
||||
) -> Result<Option<__TimeSerdeType>, D::Error> {
|
||||
use ::serde::Deserialize;
|
||||
deserializer.deserialize_option(OptionVisitor)
|
||||
let deserialize_option_imports = if cfg!(feature = "parsing") {
|
||||
quote! {
|
||||
use super::{OptionVisitor, Visitor};
|
||||
}
|
||||
} else {
|
||||
quote!()
|
||||
};
|
||||
|
||||
quote! {
|
||||
mod #(mod_name) {
|
||||
use ::time::#(ty) as __TimeSerdeType;
|
||||
|
||||
const DESCRIPTION: &[::time::format_description::FormatItem<'_>] = &[#S(items)];
|
||||
const DESCRIPTION: &[::time::format_description::FormatItem<'_>] = #S(format);
|
||||
|
||||
#S(visitor)
|
||||
#S(primary_fns)
|
||||
#S(serialize_primary)
|
||||
#S(deserialize_primary)
|
||||
|
||||
pub(super) mod option {
|
||||
use super::{DESCRIPTION, OptionVisitor, Visitor, __TimeSerdeType};
|
||||
use super::{DESCRIPTION, __TimeSerdeType};
|
||||
#S(deserialize_option_imports)
|
||||
|
||||
#S(options_fns)
|
||||
#S(serialize_option)
|
||||
#S(deserialize_option)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -10,42 +10,65 @@
|
|||
# See Cargo.toml.orig for the original contents.
|
||||
|
||||
[package]
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
rust-version = "1.60.0"
|
||||
name = "time"
|
||||
version = "0.3.9"
|
||||
authors = ["Jacob Pratt <open-source@jhpratt.dev>", "Time contributors"]
|
||||
include = ["src/**/*", "LICENSE-*", "README.md", "!src/tests.rs"]
|
||||
version = "0.3.17"
|
||||
authors = [
|
||||
"Jacob Pratt <open-source@jhpratt.dev>",
|
||||
"Time contributors",
|
||||
]
|
||||
include = [
|
||||
"src/**/*",
|
||||
"LICENSE-*",
|
||||
"README.md",
|
||||
]
|
||||
description = "Date and time library. Fully interoperable with the standard library. Mostly compatible with #![no_std]."
|
||||
homepage = "https://time-rs.github.io"
|
||||
readme = "README.md"
|
||||
keywords = ["date", "time", "calendar", "duration"]
|
||||
categories = ["date-and-time", "no-std", "parser-implementations", "value-formatting"]
|
||||
keywords = [
|
||||
"date",
|
||||
"time",
|
||||
"calendar",
|
||||
"duration",
|
||||
]
|
||||
categories = [
|
||||
"date-and-time",
|
||||
"no-std",
|
||||
"parser-implementations",
|
||||
"value-formatting",
|
||||
]
|
||||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://github.com/time-rs/time"
|
||||
resolver = "2"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
rustdoc-args = ["--cfg", "__time_03_docs"]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
[profile.dev]
|
||||
debug = 0
|
||||
rustdoc-args = [
|
||||
"--cfg",
|
||||
"__time_03_docs",
|
||||
]
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
|
||||
[[test]]
|
||||
name = "tests"
|
||||
path = "../tests/main.rs"
|
||||
|
||||
[[bench]]
|
||||
name = "benchmarks"
|
||||
path = "benchmarks/main.rs"
|
||||
path = "../benchmarks/main.rs"
|
||||
harness = false
|
||||
|
||||
[dependencies.itoa]
|
||||
version = "1.0.1"
|
||||
optional = true
|
||||
|
||||
[dependencies.quickcheck-dep]
|
||||
[dependencies.quickcheck]
|
||||
version = "1.0.3"
|
||||
optional = true
|
||||
default-features = false
|
||||
package = "quickcheck"
|
||||
|
||||
[dependencies.rand]
|
||||
version = "0.8.4"
|
||||
|
@ -57,9 +80,13 @@ version = "1.0.126"
|
|||
optional = true
|
||||
default-features = false
|
||||
|
||||
[dependencies.time-core]
|
||||
version = "=0.1.0"
|
||||
|
||||
[dependencies.time-macros]
|
||||
version = "=0.2.4"
|
||||
version = "=0.2.6"
|
||||
optional = true
|
||||
|
||||
[dev-dependencies.quickcheck_macros]
|
||||
version = "1.0.0"
|
||||
|
||||
|
@ -78,27 +105,62 @@ version = "1.0.68"
|
|||
[dev-dependencies.serde_test]
|
||||
version = "1.0.126"
|
||||
|
||||
[features]
|
||||
alloc = []
|
||||
default = ["std"]
|
||||
formatting = ["itoa", "std"]
|
||||
large-dates = ["time-macros/large-dates"]
|
||||
local-offset = ["std"]
|
||||
macros = ["time-macros"]
|
||||
parsing = []
|
||||
quickcheck = ["quickcheck-dep", "alloc"]
|
||||
serde-human-readable = ["serde", "formatting", "parsing"]
|
||||
serde-well-known = ["serde/alloc", "formatting", "parsing"]
|
||||
std = ["alloc"]
|
||||
[target."cfg(__ui_tests)".dev-dependencies.trybuild]
|
||||
version = "=1.0.34"
|
||||
[target."cfg(bench)".dev-dependencies.criterion]
|
||||
version = "0.3.5"
|
||||
[dev-dependencies.time-macros]
|
||||
version = "=0.2.6"
|
||||
|
||||
[features]
|
||||
alloc = ["serde?/alloc"]
|
||||
default = ["std"]
|
||||
formatting = [
|
||||
"dep:itoa",
|
||||
"std",
|
||||
"time-macros?/formatting",
|
||||
]
|
||||
large-dates = ["time-macros?/large-dates"]
|
||||
local-offset = [
|
||||
"std",
|
||||
"dep:libc",
|
||||
"dep:num_threads",
|
||||
]
|
||||
macros = ["dep:time-macros"]
|
||||
parsing = ["time-macros?/parsing"]
|
||||
quickcheck = [
|
||||
"dep:quickcheck",
|
||||
"alloc",
|
||||
]
|
||||
rand = ["dep:rand"]
|
||||
serde = [
|
||||
"dep:serde",
|
||||
"time-macros?/serde",
|
||||
]
|
||||
serde-human-readable = [
|
||||
"serde",
|
||||
"formatting",
|
||||
"parsing",
|
||||
]
|
||||
serde-well-known = [
|
||||
"serde",
|
||||
"formatting",
|
||||
"parsing",
|
||||
]
|
||||
std = ["alloc"]
|
||||
wasm-bindgen = ["dep:js-sys"]
|
||||
|
||||
[target."cfg(__ui_tests)".dev-dependencies.trybuild]
|
||||
version = "1.0.68"
|
||||
|
||||
[target."cfg(all(target_arch = \"wasm32\", not(any(target_os = \"emscripten\", target_os = \"wasi\"))))".dependencies.js-sys]
|
||||
version = "0.3.58"
|
||||
optional = true
|
||||
|
||||
[target."cfg(bench)".dev-dependencies.criterion]
|
||||
version = "0.4.0"
|
||||
default-features = false
|
||||
|
||||
[target."cfg(bench)".dev-dependencies.criterion-cycles-per-byte]
|
||||
version = "0.1.2"
|
||||
[target."cfg(target_family = \"unix\")".dependencies.libc]
|
||||
version = "0.2.98"
|
||||
optional = true
|
||||
|
||||
[target."cfg(target_family = \"unix\")".dependencies.num_threads]
|
||||
version = "0.1.2"
|
||||
optional = true
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
# time
|
||||
|
||||
[![minimum rustc: 1.53](https://img.shields.io/badge/minimum%20rustc-1.53-yellowgreen?logo=rust&style=flat-square)](https://www.whatrustisit.com)
|
||||
[![minimum rustc: 1.60](https://img.shields.io/badge/minimum%20rustc-1.60-yellowgreen?logo=rust&style=flat-square)](https://www.whatrustisit.com)
|
||||
[![version](https://img.shields.io/crates/v/time?color=blue&logo=rust&style=flat-square)](https://crates.io/crates/time)
|
||||
[![build status](https://img.shields.io/github/workflow/status/time-rs/time/Build/main?style=flat-square)](https://github.com/time-rs/time/actions)
|
||||
[![codecov](https://codecov.io/gh/time-rs/time/branch/main/graph/badge.svg?token=yt4XSmQNKQ)](https://codecov.io/gh/time-rs/time)
|
||||
|
||||
Documentation:
|
||||
|
||||
- [latest release](https://docs.rs/time)
|
||||
- [main branch](https://time-rs.github.io/api/time)
|
||||
- [book](https://time-rs.github.io/book)
|
||||
|
@ -22,17 +23,10 @@ Contributions are always welcome! If you have an idea, it's best to float it by
|
|||
it to ensure no effort is wasted. If there's already an open issue for it, knock yourself out.
|
||||
Internal documentation can be viewed [here](https://time-rs.github.io/internal-api/time).
|
||||
|
||||
If you have any questions, feel free to use [Discussions] or [Codestream]. There are a few notes
|
||||
inline with Codestream, though questions can be asked directly! As a bonus, they are visible and
|
||||
searchable for others. Feedback (prior to opening a pull request) can be provided with Codestream
|
||||
and [VS Live Share]. Don't hesitate to ask questions — that's what I'm here for!
|
||||
|
||||
If using Codestream, just open up a local copy of this repository. It _should_ add you
|
||||
automatically.
|
||||
If you have any questions, feel free to use [Discussions]. Don't hesitate to ask questions — that's
|
||||
what I'm here for!
|
||||
|
||||
[Discussions]: https://github.com/time-rs/time/discussions
|
||||
[Codestream]: https://codestream.com
|
||||
[VS Live Share]: https://code.visualstudio.com/learn/collaboration/live-share
|
||||
|
||||
## License
|
||||
|
||||
|
|
|
@ -14,18 +14,17 @@ use crate::util::{days_in_year, days_in_year_month, is_leap_year, weeks_in_year}
|
|||
use crate::{error, Duration, Month, PrimitiveDateTime, Time, Weekday};
|
||||
|
||||
/// The minimum valid year.
|
||||
#[cfg(feature = "large-dates")]
|
||||
pub(crate) const MIN_YEAR: i32 = -999_999;
|
||||
pub(crate) const MIN_YEAR: i32 = if cfg!(feature = "large-dates") {
|
||||
-999_999
|
||||
} else {
|
||||
-9999
|
||||
};
|
||||
/// The maximum valid year.
|
||||
#[cfg(feature = "large-dates")]
|
||||
pub(crate) const MAX_YEAR: i32 = 999_999;
|
||||
|
||||
/// The minimum valid year.
|
||||
#[cfg(not(feature = "large-dates"))]
|
||||
pub(crate) const MIN_YEAR: i32 = -9999;
|
||||
/// The maximum valid year.
|
||||
#[cfg(not(feature = "large-dates"))]
|
||||
pub(crate) const MAX_YEAR: i32 = 9999;
|
||||
pub(crate) const MAX_YEAR: i32 = if cfg!(feature = "large-dates") {
|
||||
999_999
|
||||
} else {
|
||||
9999
|
||||
};
|
||||
|
||||
/// Date in the proleptic Gregorian calendar.
|
||||
///
|
||||
|
@ -39,16 +38,7 @@ pub struct Date {
|
|||
// | 2 bits | 21 bits | 9 bits |
|
||||
// | unassigned | year | ordinal |
|
||||
// The year is 15 bits when `large-dates` is not enabled.
|
||||
pub(crate) value: i32,
|
||||
}
|
||||
|
||||
impl fmt::Debug for Date {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
f.debug_struct("Date")
|
||||
.field("year", &self.year())
|
||||
.field("ordinal", &self.ordinal())
|
||||
.finish()
|
||||
}
|
||||
value: i32,
|
||||
}
|
||||
|
||||
impl Date {
|
||||
|
@ -67,6 +57,11 @@ impl Date {
|
|||
/// guaranteed by the caller.
|
||||
#[doc(hidden)]
|
||||
pub const fn __from_ordinal_date_unchecked(year: i32, ordinal: u16) -> Self {
|
||||
debug_assert!(year >= MIN_YEAR);
|
||||
debug_assert!(year <= MAX_YEAR);
|
||||
debug_assert!(ordinal != 0);
|
||||
debug_assert!(ordinal <= days_in_year(year));
|
||||
|
||||
Self {
|
||||
value: (year << 9) | ordinal as i32,
|
||||
}
|
||||
|
@ -176,7 +171,8 @@ impl Date {
|
|||
/// freely available [here](https://www.researchgate.net/publication/316558298_Date_Algorithms).
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::{Date, macros::date};
|
||||
/// # use time::Date;
|
||||
/// # use time_macros::date;
|
||||
/// assert_eq!(Date::from_julian_day(0), Ok(date!(-4713 - 11 - 24)));
|
||||
/// assert_eq!(Date::from_julian_day(2_451_545), Ok(date!(2000 - 01 - 01)));
|
||||
/// assert_eq!(Date::from_julian_day(2_458_485), Ok(date!(2019 - 01 - 01)));
|
||||
|
@ -196,24 +192,27 @@ impl Date {
|
|||
/// internally invalid value.
|
||||
#[doc(alias = "from_julian_date_unchecked")]
|
||||
pub(crate) const fn from_julian_day_unchecked(julian_day: i32) -> Self {
|
||||
#![allow(trivial_numeric_casts)] // cast depends on type alias
|
||||
|
||||
/// A type that is either `i32` or `i64`. This subtle difference allows for optimization
|
||||
/// based on the valid values.
|
||||
#[cfg(feature = "large-dates")]
|
||||
type MaybeWidened = i64;
|
||||
#[allow(clippy::missing_docs_in_private_items)]
|
||||
#[cfg(not(feature = "large-dates"))]
|
||||
type MaybeWidened = i32;
|
||||
debug_assert!(julian_day >= Self::MIN.to_julian_day());
|
||||
debug_assert!(julian_day <= Self::MAX.to_julian_day());
|
||||
|
||||
// To avoid a potential overflow, the value may need to be widened for some arithmetic.
|
||||
|
||||
let z = julian_day - 1_721_119;
|
||||
let g = 100 * z as MaybeWidened - 25;
|
||||
let a = (g / 3_652_425) as i32;
|
||||
let b = a - a / 4;
|
||||
let mut year = div_floor!(100 * b as MaybeWidened + g, 36525) as i32;
|
||||
let mut ordinal = (b + z - div_floor!(36525 * year as MaybeWidened, 100) as i32) as _;
|
||||
let (mut year, mut ordinal) = if julian_day < -19_752_948 || julian_day > 23_195_514 {
|
||||
let g = 100 * z as i64 - 25;
|
||||
let a = (g / 3_652_425) as i32;
|
||||
let b = a - a / 4;
|
||||
let year = div_floor!(100 * b as i64 + g, 36525) as i32;
|
||||
let ordinal = (b + z - div_floor!(36525 * year as i64, 100) as i32) as _;
|
||||
(year, ordinal)
|
||||
} else {
|
||||
let g = 100 * z - 25;
|
||||
let a = g / 3_652_425;
|
||||
let b = a - a / 4;
|
||||
let year = div_floor!(100 * b + g, 36525);
|
||||
let ordinal = (b + z - div_floor!(36525 * year, 100)) as _;
|
||||
(year, ordinal)
|
||||
};
|
||||
|
||||
if is_leap_year(year) {
|
||||
ordinal += 60;
|
||||
|
@ -231,7 +230,7 @@ impl Date {
|
|||
/// Get the year of the date.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::date;
|
||||
/// # use time_macros::date;
|
||||
/// assert_eq!(date!(2019 - 01 - 01).year(), 2019);
|
||||
/// assert_eq!(date!(2019 - 12 - 31).year(), 2019);
|
||||
/// assert_eq!(date!(2020 - 01 - 01).year(), 2020);
|
||||
|
@ -243,7 +242,8 @@ impl Date {
|
|||
/// Get the month.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::{macros::date, Month};
|
||||
/// # use time::Month;
|
||||
/// # use time_macros::date;
|
||||
/// assert_eq!(date!(2019 - 01 - 01).month(), Month::January);
|
||||
/// assert_eq!(date!(2019 - 12 - 31).month(), Month::December);
|
||||
/// ```
|
||||
|
@ -256,7 +256,7 @@ impl Date {
|
|||
/// The returned value will always be in the range `1..=31`.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::date;
|
||||
/// # use time_macros::date;
|
||||
/// assert_eq!(date!(2019 - 01 - 01).day(), 1);
|
||||
/// assert_eq!(date!(2019 - 12 - 31).day(), 31);
|
||||
/// ```
|
||||
|
@ -310,7 +310,7 @@ impl Date {
|
|||
/// The returned value will always be in the range `1..=366` (`1..=365` for common years).
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::date;
|
||||
/// # use time_macros::date;
|
||||
/// assert_eq!(date!(2019 - 01 - 01).ordinal(), 1);
|
||||
/// assert_eq!(date!(2019 - 12 - 31).ordinal(), 365);
|
||||
/// ```
|
||||
|
@ -334,7 +334,7 @@ impl Date {
|
|||
/// The returned value will always be in the range `1..=53`.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::date;
|
||||
/// # use time_macros::date;
|
||||
/// assert_eq!(date!(2019 - 01 - 01).iso_week(), 1);
|
||||
/// assert_eq!(date!(2019 - 10 - 04).iso_week(), 40);
|
||||
/// assert_eq!(date!(2020 - 01 - 01).iso_week(), 1);
|
||||
|
@ -350,7 +350,7 @@ impl Date {
|
|||
/// The returned value will always be in the range `0..=53`.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::date;
|
||||
/// # use time_macros::date;
|
||||
/// assert_eq!(date!(2019 - 01 - 01).sunday_based_week(), 0);
|
||||
/// assert_eq!(date!(2020 - 01 - 01).sunday_based_week(), 0);
|
||||
/// assert_eq!(date!(2020 - 12 - 31).sunday_based_week(), 52);
|
||||
|
@ -365,7 +365,7 @@ impl Date {
|
|||
/// The returned value will always be in the range `0..=53`.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::date;
|
||||
/// # use time_macros::date;
|
||||
/// assert_eq!(date!(2019 - 01 - 01).monday_based_week(), 0);
|
||||
/// assert_eq!(date!(2020 - 01 - 01).monday_based_week(), 0);
|
||||
/// assert_eq!(date!(2020 - 12 - 31).monday_based_week(), 52);
|
||||
|
@ -378,7 +378,8 @@ impl Date {
|
|||
/// Get the year, month, and day.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::{macros::date, Month};
|
||||
/// # use time::Month;
|
||||
/// # use time_macros::date;
|
||||
/// assert_eq!(
|
||||
/// date!(2019 - 01 - 01).to_calendar_date(),
|
||||
/// (2019, Month::January, 1)
|
||||
|
@ -392,7 +393,7 @@ impl Date {
|
|||
/// Get the year and ordinal day number.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::date;
|
||||
/// # use time_macros::date;
|
||||
/// assert_eq!(date!(2019 - 01 - 01).to_ordinal_date(), (2019, 1));
|
||||
/// ```
|
||||
pub const fn to_ordinal_date(self) -> (i32, u16) {
|
||||
|
@ -402,7 +403,8 @@ impl Date {
|
|||
/// Get the ISO 8601 year, week number, and weekday.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::{Weekday::*, macros::date};
|
||||
/// # use time::Weekday::*;
|
||||
/// # use time_macros::date;
|
||||
/// assert_eq!(date!(2019 - 01 - 01).to_iso_week_date(), (2019, 1, Tuesday));
|
||||
/// assert_eq!(date!(2019 - 10 - 04).to_iso_week_date(), (2019, 40, Friday));
|
||||
/// assert_eq!(
|
||||
|
@ -429,7 +431,8 @@ impl Date {
|
|||
/// Get the weekday.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::{Weekday::*, macros::date};
|
||||
/// # use time::Weekday::*;
|
||||
/// # use time_macros::date;
|
||||
/// assert_eq!(date!(2019 - 01 - 01).weekday(), Tuesday);
|
||||
/// assert_eq!(date!(2019 - 02 - 01).weekday(), Friday);
|
||||
/// assert_eq!(date!(2019 - 03 - 01).weekday(), Friday);
|
||||
|
@ -451,14 +454,18 @@ impl Date {
|
|||
-3 | 4 => Weekday::Friday,
|
||||
-2 | 5 => Weekday::Saturday,
|
||||
-1 | 6 => Weekday::Sunday,
|
||||
_ => Weekday::Monday,
|
||||
val => {
|
||||
debug_assert!(val == 0);
|
||||
Weekday::Monday
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the next calendar date.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::{Date, macros::date};
|
||||
/// # use time::Date;
|
||||
/// # use time_macros::date;
|
||||
/// assert_eq!(
|
||||
/// date!(2019 - 01 - 01).next_day(),
|
||||
/// Some(date!(2019 - 01 - 02))
|
||||
|
@ -490,7 +497,8 @@ impl Date {
|
|||
/// Get the previous calendar date.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::{Date, macros::date};
|
||||
/// # use time::Date;
|
||||
/// # use time_macros::date;
|
||||
/// assert_eq!(
|
||||
/// date!(2019 - 01 - 02).previous_day(),
|
||||
/// Some(date!(2019 - 01 - 01))
|
||||
|
@ -526,7 +534,7 @@ impl Date {
|
|||
/// freely available [here](https://www.researchgate.net/publication/316558298_Date_Algorithms).
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::date;
|
||||
/// # use time_macros::date;
|
||||
/// assert_eq!(date!(-4713 - 11 - 24).to_julian_day(), 0);
|
||||
/// assert_eq!(date!(2000 - 01 - 01).to_julian_day(), 2_451_545);
|
||||
/// assert_eq!(date!(2019 - 01 - 01).to_julian_day(), 2_458_485);
|
||||
|
@ -546,7 +554,8 @@ impl Date {
|
|||
/// Computes `self + duration`, returning `None` if an overflow occurred.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::{Date, ext::NumericalDuration, macros::date};
|
||||
/// # use time::{Date, ext::NumericalDuration};
|
||||
/// # use time_macros::date;
|
||||
/// assert_eq!(Date::MAX.checked_add(1.days()), None);
|
||||
/// assert_eq!(Date::MIN.checked_add((-2).days()), None);
|
||||
/// assert_eq!(
|
||||
|
@ -560,7 +569,8 @@ impl Date {
|
|||
/// This function only takes whole days into account.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::{Date, ext::NumericalDuration, macros::date};
|
||||
/// # use time::{Date, ext::NumericalDuration};
|
||||
/// # use time_macros::date;
|
||||
/// assert_eq!(Date::MAX.checked_add(23.hours()), Some(Date::MAX));
|
||||
/// assert_eq!(Date::MIN.checked_add((-23).hours()), Some(Date::MIN));
|
||||
/// assert_eq!(
|
||||
|
@ -589,7 +599,8 @@ impl Date {
|
|||
/// Computes `self - duration`, returning `None` if an overflow occurred.
|
||||
///
|
||||
/// ```
|
||||
/// # use time::{Date, ext::NumericalDuration, macros::date};
|
||||
/// # use time::{Date, ext::NumericalDuration};
|
||||
/// # use time_macros::date;
|
||||
/// assert_eq!(Date::MAX.checked_sub((-2).days()), None);
|
||||
/// assert_eq!(Date::MIN.checked_sub(1.days()), None);
|
||||
/// assert_eq!(
|
||||
|
@ -603,7 +614,8 @@ impl Date {
|
|||
/// This function only takes whole days into account.
|
||||
///
|
||||
/// ```
|
||||
/// # use time::{Date, ext::NumericalDuration, macros::date};
|
||||
/// # use time::{Date, ext::NumericalDuration};
|
||||
/// # use time_macros::date;
|
||||
/// assert_eq!(Date::MAX.checked_sub((-23).hours()), Some(Date::MAX));
|
||||
/// assert_eq!(Date::MIN.checked_sub(23.hours()), Some(Date::MIN));
|
||||
/// assert_eq!(
|
||||
|
@ -634,7 +646,8 @@ impl Date {
|
|||
/// Computes `self + duration`, saturating value on overflow.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::{Date, ext::NumericalDuration, macros::date};
|
||||
/// # use time::{Date, ext::NumericalDuration};
|
||||
/// # use time_macros::date;
|
||||
/// assert_eq!(Date::MAX.saturating_add(1.days()), Date::MAX);
|
||||
/// assert_eq!(Date::MIN.saturating_add((-2).days()), Date::MIN);
|
||||
/// assert_eq!(
|
||||
|
@ -648,7 +661,8 @@ impl Date {
|
|||
/// This function only takes whole days into account.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::{ext::NumericalDuration, macros::date};
|
||||
/// # use time::ext::NumericalDuration;
|
||||
/// # use time_macros::date;
|
||||
/// assert_eq!(
|
||||
/// date!(2020 - 12 - 31).saturating_add(23.hours()),
|
||||
/// date!(2020 - 12 - 31)
|
||||
|
@ -664,6 +678,7 @@ impl Date {
|
|||
} else if duration.is_negative() {
|
||||
Self::MIN
|
||||
} else {
|
||||
debug_assert!(duration.is_positive());
|
||||
Self::MAX
|
||||
}
|
||||
}
|
||||
|
@ -671,7 +686,8 @@ impl Date {
|
|||
/// Computes `self - duration`, saturating value on overflow.
|
||||
///
|
||||
/// ```
|
||||
/// # use time::{Date, ext::NumericalDuration, macros::date};
|
||||
/// # use time::{Date, ext::NumericalDuration};
|
||||
/// # use time_macros::date;
|
||||
/// assert_eq!(Date::MAX.saturating_sub((-2).days()), Date::MAX);
|
||||
/// assert_eq!(Date::MIN.saturating_sub(1.days()), Date::MIN);
|
||||
/// assert_eq!(
|
||||
|
@ -685,7 +701,8 @@ impl Date {
|
|||
/// This function only takes whole days into account.
|
||||
///
|
||||
/// ```
|
||||
/// # use time::{ext::NumericalDuration, macros::date};
|
||||
/// # use time::ext::NumericalDuration;
|
||||
/// # use time_macros::date;
|
||||
/// assert_eq!(
|
||||
/// date!(2020 - 12 - 31).saturating_sub(23.hours()),
|
||||
/// date!(2020 - 12 - 31)
|
||||
|
@ -701,6 +718,7 @@ impl Date {
|
|||
} else if duration.is_negative() {
|
||||
Self::MAX
|
||||
} else {
|
||||
debug_assert!(duration.is_positive());
|
||||
Self::MIN
|
||||
}
|
||||
}
|
||||
|
@ -710,7 +728,7 @@ impl Date {
|
|||
/// Replace the year. The month and day will be unchanged.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::date;
|
||||
/// # use time_macros::date;
|
||||
/// assert_eq!(
|
||||
/// date!(2022 - 02 - 18).replace_year(2019),
|
||||
/// Ok(date!(2019 - 02 - 18))
|
||||
|
@ -726,15 +744,11 @@ impl Date {
|
|||
|
||||
// Dates in January and February are unaffected by leap years.
|
||||
if ordinal <= 59 {
|
||||
return Ok(Self {
|
||||
value: year << 9 | ordinal as i32,
|
||||
});
|
||||
return Ok(Self::__from_ordinal_date_unchecked(year, ordinal));
|
||||
}
|
||||
|
||||
match (is_leap_year(self.year()), is_leap_year(year)) {
|
||||
(false, false) | (true, true) => Ok(Self {
|
||||
value: year << 9 | ordinal as i32,
|
||||
}),
|
||||
(false, false) | (true, true) => Ok(Self::__from_ordinal_date_unchecked(year, ordinal)),
|
||||
// February 29 does not exist in common years.
|
||||
(true, false) if ordinal == 60 => Err(error::ComponentRange {
|
||||
name: "day",
|
||||
|
@ -745,21 +759,17 @@ impl Date {
|
|||
}),
|
||||
// We're going from a common year to a leap year. Shift dates in March and later by
|
||||
// one day.
|
||||
(false, true) => Ok(Self {
|
||||
value: year << 9 | (ordinal + 1) as i32,
|
||||
}),
|
||||
(false, true) => Ok(Self::__from_ordinal_date_unchecked(year, ordinal + 1)),
|
||||
// We're going from a leap year to a common year. Shift dates in January and
|
||||
// February by one day.
|
||||
(true, false) => Ok(Self {
|
||||
value: year << 9 | (ordinal - 1) as i32,
|
||||
}),
|
||||
(true, false) => Ok(Self::__from_ordinal_date_unchecked(year, ordinal - 1)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Replace the month of the year.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::date;
|
||||
/// # use time_macros::date;
|
||||
/// # use time::Month;
|
||||
/// assert_eq!(
|
||||
/// date!(2022 - 02 - 18).replace_month(Month::January),
|
||||
|
@ -780,7 +790,7 @@ impl Date {
|
|||
/// Replace the day of the month.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::date;
|
||||
/// # use time_macros::date;
|
||||
/// assert_eq!(
|
||||
/// date!(2022 - 02 - 18).replace_day(1),
|
||||
/// Ok(date!(2022 - 02 - 01))
|
||||
|
@ -812,7 +822,7 @@ impl Date {
|
|||
/// to midnight.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::{date, datetime};
|
||||
/// # use time_macros::{date, datetime};
|
||||
/// assert_eq!(date!(1970-01-01).midnight(), datetime!(1970-01-01 0:00));
|
||||
/// ```
|
||||
pub const fn midnight(self) -> PrimitiveDateTime {
|
||||
|
@ -822,7 +832,7 @@ impl Date {
|
|||
/// Create a [`PrimitiveDateTime`] using the existing date and the provided [`Time`].
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::{date, datetime, time};
|
||||
/// # use time_macros::{date, datetime, time};
|
||||
/// assert_eq!(
|
||||
/// date!(1970-01-01).with_time(time!(0:00)),
|
||||
/// datetime!(1970-01-01 0:00),
|
||||
|
@ -835,7 +845,7 @@ impl Date {
|
|||
/// Attempt to create a [`PrimitiveDateTime`] using the existing date and the provided time.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::date;
|
||||
/// # use time_macros::date;
|
||||
/// assert!(date!(1970 - 01 - 01).with_hms(0, 0, 0).is_ok());
|
||||
/// assert!(date!(1970 - 01 - 01).with_hms(24, 0, 0).is_err());
|
||||
/// ```
|
||||
|
@ -854,7 +864,7 @@ impl Date {
|
|||
/// Attempt to create a [`PrimitiveDateTime`] using the existing date and the provided time.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::date;
|
||||
/// # use time_macros::date;
|
||||
/// assert!(date!(1970 - 01 - 01).with_hms_milli(0, 0, 0, 0).is_ok());
|
||||
/// assert!(date!(1970 - 01 - 01).with_hms_milli(24, 0, 0, 0).is_err());
|
||||
/// ```
|
||||
|
@ -874,7 +884,7 @@ impl Date {
|
|||
/// Attempt to create a [`PrimitiveDateTime`] using the existing date and the provided time.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::date;
|
||||
/// # use time_macros::date;
|
||||
/// assert!(date!(1970 - 01 - 01).with_hms_micro(0, 0, 0, 0).is_ok());
|
||||
/// assert!(date!(1970 - 01 - 01).with_hms_micro(24, 0, 0, 0).is_err());
|
||||
/// ```
|
||||
|
@ -894,7 +904,7 @@ impl Date {
|
|||
/// Attempt to create a [`PrimitiveDateTime`] using the existing date and the provided time.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::date;
|
||||
/// # use time_macros::date;
|
||||
/// assert!(date!(1970 - 01 - 01).with_hms_nano(0, 0, 0, 0).is_ok());
|
||||
/// assert!(date!(1970 - 01 - 01).with_hms_nano(24, 0, 0, 0).is_err());
|
||||
/// ```
|
||||
|
@ -928,7 +938,8 @@ impl Date {
|
|||
/// Format the `Date` using the provided [format description](crate::format_description).
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::{format_description, macros::date};
|
||||
/// # use time::{format_description};
|
||||
/// # use time_macros::date;
|
||||
/// let format = format_description::parse("[year]-[month]-[day]")?;
|
||||
/// assert_eq!(date!(2020 - 01 - 02).format(&format)?, "2020-01-02");
|
||||
/// # Ok::<_, time::Error>(())
|
||||
|
@ -944,8 +955,9 @@ impl Date {
|
|||
/// description](crate::format_description).
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::{format_description, macros::date, Date};
|
||||
/// let format = format_description::parse("[year]-[month]-[day]")?;
|
||||
/// # use time::Date;
|
||||
/// # use time_macros::{date, format_description};
|
||||
/// let format = format_description!("[year]-[month]-[day]");
|
||||
/// assert_eq!(Date::parse("2020-01-02", &format)?, date!(2020 - 01 - 02));
|
||||
/// # Ok::<_, time::Error>(())
|
||||
/// ```
|
||||
|
@ -979,6 +991,12 @@ impl fmt::Display for Date {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Date {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
fmt::Display::fmt(self, f)
|
||||
}
|
||||
}
|
||||
// endregion formatting & parsing
|
||||
|
||||
// region: trait impls
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
//! The [`Duration`] struct and its associated `impl`s.
|
||||
|
||||
use core::cmp::Ordering;
|
||||
use core::convert::{TryFrom, TryInto};
|
||||
use core::fmt;
|
||||
use core::iter::Sum;
|
||||
use core::ops::{Add, AddAssign, Div, Mul, Neg, Sub, SubAssign};
|
||||
|
@ -184,11 +183,16 @@ impl Duration {
|
|||
Self::new_unchecked(self.seconds.saturating_abs(), self.nanoseconds.abs())
|
||||
}
|
||||
|
||||
/// Convert the existing `Duration` to a `std::time::Duration` and its sign. This doesn't
|
||||
/// actually require the standard library, but is currently only used when it's enabled.
|
||||
#[allow(clippy::missing_const_for_fn)] // false positive
|
||||
#[cfg(feature = "std")]
|
||||
pub(crate) fn abs_std(self) -> StdDuration {
|
||||
/// Convert the existing `Duration` to a `std::time::Duration` and its sign. This returns a
|
||||
/// [`std::time::Duration`] and does not saturate the returned value (unlike [`Duration::abs`]).
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::ext::{NumericalDuration, NumericalStdDuration};
|
||||
/// assert_eq!(1.seconds().unsigned_abs(), 1.std_seconds());
|
||||
/// assert_eq!(0.seconds().unsigned_abs(), 0.std_seconds());
|
||||
/// assert_eq!((-1).seconds().unsigned_abs(), 1.std_seconds());
|
||||
/// ```
|
||||
pub const fn unsigned_abs(self) -> StdDuration {
|
||||
StdDuration::new(self.seconds.unsigned_abs(), self.nanoseconds.unsigned_abs())
|
||||
}
|
||||
// endregion abs
|
||||
|
@ -196,6 +200,16 @@ impl Duration {
|
|||
// region: constructors
|
||||
/// Create a new `Duration` without checking the validity of the components.
|
||||
pub(crate) const fn new_unchecked(seconds: i64, nanoseconds: i32) -> Self {
|
||||
if seconds < 0 {
|
||||
debug_assert!(nanoseconds <= 0);
|
||||
debug_assert!(nanoseconds > -1_000_000_000);
|
||||
} else if seconds > 0 {
|
||||
debug_assert!(nanoseconds >= 0);
|
||||
debug_assert!(nanoseconds < 1_000_000_000);
|
||||
} else {
|
||||
debug_assert!(nanoseconds.unsigned_abs() < 1_000_000_000);
|
||||
}
|
||||
|
||||
Self {
|
||||
seconds,
|
||||
nanoseconds,
|
||||
|
@ -213,13 +227,18 @@ impl Duration {
|
|||
/// assert_eq!(Duration::new(1, 2_000_000_000), 3.seconds());
|
||||
/// ```
|
||||
pub const fn new(mut seconds: i64, mut nanoseconds: i32) -> Self {
|
||||
seconds += nanoseconds as i64 / 1_000_000_000;
|
||||
seconds = expect_opt!(
|
||||
seconds.checked_add(nanoseconds as i64 / 1_000_000_000),
|
||||
"overflow constructing `time::Duration`"
|
||||
);
|
||||
nanoseconds %= 1_000_000_000;
|
||||
|
||||
if seconds > 0 && nanoseconds < 0 {
|
||||
// `seconds` cannot overflow here because it is positive.
|
||||
seconds -= 1;
|
||||
nanoseconds += 1_000_000_000;
|
||||
} else if seconds < 0 && nanoseconds > 0 {
|
||||
// `seconds` cannot overflow here because it is negative.
|
||||
seconds += 1;
|
||||
nanoseconds -= 1_000_000_000;
|
||||
}
|
||||
|
@ -235,7 +254,10 @@ impl Duration {
|
|||
/// assert_eq!(Duration::weeks(1), 604_800.seconds());
|
||||
/// ```
|
||||
pub const fn weeks(weeks: i64) -> Self {
|
||||
Self::seconds(weeks * 604_800)
|
||||
Self::seconds(expect_opt!(
|
||||
weeks.checked_mul(604_800),
|
||||
"overflow constructing `time::Duration`"
|
||||
))
|
||||
}
|
||||
|
||||
/// Create a new `Duration` with the given number of days. Equivalent to
|
||||
|
@ -246,7 +268,10 @@ impl Duration {
|
|||
/// assert_eq!(Duration::days(1), 86_400.seconds());
|
||||
/// ```
|
||||
pub const fn days(days: i64) -> Self {
|
||||
Self::seconds(days * 86_400)
|
||||
Self::seconds(expect_opt!(
|
||||
days.checked_mul(86_400),
|
||||
"overflow constructing `time::Duration`"
|
||||
))
|
||||
}
|
||||
|
||||
/// Create a new `Duration` with the given number of hours. Equivalent to
|
||||
|
@ -257,7 +282,10 @@ impl Duration {
|
|||
/// assert_eq!(Duration::hours(1), 3_600.seconds());
|
||||
/// ```
|
||||
pub const fn hours(hours: i64) -> Self {
|
||||
Self::seconds(hours * 3_600)
|
||||
Self::seconds(expect_opt!(
|
||||
hours.checked_mul(3_600),
|
||||
"overflow constructing `time::Duration`"
|
||||
))
|
||||
}
|
||||
|
||||
/// Create a new `Duration` with the given number of minutes. Equivalent to
|
||||
|
@ -268,7 +296,10 @@ impl Duration {
|
|||
/// assert_eq!(Duration::minutes(1), 60.seconds());
|
||||
/// ```
|
||||
pub const fn minutes(minutes: i64) -> Self {
|
||||
Self::seconds(minutes * 60)
|
||||
Self::seconds(expect_opt!(
|
||||
minutes.checked_mul(60),
|
||||
"overflow constructing `time::Duration`"
|
||||
))
|
||||
}
|
||||
|
||||
/// Create a new `Duration` with the given number of seconds.
|
||||
|
@ -289,6 +320,12 @@ impl Duration {
|
|||
/// assert_eq!(Duration::seconds_f64(-0.5), -0.5.seconds());
|
||||
/// ```
|
||||
pub fn seconds_f64(seconds: f64) -> Self {
|
||||
if seconds > i64::MAX as f64 || seconds < i64::MIN as f64 {
|
||||
crate::expect_failed("overflow constructing `time::Duration`");
|
||||
}
|
||||
if seconds.is_nan() {
|
||||
crate::expect_failed("passed NaN to `time::Duration::seconds_f64`");
|
||||
}
|
||||
Self::new_unchecked(seconds as _, ((seconds % 1.) * 1_000_000_000.) as _)
|
||||
}
|
||||
|
||||
|
@ -300,6 +337,12 @@ impl Duration {
|
|||
/// assert_eq!(Duration::seconds_f32(-0.5), (-0.5).seconds());
|
||||
/// ```
|
||||
pub fn seconds_f32(seconds: f32) -> Self {
|
||||
if seconds > i64::MAX as f32 || seconds < i64::MIN as f32 {
|
||||
crate::expect_failed("overflow constructing `time::Duration`");
|
||||
}
|
||||
if seconds.is_nan() {
|
||||
crate::expect_failed("passed NaN to `time::Duration::seconds_f32`");
|
||||
}
|
||||
Self::new_unchecked(seconds as _, ((seconds % 1.) * 1_000_000_000.) as _)
|
||||
}
|
||||
|
||||
|
@ -350,10 +393,14 @@ impl Duration {
|
|||
/// As the input range cannot be fully mapped to the output, this should only be used where it's
|
||||
/// known to result in a valid value.
|
||||
pub(crate) const fn nanoseconds_i128(nanoseconds: i128) -> Self {
|
||||
Self::new_unchecked(
|
||||
(nanoseconds / 1_000_000_000) as _,
|
||||
(nanoseconds % 1_000_000_000) as _,
|
||||
)
|
||||
let seconds = nanoseconds / 1_000_000_000;
|
||||
let nanoseconds = nanoseconds % 1_000_000_000;
|
||||
|
||||
if seconds > i64::MAX as i128 || seconds < i64::MIN as i128 {
|
||||
crate::expect_failed("overflow constructing `time::Duration`");
|
||||
}
|
||||
|
||||
Self::new_unchecked(seconds as _, nanoseconds as _)
|
||||
}
|
||||
// endregion constructors
|
||||
|
||||
|
@ -742,38 +789,81 @@ impl Duration {
|
|||
// region: trait impls
|
||||
/// The format returned by this implementation is not stable and must not be relied upon.
|
||||
///
|
||||
/// By default this produces an exact, full-precision printout of the duration.
|
||||
/// For a concise, rounded printout instead, you can use the `.N` format specifier:
|
||||
///
|
||||
/// ```
|
||||
/// # use time::Duration;
|
||||
/// #
|
||||
/// let duration = Duration::new(123456, 789011223);
|
||||
/// println!("{duration:.3}");
|
||||
/// ```
|
||||
///
|
||||
/// For the purposes of this implementation, a day is exactly 24 hours and a minute is exactly 60
|
||||
/// seconds.
|
||||
impl fmt::Display for Duration {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
/// Format a single item.
|
||||
macro_rules! item {
|
||||
($name:literal, $value:expr) => {
|
||||
match $value {
|
||||
0 => Ok(()),
|
||||
value => value.fmt(f).and(f.write_str($name)),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if self.is_zero() {
|
||||
return f.write_str("0s");
|
||||
}
|
||||
|
||||
let seconds = self.seconds.unsigned_abs();
|
||||
let nanoseconds = self.nanoseconds.unsigned_abs();
|
||||
|
||||
if self.is_negative() {
|
||||
f.write_str("-")?;
|
||||
}
|
||||
|
||||
item!("d", seconds / 86_400)?;
|
||||
item!("h", seconds / 3_600 % 24)?;
|
||||
item!("m", seconds / 60 % 60)?;
|
||||
item!("s", seconds % 60)?;
|
||||
item!("ms", nanoseconds / 1_000_000)?;
|
||||
item!("µs", nanoseconds / 1_000 % 1_000)?;
|
||||
item!("ns", nanoseconds % 1_000)?;
|
||||
if let Some(_precision) = f.precision() {
|
||||
// Concise, rounded representation.
|
||||
|
||||
if self.is_zero() {
|
||||
// Write a zero value with the requested precision.
|
||||
return (0.).fmt(f).and_then(|_| f.write_str("s"));
|
||||
}
|
||||
|
||||
/// Format the first item that produces a value greater than 1 and then break.
|
||||
macro_rules! item {
|
||||
($name:literal, $value:expr) => {
|
||||
let value = $value;
|
||||
if value >= 1.0 {
|
||||
return value.fmt(f).and_then(|_| f.write_str($name));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Even if this produces a de-normal float, because we're rounding we don't really care.
|
||||
let seconds = self.unsigned_abs().as_secs_f64();
|
||||
|
||||
item!("d", seconds / 86_400.);
|
||||
item!("h", seconds / 3_600.);
|
||||
item!("m", seconds / 60.);
|
||||
item!("s", seconds);
|
||||
item!("ms", seconds * 1_000.);
|
||||
item!("µs", seconds * 1_000_000.);
|
||||
item!("ns", seconds * 1_000_000_000.);
|
||||
} else {
|
||||
// Precise, but verbose representation.
|
||||
|
||||
if self.is_zero() {
|
||||
return f.write_str("0s");
|
||||
}
|
||||
|
||||
/// Format a single item.
|
||||
macro_rules! item {
|
||||
($name:literal, $value:expr) => {
|
||||
match $value {
|
||||
0 => Ok(()),
|
||||
value => value.fmt(f).and_then(|_| f.write_str($name)),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let seconds = self.seconds.unsigned_abs();
|
||||
let nanoseconds = self.nanoseconds.unsigned_abs();
|
||||
|
||||
item!("d", seconds / 86_400)?;
|
||||
item!("h", seconds / 3_600 % 24)?;
|
||||
item!("m", seconds / 60 % 60)?;
|
||||
item!("s", seconds % 60)?;
|
||||
item!("ms", nanoseconds / 1_000_000)?;
|
||||
item!("µs", nanoseconds / 1_000 % 1_000)?;
|
||||
item!("ns", nanoseconds % 1_000)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
//! Component range error
|
||||
|
||||
use core::convert::TryFrom;
|
||||
use core::fmt;
|
||||
|
||||
use crate::error;
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
//! Conversion range error
|
||||
|
||||
use core::convert::TryFrom;
|
||||
use core::fmt;
|
||||
|
||||
use crate::error;
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
//! Different variant error
|
||||
|
||||
use core::convert::TryFrom;
|
||||
use core::fmt;
|
||||
|
||||
/// An error type indicating that a [`TryFrom`](core::convert::TryFrom) call failed because the
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
//! Error formatting a struct
|
||||
|
||||
use core::convert::TryFrom;
|
||||
use core::fmt;
|
||||
use std::io;
|
||||
|
||||
|
@ -9,7 +8,6 @@ use crate::error;
|
|||
/// An error occurred when formatting.
|
||||
#[non_exhaustive]
|
||||
#[allow(missing_copy_implementations)]
|
||||
#[cfg_attr(__time_03_docs, doc(cfg(feature = "formatting")))]
|
||||
#[derive(Debug)]
|
||||
pub enum Format {
|
||||
/// The type being formatted does not contain sufficient information to format a component.
|
||||
|
@ -32,8 +30,7 @@ impl fmt::Display for Format {
|
|||
),
|
||||
Self::InvalidComponent(component) => write!(
|
||||
f,
|
||||
"The {} component cannot be formatted into the requested format.",
|
||||
component
|
||||
"The {component} component cannot be formatted into the requested format."
|
||||
),
|
||||
Self::StdIo(err) => err.fmt(f),
|
||||
}
|
||||
|
@ -67,14 +64,12 @@ impl std::error::Error for Format {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(__time_03_docs, doc(cfg(feature = "formatting")))]
|
||||
impl From<Format> for crate::Error {
|
||||
fn from(original: Format) -> Self {
|
||||
Self::Format(original)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(__time_03_docs, doc(cfg(feature = "formatting")))]
|
||||
impl TryFrom<crate::Error> for Format {
|
||||
type Error = error::DifferentVariant;
|
||||
|
||||
|
@ -92,6 +87,6 @@ impl Format {
|
|||
#[doc(hidden)] // Exposed only for the `declare_format_string` macro
|
||||
pub fn into_invalid_serde_value<S: serde::Serializer>(self) -> S::Error {
|
||||
use serde::ser::Error;
|
||||
S::Error::custom(format!("{}", self))
|
||||
S::Error::custom(self)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
//! Indeterminate offset
|
||||
|
||||
use core::convert::TryFrom;
|
||||
use core::fmt;
|
||||
|
||||
use crate::error;
|
||||
|
||||
/// The system's UTC offset could not be determined at the given datetime.
|
||||
#[cfg_attr(__time_03_docs, doc(cfg(feature = "local-offset")))]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct IndeterminateOffset;
|
||||
|
||||
|
@ -19,14 +17,12 @@ impl fmt::Display for IndeterminateOffset {
|
|||
#[cfg(feature = "std")]
|
||||
impl std::error::Error for IndeterminateOffset {}
|
||||
|
||||
#[cfg_attr(__time_03_docs, doc(cfg(feature = "local-offset")))]
|
||||
impl From<IndeterminateOffset> for crate::Error {
|
||||
fn from(err: IndeterminateOffset) -> Self {
|
||||
Self::IndeterminateOffset(err)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(__time_03_docs, doc(cfg(feature = "std")))]
|
||||
impl TryFrom<crate::Error> for IndeterminateOffset {
|
||||
type Error = error::DifferentVariant;
|
||||
|
||||
|
|
|
@ -1,16 +1,11 @@
|
|||
//! Invalid format description
|
||||
|
||||
use alloc::string::String;
|
||||
use core::convert::TryFrom;
|
||||
use core::fmt;
|
||||
|
||||
use crate::error;
|
||||
|
||||
/// The format description provided was not valid.
|
||||
#[cfg_attr(
|
||||
__time_03_docs,
|
||||
doc(cfg(all(any(feature = "formatting", feature = "parsing"), feature = "alloc")))
|
||||
)]
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum InvalidFormatDescription {
|
||||
|
@ -44,20 +39,12 @@ pub enum InvalidFormatDescription {
|
|||
},
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
__time_03_docs,
|
||||
doc(cfg(all(any(feature = "formatting", feature = "parsing"), feature = "alloc")))
|
||||
)]
|
||||
impl From<InvalidFormatDescription> for crate::Error {
|
||||
fn from(original: InvalidFormatDescription) -> Self {
|
||||
Self::InvalidFormatDescription(original)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
__time_03_docs,
|
||||
doc(cfg(all(any(feature = "formatting", feature = "parsing"), feature = "alloc")))
|
||||
)]
|
||||
impl TryFrom<crate::Error> for InvalidFormatDescription {
|
||||
type Error = error::DifferentVariant;
|
||||
|
||||
|
@ -74,18 +61,16 @@ impl fmt::Display for InvalidFormatDescription {
|
|||
use InvalidFormatDescription::*;
|
||||
match self {
|
||||
UnclosedOpeningBracket { index } => {
|
||||
write!(f, "unclosed opening bracket at byte index {}", index)
|
||||
write!(f, "unclosed opening bracket at byte index {index}")
|
||||
}
|
||||
InvalidComponentName { name, index } => {
|
||||
write!(f, "invalid component name `{name}` at byte index {index}")
|
||||
}
|
||||
InvalidComponentName { name, index } => write!(
|
||||
f,
|
||||
"invalid component name `{}` at byte index {}",
|
||||
name, index
|
||||
),
|
||||
InvalidModifier { value, index } => {
|
||||
write!(f, "invalid modifier `{}` at byte index {}", value, index)
|
||||
write!(f, "invalid modifier `{value}` at byte index {index}")
|
||||
}
|
||||
MissingComponentName { index } => {
|
||||
write!(f, "missing component name at byte index {}", index)
|
||||
write!(f, "missing component name at byte index {index}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
//! Invalid variant error
|
||||
|
||||
use core::convert::TryFrom;
|
||||
use core::fmt;
|
||||
|
||||
/// An error type indicating that a [`FromStr`](core::str::FromStr) call failed because the value
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
//! Error that occurred at some stage of parsing
|
||||
|
||||
use core::convert::TryFrom;
|
||||
use core::fmt;
|
||||
|
||||
use crate::error::{self, ParseFromDescription, TryFromParsed};
|
||||
|
||||
/// An error that occurred at some stage of parsing.
|
||||
#[cfg_attr(__time_03_docs, doc(cfg(feature = "parsing")))]
|
||||
#[allow(variant_size_differences)]
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
|
@ -41,14 +39,12 @@ impl std::error::Error for Parse {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(__time_03_docs, doc(cfg(feature = "parsing")))]
|
||||
impl From<TryFromParsed> for Parse {
|
||||
fn from(err: TryFromParsed) -> Self {
|
||||
Self::TryFromParsed(err)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(__time_03_docs, doc(cfg(feature = "parsing")))]
|
||||
impl TryFrom<Parse> for TryFromParsed {
|
||||
type Error = error::DifferentVariant;
|
||||
|
||||
|
@ -60,14 +56,12 @@ impl TryFrom<Parse> for TryFromParsed {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(__time_03_docs, doc(cfg(feature = "parsing")))]
|
||||
impl From<ParseFromDescription> for Parse {
|
||||
fn from(err: ParseFromDescription) -> Self {
|
||||
Self::ParseFromDescription(err)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(__time_03_docs, doc(cfg(feature = "parsing")))]
|
||||
impl TryFrom<Parse> for ParseFromDescription {
|
||||
type Error = error::DifferentVariant;
|
||||
|
||||
|
@ -79,7 +73,6 @@ impl TryFrom<Parse> for ParseFromDescription {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(__time_03_docs, doc(cfg(feature = "parsing")))]
|
||||
impl From<Parse> for crate::Error {
|
||||
fn from(err: Parse) -> Self {
|
||||
match err {
|
||||
|
@ -90,7 +83,6 @@ impl From<Parse> for crate::Error {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(__time_03_docs, doc(cfg(feature = "parsing")))]
|
||||
impl TryFrom<crate::Error> for Parse {
|
||||
type Error = error::DifferentVariant;
|
||||
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
//! Error parsing an input into a [`Parsed`](crate::parsing::Parsed) struct
|
||||
|
||||
use core::convert::TryFrom;
|
||||
use core::fmt;
|
||||
|
||||
use crate::error;
|
||||
|
||||
/// An error that occurred while parsing the input into a [`Parsed`](crate::parsing::Parsed) struct.
|
||||
#[cfg_attr(__time_03_docs, doc(cfg(feature = "parsing")))]
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ParseFromDescription {
|
||||
|
@ -22,7 +20,7 @@ impl fmt::Display for ParseFromDescription {
|
|||
match self {
|
||||
Self::InvalidLiteral => f.write_str("a character literal was not valid"),
|
||||
Self::InvalidComponent(name) => {
|
||||
write!(f, "the '{}' component could not be parsed", name)
|
||||
write!(f, "the '{name}' component could not be parsed")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -31,14 +29,12 @@ impl fmt::Display for ParseFromDescription {
|
|||
#[cfg(feature = "std")]
|
||||
impl std::error::Error for ParseFromDescription {}
|
||||
|
||||
#[cfg_attr(__time_03_docs, doc(cfg(feature = "parsing")))]
|
||||
impl From<ParseFromDescription> for crate::Error {
|
||||
fn from(original: ParseFromDescription) -> Self {
|
||||
Self::ParseFromDescription(original)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(__time_03_docs, doc(cfg(feature = "parsing")))]
|
||||
impl TryFrom<crate::Error> for ParseFromDescription {
|
||||
type Error = error::DifferentVariant;
|
||||
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
//! Error converting a [`Parsed`](crate::parsing::Parsed) struct to another type
|
||||
|
||||
use core::convert::TryFrom;
|
||||
use core::fmt;
|
||||
|
||||
use crate::error;
|
||||
|
||||
/// An error that occurred when converting a [`Parsed`](crate::parsing::Parsed) to another type.
|
||||
#[non_exhaustive]
|
||||
#[cfg_attr(__time_03_docs, doc(cfg(feature = "parsing")))]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum TryFromParsed {
|
||||
/// The [`Parsed`](crate::parsing::Parsed) did not include enough information to construct the
|
||||
|
@ -55,14 +53,12 @@ impl std::error::Error for TryFromParsed {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(__time_03_docs, doc(cfg(feature = "parsing")))]
|
||||
impl From<TryFromParsed> for crate::Error {
|
||||
fn from(original: TryFromParsed) -> Self {
|
||||
Self::TryFromParsed(original)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(__time_03_docs, doc(cfg(feature = "parsing")))]
|
||||
impl TryFrom<crate::Error> for TryFromParsed {
|
||||
type Error = error::DifferentVariant;
|
||||
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
//! A format item with borrowed data.
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
use alloc::string::String;
|
||||
#[cfg(feature = "alloc")]
|
||||
use core::fmt;
|
||||
|
||||
use crate::error;
|
||||
use crate::format_description::Component;
|
||||
|
||||
/// A complete description of how to format and parse a type.
|
||||
#[non_exhaustive]
|
||||
#[cfg_attr(not(feature = "alloc"), derive(Debug))]
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
pub enum BorrowedFormatItem<'a> {
|
||||
/// Bytes that are formatted as-is.
|
||||
///
|
||||
/// **Note**: If you call the `format` method that returns a `String`, these bytes will be
|
||||
/// passed through `String::from_utf8_lossy`.
|
||||
Literal(&'a [u8]),
|
||||
/// A minimal representation of a single non-literal item.
|
||||
Component(Component),
|
||||
/// A series of literals or components that collectively form a partial or complete
|
||||
/// description.
|
||||
Compound(&'a [Self]),
|
||||
/// A `FormatItem` that may or may not be present when parsing. If parsing fails, there
|
||||
/// will be no effect on the resulting `struct`.
|
||||
///
|
||||
/// This variant has no effect on formatting, as the value is guaranteed to be present.
|
||||
Optional(&'a Self),
|
||||
/// A series of `FormatItem`s where, when parsing, the first successful parse is used. When
|
||||
/// formatting, the first element of the slice is used. An empty slice is a no-op when
|
||||
/// formatting or parsing.
|
||||
First(&'a [Self]),
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl fmt::Debug for BorrowedFormatItem<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Literal(literal) => f.write_str(&String::from_utf8_lossy(literal)),
|
||||
Self::Component(component) => component.fmt(f),
|
||||
Self::Compound(compound) => compound.fmt(f),
|
||||
Self::Optional(item) => f.debug_tuple("Optional").field(item).finish(),
|
||||
Self::First(items) => f.debug_tuple("First").field(items).finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Component> for BorrowedFormatItem<'_> {
|
||||
fn from(component: Component) -> Self {
|
||||
Self::Component(component)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<BorrowedFormatItem<'_>> for Component {
|
||||
type Error = error::DifferentVariant;
|
||||
|
||||
fn try_from(value: BorrowedFormatItem<'_>) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
BorrowedFormatItem::Component(component) => Ok(component),
|
||||
_ => Err(error::DifferentVariant),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a [BorrowedFormatItem<'_>]> for BorrowedFormatItem<'a> {
|
||||
fn from(items: &'a [BorrowedFormatItem<'_>]) -> Self {
|
||||
Self::Compound(items)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<BorrowedFormatItem<'a>> for &[BorrowedFormatItem<'a>] {
|
||||
type Error = error::DifferentVariant;
|
||||
|
||||
fn try_from(value: BorrowedFormatItem<'a>) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
BorrowedFormatItem::Compound(items) => Ok(items),
|
||||
_ => Err(error::DifferentVariant),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<Component> for BorrowedFormatItem<'_> {
|
||||
fn eq(&self, rhs: &Component) -> bool {
|
||||
matches!(self, Self::Component(component) if component == rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<BorrowedFormatItem<'_>> for Component {
|
||||
fn eq(&self, rhs: &BorrowedFormatItem<'_>) -> bool {
|
||||
rhs == self
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<&[Self]> for BorrowedFormatItem<'_> {
|
||||
fn eq(&self, rhs: &&[Self]) -> bool {
|
||||
matches!(self, Self::Compound(compound) if compound == rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<BorrowedFormatItem<'_>> for &[BorrowedFormatItem<'_>] {
|
||||
fn eq(&self, rhs: &BorrowedFormatItem<'_>) -> bool {
|
||||
rhs == self
|
||||
}
|
||||
}
|
|
@ -1,11 +1,6 @@
|
|||
//! Part of a format description.
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
use alloc::string::String;
|
||||
|
||||
use crate::format_description::modifier;
|
||||
#[cfg(feature = "alloc")]
|
||||
use crate::{error::InvalidFormatDescription, format_description::modifier::Modifiers};
|
||||
|
||||
/// A component of a larger format description.
|
||||
#[non_exhaustive]
|
||||
|
@ -40,128 +35,3 @@ pub enum Component {
|
|||
/// Second within the minute of the UTC offset.
|
||||
OffsetSecond(modifier::OffsetSecond),
|
||||
}
|
||||
|
||||
/// A component with no modifiers present.
|
||||
#[cfg(feature = "alloc")]
|
||||
pub(crate) enum NakedComponent {
|
||||
/// Day of the month.
|
||||
Day,
|
||||
/// Month of the year.
|
||||
Month,
|
||||
/// Ordinal day of the year.
|
||||
Ordinal,
|
||||
/// Day of the week.
|
||||
Weekday,
|
||||
/// Week within the year.
|
||||
WeekNumber,
|
||||
/// Year of the date.
|
||||
Year,
|
||||
/// Hour of the day.
|
||||
Hour,
|
||||
/// Minute within the hour.
|
||||
Minute,
|
||||
/// AM/PM part of the time.
|
||||
Period,
|
||||
/// Second within the minute.
|
||||
Second,
|
||||
/// Subsecond within the second.
|
||||
Subsecond,
|
||||
/// Hour of the UTC offset.
|
||||
OffsetHour,
|
||||
/// Minute within the hour of the UTC offset.
|
||||
OffsetMinute,
|
||||
/// Second within the minute of the UTC offset.
|
||||
OffsetSecond,
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl NakedComponent {
|
||||
/// Parse a component (without its modifiers) from the provided name.
|
||||
pub(crate) fn parse(
|
||||
component_name: &[u8],
|
||||
component_index: usize,
|
||||
) -> Result<Self, InvalidFormatDescription> {
|
||||
match component_name {
|
||||
b"day" => Ok(Self::Day),
|
||||
b"month" => Ok(Self::Month),
|
||||
b"ordinal" => Ok(Self::Ordinal),
|
||||
b"weekday" => Ok(Self::Weekday),
|
||||
b"week_number" => Ok(Self::WeekNumber),
|
||||
b"year" => Ok(Self::Year),
|
||||
b"hour" => Ok(Self::Hour),
|
||||
b"minute" => Ok(Self::Minute),
|
||||
b"period" => Ok(Self::Period),
|
||||
b"second" => Ok(Self::Second),
|
||||
b"subsecond" => Ok(Self::Subsecond),
|
||||
b"offset_hour" => Ok(Self::OffsetHour),
|
||||
b"offset_minute" => Ok(Self::OffsetMinute),
|
||||
b"offset_second" => Ok(Self::OffsetSecond),
|
||||
b"" => Err(InvalidFormatDescription::MissingComponentName {
|
||||
index: component_index,
|
||||
}),
|
||||
_ => Err(InvalidFormatDescription::InvalidComponentName {
|
||||
name: String::from_utf8_lossy(component_name).into_owned(),
|
||||
index: component_index,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Attach the necessary modifiers to the component.
|
||||
pub(crate) fn attach_modifiers(self, modifiers: &Modifiers) -> Component {
|
||||
match self {
|
||||
Self::Day => Component::Day(modifier::Day {
|
||||
padding: modifiers.padding.unwrap_or_default(),
|
||||
}),
|
||||
Self::Month => Component::Month(modifier::Month {
|
||||
padding: modifiers.padding.unwrap_or_default(),
|
||||
repr: modifiers.month_repr.unwrap_or_default(),
|
||||
case_sensitive: modifiers.case_sensitive.unwrap_or(true),
|
||||
}),
|
||||
Self::Ordinal => Component::Ordinal(modifier::Ordinal {
|
||||
padding: modifiers.padding.unwrap_or_default(),
|
||||
}),
|
||||
Self::Weekday => Component::Weekday(modifier::Weekday {
|
||||
repr: modifiers.weekday_repr.unwrap_or_default(),
|
||||
one_indexed: modifiers.weekday_is_one_indexed.unwrap_or(true),
|
||||
case_sensitive: modifiers.case_sensitive.unwrap_or(true),
|
||||
}),
|
||||
Self::WeekNumber => Component::WeekNumber(modifier::WeekNumber {
|
||||
padding: modifiers.padding.unwrap_or_default(),
|
||||
repr: modifiers.week_number_repr.unwrap_or_default(),
|
||||
}),
|
||||
Self::Year => Component::Year(modifier::Year {
|
||||
padding: modifiers.padding.unwrap_or_default(),
|
||||
repr: modifiers.year_repr.unwrap_or_default(),
|
||||
iso_week_based: modifiers.year_is_iso_week_based.unwrap_or_default(),
|
||||
sign_is_mandatory: modifiers.sign_is_mandatory.unwrap_or_default(),
|
||||
}),
|
||||
Self::Hour => Component::Hour(modifier::Hour {
|
||||
padding: modifiers.padding.unwrap_or_default(),
|
||||
is_12_hour_clock: modifiers.hour_is_12_hour_clock.unwrap_or_default(),
|
||||
}),
|
||||
Self::Minute => Component::Minute(modifier::Minute {
|
||||
padding: modifiers.padding.unwrap_or_default(),
|
||||
}),
|
||||
Self::Period => Component::Period(modifier::Period {
|
||||
is_uppercase: modifiers.period_is_uppercase.unwrap_or(true),
|
||||
case_sensitive: modifiers.case_sensitive.unwrap_or(true),
|
||||
}),
|
||||
Self::Second => Component::Second(modifier::Second {
|
||||
padding: modifiers.padding.unwrap_or_default(),
|
||||
}),
|
||||
Self::Subsecond => Component::Subsecond(modifier::Subsecond {
|
||||
digits: modifiers.subsecond_digits.unwrap_or_default(),
|
||||
}),
|
||||
Self::OffsetHour => Component::OffsetHour(modifier::OffsetHour {
|
||||
sign_is_mandatory: modifiers.sign_is_mandatory.unwrap_or_default(),
|
||||
padding: modifiers.padding.unwrap_or_default(),
|
||||
}),
|
||||
Self::OffsetMinute => Component::OffsetMinute(modifier::OffsetMinute {
|
||||
padding: modifiers.padding.unwrap_or_default(),
|
||||
}),
|
||||
Self::OffsetSecond => Component::OffsetSecond(modifier::OffsetSecond {
|
||||
padding: modifiers.padding.unwrap_or_default(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,182 +5,30 @@
|
|||
//! [`format_description!`](crate::macros::format_description) macro, the
|
||||
//! [`format_description::parse`](crate::format_description::parse()) function.
|
||||
|
||||
mod borrowed_format_item;
|
||||
mod component;
|
||||
pub mod modifier;
|
||||
#[cfg(feature = "alloc")]
|
||||
pub(crate) mod parse;
|
||||
mod owned_format_item;
|
||||
#[cfg(feature = "alloc")]
|
||||
mod parse;
|
||||
|
||||
pub use borrowed_format_item::BorrowedFormatItem as FormatItem;
|
||||
#[cfg(feature = "alloc")]
|
||||
use alloc::string::String;
|
||||
use core::convert::TryFrom;
|
||||
#[cfg(feature = "alloc")]
|
||||
use core::fmt;
|
||||
pub use owned_format_item::OwnedFormatItem;
|
||||
|
||||
pub use self::component::Component;
|
||||
#[cfg(feature = "alloc")]
|
||||
pub use self::parse::parse;
|
||||
use crate::error;
|
||||
pub use self::parse::{parse, parse_owned};
|
||||
|
||||
/// Helper methods.
|
||||
#[cfg(feature = "alloc")]
|
||||
mod helper {
|
||||
/// Consume all leading whitespace, advancing `index` as appropriate.
|
||||
#[must_use = "This does not modify the original slice."]
|
||||
pub(crate) fn consume_whitespace<'a>(bytes: &'a [u8], index: &mut usize) -> &'a [u8] {
|
||||
let first_non_whitespace = bytes
|
||||
.iter()
|
||||
.position(|c| !c.is_ascii_whitespace())
|
||||
.unwrap_or(bytes.len());
|
||||
*index += first_non_whitespace;
|
||||
&bytes[first_non_whitespace..]
|
||||
}
|
||||
}
|
||||
|
||||
/// Well-known formats, typically RFCs.
|
||||
/// Well-known formats, typically standards.
|
||||
pub mod well_known {
|
||||
/// The format described in [RFC 3339](https://tools.ietf.org/html/rfc3339#section-5.6).
|
||||
///
|
||||
/// Format example: 1985-04-12T23:20:50.52Z
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::{format_description::well_known::Rfc3339, macros::datetime, OffsetDateTime};
|
||||
/// assert_eq!(
|
||||
/// OffsetDateTime::parse("1985-04-12T23:20:50.52Z", &Rfc3339)?,
|
||||
/// datetime!(1985-04-12 23:20:50.52 +00:00)
|
||||
/// );
|
||||
/// # Ok::<_, time::Error>(())
|
||||
/// ```
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::{format_description::well_known::Rfc3339, macros::datetime};
|
||||
/// assert_eq!(
|
||||
/// datetime!(1985-04-12 23:20:50.52 +00:00).format(&Rfc3339)?,
|
||||
/// "1985-04-12T23:20:50.52Z"
|
||||
/// );
|
||||
/// # Ok::<_, time::Error>(())
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Rfc3339;
|
||||
pub mod iso8601;
|
||||
mod rfc2822;
|
||||
mod rfc3339;
|
||||
|
||||
/// The format described in [RFC 2822](https://tools.ietf.org/html/rfc2822#section-3.3).
|
||||
///
|
||||
/// Example: Fri, 21 Nov 1997 09:55:06 -0600
|
||||
///
|
||||
/// # Examples
|
||||
/// ```rust
|
||||
/// # use time::{format_description::well_known::Rfc2822, macros::datetime, OffsetDateTime};
|
||||
/// assert_eq!(
|
||||
/// OffsetDateTime::parse("Sat, 12 Jun 1993 13:25:19 GMT", &Rfc2822)?,
|
||||
/// datetime!(1993-06-12 13:25:19 +00:00)
|
||||
/// );
|
||||
/// # Ok::<_, time::Error>(())
|
||||
/// ```
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::{format_description::well_known::Rfc2822, macros::datetime};
|
||||
/// assert_eq!(
|
||||
/// datetime!(1997-11-21 09:55:06 -06:00).format(&Rfc2822)?,
|
||||
/// "Fri, 21 Nov 1997 09:55:06 -0600"
|
||||
/// );
|
||||
/// # Ok::<_, time::Error>(())
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Rfc2822;
|
||||
}
|
||||
|
||||
/// A complete description of how to format and parse a type.
|
||||
#[non_exhaustive]
|
||||
#[cfg_attr(not(feature = "alloc"), derive(Debug))]
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
pub enum FormatItem<'a> {
|
||||
/// Bytes that are formatted as-is.
|
||||
///
|
||||
/// **Note**: If you call the `format` method that returns a `String`, these bytes will be
|
||||
/// passed through `String::from_utf8_lossy`.
|
||||
Literal(&'a [u8]),
|
||||
/// A minimal representation of a single non-literal item.
|
||||
Component(Component),
|
||||
/// A series of literals or components that collectively form a partial or complete
|
||||
/// description.
|
||||
Compound(&'a [Self]),
|
||||
/// A `FormatItem` that may or may not be present when parsing. If parsing fails, there will be
|
||||
/// no effect on the resulting `struct`.
|
||||
///
|
||||
/// This variant has no effect on formatting, as the value is guaranteed to be present.
|
||||
Optional(&'a Self),
|
||||
/// A series of `FormatItem`s where, when parsing, the first successful parse is used. When
|
||||
/// formatting, the first element of the slice is used. An empty slice is a no-op when
|
||||
/// formatting or parsing.
|
||||
First(&'a [Self]),
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl fmt::Debug for FormatItem<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
FormatItem::Literal(literal) => f.write_str(&String::from_utf8_lossy(literal)),
|
||||
FormatItem::Component(component) => component.fmt(f),
|
||||
FormatItem::Compound(compound) => compound.fmt(f),
|
||||
FormatItem::Optional(item) => f.debug_tuple("Optional").field(item).finish(),
|
||||
FormatItem::First(items) => f.debug_tuple("First").field(items).finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Component> for FormatItem<'_> {
|
||||
fn from(component: Component) -> Self {
|
||||
Self::Component(component)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<FormatItem<'_>> for Component {
|
||||
type Error = error::DifferentVariant;
|
||||
|
||||
fn try_from(value: FormatItem<'_>) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
FormatItem::Component(component) => Ok(component),
|
||||
_ => Err(error::DifferentVariant),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a [FormatItem<'_>]> for FormatItem<'a> {
|
||||
fn from(items: &'a [FormatItem<'_>]) -> FormatItem<'a> {
|
||||
FormatItem::Compound(items)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<FormatItem<'a>> for &[FormatItem<'a>] {
|
||||
type Error = error::DifferentVariant;
|
||||
|
||||
fn try_from(value: FormatItem<'a>) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
FormatItem::Compound(items) => Ok(items),
|
||||
_ => Err(error::DifferentVariant),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<Component> for FormatItem<'_> {
|
||||
fn eq(&self, rhs: &Component) -> bool {
|
||||
matches!(self, FormatItem::Component(component) if component == rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<FormatItem<'_>> for Component {
|
||||
fn eq(&self, rhs: &FormatItem<'_>) -> bool {
|
||||
rhs == self
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<&[FormatItem<'_>]> for FormatItem<'_> {
|
||||
fn eq(&self, rhs: &&[FormatItem<'_>]) -> bool {
|
||||
matches!(self, FormatItem::Compound(compound) if compound == rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<FormatItem<'_>> for &[FormatItem<'_>] {
|
||||
fn eq(&self, rhs: &FormatItem<'_>) -> bool {
|
||||
rhs == self
|
||||
}
|
||||
#[doc(inline)]
|
||||
pub use iso8601::Iso8601;
|
||||
pub use rfc2822::Rfc2822;
|
||||
pub use rfc3339::Rfc3339;
|
||||
}
|
||||
|
|
|
@ -1,13 +1,5 @@
|
|||
//! Various modifiers for components.
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
use alloc::string::String;
|
||||
#[cfg(feature = "alloc")]
|
||||
use core::mem;
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
use crate::{error::InvalidFormatDescription, format_description::helper};
|
||||
|
||||
// region: date modifiers
|
||||
/// Day of the month.
|
||||
#[non_exhaustive]
|
||||
|
@ -361,149 +353,3 @@ impl_const_default! {
|
|||
/// Creates a modifier that indicates the value is [padded with zeroes](Self::Zero).
|
||||
Padding => Self::Zero;
|
||||
}
|
||||
|
||||
/// The modifiers parsed for any given component. `None` indicates the modifier was not present.
|
||||
#[cfg(feature = "alloc")]
|
||||
#[allow(clippy::missing_docs_in_private_items)] // fields
|
||||
#[derive(Debug, Default)]
|
||||
pub(crate) struct Modifiers {
|
||||
pub(crate) padding: Option<Padding>,
|
||||
pub(crate) hour_is_12_hour_clock: Option<bool>,
|
||||
pub(crate) period_is_uppercase: Option<bool>,
|
||||
pub(crate) month_repr: Option<MonthRepr>,
|
||||
pub(crate) subsecond_digits: Option<SubsecondDigits>,
|
||||
pub(crate) weekday_repr: Option<WeekdayRepr>,
|
||||
pub(crate) weekday_is_one_indexed: Option<bool>,
|
||||
pub(crate) week_number_repr: Option<WeekNumberRepr>,
|
||||
pub(crate) year_repr: Option<YearRepr>,
|
||||
pub(crate) year_is_iso_week_based: Option<bool>,
|
||||
pub(crate) sign_is_mandatory: Option<bool>,
|
||||
pub(crate) case_sensitive: Option<bool>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl Modifiers {
|
||||
/// Parse the modifiers of a given component.
|
||||
#[allow(clippy::too_many_lines)]
|
||||
pub(crate) fn parse(
|
||||
component_name: &[u8],
|
||||
mut bytes: &[u8],
|
||||
index: &mut usize,
|
||||
) -> Result<Self, InvalidFormatDescription> {
|
||||
let mut modifiers = Self::default();
|
||||
|
||||
while !bytes.is_empty() {
|
||||
// Trim any whitespace between modifiers.
|
||||
bytes = helper::consume_whitespace(bytes, index);
|
||||
|
||||
let modifier;
|
||||
if let Some(whitespace_loc) = bytes.iter().position(u8::is_ascii_whitespace) {
|
||||
*index += whitespace_loc;
|
||||
modifier = &bytes[..whitespace_loc];
|
||||
bytes = &bytes[whitespace_loc..];
|
||||
} else {
|
||||
modifier = mem::take(&mut bytes);
|
||||
}
|
||||
|
||||
if modifier.is_empty() {
|
||||
break;
|
||||
}
|
||||
|
||||
match (component_name, modifier) {
|
||||
(
|
||||
b"day" | b"hour" | b"minute" | b"month" | b"offset_hour" | b"offset_minute"
|
||||
| b"offset_second" | b"ordinal" | b"second" | b"week_number" | b"year",
|
||||
b"padding:space",
|
||||
) => modifiers.padding = Some(Padding::Space),
|
||||
(
|
||||
b"day" | b"hour" | b"minute" | b"month" | b"offset_hour" | b"offset_minute"
|
||||
| b"offset_second" | b"ordinal" | b"second" | b"week_number" | b"year",
|
||||
b"padding:zero",
|
||||
) => modifiers.padding = Some(Padding::Zero),
|
||||
(
|
||||
b"day" | b"hour" | b"minute" | b"month" | b"offset_hour" | b"offset_minute"
|
||||
| b"offset_second" | b"ordinal" | b"second" | b"week_number" | b"year",
|
||||
b"padding:none",
|
||||
) => modifiers.padding = Some(Padding::None),
|
||||
(b"hour", b"repr:24") => modifiers.hour_is_12_hour_clock = Some(false),
|
||||
(b"hour", b"repr:12") => modifiers.hour_is_12_hour_clock = Some(true),
|
||||
(b"month" | b"period" | b"weekday", b"case_sensitive:true") => {
|
||||
modifiers.case_sensitive = Some(true)
|
||||
}
|
||||
(b"month" | b"period" | b"weekday", b"case_sensitive:false") => {
|
||||
modifiers.case_sensitive = Some(false)
|
||||
}
|
||||
(b"month", b"repr:numerical") => modifiers.month_repr = Some(MonthRepr::Numerical),
|
||||
(b"month", b"repr:long") => modifiers.month_repr = Some(MonthRepr::Long),
|
||||
(b"month", b"repr:short") => modifiers.month_repr = Some(MonthRepr::Short),
|
||||
(b"offset_hour" | b"year", b"sign:automatic") => {
|
||||
modifiers.sign_is_mandatory = Some(false);
|
||||
}
|
||||
(b"offset_hour" | b"year", b"sign:mandatory") => {
|
||||
modifiers.sign_is_mandatory = Some(true);
|
||||
}
|
||||
(b"period", b"case:upper") => modifiers.period_is_uppercase = Some(true),
|
||||
(b"period", b"case:lower") => modifiers.period_is_uppercase = Some(false),
|
||||
(b"subsecond", b"digits:1") => {
|
||||
modifiers.subsecond_digits = Some(SubsecondDigits::One);
|
||||
}
|
||||
(b"subsecond", b"digits:2") => {
|
||||
modifiers.subsecond_digits = Some(SubsecondDigits::Two);
|
||||
}
|
||||
(b"subsecond", b"digits:3") => {
|
||||
modifiers.subsecond_digits = Some(SubsecondDigits::Three);
|
||||
}
|
||||
(b"subsecond", b"digits:4") => {
|
||||
modifiers.subsecond_digits = Some(SubsecondDigits::Four);
|
||||
}
|
||||
(b"subsecond", b"digits:5") => {
|
||||
modifiers.subsecond_digits = Some(SubsecondDigits::Five);
|
||||
}
|
||||
(b"subsecond", b"digits:6") => {
|
||||
modifiers.subsecond_digits = Some(SubsecondDigits::Six);
|
||||
}
|
||||
(b"subsecond", b"digits:7") => {
|
||||
modifiers.subsecond_digits = Some(SubsecondDigits::Seven);
|
||||
}
|
||||
(b"subsecond", b"digits:8") => {
|
||||
modifiers.subsecond_digits = Some(SubsecondDigits::Eight);
|
||||
}
|
||||
(b"subsecond", b"digits:9") => {
|
||||
modifiers.subsecond_digits = Some(SubsecondDigits::Nine);
|
||||
}
|
||||
(b"subsecond", b"digits:1+") => {
|
||||
modifiers.subsecond_digits = Some(SubsecondDigits::OneOrMore);
|
||||
}
|
||||
(b"weekday", b"repr:short") => modifiers.weekday_repr = Some(WeekdayRepr::Short),
|
||||
(b"weekday", b"repr:long") => modifiers.weekday_repr = Some(WeekdayRepr::Long),
|
||||
(b"weekday", b"repr:sunday") => modifiers.weekday_repr = Some(WeekdayRepr::Sunday),
|
||||
(b"weekday", b"repr:monday") => modifiers.weekday_repr = Some(WeekdayRepr::Monday),
|
||||
(b"weekday", b"one_indexed:true") => modifiers.weekday_is_one_indexed = Some(true),
|
||||
(b"weekday", b"one_indexed:false") => {
|
||||
modifiers.weekday_is_one_indexed = Some(false);
|
||||
}
|
||||
(b"week_number", b"repr:iso") => {
|
||||
modifiers.week_number_repr = Some(WeekNumberRepr::Iso);
|
||||
}
|
||||
(b"week_number", b"repr:sunday") => {
|
||||
modifiers.week_number_repr = Some(WeekNumberRepr::Sunday);
|
||||
}
|
||||
(b"week_number", b"repr:monday") => {
|
||||
modifiers.week_number_repr = Some(WeekNumberRepr::Monday);
|
||||
}
|
||||
(b"year", b"repr:full") => modifiers.year_repr = Some(YearRepr::Full),
|
||||
(b"year", b"repr:last_two") => modifiers.year_repr = Some(YearRepr::LastTwo),
|
||||
(b"year", b"base:calendar") => modifiers.year_is_iso_week_based = Some(false),
|
||||
(b"year", b"base:iso_week") => modifiers.year_is_iso_week_based = Some(true),
|
||||
_ => {
|
||||
return Err(InvalidFormatDescription::InvalidModifier {
|
||||
value: String::from_utf8_lossy(modifier).into_owned(),
|
||||
index: *index,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(modifiers)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,162 @@
|
|||
//! A format item with owned data.
|
||||
|
||||
use alloc::boxed::Box;
|
||||
use alloc::string::String;
|
||||
use alloc::vec::Vec;
|
||||
use core::fmt;
|
||||
|
||||
use crate::error;
|
||||
use crate::format_description::{Component, FormatItem};
|
||||
|
||||
/// A complete description of how to format and parse a type.
|
||||
#[non_exhaustive]
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
pub enum OwnedFormatItem {
|
||||
/// Bytes that are formatted as-is.
|
||||
///
|
||||
/// **Note**: If you call the `format` method that returns a `String`, these bytes will be
|
||||
/// passed through `String::from_utf8_lossy`.
|
||||
Literal(Box<[u8]>),
|
||||
/// A minimal representation of a single non-literal item.
|
||||
Component(Component),
|
||||
/// A series of literals or components that collectively form a partial or complete
|
||||
/// description.
|
||||
Compound(Box<[Self]>),
|
||||
/// A `FormatItem` that may or may not be present when parsing. If parsing fails, there
|
||||
/// will be no effect on the resulting `struct`.
|
||||
///
|
||||
/// This variant has no effect on formatting, as the value is guaranteed to be present.
|
||||
Optional(Box<Self>),
|
||||
/// A series of `FormatItem`s where, when parsing, the first successful parse is used. When
|
||||
/// formatting, the first element of the [`Vec`] is used. An empty [`Vec`] is a no-op when
|
||||
/// formatting or parsing.
|
||||
First(Box<[Self]>),
|
||||
}
|
||||
|
||||
impl fmt::Debug for OwnedFormatItem {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Literal(literal) => f.write_str(&String::from_utf8_lossy(literal)),
|
||||
Self::Component(component) => component.fmt(f),
|
||||
Self::Compound(compound) => compound.fmt(f),
|
||||
Self::Optional(item) => f.debug_tuple("Optional").field(item).finish(),
|
||||
Self::First(items) => f.debug_tuple("First").field(items).finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// region: conversions from FormatItem
|
||||
impl From<FormatItem<'_>> for OwnedFormatItem {
|
||||
fn from(item: FormatItem<'_>) -> Self {
|
||||
(&item).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&FormatItem<'_>> for OwnedFormatItem {
|
||||
fn from(item: &FormatItem<'_>) -> Self {
|
||||
match item {
|
||||
FormatItem::Literal(literal) => Self::Literal(literal.to_vec().into_boxed_slice()),
|
||||
FormatItem::Component(component) => Self::Component(*component),
|
||||
FormatItem::Compound(compound) => Self::Compound(
|
||||
compound
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(Into::into)
|
||||
.collect::<Vec<_>>()
|
||||
.into_boxed_slice(),
|
||||
),
|
||||
FormatItem::Optional(item) => Self::Optional(Box::new((*item).into())),
|
||||
FormatItem::First(items) => Self::First(
|
||||
items
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(Into::into)
|
||||
.collect::<Vec<_>>()
|
||||
.into_boxed_slice(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<FormatItem<'_>>> for OwnedFormatItem {
|
||||
fn from(items: Vec<FormatItem<'_>>) -> Self {
|
||||
items.as_slice().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: AsRef<[FormatItem<'a>]> + ?Sized> From<&T> for OwnedFormatItem {
|
||||
fn from(items: &T) -> Self {
|
||||
Self::Compound(
|
||||
items
|
||||
.as_ref()
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(Into::into)
|
||||
.collect::<Vec<_>>()
|
||||
.into_boxed_slice(),
|
||||
)
|
||||
}
|
||||
}
|
||||
// endregion conversions from FormatItem
|
||||
|
||||
// region: from variants
|
||||
impl From<Component> for OwnedFormatItem {
|
||||
fn from(component: Component) -> Self {
|
||||
Self::Component(component)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<OwnedFormatItem> for Component {
|
||||
type Error = error::DifferentVariant;
|
||||
|
||||
fn try_from(value: OwnedFormatItem) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
OwnedFormatItem::Component(component) => Ok(component),
|
||||
_ => Err(error::DifferentVariant),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<Self>> for OwnedFormatItem {
|
||||
fn from(items: Vec<Self>) -> Self {
|
||||
Self::Compound(items.into_boxed_slice())
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<OwnedFormatItem> for Vec<OwnedFormatItem> {
|
||||
type Error = error::DifferentVariant;
|
||||
|
||||
fn try_from(value: OwnedFormatItem) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
OwnedFormatItem::Compound(items) => Ok(items.into_vec()),
|
||||
_ => Err(error::DifferentVariant),
|
||||
}
|
||||
}
|
||||
}
|
||||
// endregion from variants
|
||||
|
||||
// region: equality
|
||||
impl PartialEq<Component> for OwnedFormatItem {
|
||||
fn eq(&self, rhs: &Component) -> bool {
|
||||
matches!(self, Self::Component(component) if component == rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<OwnedFormatItem> for Component {
|
||||
fn eq(&self, rhs: &OwnedFormatItem) -> bool {
|
||||
rhs == self
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<&[Self]> for OwnedFormatItem {
|
||||
fn eq(&self, rhs: &&[Self]) -> bool {
|
||||
matches!(self, Self::Compound(compound) if &&**compound == rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<OwnedFormatItem> for &[OwnedFormatItem] {
|
||||
fn eq(&self, rhs: &OwnedFormatItem) -> bool {
|
||||
rhs == self
|
||||
}
|
||||
}
|
||||
// endregion equality
|
|
@ -1,97 +0,0 @@
|
|||
//! Parse a format description into a standardized representation.
|
||||
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use crate::error::InvalidFormatDescription;
|
||||
use crate::format_description::component::{Component, NakedComponent};
|
||||
use crate::format_description::{helper, modifier, FormatItem};
|
||||
|
||||
/// The item parsed and remaining chunk of the format description after one iteration.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct ParsedItem<'a> {
|
||||
/// The item that was parsed.
|
||||
pub(crate) item: FormatItem<'a>,
|
||||
/// What is left of the input string after the item was parsed.
|
||||
pub(crate) remaining: &'a [u8],
|
||||
}
|
||||
|
||||
/// Parse a component from the format description. Neither the leading nor trailing bracket should
|
||||
/// be present in the parameter.
|
||||
fn parse_component(mut s: &[u8], index: &mut usize) -> Result<Component, InvalidFormatDescription> {
|
||||
// Trim any whitespace between the opening bracket and the component name.
|
||||
s = helper::consume_whitespace(s, index);
|
||||
|
||||
// Everything before the first whitespace is the component name.
|
||||
let component_index = *index;
|
||||
let whitespace_loc = s
|
||||
.iter()
|
||||
.position(u8::is_ascii_whitespace)
|
||||
.unwrap_or(s.len());
|
||||
*index += whitespace_loc;
|
||||
let component_name = &s[..whitespace_loc];
|
||||
s = &s[whitespace_loc..];
|
||||
s = helper::consume_whitespace(s, index);
|
||||
|
||||
Ok(NakedComponent::parse(component_name, component_index)?
|
||||
.attach_modifiers(&modifier::Modifiers::parse(component_name, s, index)?))
|
||||
}
|
||||
|
||||
/// Parse a literal string from the format description.
|
||||
fn parse_literal<'a>(s: &'a [u8], index: &mut usize) -> ParsedItem<'a> {
|
||||
let loc = s.iter().position(|&c| c == b'[').unwrap_or(s.len());
|
||||
*index += loc;
|
||||
ParsedItem {
|
||||
item: FormatItem::Literal(&s[..loc]),
|
||||
remaining: &s[loc..],
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse either a literal or a component from the format description.
|
||||
fn parse_item<'a>(
|
||||
s: &'a [u8],
|
||||
index: &mut usize,
|
||||
) -> Result<ParsedItem<'a>, InvalidFormatDescription> {
|
||||
if let [b'[', b'[', remaining @ ..] = s {
|
||||
*index += 2;
|
||||
return Ok(ParsedItem {
|
||||
item: FormatItem::Literal(&[b'[']),
|
||||
remaining,
|
||||
});
|
||||
};
|
||||
|
||||
if s.starts_with(&[b'[']) {
|
||||
if let Some(bracket_index) = s.iter().position(|&c| c == b']') {
|
||||
*index += 1; // opening bracket
|
||||
let ret_val = ParsedItem {
|
||||
item: FormatItem::Component(parse_component(&s[1..bracket_index], index)?),
|
||||
remaining: &s[bracket_index + 1..],
|
||||
};
|
||||
*index += 1; // closing bracket
|
||||
Ok(ret_val)
|
||||
} else {
|
||||
Err(InvalidFormatDescription::UnclosedOpeningBracket { index: *index })
|
||||
}
|
||||
} else {
|
||||
Ok(parse_literal(s, index))
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a sequence of items from the format description.
|
||||
///
|
||||
/// The syntax for the format description can be found in [the
|
||||
/// book](https://time-rs.github.io/book/api/format-description.html).
|
||||
#[cfg_attr(__time_03_docs, doc(cfg(feature = "alloc")))]
|
||||
pub fn parse(s: &str) -> Result<Vec<FormatItem<'_>>, InvalidFormatDescription> {
|
||||
let mut compound = Vec::new();
|
||||
let mut loc = 0;
|
||||
|
||||
let mut s = s.as_bytes();
|
||||
|
||||
while !s.is_empty() {
|
||||
let ParsedItem { item, remaining } = parse_item(s, &mut loc)?;
|
||||
s = remaining;
|
||||
compound.push(item);
|
||||
}
|
||||
|
||||
Ok(compound)
|
||||
}
|
|
@ -0,0 +1,278 @@
|
|||
//! AST for parsing format descriptions.
|
||||
|
||||
use alloc::string::String;
|
||||
use alloc::vec::Vec;
|
||||
use core::iter;
|
||||
use core::iter::Peekable;
|
||||
|
||||
use super::{lexer, Error, Location, Span};
|
||||
|
||||
/// One part of a complete format description.
|
||||
#[allow(variant_size_differences)]
|
||||
pub(super) enum Item<'a> {
|
||||
/// A literal string, formatted and parsed as-is.
|
||||
Literal {
|
||||
/// The string itself.
|
||||
value: &'a [u8],
|
||||
/// Where the string originates from in the format string.
|
||||
_span: Span,
|
||||
},
|
||||
/// A sequence of brackets. The first acts as the escape character.
|
||||
EscapedBracket {
|
||||
/// The first bracket.
|
||||
_first: Location,
|
||||
/// The second bracket.
|
||||
_second: Location,
|
||||
},
|
||||
/// Part of a type, along with its modifiers.
|
||||
Component {
|
||||
/// Where the opening bracket was in the format string.
|
||||
_opening_bracket: Location,
|
||||
/// Whitespace between the opening bracket and name.
|
||||
_leading_whitespace: Option<Whitespace<'a>>,
|
||||
/// The name of the component.
|
||||
name: Name<'a>,
|
||||
/// The modifiers for the component.
|
||||
modifiers: Vec<Modifier<'a>>,
|
||||
/// Whitespace between the modifiers and closing bracket.
|
||||
_trailing_whitespace: Option<Whitespace<'a>>,
|
||||
/// Where the closing bracket was in the format string.
|
||||
_closing_bracket: Location,
|
||||
},
|
||||
}
|
||||
|
||||
/// Whitespace within a component.
|
||||
pub(super) struct Whitespace<'a> {
|
||||
/// The whitespace itself.
|
||||
pub(super) _value: &'a [u8],
|
||||
/// Where the whitespace was in the format string.
|
||||
pub(super) span: Span,
|
||||
}
|
||||
|
||||
/// The name of a component.
|
||||
pub(super) struct Name<'a> {
|
||||
/// The name itself.
|
||||
pub(super) value: &'a [u8],
|
||||
/// Where the name was in the format string.
|
||||
pub(super) span: Span,
|
||||
}
|
||||
|
||||
/// A modifier for a component.
|
||||
pub(super) struct Modifier<'a> {
|
||||
/// Whitespace preceding the modifier.
|
||||
pub(super) _leading_whitespace: Whitespace<'a>,
|
||||
/// The key of the modifier.
|
||||
pub(super) key: Key<'a>,
|
||||
/// Where the colon of the modifier was in the format string.
|
||||
pub(super) _colon: Location,
|
||||
/// The value of the modifier.
|
||||
pub(super) value: Value<'a>,
|
||||
}
|
||||
|
||||
/// The key of a modifier.
|
||||
pub(super) struct Key<'a> {
|
||||
/// The key itself.
|
||||
pub(super) value: &'a [u8],
|
||||
/// Where the key was in the format string.
|
||||
pub(super) span: Span,
|
||||
}
|
||||
|
||||
/// The value of a modifier.
|
||||
pub(super) struct Value<'a> {
|
||||
/// The value itself.
|
||||
pub(super) value: &'a [u8],
|
||||
/// Where the value was in the format string.
|
||||
pub(super) span: Span,
|
||||
}
|
||||
|
||||
/// Parse the provided tokens into an AST.
|
||||
pub(super) fn parse<'a>(
|
||||
tokens: impl Iterator<Item = lexer::Token<'a>>,
|
||||
) -> impl Iterator<Item = Result<Item<'a>, Error>> {
|
||||
let mut tokens = tokens.peekable();
|
||||
iter::from_fn(move || {
|
||||
Some(match tokens.next()? {
|
||||
lexer::Token::Literal { value, span } => Ok(Item::Literal { value, _span: span }),
|
||||
lexer::Token::Bracket {
|
||||
kind: lexer::BracketKind::Opening,
|
||||
location,
|
||||
} => {
|
||||
// escaped bracket
|
||||
if let Some(&lexer::Token::Bracket {
|
||||
kind: lexer::BracketKind::Opening,
|
||||
location: second_location,
|
||||
}) = tokens.peek()
|
||||
{
|
||||
tokens.next(); // consume
|
||||
Ok(Item::EscapedBracket {
|
||||
_first: location,
|
||||
_second: second_location,
|
||||
})
|
||||
}
|
||||
// component
|
||||
else {
|
||||
parse_component(location, &mut tokens)
|
||||
}
|
||||
}
|
||||
lexer::Token::Bracket {
|
||||
kind: lexer::BracketKind::Closing,
|
||||
location: _,
|
||||
} => unreachable!(
|
||||
"internal error: closing bracket should have been consumed by `parse_component`",
|
||||
),
|
||||
lexer::Token::ComponentPart {
|
||||
kind: _,
|
||||
value: _,
|
||||
span: _,
|
||||
} => unreachable!(
|
||||
"internal error: component part should have been consumed by `parse_component`",
|
||||
),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Parse a component. This assumes that the opening bracket has already been consumed.
|
||||
fn parse_component<'a>(
|
||||
opening_bracket: Location,
|
||||
tokens: &mut Peekable<impl Iterator<Item = lexer::Token<'a>>>,
|
||||
) -> Result<Item<'a>, Error> {
|
||||
let leading_whitespace = if let Some(&lexer::Token::ComponentPart {
|
||||
kind: lexer::ComponentKind::Whitespace,
|
||||
value,
|
||||
span,
|
||||
}) = tokens.peek()
|
||||
{
|
||||
tokens.next(); // consume
|
||||
Some(Whitespace {
|
||||
_value: value,
|
||||
span,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let name = if let Some(&lexer::Token::ComponentPart {
|
||||
kind: lexer::ComponentKind::NotWhitespace,
|
||||
value,
|
||||
span,
|
||||
}) = tokens.peek()
|
||||
{
|
||||
tokens.next(); // consume
|
||||
Name { value, span }
|
||||
} else {
|
||||
let span = leading_whitespace.map_or_else(
|
||||
|| Span {
|
||||
start: opening_bracket,
|
||||
end: opening_bracket,
|
||||
},
|
||||
|whitespace| whitespace.span.shrink_to_end(),
|
||||
);
|
||||
return Err(Error {
|
||||
_inner: span.error("expected component name"),
|
||||
public: crate::error::InvalidFormatDescription::MissingComponentName {
|
||||
index: span.start_byte(),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
let mut modifiers = Vec::new();
|
||||
let trailing_whitespace = loop {
|
||||
let whitespace = if let Some(&lexer::Token::ComponentPart {
|
||||
kind: lexer::ComponentKind::Whitespace,
|
||||
value,
|
||||
span,
|
||||
}) = tokens.peek()
|
||||
{
|
||||
tokens.next(); // consume
|
||||
Whitespace {
|
||||
_value: value,
|
||||
span,
|
||||
}
|
||||
} else {
|
||||
break None;
|
||||
};
|
||||
|
||||
if let Some(&lexer::Token::ComponentPart {
|
||||
kind: lexer::ComponentKind::NotWhitespace,
|
||||
value,
|
||||
span,
|
||||
}) = tokens.peek()
|
||||
{
|
||||
tokens.next(); // consume
|
||||
|
||||
let colon_index = match value.iter().position(|&b| b == b':') {
|
||||
Some(index) => index,
|
||||
None => {
|
||||
return Err(Error {
|
||||
_inner: span.error("modifier must be of the form `key:value`"),
|
||||
public: crate::error::InvalidFormatDescription::InvalidModifier {
|
||||
value: String::from_utf8_lossy(value).into_owned(),
|
||||
index: span.start_byte(),
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
let key = &value[..colon_index];
|
||||
let value = &value[colon_index + 1..];
|
||||
|
||||
if key.is_empty() {
|
||||
return Err(Error {
|
||||
_inner: span.shrink_to_start().error("expected modifier key"),
|
||||
public: crate::error::InvalidFormatDescription::InvalidModifier {
|
||||
value: String::new(),
|
||||
index: span.start_byte(),
|
||||
},
|
||||
});
|
||||
}
|
||||
if value.is_empty() {
|
||||
return Err(Error {
|
||||
_inner: span.shrink_to_end().error("expected modifier value"),
|
||||
public: crate::error::InvalidFormatDescription::InvalidModifier {
|
||||
value: String::new(),
|
||||
index: span.shrink_to_end().start_byte(),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
modifiers.push(Modifier {
|
||||
_leading_whitespace: whitespace,
|
||||
key: Key {
|
||||
value: key,
|
||||
span: span.subspan(..colon_index),
|
||||
},
|
||||
_colon: span.start.offset(colon_index),
|
||||
value: Value {
|
||||
value,
|
||||
span: span.subspan(colon_index + 1..),
|
||||
},
|
||||
});
|
||||
} else {
|
||||
break Some(whitespace);
|
||||
}
|
||||
};
|
||||
|
||||
let closing_bracket = if let Some(&lexer::Token::Bracket {
|
||||
kind: lexer::BracketKind::Closing,
|
||||
location,
|
||||
}) = tokens.peek()
|
||||
{
|
||||
tokens.next(); // consume
|
||||
location
|
||||
} else {
|
||||
return Err(Error {
|
||||
_inner: opening_bracket.error("unclosed bracket"),
|
||||
public: crate::error::InvalidFormatDescription::UnclosedOpeningBracket {
|
||||
index: opening_bracket.byte,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
Ok(Item::Component {
|
||||
_opening_bracket: opening_bracket,
|
||||
_leading_whitespace: leading_whitespace,
|
||||
name,
|
||||
modifiers,
|
||||
_trailing_whitespace: trailing_whitespace,
|
||||
_closing_bracket: closing_bracket,
|
||||
})
|
||||
}
|
|
@ -0,0 +1,386 @@
|
|||
//! Typed, validated representation of a parsed format description.
|
||||
|
||||
use alloc::string::String;
|
||||
|
||||
use super::{ast, Error};
|
||||
|
||||
/// Parse an AST iterator into a sequence of format items.
|
||||
pub(super) fn parse<'a>(
|
||||
ast_items: impl Iterator<Item = Result<ast::Item<'a>, Error>>,
|
||||
) -> impl Iterator<Item = Result<Item<'a>, Error>> {
|
||||
ast_items.map(|ast_item| ast_item.and_then(Item::from_ast))
|
||||
}
|
||||
|
||||
/// A description of how to format and parse one part of a type.
|
||||
#[allow(variant_size_differences)]
|
||||
pub(super) enum Item<'a> {
|
||||
/// A literal string.
|
||||
Literal(&'a [u8]),
|
||||
/// Part of a type, along with its modifiers.
|
||||
Component(Component),
|
||||
}
|
||||
|
||||
impl Item<'_> {
|
||||
/// Parse an AST item into a format item.
|
||||
pub(super) fn from_ast(ast_item: ast::Item<'_>) -> Result<Item<'_>, Error> {
|
||||
Ok(match ast_item {
|
||||
ast::Item::Component {
|
||||
_opening_bracket: _,
|
||||
_leading_whitespace: _,
|
||||
name,
|
||||
modifiers,
|
||||
_trailing_whitespace: _,
|
||||
_closing_bracket: _,
|
||||
} => Item::Component(component_from_ast(&name, &modifiers)?),
|
||||
ast::Item::Literal { value, _span: _ } => Item::Literal(value),
|
||||
ast::Item::EscapedBracket {
|
||||
_first: _,
|
||||
_second: _,
|
||||
} => Item::Literal(b"["),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<Item<'a>> for crate::format_description::FormatItem<'a> {
|
||||
fn from(item: Item<'a>) -> Self {
|
||||
match item {
|
||||
Item::Literal(literal) => Self::Literal(literal),
|
||||
Item::Component(component) => Self::Component(component.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Item<'_>> for crate::format_description::OwnedFormatItem {
|
||||
fn from(item: Item<'_>) -> Self {
|
||||
match item {
|
||||
Item::Literal(literal) => Self::Literal(literal.to_vec().into_boxed_slice()),
|
||||
Item::Component(component) => Self::Component(component.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Declare the `Component` struct.
|
||||
macro_rules! component_definition {
|
||||
($vis:vis enum $name:ident {
|
||||
$($variant:ident = $parse_variant:literal {
|
||||
$($field:ident = $parse_field:literal:
|
||||
Option<$field_type:ty> => $target_field:ident),* $(,)?
|
||||
}),* $(,)?
|
||||
}) => {
|
||||
$vis enum $name {
|
||||
$($variant($variant),)*
|
||||
}
|
||||
|
||||
$($vis struct $variant {
|
||||
$($field: Option<$field_type>),*
|
||||
})*
|
||||
|
||||
$(impl $variant {
|
||||
/// Parse the component from the AST, given its modifiers.
|
||||
fn with_modifiers(modifiers: &[ast::Modifier<'_>]) -> Result<Self, Error> {
|
||||
let mut this = Self {
|
||||
$($field: None),*
|
||||
};
|
||||
|
||||
for modifier in modifiers {
|
||||
$(if modifier.key.value.eq_ignore_ascii_case($parse_field) {
|
||||
this.$field = <$field_type>::from_modifier_value(&modifier.value)?;
|
||||
continue;
|
||||
})*
|
||||
return Err(Error {
|
||||
_inner: modifier.key.span.error("invalid modifier key"),
|
||||
public: crate::error::InvalidFormatDescription::InvalidModifier {
|
||||
value: String::from_utf8_lossy(modifier.key.value).into_owned(),
|
||||
index: modifier.key.span.start_byte(),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Ok(this)
|
||||
}
|
||||
})*
|
||||
|
||||
impl From<$name> for crate::format_description::Component {
|
||||
fn from(component: $name) -> Self {
|
||||
match component {$(
|
||||
$name::$variant($variant { $($field),* }) => {
|
||||
$crate::format_description::component::Component::$variant(
|
||||
$crate::format_description::modifier::$variant {$(
|
||||
$target_field: $field.unwrap_or_default().into()
|
||||
),*}
|
||||
)
|
||||
}
|
||||
)*}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a component from the AST, given its name and modifiers.
|
||||
fn component_from_ast(
|
||||
name: &ast::Name<'_>,
|
||||
modifiers: &[ast::Modifier<'_>],
|
||||
) -> Result<Component, Error> {
|
||||
$(if name.value.eq_ignore_ascii_case($parse_variant) {
|
||||
return Ok(Component::$variant($variant::with_modifiers(&modifiers)?));
|
||||
})*
|
||||
Err(Error {
|
||||
_inner: name.span.error("invalid component"),
|
||||
public: crate::error::InvalidFormatDescription::InvalidComponentName {
|
||||
name: String::from_utf8_lossy(name.value).into_owned(),
|
||||
index: name.span.start_byte(),
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Keep in alphabetical order.
|
||||
component_definition! {
|
||||
pub(super) enum Component {
|
||||
Day = b"day" {
|
||||
padding = b"padding": Option<Padding> => padding,
|
||||
},
|
||||
Hour = b"hour" {
|
||||
padding = b"padding": Option<Padding> => padding,
|
||||
base = b"repr": Option<HourBase> => is_12_hour_clock,
|
||||
},
|
||||
Minute = b"minute" {
|
||||
padding = b"padding": Option<Padding> => padding,
|
||||
},
|
||||
Month = b"month" {
|
||||
padding = b"padding": Option<Padding> => padding,
|
||||
repr = b"repr": Option<MonthRepr> => repr,
|
||||
case_sensitive = b"case_sensitive": Option<MonthCaseSensitive> => case_sensitive,
|
||||
},
|
||||
OffsetHour = b"offset_hour" {
|
||||
sign_behavior = b"sign": Option<SignBehavior> => sign_is_mandatory,
|
||||
padding = b"padding": Option<Padding> => padding,
|
||||
},
|
||||
OffsetMinute = b"offset_minute" {
|
||||
padding = b"padding": Option<Padding> => padding,
|
||||
},
|
||||
OffsetSecond = b"offset_second" {
|
||||
padding = b"padding": Option<Padding> => padding,
|
||||
},
|
||||
Ordinal = b"ordinal" {
|
||||
padding = b"padding": Option<Padding> => padding,
|
||||
},
|
||||
Period = b"period" {
|
||||
case = b"case": Option<PeriodCase> => is_uppercase,
|
||||
case_sensitive = b"case_sensitive": Option<PeriodCaseSensitive> => case_sensitive,
|
||||
},
|
||||
Second = b"second" {
|
||||
padding = b"padding": Option<Padding> => padding,
|
||||
},
|
||||
Subsecond = b"subsecond" {
|
||||
digits = b"digits": Option<SubsecondDigits> => digits,
|
||||
},
|
||||
Weekday = b"weekday" {
|
||||
repr = b"repr": Option<WeekdayRepr> => repr,
|
||||
one_indexed = b"one_indexed": Option<WeekdayOneIndexed> => one_indexed,
|
||||
case_sensitive = b"case_sensitive": Option<WeekdayCaseSensitive> => case_sensitive,
|
||||
},
|
||||
WeekNumber = b"week_number" {
|
||||
padding = b"padding": Option<Padding> => padding,
|
||||
repr = b"repr": Option<WeekNumberRepr> => repr,
|
||||
},
|
||||
Year = b"year" {
|
||||
padding = b"padding": Option<Padding> => padding,
|
||||
repr = b"repr": Option<YearRepr> => repr,
|
||||
base = b"base": Option<YearBase> => iso_week_based,
|
||||
sign_behavior = b"sign": Option<SignBehavior> => sign_is_mandatory,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the target type for a given enum.
|
||||
macro_rules! target_ty {
|
||||
($name:ident $type:ty) => {
|
||||
$type
|
||||
};
|
||||
($name:ident) => {
|
||||
$crate::format_description::modifier::$name
|
||||
};
|
||||
}
|
||||
|
||||
/// Get the target value for a given enum.
|
||||
macro_rules! target_value {
|
||||
($name:ident $variant:ident $value:expr) => {
|
||||
$value
|
||||
};
|
||||
($name:ident $variant:ident) => {
|
||||
$crate::format_description::modifier::$name::$variant
|
||||
};
|
||||
}
|
||||
|
||||
// TODO use `#[derive(Default)]` on enums once MSRV is 1.62 (NET 2022-12-30)
|
||||
/// Simulate `#[derive(Default)]` on enums.
|
||||
macro_rules! derived_default_on_enum {
|
||||
($type:ty; $default:expr) => {};
|
||||
($attr:meta $type:ty; $default:expr) => {
|
||||
impl Default for $type {
|
||||
fn default() -> Self {
|
||||
$default
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Declare the various modifiers.
|
||||
///
|
||||
/// For the general case, ordinary syntax can be used. Note that you _must_ declare a default
|
||||
/// variant. The only significant change is that the string representation of the variant must be
|
||||
/// provided after the variant name. For example, `Numerical = b"numerical"` declares a variant
|
||||
/// named `Numerical` with the string representation `b"numerical"`. This is the value that will be
|
||||
/// used when parsing the modifier. The value is not case sensitive.
|
||||
///
|
||||
/// If the type in the public API does not have the same name as the type in the internal
|
||||
/// representation, then the former must be specified in parenthesis after the internal name. For
|
||||
/// example, `HourBase(bool)` has an internal name "HourBase", but is represented as a boolean in
|
||||
/// the public API.
|
||||
///
|
||||
/// By default, the internal variant name is assumed to be the same as the public variant name. If
|
||||
/// this is not the case, the qualified path to the variant must be specified in parenthesis after
|
||||
/// the internal variant name. For example, `Twelve(true)` has an internal variant name "Twelve",
|
||||
/// but is represented as `true` in the public API.
|
||||
macro_rules! modifier {
|
||||
($(
|
||||
enum $name:ident $(($target_ty:ty))? {
|
||||
$(
|
||||
$(#[$attr:meta])?
|
||||
$variant:ident $(($target_value:expr))? = $parse_variant:literal
|
||||
),* $(,)?
|
||||
}
|
||||
)+) => {$(
|
||||
enum $name {
|
||||
$($variant),*
|
||||
}
|
||||
|
||||
$(derived_default_on_enum! {
|
||||
$($attr)? $name; $name::$variant
|
||||
})*
|
||||
|
||||
impl $name {
|
||||
/// Parse the modifier from its string representation.
|
||||
fn from_modifier_value(value: &ast::Value<'_>) -> Result<Option<Self>, Error> {
|
||||
$(if value.value.eq_ignore_ascii_case($parse_variant) {
|
||||
return Ok(Some(Self::$variant));
|
||||
})*
|
||||
Err(Error {
|
||||
_inner: value.span.error("invalid modifier value"),
|
||||
public: crate::error::InvalidFormatDescription::InvalidModifier {
|
||||
value: String::from_utf8_lossy(value.value).into_owned(),
|
||||
index: value.span.start_byte(),
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<$name> for target_ty!($name $($target_ty)?) {
|
||||
fn from(modifier: $name) -> Self {
|
||||
match modifier {
|
||||
$($name::$variant => target_value!($name $variant $($target_value)?)),*
|
||||
}
|
||||
}
|
||||
}
|
||||
)+};
|
||||
}
|
||||
|
||||
// Keep in alphabetical order.
|
||||
modifier! {
|
||||
enum HourBase(bool) {
|
||||
Twelve(true) = b"12",
|
||||
#[default]
|
||||
TwentyFour(false) = b"24",
|
||||
}
|
||||
|
||||
enum MonthCaseSensitive(bool) {
|
||||
False(false) = b"false",
|
||||
#[default]
|
||||
True(true) = b"true",
|
||||
}
|
||||
|
||||
enum MonthRepr {
|
||||
#[default]
|
||||
Numerical = b"numerical",
|
||||
Long = b"long",
|
||||
Short = b"short",
|
||||
}
|
||||
|
||||
enum Padding {
|
||||
Space = b"space",
|
||||
#[default]
|
||||
Zero = b"zero",
|
||||
None = b"none",
|
||||
}
|
||||
|
||||
enum PeriodCase(bool) {
|
||||
Lower(false) = b"lower",
|
||||
#[default]
|
||||
Upper(true) = b"upper",
|
||||
}
|
||||
|
||||
enum PeriodCaseSensitive(bool) {
|
||||
False(false) = b"false",
|
||||
#[default]
|
||||
True(true) = b"true",
|
||||
}
|
||||
|
||||
enum SignBehavior(bool) {
|
||||
#[default]
|
||||
Automatic(false) = b"automatic",
|
||||
Mandatory(true) = b"mandatory",
|
||||
}
|
||||
|
||||
enum SubsecondDigits {
|
||||
One = b"1",
|
||||
Two = b"2",
|
||||
Three = b"3",
|
||||
Four = b"4",
|
||||
Five = b"5",
|
||||
Six = b"6",
|
||||
Seven = b"7",
|
||||
Eight = b"8",
|
||||
Nine = b"9",
|
||||
#[default]
|
||||
OneOrMore = b"1+",
|
||||
}
|
||||
|
||||
enum WeekNumberRepr {
|
||||
#[default]
|
||||
Iso = b"iso",
|
||||
Sunday = b"sunday",
|
||||
Monday = b"monday",
|
||||
}
|
||||
|
||||
enum WeekdayCaseSensitive(bool) {
|
||||
False(false) = b"false",
|
||||
#[default]
|
||||
True(true) = b"true",
|
||||
}
|
||||
|
||||
enum WeekdayOneIndexed(bool) {
|
||||
False(false) = b"false",
|
||||
#[default]
|
||||
True(true) = b"true",
|
||||
}
|
||||
|
||||
enum WeekdayRepr {
|
||||
Short = b"short",
|
||||
#[default]
|
||||
Long = b"long",
|
||||
Sunday = b"sunday",
|
||||
Monday = b"monday",
|
||||
}
|
||||
|
||||
enum YearBase(bool) {
|
||||
#[default]
|
||||
Calendar(false) = b"calendar",
|
||||
IsoWeek(true) = b"iso_week",
|
||||
}
|
||||
|
||||
enum YearRepr {
|
||||
#[default]
|
||||
Full = b"full",
|
||||
LastTwo = b"last_two",
|
||||
}
|
||||
}
|
|
@ -0,0 +1,159 @@
|
|||
//! Lexer for parsing format descriptions.
|
||||
|
||||
use core::iter;
|
||||
|
||||
use super::{Location, Span};
|
||||
|
||||
/// A token emitted by the lexer. There is no semantic meaning at this stage.
|
||||
pub(super) enum Token<'a> {
|
||||
/// A literal string, formatted and parsed as-is.
|
||||
Literal {
|
||||
/// The string itself.
|
||||
value: &'a [u8],
|
||||
/// Where the string was in the format string.
|
||||
span: Span,
|
||||
},
|
||||
/// An opening or closing bracket. May or may not be the start or end of a component.
|
||||
Bracket {
|
||||
/// Whether the bracket is opening or closing.
|
||||
kind: BracketKind,
|
||||
/// Where the bracket was in the format string.
|
||||
location: Location,
|
||||
},
|
||||
/// One part of a component. This could be its name, a modifier, or whitespace.
|
||||
ComponentPart {
|
||||
/// Whether the part is whitespace or not.
|
||||
kind: ComponentKind,
|
||||
/// The part itself.
|
||||
value: &'a [u8],
|
||||
/// Where the part was in the format string.
|
||||
span: Span,
|
||||
},
|
||||
}
|
||||
|
||||
/// What type of bracket is present.
|
||||
pub(super) enum BracketKind {
|
||||
/// An opening bracket: `[`
|
||||
Opening,
|
||||
/// A closing bracket: `]`
|
||||
Closing,
|
||||
}
|
||||
|
||||
/// Indicates whether the component is whitespace or not.
|
||||
pub(super) enum ComponentKind {
|
||||
#[allow(clippy::missing_docs_in_private_items)]
|
||||
Whitespace,
|
||||
#[allow(clippy::missing_docs_in_private_items)]
|
||||
NotWhitespace,
|
||||
}
|
||||
|
||||
/// Attach [`Location`] information to each byte in the iterator.
|
||||
fn attach_location(iter: impl Iterator<Item = u8>) -> impl Iterator<Item = (u8, Location)> {
|
||||
let mut line = 1;
|
||||
let mut column = 1;
|
||||
let mut byte_pos = 0;
|
||||
|
||||
iter.map(move |byte| {
|
||||
let location = Location {
|
||||
line,
|
||||
column,
|
||||
byte: byte_pos,
|
||||
};
|
||||
column += 1;
|
||||
byte_pos += 1;
|
||||
|
||||
if byte == b'\n' {
|
||||
line += 1;
|
||||
column = 1;
|
||||
}
|
||||
|
||||
(byte, location)
|
||||
})
|
||||
}
|
||||
|
||||
/// Parse the string into a series of [`Token`]s.
|
||||
pub(super) fn lex(mut input: &[u8]) -> impl Iterator<Item = Token<'_>> {
|
||||
let mut depth: u8 = 0;
|
||||
let mut iter = attach_location(input.iter().copied()).peekable();
|
||||
let mut second_bracket_location = None;
|
||||
|
||||
iter::from_fn(move || {
|
||||
// There is a flag set to emit the second half of an escaped bracket pair.
|
||||
if let Some(location) = second_bracket_location.take() {
|
||||
return Some(Token::Bracket {
|
||||
kind: BracketKind::Opening,
|
||||
location,
|
||||
});
|
||||
}
|
||||
|
||||
Some(match iter.next()? {
|
||||
(b'[', location) => {
|
||||
if let Some((_, second_location)) = iter.next_if(|&(byte, _)| byte == b'[') {
|
||||
// escaped bracket
|
||||
second_bracket_location = Some(second_location);
|
||||
input = &input[2..];
|
||||
} else {
|
||||
// opening bracket
|
||||
depth += 1;
|
||||
input = &input[1..];
|
||||
}
|
||||
|
||||
Token::Bracket {
|
||||
kind: BracketKind::Opening,
|
||||
location,
|
||||
}
|
||||
}
|
||||
// closing bracket
|
||||
(b']', location) if depth > 0 => {
|
||||
depth -= 1;
|
||||
input = &input[1..];
|
||||
Token::Bracket {
|
||||
kind: BracketKind::Closing,
|
||||
location,
|
||||
}
|
||||
}
|
||||
// literal
|
||||
(_, start_location) if depth == 0 => {
|
||||
let mut bytes = 1;
|
||||
let mut end_location = start_location;
|
||||
|
||||
while let Some((_, location)) = iter.next_if(|&(byte, _)| byte != b'[') {
|
||||
end_location = location;
|
||||
bytes += 1;
|
||||
}
|
||||
|
||||
let value = &input[..bytes];
|
||||
input = &input[bytes..];
|
||||
Token::Literal {
|
||||
value,
|
||||
span: Span::start_end(start_location, end_location),
|
||||
}
|
||||
}
|
||||
// component part
|
||||
(byte, start_location) => {
|
||||
let mut bytes = 1;
|
||||
let mut end_location = start_location;
|
||||
let is_whitespace = byte.is_ascii_whitespace();
|
||||
|
||||
while let Some((_, location)) = iter.next_if(|&(byte, _)| {
|
||||
byte != b'[' && byte != b']' && is_whitespace == byte.is_ascii_whitespace()
|
||||
}) {
|
||||
end_location = location;
|
||||
bytes += 1;
|
||||
}
|
||||
|
||||
let value = &input[..bytes];
|
||||
input = &input[bytes..];
|
||||
Token::ComponentPart {
|
||||
kind: if is_whitespace {
|
||||
ComponentKind::Whitespace
|
||||
} else {
|
||||
ComponentKind::NotWhitespace
|
||||
},
|
||||
value,
|
||||
span: Span::start_end(start_location, end_location),
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
|
@ -0,0 +1,193 @@
|
|||
//! Parser for format descriptions.
|
||||
|
||||
use alloc::vec::Vec;
|
||||
use core::ops::{RangeFrom, RangeTo};
|
||||
|
||||
mod ast;
|
||||
mod format_item;
|
||||
mod lexer;
|
||||
|
||||
/// Parse a sequence of items from the format description.
|
||||
///
|
||||
/// The syntax for the format description can be found in [the
|
||||
/// book](https://time-rs.github.io/book/api/format-description.html).
|
||||
pub fn parse(
|
||||
s: &str,
|
||||
) -> Result<Vec<crate::format_description::FormatItem<'_>>, crate::error::InvalidFormatDescription>
|
||||
{
|
||||
let lexed = lexer::lex(s.as_bytes());
|
||||
let ast = ast::parse(lexed);
|
||||
let format_items = format_item::parse(ast);
|
||||
Ok(format_items
|
||||
.map(|res| res.map(Into::into))
|
||||
.collect::<Result<Vec<_>, _>>()?)
|
||||
}
|
||||
|
||||
/// Parse a sequence of items from the format description.
|
||||
///
|
||||
/// The syntax for the format description can be found in [the
|
||||
/// book](https://time-rs.github.io/book/api/format-description.html).
|
||||
///
|
||||
/// Unlike [`parse`], this function returns [`OwnedFormatItem`], which owns its contents. This means
|
||||
/// that there is no lifetime that needs to be handled.
|
||||
///
|
||||
/// [`OwnedFormatItem`]: crate::format_description::OwnedFormatItem
|
||||
pub fn parse_owned(
|
||||
s: &str,
|
||||
) -> Result<crate::format_description::OwnedFormatItem, crate::error::InvalidFormatDescription> {
|
||||
let lexed = lexer::lex(s.as_bytes());
|
||||
let ast = ast::parse(lexed);
|
||||
let format_items = format_item::parse(ast);
|
||||
let items = format_items
|
||||
.map(|res| res.map(Into::into))
|
||||
.collect::<Result<Vec<_>, _>>()?
|
||||
.into_boxed_slice();
|
||||
Ok(crate::format_description::OwnedFormatItem::Compound(items))
|
||||
}
|
||||
|
||||
/// A location within a string.
|
||||
#[derive(Clone, Copy)]
|
||||
struct Location {
|
||||
/// The one-indexed line of the string.
|
||||
line: usize,
|
||||
/// The one-indexed column of the string.
|
||||
column: usize,
|
||||
/// The zero-indexed byte of the string.
|
||||
byte: usize,
|
||||
}
|
||||
|
||||
impl Location {
|
||||
/// Offset the location by the provided amount.
|
||||
///
|
||||
/// Note that this assumes the resulting location is on the same line as the original location.
|
||||
#[must_use = "this does not modify the original value"]
|
||||
const fn offset(&self, offset: usize) -> Self {
|
||||
Self {
|
||||
line: self.line,
|
||||
column: self.column + offset,
|
||||
byte: self.byte + offset,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create an error with the provided message at this location.
|
||||
const fn error(self, message: &'static str) -> ErrorInner {
|
||||
ErrorInner {
|
||||
_message: message,
|
||||
_span: Span {
|
||||
start: self,
|
||||
end: self,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A start and end point within a string.
|
||||
#[derive(Clone, Copy)]
|
||||
struct Span {
|
||||
#[allow(clippy::missing_docs_in_private_items)]
|
||||
start: Location,
|
||||
#[allow(clippy::missing_docs_in_private_items)]
|
||||
end: Location,
|
||||
}
|
||||
|
||||
impl Span {
|
||||
/// Create a new `Span` from the provided start and end locations.
|
||||
const fn start_end(start: Location, end: Location) -> Self {
|
||||
Self { start, end }
|
||||
}
|
||||
|
||||
/// Reduce this span to the provided range.
|
||||
#[must_use = "this does not modify the original value"]
|
||||
fn subspan(&self, range: impl Subspan) -> Self {
|
||||
range.subspan(self)
|
||||
}
|
||||
|
||||
/// Obtain a `Span` pointing at the start of the pre-existing span.
|
||||
#[must_use = "this does not modify the original value"]
|
||||
const fn shrink_to_start(&self) -> Self {
|
||||
Self {
|
||||
start: self.start,
|
||||
end: self.start,
|
||||
}
|
||||
}
|
||||
|
||||
/// Obtain a `Span` pointing at the end of the pre-existing span.
|
||||
#[must_use = "this does not modify the original value"]
|
||||
const fn shrink_to_end(&self) -> Self {
|
||||
Self {
|
||||
start: self.end,
|
||||
end: self.end,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create an error with the provided message at this span.
|
||||
const fn error(self, message: &'static str) -> ErrorInner {
|
||||
ErrorInner {
|
||||
_message: message,
|
||||
_span: self,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the byte index that the span starts at.
|
||||
const fn start_byte(&self) -> usize {
|
||||
self.start.byte
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait for types that can be used to reduce a `Span`.
|
||||
trait Subspan {
|
||||
/// Reduce the provided `Span` to a new `Span`.
|
||||
fn subspan(self, span: &Span) -> Span;
|
||||
}
|
||||
|
||||
impl Subspan for RangeFrom<usize> {
|
||||
fn subspan(self, span: &Span) -> Span {
|
||||
assert_eq!(span.start.line, span.end.line);
|
||||
|
||||
Span {
|
||||
start: Location {
|
||||
line: span.start.line,
|
||||
column: span.start.column + self.start,
|
||||
byte: span.start.byte + self.start,
|
||||
},
|
||||
end: span.end,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Subspan for RangeTo<usize> {
|
||||
fn subspan(self, span: &Span) -> Span {
|
||||
assert_eq!(span.start.line, span.end.line);
|
||||
|
||||
Span {
|
||||
start: span.start,
|
||||
end: Location {
|
||||
line: span.start.line,
|
||||
column: span.start.column + self.end - 1,
|
||||
byte: span.start.byte + self.end - 1,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The internal error type.
|
||||
struct ErrorInner {
|
||||
/// The message displayed to the user.
|
||||
_message: &'static str,
|
||||
/// Where the error originated.
|
||||
_span: Span,
|
||||
}
|
||||
|
||||
/// A complete error description.
|
||||
struct Error {
|
||||
/// The internal error.
|
||||
_inner: ErrorInner,
|
||||
/// The error needed for interoperability with the rest of `time`.
|
||||
public: crate::error::InvalidFormatDescription,
|
||||
}
|
||||
|
||||
impl From<Error> for crate::error::InvalidFormatDescription {
|
||||
fn from(error: Error) -> Self {
|
||||
error.public
|
||||
}
|
||||
}
|
|
@ -0,0 +1,233 @@
|
|||
//! The format described in ISO 8601.
|
||||
|
||||
mod adt_hack;
|
||||
|
||||
use core::num::NonZeroU8;
|
||||
|
||||
pub use self::adt_hack::{DoNotRelyOnWhatThisIs, EncodedConfig};
|
||||
|
||||
/// A configuration for [`Iso8601`] that only parses values.
|
||||
const PARSING_ONLY: EncodedConfig = Config {
|
||||
formatted_components: FormattedComponents::None,
|
||||
use_separators: false,
|
||||
year_is_six_digits: false,
|
||||
date_kind: DateKind::Calendar,
|
||||
time_precision: TimePrecision::Hour {
|
||||
decimal_digits: None,
|
||||
},
|
||||
offset_precision: OffsetPrecision::Hour,
|
||||
}
|
||||
.encode();
|
||||
|
||||
/// The default configuration for [`Iso8601`].
|
||||
const DEFAULT_CONFIG: EncodedConfig = Config::DEFAULT.encode();
|
||||
|
||||
/// The format described in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html).
|
||||
///
|
||||
/// This implementation is of ISO 8601-1:2019. It may not be compatible with other versions.
|
||||
///
|
||||
/// The const parameter `CONFIG` **must** be a value that was returned by [`Config::encode`].
|
||||
/// Passing any other value is **unspecified behavior**.
|
||||
///
|
||||
/// Example: 1997-11-21T09:55:06.000000000-06:00
|
||||
///
|
||||
/// # Examples
|
||||
#[cfg_attr(feature = "formatting", doc = "```rust")]
|
||||
#[cfg_attr(not(feature = "formatting"), doc = "```rust,ignore")]
|
||||
/// # use time::format_description::well_known::Iso8601;
|
||||
/// # use time_macros::datetime;
|
||||
/// assert_eq!(
|
||||
/// datetime!(1997-11-12 9:55:06 -6:00).format(&Iso8601::DEFAULT)?,
|
||||
/// "1997-11-12T09:55:06.000000000-06:00"
|
||||
/// );
|
||||
/// # Ok::<_, time::Error>(())
|
||||
/// ```
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Iso8601<const CONFIG: EncodedConfig = DEFAULT_CONFIG>;
|
||||
|
||||
impl<const CONFIG: EncodedConfig> core::fmt::Debug for Iso8601<CONFIG> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
f.debug_struct("Iso8601")
|
||||
.field("config", &Config::decode(CONFIG))
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Iso8601<DEFAULT_CONFIG> {
|
||||
/// An [`Iso8601`] with the default configuration.
|
||||
///
|
||||
/// The following is the default behavior:
|
||||
///
|
||||
/// - The configuration can be used for both formatting and parsing.
|
||||
/// - The date, time, and UTC offset are all formatted.
|
||||
/// - Separators (such as `-` and `:`) are included.
|
||||
/// - The year contains four digits, such that the year must be between 0 and 9999.
|
||||
/// - The date uses the calendar format.
|
||||
/// - The time has precision to the second and nine decimal digits.
|
||||
/// - The UTC offset has precision to the minute.
|
||||
///
|
||||
/// If you need different behavior, use [`Config::DEFAULT`] and [`Config`]'s methods to create
|
||||
/// a custom configuration.
|
||||
pub const DEFAULT: Self = Self;
|
||||
}
|
||||
|
||||
impl Iso8601<PARSING_ONLY> {
|
||||
/// An [`Iso8601`] that can only be used for parsing. Using this to format a value is
|
||||
/// unspecified behavior.
|
||||
pub const PARSING: Self = Self;
|
||||
}
|
||||
|
||||
/// Which components to format.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum FormattedComponents {
|
||||
/// The configuration can only be used for parsing. Using this to format a value is
|
||||
/// unspecified behavior.
|
||||
None,
|
||||
/// Format only the date.
|
||||
Date,
|
||||
/// Format only the time.
|
||||
Time,
|
||||
/// Format only the UTC offset.
|
||||
Offset,
|
||||
/// Format the date and time.
|
||||
DateTime,
|
||||
/// Format the date, time, and UTC offset.
|
||||
DateTimeOffset,
|
||||
/// Format the time and UTC offset.
|
||||
TimeOffset,
|
||||
}
|
||||
|
||||
/// Which format to use for the date.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum DateKind {
|
||||
/// Use the year-month-day format.
|
||||
Calendar,
|
||||
/// Use the year-week-weekday format.
|
||||
Week,
|
||||
/// Use the week-ordinal format.
|
||||
Ordinal,
|
||||
}
|
||||
|
||||
/// The precision and number of decimal digits present for the time.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum TimePrecision {
|
||||
/// Format the hour only. Minutes, seconds, and nanoseconds will be represented with the
|
||||
/// specified number of decimal digits, if any.
|
||||
Hour {
|
||||
#[allow(clippy::missing_docs_in_private_items)]
|
||||
decimal_digits: Option<NonZeroU8>,
|
||||
},
|
||||
/// Format the hour and minute. Seconds and nanoseconds will be represented with the specified
|
||||
/// number of decimal digits, if any.
|
||||
Minute {
|
||||
#[allow(clippy::missing_docs_in_private_items)]
|
||||
decimal_digits: Option<NonZeroU8>,
|
||||
},
|
||||
/// Format the hour, minute, and second. Nanoseconds will be represented with the specified
|
||||
/// number of decimal digits, if any.
|
||||
Second {
|
||||
#[allow(clippy::missing_docs_in_private_items)]
|
||||
decimal_digits: Option<NonZeroU8>,
|
||||
},
|
||||
}
|
||||
|
||||
/// The precision for the UTC offset.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum OffsetPrecision {
|
||||
/// Format only the offset hour. Requires the offset minute to be zero.
|
||||
Hour,
|
||||
/// Format both the offset hour and minute.
|
||||
Minute,
|
||||
}
|
||||
|
||||
/// Configuration for [`Iso8601`].
|
||||
// This is only used as a const generic, so there's no need to have a number of implementations on
|
||||
// it.
|
||||
#[allow(missing_copy_implementations)]
|
||||
#[doc(alias = "EncodedConfig")] // People will likely search for `EncodedConfig`, so show them this.
|
||||
#[derive(Debug)]
|
||||
pub struct Config {
|
||||
/// Which components, if any, will be formatted.
|
||||
pub(crate) formatted_components: FormattedComponents,
|
||||
/// Whether the format contains separators (such as `-` or `:`).
|
||||
pub(crate) use_separators: bool,
|
||||
/// Whether the year is six digits.
|
||||
pub(crate) year_is_six_digits: bool,
|
||||
/// The format used for the date.
|
||||
pub(crate) date_kind: DateKind,
|
||||
/// The precision and number of decimal digits present for the time.
|
||||
pub(crate) time_precision: TimePrecision,
|
||||
/// The precision for the UTC offset.
|
||||
pub(crate) offset_precision: OffsetPrecision,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
/// A configuration for the [`Iso8601`] format.
|
||||
///
|
||||
/// The following is the default behavior:
|
||||
///
|
||||
/// - The configuration can be used for both formatting and parsing.
|
||||
/// - The date, time, and UTC offset are all formatted.
|
||||
/// - Separators (such as `-` and `:`) are included.
|
||||
/// - The year contains four digits, such that the year must be between 0 and 9999.
|
||||
/// - The date uses the calendar format.
|
||||
/// - The time has precision to the second and nine decimal digits.
|
||||
/// - The UTC offset has precision to the minute.
|
||||
///
|
||||
/// If you need different behavior, use the setter methods on this struct.
|
||||
pub const DEFAULT: Self = Self {
|
||||
formatted_components: FormattedComponents::DateTimeOffset,
|
||||
use_separators: true,
|
||||
year_is_six_digits: false,
|
||||
date_kind: DateKind::Calendar,
|
||||
time_precision: TimePrecision::Second {
|
||||
decimal_digits: NonZeroU8::new(9),
|
||||
},
|
||||
offset_precision: OffsetPrecision::Minute,
|
||||
};
|
||||
|
||||
/// Set whether the format the date, time, and/or UTC offset.
|
||||
pub const fn set_formatted_components(self, formatted_components: FormattedComponents) -> Self {
|
||||
Self {
|
||||
formatted_components,
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
/// Set whether the format contains separators (such as `-` or `:`).
|
||||
pub const fn set_use_separators(self, use_separators: bool) -> Self {
|
||||
Self {
|
||||
use_separators,
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
/// Set whether the year is six digits.
|
||||
pub const fn set_year_is_six_digits(self, year_is_six_digits: bool) -> Self {
|
||||
Self {
|
||||
year_is_six_digits,
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the format used for the date.
|
||||
pub const fn set_date_kind(self, date_kind: DateKind) -> Self {
|
||||
Self { date_kind, ..self }
|
||||
}
|
||||
|
||||
/// Set the precision and number of decimal digits present for the time.
|
||||
pub const fn set_time_precision(self, time_precision: TimePrecision) -> Self {
|
||||
Self {
|
||||
time_precision,
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the precision for the UTC offset.
|
||||
pub const fn set_offset_precision(self, offset_precision: OffsetPrecision) -> Self {
|
||||
Self {
|
||||
offset_precision,
|
||||
..self
|
||||
}
|
||||
}
|
||||
}
|
249
third_party/rust/time/src/format_description/well_known/iso8601/adt_hack.rs
поставляемый
Normal file
249
third_party/rust/time/src/format_description/well_known/iso8601/adt_hack.rs
поставляемый
Normal file
|
@ -0,0 +1,249 @@
|
|||
//! Hackery to work around not being able to use ADTs in const generics on stable.
|
||||
|
||||
use core::num::NonZeroU8;
|
||||
|
||||
#[cfg(feature = "formatting")]
|
||||
use super::Iso8601;
|
||||
use super::{Config, DateKind, FormattedComponents as FC, OffsetPrecision, TimePrecision};
|
||||
|
||||
// This provides a way to include `EncodedConfig` in documentation without displaying the type it is
|
||||
// aliased to.
|
||||
#[doc(hidden)]
|
||||
pub type DoNotRelyOnWhatThisIs = u128;
|
||||
|
||||
/// An encoded [`Config`] that can be used as a const parameter to [`Iso8601`].
|
||||
///
|
||||
/// The type this is aliased to must not be relied upon. It can change in any release without
|
||||
/// notice.
|
||||
pub type EncodedConfig = DoNotRelyOnWhatThisIs;
|
||||
|
||||
#[cfg(feature = "formatting")]
|
||||
impl<const CONFIG: EncodedConfig> Iso8601<CONFIG> {
|
||||
/// The user-provided configuration for the ISO 8601 format.
|
||||
const CONFIG: Config = Config::decode(CONFIG);
|
||||
/// Whether the date should be formatted.
|
||||
pub(crate) const FORMAT_DATE: bool = matches!(
|
||||
Self::CONFIG.formatted_components,
|
||||
FC::Date | FC::DateTime | FC::DateTimeOffset
|
||||
);
|
||||
/// Whether the time should be formatted.
|
||||
pub(crate) const FORMAT_TIME: bool = matches!(
|
||||
Self::CONFIG.formatted_components,
|
||||
FC::Time | FC::DateTime | FC::DateTimeOffset | FC::TimeOffset
|
||||
);
|
||||
/// Whether the UTC offset should be formatted.
|
||||
pub(crate) const FORMAT_OFFSET: bool = matches!(
|
||||
Self::CONFIG.formatted_components,
|
||||
FC::Offset | FC::DateTimeOffset | FC::TimeOffset
|
||||
);
|
||||
/// Whether the year is six digits.
|
||||
pub(crate) const YEAR_IS_SIX_DIGITS: bool = Self::CONFIG.year_is_six_digits;
|
||||
/// Whether the format contains separators (such as `-` or `:`).
|
||||
pub(crate) const USE_SEPARATORS: bool = Self::CONFIG.use_separators;
|
||||
/// Which format to use for the date.
|
||||
pub(crate) const DATE_KIND: DateKind = Self::CONFIG.date_kind;
|
||||
/// The precision and number of decimal digits to use for the time.
|
||||
pub(crate) const TIME_PRECISION: TimePrecision = Self::CONFIG.time_precision;
|
||||
/// The precision for the UTC offset.
|
||||
pub(crate) const OFFSET_PRECISION: OffsetPrecision = Self::CONFIG.offset_precision;
|
||||
}
|
||||
|
||||
impl Config {
|
||||
/// Encode the configuration, permitting it to be used as a const parameter of [`Iso8601`].
|
||||
///
|
||||
/// The value returned by this method must only be used as a const parameter to [`Iso8601`]. Any
|
||||
/// other usage is unspecified behavior.
|
||||
pub const fn encode(&self) -> EncodedConfig {
|
||||
let mut bytes = [0; EncodedConfig::BITS as usize / 8];
|
||||
|
||||
bytes[0] = match self.formatted_components {
|
||||
FC::None => 0,
|
||||
FC::Date => 1,
|
||||
FC::Time => 2,
|
||||
FC::Offset => 3,
|
||||
FC::DateTime => 4,
|
||||
FC::DateTimeOffset => 5,
|
||||
FC::TimeOffset => 6,
|
||||
};
|
||||
bytes[1] = self.use_separators as _;
|
||||
bytes[2] = self.year_is_six_digits as _;
|
||||
bytes[3] = match self.date_kind {
|
||||
DateKind::Calendar => 0,
|
||||
DateKind::Week => 1,
|
||||
DateKind::Ordinal => 2,
|
||||
};
|
||||
bytes[4] = match self.time_precision {
|
||||
TimePrecision::Hour { .. } => 0,
|
||||
TimePrecision::Minute { .. } => 1,
|
||||
TimePrecision::Second { .. } => 2,
|
||||
};
|
||||
bytes[5] = match self.time_precision {
|
||||
TimePrecision::Hour { decimal_digits }
|
||||
| TimePrecision::Minute { decimal_digits }
|
||||
| TimePrecision::Second { decimal_digits } => match decimal_digits {
|
||||
None => 0,
|
||||
Some(decimal_digits) => decimal_digits.get(),
|
||||
},
|
||||
};
|
||||
bytes[6] = match self.offset_precision {
|
||||
OffsetPrecision::Hour => 0,
|
||||
OffsetPrecision::Minute => 1,
|
||||
};
|
||||
|
||||
EncodedConfig::from_be_bytes(bytes)
|
||||
}
|
||||
|
||||
/// Decode the configuration. The configuration must have been generated from
|
||||
/// [`Config::encode`].
|
||||
pub(super) const fn decode(encoded: EncodedConfig) -> Self {
|
||||
let bytes = encoded.to_be_bytes();
|
||||
|
||||
let formatted_components = match bytes[0] {
|
||||
0 => FC::None,
|
||||
1 => FC::Date,
|
||||
2 => FC::Time,
|
||||
3 => FC::Offset,
|
||||
4 => FC::DateTime,
|
||||
5 => FC::DateTimeOffset,
|
||||
6 => FC::TimeOffset,
|
||||
_ => panic!("invalid configuration"),
|
||||
};
|
||||
let use_separators = match bytes[1] {
|
||||
0 => false,
|
||||
1 => true,
|
||||
_ => panic!("invalid configuration"),
|
||||
};
|
||||
let year_is_six_digits = match bytes[2] {
|
||||
0 => false,
|
||||
1 => true,
|
||||
_ => panic!("invalid configuration"),
|
||||
};
|
||||
let date_kind = match bytes[3] {
|
||||
0 => DateKind::Calendar,
|
||||
1 => DateKind::Week,
|
||||
2 => DateKind::Ordinal,
|
||||
_ => panic!("invalid configuration"),
|
||||
};
|
||||
let time_precision = match bytes[4] {
|
||||
0 => TimePrecision::Hour {
|
||||
decimal_digits: NonZeroU8::new(bytes[5]),
|
||||
},
|
||||
1 => TimePrecision::Minute {
|
||||
decimal_digits: NonZeroU8::new(bytes[5]),
|
||||
},
|
||||
2 => TimePrecision::Second {
|
||||
decimal_digits: NonZeroU8::new(bytes[5]),
|
||||
},
|
||||
_ => panic!("invalid configuration"),
|
||||
};
|
||||
let offset_precision = match bytes[6] {
|
||||
0 => OffsetPrecision::Hour,
|
||||
1 => OffsetPrecision::Minute,
|
||||
_ => panic!("invalid configuration"),
|
||||
};
|
||||
|
||||
// No `for` loops in `const fn`.
|
||||
let mut idx = 7; // first unused byte
|
||||
while idx < EncodedConfig::BITS as usize / 8 {
|
||||
assert!(bytes[idx] == 0, "invalid configuration");
|
||||
idx += 1;
|
||||
}
|
||||
|
||||
Self {
|
||||
formatted_components,
|
||||
use_separators,
|
||||
year_is_six_digits,
|
||||
date_kind,
|
||||
time_precision,
|
||||
offset_precision,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
macro_rules! eq {
|
||||
($a:expr, $b:expr) => {{
|
||||
let a = $a;
|
||||
let b = $b;
|
||||
a.formatted_components == b.formatted_components
|
||||
&& a.use_separators == b.use_separators
|
||||
&& a.year_is_six_digits == b.year_is_six_digits
|
||||
&& a.date_kind == b.date_kind
|
||||
&& a.time_precision == b.time_precision
|
||||
&& a.offset_precision == b.offset_precision
|
||||
}};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encoding_roundtrip() {
|
||||
macro_rules! assert_roundtrip {
|
||||
($config:expr) => {
|
||||
let config = $config;
|
||||
let encoded = config.encode();
|
||||
let decoded = Config::decode(encoded);
|
||||
assert!(eq!(config, decoded));
|
||||
};
|
||||
}
|
||||
|
||||
assert_roundtrip!(Config::DEFAULT);
|
||||
assert_roundtrip!(Config::DEFAULT.set_formatted_components(FC::None));
|
||||
assert_roundtrip!(Config::DEFAULT.set_formatted_components(FC::Date));
|
||||
assert_roundtrip!(Config::DEFAULT.set_formatted_components(FC::Time));
|
||||
assert_roundtrip!(Config::DEFAULT.set_formatted_components(FC::Offset));
|
||||
assert_roundtrip!(Config::DEFAULT.set_formatted_components(FC::DateTime));
|
||||
assert_roundtrip!(Config::DEFAULT.set_formatted_components(FC::DateTimeOffset));
|
||||
assert_roundtrip!(Config::DEFAULT.set_formatted_components(FC::TimeOffset));
|
||||
assert_roundtrip!(Config::DEFAULT.set_use_separators(false));
|
||||
assert_roundtrip!(Config::DEFAULT.set_use_separators(true));
|
||||
assert_roundtrip!(Config::DEFAULT.set_year_is_six_digits(false));
|
||||
assert_roundtrip!(Config::DEFAULT.set_year_is_six_digits(true));
|
||||
assert_roundtrip!(Config::DEFAULT.set_date_kind(DateKind::Calendar));
|
||||
assert_roundtrip!(Config::DEFAULT.set_date_kind(DateKind::Week));
|
||||
assert_roundtrip!(Config::DEFAULT.set_date_kind(DateKind::Ordinal));
|
||||
assert_roundtrip!(Config::DEFAULT.set_time_precision(TimePrecision::Hour {
|
||||
decimal_digits: None,
|
||||
}));
|
||||
assert_roundtrip!(Config::DEFAULT.set_time_precision(TimePrecision::Minute {
|
||||
decimal_digits: None,
|
||||
}));
|
||||
assert_roundtrip!(Config::DEFAULT.set_time_precision(TimePrecision::Second {
|
||||
decimal_digits: None,
|
||||
}));
|
||||
assert_roundtrip!(Config::DEFAULT.set_time_precision(TimePrecision::Hour {
|
||||
decimal_digits: NonZeroU8::new(1),
|
||||
}));
|
||||
assert_roundtrip!(Config::DEFAULT.set_time_precision(TimePrecision::Minute {
|
||||
decimal_digits: NonZeroU8::new(1),
|
||||
}));
|
||||
assert_roundtrip!(Config::DEFAULT.set_time_precision(TimePrecision::Second {
|
||||
decimal_digits: NonZeroU8::new(1),
|
||||
}));
|
||||
assert_roundtrip!(Config::DEFAULT.set_offset_precision(OffsetPrecision::Hour));
|
||||
assert_roundtrip!(Config::DEFAULT.set_offset_precision(OffsetPrecision::Minute));
|
||||
}
|
||||
|
||||
macro_rules! assert_decode_fail {
|
||||
($encoding:expr) => {
|
||||
assert!(
|
||||
std::panic::catch_unwind(|| {
|
||||
Config::decode($encoding);
|
||||
})
|
||||
.is_err()
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_fail() {
|
||||
assert_decode_fail!(0x07_00_00_00_00_00_00_00_00_00_00_00_00_00_00_00);
|
||||
assert_decode_fail!(0x00_02_00_00_00_00_00_00_00_00_00_00_00_00_00_00);
|
||||
assert_decode_fail!(0x00_00_02_00_00_00_00_00_00_00_00_00_00_00_00_00);
|
||||
assert_decode_fail!(0x00_00_00_03_00_00_00_00_00_00_00_00_00_00_00_00);
|
||||
assert_decode_fail!(0x00_00_00_00_03_00_00_00_00_00_00_00_00_00_00_00);
|
||||
assert_decode_fail!(0x00_00_00_00_00_00_02_00_00_00_00_00_00_00_00_00);
|
||||
assert_decode_fail!(0x00_00_00_00_00_00_00_01_00_00_00_00_00_00_00_00);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
//! The format described in RFC 2822.
|
||||
|
||||
/// The format described in [RFC 2822](https://tools.ietf.org/html/rfc2822#section-3.3).
|
||||
///
|
||||
/// Example: Fri, 21 Nov 1997 09:55:06 -0600
|
||||
///
|
||||
/// # Examples
|
||||
#[cfg_attr(feature = "parsing", doc = "```rust")]
|
||||
#[cfg_attr(not(feature = "parsing"), doc = "```rust,ignore")]
|
||||
/// # use time::{format_description::well_known::Rfc2822, OffsetDateTime};
|
||||
/// use time_macros::datetime;
|
||||
/// assert_eq!(
|
||||
/// OffsetDateTime::parse("Sat, 12 Jun 1993 13:25:19 GMT", &Rfc2822)?,
|
||||
/// datetime!(1993-06-12 13:25:19 +00:00)
|
||||
/// );
|
||||
/// # Ok::<_, time::Error>(())
|
||||
/// ```
|
||||
///
|
||||
#[cfg_attr(feature = "formatting", doc = "```rust")]
|
||||
#[cfg_attr(not(feature = "formatting"), doc = "```rust,ignore")]
|
||||
/// # use time::format_description::well_known::Rfc2822;
|
||||
/// # use time_macros::datetime;
|
||||
/// assert_eq!(
|
||||
/// datetime!(1997-11-21 09:55:06 -06:00).format(&Rfc2822)?,
|
||||
/// "Fri, 21 Nov 1997 09:55:06 -0600"
|
||||
/// );
|
||||
/// # Ok::<_, time::Error>(())
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Rfc2822;
|
|
@ -0,0 +1,30 @@
|
|||
//! The format described in RFC 3339.
|
||||
|
||||
/// The format described in [RFC 3339](https://tools.ietf.org/html/rfc3339#section-5.6).
|
||||
///
|
||||
/// Format example: 1985-04-12T23:20:50.52Z
|
||||
///
|
||||
/// # Examples
|
||||
#[cfg_attr(feature = "parsing", doc = "```rust")]
|
||||
#[cfg_attr(not(feature = "parsing"), doc = "```rust,ignore")]
|
||||
/// # use time::{format_description::well_known::Rfc3339, OffsetDateTime};
|
||||
/// # use time_macros::datetime;
|
||||
/// assert_eq!(
|
||||
/// OffsetDateTime::parse("1985-04-12T23:20:50.52Z", &Rfc3339)?,
|
||||
/// datetime!(1985-04-12 23:20:50.52 +00:00)
|
||||
/// );
|
||||
/// # Ok::<_, time::Error>(())
|
||||
/// ```
|
||||
///
|
||||
#[cfg_attr(feature = "formatting", doc = "```rust")]
|
||||
#[cfg_attr(not(feature = "formatting"), doc = "```rust,ignore")]
|
||||
/// # use time::format_description::well_known::Rfc3339;
|
||||
/// # use time_macros::datetime;
|
||||
/// assert_eq!(
|
||||
/// datetime!(1985-04-12 23:20:50.52 +00:00).format(&Rfc3339)?,
|
||||
/// "1985-04-12T23:20:50.52Z"
|
||||
/// );
|
||||
/// # Ok::<_, time::Error>(())
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Rfc3339;
|
|
@ -3,10 +3,11 @@
|
|||
use core::ops::Deref;
|
||||
use std::io;
|
||||
|
||||
use crate::format_description::well_known::{Rfc2822, Rfc3339};
|
||||
use crate::format_description::FormatItem;
|
||||
use crate::format_description::well_known::iso8601::EncodedConfig;
|
||||
use crate::format_description::well_known::{Iso8601, Rfc2822, Rfc3339};
|
||||
use crate::format_description::{FormatItem, OwnedFormatItem};
|
||||
use crate::formatting::{
|
||||
format_component, format_number_pad_zero, write, MONTH_NAMES, WEEKDAY_NAMES,
|
||||
format_component, format_number_pad_zero, iso8601, write, MONTH_NAMES, WEEKDAY_NAMES,
|
||||
};
|
||||
use crate::{error, Date, Time, UtcOffset};
|
||||
|
||||
|
@ -15,8 +16,11 @@ use crate::{error, Date, Time, UtcOffset};
|
|||
pub trait Formattable: sealed::Sealed {}
|
||||
impl Formattable for FormatItem<'_> {}
|
||||
impl Formattable for [FormatItem<'_>] {}
|
||||
impl Formattable for OwnedFormatItem {}
|
||||
impl Formattable for [OwnedFormatItem] {}
|
||||
impl Formattable for Rfc3339 {}
|
||||
impl Formattable for Rfc2822 {}
|
||||
impl<const CONFIG: EncodedConfig> Formattable for Iso8601<CONFIG> {}
|
||||
impl<T: Deref> Formattable for T where T::Target: Formattable {}
|
||||
|
||||
/// Seal the trait to prevent downstream users from implementing it.
|
||||
|
@ -25,7 +29,6 @@ mod sealed {
|
|||
use super::*;
|
||||
|
||||
/// Format the item using a format description, the intended output, and the various components.
|
||||
#[cfg_attr(__time_03_docs, doc(cfg(feature = "formatting")))]
|
||||
pub trait Sealed {
|
||||
/// Format the item into the provided output, returning the number of bytes written.
|
||||
fn format_into(
|
||||
|
@ -88,6 +91,43 @@ impl<'a> sealed::Sealed for [FormatItem<'a>] {
|
|||
}
|
||||
}
|
||||
|
||||
impl sealed::Sealed for OwnedFormatItem {
|
||||
fn format_into(
|
||||
&self,
|
||||
output: &mut impl io::Write,
|
||||
date: Option<Date>,
|
||||
time: Option<Time>,
|
||||
offset: Option<UtcOffset>,
|
||||
) -> Result<usize, error::Format> {
|
||||
match self {
|
||||
Self::Literal(literal) => Ok(write(output, literal)?),
|
||||
Self::Component(component) => format_component(output, *component, date, time, offset),
|
||||
Self::Compound(items) => items.format_into(output, date, time, offset),
|
||||
Self::Optional(item) => item.format_into(output, date, time, offset),
|
||||
Self::First(items) => match &**items {
|
||||
[] => Ok(0),
|
||||
[item, ..] => item.format_into(output, date, time, offset),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl sealed::Sealed for [OwnedFormatItem] {
|
||||
fn format_into(
|
||||
&self,
|
||||
output: &mut impl io::Write,
|
||||
date: Option<Date>,
|
||||
time: Option<Time>,
|
||||
offset: Option<UtcOffset>,
|
||||
) -> Result<usize, error::Format> {
|
||||
let mut bytes = 0;
|
||||
for item in self.iter() {
|
||||
bytes += item.format_into(output, date, time, offset)?;
|
||||
}
|
||||
Ok(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Deref> sealed::Sealed for T
|
||||
where
|
||||
T::Target: sealed::Sealed,
|
||||
|
@ -133,22 +173,22 @@ impl sealed::Sealed for Rfc2822 {
|
|||
&WEEKDAY_NAMES[date.weekday().number_days_from_monday() as usize][..3],
|
||||
)?;
|
||||
bytes += write(output, b", ")?;
|
||||
bytes += format_number_pad_zero::<_, _, 2>(output, day)?;
|
||||
bytes += format_number_pad_zero::<2, _, _>(output, day)?;
|
||||
bytes += write(output, b" ")?;
|
||||
bytes += write(output, &MONTH_NAMES[month as usize - 1][..3])?;
|
||||
bytes += write(output, b" ")?;
|
||||
bytes += format_number_pad_zero::<_, _, 4>(output, year as u32)?;
|
||||
bytes += format_number_pad_zero::<4, _, _>(output, year as u32)?;
|
||||
bytes += write(output, b" ")?;
|
||||
bytes += format_number_pad_zero::<_, _, 2>(output, time.hour())?;
|
||||
bytes += format_number_pad_zero::<2, _, _>(output, time.hour())?;
|
||||
bytes += write(output, b":")?;
|
||||
bytes += format_number_pad_zero::<_, _, 2>(output, time.minute())?;
|
||||
bytes += format_number_pad_zero::<2, _, _>(output, time.minute())?;
|
||||
bytes += write(output, b":")?;
|
||||
bytes += format_number_pad_zero::<_, _, 2>(output, time.second())?;
|
||||
bytes += format_number_pad_zero::<2, _, _>(output, time.second())?;
|
||||
bytes += write(output, b" ")?;
|
||||
bytes += write(output, if offset.is_negative() { b"-" } else { b"+" })?;
|
||||
bytes += format_number_pad_zero::<_, _, 2>(output, offset.whole_hours().unsigned_abs())?;
|
||||
bytes += format_number_pad_zero::<2, _, _>(output, offset.whole_hours().unsigned_abs())?;
|
||||
bytes +=
|
||||
format_number_pad_zero::<_, _, 2>(output, offset.minutes_past_hour().unsigned_abs())?;
|
||||
format_number_pad_zero::<2, _, _>(output, offset.minutes_past_hour().unsigned_abs())?;
|
||||
|
||||
Ok(bytes)
|
||||
}
|
||||
|
@ -177,60 +217,86 @@ impl sealed::Sealed for Rfc3339 {
|
|||
return Err(error::Format::InvalidComponent("offset_second"));
|
||||
}
|
||||
|
||||
bytes += format_number_pad_zero::<_, _, 4>(output, year as u32)?;
|
||||
bytes += write(output, &[b'-'])?;
|
||||
bytes += format_number_pad_zero::<_, _, 2>(output, date.month() as u8)?;
|
||||
bytes += write(output, &[b'-'])?;
|
||||
bytes += format_number_pad_zero::<_, _, 2>(output, date.day())?;
|
||||
bytes += write(output, &[b'T'])?;
|
||||
bytes += format_number_pad_zero::<_, _, 2>(output, time.hour())?;
|
||||
bytes += write(output, &[b':'])?;
|
||||
bytes += format_number_pad_zero::<_, _, 2>(output, time.minute())?;
|
||||
bytes += write(output, &[b':'])?;
|
||||
bytes += format_number_pad_zero::<_, _, 2>(output, time.second())?;
|
||||
bytes += format_number_pad_zero::<4, _, _>(output, year as u32)?;
|
||||
bytes += write(output, b"-")?;
|
||||
bytes += format_number_pad_zero::<2, _, _>(output, date.month() as u8)?;
|
||||
bytes += write(output, b"-")?;
|
||||
bytes += format_number_pad_zero::<2, _, _>(output, date.day())?;
|
||||
bytes += write(output, b"T")?;
|
||||
bytes += format_number_pad_zero::<2, _, _>(output, time.hour())?;
|
||||
bytes += write(output, b":")?;
|
||||
bytes += format_number_pad_zero::<2, _, _>(output, time.minute())?;
|
||||
bytes += write(output, b":")?;
|
||||
bytes += format_number_pad_zero::<2, _, _>(output, time.second())?;
|
||||
|
||||
#[allow(clippy::if_not_else)]
|
||||
if time.nanosecond() != 0 {
|
||||
let nanos = time.nanosecond();
|
||||
bytes += write(output, &[b'.'])?;
|
||||
bytes += write(output, b".")?;
|
||||
bytes += if nanos % 10 != 0 {
|
||||
format_number_pad_zero::<_, _, 9>(output, nanos)
|
||||
format_number_pad_zero::<9, _, _>(output, nanos)
|
||||
} else if (nanos / 10) % 10 != 0 {
|
||||
format_number_pad_zero::<_, _, 8>(output, nanos / 10)
|
||||
format_number_pad_zero::<8, _, _>(output, nanos / 10)
|
||||
} else if (nanos / 100) % 10 != 0 {
|
||||
format_number_pad_zero::<_, _, 7>(output, nanos / 100)
|
||||
format_number_pad_zero::<7, _, _>(output, nanos / 100)
|
||||
} else if (nanos / 1_000) % 10 != 0 {
|
||||
format_number_pad_zero::<_, _, 6>(output, nanos / 1_000)
|
||||
format_number_pad_zero::<6, _, _>(output, nanos / 1_000)
|
||||
} else if (nanos / 10_000) % 10 != 0 {
|
||||
format_number_pad_zero::<_, _, 5>(output, nanos / 10_000)
|
||||
format_number_pad_zero::<5, _, _>(output, nanos / 10_000)
|
||||
} else if (nanos / 100_000) % 10 != 0 {
|
||||
format_number_pad_zero::<_, _, 4>(output, nanos / 100_000)
|
||||
format_number_pad_zero::<4, _, _>(output, nanos / 100_000)
|
||||
} else if (nanos / 1_000_000) % 10 != 0 {
|
||||
format_number_pad_zero::<_, _, 3>(output, nanos / 1_000_000)
|
||||
format_number_pad_zero::<3, _, _>(output, nanos / 1_000_000)
|
||||
} else if (nanos / 10_000_000) % 10 != 0 {
|
||||
format_number_pad_zero::<_, _, 2>(output, nanos / 10_000_000)
|
||||
format_number_pad_zero::<2, _, _>(output, nanos / 10_000_000)
|
||||
} else {
|
||||
format_number_pad_zero::<_, _, 1>(output, nanos / 100_000_000)
|
||||
format_number_pad_zero::<1, _, _>(output, nanos / 100_000_000)
|
||||
}?;
|
||||
}
|
||||
|
||||
if offset == UtcOffset::UTC {
|
||||
bytes += write(output, &[b'Z'])?;
|
||||
bytes += write(output, b"Z")?;
|
||||
return Ok(bytes);
|
||||
}
|
||||
|
||||
bytes += write(
|
||||
output,
|
||||
if offset.is_negative() {
|
||||
&[b'-']
|
||||
} else {
|
||||
&[b'+']
|
||||
},
|
||||
)?;
|
||||
bytes += format_number_pad_zero::<_, _, 2>(output, offset.whole_hours().unsigned_abs())?;
|
||||
bytes += write(output, &[b':'])?;
|
||||
bytes += write(output, if offset.is_negative() { b"-" } else { b"+" })?;
|
||||
bytes += format_number_pad_zero::<2, _, _>(output, offset.whole_hours().unsigned_abs())?;
|
||||
bytes += write(output, b":")?;
|
||||
bytes +=
|
||||
format_number_pad_zero::<_, _, 2>(output, offset.minutes_past_hour().unsigned_abs())?;
|
||||
format_number_pad_zero::<2, _, _>(output, offset.minutes_past_hour().unsigned_abs())?;
|
||||
|
||||
Ok(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const CONFIG: EncodedConfig> sealed::Sealed for Iso8601<CONFIG> {
|
||||
fn format_into(
|
||||
&self,
|
||||
output: &mut impl io::Write,
|
||||
date: Option<Date>,
|
||||
time: Option<Time>,
|
||||
offset: Option<UtcOffset>,
|
||||
) -> Result<usize, error::Format> {
|
||||
let mut bytes = 0;
|
||||
|
||||
if Self::FORMAT_DATE {
|
||||
let date = date.ok_or(error::Format::InsufficientTypeInformation)?;
|
||||
bytes += iso8601::format_date::<_, CONFIG>(output, date)?;
|
||||
}
|
||||
if Self::FORMAT_TIME {
|
||||
let time = time.ok_or(error::Format::InsufficientTypeInformation)?;
|
||||
bytes += iso8601::format_time::<_, CONFIG>(output, time)?;
|
||||
}
|
||||
if Self::FORMAT_OFFSET {
|
||||
let offset = offset.ok_or(error::Format::InsufficientTypeInformation)?;
|
||||
bytes += iso8601::format_offset::<_, CONFIG>(output, offset)?;
|
||||
}
|
||||
|
||||
if bytes == 0 {
|
||||
// The only reason there would be no bytes written is if the format was only for
|
||||
// parsing.
|
||||
panic!("attempted to format a parsing-only format description");
|
||||
}
|
||||
|
||||
Ok(bytes)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,139 @@
|
|||
//! Helpers for implementing formatting for ISO 8601.
|
||||
|
||||
use std::io;
|
||||
|
||||
use crate::format_description::well_known::iso8601::{
|
||||
DateKind, EncodedConfig, OffsetPrecision, TimePrecision,
|
||||
};
|
||||
use crate::format_description::well_known::Iso8601;
|
||||
use crate::formatting::{format_float, format_number_pad_zero, write, write_if, write_if_else};
|
||||
use crate::{error, Date, Time, UtcOffset};
|
||||
|
||||
/// Format the date portion of ISO 8601.
|
||||
pub(super) fn format_date<W: io::Write, const CONFIG: EncodedConfig>(
|
||||
output: &mut W,
|
||||
date: Date,
|
||||
) -> Result<usize, error::Format> {
|
||||
let mut bytes = 0;
|
||||
|
||||
match Iso8601::<CONFIG>::DATE_KIND {
|
||||
DateKind::Calendar => {
|
||||
let (year, month, day) = date.to_calendar_date();
|
||||
if Iso8601::<CONFIG>::YEAR_IS_SIX_DIGITS {
|
||||
bytes += write_if_else(output, year < 0, b"-", b"+")?;
|
||||
bytes += format_number_pad_zero::<6, _, _>(output, year.unsigned_abs())?;
|
||||
} else if !(0..=9999).contains(&year) {
|
||||
return Err(error::Format::InvalidComponent("year"));
|
||||
} else {
|
||||
bytes += format_number_pad_zero::<4, _, _>(output, year as u32)?;
|
||||
}
|
||||
bytes += write_if(output, Iso8601::<CONFIG>::USE_SEPARATORS, b"-")?;
|
||||
bytes += format_number_pad_zero::<2, _, _>(output, month as u8)?;
|
||||
bytes += write_if(output, Iso8601::<CONFIG>::USE_SEPARATORS, b"-")?;
|
||||
bytes += format_number_pad_zero::<2, _, _>(output, day)?;
|
||||
}
|
||||
DateKind::Week => {
|
||||
let (year, week, day) = date.to_iso_week_date();
|
||||
if Iso8601::<CONFIG>::YEAR_IS_SIX_DIGITS {
|
||||
bytes += write_if_else(output, year < 0, b"-", b"+")?;
|
||||
bytes += format_number_pad_zero::<6, _, _>(output, year.unsigned_abs())?;
|
||||
} else if !(0..=9999).contains(&year) {
|
||||
return Err(error::Format::InvalidComponent("year"));
|
||||
} else {
|
||||
bytes += format_number_pad_zero::<4, _, _>(output, year as u32)?;
|
||||
}
|
||||
bytes += write_if_else(output, Iso8601::<CONFIG>::USE_SEPARATORS, b"-W", b"W")?;
|
||||
bytes += format_number_pad_zero::<2, _, _>(output, week)?;
|
||||
bytes += write_if(output, Iso8601::<CONFIG>::USE_SEPARATORS, b"-")?;
|
||||
bytes += format_number_pad_zero::<1, _, _>(output, day.number_from_monday())?;
|
||||
}
|
||||
DateKind::Ordinal => {
|
||||
let (year, day) = date.to_ordinal_date();
|
||||
if Iso8601::<CONFIG>::YEAR_IS_SIX_DIGITS {
|
||||
bytes += write_if_else(output, year < 0, b"-", b"+")?;
|
||||
bytes += format_number_pad_zero::<6, _, _>(output, year.unsigned_abs())?;
|
||||
} else if !(0..=9999).contains(&year) {
|
||||
return Err(error::Format::InvalidComponent("year"));
|
||||
} else {
|
||||
bytes += format_number_pad_zero::<4, _, _>(output, year as u32)?;
|
||||
}
|
||||
bytes += write_if(output, Iso8601::<CONFIG>::USE_SEPARATORS, b"-")?;
|
||||
bytes += format_number_pad_zero::<3, _, _>(output, day)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(bytes)
|
||||
}
|
||||
|
||||
/// Format the time portion of ISO 8601.
|
||||
pub(super) fn format_time<W: io::Write, const CONFIG: EncodedConfig>(
|
||||
output: &mut W,
|
||||
time: Time,
|
||||
) -> Result<usize, error::Format> {
|
||||
let mut bytes = 0;
|
||||
|
||||
// The "T" can only be omitted in extended format where there is no date being formatted.
|
||||
bytes += write_if(
|
||||
output,
|
||||
Iso8601::<CONFIG>::USE_SEPARATORS || Iso8601::<CONFIG>::FORMAT_DATE,
|
||||
b"T",
|
||||
)?;
|
||||
|
||||
let (hours, minutes, seconds, nanoseconds) = time.as_hms_nano();
|
||||
|
||||
match Iso8601::<CONFIG>::TIME_PRECISION {
|
||||
TimePrecision::Hour { decimal_digits } => {
|
||||
let hours = (hours as f64)
|
||||
+ (minutes as f64) / 60.
|
||||
+ (seconds as f64) / 3_600.
|
||||
+ (nanoseconds as f64) / 3_600. / 1_000_000_000.;
|
||||
format_float(output, hours, 2, decimal_digits)?;
|
||||
}
|
||||
TimePrecision::Minute { decimal_digits } => {
|
||||
bytes += format_number_pad_zero::<2, _, _>(output, hours)?;
|
||||
bytes += write_if(output, Iso8601::<CONFIG>::USE_SEPARATORS, b":")?;
|
||||
let minutes = (minutes as f64)
|
||||
+ (seconds as f64) / 60.
|
||||
+ (nanoseconds as f64) / 60. / 1_000_000_000.;
|
||||
bytes += format_float(output, minutes, 2, decimal_digits)?;
|
||||
}
|
||||
TimePrecision::Second { decimal_digits } => {
|
||||
bytes += format_number_pad_zero::<2, _, _>(output, hours)?;
|
||||
bytes += write_if(output, Iso8601::<CONFIG>::USE_SEPARATORS, b":")?;
|
||||
bytes += format_number_pad_zero::<2, _, _>(output, minutes)?;
|
||||
bytes += write_if(output, Iso8601::<CONFIG>::USE_SEPARATORS, b":")?;
|
||||
let seconds = (seconds as f64) + (nanoseconds as f64) / 1_000_000_000.;
|
||||
bytes += format_float(output, seconds, 2, decimal_digits)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(bytes)
|
||||
}
|
||||
|
||||
/// Format the UTC offset portion of ISO 8601.
|
||||
pub(super) fn format_offset<W: io::Write, const CONFIG: EncodedConfig>(
|
||||
output: &mut W,
|
||||
offset: UtcOffset,
|
||||
) -> Result<usize, error::Format> {
|
||||
if Iso8601::<CONFIG>::FORMAT_TIME && offset.is_utc() {
|
||||
return Ok(write(output, b"Z")?);
|
||||
}
|
||||
|
||||
let mut bytes = 0;
|
||||
|
||||
let (hours, minutes, seconds) = offset.as_hms();
|
||||
if seconds != 0 {
|
||||
return Err(error::Format::InvalidComponent("offset_second"));
|
||||
}
|
||||
bytes += write_if_else(output, offset.is_negative(), b"-", b"+")?;
|
||||
bytes += format_number_pad_zero::<2, _, _>(output, hours.unsigned_abs())?;
|
||||
|
||||
if Iso8601::<CONFIG>::OFFSET_PRECISION == OffsetPrecision::Hour && minutes != 0 {
|
||||
return Err(error::Format::InvalidComponent("offset_minute"));
|
||||
} else if Iso8601::<CONFIG>::OFFSET_PRECISION == OffsetPrecision::Minute {
|
||||
bytes += write_if(output, Iso8601::<CONFIG>::USE_SEPARATORS, b":")?;
|
||||
bytes += format_number_pad_zero::<2, _, _>(output, minutes.unsigned_abs())?;
|
||||
}
|
||||
|
||||
Ok(bytes)
|
||||
}
|
|
@ -1,7 +1,9 @@
|
|||
//! Formatting for various types.
|
||||
|
||||
pub(crate) mod formattable;
|
||||
mod iso8601;
|
||||
|
||||
use core::num::NonZeroU8;
|
||||
use std::io;
|
||||
|
||||
pub use self::formattable::Formattable;
|
||||
|
@ -116,22 +118,69 @@ impl DigitCount for u32 {
|
|||
// endregion extension trait
|
||||
|
||||
/// Write all bytes to the output, returning the number of bytes written.
|
||||
fn write(output: &mut impl io::Write, bytes: &[u8]) -> io::Result<usize> {
|
||||
pub(crate) fn write(output: &mut impl io::Write, bytes: &[u8]) -> io::Result<usize> {
|
||||
output.write_all(bytes)?;
|
||||
Ok(bytes.len())
|
||||
}
|
||||
|
||||
/// If `pred` is true, write all bytes to the output, returning the number of bytes written.
|
||||
pub(crate) fn write_if(output: &mut impl io::Write, pred: bool, bytes: &[u8]) -> io::Result<usize> {
|
||||
if pred { write(output, bytes) } else { Ok(0) }
|
||||
}
|
||||
|
||||
/// If `pred` is true, write `true_bytes` to the output. Otherwise, write `false_bytes`.
|
||||
pub(crate) fn write_if_else(
|
||||
output: &mut impl io::Write,
|
||||
pred: bool,
|
||||
true_bytes: &[u8],
|
||||
false_bytes: &[u8],
|
||||
) -> io::Result<usize> {
|
||||
write(output, if pred { true_bytes } else { false_bytes })
|
||||
}
|
||||
|
||||
/// Write the floating point number to the output, returning the number of bytes written.
|
||||
///
|
||||
/// This method accepts the number of digits before and after the decimal. The value will be padded
|
||||
/// with zeroes to the left if necessary.
|
||||
pub(crate) fn format_float(
|
||||
output: &mut impl io::Write,
|
||||
value: f64,
|
||||
digits_before_decimal: u8,
|
||||
digits_after_decimal: Option<NonZeroU8>,
|
||||
) -> io::Result<usize> {
|
||||
match digits_after_decimal {
|
||||
Some(digits_after_decimal) => {
|
||||
let digits_after_decimal = digits_after_decimal.get() as usize;
|
||||
let width = digits_before_decimal as usize + 1 + digits_after_decimal;
|
||||
write!(
|
||||
output,
|
||||
"{value:0>width$.digits_after_decimal$}",
|
||||
value = value,
|
||||
width = width,
|
||||
digits_after_decimal = digits_after_decimal,
|
||||
)?;
|
||||
Ok(width)
|
||||
}
|
||||
None => {
|
||||
let value = value.trunc() as u64;
|
||||
let width = digits_before_decimal as usize;
|
||||
write!(output, "{value:0>width$?}", value = value, width = width)?;
|
||||
Ok(width)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Format a number with the provided padding and width.
|
||||
///
|
||||
/// The sign must be written by the caller.
|
||||
pub(crate) fn format_number<W: io::Write, V: itoa::Integer + DigitCount + Copy, const WIDTH: u8>(
|
||||
pub(crate) fn format_number<const WIDTH: u8, W: io::Write, V: itoa::Integer + DigitCount + Copy>(
|
||||
output: &mut W,
|
||||
value: V,
|
||||
padding: modifier::Padding,
|
||||
) -> Result<usize, io::Error> {
|
||||
match padding {
|
||||
modifier::Padding::Space => format_number_pad_space::<_, _, WIDTH>(output, value),
|
||||
modifier::Padding::Zero => format_number_pad_zero::<_, _, WIDTH>(output, value),
|
||||
modifier::Padding::Space => format_number_pad_space::<WIDTH, _, _>(output, value),
|
||||
modifier::Padding::Zero => format_number_pad_zero::<WIDTH, _, _>(output, value),
|
||||
modifier::Padding::None => write(output, itoa::Buffer::new().format(value).as_bytes()),
|
||||
}
|
||||
}
|
||||
|
@ -140,16 +189,16 @@ pub(crate) fn format_number<W: io::Write, V: itoa::Integer + DigitCount + Copy,
|
|||
///
|
||||
/// The sign must be written by the caller.
|
||||
pub(crate) fn format_number_pad_space<
|
||||
const WIDTH: u8,
|
||||
W: io::Write,
|
||||
V: itoa::Integer + DigitCount + Copy,
|
||||
const WIDTH: u8,
|
||||
>(
|
||||
output: &mut W,
|
||||
value: V,
|
||||
) -> Result<usize, io::Error> {
|
||||
let mut bytes = 0;
|
||||
for _ in 0..(WIDTH.saturating_sub(value.num_digits())) {
|
||||
bytes += write(output, &[b' '])?;
|
||||
bytes += write(output, b" ")?;
|
||||
}
|
||||
bytes += write(output, itoa::Buffer::new().format(value).as_bytes())?;
|
||||
Ok(bytes)
|
||||
|
@ -159,16 +208,16 @@ pub(crate) fn format_number_pad_space<
|
|||
///
|
||||
/// The sign must be written by the caller.
|
||||
pub(crate) fn format_number_pad_zero<
|
||||
const WIDTH: u8,
|
||||
W: io::Write,
|
||||
V: itoa::Integer + DigitCount + Copy,
|
||||
const WIDTH: u8,
|
||||
>(
|
||||
output: &mut W,
|
||||
value: V,
|
||||
) -> Result<usize, io::Error> {
|
||||
let mut bytes = 0;
|
||||
for _ in 0..(WIDTH.saturating_sub(value.num_digits())) {
|
||||
bytes += write(output, &[b'0'])?;
|
||||
bytes += write(output, b"0")?;
|
||||
}
|
||||
bytes += write(output, itoa::Buffer::new().format(value).as_bytes())?;
|
||||
Ok(bytes)
|
||||
|
@ -211,7 +260,7 @@ fn fmt_day(
|
|||
date: Date,
|
||||
modifier::Day { padding }: modifier::Day,
|
||||
) -> Result<usize, io::Error> {
|
||||
format_number::<_, _, 2>(output, date.day(), padding)
|
||||
format_number::<2, _, _>(output, date.day(), padding)
|
||||
}
|
||||
|
||||
/// Format the month into the designated output.
|
||||
|
@ -226,7 +275,7 @@ fn fmt_month(
|
|||
) -> Result<usize, io::Error> {
|
||||
match repr {
|
||||
modifier::MonthRepr::Numerical => {
|
||||
format_number::<_, _, 2>(output, date.month() as u8, padding)
|
||||
format_number::<2, _, _>(output, date.month() as u8, padding)
|
||||
}
|
||||
modifier::MonthRepr::Long => write(output, MONTH_NAMES[date.month() as usize - 1]),
|
||||
modifier::MonthRepr::Short => write(output, &MONTH_NAMES[date.month() as usize - 1][..3]),
|
||||
|
@ -239,7 +288,7 @@ fn fmt_ordinal(
|
|||
date: Date,
|
||||
modifier::Ordinal { padding }: modifier::Ordinal,
|
||||
) -> Result<usize, io::Error> {
|
||||
format_number::<_, _, 3>(output, date.ordinal(), padding)
|
||||
format_number::<3, _, _>(output, date.ordinal(), padding)
|
||||
}
|
||||
|
||||
/// Format the weekday into the designated output.
|
||||
|
@ -261,12 +310,12 @@ fn fmt_weekday(
|
|||
output,
|
||||
WEEKDAY_NAMES[date.weekday().number_days_from_monday() as usize],
|
||||
),
|
||||
modifier::WeekdayRepr::Sunday => format_number::<_, _, 1>(
|
||||
modifier::WeekdayRepr::Sunday => format_number::<1, _, _>(
|
||||
output,
|
||||
date.weekday().number_days_from_sunday() + one_indexed as u8,
|
||||
modifier::Padding::None,
|
||||
),
|
||||
modifier::WeekdayRepr::Monday => format_number::<_, _, 1>(
|
||||
modifier::WeekdayRepr::Monday => format_number::<1, _, _>(
|
||||
output,
|
||||
date.weekday().number_days_from_monday() + one_indexed as u8,
|
||||
modifier::Padding::None,
|
||||
|
@ -280,7 +329,7 @@ fn fmt_week_number(
|
|||
date: Date,
|
||||
modifier::WeekNumber { padding, repr }: modifier::WeekNumber,
|
||||
) -> Result<usize, io::Error> {
|
||||
format_number::<_, _, 2>(
|
||||
format_number::<2, _, _>(
|
||||
output,
|
||||
match repr {
|
||||
modifier::WeekNumberRepr::Iso => date.iso_week(),
|
||||
|
@ -313,18 +362,18 @@ fn fmt_year(
|
|||
};
|
||||
let format_number = match repr {
|
||||
#[cfg(feature = "large-dates")]
|
||||
modifier::YearRepr::Full if value.abs() >= 100_000 => format_number::<_, _, 6>,
|
||||
modifier::YearRepr::Full if value.abs() >= 100_000 => format_number::<6, _, _>,
|
||||
#[cfg(feature = "large-dates")]
|
||||
modifier::YearRepr::Full if value.abs() >= 10_000 => format_number::<_, _, 5>,
|
||||
modifier::YearRepr::Full => format_number::<_, _, 4>,
|
||||
modifier::YearRepr::LastTwo => format_number::<_, _, 2>,
|
||||
modifier::YearRepr::Full if value.abs() >= 10_000 => format_number::<5, _, _>,
|
||||
modifier::YearRepr::Full => format_number::<4, _, _>,
|
||||
modifier::YearRepr::LastTwo => format_number::<2, _, _>,
|
||||
};
|
||||
let mut bytes = 0;
|
||||
if repr != modifier::YearRepr::LastTwo {
|
||||
if full_year < 0 {
|
||||
bytes += write(output, &[b'-'])?;
|
||||
bytes += write(output, b"-")?;
|
||||
} else if sign_is_mandatory || cfg!(feature = "large-dates") && full_year >= 10_000 {
|
||||
bytes += write(output, &[b'+'])?;
|
||||
bytes += write(output, b"+")?;
|
||||
}
|
||||
}
|
||||
bytes += format_number(output, value.unsigned_abs(), padding)?;
|
||||
|
@ -348,7 +397,7 @@ fn fmt_hour(
|
|||
(hour, true) if hour < 12 => hour,
|
||||
(hour, true) => hour - 12,
|
||||
};
|
||||
format_number::<_, _, 2>(output, value, padding)
|
||||
format_number::<2, _, _>(output, value, padding)
|
||||
}
|
||||
|
||||
/// Format the minute into the designated output.
|
||||
|
@ -357,7 +406,7 @@ fn fmt_minute(
|
|||
time: Time,
|
||||
modifier::Minute { padding }: modifier::Minute,
|
||||
) -> Result<usize, io::Error> {
|
||||
format_number::<_, _, 2>(output, time.minute(), padding)
|
||||
format_number::<2, _, _>(output, time.minute(), padding)
|
||||
}
|
||||
|
||||
/// Format the period into the designated output.
|
||||
|
@ -383,7 +432,7 @@ fn fmt_second(
|
|||
time: Time,
|
||||
modifier::Second { padding }: modifier::Second,
|
||||
) -> Result<usize, io::Error> {
|
||||
format_number::<_, _, 2>(output, time.second(), padding)
|
||||
format_number::<2, _, _>(output, time.second(), padding)
|
||||
}
|
||||
|
||||
/// Format the subsecond into the designated output.
|
||||
|
@ -396,23 +445,23 @@ fn fmt_subsecond<W: io::Write>(
|
|||
let nanos = time.nanosecond();
|
||||
|
||||
if digits == Nine || (digits == OneOrMore && nanos % 10 != 0) {
|
||||
format_number_pad_zero::<_, _, 9>(output, nanos)
|
||||
format_number_pad_zero::<9, _, _>(output, nanos)
|
||||
} else if digits == Eight || (digits == OneOrMore && (nanos / 10) % 10 != 0) {
|
||||
format_number_pad_zero::<_, _, 8>(output, nanos / 10)
|
||||
format_number_pad_zero::<8, _, _>(output, nanos / 10)
|
||||
} else if digits == Seven || (digits == OneOrMore && (nanos / 100) % 10 != 0) {
|
||||
format_number_pad_zero::<_, _, 7>(output, nanos / 100)
|
||||
format_number_pad_zero::<7, _, _>(output, nanos / 100)
|
||||
} else if digits == Six || (digits == OneOrMore && (nanos / 1_000) % 10 != 0) {
|
||||
format_number_pad_zero::<_, _, 6>(output, nanos / 1_000)
|
||||
format_number_pad_zero::<6, _, _>(output, nanos / 1_000)
|
||||
} else if digits == Five || (digits == OneOrMore && (nanos / 10_000) % 10 != 0) {
|
||||
format_number_pad_zero::<_, _, 5>(output, nanos / 10_000)
|
||||
format_number_pad_zero::<5, _, _>(output, nanos / 10_000)
|
||||
} else if digits == Four || (digits == OneOrMore && (nanos / 100_000) % 10 != 0) {
|
||||
format_number_pad_zero::<_, _, 4>(output, nanos / 100_000)
|
||||
format_number_pad_zero::<4, _, _>(output, nanos / 100_000)
|
||||
} else if digits == Three || (digits == OneOrMore && (nanos / 1_000_000) % 10 != 0) {
|
||||
format_number_pad_zero::<_, _, 3>(output, nanos / 1_000_000)
|
||||
format_number_pad_zero::<3, _, _>(output, nanos / 1_000_000)
|
||||
} else if digits == Two || (digits == OneOrMore && (nanos / 10_000_000) % 10 != 0) {
|
||||
format_number_pad_zero::<_, _, 2>(output, nanos / 10_000_000)
|
||||
format_number_pad_zero::<2, _, _>(output, nanos / 10_000_000)
|
||||
} else {
|
||||
format_number_pad_zero::<_, _, 1>(output, nanos / 100_000_000)
|
||||
format_number_pad_zero::<1, _, _>(output, nanos / 100_000_000)
|
||||
}
|
||||
}
|
||||
// endregion time formatters
|
||||
|
@ -429,11 +478,11 @@ fn fmt_offset_hour(
|
|||
) -> Result<usize, io::Error> {
|
||||
let mut bytes = 0;
|
||||
if offset.is_negative() {
|
||||
bytes += write(output, &[b'-'])?;
|
||||
bytes += write(output, b"-")?;
|
||||
} else if sign_is_mandatory {
|
||||
bytes += write(output, &[b'+'])?;
|
||||
bytes += write(output, b"+")?;
|
||||
}
|
||||
bytes += format_number::<_, _, 2>(output, offset.whole_hours().unsigned_abs(), padding)?;
|
||||
bytes += format_number::<2, _, _>(output, offset.whole_hours().unsigned_abs(), padding)?;
|
||||
Ok(bytes)
|
||||
}
|
||||
|
||||
|
@ -443,7 +492,7 @@ fn fmt_offset_minute(
|
|||
offset: UtcOffset,
|
||||
modifier::OffsetMinute { padding }: modifier::OffsetMinute,
|
||||
) -> Result<usize, io::Error> {
|
||||
format_number::<_, _, 2>(output, offset.minutes_past_hour().unsigned_abs(), padding)
|
||||
format_number::<2, _, _>(output, offset.minutes_past_hour().unsigned_abs(), padding)
|
||||
}
|
||||
|
||||
/// Format the offset second into the designated output.
|
||||
|
@ -452,6 +501,6 @@ fn fmt_offset_second(
|
|||
offset: UtcOffset,
|
||||
modifier::OffsetSecond { padding }: modifier::OffsetSecond,
|
||||
) -> Result<usize, io::Error> {
|
||||
format_number::<_, _, 2>(output, offset.seconds_past_minute().unsigned_abs(), padding)
|
||||
format_number::<2, _, _>(output, offset.seconds_past_minute().unsigned_abs(), padding)
|
||||
}
|
||||
// endregion offset formatters
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
//! The [`Instant`] struct and its associated `impl`s.
|
||||
|
||||
use core::borrow::Borrow;
|
||||
use core::cmp::{Ord, Ordering, PartialEq, PartialOrd};
|
||||
use core::convert::{TryFrom, TryInto};
|
||||
use core::ops::{Add, Sub};
|
||||
use core::time::Duration as StdDuration;
|
||||
use std::borrow::Borrow;
|
||||
use std::time::Instant as StdInstant;
|
||||
|
||||
use crate::Duration;
|
||||
|
@ -26,7 +25,6 @@ use crate::Duration;
|
|||
///
|
||||
/// This implementation allows for operations with signed [`Duration`]s, but is otherwise identical
|
||||
/// to [`std::time::Instant`].
|
||||
#[cfg_attr(__time_03_docs, doc(cfg(feature = "std")))]
|
||||
#[repr(transparent)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct Instant(pub StdInstant);
|
||||
|
@ -73,10 +71,10 @@ impl Instant {
|
|||
if duration.is_zero() {
|
||||
Some(self)
|
||||
} else if duration.is_positive() {
|
||||
self.0.checked_add(duration.abs_std()).map(Self)
|
||||
self.0.checked_add(duration.unsigned_abs()).map(Self)
|
||||
} else {
|
||||
debug_assert!(duration.is_negative());
|
||||
self.0.checked_sub(duration.abs_std()).map(Self)
|
||||
self.0.checked_sub(duration.unsigned_abs()).map(Self)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -94,10 +92,10 @@ impl Instant {
|
|||
if duration.is_zero() {
|
||||
Some(self)
|
||||
} else if duration.is_positive() {
|
||||
self.0.checked_sub(duration.abs_std()).map(Self)
|
||||
self.0.checked_sub(duration.unsigned_abs()).map(Self)
|
||||
} else {
|
||||
debug_assert!(duration.is_negative());
|
||||
self.0.checked_add(duration.abs_std()).map(Self)
|
||||
self.0.checked_add(duration.unsigned_abs()).map(Self)
|
||||
}
|
||||
}
|
||||
// endregion checked arithmetic
|
||||
|
@ -163,10 +161,11 @@ impl Add<Duration> for Instant {
|
|||
|
||||
fn add(self, duration: Duration) -> Self::Output {
|
||||
if duration.is_positive() {
|
||||
Self(self.0 + duration.abs_std())
|
||||
Self(self.0 + duration.unsigned_abs())
|
||||
} else if duration.is_negative() {
|
||||
Self(self.0 - duration.abs_std())
|
||||
Self(self.0 - duration.unsigned_abs())
|
||||
} else {
|
||||
debug_assert!(duration.is_zero());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
@ -196,10 +195,11 @@ impl Sub<Duration> for Instant {
|
|||
|
||||
fn sub(self, duration: Duration) -> Self::Output {
|
||||
if duration.is_positive() {
|
||||
Self(self.0 - duration.abs_std())
|
||||
Self(self.0 - duration.unsigned_abs())
|
||||
} else if duration.is_negative() {
|
||||
Self(self.0 + duration.abs_std())
|
||||
Self(self.0 + duration.unsigned_abs())
|
||||
} else {
|
||||
debug_assert!(duration.is_zero());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,10 +39,6 @@
|
|||
//! Note that enabling this feature has some costs, as it means forgoing some optimizations.
|
||||
//! Ambiguities may be introduced when parsing that would not otherwise exist.
|
||||
//!
|
||||
//! If you are using this feature, **please leave a comment**
|
||||
//! [on this discussion](https://github.com/time-rs/time/discussions/306) with your use case. If
|
||||
//! there is not sufficient demand for this feature, it will be dropped in a future release.
|
||||
//!
|
||||
//! - `serde`
|
||||
//!
|
||||
//! Enables [serde](https://docs.rs/serde) support for all types except [`Instant`].
|
||||
|
@ -56,7 +52,10 @@
|
|||
//! Libraries should never enable this feature, as the decision of what format to use should be up
|
||||
//! to the user.
|
||||
//!
|
||||
//! - `serde-well-known` (_implicitly enables `serde/alloc`, `formatting`, and `parsing`_)
|
||||
//! - `serde-well-known` (_implicitly enables `serde-human-readable`_)
|
||||
//!
|
||||
//! _This feature flag is deprecated and will be removed in a future breaking release. Use the
|
||||
//! `serde-human-readable` feature instead._
|
||||
//!
|
||||
//! Enables support for serializing and deserializing well-known formats using serde's
|
||||
//! [`#[with]` attribute](https://serde.rs/field-attrs.html#with).
|
||||
|
@ -69,25 +68,30 @@
|
|||
//!
|
||||
//! Enables [quickcheck](https://docs.rs/quickcheck) support for all types except [`Instant`].
|
||||
//!
|
||||
//! One pseudo-feature flag that is only available to end users is the `unsound_local_offset` cfg.
|
||||
//! As the name indicates, using the feature is unsound, and [may cause unexpected segmentation
|
||||
//! faults](https://github.com/time-rs/time/issues/293). Unlike other flags, this is deliberately
|
||||
//! only available to end users; this is to ensure that a user doesn't have unsound behavior without
|
||||
//! knowing it. To enable this behavior, you must use `RUSTFLAGS="--cfg unsound_local_offset" cargo
|
||||
//! build` or similar. Note: This flag is _not tested anywhere_, including in the regular test of
|
||||
//! the powerset of all feature flags. Use at your own risk. Without this flag, any method that
|
||||
//! requires the local offset will return the `Err` variant.
|
||||
//! - `wasm-bindgen`
|
||||
//!
|
||||
//! Enables [wasm-bindgen](https://github.com/rustwasm/wasm-bindgen) support for converting
|
||||
//! [JavaScript dates](https://rustwasm.github.io/wasm-bindgen/api/js_sys/struct.Date.html), as
|
||||
//! well as obtaining the UTC offset from JavaScript.
|
||||
//!
|
||||
//! <small>
|
||||
//! One feature only available to end users is the <code>unsound_local_offset</code> cfg. This
|
||||
//! enables obtaining the system's UTC offset even when it is unsound. To enable this, use the
|
||||
//! <code>RUSTFLAGS</code> environment variable. This is untested and officially unsupported. Do not
|
||||
//! use this unless you understand the risk.
|
||||
//! </small>
|
||||
|
||||
#![doc(html_playground_url = "https://play.rust-lang.org")]
|
||||
#![cfg_attr(__time_03_docs, feature(doc_cfg, doc_auto_cfg, doc_notable_trait))]
|
||||
#![cfg_attr(
|
||||
__time_03_docs,
|
||||
deny(rustdoc::broken_intra_doc_links, rustdoc::private_intra_doc_links)
|
||||
)]
|
||||
#![cfg_attr(__time_03_docs, feature(doc_auto_cfg, doc_notable_trait))]
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
#![deny(
|
||||
anonymous_parameters,
|
||||
clippy::all,
|
||||
clippy::alloc_instead_of_core,
|
||||
clippy::explicit_auto_deref,
|
||||
clippy::obfuscated_if_else,
|
||||
clippy::std_instead_of_core,
|
||||
clippy::undocumented_unsafe_blocks,
|
||||
const_err,
|
||||
illegal_floating_point_literal_pattern,
|
||||
late_bound_lifetime_arguments,
|
||||
|
@ -97,9 +101,10 @@
|
|||
trivial_casts,
|
||||
trivial_numeric_casts,
|
||||
unreachable_pub,
|
||||
unsafe_code,
|
||||
unsafe_op_in_unsafe_fn,
|
||||
unused_extern_crates
|
||||
unused_extern_crates,
|
||||
rustdoc::broken_intra_doc_links,
|
||||
rustdoc::private_intra_doc_links
|
||||
)]
|
||||
#![warn(
|
||||
clippy::dbg_macro,
|
||||
|
@ -120,7 +125,12 @@
|
|||
unused_qualifications,
|
||||
variant_size_differences
|
||||
)]
|
||||
#![allow(clippy::redundant_pub_crate)]
|
||||
#![allow(
|
||||
clippy::redundant_pub_crate, // suggests bad style
|
||||
clippy::option_if_let_else, // suggests terrible code
|
||||
clippy::unused_peekable, // temporary due to bug: remove when Rust 1.66 is released
|
||||
clippy::std_instead_of_core, // temporary due to bug: remove when Rust 1.66 is released
|
||||
)]
|
||||
#![doc(html_favicon_url = "https://avatars0.githubusercontent.com/u/55999857")]
|
||||
#![doc(html_logo_url = "https://avatars0.githubusercontent.com/u/55999857")]
|
||||
#![doc(test(attr(deny(warnings))))]
|
||||
|
@ -210,12 +220,12 @@ macro_rules! cascade {
|
|||
cascade!(@ordinal $ordinal);
|
||||
cascade!(@year $year);
|
||||
#[allow(unused_assignments)]
|
||||
if $ordinal > crate::util::days_in_year($year) {
|
||||
if $ordinal > crate::util::days_in_year($year) as i16 {
|
||||
$ordinal -= crate::util::days_in_year($year) as i16;
|
||||
$year += 1;
|
||||
$ordinal = 1;
|
||||
} else if $ordinal == 0 {
|
||||
} else if $ordinal < 1 {
|
||||
$year -= 1;
|
||||
$ordinal = crate::util::days_in_year($year);
|
||||
$ordinal += crate::util::days_in_year($year) as i16;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -277,6 +287,18 @@ macro_rules! const_try_opt {
|
|||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Try to unwrap an expression, panicking if not possible.
|
||||
///
|
||||
/// This is similar to `$e.expect($message)`, but is usable in `const` contexts.
|
||||
macro_rules! expect_opt {
|
||||
($e:expr, $message:literal) => {
|
||||
match $e {
|
||||
Some(value) => value,
|
||||
None => crate::expect_failed($message),
|
||||
}
|
||||
};
|
||||
}
|
||||
// endregion macros
|
||||
|
||||
mod date;
|
||||
|
@ -297,13 +319,10 @@ mod offset_date_time;
|
|||
pub mod parsing;
|
||||
mod primitive_date_time;
|
||||
#[cfg(feature = "quickcheck")]
|
||||
#[cfg_attr(__time_03_docs, doc(cfg(feature = "quickcheck")))]
|
||||
mod quickcheck;
|
||||
#[cfg(feature = "rand")]
|
||||
#[cfg_attr(__time_03_docs, doc(cfg(feature = "rand")))]
|
||||
mod rand;
|
||||
#[cfg(feature = "serde")]
|
||||
#[cfg_attr(__time_03_docs, doc(cfg(feature = "serde")))]
|
||||
#[allow(missing_copy_implementations, missing_debug_implementations)]
|
||||
pub mod serde;
|
||||
mod sys;
|
||||
|
@ -328,3 +347,11 @@ pub use crate::weekday::Weekday;
|
|||
|
||||
/// An alias for [`std::result::Result`] with a generic error from the time crate.
|
||||
pub type Result<T> = core::result::Result<T, Error>;
|
||||
|
||||
/// This is a separate function to reduce the code size of `expect_opt!`.
|
||||
#[inline(never)]
|
||||
#[cold]
|
||||
#[track_caller]
|
||||
const fn expect_failed(message: &str) -> ! {
|
||||
panic!("{}", message)
|
||||
}
|
||||
|
|
|
@ -30,6 +30,24 @@ pub use time_macros::date;
|
|||
///
|
||||
/// [`OffsetDateTime`]: crate::OffsetDateTime
|
||||
/// [`PrimitiveDateTime`]: crate::PrimitiveDateTime
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::{Date, Month, macros::datetime, UtcOffset};
|
||||
/// assert_eq!(
|
||||
/// datetime!(2020-01-01 0:00),
|
||||
/// Date::from_calendar_date(2020, Month::January, 1)?.midnight()
|
||||
/// );
|
||||
/// assert_eq!(
|
||||
/// datetime!(2020-01-01 0:00 UTC),
|
||||
/// Date::from_calendar_date(2020, Month::January, 1)?.midnight().assume_utc()
|
||||
/// );
|
||||
/// assert_eq!(
|
||||
/// datetime!(2020-01-01 0:00 -1),
|
||||
/// Date::from_calendar_date(2020, Month::January, 1)?.midnight()
|
||||
/// .assume_offset(UtcOffset::from_hms(-1, 0, 0)?)
|
||||
/// );
|
||||
/// # Ok::<_, time::Error>(())
|
||||
/// ```
|
||||
pub use time_macros::datetime;
|
||||
/// Equivalent of performing [`format_description::parse()`] at compile time.
|
||||
///
|
||||
|
@ -38,8 +56,8 @@ pub use time_macros::datetime;
|
|||
///
|
||||
/// The resulting expression can be used in `const` or `static` declarations, and implements
|
||||
/// the sealed traits required for both formatting and parsing.
|
||||
///
|
||||
/// ```rust
|
||||
#[cfg_attr(feature = "alloc", doc = "```rust")]
|
||||
#[cfg_attr(not(feature = "alloc"), doc = "```rust,ignore")]
|
||||
/// # use time::{format_description, macros::format_description};
|
||||
/// assert_eq!(
|
||||
/// format_description!("[hour]:[minute]:[second]"),
|
||||
|
@ -47,7 +65,7 @@ pub use time_macros::datetime;
|
|||
/// );
|
||||
/// # Ok::<_, time::Error>(())
|
||||
/// ```
|
||||
///
|
||||
///
|
||||
/// The syntax accepted by this macro is the same as [`format_description::parse()`], which can
|
||||
/// be found in [the book](https://time-rs.github.io/book/api/format-description.html).
|
||||
///
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
//! The `Month` enum and its associated `impl`s.
|
||||
|
||||
use core::convert::TryFrom;
|
||||
use core::fmt;
|
||||
use core::num::NonZeroU8;
|
||||
use core::str::FromStr;
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -66,10 +66,10 @@ pub(crate) fn one_or_more<'a, P: Fn(&'a [u8]) -> Option<ParsedItem<'a, ()>>>(
|
|||
/// Consume between `n` and `m` instances of the provided parser.
|
||||
pub(crate) fn n_to_m<
|
||||
'a,
|
||||
T,
|
||||
P: Fn(&'a [u8]) -> Option<ParsedItem<'a, T>>,
|
||||
const N: u8,
|
||||
const M: u8,
|
||||
T,
|
||||
P: Fn(&'a [u8]) -> Option<ParsedItem<'a, T>>,
|
||||
>(
|
||||
parser: P,
|
||||
) -> impl Fn(&'a [u8]) -> Option<ParsedItem<'a, &'a [u8]>> {
|
||||
|
@ -99,32 +99,32 @@ pub(crate) fn n_to_m<
|
|||
}
|
||||
|
||||
/// Consume between `n` and `m` digits, returning the numerical value.
|
||||
pub(crate) fn n_to_m_digits<T: Integer, const N: u8, const M: u8>(
|
||||
pub(crate) fn n_to_m_digits<const N: u8, const M: u8, T: Integer>(
|
||||
input: &[u8],
|
||||
) -> Option<ParsedItem<'_, T>> {
|
||||
debug_assert!(M >= N);
|
||||
n_to_m::<_, _, N, M>(any_digit)(input)?.flat_map(|value| value.parse_bytes())
|
||||
n_to_m::<N, M, _, _>(any_digit)(input)?.flat_map(|value| value.parse_bytes())
|
||||
}
|
||||
|
||||
/// Consume exactly `n` digits, returning the numerical value.
|
||||
pub(crate) fn exactly_n_digits<T: Integer, const N: u8>(input: &[u8]) -> Option<ParsedItem<'_, T>> {
|
||||
n_to_m_digits::<_, N, N>(input)
|
||||
pub(crate) fn exactly_n_digits<const N: u8, T: Integer>(input: &[u8]) -> Option<ParsedItem<'_, T>> {
|
||||
n_to_m_digits::<N, N, _>(input)
|
||||
}
|
||||
|
||||
/// Consume exactly `n` digits, returning the numerical value.
|
||||
pub(crate) fn exactly_n_digits_padded<'a, T: Integer, const N: u8>(
|
||||
pub(crate) fn exactly_n_digits_padded<'a, const N: u8, T: Integer>(
|
||||
padding: Padding,
|
||||
) -> impl Fn(&'a [u8]) -> Option<ParsedItem<'a, T>> {
|
||||
n_to_m_digits_padded::<_, N, N>(padding)
|
||||
n_to_m_digits_padded::<N, N, _>(padding)
|
||||
}
|
||||
|
||||
/// Consume between `n` and `m` digits, returning the numerical value.
|
||||
pub(crate) fn n_to_m_digits_padded<'a, T: Integer, const N: u8, const M: u8>(
|
||||
pub(crate) fn n_to_m_digits_padded<'a, const N: u8, const M: u8, T: Integer>(
|
||||
padding: Padding,
|
||||
) -> impl Fn(&'a [u8]) -> Option<ParsedItem<'a, T>> {
|
||||
debug_assert!(M >= N);
|
||||
move |mut input| match padding {
|
||||
Padding::None => n_to_m_digits::<_, 1, M>(input),
|
||||
Padding::None => n_to_m_digits::<1, M, _>(input),
|
||||
Padding::Space => {
|
||||
debug_assert!(N > 0);
|
||||
|
||||
|
@ -151,7 +151,7 @@ pub(crate) fn n_to_m_digits_padded<'a, T: Integer, const N: u8, const M: u8>(
|
|||
ParsedItem(input, &orig_input[..(orig_input.len() - input.len())])
|
||||
.flat_map(|value| value.parse_bytes())
|
||||
}
|
||||
Padding::Zero => n_to_m_digits::<_, N, M>(input),
|
||||
Padding::Zero => n_to_m_digits::<N, M, _>(input),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,173 @@
|
|||
//! Rules defined in [ISO 8601].
|
||||
//!
|
||||
//! [ISO 8601]: https://www.iso.org/iso-8601-date-and-time-format.html
|
||||
|
||||
use core::num::{NonZeroU16, NonZeroU8};
|
||||
|
||||
use crate::parsing::combinator::{any_digit, ascii_char, exactly_n_digits, first_match, sign};
|
||||
use crate::parsing::ParsedItem;
|
||||
use crate::{Month, Weekday};
|
||||
|
||||
/// What kind of format is being parsed. This is used to ensure each part of the format (date, time,
|
||||
/// offset) is the same kind.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(crate) enum ExtendedKind {
|
||||
/// The basic format.
|
||||
Basic,
|
||||
/// The extended format.
|
||||
Extended,
|
||||
/// ¯\_(ツ)_/¯
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl ExtendedKind {
|
||||
/// Is it possible that the format is extended?
|
||||
pub(crate) const fn maybe_extended(self) -> bool {
|
||||
matches!(self, Self::Extended | Self::Unknown)
|
||||
}
|
||||
|
||||
/// Is the format known for certain to be extended?
|
||||
pub(crate) const fn is_extended(self) -> bool {
|
||||
matches!(self, Self::Extended)
|
||||
}
|
||||
|
||||
/// If the kind is `Unknown`, make it `Basic`. Otherwise, do nothing. Returns `Some` if and only
|
||||
/// if the kind is now `Basic`.
|
||||
pub(crate) fn coerce_basic(&mut self) -> Option<()> {
|
||||
match self {
|
||||
Self::Basic => Some(()),
|
||||
Self::Extended => None,
|
||||
Self::Unknown => {
|
||||
*self = Self::Basic;
|
||||
Some(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// If the kind is `Unknown`, make it `Extended`. Otherwise, do nothing. Returns `Some` if and
|
||||
/// only if the kind is now `Extended`.
|
||||
pub(crate) fn coerce_extended(&mut self) -> Option<()> {
|
||||
match self {
|
||||
Self::Basic => None,
|
||||
Self::Extended => Some(()),
|
||||
Self::Unknown => {
|
||||
*self = Self::Extended;
|
||||
Some(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a possibly expanded year.
|
||||
pub(crate) fn year(input: &[u8]) -> Option<ParsedItem<'_, i32>> {
|
||||
Some(match sign(input) {
|
||||
Some(ParsedItem(input, sign)) => exactly_n_digits::<6, u32>(input)?.map(|val| {
|
||||
let val = val as i32;
|
||||
if sign == b'-' { -val } else { val }
|
||||
}),
|
||||
None => exactly_n_digits::<4, u32>(input)?.map(|val| val as _),
|
||||
})
|
||||
}
|
||||
|
||||
/// Parse a month.
|
||||
pub(crate) fn month(input: &[u8]) -> Option<ParsedItem<'_, Month>> {
|
||||
first_match(
|
||||
[
|
||||
(b"01".as_slice(), Month::January),
|
||||
(b"02".as_slice(), Month::February),
|
||||
(b"03".as_slice(), Month::March),
|
||||
(b"04".as_slice(), Month::April),
|
||||
(b"05".as_slice(), Month::May),
|
||||
(b"06".as_slice(), Month::June),
|
||||
(b"07".as_slice(), Month::July),
|
||||
(b"08".as_slice(), Month::August),
|
||||
(b"09".as_slice(), Month::September),
|
||||
(b"10".as_slice(), Month::October),
|
||||
(b"11".as_slice(), Month::November),
|
||||
(b"12".as_slice(), Month::December),
|
||||
],
|
||||
true,
|
||||
)(input)
|
||||
}
|
||||
|
||||
/// Parse a week number.
|
||||
pub(crate) fn week(input: &[u8]) -> Option<ParsedItem<'_, NonZeroU8>> {
|
||||
exactly_n_digits::<2, _>(input)
|
||||
}
|
||||
|
||||
/// Parse a day of the month.
|
||||
pub(crate) fn day(input: &[u8]) -> Option<ParsedItem<'_, NonZeroU8>> {
|
||||
exactly_n_digits::<2, _>(input)
|
||||
}
|
||||
|
||||
/// Parse a day of the week.
|
||||
pub(crate) fn dayk(input: &[u8]) -> Option<ParsedItem<'_, Weekday>> {
|
||||
first_match(
|
||||
[
|
||||
(b"1".as_slice(), Weekday::Monday),
|
||||
(b"2".as_slice(), Weekday::Tuesday),
|
||||
(b"3".as_slice(), Weekday::Wednesday),
|
||||
(b"4".as_slice(), Weekday::Thursday),
|
||||
(b"5".as_slice(), Weekday::Friday),
|
||||
(b"6".as_slice(), Weekday::Saturday),
|
||||
(b"7".as_slice(), Weekday::Sunday),
|
||||
],
|
||||
true,
|
||||
)(input)
|
||||
}
|
||||
|
||||
/// Parse a day of the year.
|
||||
pub(crate) fn dayo(input: &[u8]) -> Option<ParsedItem<'_, NonZeroU16>> {
|
||||
exactly_n_digits::<3, _>(input)
|
||||
}
|
||||
|
||||
/// Parse the hour.
|
||||
pub(crate) fn hour(input: &[u8]) -> Option<ParsedItem<'_, u8>> {
|
||||
exactly_n_digits::<2, _>(input)
|
||||
}
|
||||
|
||||
/// Parse the minute.
|
||||
pub(crate) fn min(input: &[u8]) -> Option<ParsedItem<'_, u8>> {
|
||||
exactly_n_digits::<2, _>(input)
|
||||
}
|
||||
|
||||
/// Parse a floating point number as its integer and optional fractional parts.
|
||||
///
|
||||
/// The number must have two digits before the decimal point. If a decimal point is present, at
|
||||
/// least one digit must follow.
|
||||
///
|
||||
/// The return type is a tuple of the integer part and optional fraction part.
|
||||
pub(crate) fn float(input: &[u8]) -> Option<ParsedItem<'_, (u8, Option<f64>)>> {
|
||||
// Two digits before the decimal.
|
||||
let ParsedItem(input, integer_part) = match input {
|
||||
[
|
||||
first_digit @ b'0'..=b'9',
|
||||
second_digit @ b'0'..=b'9',
|
||||
input @ ..,
|
||||
] => ParsedItem(input, (first_digit - b'0') * 10 + (second_digit - b'0')),
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
if let Some(ParsedItem(input, ())) = decimal_sign(input) {
|
||||
// Mandatory post-decimal digit.
|
||||
let ParsedItem(mut input, mut fractional_part) =
|
||||
any_digit(input)?.map(|digit| ((digit - b'0') as f64) / 10.);
|
||||
|
||||
let mut divisor = 10.;
|
||||
// Any number of subsequent digits.
|
||||
while let Some(ParsedItem(new_input, digit)) = any_digit(input) {
|
||||
input = new_input;
|
||||
divisor *= 10.;
|
||||
fractional_part += (digit - b'0') as f64 / divisor;
|
||||
}
|
||||
|
||||
Some(ParsedItem(input, (integer_part, Some(fractional_part))))
|
||||
} else {
|
||||
Some(ParsedItem(input, (integer_part, None)))
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a "decimal sign", which is either a comma or a period.
|
||||
fn decimal_sign(input: &[u8]) -> Option<ParsedItem<'_, ()>> {
|
||||
ascii_char::<b'.'>(input).or_else(|| ascii_char::<b','>(input))
|
||||
}
|
|
@ -1,8 +1,10 @@
|
|||
//! Combinators for rules as defined in an RFC.
|
||||
//! Combinators for rules as defined in a standard.
|
||||
//!
|
||||
//! These rules have been converted strictly following the ABNF syntax as specified in [RFC 2234].
|
||||
//! When applicable, these rules have been converted strictly following the ABNF syntax as specified
|
||||
//! in [RFC 2234].
|
||||
//!
|
||||
//! [RFC 2234]: https://datatracker.ietf.org/doc/html/rfc2234
|
||||
|
||||
pub(crate) mod iso8601;
|
||||
pub(crate) mod rfc2234;
|
||||
pub(crate) mod rfc2822;
|
||||
|
|
|
@ -19,10 +19,10 @@ pub(crate) fn parse_year(input: &[u8], modifiers: modifier::Year) -> Option<Pars
|
|||
let ParsedItem(input, sign) = opt(sign)(input);
|
||||
#[cfg(not(feature = "large-dates"))]
|
||||
let ParsedItem(input, year) =
|
||||
exactly_n_digits_padded::<u32, 4>(modifiers.padding)(input)?;
|
||||
exactly_n_digits_padded::<4, u32>(modifiers.padding)(input)?;
|
||||
#[cfg(feature = "large-dates")]
|
||||
let ParsedItem(input, year) =
|
||||
n_to_m_digits_padded::<u32, 4, 6>(modifiers.padding)(input)?;
|
||||
n_to_m_digits_padded::<4, 6, u32>(modifiers.padding)(input)?;
|
||||
match sign {
|
||||
Some(b'-') => Some(ParsedItem(input, -(year as i32))),
|
||||
None if modifiers.sign_is_mandatory || year >= 10_000 => None,
|
||||
|
@ -30,7 +30,7 @@ pub(crate) fn parse_year(input: &[u8], modifiers: modifier::Year) -> Option<Pars
|
|||
}
|
||||
}
|
||||
modifier::YearRepr::LastTwo => {
|
||||
Some(exactly_n_digits_padded::<u32, 2>(modifiers.padding)(input)?.map(|v| v as i32))
|
||||
Some(exactly_n_digits_padded::<2, u32>(modifiers.padding)(input)?.map(|v| v as i32))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -44,36 +44,36 @@ pub(crate) fn parse_month(
|
|||
let ParsedItem(remaining, value) = first_match(
|
||||
match modifiers.repr {
|
||||
modifier::MonthRepr::Numerical => {
|
||||
return exactly_n_digits_padded::<_, 2>(modifiers.padding)(input)?
|
||||
return exactly_n_digits_padded::<2, _>(modifiers.padding)(input)?
|
||||
.flat_map(|n| Month::from_number(n).ok());
|
||||
}
|
||||
modifier::MonthRepr::Long => [
|
||||
(&b"January"[..], January),
|
||||
(&b"February"[..], February),
|
||||
(&b"March"[..], March),
|
||||
(&b"April"[..], April),
|
||||
(&b"May"[..], May),
|
||||
(&b"June"[..], June),
|
||||
(&b"July"[..], July),
|
||||
(&b"August"[..], August),
|
||||
(&b"September"[..], September),
|
||||
(&b"October"[..], October),
|
||||
(&b"November"[..], November),
|
||||
(&b"December"[..], December),
|
||||
(b"January".as_slice(), January),
|
||||
(b"February".as_slice(), February),
|
||||
(b"March".as_slice(), March),
|
||||
(b"April".as_slice(), April),
|
||||
(b"May".as_slice(), May),
|
||||
(b"June".as_slice(), June),
|
||||
(b"July".as_slice(), July),
|
||||
(b"August".as_slice(), August),
|
||||
(b"September".as_slice(), September),
|
||||
(b"October".as_slice(), October),
|
||||
(b"November".as_slice(), November),
|
||||
(b"December".as_slice(), December),
|
||||
],
|
||||
modifier::MonthRepr::Short => [
|
||||
(&b"Jan"[..], January),
|
||||
(&b"Feb"[..], February),
|
||||
(&b"Mar"[..], March),
|
||||
(&b"Apr"[..], April),
|
||||
(&b"May"[..], May),
|
||||
(&b"Jun"[..], June),
|
||||
(&b"Jul"[..], July),
|
||||
(&b"Aug"[..], August),
|
||||
(&b"Sep"[..], September),
|
||||
(&b"Oct"[..], October),
|
||||
(&b"Nov"[..], November),
|
||||
(&b"Dec"[..], December),
|
||||
(b"Jan".as_slice(), January),
|
||||
(b"Feb".as_slice(), February),
|
||||
(b"Mar".as_slice(), March),
|
||||
(b"Apr".as_slice(), April),
|
||||
(b"May".as_slice(), May),
|
||||
(b"Jun".as_slice(), June),
|
||||
(b"Jul".as_slice(), July),
|
||||
(b"Aug".as_slice(), August),
|
||||
(b"Sep".as_slice(), September),
|
||||
(b"Oct".as_slice(), October),
|
||||
(b"Nov".as_slice(), November),
|
||||
(b"Dec".as_slice(), December),
|
||||
],
|
||||
},
|
||||
modifiers.case_sensitive,
|
||||
|
@ -86,7 +86,7 @@ pub(crate) fn parse_week_number(
|
|||
input: &[u8],
|
||||
modifiers: modifier::WeekNumber,
|
||||
) -> Option<ParsedItem<'_, u8>> {
|
||||
exactly_n_digits_padded::<_, 2>(modifiers.padding)(input)
|
||||
exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
|
||||
}
|
||||
|
||||
/// Parse the "weekday" component of a `Date`.
|
||||
|
@ -97,58 +97,58 @@ pub(crate) fn parse_weekday(
|
|||
first_match(
|
||||
match (modifiers.repr, modifiers.one_indexed) {
|
||||
(modifier::WeekdayRepr::Short, _) => [
|
||||
(&b"Mon"[..], Weekday::Monday),
|
||||
(&b"Tue"[..], Weekday::Tuesday),
|
||||
(&b"Wed"[..], Weekday::Wednesday),
|
||||
(&b"Thu"[..], Weekday::Thursday),
|
||||
(&b"Fri"[..], Weekday::Friday),
|
||||
(&b"Sat"[..], Weekday::Saturday),
|
||||
(&b"Sun"[..], Weekday::Sunday),
|
||||
(b"Mon".as_slice(), Weekday::Monday),
|
||||
(b"Tue".as_slice(), Weekday::Tuesday),
|
||||
(b"Wed".as_slice(), Weekday::Wednesday),
|
||||
(b"Thu".as_slice(), Weekday::Thursday),
|
||||
(b"Fri".as_slice(), Weekday::Friday),
|
||||
(b"Sat".as_slice(), Weekday::Saturday),
|
||||
(b"Sun".as_slice(), Weekday::Sunday),
|
||||
],
|
||||
(modifier::WeekdayRepr::Long, _) => [
|
||||
(&b"Monday"[..], Weekday::Monday),
|
||||
(&b"Tuesday"[..], Weekday::Tuesday),
|
||||
(&b"Wednesday"[..], Weekday::Wednesday),
|
||||
(&b"Thursday"[..], Weekday::Thursday),
|
||||
(&b"Friday"[..], Weekday::Friday),
|
||||
(&b"Saturday"[..], Weekday::Saturday),
|
||||
(&b"Sunday"[..], Weekday::Sunday),
|
||||
(b"Monday".as_slice(), Weekday::Monday),
|
||||
(b"Tuesday".as_slice(), Weekday::Tuesday),
|
||||
(b"Wednesday".as_slice(), Weekday::Wednesday),
|
||||
(b"Thursday".as_slice(), Weekday::Thursday),
|
||||
(b"Friday".as_slice(), Weekday::Friday),
|
||||
(b"Saturday".as_slice(), Weekday::Saturday),
|
||||
(b"Sunday".as_slice(), Weekday::Sunday),
|
||||
],
|
||||
(modifier::WeekdayRepr::Sunday, false) => [
|
||||
(&b"1"[..], Weekday::Monday),
|
||||
(&b"2"[..], Weekday::Tuesday),
|
||||
(&b"3"[..], Weekday::Wednesday),
|
||||
(&b"4"[..], Weekday::Thursday),
|
||||
(&b"5"[..], Weekday::Friday),
|
||||
(&b"6"[..], Weekday::Saturday),
|
||||
(&b"0"[..], Weekday::Sunday),
|
||||
(b"1".as_slice(), Weekday::Monday),
|
||||
(b"2".as_slice(), Weekday::Tuesday),
|
||||
(b"3".as_slice(), Weekday::Wednesday),
|
||||
(b"4".as_slice(), Weekday::Thursday),
|
||||
(b"5".as_slice(), Weekday::Friday),
|
||||
(b"6".as_slice(), Weekday::Saturday),
|
||||
(b"0".as_slice(), Weekday::Sunday),
|
||||
],
|
||||
(modifier::WeekdayRepr::Sunday, true) => [
|
||||
(&b"2"[..], Weekday::Monday),
|
||||
(&b"3"[..], Weekday::Tuesday),
|
||||
(&b"4"[..], Weekday::Wednesday),
|
||||
(&b"5"[..], Weekday::Thursday),
|
||||
(&b"6"[..], Weekday::Friday),
|
||||
(&b"7"[..], Weekday::Saturday),
|
||||
(&b"1"[..], Weekday::Sunday),
|
||||
(b"2".as_slice(), Weekday::Monday),
|
||||
(b"3".as_slice(), Weekday::Tuesday),
|
||||
(b"4".as_slice(), Weekday::Wednesday),
|
||||
(b"5".as_slice(), Weekday::Thursday),
|
||||
(b"6".as_slice(), Weekday::Friday),
|
||||
(b"7".as_slice(), Weekday::Saturday),
|
||||
(b"1".as_slice(), Weekday::Sunday),
|
||||
],
|
||||
(modifier::WeekdayRepr::Monday, false) => [
|
||||
(&b"0"[..], Weekday::Monday),
|
||||
(&b"1"[..], Weekday::Tuesday),
|
||||
(&b"2"[..], Weekday::Wednesday),
|
||||
(&b"3"[..], Weekday::Thursday),
|
||||
(&b"4"[..], Weekday::Friday),
|
||||
(&b"5"[..], Weekday::Saturday),
|
||||
(&b"6"[..], Weekday::Sunday),
|
||||
(b"0".as_slice(), Weekday::Monday),
|
||||
(b"1".as_slice(), Weekday::Tuesday),
|
||||
(b"2".as_slice(), Weekday::Wednesday),
|
||||
(b"3".as_slice(), Weekday::Thursday),
|
||||
(b"4".as_slice(), Weekday::Friday),
|
||||
(b"5".as_slice(), Weekday::Saturday),
|
||||
(b"6".as_slice(), Weekday::Sunday),
|
||||
],
|
||||
(modifier::WeekdayRepr::Monday, true) => [
|
||||
(&b"1"[..], Weekday::Monday),
|
||||
(&b"2"[..], Weekday::Tuesday),
|
||||
(&b"3"[..], Weekday::Wednesday),
|
||||
(&b"4"[..], Weekday::Thursday),
|
||||
(&b"5"[..], Weekday::Friday),
|
||||
(&b"6"[..], Weekday::Saturday),
|
||||
(&b"7"[..], Weekday::Sunday),
|
||||
(b"1".as_slice(), Weekday::Monday),
|
||||
(b"2".as_slice(), Weekday::Tuesday),
|
||||
(b"3".as_slice(), Weekday::Wednesday),
|
||||
(b"4".as_slice(), Weekday::Thursday),
|
||||
(b"5".as_slice(), Weekday::Friday),
|
||||
(b"6".as_slice(), Weekday::Saturday),
|
||||
(b"7".as_slice(), Weekday::Sunday),
|
||||
],
|
||||
},
|
||||
modifiers.case_sensitive,
|
||||
|
@ -160,7 +160,7 @@ pub(crate) fn parse_ordinal(
|
|||
input: &[u8],
|
||||
modifiers: modifier::Ordinal,
|
||||
) -> Option<ParsedItem<'_, NonZeroU16>> {
|
||||
exactly_n_digits_padded::<_, 3>(modifiers.padding)(input)
|
||||
exactly_n_digits_padded::<3, _>(modifiers.padding)(input)
|
||||
}
|
||||
|
||||
/// Parse the "day" component of a `Date`.
|
||||
|
@ -168,7 +168,7 @@ pub(crate) fn parse_day(
|
|||
input: &[u8],
|
||||
modifiers: modifier::Day,
|
||||
) -> Option<ParsedItem<'_, NonZeroU8>> {
|
||||
exactly_n_digits_padded::<_, 2>(modifiers.padding)(input)
|
||||
exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
|
||||
}
|
||||
// endregion date components
|
||||
|
||||
|
@ -184,7 +184,7 @@ pub(crate) enum Period {
|
|||
|
||||
/// Parse the "hour" component of a `Time`.
|
||||
pub(crate) fn parse_hour(input: &[u8], modifiers: modifier::Hour) -> Option<ParsedItem<'_, u8>> {
|
||||
exactly_n_digits_padded::<_, 2>(modifiers.padding)(input)
|
||||
exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
|
||||
}
|
||||
|
||||
/// Parse the "minute" component of a `Time`.
|
||||
|
@ -192,7 +192,7 @@ pub(crate) fn parse_minute(
|
|||
input: &[u8],
|
||||
modifiers: modifier::Minute,
|
||||
) -> Option<ParsedItem<'_, u8>> {
|
||||
exactly_n_digits_padded::<_, 2>(modifiers.padding)(input)
|
||||
exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
|
||||
}
|
||||
|
||||
/// Parse the "second" component of a `Time`.
|
||||
|
@ -200,7 +200,7 @@ pub(crate) fn parse_second(
|
|||
input: &[u8],
|
||||
modifiers: modifier::Second,
|
||||
) -> Option<ParsedItem<'_, u8>> {
|
||||
exactly_n_digits_padded::<_, 2>(modifiers.padding)(input)
|
||||
exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
|
||||
}
|
||||
|
||||
/// Parse the "period" component of a `Time`. Required if the hour is on a 12-hour clock.
|
||||
|
@ -210,9 +210,15 @@ pub(crate) fn parse_period(
|
|||
) -> Option<ParsedItem<'_, Period>> {
|
||||
first_match(
|
||||
if modifiers.is_uppercase {
|
||||
[(&b"AM"[..], Period::Am), (&b"PM"[..], Period::Pm)]
|
||||
[
|
||||
(b"AM".as_slice(), Period::Am),
|
||||
(b"PM".as_slice(), Period::Pm),
|
||||
]
|
||||
} else {
|
||||
[(&b"am"[..], Period::Am), (&b"pm"[..], Period::Pm)]
|
||||
[
|
||||
(b"am".as_slice(), Period::Am),
|
||||
(b"pm".as_slice(), Period::Pm),
|
||||
]
|
||||
},
|
||||
modifiers.case_sensitive,
|
||||
)(input)
|
||||
|
@ -225,15 +231,15 @@ pub(crate) fn parse_subsecond(
|
|||
) -> Option<ParsedItem<'_, u32>> {
|
||||
use modifier::SubsecondDigits::*;
|
||||
Some(match modifiers.digits {
|
||||
One => exactly_n_digits::<u32, 1>(input)?.map(|v| v * 100_000_000),
|
||||
Two => exactly_n_digits::<u32, 2>(input)?.map(|v| v * 10_000_000),
|
||||
Three => exactly_n_digits::<u32, 3>(input)?.map(|v| v * 1_000_000),
|
||||
Four => exactly_n_digits::<u32, 4>(input)?.map(|v| v * 100_000),
|
||||
Five => exactly_n_digits::<u32, 5>(input)?.map(|v| v * 10_000),
|
||||
Six => exactly_n_digits::<u32, 6>(input)?.map(|v| v * 1_000),
|
||||
Seven => exactly_n_digits::<u32, 7>(input)?.map(|v| v * 100),
|
||||
Eight => exactly_n_digits::<u32, 8>(input)?.map(|v| v * 10),
|
||||
Nine => exactly_n_digits::<u32, 9>(input)?,
|
||||
One => exactly_n_digits::<1, u32>(input)?.map(|v| v * 100_000_000),
|
||||
Two => exactly_n_digits::<2, u32>(input)?.map(|v| v * 10_000_000),
|
||||
Three => exactly_n_digits::<3, u32>(input)?.map(|v| v * 1_000_000),
|
||||
Four => exactly_n_digits::<4, u32>(input)?.map(|v| v * 100_000),
|
||||
Five => exactly_n_digits::<5, u32>(input)?.map(|v| v * 10_000),
|
||||
Six => exactly_n_digits::<6, u32>(input)?.map(|v| v * 1_000),
|
||||
Seven => exactly_n_digits::<7, u32>(input)?.map(|v| v * 100),
|
||||
Eight => exactly_n_digits::<8, u32>(input)?.map(|v| v * 10),
|
||||
Nine => exactly_n_digits::<9, _>(input)?,
|
||||
OneOrMore => {
|
||||
let ParsedItem(mut input, mut value) =
|
||||
any_digit(input)?.map(|v| (v - b'0') as u32 * 100_000_000);
|
||||
|
@ -258,7 +264,7 @@ pub(crate) fn parse_offset_hour(
|
|||
modifiers: modifier::OffsetHour,
|
||||
) -> Option<ParsedItem<'_, i8>> {
|
||||
let ParsedItem(input, sign) = opt(sign)(input);
|
||||
let ParsedItem(input, hour) = exactly_n_digits_padded::<u8, 2>(modifiers.padding)(input)?;
|
||||
let ParsedItem(input, hour) = exactly_n_digits_padded::<2, u8>(modifiers.padding)(input)?;
|
||||
match sign {
|
||||
Some(b'-') => Some(ParsedItem(input, -(hour as i8))),
|
||||
None if modifiers.sign_is_mandatory => None,
|
||||
|
@ -272,7 +278,7 @@ pub(crate) fn parse_offset_minute(
|
|||
modifiers: modifier::OffsetMinute,
|
||||
) -> Option<ParsedItem<'_, i8>> {
|
||||
Some(
|
||||
exactly_n_digits_padded::<u8, 2>(modifiers.padding)(input)?
|
||||
exactly_n_digits_padded::<2, u8>(modifiers.padding)(input)?
|
||||
.map(|offset_minute| offset_minute as _),
|
||||
)
|
||||
}
|
||||
|
@ -283,7 +289,7 @@ pub(crate) fn parse_offset_second(
|
|||
modifiers: modifier::OffsetSecond,
|
||||
) -> Option<ParsedItem<'_, i8>> {
|
||||
Some(
|
||||
exactly_n_digits_padded::<u8, 2>(modifiers.padding)(input)?
|
||||
exactly_n_digits_padded::<2, u8>(modifiers.padding)(input)?
|
||||
.map(|offset_second| offset_second as _),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,308 @@
|
|||
//! Parse parts of an ISO 8601-formatted value.
|
||||
|
||||
use crate::error;
|
||||
use crate::error::ParseFromDescription::{InvalidComponent, InvalidLiteral};
|
||||
use crate::format_description::well_known::iso8601::EncodedConfig;
|
||||
use crate::format_description::well_known::Iso8601;
|
||||
use crate::parsing::combinator::rfc::iso8601::{
|
||||
day, dayk, dayo, float, hour, min, month, week, year, ExtendedKind,
|
||||
};
|
||||
use crate::parsing::combinator::{ascii_char, sign};
|
||||
use crate::parsing::{Parsed, ParsedItem};
|
||||
|
||||
impl<const CONFIG: EncodedConfig> Iso8601<CONFIG> {
|
||||
// Basic: [year][month][day]
|
||||
// Extended: [year]["-"][month]["-"][day]
|
||||
// Basic: [year][dayo]
|
||||
// Extended: [year]["-"][dayo]
|
||||
// Basic: [year]["W"][week][dayk]
|
||||
// Extended: [year]["-"]["W"][week]["-"][dayk]
|
||||
/// Parse a date in the basic or extended format. Reduced precision is permitted.
|
||||
pub(crate) fn parse_date<'a>(
|
||||
parsed: &'a mut Parsed,
|
||||
extended_kind: &'a mut ExtendedKind,
|
||||
) -> impl FnMut(&[u8]) -> Result<&[u8], error::Parse> + 'a {
|
||||
move |input| {
|
||||
// Same for any acceptable format.
|
||||
let ParsedItem(mut input, year) = year(input).ok_or(InvalidComponent("year"))?;
|
||||
*extended_kind = match ascii_char::<b'-'>(input) {
|
||||
Some(ParsedItem(new_input, ())) => {
|
||||
input = new_input;
|
||||
ExtendedKind::Extended
|
||||
}
|
||||
None => ExtendedKind::Basic, // no separator before mandatory month/ordinal/week
|
||||
};
|
||||
|
||||
let mut ret_error = match (|| {
|
||||
let ParsedItem(mut input, month) = month(input).ok_or(InvalidComponent("month"))?;
|
||||
if extended_kind.is_extended() {
|
||||
input = ascii_char::<b'-'>(input)
|
||||
.ok_or(InvalidLiteral)?
|
||||
.into_inner();
|
||||
}
|
||||
let ParsedItem(input, day) = day(input).ok_or(InvalidComponent("day"))?;
|
||||
Ok(ParsedItem(input, (month, day)))
|
||||
})() {
|
||||
Ok(ParsedItem(input, (month, day))) => {
|
||||
*parsed = parsed
|
||||
.with_year(year)
|
||||
.ok_or(InvalidComponent("year"))?
|
||||
.with_month(month)
|
||||
.ok_or(InvalidComponent("month"))?
|
||||
.with_day(day)
|
||||
.ok_or(InvalidComponent("day"))?;
|
||||
return Ok(input);
|
||||
}
|
||||
Err(err) => err,
|
||||
};
|
||||
|
||||
// Don't check for `None`, as the error from year-month-day will always take priority.
|
||||
if let Some(ParsedItem(input, ordinal)) = dayo(input) {
|
||||
*parsed = parsed
|
||||
.with_year(year)
|
||||
.ok_or(InvalidComponent("year"))?
|
||||
.with_ordinal(ordinal)
|
||||
.ok_or(InvalidComponent("ordinal"))?;
|
||||
return Ok(input);
|
||||
}
|
||||
|
||||
match (|| {
|
||||
let input = ascii_char::<b'W'>(input)
|
||||
.ok_or((false, InvalidLiteral))?
|
||||
.into_inner();
|
||||
let ParsedItem(mut input, week) =
|
||||
week(input).ok_or((true, InvalidComponent("week")))?;
|
||||
if extended_kind.is_extended() {
|
||||
input = ascii_char::<b'-'>(input)
|
||||
.ok_or((true, InvalidLiteral))?
|
||||
.into_inner();
|
||||
}
|
||||
let ParsedItem(input, weekday) =
|
||||
dayk(input).ok_or((true, InvalidComponent("weekday")))?;
|
||||
Ok(ParsedItem(input, (week, weekday)))
|
||||
})() {
|
||||
Ok(ParsedItem(input, (week, weekday))) => {
|
||||
*parsed = parsed
|
||||
.with_iso_year(year)
|
||||
.ok_or(InvalidComponent("year"))?
|
||||
.with_iso_week_number(week)
|
||||
.ok_or(InvalidComponent("week"))?
|
||||
.with_weekday(weekday)
|
||||
.ok_or(InvalidComponent("weekday"))?;
|
||||
return Ok(input);
|
||||
}
|
||||
Err((false, _err)) => {}
|
||||
// This error is more accurate than the one from year-month-day.
|
||||
Err((true, err)) => ret_error = err,
|
||||
}
|
||||
|
||||
Err(ret_error.into())
|
||||
}
|
||||
}
|
||||
|
||||
// Basic: ["T"][hour][min][sec]
|
||||
// Extended: ["T"][hour][":"][min][":"][sec]
|
||||
// Reduced precision: components after [hour] (including their preceding separator) can be
|
||||
// omitted. ["T"] can be omitted if there is no date present.
|
||||
/// Parse a time in the basic or extended format. Reduced precision is permitted.
|
||||
pub(crate) fn parse_time<'a>(
|
||||
parsed: &'a mut Parsed,
|
||||
extended_kind: &'a mut ExtendedKind,
|
||||
date_is_present: bool,
|
||||
) -> impl FnMut(&[u8]) -> Result<&[u8], error::Parse> + 'a {
|
||||
move |mut input| {
|
||||
if date_is_present {
|
||||
input = ascii_char::<b'T'>(input)
|
||||
.ok_or(InvalidLiteral)?
|
||||
.into_inner();
|
||||
}
|
||||
|
||||
let ParsedItem(mut input, hour) = float(input).ok_or(InvalidComponent("hour"))?;
|
||||
match hour {
|
||||
(hour, None) => parsed.set_hour_24(hour).ok_or(InvalidComponent("hour"))?,
|
||||
(hour, Some(fractional_part)) => {
|
||||
*parsed = parsed
|
||||
.with_hour_24(hour)
|
||||
.ok_or(InvalidComponent("hour"))?
|
||||
.with_minute((fractional_part * 60.0) as _)
|
||||
.ok_or(InvalidComponent("minute"))?
|
||||
.with_second((fractional_part * 3600.0 % 60.) as _)
|
||||
.ok_or(InvalidComponent("second"))?
|
||||
.with_subsecond(
|
||||
(fractional_part * 3_600. * 1_000_000_000. % 1_000_000_000.) as _,
|
||||
)
|
||||
.ok_or(InvalidComponent("subsecond"))?;
|
||||
return Ok(input);
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(ParsedItem(new_input, ())) = ascii_char::<b':'>(input) {
|
||||
extended_kind
|
||||
.coerce_extended()
|
||||
.ok_or(InvalidComponent("minute"))?;
|
||||
input = new_input;
|
||||
};
|
||||
|
||||
let mut input = match float(input) {
|
||||
Some(ParsedItem(input, (minute, None))) => {
|
||||
extended_kind.coerce_basic();
|
||||
parsed
|
||||
.set_minute(minute)
|
||||
.ok_or(InvalidComponent("minute"))?;
|
||||
input
|
||||
}
|
||||
Some(ParsedItem(input, (minute, Some(fractional_part)))) => {
|
||||
// `None` is valid behavior, so don't error if this fails.
|
||||
extended_kind.coerce_basic();
|
||||
*parsed = parsed
|
||||
.with_minute(minute)
|
||||
.ok_or(InvalidComponent("minute"))?
|
||||
.with_second((fractional_part * 60.) as _)
|
||||
.ok_or(InvalidComponent("second"))?
|
||||
.with_subsecond(
|
||||
(fractional_part * 60. * 1_000_000_000. % 1_000_000_000.) as _,
|
||||
)
|
||||
.ok_or(InvalidComponent("subsecond"))?;
|
||||
return Ok(input);
|
||||
}
|
||||
// colon was present, so minutes are required
|
||||
None if extended_kind.is_extended() => {
|
||||
return Err(error::Parse::ParseFromDescription(InvalidComponent(
|
||||
"minute",
|
||||
)));
|
||||
}
|
||||
None => {
|
||||
// Missing components are assumed to be zero.
|
||||
*parsed = parsed
|
||||
.with_minute(0)
|
||||
.ok_or(InvalidComponent("minute"))?
|
||||
.with_second(0)
|
||||
.ok_or(InvalidComponent("second"))?
|
||||
.with_subsecond(0)
|
||||
.ok_or(InvalidComponent("subsecond"))?;
|
||||
return Ok(input);
|
||||
}
|
||||
};
|
||||
|
||||
if extended_kind.is_extended() {
|
||||
match ascii_char::<b':'>(input) {
|
||||
Some(ParsedItem(new_input, ())) => input = new_input,
|
||||
None => {
|
||||
*parsed = parsed
|
||||
.with_second(0)
|
||||
.ok_or(InvalidComponent("second"))?
|
||||
.with_subsecond(0)
|
||||
.ok_or(InvalidComponent("subsecond"))?;
|
||||
return Ok(input);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let (input, second, subsecond) = match float(input) {
|
||||
Some(ParsedItem(input, (second, None))) => (input, second, 0),
|
||||
Some(ParsedItem(input, (second, Some(fractional_part)))) => {
|
||||
(input, second, round(fractional_part * 1_000_000_000.) as _)
|
||||
}
|
||||
None if extended_kind.is_extended() => {
|
||||
return Err(error::Parse::ParseFromDescription(InvalidComponent(
|
||||
"second",
|
||||
)));
|
||||
}
|
||||
// Missing components are assumed to be zero.
|
||||
None => (input, 0, 0),
|
||||
};
|
||||
*parsed = parsed
|
||||
.with_second(second)
|
||||
.ok_or(InvalidComponent("second"))?
|
||||
.with_subsecond(subsecond)
|
||||
.ok_or(InvalidComponent("subsecond"))?;
|
||||
|
||||
Ok(input)
|
||||
}
|
||||
}
|
||||
|
||||
// Basic: [±][hour][min] or ["Z"]
|
||||
// Extended: [±][hour][":"][min] or ["Z"]
|
||||
// Reduced precision: [±][hour] or ["Z"]
|
||||
/// Parse a UTC offset in the basic or extended format. Reduced precision is supported.
|
||||
pub(crate) fn parse_offset<'a>(
|
||||
parsed: &'a mut Parsed,
|
||||
extended_kind: &'a mut ExtendedKind,
|
||||
) -> impl FnMut(&[u8]) -> Result<&[u8], error::Parse> + 'a {
|
||||
move |input| {
|
||||
if let Some(ParsedItem(input, ())) = ascii_char::<b'Z'>(input) {
|
||||
*parsed = parsed
|
||||
.with_offset_hour(0)
|
||||
.ok_or(InvalidComponent("offset hour"))?
|
||||
.with_offset_minute_signed(0)
|
||||
.ok_or(InvalidComponent("offset minute"))?
|
||||
.with_offset_second_signed(0)
|
||||
.ok_or(InvalidComponent("offset second"))?;
|
||||
return Ok(input);
|
||||
}
|
||||
|
||||
let ParsedItem(input, sign) = sign(input).ok_or(InvalidComponent("offset hour"))?;
|
||||
let mut input = hour(input)
|
||||
.and_then(|parsed_item| {
|
||||
parsed_item.consume_value(|hour| {
|
||||
parsed.set_offset_hour(if sign == b'-' {
|
||||
-(hour as i8)
|
||||
} else {
|
||||
hour as _
|
||||
})
|
||||
})
|
||||
})
|
||||
.ok_or(InvalidComponent("offset hour"))?;
|
||||
|
||||
if extended_kind.maybe_extended() {
|
||||
if let Some(ParsedItem(new_input, ())) = ascii_char::<b':'>(input) {
|
||||
extended_kind
|
||||
.coerce_extended()
|
||||
.ok_or(InvalidComponent("offset minute"))?;
|
||||
input = new_input;
|
||||
};
|
||||
}
|
||||
|
||||
let input = min(input)
|
||||
.and_then(|parsed_item| {
|
||||
parsed_item.consume_value(|min| {
|
||||
parsed.set_offset_minute_signed(if sign == b'-' {
|
||||
-(min as i8)
|
||||
} else {
|
||||
min as _
|
||||
})
|
||||
})
|
||||
})
|
||||
.ok_or(InvalidComponent("offset minute"))?;
|
||||
// If `:` was present, the format has already been set to extended. As such, this call
|
||||
// will do nothing in that case. If there wasn't `:` but minutes were
|
||||
// present, we know it's the basic format. Do not use `?` on the call, as
|
||||
// returning `None` is valid behavior.
|
||||
extended_kind.coerce_basic();
|
||||
|
||||
Ok(input)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Round wrapper that uses hardware implementation if `std` is available, falling back to manual
|
||||
/// implementation for `no_std`
|
||||
fn round(value: f64) -> f64 {
|
||||
#[cfg(feature = "std")]
|
||||
{
|
||||
value.round()
|
||||
}
|
||||
#[cfg(not(feature = "std"))]
|
||||
{
|
||||
round_impl(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
#[allow(clippy::missing_docs_in_private_items)]
|
||||
fn round_impl(value: f64) -> f64 {
|
||||
debug_assert!(value.is_sign_positive() && !value.is_nan());
|
||||
|
||||
let f = value % 1.;
|
||||
if f < 0.5 { value - f } else { value - f + 1. }
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
pub(crate) mod combinator;
|
||||
pub(crate) mod component;
|
||||
mod iso8601;
|
||||
pub(crate) mod parsable;
|
||||
mod parsed;
|
||||
pub(crate) mod shim;
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
//! A trait that can be used to parse an item from an input.
|
||||
|
||||
use core::convert::TryInto;
|
||||
use core::ops::Deref;
|
||||
|
||||
use crate::error::TryFromParsed;
|
||||
use crate::format_description::well_known::{Rfc2822, Rfc3339};
|
||||
use crate::format_description::well_known::iso8601::EncodedConfig;
|
||||
use crate::format_description::well_known::{Iso8601, Rfc2822, Rfc3339};
|
||||
use crate::format_description::FormatItem;
|
||||
#[cfg(feature = "alloc")]
|
||||
use crate::format_description::OwnedFormatItem;
|
||||
use crate::parsing::{Parsed, ParsedItem};
|
||||
use crate::{error, Date, Month, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset, Weekday};
|
||||
|
||||
|
@ -14,8 +16,13 @@ use crate::{error, Date, Month, OffsetDateTime, PrimitiveDateTime, Time, UtcOffs
|
|||
pub trait Parsable: sealed::Sealed {}
|
||||
impl Parsable for FormatItem<'_> {}
|
||||
impl Parsable for [FormatItem<'_>] {}
|
||||
#[cfg(feature = "alloc")]
|
||||
impl Parsable for OwnedFormatItem {}
|
||||
#[cfg(feature = "alloc")]
|
||||
impl Parsable for [OwnedFormatItem] {}
|
||||
impl Parsable for Rfc2822 {}
|
||||
impl Parsable for Rfc3339 {}
|
||||
impl<const CONFIG: EncodedConfig> Parsable for Iso8601<CONFIG> {}
|
||||
impl<T: Deref> Parsable for T where T::Target: Parsable {}
|
||||
|
||||
/// Seal the trait to prevent downstream users from implementing it, while still allowing it to
|
||||
|
@ -26,7 +33,6 @@ mod sealed {
|
|||
use super::*;
|
||||
|
||||
/// Parse the item using a format description and an input.
|
||||
#[cfg_attr(__time_03_docs, doc(cfg(feature = "parsing")))]
|
||||
pub trait Sealed {
|
||||
/// Parse the item into the provided [`Parsed`] struct.
|
||||
///
|
||||
|
@ -98,6 +104,28 @@ impl sealed::Sealed for [FormatItem<'_>] {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl sealed::Sealed for OwnedFormatItem {
|
||||
fn parse_into<'a>(
|
||||
&self,
|
||||
input: &'a [u8],
|
||||
parsed: &mut Parsed,
|
||||
) -> Result<&'a [u8], error::Parse> {
|
||||
Ok(parsed.parse_item(input, self)?)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl sealed::Sealed for [OwnedFormatItem] {
|
||||
fn parse_into<'a>(
|
||||
&self,
|
||||
input: &'a [u8],
|
||||
parsed: &mut Parsed,
|
||||
) -> Result<&'a [u8], error::Parse> {
|
||||
Ok(parsed.parse_items(input, self)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Deref> sealed::Sealed for T
|
||||
where
|
||||
T::Target: sealed::Sealed,
|
||||
|
@ -131,13 +159,13 @@ impl sealed::Sealed for Rfc2822 {
|
|||
let input = opt(fws)(input).into_inner();
|
||||
let input = first_match(
|
||||
[
|
||||
(&b"Mon"[..], Weekday::Monday),
|
||||
(&b"Tue"[..], Weekday::Tuesday),
|
||||
(&b"Wed"[..], Weekday::Wednesday),
|
||||
(&b"Thu"[..], Weekday::Thursday),
|
||||
(&b"Fri"[..], Weekday::Friday),
|
||||
(&b"Sat"[..], Weekday::Saturday),
|
||||
(&b"Sun"[..], Weekday::Sunday),
|
||||
(b"Mon".as_slice(), Weekday::Monday),
|
||||
(b"Tue".as_slice(), Weekday::Tuesday),
|
||||
(b"Wed".as_slice(), Weekday::Wednesday),
|
||||
(b"Thu".as_slice(), Weekday::Thursday),
|
||||
(b"Fri".as_slice(), Weekday::Friday),
|
||||
(b"Sat".as_slice(), Weekday::Saturday),
|
||||
(b"Sun".as_slice(), Weekday::Sunday),
|
||||
],
|
||||
false,
|
||||
)(input)
|
||||
|
@ -145,70 +173,67 @@ impl sealed::Sealed for Rfc2822 {
|
|||
.ok_or(InvalidComponent("weekday"))?;
|
||||
let input = comma(input).ok_or(InvalidLiteral)?.into_inner();
|
||||
let input = cfws(input).ok_or(InvalidLiteral)?.into_inner();
|
||||
let input = n_to_m_digits::<_, 1, 2>(input)
|
||||
let input = n_to_m_digits::<1, 2, _>(input)
|
||||
.and_then(|item| item.consume_value(|value| parsed.set_day(value)))
|
||||
.ok_or(InvalidComponent("day"))?;
|
||||
let input = cfws(input).ok_or(InvalidLiteral)?.into_inner();
|
||||
let input = first_match(
|
||||
[
|
||||
(&b"Jan"[..], Month::January),
|
||||
(&b"Feb"[..], Month::February),
|
||||
(&b"Mar"[..], Month::March),
|
||||
(&b"Apr"[..], Month::April),
|
||||
(&b"May"[..], Month::May),
|
||||
(&b"Jun"[..], Month::June),
|
||||
(&b"Jul"[..], Month::July),
|
||||
(&b"Aug"[..], Month::August),
|
||||
(&b"Sep"[..], Month::September),
|
||||
(&b"Oct"[..], Month::October),
|
||||
(&b"Nov"[..], Month::November),
|
||||
(&b"Dec"[..], Month::December),
|
||||
(b"Jan".as_slice(), Month::January),
|
||||
(b"Feb".as_slice(), Month::February),
|
||||
(b"Mar".as_slice(), Month::March),
|
||||
(b"Apr".as_slice(), Month::April),
|
||||
(b"May".as_slice(), Month::May),
|
||||
(b"Jun".as_slice(), Month::June),
|
||||
(b"Jul".as_slice(), Month::July),
|
||||
(b"Aug".as_slice(), Month::August),
|
||||
(b"Sep".as_slice(), Month::September),
|
||||
(b"Oct".as_slice(), Month::October),
|
||||
(b"Nov".as_slice(), Month::November),
|
||||
(b"Dec".as_slice(), Month::December),
|
||||
],
|
||||
false,
|
||||
)(input)
|
||||
.and_then(|item| item.consume_value(|value| parsed.set_month(value)))
|
||||
.ok_or(InvalidComponent("month"))?;
|
||||
let input = cfws(input).ok_or(InvalidLiteral)?.into_inner();
|
||||
let input = match exactly_n_digits::<u32, 4>(input) {
|
||||
let input = match exactly_n_digits::<4, u32>(input) {
|
||||
Some(item) => {
|
||||
let input = item
|
||||
.flat_map(|year| if year >= 1900 { Some(year) } else { None })
|
||||
.and_then(|item| item.consume_value(|value| parsed.set_year(value as _)))
|
||||
.ok_or(InvalidComponent("year"))?;
|
||||
let input = fws(input).ok_or(InvalidLiteral)?.into_inner();
|
||||
input
|
||||
fws(input).ok_or(InvalidLiteral)?.into_inner()
|
||||
}
|
||||
None => {
|
||||
let input = exactly_n_digits::<u32, 2>(input)
|
||||
let input = exactly_n_digits::<2, u32>(input)
|
||||
.and_then(|item| {
|
||||
item.map(|year| if year < 50 { year + 2000 } else { year + 1900 })
|
||||
.map(|year| year as _)
|
||||
.consume_value(|value| parsed.set_year(value))
|
||||
})
|
||||
.ok_or(InvalidComponent("year"))?;
|
||||
let input = cfws(input).ok_or(InvalidLiteral)?.into_inner();
|
||||
input
|
||||
cfws(input).ok_or(InvalidLiteral)?.into_inner()
|
||||
}
|
||||
};
|
||||
|
||||
let input = exactly_n_digits::<_, 2>(input)
|
||||
let input = exactly_n_digits::<2, _>(input)
|
||||
.and_then(|item| item.consume_value(|value| parsed.set_hour_24(value)))
|
||||
.ok_or(InvalidComponent("hour"))?;
|
||||
let input = opt(cfws)(input).into_inner();
|
||||
let input = colon(input).ok_or(InvalidLiteral)?.into_inner();
|
||||
let input = opt(cfws)(input).into_inner();
|
||||
let input = exactly_n_digits::<_, 2>(input)
|
||||
let input = exactly_n_digits::<2, _>(input)
|
||||
.and_then(|item| item.consume_value(|value| parsed.set_minute(value)))
|
||||
.ok_or(InvalidComponent("minute"))?;
|
||||
|
||||
let input = if let Some(input) = colon(opt(cfws)(input).into_inner()) {
|
||||
let input = input.into_inner(); // discard the colon
|
||||
let input = opt(cfws)(input).into_inner();
|
||||
let input = exactly_n_digits::<_, 2>(input)
|
||||
let input = exactly_n_digits::<2, _>(input)
|
||||
.and_then(|item| item.consume_value(|value| parsed.set_second(value)))
|
||||
.ok_or(InvalidComponent("second"))?;
|
||||
let input = cfws(input).ok_or(InvalidLiteral)?.into_inner();
|
||||
input
|
||||
cfws(input).ok_or(InvalidLiteral)?.into_inner()
|
||||
} else {
|
||||
cfws(input).ok_or(InvalidLiteral)?.into_inner()
|
||||
};
|
||||
|
@ -219,16 +244,16 @@ impl sealed::Sealed for Rfc2822 {
|
|||
#[allow(clippy::unnecessary_lazy_evaluations)] // rust-lang/rust-clippy#8522
|
||||
let zone_literal = first_match(
|
||||
[
|
||||
(&b"UT"[..], 0),
|
||||
(&b"GMT"[..], 0),
|
||||
(&b"EST"[..], -5),
|
||||
(&b"EDT"[..], -4),
|
||||
(&b"CST"[..], -6),
|
||||
(&b"CDT"[..], -5),
|
||||
(&b"MST"[..], -7),
|
||||
(&b"MDT"[..], -6),
|
||||
(&b"PST"[..], -8),
|
||||
(&b"PDT"[..], -7),
|
||||
(b"UT".as_slice(), 0),
|
||||
(b"GMT".as_slice(), 0),
|
||||
(b"EST".as_slice(), -5),
|
||||
(b"EDT".as_slice(), -4),
|
||||
(b"CST".as_slice(), -6),
|
||||
(b"CDT".as_slice(), -5),
|
||||
(b"MST".as_slice(), -7),
|
||||
(b"MDT".as_slice(), -6),
|
||||
(b"PST".as_slice(), -8),
|
||||
(b"PDT".as_slice(), -7),
|
||||
],
|
||||
false,
|
||||
)(input)
|
||||
|
@ -253,7 +278,7 @@ impl sealed::Sealed for Rfc2822 {
|
|||
}
|
||||
|
||||
let ParsedItem(input, offset_sign) = sign(input).ok_or(InvalidComponent("offset hour"))?;
|
||||
let input = exactly_n_digits::<u8, 2>(input)
|
||||
let input = exactly_n_digits::<2, u8>(input)
|
||||
.and_then(|item| {
|
||||
item.map(|offset_hour| {
|
||||
if offset_sign == b'-' {
|
||||
|
@ -265,7 +290,7 @@ impl sealed::Sealed for Rfc2822 {
|
|||
.consume_value(|value| parsed.set_offset_hour(value))
|
||||
})
|
||||
.ok_or(InvalidComponent("offset hour"))?;
|
||||
let input = exactly_n_digits::<u8, 2>(input)
|
||||
let input = exactly_n_digits::<2, u8>(input)
|
||||
.and_then(|item| {
|
||||
item.consume_value(|value| parsed.set_offset_minute_signed(value as _))
|
||||
})
|
||||
|
@ -273,6 +298,174 @@ impl sealed::Sealed for Rfc2822 {
|
|||
|
||||
Ok(input)
|
||||
}
|
||||
|
||||
fn parse_offset_date_time(&self, input: &[u8]) -> Result<OffsetDateTime, error::Parse> {
|
||||
use crate::error::ParseFromDescription::{InvalidComponent, InvalidLiteral};
|
||||
use crate::parsing::combinator::rfc::rfc2822::{cfws, fws};
|
||||
use crate::parsing::combinator::{
|
||||
ascii_char, exactly_n_digits, first_match, n_to_m_digits, opt, sign,
|
||||
};
|
||||
|
||||
let colon = ascii_char::<b':'>;
|
||||
let comma = ascii_char::<b','>;
|
||||
|
||||
let input = opt(fws)(input).into_inner();
|
||||
// This parses the weekday, but we don't actually use the value anywhere. Because of this,
|
||||
// just return `()` to avoid unnecessary generated code.
|
||||
let ParsedItem(input, ()) = first_match(
|
||||
[
|
||||
(b"Mon".as_slice(), ()),
|
||||
(b"Tue".as_slice(), ()),
|
||||
(b"Wed".as_slice(), ()),
|
||||
(b"Thu".as_slice(), ()),
|
||||
(b"Fri".as_slice(), ()),
|
||||
(b"Sat".as_slice(), ()),
|
||||
(b"Sun".as_slice(), ()),
|
||||
],
|
||||
false,
|
||||
)(input)
|
||||
.ok_or(InvalidComponent("weekday"))?;
|
||||
let input = comma(input).ok_or(InvalidLiteral)?.into_inner();
|
||||
let input = cfws(input).ok_or(InvalidLiteral)?.into_inner();
|
||||
let ParsedItem(input, day) =
|
||||
n_to_m_digits::<1, 2, _>(input).ok_or(InvalidComponent("day"))?;
|
||||
let input = cfws(input).ok_or(InvalidLiteral)?.into_inner();
|
||||
let ParsedItem(input, month) = first_match(
|
||||
[
|
||||
(b"Jan".as_slice(), Month::January),
|
||||
(b"Feb".as_slice(), Month::February),
|
||||
(b"Mar".as_slice(), Month::March),
|
||||
(b"Apr".as_slice(), Month::April),
|
||||
(b"May".as_slice(), Month::May),
|
||||
(b"Jun".as_slice(), Month::June),
|
||||
(b"Jul".as_slice(), Month::July),
|
||||
(b"Aug".as_slice(), Month::August),
|
||||
(b"Sep".as_slice(), Month::September),
|
||||
(b"Oct".as_slice(), Month::October),
|
||||
(b"Nov".as_slice(), Month::November),
|
||||
(b"Dec".as_slice(), Month::December),
|
||||
],
|
||||
false,
|
||||
)(input)
|
||||
.ok_or(InvalidComponent("month"))?;
|
||||
let input = cfws(input).ok_or(InvalidLiteral)?.into_inner();
|
||||
let (input, year) = match exactly_n_digits::<4, u32>(input) {
|
||||
Some(item) => {
|
||||
let ParsedItem(input, year) = item
|
||||
.flat_map(|year| if year >= 1900 { Some(year) } else { None })
|
||||
.ok_or(InvalidComponent("year"))?;
|
||||
let input = fws(input).ok_or(InvalidLiteral)?.into_inner();
|
||||
(input, year)
|
||||
}
|
||||
None => {
|
||||
let ParsedItem(input, year) = exactly_n_digits::<2, u32>(input)
|
||||
.map(|item| item.map(|year| if year < 50 { year + 2000 } else { year + 1900 }))
|
||||
.ok_or(InvalidComponent("year"))?;
|
||||
let input = cfws(input).ok_or(InvalidLiteral)?.into_inner();
|
||||
(input, year)
|
||||
}
|
||||
};
|
||||
|
||||
let ParsedItem(input, hour) =
|
||||
exactly_n_digits::<2, _>(input).ok_or(InvalidComponent("hour"))?;
|
||||
let input = opt(cfws)(input).into_inner();
|
||||
let input = colon(input).ok_or(InvalidLiteral)?.into_inner();
|
||||
let input = opt(cfws)(input).into_inner();
|
||||
let ParsedItem(input, minute) =
|
||||
exactly_n_digits::<2, _>(input).ok_or(InvalidComponent("minute"))?;
|
||||
|
||||
let (input, mut second) = if let Some(input) = colon(opt(cfws)(input).into_inner()) {
|
||||
let input = input.into_inner(); // discard the colon
|
||||
let input = opt(cfws)(input).into_inner();
|
||||
let ParsedItem(input, second) =
|
||||
exactly_n_digits::<2, _>(input).ok_or(InvalidComponent("second"))?;
|
||||
let input = cfws(input).ok_or(InvalidLiteral)?.into_inner();
|
||||
(input, second)
|
||||
} else {
|
||||
(cfws(input).ok_or(InvalidLiteral)?.into_inner(), 0)
|
||||
};
|
||||
|
||||
#[allow(clippy::unnecessary_lazy_evaluations)] // rust-lang/rust-clippy#8522
|
||||
let zone_literal = first_match(
|
||||
[
|
||||
(b"UT".as_slice(), 0),
|
||||
(b"GMT".as_slice(), 0),
|
||||
(b"EST".as_slice(), -5),
|
||||
(b"EDT".as_slice(), -4),
|
||||
(b"CST".as_slice(), -6),
|
||||
(b"CDT".as_slice(), -5),
|
||||
(b"MST".as_slice(), -7),
|
||||
(b"MDT".as_slice(), -6),
|
||||
(b"PST".as_slice(), -8),
|
||||
(b"PDT".as_slice(), -7),
|
||||
],
|
||||
false,
|
||||
)(input)
|
||||
.or_else(|| match input {
|
||||
[
|
||||
b'a'..=b'i' | b'k'..=b'z' | b'A'..=b'I' | b'K'..=b'Z',
|
||||
rest @ ..,
|
||||
] => Some(ParsedItem(rest, 0)),
|
||||
_ => None,
|
||||
});
|
||||
|
||||
let (input, offset_hour, offset_minute) = if let Some(zone_literal) = zone_literal {
|
||||
let ParsedItem(input, offset_hour) = zone_literal;
|
||||
(input, offset_hour, 0)
|
||||
} else {
|
||||
let ParsedItem(input, offset_sign) =
|
||||
sign(input).ok_or(InvalidComponent("offset hour"))?;
|
||||
let ParsedItem(input, offset_hour) = exactly_n_digits::<2, u8>(input)
|
||||
.map(|item| {
|
||||
item.map(|offset_hour| {
|
||||
if offset_sign == b'-' {
|
||||
-(offset_hour as i8)
|
||||
} else {
|
||||
offset_hour as _
|
||||
}
|
||||
})
|
||||
})
|
||||
.ok_or(InvalidComponent("offset hour"))?;
|
||||
let ParsedItem(input, offset_minute) =
|
||||
exactly_n_digits::<2, u8>(input).ok_or(InvalidComponent("offset minute"))?;
|
||||
(input, offset_hour, offset_minute as i8)
|
||||
};
|
||||
|
||||
if !input.is_empty() {
|
||||
return Err(error::Parse::UnexpectedTrailingCharacters);
|
||||
}
|
||||
|
||||
let mut nanosecond = 0;
|
||||
let leap_second_input = if second == 60 {
|
||||
second = 59;
|
||||
nanosecond = 999_999_999;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
let dt = (|| {
|
||||
let date = Date::from_calendar_date(year as _, month, day)?;
|
||||
let time = Time::from_hms_nano(hour, minute, second, nanosecond)?;
|
||||
let offset = UtcOffset::from_hms(offset_hour, offset_minute, 0)?;
|
||||
Ok(date.with_time(time).assume_offset(offset))
|
||||
})()
|
||||
.map_err(TryFromParsed::ComponentRange)?;
|
||||
|
||||
if leap_second_input && !dt.is_valid_leap_second_stand_in() {
|
||||
return Err(error::Parse::TryFromParsed(TryFromParsed::ComponentRange(
|
||||
error::ComponentRange {
|
||||
name: "second",
|
||||
minimum: 0,
|
||||
maximum: 59,
|
||||
value: 60,
|
||||
conditional_range: true,
|
||||
},
|
||||
)));
|
||||
}
|
||||
|
||||
Ok(dt)
|
||||
}
|
||||
}
|
||||
|
||||
impl sealed::Sealed for Rfc3339 {
|
||||
|
@ -289,30 +482,30 @@ impl sealed::Sealed for Rfc3339 {
|
|||
let dash = ascii_char::<b'-'>;
|
||||
let colon = ascii_char::<b':'>;
|
||||
|
||||
let input = exactly_n_digits::<u32, 4>(input)
|
||||
let input = exactly_n_digits::<4, u32>(input)
|
||||
.and_then(|item| item.consume_value(|value| parsed.set_year(value as _)))
|
||||
.ok_or(InvalidComponent("year"))?;
|
||||
let input = dash(input).ok_or(InvalidLiteral)?.into_inner();
|
||||
let input = exactly_n_digits::<_, 2>(input)
|
||||
let input = exactly_n_digits::<2, _>(input)
|
||||
.and_then(|item| item.flat_map(|value| Month::from_number(value).ok()))
|
||||
.and_then(|item| item.consume_value(|value| parsed.set_month(value)))
|
||||
.ok_or(InvalidComponent("month"))?;
|
||||
let input = dash(input).ok_or(InvalidLiteral)?.into_inner();
|
||||
let input = exactly_n_digits::<_, 2>(input)
|
||||
let input = exactly_n_digits::<2, _>(input)
|
||||
.and_then(|item| item.consume_value(|value| parsed.set_day(value)))
|
||||
.ok_or(InvalidComponent("day"))?;
|
||||
let input = ascii_char_ignore_case::<b'T'>(input)
|
||||
.ok_or(InvalidLiteral)?
|
||||
.into_inner();
|
||||
let input = exactly_n_digits::<_, 2>(input)
|
||||
let input = exactly_n_digits::<2, _>(input)
|
||||
.and_then(|item| item.consume_value(|value| parsed.set_hour_24(value)))
|
||||
.ok_or(InvalidComponent("hour"))?;
|
||||
let input = colon(input).ok_or(InvalidLiteral)?.into_inner();
|
||||
let input = exactly_n_digits::<_, 2>(input)
|
||||
let input = exactly_n_digits::<2, _>(input)
|
||||
.and_then(|item| item.consume_value(|value| parsed.set_minute(value)))
|
||||
.ok_or(InvalidComponent("minute"))?;
|
||||
let input = colon(input).ok_or(InvalidLiteral)?.into_inner();
|
||||
let input = exactly_n_digits::<_, 2>(input)
|
||||
let input = exactly_n_digits::<2, _>(input)
|
||||
.and_then(|item| item.consume_value(|value| parsed.set_second(value)))
|
||||
.ok_or(InvalidComponent("second"))?;
|
||||
let input = if let Some(ParsedItem(input, ())) = ascii_char::<b'.'>(input) {
|
||||
|
@ -352,7 +545,7 @@ impl sealed::Sealed for Rfc3339 {
|
|||
}
|
||||
|
||||
let ParsedItem(input, offset_sign) = sign(input).ok_or(InvalidComponent("offset hour"))?;
|
||||
let input = exactly_n_digits::<u8, 2>(input)
|
||||
let input = exactly_n_digits::<2, u8>(input)
|
||||
.and_then(|item| {
|
||||
item.map(|offset_hour| {
|
||||
if offset_sign == b'-' {
|
||||
|
@ -365,9 +558,16 @@ impl sealed::Sealed for Rfc3339 {
|
|||
})
|
||||
.ok_or(InvalidComponent("offset hour"))?;
|
||||
let input = colon(input).ok_or(InvalidLiteral)?.into_inner();
|
||||
let input = exactly_n_digits::<u8, 2>(input)
|
||||
let input = exactly_n_digits::<2, u8>(input)
|
||||
.and_then(|item| {
|
||||
item.consume_value(|value| parsed.set_offset_minute_signed(value as _))
|
||||
item.map(|offset_minute| {
|
||||
if offset_sign == b'-' {
|
||||
-(offset_minute as i8)
|
||||
} else {
|
||||
offset_minute as _
|
||||
}
|
||||
})
|
||||
.consume_value(|value| parsed.set_offset_minute_signed(value))
|
||||
})
|
||||
.ok_or(InvalidComponent("offset minute"))?;
|
||||
|
||||
|
@ -384,24 +584,24 @@ impl sealed::Sealed for Rfc3339 {
|
|||
let colon = ascii_char::<b':'>;
|
||||
|
||||
let ParsedItem(input, year) =
|
||||
exactly_n_digits::<u32, 4>(input).ok_or(InvalidComponent("year"))?;
|
||||
exactly_n_digits::<4, u32>(input).ok_or(InvalidComponent("year"))?;
|
||||
let input = dash(input).ok_or(InvalidLiteral)?.into_inner();
|
||||
let ParsedItem(input, month) =
|
||||
exactly_n_digits::<_, 2>(input).ok_or(InvalidComponent("month"))?;
|
||||
exactly_n_digits::<2, _>(input).ok_or(InvalidComponent("month"))?;
|
||||
let input = dash(input).ok_or(InvalidLiteral)?.into_inner();
|
||||
let ParsedItem(input, day) =
|
||||
exactly_n_digits::<_, 2>(input).ok_or(InvalidComponent("day"))?;
|
||||
exactly_n_digits::<2, _>(input).ok_or(InvalidComponent("day"))?;
|
||||
let input = ascii_char_ignore_case::<b'T'>(input)
|
||||
.ok_or(InvalidLiteral)?
|
||||
.into_inner();
|
||||
let ParsedItem(input, hour) =
|
||||
exactly_n_digits::<_, 2>(input).ok_or(InvalidComponent("hour"))?;
|
||||
exactly_n_digits::<2, _>(input).ok_or(InvalidComponent("hour"))?;
|
||||
let input = colon(input).ok_or(InvalidLiteral)?.into_inner();
|
||||
let ParsedItem(input, minute) =
|
||||
exactly_n_digits::<_, 2>(input).ok_or(InvalidComponent("minute"))?;
|
||||
exactly_n_digits::<2, _>(input).ok_or(InvalidComponent("minute"))?;
|
||||
let input = colon(input).ok_or(InvalidLiteral)?.into_inner();
|
||||
let ParsedItem(input, mut second) =
|
||||
exactly_n_digits::<_, 2>(input).ok_or(InvalidComponent("second"))?;
|
||||
exactly_n_digits::<2, _>(input).ok_or(InvalidComponent("second"))?;
|
||||
let ParsedItem(input, mut nanosecond) =
|
||||
if let Some(ParsedItem(input, ())) = ascii_char::<b'.'>(input) {
|
||||
let ParsedItem(mut input, mut value) = any_digit(input)
|
||||
|
@ -426,17 +626,21 @@ impl sealed::Sealed for Rfc3339 {
|
|||
let ParsedItem(input, offset_sign) =
|
||||
sign(input).ok_or(InvalidComponent("offset hour"))?;
|
||||
let ParsedItem(input, offset_hour) =
|
||||
exactly_n_digits::<u8, 2>(input).ok_or(InvalidComponent("offset hour"))?;
|
||||
exactly_n_digits::<2, u8>(input).ok_or(InvalidComponent("offset hour"))?;
|
||||
let input = colon(input).ok_or(InvalidLiteral)?.into_inner();
|
||||
let ParsedItem(input, offset_minute) =
|
||||
exactly_n_digits::<u8, 2>(input).ok_or(InvalidComponent("offset minute"))?;
|
||||
exactly_n_digits::<2, u8>(input).ok_or(InvalidComponent("offset minute"))?;
|
||||
UtcOffset::from_hms(
|
||||
if offset_sign == b'-' {
|
||||
-(offset_hour as i8)
|
||||
} else {
|
||||
offset_hour as _
|
||||
},
|
||||
offset_minute as _,
|
||||
if offset_sign == b'-' {
|
||||
-(offset_minute as i8)
|
||||
} else {
|
||||
offset_minute as _
|
||||
},
|
||||
0,
|
||||
)
|
||||
.map(|offset| ParsedItem(input, offset))
|
||||
|
@ -489,4 +693,62 @@ impl sealed::Sealed for Rfc3339 {
|
|||
Ok(dt)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const CONFIG: EncodedConfig> sealed::Sealed for Iso8601<CONFIG> {
|
||||
fn parse_into<'a>(
|
||||
&self,
|
||||
mut input: &'a [u8],
|
||||
parsed: &mut Parsed,
|
||||
) -> Result<&'a [u8], error::Parse> {
|
||||
use crate::parsing::combinator::rfc::iso8601::ExtendedKind;
|
||||
|
||||
let mut extended_kind = ExtendedKind::Unknown;
|
||||
let mut date_is_present = false;
|
||||
let mut time_is_present = false;
|
||||
let mut offset_is_present = false;
|
||||
let mut first_error = None;
|
||||
|
||||
match Self::parse_date(parsed, &mut extended_kind)(input) {
|
||||
Ok(new_input) => {
|
||||
input = new_input;
|
||||
date_is_present = true;
|
||||
}
|
||||
Err(err) => {
|
||||
first_error.get_or_insert(err);
|
||||
}
|
||||
}
|
||||
|
||||
match Self::parse_time(parsed, &mut extended_kind, date_is_present)(input) {
|
||||
Ok(new_input) => {
|
||||
input = new_input;
|
||||
time_is_present = true;
|
||||
}
|
||||
Err(err) => {
|
||||
first_error.get_or_insert(err);
|
||||
}
|
||||
}
|
||||
|
||||
// If a date and offset are present, a time must be as well.
|
||||
if !date_is_present || time_is_present {
|
||||
match Self::parse_offset(parsed, &mut extended_kind)(input) {
|
||||
Ok(new_input) => {
|
||||
input = new_input;
|
||||
offset_is_present = true;
|
||||
}
|
||||
Err(err) => {
|
||||
first_error.get_or_insert(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !date_is_present && !time_is_present && !offset_is_present {
|
||||
match first_error {
|
||||
Some(err) => return Err(err),
|
||||
None => unreachable!("an error should be present if no components were parsed"),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(input)
|
||||
}
|
||||
}
|
||||
// endregion well-known formats
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
//! Information parsed from an input and format description.
|
||||
|
||||
use core::convert::{TryFrom, TryInto};
|
||||
use core::mem::MaybeUninit;
|
||||
use core::num::{NonZeroU16, NonZeroU8};
|
||||
|
||||
use crate::error::TryFromParsed::InsufficientInformation;
|
||||
use crate::format_description::modifier::{WeekNumberRepr, YearRepr};
|
||||
#[cfg(feature = "alloc")]
|
||||
use crate::format_description::OwnedFormatItem;
|
||||
use crate::format_description::{Component, FormatItem};
|
||||
use crate::parsing::component::{
|
||||
parse_day, parse_hour, parse_minute, parse_month, parse_offset_hour, parse_offset_minute,
|
||||
|
@ -14,108 +16,37 @@ use crate::parsing::component::{
|
|||
use crate::parsing::ParsedItem;
|
||||
use crate::{error, Date, Month, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset, Weekday};
|
||||
|
||||
/// All information parsed.
|
||||
///
|
||||
/// This information is directly used to construct the final values.
|
||||
///
|
||||
/// Most users will not need think about this struct in any way. It is public to allow for manual
|
||||
/// control over values, in the instance that the default parser is insufficient.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Parsed {
|
||||
/// Calendar year.
|
||||
year: Option<i32>,
|
||||
/// The last two digits of the calendar year.
|
||||
year_last_two: Option<u8>,
|
||||
/// Year of the [ISO week date](https://en.wikipedia.org/wiki/ISO_week_date).
|
||||
iso_year: Option<i32>,
|
||||
/// The last two digits of the ISO week year.
|
||||
iso_year_last_two: Option<u8>,
|
||||
/// Month of the year.
|
||||
month: Option<Month>,
|
||||
/// Week of the year, where week one begins on the first Sunday of the calendar year.
|
||||
sunday_week_number: Option<u8>,
|
||||
/// Week of the year, where week one begins on the first Monday of the calendar year.
|
||||
monday_week_number: Option<u8>,
|
||||
/// Week of the year, where week one is the Monday-to-Sunday period containing January 4.
|
||||
iso_week_number: Option<NonZeroU8>,
|
||||
/// Day of the week.
|
||||
weekday: Option<Weekday>,
|
||||
/// Day of the year.
|
||||
ordinal: Option<NonZeroU16>,
|
||||
/// Day of the month.
|
||||
day: Option<NonZeroU8>,
|
||||
/// Hour within the day.
|
||||
hour_24: Option<u8>,
|
||||
/// Hour within the 12-hour period (midnight to noon or vice versa). This is typically used in
|
||||
/// conjunction with AM/PM, which is indicated by the `hour_12_is_pm` field.
|
||||
hour_12: Option<NonZeroU8>,
|
||||
/// Whether the `hour_12` field indicates a time that "PM".
|
||||
hour_12_is_pm: Option<bool>,
|
||||
/// Minute within the hour.
|
||||
minute: Option<u8>,
|
||||
/// Second within the minute.
|
||||
second: Option<u8>,
|
||||
/// Nanosecond within the second.
|
||||
subsecond: Option<u32>,
|
||||
/// Whole hours of the UTC offset.
|
||||
offset_hour: Option<i8>,
|
||||
/// Minutes within the hour of the UTC offset.
|
||||
offset_minute: Option<i8>,
|
||||
/// Seconds within the minute of the UTC offset.
|
||||
offset_second: Option<i8>,
|
||||
/// Indicates whether a leap second is permitted to be parsed. This is required by some
|
||||
/// well-known formats.
|
||||
leap_second_allowed: bool,
|
||||
/// Sealed to prevent downstream implementations.
|
||||
mod sealed {
|
||||
use super::*;
|
||||
|
||||
/// A trait to allow `parse_item` to be generic.
|
||||
pub trait AnyFormatItem {
|
||||
/// Parse a single item, returning the remaining input on success.
|
||||
fn parse_item<'a>(
|
||||
&self,
|
||||
parsed: &mut Parsed,
|
||||
input: &'a [u8],
|
||||
) -> Result<&'a [u8], error::ParseFromDescription>;
|
||||
}
|
||||
}
|
||||
|
||||
impl Parsed {
|
||||
/// Create a new instance of `Parsed` with no information known.
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
year: None,
|
||||
year_last_two: None,
|
||||
iso_year: None,
|
||||
iso_year_last_two: None,
|
||||
month: None,
|
||||
sunday_week_number: None,
|
||||
monday_week_number: None,
|
||||
iso_week_number: None,
|
||||
weekday: None,
|
||||
ordinal: None,
|
||||
day: None,
|
||||
hour_24: None,
|
||||
hour_12: None,
|
||||
hour_12_is_pm: None,
|
||||
minute: None,
|
||||
second: None,
|
||||
subsecond: None,
|
||||
offset_hour: None,
|
||||
offset_minute: None,
|
||||
offset_second: None,
|
||||
leap_second_allowed: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a single [`FormatItem`], mutating the struct. The remaining input is returned as the
|
||||
/// `Ok` value.
|
||||
///
|
||||
/// If a [`FormatItem::Optional`] is passed, parsing will not fail; the input will be returned
|
||||
/// as-is if the expected format is not present.
|
||||
pub fn parse_item<'a>(
|
||||
&mut self,
|
||||
impl sealed::AnyFormatItem for FormatItem<'_> {
|
||||
fn parse_item<'a>(
|
||||
&self,
|
||||
parsed: &mut Parsed,
|
||||
input: &'a [u8],
|
||||
item: &FormatItem<'_>,
|
||||
) -> Result<&'a [u8], error::ParseFromDescription> {
|
||||
match item {
|
||||
FormatItem::Literal(literal) => Self::parse_literal(input, literal),
|
||||
FormatItem::Component(component) => self.parse_component(input, *component),
|
||||
FormatItem::Compound(compound) => self.parse_items(input, compound),
|
||||
FormatItem::Optional(item) => self.parse_item(input, item).or(Ok(input)),
|
||||
FormatItem::First(items) => {
|
||||
match self {
|
||||
Self::Literal(literal) => Parsed::parse_literal(input, literal),
|
||||
Self::Component(component) => parsed.parse_component(input, *component),
|
||||
Self::Compound(compound) => parsed.parse_items(input, compound),
|
||||
Self::Optional(item) => parsed.parse_item(input, *item).or(Ok(input)),
|
||||
Self::First(items) => {
|
||||
let mut first_err = None;
|
||||
|
||||
for item in items.iter() {
|
||||
match self.parse_item(input, item) {
|
||||
match parsed.parse_item(input, item) {
|
||||
Ok(remaining_input) => return Ok(remaining_input),
|
||||
Err(err) if first_err.is_none() => first_err = Some(err),
|
||||
Err(_) => {}
|
||||
|
@ -131,16 +62,165 @@ impl Parsed {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a sequence of [`FormatItem`]s, mutating the struct. The remaining input is returned as
|
||||
/// the `Ok` value.
|
||||
#[cfg(feature = "alloc")]
|
||||
impl sealed::AnyFormatItem for OwnedFormatItem {
|
||||
fn parse_item<'a>(
|
||||
&self,
|
||||
parsed: &mut Parsed,
|
||||
input: &'a [u8],
|
||||
) -> Result<&'a [u8], error::ParseFromDescription> {
|
||||
match self {
|
||||
Self::Literal(literal) => Parsed::parse_literal(input, literal),
|
||||
Self::Component(component) => parsed.parse_component(input, *component),
|
||||
Self::Compound(compound) => parsed.parse_items(input, compound),
|
||||
Self::Optional(item) => parsed.parse_item(input, item.as_ref()).or(Ok(input)),
|
||||
Self::First(items) => {
|
||||
let mut first_err = None;
|
||||
|
||||
for item in items.iter() {
|
||||
match parsed.parse_item(input, item) {
|
||||
Ok(remaining_input) => return Ok(remaining_input),
|
||||
Err(err) if first_err.is_none() => first_err = Some(err),
|
||||
Err(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
match first_err {
|
||||
Some(err) => Err(err),
|
||||
// This location will be reached if the slice is empty, skipping the `for` loop.
|
||||
// As this case is expected to be uncommon, there's no need to check up front.
|
||||
None => Ok(input),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// All information parsed.
|
||||
///
|
||||
/// This information is directly used to construct the final values.
|
||||
///
|
||||
/// Most users will not need think about this struct in any way. It is public to allow for manual
|
||||
/// control over values, in the instance that the default parser is insufficient.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Parsed {
|
||||
/// Bitflags indicating whether a particular field is present.
|
||||
flags: u16,
|
||||
/// Calendar year.
|
||||
year: MaybeUninit<i32>,
|
||||
/// The last two digits of the calendar year.
|
||||
year_last_two: MaybeUninit<u8>,
|
||||
/// Year of the [ISO week date](https://en.wikipedia.org/wiki/ISO_week_date).
|
||||
iso_year: MaybeUninit<i32>,
|
||||
/// The last two digits of the ISO week year.
|
||||
iso_year_last_two: MaybeUninit<u8>,
|
||||
/// Month of the year.
|
||||
month: Option<Month>,
|
||||
/// Week of the year, where week one begins on the first Sunday of the calendar year.
|
||||
sunday_week_number: MaybeUninit<u8>,
|
||||
/// Week of the year, where week one begins on the first Monday of the calendar year.
|
||||
monday_week_number: MaybeUninit<u8>,
|
||||
/// Week of the year, where week one is the Monday-to-Sunday period containing January 4.
|
||||
iso_week_number: Option<NonZeroU8>,
|
||||
/// Day of the week.
|
||||
weekday: Option<Weekday>,
|
||||
/// Day of the year.
|
||||
ordinal: Option<NonZeroU16>,
|
||||
/// Day of the month.
|
||||
day: Option<NonZeroU8>,
|
||||
/// Hour within the day.
|
||||
hour_24: MaybeUninit<u8>,
|
||||
/// Hour within the 12-hour period (midnight to noon or vice versa). This is typically used in
|
||||
/// conjunction with AM/PM, which is indicated by the `hour_12_is_pm` field.
|
||||
hour_12: Option<NonZeroU8>,
|
||||
/// Whether the `hour_12` field indicates a time that "PM".
|
||||
hour_12_is_pm: Option<bool>,
|
||||
/// Minute within the hour.
|
||||
minute: MaybeUninit<u8>,
|
||||
/// Second within the minute.
|
||||
second: MaybeUninit<u8>,
|
||||
/// Nanosecond within the second.
|
||||
subsecond: MaybeUninit<u32>,
|
||||
/// Whole hours of the UTC offset.
|
||||
offset_hour: MaybeUninit<i8>,
|
||||
/// Minutes within the hour of the UTC offset.
|
||||
offset_minute: MaybeUninit<i8>,
|
||||
/// Seconds within the minute of the UTC offset.
|
||||
offset_second: MaybeUninit<i8>,
|
||||
}
|
||||
|
||||
#[allow(clippy::missing_docs_in_private_items)]
|
||||
impl Parsed {
|
||||
const YEAR_FLAG: u16 = 1 << 0;
|
||||
const YEAR_LAST_TWO_FLAG: u16 = 1 << 1;
|
||||
const ISO_YEAR_FLAG: u16 = 1 << 2;
|
||||
const ISO_YEAR_LAST_TWO_FLAG: u16 = 1 << 3;
|
||||
const SUNDAY_WEEK_NUMBER_FLAG: u16 = 1 << 4;
|
||||
const MONDAY_WEEK_NUMBER_FLAG: u16 = 1 << 5;
|
||||
const HOUR_24_FLAG: u16 = 1 << 6;
|
||||
const MINUTE_FLAG: u16 = 1 << 7;
|
||||
const SECOND_FLAG: u16 = 1 << 8;
|
||||
const SUBSECOND_FLAG: u16 = 1 << 9;
|
||||
const OFFSET_HOUR_FLAG: u16 = 1 << 10;
|
||||
const OFFSET_MINUTE_FLAG: u16 = 1 << 11;
|
||||
const OFFSET_SECOND_FLAG: u16 = 1 << 12;
|
||||
/// Indicates whether a leap second is permitted to be parsed. This is required by some
|
||||
/// well-known formats.
|
||||
const LEAP_SECOND_ALLOWED_FLAG: u16 = 1 << 13;
|
||||
}
|
||||
|
||||
impl Parsed {
|
||||
/// Create a new instance of `Parsed` with no information known.
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
flags: 0,
|
||||
year: MaybeUninit::uninit(),
|
||||
year_last_two: MaybeUninit::uninit(),
|
||||
iso_year: MaybeUninit::uninit(),
|
||||
iso_year_last_two: MaybeUninit::uninit(),
|
||||
month: None,
|
||||
sunday_week_number: MaybeUninit::uninit(),
|
||||
monday_week_number: MaybeUninit::uninit(),
|
||||
iso_week_number: None,
|
||||
weekday: None,
|
||||
ordinal: None,
|
||||
day: None,
|
||||
hour_24: MaybeUninit::uninit(),
|
||||
hour_12: None,
|
||||
hour_12_is_pm: None,
|
||||
minute: MaybeUninit::uninit(),
|
||||
second: MaybeUninit::uninit(),
|
||||
subsecond: MaybeUninit::uninit(),
|
||||
offset_hour: MaybeUninit::uninit(),
|
||||
offset_minute: MaybeUninit::uninit(),
|
||||
offset_second: MaybeUninit::uninit(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a single [`FormatItem`] or [`OwnedFormatItem`], mutating the struct. The remaining
|
||||
/// input is returned as the `Ok` value.
|
||||
///
|
||||
/// This method will fail if any of the contained [`FormatItem`]s fail to parse. `self` will not
|
||||
/// be mutated in this instance.
|
||||
/// If a [`FormatItem::Optional`] or [`OwnedFormatItem::Optional`] is passed, parsing will not
|
||||
/// fail; the input will be returned as-is if the expected format is not present.
|
||||
pub fn parse_item<'a>(
|
||||
&mut self,
|
||||
input: &'a [u8],
|
||||
item: &impl sealed::AnyFormatItem,
|
||||
) -> Result<&'a [u8], error::ParseFromDescription> {
|
||||
item.parse_item(self, input)
|
||||
}
|
||||
|
||||
/// Parse a sequence of [`FormatItem`]s or [`OwnedFormatItem`]s, mutating the struct. The
|
||||
/// remaining input is returned as the `Ok` value.
|
||||
///
|
||||
/// This method will fail if any of the contained [`FormatItem`]s or [`OwnedFormatItem`]s fail
|
||||
/// to parse. `self` will not be mutated in this instance.
|
||||
pub fn parse_items<'a>(
|
||||
&mut self,
|
||||
mut input: &'a [u8],
|
||||
items: &[FormatItem<'_>],
|
||||
items: &[impl sealed::AnyFormatItem],
|
||||
) -> Result<&'a [u8], error::ParseFromDescription> {
|
||||
// Make a copy that we can mutate. It will only be set to the user's copy if everything
|
||||
// succeeds.
|
||||
|
@ -253,35 +333,49 @@ impl Parsed {
|
|||
|
||||
/// Generate getters for each of the fields.
|
||||
macro_rules! getters {
|
||||
($($name:ident: $ty:ty),+ $(,)?) => {$(
|
||||
($($(@$flag:ident)? $name:ident: $ty:ty),+ $(,)?) => {$(
|
||||
getters!(! $(@$flag)? $name: $ty);
|
||||
)*};
|
||||
(! $name:ident : $ty:ty) => {
|
||||
/// Obtain the named component.
|
||||
pub const fn $name(&self) -> Option<$ty> {
|
||||
self.$name
|
||||
}
|
||||
)*}
|
||||
};
|
||||
(! @$flag:ident $name:ident : $ty:ty) => {
|
||||
/// Obtain the named component.
|
||||
pub const fn $name(&self) -> Option<$ty> {
|
||||
if self.flags & Self::$flag != Self::$flag {
|
||||
None
|
||||
} else {
|
||||
// SAFETY: We just checked if the field is present.
|
||||
Some(unsafe { self.$name.assume_init() })
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Getter methods
|
||||
impl Parsed {
|
||||
getters! {
|
||||
year: i32,
|
||||
year_last_two: u8,
|
||||
iso_year: i32,
|
||||
iso_year_last_two: u8,
|
||||
@YEAR_FLAG year: i32,
|
||||
@YEAR_LAST_TWO_FLAG year_last_two: u8,
|
||||
@ISO_YEAR_FLAG iso_year: i32,
|
||||
@ISO_YEAR_LAST_TWO_FLAG iso_year_last_two: u8,
|
||||
month: Month,
|
||||
sunday_week_number: u8,
|
||||
monday_week_number: u8,
|
||||
@SUNDAY_WEEK_NUMBER_FLAG sunday_week_number: u8,
|
||||
@MONDAY_WEEK_NUMBER_FLAG monday_week_number: u8,
|
||||
iso_week_number: NonZeroU8,
|
||||
weekday: Weekday,
|
||||
ordinal: NonZeroU16,
|
||||
day: NonZeroU8,
|
||||
hour_24: u8,
|
||||
@HOUR_24_FLAG hour_24: u8,
|
||||
hour_12: NonZeroU8,
|
||||
hour_12_is_pm: bool,
|
||||
minute: u8,
|
||||
second: u8,
|
||||
subsecond: u32,
|
||||
offset_hour: i8,
|
||||
@MINUTE_FLAG minute: u8,
|
||||
@SECOND_FLAG second: u8,
|
||||
@SUBSECOND_FLAG subsecond: u32,
|
||||
@OFFSET_HOUR_FLAG offset_hour: i8,
|
||||
}
|
||||
|
||||
/// Obtain the absolute value of the offset minute.
|
||||
|
@ -292,7 +386,12 @@ impl Parsed {
|
|||
|
||||
/// Obtain the offset minute as an `i8`.
|
||||
pub const fn offset_minute_signed(&self) -> Option<i8> {
|
||||
self.offset_minute
|
||||
if self.flags & Self::OFFSET_MINUTE_FLAG != Self::OFFSET_MINUTE_FLAG {
|
||||
None
|
||||
} else {
|
||||
// SAFETY: We just checked if the field is present.
|
||||
Some(unsafe { self.offset_minute.assume_init() })
|
||||
}
|
||||
}
|
||||
|
||||
/// Obtain the absolute value of the offset second.
|
||||
|
@ -303,12 +402,17 @@ impl Parsed {
|
|||
|
||||
/// Obtain the offset second as an `i8`.
|
||||
pub const fn offset_second_signed(&self) -> Option<i8> {
|
||||
self.offset_second
|
||||
if self.flags & Self::OFFSET_SECOND_FLAG != Self::OFFSET_SECOND_FLAG {
|
||||
None
|
||||
} else {
|
||||
// SAFETY: We just checked if the field is present.
|
||||
Some(unsafe { self.offset_second.assume_init() })
|
||||
}
|
||||
}
|
||||
|
||||
/// Obtain whether leap seconds are permitted in the current format.
|
||||
pub(crate) const fn leap_second_allowed(&self) -> bool {
|
||||
self.leap_second_allowed
|
||||
self.flags & Self::LEAP_SECOND_ALLOWED_FLAG == Self::LEAP_SECOND_ALLOWED_FLAG
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -316,13 +420,24 @@ impl Parsed {
|
|||
///
|
||||
/// This macro should only be used for fields where the value is not validated beyond its type.
|
||||
macro_rules! setters {
|
||||
($($setter_name:ident $name:ident: $ty:ty),+ $(,)?) => {$(
|
||||
($($(@$flag:ident)? $setter_name:ident $name:ident: $ty:ty),+ $(,)?) => {$(
|
||||
setters!(! $(@$flag)? $setter_name $name: $ty);
|
||||
)*};
|
||||
(! $setter_name:ident $name:ident : $ty:ty) => {
|
||||
/// Set the named component.
|
||||
pub fn $setter_name(&mut self, value: $ty) -> Option<()> {
|
||||
self.$name = Some(value);
|
||||
Some(())
|
||||
}
|
||||
)*}
|
||||
};
|
||||
(! @$flag:ident $setter_name:ident $name:ident : $ty:ty) => {
|
||||
/// Set the named component.
|
||||
pub fn $setter_name(&mut self, value: $ty) -> Option<()> {
|
||||
self.$name = MaybeUninit::new(value);
|
||||
self.flags |= Self::$flag;
|
||||
Some(())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Setter methods
|
||||
|
@ -331,24 +446,24 @@ macro_rules! setters {
|
|||
/// setters _may_ fail if the value is invalid, though behavior is not guaranteed.
|
||||
impl Parsed {
|
||||
setters! {
|
||||
set_year year: i32,
|
||||
set_year_last_two year_last_two: u8,
|
||||
set_iso_year iso_year: i32,
|
||||
set_iso_year_last_two iso_year_last_two: u8,
|
||||
@YEAR_FLAG set_year year: i32,
|
||||
@YEAR_LAST_TWO_FLAG set_year_last_two year_last_two: u8,
|
||||
@ISO_YEAR_FLAG set_iso_year iso_year: i32,
|
||||
@ISO_YEAR_LAST_TWO_FLAG set_iso_year_last_two iso_year_last_two: u8,
|
||||
set_month month: Month,
|
||||
set_sunday_week_number sunday_week_number: u8,
|
||||
set_monday_week_number monday_week_number: u8,
|
||||
@SUNDAY_WEEK_NUMBER_FLAG set_sunday_week_number sunday_week_number: u8,
|
||||
@MONDAY_WEEK_NUMBER_FLAG set_monday_week_number monday_week_number: u8,
|
||||
set_iso_week_number iso_week_number: NonZeroU8,
|
||||
set_weekday weekday: Weekday,
|
||||
set_ordinal ordinal: NonZeroU16,
|
||||
set_day day: NonZeroU8,
|
||||
set_hour_24 hour_24: u8,
|
||||
@HOUR_24_FLAG set_hour_24 hour_24: u8,
|
||||
set_hour_12 hour_12: NonZeroU8,
|
||||
set_hour_12_is_pm hour_12_is_pm: bool,
|
||||
set_minute minute: u8,
|
||||
set_second second: u8,
|
||||
set_subsecond subsecond: u32,
|
||||
set_offset_hour offset_hour: i8,
|
||||
@MINUTE_FLAG set_minute minute: u8,
|
||||
@SECOND_FLAG set_second second: u8,
|
||||
@SUBSECOND_FLAG set_subsecond subsecond: u32,
|
||||
@OFFSET_HOUR_FLAG set_offset_hour offset_hour: i8,
|
||||
}
|
||||
|
||||
/// Set the named component.
|
||||
|
@ -366,7 +481,8 @@ impl Parsed {
|
|||
|
||||
/// Set the `offset_minute` component.
|
||||
pub fn set_offset_minute_signed(&mut self, value: i8) -> Option<()> {
|
||||
self.offset_minute = Some(value);
|
||||
self.offset_minute = MaybeUninit::new(value);
|
||||
self.flags |= Self::OFFSET_MINUTE_FLAG;
|
||||
Some(())
|
||||
}
|
||||
|
||||
|
@ -385,13 +501,18 @@ impl Parsed {
|
|||
|
||||
/// Set the `offset_second` component.
|
||||
pub fn set_offset_second_signed(&mut self, value: i8) -> Option<()> {
|
||||
self.offset_second = Some(value);
|
||||
self.offset_second = MaybeUninit::new(value);
|
||||
self.flags |= Self::OFFSET_SECOND_FLAG;
|
||||
Some(())
|
||||
}
|
||||
|
||||
/// Set the leap second allowed flag.
|
||||
pub(crate) fn set_leap_second_allowed(&mut self, value: bool) {
|
||||
self.leap_second_allowed = value;
|
||||
if value {
|
||||
self.flags |= Self::LEAP_SECOND_ALLOWED_FLAG;
|
||||
} else {
|
||||
self.flags &= !Self::LEAP_SECOND_ALLOWED_FLAG;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -399,13 +520,24 @@ impl Parsed {
|
|||
///
|
||||
/// This macro should only be used for fields where the value is not validated beyond its type.
|
||||
macro_rules! builders {
|
||||
($($builder_name:ident $name:ident: $ty:ty),+ $(,)?) => {$(
|
||||
($($(@$flag:ident)? $builder_name:ident $name:ident: $ty:ty),+ $(,)?) => {$(
|
||||
builders!(! $(@$flag)? $builder_name $name: $ty);
|
||||
)*};
|
||||
(! $builder_name:ident $name:ident : $ty:ty) => {
|
||||
/// Set the named component and return `self`.
|
||||
pub const fn $builder_name(mut self, value: $ty) -> Option<Self> {
|
||||
self.$name = Some(value);
|
||||
Some(self)
|
||||
}
|
||||
)*}
|
||||
};
|
||||
(! @$flag:ident $builder_name:ident $name:ident : $ty:ty) => {
|
||||
/// Set the named component and return `self`.
|
||||
pub const fn $builder_name(mut self, value: $ty) -> Option<Self> {
|
||||
self.$name = MaybeUninit::new(value);
|
||||
self.flags |= Self::$flag;
|
||||
Some(self)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Builder methods
|
||||
|
@ -414,24 +546,24 @@ macro_rules! builders {
|
|||
/// not. The builder methods _may_ fail if the value is invalid, though behavior is not guaranteed.
|
||||
impl Parsed {
|
||||
builders! {
|
||||
with_year year: i32,
|
||||
with_year_last_two year_last_two: u8,
|
||||
with_iso_year iso_year: i32,
|
||||
with_iso_year_last_two iso_year_last_two: u8,
|
||||
@YEAR_FLAG with_year year: i32,
|
||||
@YEAR_LAST_TWO_FLAG with_year_last_two year_last_two: u8,
|
||||
@ISO_YEAR_FLAG with_iso_year iso_year: i32,
|
||||
@ISO_YEAR_LAST_TWO_FLAG with_iso_year_last_two iso_year_last_two: u8,
|
||||
with_month month: Month,
|
||||
with_sunday_week_number sunday_week_number: u8,
|
||||
with_monday_week_number monday_week_number: u8,
|
||||
@SUNDAY_WEEK_NUMBER_FLAG with_sunday_week_number sunday_week_number: u8,
|
||||
@MONDAY_WEEK_NUMBER_FLAG with_monday_week_number monday_week_number: u8,
|
||||
with_iso_week_number iso_week_number: NonZeroU8,
|
||||
with_weekday weekday: Weekday,
|
||||
with_ordinal ordinal: NonZeroU16,
|
||||
with_day day: NonZeroU8,
|
||||
with_hour_24 hour_24: u8,
|
||||
@HOUR_24_FLAG with_hour_24 hour_24: u8,
|
||||
with_hour_12 hour_12: NonZeroU8,
|
||||
with_hour_12_is_pm hour_12_is_pm: bool,
|
||||
with_minute minute: u8,
|
||||
with_second second: u8,
|
||||
with_subsecond subsecond: u32,
|
||||
with_offset_hour offset_hour: i8,
|
||||
@MINUTE_FLAG with_minute minute: u8,
|
||||
@SECOND_FLAG with_second second: u8,
|
||||
@SUBSECOND_FLAG with_subsecond subsecond: u32,
|
||||
@OFFSET_HOUR_FLAG with_offset_hour offset_hour: i8,
|
||||
}
|
||||
|
||||
/// Set the named component and return `self`.
|
||||
|
@ -449,7 +581,8 @@ impl Parsed {
|
|||
|
||||
/// Set the `offset_minute` component and return `self`.
|
||||
pub const fn with_offset_minute_signed(mut self, value: i8) -> Option<Self> {
|
||||
self.offset_minute = Some(value);
|
||||
self.offset_minute = MaybeUninit::new(value);
|
||||
self.flags |= Self::OFFSET_MINUTE_FLAG;
|
||||
Some(self)
|
||||
}
|
||||
|
||||
|
@ -468,7 +601,8 @@ impl Parsed {
|
|||
|
||||
/// Set the `offset_second` component and return `self`.
|
||||
pub const fn with_offset_second_signed(mut self, value: i8) -> Option<Self> {
|
||||
self.offset_second = Some(value);
|
||||
self.offset_second = MaybeUninit::new(value);
|
||||
self.flags |= Self::OFFSET_SECOND_FLAG;
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ use crate::parsing::Parsable;
|
|||
use crate::{error, util, Date, Duration, Month, OffsetDateTime, Time, UtcOffset, Weekday};
|
||||
|
||||
/// Combined date and time.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct PrimitiveDateTime {
|
||||
#[allow(clippy::missing_docs_in_private_items)]
|
||||
pub(crate) date: Date,
|
||||
|
@ -26,13 +26,28 @@ impl PrimitiveDateTime {
|
|||
///
|
||||
/// Depending on `large-dates` feature flag, value of this constant may vary.
|
||||
///
|
||||
/// 1. With `large-dates` disabled it is equal to `-9999 - 01 - 01 00:00:00.0`
|
||||
/// 2. With `large-dates` enabled it is equal to `-999999 - 01 - 01 00:00:00.0`
|
||||
/// 1. With `large-dates` disabled it is equal to `-9999-01-01 00:00:00.0`
|
||||
/// 2. With `large-dates` enabled it is equal to `-999999-01-01 00:00:00.0`
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::{PrimitiveDateTime, macros::datetime};
|
||||
/// // Assuming `large-dates` feature is enabled.
|
||||
/// assert_eq!(PrimitiveDateTime::MIN, datetime!(-999999 - 01 - 01 0:00));
|
||||
/// # use time::PrimitiveDateTime;
|
||||
/// # use time_macros::datetime;
|
||||
#[cfg_attr(
|
||||
feature = "large-dates",
|
||||
doc = "// Assuming `large-dates` feature is enabled."
|
||||
)]
|
||||
#[cfg_attr(
|
||||
feature = "large-dates",
|
||||
doc = "assert_eq!(PrimitiveDateTime::MIN, datetime!(-999999-01-01 0:00));"
|
||||
)]
|
||||
#[cfg_attr(
|
||||
not(feature = "large-dates"),
|
||||
doc = "// Assuming `large-dates` feature is disabled."
|
||||
)]
|
||||
#[cfg_attr(
|
||||
not(feature = "large-dates"),
|
||||
doc = "assert_eq!(PrimitiveDateTime::MIN, datetime!(-9999-01-01 0:00));"
|
||||
)]
|
||||
/// ```
|
||||
pub const MIN: Self = Self::new(Date::MIN, Time::MIN);
|
||||
|
||||
|
@ -40,20 +55,36 @@ impl PrimitiveDateTime {
|
|||
///
|
||||
/// Depending on `large-dates` feature flag, value of this constant may vary.
|
||||
///
|
||||
/// 1. With `large-dates` disabled it is equal to `9999 - 12 - 31 23:59:59.999_999_999`
|
||||
/// 2. With `large-dates` enabled it is equal to `999999 - 12 - 31 23:59:59.999_999_999`
|
||||
/// 1. With `large-dates` disabled it is equal to `9999-12-31 23:59:59.999_999_999`
|
||||
/// 2. With `large-dates` enabled it is equal to `999999-12-31 23:59:59.999_999_999`
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::{PrimitiveDateTime, macros::datetime};
|
||||
/// // Assuming `large-dates` feature is enabled.
|
||||
/// assert_eq!(PrimitiveDateTime::MAX, datetime!(+999999 - 12 - 31 23:59:59.999_999_999));
|
||||
/// # use time::PrimitiveDateTime;
|
||||
/// # use time_macros::datetime;
|
||||
#[cfg_attr(
|
||||
feature = "large-dates",
|
||||
doc = "// Assuming `large-dates` feature is enabled."
|
||||
)]
|
||||
#[cfg_attr(
|
||||
feature = "large-dates",
|
||||
doc = "assert_eq!(PrimitiveDateTime::MAX, datetime!(+999999-12-31 23:59:59.999_999_999));"
|
||||
)]
|
||||
#[cfg_attr(
|
||||
not(feature = "large-dates"),
|
||||
doc = "// Assuming `large-dates` feature is disabled."
|
||||
)]
|
||||
#[cfg_attr(
|
||||
not(feature = "large-dates"),
|
||||
doc = "assert_eq!(PrimitiveDateTime::MAX, datetime!(+9999-12-31 23:59:59.999_999_999));"
|
||||
)]
|
||||
/// ```
|
||||
pub const MAX: Self = Self::new(Date::MAX, Time::MAX);
|
||||
|
||||
/// Create a new `PrimitiveDateTime` from the provided [`Date`] and [`Time`].
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::{PrimitiveDateTime, macros::{date, datetime, time}};
|
||||
/// # use time::PrimitiveDateTime;
|
||||
/// # use time_macros::{date, datetime, time};
|
||||
/// assert_eq!(
|
||||
/// PrimitiveDateTime::new(date!(2019-01-01), time!(0:00)),
|
||||
/// datetime!(2019-01-01 0:00),
|
||||
|
@ -67,7 +98,7 @@ impl PrimitiveDateTime {
|
|||
/// Get the [`Date`] component of the `PrimitiveDateTime`.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::{date, datetime};
|
||||
/// # use time_macros::{date, datetime};
|
||||
/// assert_eq!(datetime!(2019-01-01 0:00).date(), date!(2019-01-01));
|
||||
/// ```
|
||||
pub const fn date(self) -> Date {
|
||||
|
@ -77,7 +108,7 @@ impl PrimitiveDateTime {
|
|||
/// Get the [`Time`] component of the `PrimitiveDateTime`.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::{datetime, time};
|
||||
/// # use time_macros::{datetime, time};
|
||||
/// assert_eq!(datetime!(2019-01-01 0:00).time(), time!(0:00));
|
||||
pub const fn time(self) -> Time {
|
||||
self.time
|
||||
|
@ -88,7 +119,7 @@ impl PrimitiveDateTime {
|
|||
/// Get the year of the date.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::datetime;
|
||||
/// # use time_macros::datetime;
|
||||
/// assert_eq!(datetime!(2019-01-01 0:00).year(), 2019);
|
||||
/// assert_eq!(datetime!(2019-12-31 0:00).year(), 2019);
|
||||
/// assert_eq!(datetime!(2020-01-01 0:00).year(), 2020);
|
||||
|
@ -100,7 +131,8 @@ impl PrimitiveDateTime {
|
|||
/// Get the month of the date.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::{macros::datetime, Month};
|
||||
/// # use time::Month;
|
||||
/// # use time_macros::datetime;
|
||||
/// assert_eq!(datetime!(2019-01-01 0:00).month(), Month::January);
|
||||
/// assert_eq!(datetime!(2019-12-31 0:00).month(), Month::December);
|
||||
/// ```
|
||||
|
@ -113,7 +145,7 @@ impl PrimitiveDateTime {
|
|||
/// The returned value will always be in the range `1..=31`.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::datetime;
|
||||
/// # use time_macros::datetime;
|
||||
/// assert_eq!(datetime!(2019-01-01 0:00).day(), 1);
|
||||
/// assert_eq!(datetime!(2019-12-31 0:00).day(), 31);
|
||||
/// ```
|
||||
|
@ -126,7 +158,7 @@ impl PrimitiveDateTime {
|
|||
/// The returned value will always be in the range `1..=366` (`1..=365` for common years).
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::datetime;
|
||||
/// # use time_macros::datetime;
|
||||
/// assert_eq!(datetime!(2019-01-01 0:00).ordinal(), 1);
|
||||
/// assert_eq!(datetime!(2019-12-31 0:00).ordinal(), 365);
|
||||
/// ```
|
||||
|
@ -139,7 +171,7 @@ impl PrimitiveDateTime {
|
|||
/// The returned value will always be in the range `1..=53`.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::datetime;
|
||||
/// # use time_macros::datetime;
|
||||
/// assert_eq!(datetime!(2019-01-01 0:00).iso_week(), 1);
|
||||
/// assert_eq!(datetime!(2019-10-04 0:00).iso_week(), 40);
|
||||
/// assert_eq!(datetime!(2020-01-01 0:00).iso_week(), 1);
|
||||
|
@ -155,7 +187,7 @@ impl PrimitiveDateTime {
|
|||
/// The returned value will always be in the range `0..=53`.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::datetime;
|
||||
/// # use time_macros::datetime;
|
||||
/// assert_eq!(datetime!(2019-01-01 0:00).sunday_based_week(), 0);
|
||||
/// assert_eq!(datetime!(2020-01-01 0:00).sunday_based_week(), 0);
|
||||
/// assert_eq!(datetime!(2020-12-31 0:00).sunday_based_week(), 52);
|
||||
|
@ -170,7 +202,7 @@ impl PrimitiveDateTime {
|
|||
/// The returned value will always be in the range `0..=53`.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::datetime;
|
||||
/// # use time_macros::datetime;
|
||||
/// assert_eq!(datetime!(2019-01-01 0:00).monday_based_week(), 0);
|
||||
/// assert_eq!(datetime!(2020-01-01 0:00).monday_based_week(), 0);
|
||||
/// assert_eq!(datetime!(2020-12-31 0:00).monday_based_week(), 52);
|
||||
|
@ -183,7 +215,8 @@ impl PrimitiveDateTime {
|
|||
/// Get the year, month, and day.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::{macros::datetime, Month};
|
||||
/// # use time::Month;
|
||||
/// # use time_macros::datetime;
|
||||
/// assert_eq!(
|
||||
/// datetime!(2019-01-01 0:00).to_calendar_date(),
|
||||
/// (2019, Month::January, 1)
|
||||
|
@ -196,7 +229,7 @@ impl PrimitiveDateTime {
|
|||
/// Get the year and ordinal day number.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::datetime;
|
||||
/// # use time_macros::datetime;
|
||||
/// assert_eq!(datetime!(2019-01-01 0:00).to_ordinal_date(), (2019, 1));
|
||||
/// ```
|
||||
pub const fn to_ordinal_date(self) -> (i32, u16) {
|
||||
|
@ -206,7 +239,8 @@ impl PrimitiveDateTime {
|
|||
/// Get the ISO 8601 year, week number, and weekday.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::{Weekday::*, macros::datetime};
|
||||
/// # use time::Weekday::*;
|
||||
/// # use time_macros::datetime;
|
||||
/// assert_eq!(
|
||||
/// datetime!(2019-01-01 0:00).to_iso_week_date(),
|
||||
/// (2019, 1, Tuesday)
|
||||
|
@ -235,7 +269,8 @@ impl PrimitiveDateTime {
|
|||
/// Get the weekday.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::{Weekday::*, macros::datetime};
|
||||
/// # use time::Weekday::*;
|
||||
/// # use time_macros::datetime;
|
||||
/// assert_eq!(datetime!(2019-01-01 0:00).weekday(), Tuesday);
|
||||
/// assert_eq!(datetime!(2019-02-01 0:00).weekday(), Friday);
|
||||
/// assert_eq!(datetime!(2019-03-01 0:00).weekday(), Friday);
|
||||
|
@ -259,7 +294,7 @@ impl PrimitiveDateTime {
|
|||
/// freely available [here](https://www.researchgate.net/publication/316558298_Date_Algorithms).
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::datetime;
|
||||
/// # use time_macros::datetime;
|
||||
/// assert_eq!(datetime!(-4713-11-24 0:00).to_julian_day(), 0);
|
||||
/// assert_eq!(datetime!(2000-01-01 0:00).to_julian_day(), 2_451_545);
|
||||
/// assert_eq!(datetime!(2019-01-01 0:00).to_julian_day(), 2_458_485);
|
||||
|
@ -274,7 +309,7 @@ impl PrimitiveDateTime {
|
|||
/// Get the clock hour, minute, and second.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::datetime;
|
||||
/// # use time_macros::datetime;
|
||||
/// assert_eq!(datetime!(2020-01-01 0:00:00).as_hms(), (0, 0, 0));
|
||||
/// assert_eq!(datetime!(2020-01-01 23:59:59).as_hms(), (23, 59, 59));
|
||||
/// ```
|
||||
|
@ -285,7 +320,7 @@ impl PrimitiveDateTime {
|
|||
/// Get the clock hour, minute, second, and millisecond.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::datetime;
|
||||
/// # use time_macros::datetime;
|
||||
/// assert_eq!(datetime!(2020-01-01 0:00:00).as_hms_milli(), (0, 0, 0, 0));
|
||||
/// assert_eq!(
|
||||
/// datetime!(2020-01-01 23:59:59.999).as_hms_milli(),
|
||||
|
@ -299,7 +334,7 @@ impl PrimitiveDateTime {
|
|||
/// Get the clock hour, minute, second, and microsecond.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::datetime;
|
||||
/// # use time_macros::datetime;
|
||||
/// assert_eq!(datetime!(2020-01-01 0:00:00).as_hms_micro(), (0, 0, 0, 0));
|
||||
/// assert_eq!(
|
||||
/// datetime!(2020-01-01 23:59:59.999_999).as_hms_micro(),
|
||||
|
@ -313,7 +348,7 @@ impl PrimitiveDateTime {
|
|||
/// Get the clock hour, minute, second, and nanosecond.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::datetime;
|
||||
/// # use time_macros::datetime;
|
||||
/// assert_eq!(datetime!(2020-01-01 0:00:00).as_hms_nano(), (0, 0, 0, 0));
|
||||
/// assert_eq!(
|
||||
/// datetime!(2020-01-01 23:59:59.999_999_999).as_hms_nano(),
|
||||
|
@ -329,7 +364,7 @@ impl PrimitiveDateTime {
|
|||
/// The returned value will always be in the range `0..24`.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::datetime;
|
||||
/// # use time_macros::datetime;
|
||||
/// assert_eq!(datetime!(2019-01-01 0:00).hour(), 0);
|
||||
/// assert_eq!(datetime!(2019-01-01 23:59:59).hour(), 23);
|
||||
/// ```
|
||||
|
@ -342,7 +377,7 @@ impl PrimitiveDateTime {
|
|||
/// The returned value will always be in the range `0..60`.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::datetime;
|
||||
/// # use time_macros::datetime;
|
||||
/// assert_eq!(datetime!(2019-01-01 0:00).minute(), 0);
|
||||
/// assert_eq!(datetime!(2019-01-01 23:59:59).minute(), 59);
|
||||
/// ```
|
||||
|
@ -355,7 +390,7 @@ impl PrimitiveDateTime {
|
|||
/// The returned value will always be in the range `0..60`.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::datetime;
|
||||
/// # use time_macros::datetime;
|
||||
/// assert_eq!(datetime!(2019-01-01 0:00).second(), 0);
|
||||
/// assert_eq!(datetime!(2019-01-01 23:59:59).second(), 59);
|
||||
/// ```
|
||||
|
@ -368,7 +403,7 @@ impl PrimitiveDateTime {
|
|||
/// The returned value will always be in the range `0..1_000`.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::datetime;
|
||||
/// # use time_macros::datetime;
|
||||
/// assert_eq!(datetime!(2019-01-01 0:00).millisecond(), 0);
|
||||
/// assert_eq!(datetime!(2019-01-01 23:59:59.999).millisecond(), 999);
|
||||
/// ```
|
||||
|
@ -381,7 +416,7 @@ impl PrimitiveDateTime {
|
|||
/// The returned value will always be in the range `0..1_000_000`.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::datetime;
|
||||
/// # use time_macros::datetime;
|
||||
/// assert_eq!(datetime!(2019-01-01 0:00).microsecond(), 0);
|
||||
/// assert_eq!(
|
||||
/// datetime!(2019-01-01 23:59:59.999_999).microsecond(),
|
||||
|
@ -397,7 +432,7 @@ impl PrimitiveDateTime {
|
|||
/// The returned value will always be in the range `0..1_000_000_000`.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::datetime;
|
||||
/// # use time_macros::datetime;
|
||||
/// assert_eq!(datetime!(2019-01-01 0:00).nanosecond(), 0);
|
||||
/// assert_eq!(
|
||||
/// datetime!(2019-01-01 23:59:59.999_999_999).nanosecond(),
|
||||
|
@ -414,7 +449,7 @@ impl PrimitiveDateTime {
|
|||
/// [`UtcOffset`], return an [`OffsetDateTime`].
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::{datetime, offset};
|
||||
/// # use time_macros::{datetime, offset};
|
||||
/// assert_eq!(
|
||||
/// datetime!(2019-01-01 0:00)
|
||||
/// .assume_offset(offset!(UTC))
|
||||
|
@ -430,7 +465,7 @@ impl PrimitiveDateTime {
|
|||
/// ```
|
||||
pub const fn assume_offset(self, offset: UtcOffset) -> OffsetDateTime {
|
||||
OffsetDateTime {
|
||||
utc_datetime: self.offset_to_utc(offset),
|
||||
local_datetime: self,
|
||||
offset,
|
||||
}
|
||||
}
|
||||
|
@ -439,17 +474,14 @@ impl PrimitiveDateTime {
|
|||
/// [`OffsetDateTime`].
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::datetime;
|
||||
/// # use time_macros::datetime;
|
||||
/// assert_eq!(
|
||||
/// datetime!(2019-01-01 0:00).assume_utc().unix_timestamp(),
|
||||
/// 1_546_300_800,
|
||||
/// );
|
||||
/// ```
|
||||
pub const fn assume_utc(self) -> OffsetDateTime {
|
||||
OffsetDateTime {
|
||||
utc_datetime: self,
|
||||
offset: UtcOffset::UTC,
|
||||
}
|
||||
self.assume_offset(UtcOffset::UTC)
|
||||
}
|
||||
// endregion attach offset
|
||||
|
||||
|
@ -458,7 +490,7 @@ impl PrimitiveDateTime {
|
|||
///
|
||||
/// ```
|
||||
/// # use time::{Date, ext::NumericalDuration};
|
||||
/// # use time::macros::datetime;
|
||||
/// # use time_macros::datetime;
|
||||
/// let datetime = Date::MIN.midnight();
|
||||
/// assert_eq!(datetime.checked_add((-2).days()), None);
|
||||
///
|
||||
|
@ -488,7 +520,7 @@ impl PrimitiveDateTime {
|
|||
///
|
||||
/// ```
|
||||
/// # use time::{Date, ext::NumericalDuration};
|
||||
/// # use time::macros::datetime;
|
||||
/// # use time_macros::datetime;
|
||||
/// let datetime = Date::MIN.midnight();
|
||||
/// assert_eq!(datetime.checked_sub(2.days()), None);
|
||||
///
|
||||
|
@ -520,7 +552,7 @@ impl PrimitiveDateTime {
|
|||
///
|
||||
/// ```
|
||||
/// # use time::{PrimitiveDateTime, ext::NumericalDuration};
|
||||
/// # use time::macros::datetime;
|
||||
/// # use time_macros::datetime;
|
||||
/// assert_eq!(
|
||||
/// PrimitiveDateTime::MIN.saturating_add((-2).days()),
|
||||
/// PrimitiveDateTime::MIN
|
||||
|
@ -550,7 +582,7 @@ impl PrimitiveDateTime {
|
|||
///
|
||||
/// ```
|
||||
/// # use time::{PrimitiveDateTime, ext::NumericalDuration};
|
||||
/// # use time::macros::datetime;
|
||||
/// # use time_macros::datetime;
|
||||
/// assert_eq!(
|
||||
/// PrimitiveDateTime::MIN.saturating_sub(2.days()),
|
||||
/// PrimitiveDateTime::MIN
|
||||
|
@ -584,7 +616,7 @@ impl PrimitiveDateTime {
|
|||
/// Replace the time, preserving the date.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::{datetime, time};
|
||||
/// # use time_macros::{datetime, time};
|
||||
/// assert_eq!(
|
||||
/// datetime!(2020-01-01 17:00).replace_time(time!(5:00)),
|
||||
/// datetime!(2020-01-01 5:00)
|
||||
|
@ -598,7 +630,7 @@ impl PrimitiveDateTime {
|
|||
/// Replace the date, preserving the time.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::{datetime, date};
|
||||
/// # use time_macros::{datetime, date};
|
||||
/// assert_eq!(
|
||||
/// datetime!(2020-01-01 12:00).replace_date(date!(2020-01-30)),
|
||||
/// datetime!(2020-01-30 12:00)
|
||||
|
@ -612,7 +644,7 @@ impl PrimitiveDateTime {
|
|||
/// Replace the year. The month and day will be unchanged.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::datetime;
|
||||
/// # use time_macros::datetime;
|
||||
/// assert_eq!(
|
||||
/// datetime!(2022 - 02 - 18 12:00).replace_year(2019),
|
||||
/// Ok(datetime!(2019 - 02 - 18 12:00))
|
||||
|
@ -628,7 +660,7 @@ impl PrimitiveDateTime {
|
|||
/// Replace the month of the year.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::datetime;
|
||||
/// # use time_macros::datetime;
|
||||
/// # use time::Month;
|
||||
/// assert_eq!(
|
||||
/// datetime!(2022 - 02 - 18 12:00).replace_month(Month::January),
|
||||
|
@ -644,7 +676,7 @@ impl PrimitiveDateTime {
|
|||
/// Replace the day of the month.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::datetime;
|
||||
/// # use time_macros::datetime;
|
||||
/// assert_eq!(
|
||||
/// datetime!(2022 - 02 - 18 12:00).replace_day(1),
|
||||
/// Ok(datetime!(2022 - 02 - 01 12:00))
|
||||
|
@ -660,7 +692,7 @@ impl PrimitiveDateTime {
|
|||
/// Replace the clock hour.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::datetime;
|
||||
/// # use time_macros::datetime;
|
||||
/// assert_eq!(
|
||||
/// datetime!(2022 - 02 - 18 01:02:03.004_005_006).replace_hour(7),
|
||||
/// Ok(datetime!(2022 - 02 - 18 07:02:03.004_005_006))
|
||||
|
@ -677,7 +709,7 @@ impl PrimitiveDateTime {
|
|||
/// Replace the minutes within the hour.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::datetime;
|
||||
/// # use time_macros::datetime;
|
||||
/// assert_eq!(
|
||||
/// datetime!(2022 - 02 - 18 01:02:03.004_005_006).replace_minute(7),
|
||||
/// Ok(datetime!(2022 - 02 - 18 01:07:03.004_005_006))
|
||||
|
@ -694,7 +726,7 @@ impl PrimitiveDateTime {
|
|||
/// Replace the seconds within the minute.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::datetime;
|
||||
/// # use time_macros::datetime;
|
||||
/// assert_eq!(
|
||||
/// datetime!(2022 - 02 - 18 01:02:03.004_005_006).replace_second(7),
|
||||
/// Ok(datetime!(2022 - 02 - 18 01:02:07.004_005_006))
|
||||
|
@ -711,7 +743,7 @@ impl PrimitiveDateTime {
|
|||
/// Replace the milliseconds within the second.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::datetime;
|
||||
/// # use time_macros::datetime;
|
||||
/// assert_eq!(
|
||||
/// datetime!(2022 - 02 - 18 01:02:03.004_005_006).replace_millisecond(7),
|
||||
/// Ok(datetime!(2022 - 02 - 18 01:02:03.007))
|
||||
|
@ -731,7 +763,7 @@ impl PrimitiveDateTime {
|
|||
/// Replace the microseconds within the second.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::datetime;
|
||||
/// # use time_macros::datetime;
|
||||
/// assert_eq!(
|
||||
/// datetime!(2022 - 02 - 18 01:02:03.004_005_006).replace_microsecond(7_008),
|
||||
/// Ok(datetime!(2022 - 02 - 18 01:02:03.007_008))
|
||||
|
@ -751,7 +783,7 @@ impl PrimitiveDateTime {
|
|||
/// Replace the nanoseconds within the second.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::datetime;
|
||||
/// # use time_macros::datetime;
|
||||
/// assert_eq!(
|
||||
/// datetime!(2022 - 02 - 18 01:02:03.004_005_006).replace_nanosecond(7_008_009),
|
||||
/// Ok(datetime!(2022 - 02 - 18 01:02:03.007_008_009))
|
||||
|
@ -767,45 +799,6 @@ impl PrimitiveDateTime {
|
|||
}
|
||||
// endregion replacement
|
||||
|
||||
// region: offset conversion helpers
|
||||
/// Helper methods to adjust a [`PrimitiveDateTime`] to a given [`UtcOffset`].
|
||||
impl PrimitiveDateTime {
|
||||
/// Assuming that the current [`PrimitiveDateTime`] is a value in the provided [`UtcOffset`],
|
||||
/// obtain the equivalent value in the UTC.
|
||||
pub(crate) const fn offset_to_utc(self, offset: UtcOffset) -> Self {
|
||||
let mut second = self.second() as i8 - offset.seconds_past_minute();
|
||||
let mut minute = self.minute() as i8 - offset.minutes_past_hour();
|
||||
let mut hour = self.hour() as i8 - offset.whole_hours();
|
||||
let (mut year, mut ordinal) = self.date.to_ordinal_date();
|
||||
|
||||
cascade!(second in 0..60 => minute);
|
||||
cascade!(minute in 0..60 => hour);
|
||||
cascade!(hour in 0..24 => ordinal);
|
||||
cascade!(ordinal => year);
|
||||
|
||||
Self {
|
||||
date: Date::__from_ordinal_date_unchecked(year, ordinal),
|
||||
time: Time::__from_hms_nanos_unchecked(
|
||||
hour as _,
|
||||
minute as _,
|
||||
second as _,
|
||||
self.nanosecond(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// Assuming that the current [`PrimitiveDateTime`] is a value in UTC, obtain the equivalent
|
||||
/// value in the provided [`UtcOffset`].
|
||||
pub(crate) const fn utc_to_offset(self, offset: UtcOffset) -> Self {
|
||||
self.offset_to_utc(UtcOffset::__from_hms_unchecked(
|
||||
-offset.whole_hours(),
|
||||
-offset.minutes_past_hour(),
|
||||
-offset.seconds_past_minute(),
|
||||
))
|
||||
}
|
||||
}
|
||||
// endregion offset conversion helpers
|
||||
|
||||
// region: formatting & parsing
|
||||
#[cfg(feature = "formatting")]
|
||||
impl PrimitiveDateTime {
|
||||
|
@ -823,7 +816,8 @@ impl PrimitiveDateTime {
|
|||
/// description](crate::format_description).
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::{format_description, macros::datetime};
|
||||
/// # use time::format_description;
|
||||
/// # use time_macros::datetime;
|
||||
/// let format = format_description::parse("[year]-[month]-[day] [hour]:[minute]:[second]")?;
|
||||
/// assert_eq!(
|
||||
/// datetime!(2020-01-02 03:04:05).format(&format)?,
|
||||
|
@ -842,8 +836,9 @@ impl PrimitiveDateTime {
|
|||
/// description](crate::format_description).
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::{format_description, macros::datetime, PrimitiveDateTime};
|
||||
/// let format = format_description::parse("[year]-[month]-[day] [hour]:[minute]:[second]")?;
|
||||
/// # use time::PrimitiveDateTime;
|
||||
/// # use time_macros::{datetime, format_description};
|
||||
/// let format = format_description!("[year]-[month]-[day] [hour]:[minute]:[second]");
|
||||
/// assert_eq!(
|
||||
/// PrimitiveDateTime::parse("2020-01-02 03:04:05", &format)?,
|
||||
/// datetime!(2020-01-02 03:04:05)
|
||||
|
@ -863,6 +858,12 @@ impl fmt::Display for PrimitiveDateTime {
|
|||
write!(f, "{} {}", self.date, self.time)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for PrimitiveDateTime {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Display::fmt(self, f)
|
||||
}
|
||||
}
|
||||
// endregion formatting & parsing
|
||||
|
||||
// region: trait impls
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
//! Implementations of the [`quickcheck::Arbitrary`](quickcheck_dep::Arbitrary) trait.
|
||||
//! Implementations of the [`quickcheck::Arbitrary`](quickcheck::Arbitrary) trait.
|
||||
//!
|
||||
//! This enables users to write tests such as this, and have test values provided automatically:
|
||||
//!
|
||||
//! ```
|
||||
//! # #![allow(dead_code)]
|
||||
//! # use quickcheck_dep::quickcheck;
|
||||
//! # #[cfg(pretend_we_didnt_rename_the_dependency)]
|
||||
//! use quickcheck::quickcheck;
|
||||
//! use time::Date;
|
||||
//!
|
||||
|
@ -38,7 +36,7 @@
|
|||
|
||||
use alloc::boxed::Box;
|
||||
|
||||
use quickcheck_dep::{empty_shrinker, single_shrinker, Arbitrary, Gen};
|
||||
use quickcheck::{empty_shrinker, single_shrinker, Arbitrary, Gen};
|
||||
|
||||
use crate::{Date, Duration, Month, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset, Weekday};
|
||||
|
||||
|
@ -159,9 +157,9 @@ impl Arbitrary for OffsetDateTime {
|
|||
|
||||
fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
|
||||
Box::new(
|
||||
(self.utc_datetime.utc_to_offset(self.offset), self.offset)
|
||||
(self.local_datetime, self.offset)
|
||||
.shrink()
|
||||
.map(|(utc_datetime, offset)| utc_datetime.assume_offset(offset)),
|
||||
.map(|(local_datetime, offset)| local_datetime.assume_offset(offset)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -176,7 +174,10 @@ impl Arbitrary for Weekday {
|
|||
3 => Thursday,
|
||||
4 => Friday,
|
||||
5 => Saturday,
|
||||
_ => Sunday,
|
||||
val => {
|
||||
debug_assert!(val == 6);
|
||||
Sunday
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -203,7 +204,10 @@ impl Arbitrary for Month {
|
|||
9 => September,
|
||||
10 => October,
|
||||
11 => November,
|
||||
_ => December,
|
||||
val => {
|
||||
debug_assert!(val == 12);
|
||||
December
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -67,7 +67,10 @@ impl Distribution<Weekday> for Standard {
|
|||
3 => Thursday,
|
||||
4 => Friday,
|
||||
5 => Saturday,
|
||||
_ => Sunday,
|
||||
val => {
|
||||
debug_assert!(val == 6);
|
||||
Sunday
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -87,7 +90,10 @@ impl Distribution<Month> for Standard {
|
|||
9 => September,
|
||||
10 => October,
|
||||
11 => November,
|
||||
_ => December,
|
||||
val => {
|
||||
debug_assert!(val == 12);
|
||||
December
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
//! Use the well-known [ISO 8601 format] when serializing and deserializing an [`OffsetDateTime`].
|
||||
//!
|
||||
//! Use this module in combination with serde's [`#[with]`][with] attribute.
|
||||
//!
|
||||
//! [ISO 8601 format]: https://www.iso.org/iso-8601-date-and-time-format.html
|
||||
//! [with]: https://serde.rs/field-attrs.html#with
|
||||
|
||||
#[cfg(feature = "parsing")]
|
||||
use core::marker::PhantomData;
|
||||
|
||||
#[cfg(feature = "formatting")]
|
||||
use serde::ser::Error as _;
|
||||
#[cfg(feature = "parsing")]
|
||||
use serde::Deserializer;
|
||||
#[cfg(feature = "formatting")]
|
||||
use serde::{Serialize, Serializer};
|
||||
|
||||
#[cfg(feature = "parsing")]
|
||||
use super::Visitor;
|
||||
use crate::format_description::well_known::iso8601::{Config, EncodedConfig};
|
||||
use crate::format_description::well_known::Iso8601;
|
||||
use crate::OffsetDateTime;
|
||||
|
||||
/// The configuration of ISO 8601 used for serde implementations.
|
||||
pub(crate) const SERDE_CONFIG: EncodedConfig =
|
||||
Config::DEFAULT.set_year_is_six_digits(true).encode();
|
||||
|
||||
/// Serialize an [`OffsetDateTime`] using the well-known ISO 8601 format.
|
||||
#[cfg(feature = "formatting")]
|
||||
pub fn serialize<S: Serializer>(
|
||||
datetime: &OffsetDateTime,
|
||||
serializer: S,
|
||||
) -> Result<S::Ok, S::Error> {
|
||||
datetime
|
||||
.format(&Iso8601::<SERDE_CONFIG>)
|
||||
.map_err(S::Error::custom)?
|
||||
.serialize(serializer)
|
||||
}
|
||||
|
||||
/// Deserialize an [`OffsetDateTime`] from its ISO 8601 representation.
|
||||
#[cfg(feature = "parsing")]
|
||||
pub fn deserialize<'a, D: Deserializer<'a>>(deserializer: D) -> Result<OffsetDateTime, D::Error> {
|
||||
deserializer.deserialize_any(Visitor::<Iso8601<SERDE_CONFIG>>(PhantomData))
|
||||
}
|
||||
|
||||
/// Use the well-known ISO 8601 format when serializing and deserializing an
|
||||
/// [`Option<OffsetDateTime>`].
|
||||
///
|
||||
/// Use this module in combination with serde's [`#[with]`][with] attribute.
|
||||
///
|
||||
/// [ISO 8601 format]: https://www.iso.org/iso-8601-date-and-time-format.html
|
||||
/// [with]: https://serde.rs/field-attrs.html#with
|
||||
pub mod option {
|
||||
#[allow(clippy::wildcard_imports)]
|
||||
use super::*;
|
||||
|
||||
/// Serialize an [`Option<OffsetDateTime>`] using the well-known ISO 8601 format.
|
||||
#[cfg(feature = "formatting")]
|
||||
pub fn serialize<S: Serializer>(
|
||||
option: &Option<OffsetDateTime>,
|
||||
serializer: S,
|
||||
) -> Result<S::Ok, S::Error> {
|
||||
option
|
||||
.map(|odt| odt.format(&Iso8601::<SERDE_CONFIG>))
|
||||
.transpose()
|
||||
.map_err(S::Error::custom)?
|
||||
.serialize(serializer)
|
||||
}
|
||||
|
||||
/// Deserialize an [`Option<OffsetDateTime>`] from its ISO 8601 representation.
|
||||
#[cfg(feature = "parsing")]
|
||||
pub fn deserialize<'a, D: Deserializer<'a>>(
|
||||
deserializer: D,
|
||||
) -> Result<Option<OffsetDateTime>, D::Error> {
|
||||
deserializer.deserialize_option(Visitor::<Option<Iso8601<SERDE_CONFIG>>>(PhantomData))
|
||||
}
|
||||
}
|
|
@ -13,9 +13,11 @@ macro_rules! item {
|
|||
};
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde-well-known")]
|
||||
#[cfg(any(feature = "formatting", feature = "parsing"))]
|
||||
pub mod iso8601;
|
||||
#[cfg(any(feature = "formatting", feature = "parsing"))]
|
||||
pub mod rfc2822;
|
||||
#[cfg(feature = "serde-well-known")]
|
||||
#[cfg(any(feature = "formatting", feature = "parsing"))]
|
||||
pub mod rfc3339;
|
||||
pub mod timestamp;
|
||||
mod visitor;
|
||||
|
@ -37,30 +39,53 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
|||
/// submodule (`mod_name::option`) is also generated for `Option<Date>`. Both modules are only
|
||||
/// visible in the current scope.
|
||||
///
|
||||
/// The returned `Option` will contain a deserialized value if present and `None` if the field
|
||||
/// is present but the value is `null` (or the equivalent in other formats). To return `None`
|
||||
/// when the field is not present, you should use `#[serde(default)]` on the field.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// ```rust,no_run
|
||||
/// # use time::OffsetDateTime;
|
||||
/// # use ::serde::{Serialize, Deserialize};
|
||||
#[cfg_attr(
|
||||
all(feature = "formatting", feature = "parsing"),
|
||||
doc = "use ::serde::{Serialize, Deserialize};"
|
||||
)]
|
||||
#[cfg_attr(
|
||||
all(feature = "formatting", not(feature = "parsing")),
|
||||
doc = "use ::serde::Serialize;"
|
||||
)]
|
||||
#[cfg_attr(
|
||||
all(not(feature = "formatting"), feature = "parsing"),
|
||||
doc = "use ::serde::Deserialize;"
|
||||
)]
|
||||
/// use time::serde;
|
||||
///
|
||||
/// // Makes a module `mod my_format { ... }`.
|
||||
/// serde::format_description!(my_format, OffsetDateTime, "hour=[hour], minute=[minute]");
|
||||
///
|
||||
/// #[derive(Serialize, Deserialize)]
|
||||
#[cfg_attr(
|
||||
all(feature = "formatting", feature = "parsing"),
|
||||
doc = "#[derive(Serialize, Deserialize)]"
|
||||
)]
|
||||
#[cfg_attr(
|
||||
all(feature = "formatting", not(feature = "parsing")),
|
||||
doc = "#[derive(Serialize)]"
|
||||
)]
|
||||
#[cfg_attr(
|
||||
all(not(feature = "formatting"), feature = "parsing"),
|
||||
doc = "#[derive(Deserialize)]"
|
||||
)]
|
||||
/// # #[allow(dead_code)]
|
||||
/// struct SerializesWithCustom {
|
||||
/// #[serde(with = "my_format")]
|
||||
/// dt: OffsetDateTime,
|
||||
/// #[serde(with = "my_format::option")]
|
||||
/// maybe_dt: Option<OffsetDateTime>,
|
||||
/// }
|
||||
/// #
|
||||
/// # // otherwise rustdoc tests don't work because we put a module in `main()`
|
||||
/// # fn main() {}
|
||||
/// ```
|
||||
///
|
||||
///
|
||||
/// [`format_description::parse()`]: crate::format_description::parse()
|
||||
#[cfg(all(feature = "macros", feature = "serde-human-readable"))]
|
||||
#[cfg(all(feature = "macros", any(feature = "formatting", feature = "parsing"),))]
|
||||
pub use time_macros::serde_format_description as format_description;
|
||||
|
||||
use self::visitor::Visitor;
|
||||
|
@ -95,7 +120,11 @@ impl Serialize for Date {
|
|||
|
||||
impl<'a> Deserialize<'a> for Date {
|
||||
fn deserialize<D: Deserializer<'a>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
deserializer.deserialize_any(Visitor::<Self>(PhantomData))
|
||||
if cfg!(feature = "serde-human-readable") && deserializer.is_human_readable() {
|
||||
deserializer.deserialize_any(Visitor::<Self>(PhantomData))
|
||||
} else {
|
||||
deserializer.deserialize_tuple(2, Visitor::<Self>(PhantomData))
|
||||
}
|
||||
}
|
||||
}
|
||||
// endregion date
|
||||
|
@ -118,7 +147,11 @@ impl Serialize for Duration {
|
|||
|
||||
impl<'a> Deserialize<'a> for Duration {
|
||||
fn deserialize<D: Deserializer<'a>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
deserializer.deserialize_any(Visitor::<Self>(PhantomData))
|
||||
if cfg!(feature = "serde-human-readable") && deserializer.is_human_readable() {
|
||||
deserializer.deserialize_any(Visitor::<Self>(PhantomData))
|
||||
} else {
|
||||
deserializer.deserialize_tuple(2, Visitor::<Self>(PhantomData))
|
||||
}
|
||||
}
|
||||
}
|
||||
// endregion Duration
|
||||
|
@ -161,7 +194,11 @@ impl Serialize for OffsetDateTime {
|
|||
|
||||
impl<'a> Deserialize<'a> for OffsetDateTime {
|
||||
fn deserialize<D: Deserializer<'a>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
deserializer.deserialize_any(Visitor::<Self>(PhantomData))
|
||||
if cfg!(feature = "serde-human-readable") && deserializer.is_human_readable() {
|
||||
deserializer.deserialize_any(Visitor::<Self>(PhantomData))
|
||||
} else {
|
||||
deserializer.deserialize_tuple(9, Visitor::<Self>(PhantomData))
|
||||
}
|
||||
}
|
||||
}
|
||||
// endregion OffsetDateTime
|
||||
|
@ -199,7 +236,11 @@ impl Serialize for PrimitiveDateTime {
|
|||
|
||||
impl<'a> Deserialize<'a> for PrimitiveDateTime {
|
||||
fn deserialize<D: Deserializer<'a>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
deserializer.deserialize_any(Visitor::<Self>(PhantomData))
|
||||
if cfg!(feature = "serde-human-readable") && deserializer.is_human_readable() {
|
||||
deserializer.deserialize_any(Visitor::<Self>(PhantomData))
|
||||
} else {
|
||||
deserializer.deserialize_tuple(6, Visitor::<Self>(PhantomData))
|
||||
}
|
||||
}
|
||||
}
|
||||
// endregion PrimitiveDateTime
|
||||
|
@ -233,7 +274,11 @@ impl Serialize for Time {
|
|||
|
||||
impl<'a> Deserialize<'a> for Time {
|
||||
fn deserialize<D: Deserializer<'a>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
deserializer.deserialize_any(Visitor::<Self>(PhantomData))
|
||||
if cfg!(feature = "serde-human-readable") && deserializer.is_human_readable() {
|
||||
deserializer.deserialize_any(Visitor::<Self>(PhantomData))
|
||||
} else {
|
||||
deserializer.deserialize_tuple(4, Visitor::<Self>(PhantomData))
|
||||
}
|
||||
}
|
||||
}
|
||||
// endregion Time
|
||||
|
@ -270,7 +315,11 @@ impl Serialize for UtcOffset {
|
|||
|
||||
impl<'a> Deserialize<'a> for UtcOffset {
|
||||
fn deserialize<D: Deserializer<'a>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
deserializer.deserialize_any(Visitor::<Self>(PhantomData))
|
||||
if cfg!(feature = "serde-human-readable") && deserializer.is_human_readable() {
|
||||
deserializer.deserialize_any(Visitor::<Self>(PhantomData))
|
||||
} else {
|
||||
deserializer.deserialize_tuple(3, Visitor::<Self>(PhantomData))
|
||||
}
|
||||
}
|
||||
}
|
||||
// endregion UtcOffset
|
||||
|
@ -291,7 +340,11 @@ impl Serialize for Weekday {
|
|||
|
||||
impl<'a> Deserialize<'a> for Weekday {
|
||||
fn deserialize<D: Deserializer<'a>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
deserializer.deserialize_any(Visitor::<Self>(PhantomData))
|
||||
if cfg!(feature = "serde-human-readable") && deserializer.is_human_readable() {
|
||||
deserializer.deserialize_any(Visitor::<Self>(PhantomData))
|
||||
} else {
|
||||
deserializer.deserialize_u8(Visitor::<Self>(PhantomData))
|
||||
}
|
||||
}
|
||||
}
|
||||
// endregion Weekday
|
||||
|
@ -312,7 +365,11 @@ impl Serialize for Month {
|
|||
|
||||
impl<'a> Deserialize<'a> for Month {
|
||||
fn deserialize<D: Deserializer<'a>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
deserializer.deserialize_any(Visitor::<Self>(PhantomData))
|
||||
if cfg!(feature = "serde-human-readable") && deserializer.is_human_readable() {
|
||||
deserializer.deserialize_any(Visitor::<Self>(PhantomData))
|
||||
} else {
|
||||
deserializer.deserialize_u8(Visitor::<Self>(PhantomData))
|
||||
}
|
||||
}
|
||||
}
|
||||
// endregion Month
|
||||
|
|
|
@ -5,16 +5,23 @@
|
|||
//! [RFC2822 format]: https://tools.ietf.org/html/rfc2822#section-3.3
|
||||
//! [with]: https://serde.rs/field-attrs.html#with
|
||||
|
||||
#[cfg(feature = "parsing")]
|
||||
use core::marker::PhantomData;
|
||||
|
||||
#[cfg(feature = "formatting")]
|
||||
use serde::ser::Error as _;
|
||||
use serde::{Deserializer, Serialize, Serializer};
|
||||
#[cfg(feature = "parsing")]
|
||||
use serde::Deserializer;
|
||||
#[cfg(feature = "formatting")]
|
||||
use serde::{Serialize, Serializer};
|
||||
|
||||
#[cfg(feature = "parsing")]
|
||||
use super::Visitor;
|
||||
use crate::format_description::well_known::Rfc2822;
|
||||
use crate::OffsetDateTime;
|
||||
|
||||
/// Serialize an [`OffsetDateTime`] using the well-known RFC2822 format.
|
||||
#[cfg(feature = "formatting")]
|
||||
pub fn serialize<S: Serializer>(
|
||||
datetime: &OffsetDateTime,
|
||||
serializer: S,
|
||||
|
@ -26,8 +33,9 @@ pub fn serialize<S: Serializer>(
|
|||
}
|
||||
|
||||
/// Deserialize an [`OffsetDateTime`] from its RFC2822 representation.
|
||||
#[cfg(feature = "parsing")]
|
||||
pub fn deserialize<'a, D: Deserializer<'a>>(deserializer: D) -> Result<OffsetDateTime, D::Error> {
|
||||
deserializer.deserialize_any(Visitor::<Rfc2822>(PhantomData))
|
||||
deserializer.deserialize_str(Visitor::<Rfc2822>(PhantomData))
|
||||
}
|
||||
|
||||
/// Use the well-known [RFC2822 format] when serializing and deserializing an
|
||||
|
@ -42,6 +50,7 @@ pub mod option {
|
|||
use super::*;
|
||||
|
||||
/// Serialize an [`Option<OffsetDateTime>`] using the well-known RFC2822 format.
|
||||
#[cfg(feature = "formatting")]
|
||||
pub fn serialize<S: Serializer>(
|
||||
option: &Option<OffsetDateTime>,
|
||||
serializer: S,
|
||||
|
@ -54,6 +63,7 @@ pub mod option {
|
|||
}
|
||||
|
||||
/// Deserialize an [`Option<OffsetDateTime>`] from its RFC2822 representation.
|
||||
#[cfg(feature = "parsing")]
|
||||
pub fn deserialize<'a, D: Deserializer<'a>>(
|
||||
deserializer: D,
|
||||
) -> Result<Option<OffsetDateTime>, D::Error> {
|
||||
|
|
|
@ -5,16 +5,23 @@
|
|||
//! [RFC3339 format]: https://tools.ietf.org/html/rfc3339#section-5.6
|
||||
//! [with]: https://serde.rs/field-attrs.html#with
|
||||
|
||||
#[cfg(feature = "parsing")]
|
||||
use core::marker::PhantomData;
|
||||
|
||||
#[cfg(feature = "formatting")]
|
||||
use serde::ser::Error as _;
|
||||
use serde::{Deserializer, Serialize, Serializer};
|
||||
#[cfg(feature = "parsing")]
|
||||
use serde::Deserializer;
|
||||
#[cfg(feature = "formatting")]
|
||||
use serde::{Serialize, Serializer};
|
||||
|
||||
#[cfg(feature = "parsing")]
|
||||
use super::Visitor;
|
||||
use crate::format_description::well_known::Rfc3339;
|
||||
use crate::OffsetDateTime;
|
||||
|
||||
/// Serialize an [`OffsetDateTime`] using the well-known RFC3339 format.
|
||||
#[cfg(feature = "formatting")]
|
||||
pub fn serialize<S: Serializer>(
|
||||
datetime: &OffsetDateTime,
|
||||
serializer: S,
|
||||
|
@ -26,8 +33,9 @@ pub fn serialize<S: Serializer>(
|
|||
}
|
||||
|
||||
/// Deserialize an [`OffsetDateTime`] from its RFC3339 representation.
|
||||
#[cfg(feature = "parsing")]
|
||||
pub fn deserialize<'a, D: Deserializer<'a>>(deserializer: D) -> Result<OffsetDateTime, D::Error> {
|
||||
deserializer.deserialize_any(Visitor::<Rfc3339>(PhantomData))
|
||||
deserializer.deserialize_str(Visitor::<Rfc3339>(PhantomData))
|
||||
}
|
||||
|
||||
/// Use the well-known [RFC3339 format] when serializing and deserializing an
|
||||
|
@ -42,6 +50,7 @@ pub mod option {
|
|||
use super::*;
|
||||
|
||||
/// Serialize an [`Option<OffsetDateTime>`] using the well-known RFC3339 format.
|
||||
#[cfg(feature = "formatting")]
|
||||
pub fn serialize<S: Serializer>(
|
||||
option: &Option<OffsetDateTime>,
|
||||
serializer: S,
|
||||
|
@ -54,6 +63,7 @@ pub mod option {
|
|||
}
|
||||
|
||||
/// Deserialize an [`Option<OffsetDateTime>`] from its RFC3339 representation.
|
||||
#[cfg(feature = "parsing")]
|
||||
pub fn deserialize<'a, D: Deserializer<'a>>(
|
||||
deserializer: D,
|
||||
) -> Result<Option<OffsetDateTime>, D::Error> {
|
||||
|
|
|
@ -4,7 +4,7 @@ use core::fmt;
|
|||
use core::marker::PhantomData;
|
||||
|
||||
use serde::de;
|
||||
#[cfg(feature = "serde-well-known")]
|
||||
#[cfg(feature = "parsing")]
|
||||
use serde::Deserializer;
|
||||
|
||||
#[cfg(feature = "parsing")]
|
||||
|
@ -13,8 +13,8 @@ use super::{
|
|||
UTC_OFFSET_FORMAT,
|
||||
};
|
||||
use crate::error::ComponentRange;
|
||||
#[cfg(feature = "serde-well-known")]
|
||||
use crate::format_description::well_known;
|
||||
#[cfg(feature = "parsing")]
|
||||
use crate::format_description::well_known::*;
|
||||
use crate::{Date, Duration, Month, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset, Weekday};
|
||||
|
||||
/// A serde visitor for various types.
|
||||
|
@ -194,7 +194,7 @@ impl<'a> de::Visitor<'a> for Visitor<Weekday> {
|
|||
}
|
||||
}
|
||||
|
||||
fn visit_u8<E: de::Error>(self, value: u8) -> Result<Weekday, E> {
|
||||
fn visit_u64<E: de::Error>(self, value: u64) -> Result<Weekday, E> {
|
||||
match value {
|
||||
1 => Ok(Weekday::Monday),
|
||||
2 => Ok(Weekday::Tuesday),
|
||||
|
@ -204,7 +204,7 @@ impl<'a> de::Visitor<'a> for Visitor<Weekday> {
|
|||
6 => Ok(Weekday::Saturday),
|
||||
7 => Ok(Weekday::Sunday),
|
||||
_ => Err(E::invalid_value(
|
||||
de::Unexpected::Unsigned(value.into()),
|
||||
de::Unexpected::Unsigned(value),
|
||||
&"a value in the range 1..=7",
|
||||
)),
|
||||
}
|
||||
|
@ -236,7 +236,7 @@ impl<'a> de::Visitor<'a> for Visitor<Month> {
|
|||
}
|
||||
}
|
||||
|
||||
fn visit_u8<E: de::Error>(self, value: u8) -> Result<Month, E> {
|
||||
fn visit_u64<E: de::Error>(self, value: u64) -> Result<Month, E> {
|
||||
match value {
|
||||
1 => Ok(Month::January),
|
||||
2 => Ok(Month::February),
|
||||
|
@ -251,79 +251,66 @@ impl<'a> de::Visitor<'a> for Visitor<Month> {
|
|||
11 => Ok(Month::November),
|
||||
12 => Ok(Month::December),
|
||||
_ => Err(E::invalid_value(
|
||||
de::Unexpected::Unsigned(value.into()),
|
||||
de::Unexpected::Unsigned(value),
|
||||
&"a value in the range 1..=12",
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde-well-known")]
|
||||
impl<'a> de::Visitor<'a> for Visitor<well_known::Rfc2822> {
|
||||
type Value = OffsetDateTime;
|
||||
/// Implement a visitor for a well-known format.
|
||||
macro_rules! well_known {
|
||||
($article:literal, $name:literal, $($ty:tt)+) => {
|
||||
#[cfg(feature = "parsing")]
|
||||
impl<'a> de::Visitor<'a> for Visitor<$($ty)+> {
|
||||
type Value = OffsetDateTime;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter.write_str("an RFC2822-formatted `OffsetDateTime`")
|
||||
}
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter.write_str(concat!($article, " ", $name, "-formatted `OffsetDateTime`"))
|
||||
}
|
||||
|
||||
fn visit_str<E: de::Error>(self, value: &str) -> Result<OffsetDateTime, E> {
|
||||
OffsetDateTime::parse(value, &well_known::Rfc2822).map_err(E::custom)
|
||||
}
|
||||
fn visit_str<E: de::Error>(self, value: &str) -> Result<OffsetDateTime, E> {
|
||||
OffsetDateTime::parse(value, &$($ty)+).map_err(E::custom)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "parsing")]
|
||||
impl<'a> de::Visitor<'a> for Visitor<Option<$($ty)+>> {
|
||||
type Value = Option<OffsetDateTime>;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter.write_str(concat!(
|
||||
$article,
|
||||
" ",
|
||||
$name,
|
||||
"-formatted `Option<OffsetDateTime>`"
|
||||
))
|
||||
}
|
||||
|
||||
fn visit_some<D: Deserializer<'a>>(
|
||||
self,
|
||||
deserializer: D,
|
||||
) -> Result<Option<OffsetDateTime>, D::Error> {
|
||||
deserializer
|
||||
.deserialize_any(Visitor::<$($ty)+>(PhantomData))
|
||||
.map(Some)
|
||||
}
|
||||
|
||||
fn visit_none<E: de::Error>(self) -> Result<Option<OffsetDateTime>, E> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn visit_unit<E: de::Error>(self) -> Result<Self::Value, E> {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde-well-known")]
|
||||
impl<'a> de::Visitor<'a> for Visitor<Option<well_known::Rfc2822>> {
|
||||
type Value = Option<OffsetDateTime>;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter.write_str("an RFC2822-formatted `Option<OffsetDateTime>`")
|
||||
}
|
||||
|
||||
fn visit_some<D: Deserializer<'a>>(
|
||||
self,
|
||||
deserializer: D,
|
||||
) -> Result<Option<OffsetDateTime>, D::Error> {
|
||||
deserializer
|
||||
.deserialize_any(Visitor::<well_known::Rfc2822>(PhantomData))
|
||||
.map(Some)
|
||||
}
|
||||
|
||||
fn visit_none<E: de::Error>(self) -> Result<Option<OffsetDateTime>, E> {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde-well-known")]
|
||||
impl<'a> de::Visitor<'a> for Visitor<well_known::Rfc3339> {
|
||||
type Value = OffsetDateTime;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter.write_str("an RFC3339-formatted `OffsetDateTime`")
|
||||
}
|
||||
|
||||
fn visit_str<E: de::Error>(self, value: &str) -> Result<OffsetDateTime, E> {
|
||||
OffsetDateTime::parse(value, &well_known::Rfc3339).map_err(E::custom)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde-well-known")]
|
||||
impl<'a> de::Visitor<'a> for Visitor<Option<well_known::Rfc3339>> {
|
||||
type Value = Option<OffsetDateTime>;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter.write_str("an RFC3339-formatted `Option<OffsetDateTime>`")
|
||||
}
|
||||
|
||||
fn visit_some<D: Deserializer<'a>>(
|
||||
self,
|
||||
deserializer: D,
|
||||
) -> Result<Option<OffsetDateTime>, D::Error> {
|
||||
deserializer
|
||||
.deserialize_any(Visitor::<well_known::Rfc3339>(PhantomData))
|
||||
.map(Some)
|
||||
}
|
||||
|
||||
fn visit_none<E: de::Error>(self) -> Result<Option<OffsetDateTime>, E> {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
well_known!("an", "RFC2822", Rfc2822);
|
||||
well_known!("an", "RFC3339", Rfc3339);
|
||||
well_known!(
|
||||
"an",
|
||||
"ISO 8601",
|
||||
Iso8601::<{ super::iso8601::SERDE_CONFIG }>
|
||||
);
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
use crate::{OffsetDateTime, UtcOffset};
|
||||
|
||||
#[allow(clippy::missing_docs_in_private_items)]
|
||||
pub(super) fn local_offset_at(_datetime: OffsetDateTime) -> Option<UtcOffset> {
|
||||
None
|
||||
}
|
||||
|
|
|
@ -1,7 +1,17 @@
|
|||
//! A method to obtain the local offset from UTC.
|
||||
|
||||
#![allow(clippy::missing_const_for_fn)]
|
||||
|
||||
#[cfg_attr(target_family = "windows", path = "windows.rs")]
|
||||
#[cfg_attr(target_family = "unix", path = "unix.rs")]
|
||||
#[cfg_attr(
|
||||
all(
|
||||
target_arch = "wasm32",
|
||||
not(any(target_os = "emscripten", target_os = "wasi")),
|
||||
feature = "wasm-bindgen"
|
||||
),
|
||||
path = "wasm_js.rs"
|
||||
)]
|
||||
mod imp;
|
||||
|
||||
use crate::{OffsetDateTime, UtcOffset};
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
//! Get the system's UTC offset on Unix.
|
||||
|
||||
use core::convert::TryInto;
|
||||
use core::mem::MaybeUninit;
|
||||
|
||||
use crate::{OffsetDateTime, UtcOffset};
|
||||
|
@ -44,8 +43,22 @@ unsafe fn timestamp_to_tm(timestamp: i64) -> Option<libc::tm> {
|
|||
}
|
||||
|
||||
/// Convert a `libc::tm` to a `UtcOffset`. Returns `None` on any error.
|
||||
// `tm_gmtoff` extension
|
||||
#[cfg(not(any(target_os = "solaris", target_os = "illumos")))]
|
||||
// This is available to any target known to have the `tm_gmtoff` extension.
|
||||
#[cfg(any(
|
||||
target_os = "redox",
|
||||
target_os = "linux",
|
||||
target_os = "l4re",
|
||||
target_os = "android",
|
||||
target_os = "emscripten",
|
||||
target_os = "macos",
|
||||
target_os = "ios",
|
||||
target_os = "watchos",
|
||||
target_os = "freebsd",
|
||||
target_os = "dragonfly",
|
||||
target_os = "openbsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "haiku",
|
||||
))]
|
||||
fn tm_to_offset(tm: libc::tm) -> Option<UtcOffset> {
|
||||
let seconds: i32 = tm.tm_gmtoff.try_into().ok()?;
|
||||
UtcOffset::from_hms(
|
||||
|
@ -57,10 +70,23 @@ fn tm_to_offset(tm: libc::tm) -> Option<UtcOffset> {
|
|||
}
|
||||
|
||||
/// Convert a `libc::tm` to a `UtcOffset`. Returns `None` on any error.
|
||||
// Solaris/Illumos is unsound and requires opting into.
|
||||
#[cfg(all(
|
||||
not(unsound_local_offset),
|
||||
any(target_os = "solaris", target_os = "illumos")
|
||||
not(any(
|
||||
target_os = "redox",
|
||||
target_os = "linux",
|
||||
target_os = "l4re",
|
||||
target_os = "android",
|
||||
target_os = "emscripten",
|
||||
target_os = "macos",
|
||||
target_os = "ios",
|
||||
target_os = "watchos",
|
||||
target_os = "freebsd",
|
||||
target_os = "dragonfly",
|
||||
target_os = "openbsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "haiku",
|
||||
))
|
||||
))]
|
||||
#[allow(unused_variables, clippy::missing_const_for_fn)]
|
||||
fn tm_to_offset(tm: libc::tm) -> Option<UtcOffset> {
|
||||
|
@ -68,13 +94,30 @@ fn tm_to_offset(tm: libc::tm) -> Option<UtcOffset> {
|
|||
}
|
||||
|
||||
/// Convert a `libc::tm` to a `UtcOffset`. Returns `None` on any error.
|
||||
// This method can return an incorrect value, as it only approximates the `tm_gmtoff` field. As such
|
||||
// it is gated behind `--cfg unsound_local_offset`. The reason it can return an incorrect value is
|
||||
// that daylight saving time does not start on the same date every year, nor are the rules for
|
||||
// daylight saving time the same for every year. This implementation assumes 1970 is equivalent to
|
||||
// every other year, which is not always the case.
|
||||
#[cfg(all(
|
||||
unsound_local_offset,
|
||||
any(target_os = "solaris", target_os = "illumos")
|
||||
not(any(
|
||||
target_os = "redox",
|
||||
target_os = "linux",
|
||||
target_os = "l4re",
|
||||
target_os = "android",
|
||||
target_os = "emscripten",
|
||||
target_os = "macos",
|
||||
target_os = "ios",
|
||||
target_os = "watchos",
|
||||
target_os = "freebsd",
|
||||
target_os = "dragonfly",
|
||||
target_os = "openbsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "haiku",
|
||||
))
|
||||
))]
|
||||
fn tm_to_offset(tm: libc::tm) -> Option<UtcOffset> {
|
||||
use core::convert::TryFrom;
|
||||
|
||||
use crate::Date;
|
||||
|
||||
let mut tm = tm;
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
use crate::{OffsetDateTime, UtcOffset};
|
||||
|
||||
/// Obtain the system's UTC offset.
|
||||
pub(super) fn local_offset_at(datetime: OffsetDateTime) -> Option<UtcOffset> {
|
||||
let js_date: js_sys::Date = datetime.into();
|
||||
// The number of minutes returned by getTimezoneOffset() is positive if the local time zone
|
||||
// is behind UTC, and negative if the local time zone is ahead of UTC. For example,
|
||||
// for UTC+10, -600 will be returned.
|
||||
let timezone_offset = (js_date.get_timezone_offset() as i32) * -60;
|
||||
|
||||
UtcOffset::from_whole_seconds(timezone_offset).ok()
|
||||
}
|
|
@ -1,13 +1,12 @@
|
|||
//! Get the system's UTC offset on Windows.
|
||||
|
||||
use core::convert::TryInto;
|
||||
use core::mem::MaybeUninit;
|
||||
|
||||
use crate::{OffsetDateTime, UtcOffset};
|
||||
|
||||
// ffi: WINAPI FILETIME struct
|
||||
#[repr(C)]
|
||||
#[allow(non_snake_case)]
|
||||
#[allow(non_snake_case, clippy::missing_docs_in_private_items)]
|
||||
struct FileTime {
|
||||
dwLowDateTime: u32,
|
||||
dwHighDateTime: u32,
|
||||
|
@ -15,7 +14,7 @@ struct FileTime {
|
|||
|
||||
// ffi: WINAPI SYSTEMTIME struct
|
||||
#[repr(C)]
|
||||
#[allow(non_snake_case)]
|
||||
#[allow(non_snake_case, clippy::missing_docs_in_private_items)]
|
||||
struct SystemTime {
|
||||
wYear: u16,
|
||||
wMonth: u16,
|
||||
|
@ -34,7 +33,7 @@ extern "system" {
|
|||
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/timezoneapi/nf-timezoneapi-systemtimetotzspecificlocaltime
|
||||
fn SystemTimeToTzSpecificLocalTime(
|
||||
lpTimeZoneInformation: *const std::ffi::c_void, // We only pass `nullptr` here
|
||||
lpTimeZoneInformation: *const core::ffi::c_void, // We only pass `nullptr` here
|
||||
lpUniversalTime: *const SystemTime,
|
||||
lpLocalTime: *mut SystemTime,
|
||||
) -> i32;
|
||||
|
|
|
@ -0,0 +1,113 @@
|
|||
#![cfg(all( // require `--all-features` to be passed
|
||||
feature = "default",
|
||||
feature = "alloc",
|
||||
feature = "formatting",
|
||||
feature = "large-dates",
|
||||
feature = "local-offset",
|
||||
feature = "macros",
|
||||
feature = "parsing",
|
||||
feature = "quickcheck",
|
||||
feature = "serde-human-readable",
|
||||
feature = "serde-well-known",
|
||||
feature = "std",
|
||||
feature = "rand",
|
||||
feature = "serde",
|
||||
))]
|
||||
#![allow(
|
||||
clippy::let_underscore_drop,
|
||||
clippy::clone_on_copy,
|
||||
clippy::cognitive_complexity,
|
||||
clippy::std_instead_of_core
|
||||
)]
|
||||
|
||||
//! Tests for internal details.
|
||||
//!
|
||||
//! This module should only be used when it is not possible to test the implementation in a
|
||||
//! reasonable manner externally.
|
||||
|
||||
use std::num::NonZeroU8;
|
||||
|
||||
use crate::formatting::DigitCount;
|
||||
use crate::parsing::combinator::rfc::iso8601;
|
||||
use crate::parsing::shim::Integer;
|
||||
use crate::{duration, parsing};
|
||||
|
||||
#[test]
|
||||
fn digit_count() {
|
||||
assert_eq!(1_u8.num_digits(), 1);
|
||||
assert_eq!(9_u8.num_digits(), 1);
|
||||
assert_eq!(10_u8.num_digits(), 2);
|
||||
assert_eq!(99_u8.num_digits(), 2);
|
||||
assert_eq!(100_u8.num_digits(), 3);
|
||||
|
||||
assert_eq!(1_u16.num_digits(), 1);
|
||||
assert_eq!(9_u16.num_digits(), 1);
|
||||
assert_eq!(10_u16.num_digits(), 2);
|
||||
assert_eq!(99_u16.num_digits(), 2);
|
||||
assert_eq!(100_u16.num_digits(), 3);
|
||||
assert_eq!(999_u16.num_digits(), 3);
|
||||
assert_eq!(1_000_u16.num_digits(), 4);
|
||||
assert_eq!(9_999_u16.num_digits(), 4);
|
||||
assert_eq!(10_000_u16.num_digits(), 5);
|
||||
|
||||
assert_eq!(1_u32.num_digits(), 1);
|
||||
assert_eq!(9_u32.num_digits(), 1);
|
||||
assert_eq!(10_u32.num_digits(), 2);
|
||||
assert_eq!(99_u32.num_digits(), 2);
|
||||
assert_eq!(100_u32.num_digits(), 3);
|
||||
assert_eq!(999_u32.num_digits(), 3);
|
||||
assert_eq!(1_000_u32.num_digits(), 4);
|
||||
assert_eq!(9_999_u32.num_digits(), 4);
|
||||
assert_eq!(10_000_u32.num_digits(), 5);
|
||||
assert_eq!(99_999_u32.num_digits(), 5);
|
||||
assert_eq!(100_000_u32.num_digits(), 6);
|
||||
assert_eq!(999_999_u32.num_digits(), 6);
|
||||
assert_eq!(1_000_000_u32.num_digits(), 7);
|
||||
assert_eq!(9_999_999_u32.num_digits(), 7);
|
||||
assert_eq!(10_000_000_u32.num_digits(), 8);
|
||||
assert_eq!(99_999_999_u32.num_digits(), 8);
|
||||
assert_eq!(100_000_000_u32.num_digits(), 9);
|
||||
assert_eq!(999_999_999_u32.num_digits(), 9);
|
||||
assert_eq!(1_000_000_000_u32.num_digits(), 10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default() {
|
||||
assert_eq!(
|
||||
duration::Padding::Optimize.clone(),
|
||||
duration::Padding::default()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn debug() {
|
||||
let _ = format!("{:?}", duration::Padding::Optimize);
|
||||
let _ = format!("{:?}", parsing::ParsedItem(b"", 0));
|
||||
let _ = format!("{:?}", parsing::component::Period::Am);
|
||||
let _ = format!("{:?}", iso8601::ExtendedKind::Basic);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clone() {
|
||||
assert_eq!(
|
||||
parsing::component::Period::Am.clone(),
|
||||
parsing::component::Period::Am
|
||||
);
|
||||
// does not impl Debug
|
||||
assert!(crate::time::Padding::Optimize.clone() == crate::time::Padding::Optimize);
|
||||
// does not impl PartialEq
|
||||
assert!(matches!(
|
||||
iso8601::ExtendedKind::Basic.clone(),
|
||||
iso8601::ExtendedKind::Basic
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parsing_internals() {
|
||||
assert!(
|
||||
parsing::ParsedItem(b"", ())
|
||||
.flat_map(|_| None::<()>)
|
||||
.is_none()
|
||||
);
|
||||
assert!(<NonZeroU8 as Integer>::parse_bytes(b"256").is_none());
|
||||
}
|
|
@ -42,22 +42,12 @@ pub struct Time {
|
|||
padding: Padding,
|
||||
}
|
||||
|
||||
impl fmt::Debug for Time {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Time")
|
||||
.field("hour", &self.hour)
|
||||
.field("minute", &self.minute)
|
||||
.field("second", &self.second)
|
||||
.field("nanosecond", &self.nanosecond)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Time {
|
||||
/// Create a `Time` that is exactly midnight.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::{Time, macros::time};
|
||||
/// # use time::Time;
|
||||
/// # use time_macros::time;
|
||||
/// assert_eq!(Time::MIDNIGHT, time!(0:00));
|
||||
/// ```
|
||||
pub const MIDNIGHT: Self = Self::__from_hms_nanos_unchecked(0, 0, 0, 0);
|
||||
|
@ -81,6 +71,11 @@ impl Time {
|
|||
second: u8,
|
||||
nanosecond: u32,
|
||||
) -> Self {
|
||||
debug_assert!(hour < 24);
|
||||
debug_assert!(minute < 60);
|
||||
debug_assert!(second < 60);
|
||||
debug_assert!(nanosecond < 1_000_000_000);
|
||||
|
||||
Self {
|
||||
hour,
|
||||
minute,
|
||||
|
@ -208,7 +203,7 @@ impl Time {
|
|||
/// Get the clock hour, minute, and second.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::time;
|
||||
/// # use time_macros::time;
|
||||
/// assert_eq!(time!(0:00:00).as_hms(), (0, 0, 0));
|
||||
/// assert_eq!(time!(23:59:59).as_hms(), (23, 59, 59));
|
||||
/// ```
|
||||
|
@ -219,7 +214,7 @@ impl Time {
|
|||
/// Get the clock hour, minute, second, and millisecond.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::time;
|
||||
/// # use time_macros::time;
|
||||
/// assert_eq!(time!(0:00:00).as_hms_milli(), (0, 0, 0, 0));
|
||||
/// assert_eq!(time!(23:59:59.999).as_hms_milli(), (23, 59, 59, 999));
|
||||
/// ```
|
||||
|
@ -235,7 +230,7 @@ impl Time {
|
|||
/// Get the clock hour, minute, second, and microsecond.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::time;
|
||||
/// # use time_macros::time;
|
||||
/// assert_eq!(time!(0:00:00).as_hms_micro(), (0, 0, 0, 0));
|
||||
/// assert_eq!(
|
||||
/// time!(23:59:59.999_999).as_hms_micro(),
|
||||
|
@ -249,7 +244,7 @@ impl Time {
|
|||
/// Get the clock hour, minute, second, and nanosecond.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::time;
|
||||
/// # use time_macros::time;
|
||||
/// assert_eq!(time!(0:00:00).as_hms_nano(), (0, 0, 0, 0));
|
||||
/// assert_eq!(
|
||||
/// time!(23:59:59.999_999_999).as_hms_nano(),
|
||||
|
@ -265,7 +260,7 @@ impl Time {
|
|||
/// The returned value will always be in the range `0..24`.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::time;
|
||||
/// # use time_macros::time;
|
||||
/// assert_eq!(time!(0:00:00).hour(), 0);
|
||||
/// assert_eq!(time!(23:59:59).hour(), 23);
|
||||
/// ```
|
||||
|
@ -278,7 +273,7 @@ impl Time {
|
|||
/// The returned value will always be in the range `0..60`.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::time;
|
||||
/// # use time_macros::time;
|
||||
/// assert_eq!(time!(0:00:00).minute(), 0);
|
||||
/// assert_eq!(time!(23:59:59).minute(), 59);
|
||||
/// ```
|
||||
|
@ -291,7 +286,7 @@ impl Time {
|
|||
/// The returned value will always be in the range `0..60`.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::time;
|
||||
/// # use time_macros::time;
|
||||
/// assert_eq!(time!(0:00:00).second(), 0);
|
||||
/// assert_eq!(time!(23:59:59).second(), 59);
|
||||
/// ```
|
||||
|
@ -304,7 +299,7 @@ impl Time {
|
|||
/// The returned value will always be in the range `0..1_000`.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::time;
|
||||
/// # use time_macros::time;
|
||||
/// assert_eq!(time!(0:00).millisecond(), 0);
|
||||
/// assert_eq!(time!(23:59:59.999).millisecond(), 999);
|
||||
/// ```
|
||||
|
@ -317,7 +312,7 @@ impl Time {
|
|||
/// The returned value will always be in the range `0..1_000_000`.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::time;
|
||||
/// # use time_macros::time;
|
||||
/// assert_eq!(time!(0:00).microsecond(), 0);
|
||||
/// assert_eq!(time!(23:59:59.999_999).microsecond(), 999_999);
|
||||
/// ```
|
||||
|
@ -330,7 +325,7 @@ impl Time {
|
|||
/// The returned value will always be in the range `0..1_000_000_000`.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::time;
|
||||
/// # use time_macros::time;
|
||||
/// assert_eq!(time!(0:00).nanosecond(), 0);
|
||||
/// assert_eq!(time!(23:59:59.999_999_999).nanosecond(), 999_999_999);
|
||||
/// ```
|
||||
|
@ -453,7 +448,7 @@ impl Time {
|
|||
/// Replace the clock hour.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::time;
|
||||
/// # use time_macros::time;
|
||||
/// assert_eq!(
|
||||
/// time!(01:02:03.004_005_006).replace_hour(7),
|
||||
/// Ok(time!(07:02:03.004_005_006))
|
||||
|
@ -474,7 +469,7 @@ impl Time {
|
|||
/// Replace the minutes within the hour.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::time;
|
||||
/// # use time_macros::time;
|
||||
/// assert_eq!(
|
||||
/// time!(01:02:03.004_005_006).replace_minute(7),
|
||||
/// Ok(time!(01:07:03.004_005_006))
|
||||
|
@ -495,7 +490,7 @@ impl Time {
|
|||
/// Replace the seconds within the minute.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::time;
|
||||
/// # use time_macros::time;
|
||||
/// assert_eq!(
|
||||
/// time!(01:02:03.004_005_006).replace_second(7),
|
||||
/// Ok(time!(01:02:07.004_005_006))
|
||||
|
@ -516,7 +511,7 @@ impl Time {
|
|||
/// Replace the milliseconds within the second.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::time;
|
||||
/// # use time_macros::time;
|
||||
/// assert_eq!(
|
||||
/// time!(01:02:03.004_005_006).replace_millisecond(7),
|
||||
/// Ok(time!(01:02:03.007))
|
||||
|
@ -540,7 +535,7 @@ impl Time {
|
|||
/// Replace the microseconds within the second.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::time;
|
||||
/// # use time_macros::time;
|
||||
/// assert_eq!(
|
||||
/// time!(01:02:03.004_005_006).replace_microsecond(7_008),
|
||||
/// Ok(time!(01:02:03.007_008))
|
||||
|
@ -564,7 +559,7 @@ impl Time {
|
|||
/// Replace the nanoseconds within the second.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::time;
|
||||
/// # use time_macros::time;
|
||||
/// assert_eq!(
|
||||
/// time!(01:02:03.004_005_006).replace_nanosecond(7_008_009),
|
||||
/// Ok(time!(01:02:03.007_008_009))
|
||||
|
@ -599,7 +594,8 @@ impl Time {
|
|||
/// Format the `Time` using the provided [format description](crate::format_description).
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::{format_description, macros::time};
|
||||
/// # use time::format_description;
|
||||
/// # use time_macros::time;
|
||||
/// let format = format_description::parse("[hour]:[minute]:[second]")?;
|
||||
/// assert_eq!(time!(12:00).format(&format)?, "12:00:00");
|
||||
/// # Ok::<_, time::Error>(())
|
||||
|
@ -618,8 +614,9 @@ impl Time {
|
|||
/// description](crate::format_description).
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::{format_description, macros::time, Time};
|
||||
/// let format = format_description::parse("[hour]:[minute]:[second]")?;
|
||||
/// # use time::Time;
|
||||
/// # use time_macros::{time, format_description};
|
||||
/// let format = format_description!("[hour]:[minute]:[second]");
|
||||
/// assert_eq!(Time::parse("12:00:00", &format)?, time!(12:00));
|
||||
/// # Ok::<_, time::Error>(())
|
||||
/// ```
|
||||
|
@ -646,15 +643,17 @@ impl fmt::Display for Time {
|
|||
};
|
||||
write!(
|
||||
f,
|
||||
"{}:{:02}:{:02}.{:0width$}",
|
||||
self.hour,
|
||||
self.minute,
|
||||
self.second,
|
||||
value,
|
||||
width = width
|
||||
"{}:{:02}:{:02}.{value:0width$}",
|
||||
self.hour, self.minute, self.second,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Time {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Display::fmt(self, f)
|
||||
}
|
||||
}
|
||||
// endregion formatting & parsing
|
||||
|
||||
// region: trait impls
|
||||
|
@ -664,7 +663,8 @@ impl Add<Duration> for Time {
|
|||
/// Add the sub-day time of the [`Duration`] to the `Time`. Wraps on overflow.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::{ext::NumericalDuration, macros::time};
|
||||
/// # use time::ext::NumericalDuration;
|
||||
/// # use time_macros::time;
|
||||
/// assert_eq!(time!(12:00) + 2.hours(), time!(14:00));
|
||||
/// assert_eq!(time!(0:00:01) + (-2).seconds(), time!(23:59:59));
|
||||
/// ```
|
||||
|
@ -679,7 +679,8 @@ impl Add<StdDuration> for Time {
|
|||
/// Add the sub-day time of the [`std::time::Duration`] to the `Time`. Wraps on overflow.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::{ext::NumericalStdDuration, macros::time};
|
||||
/// # use time::ext::NumericalStdDuration;
|
||||
/// # use time_macros::time;
|
||||
/// assert_eq!(time!(12:00) + 2.std_hours(), time!(14:00));
|
||||
/// assert_eq!(time!(23:59:59) + 2.std_seconds(), time!(0:00:01));
|
||||
/// ```
|
||||
|
@ -696,7 +697,8 @@ impl Sub<Duration> for Time {
|
|||
/// Subtract the sub-day time of the [`Duration`] from the `Time`. Wraps on overflow.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::{ext::NumericalDuration, macros::time};
|
||||
/// # use time::ext::NumericalDuration;
|
||||
/// # use time_macros::time;
|
||||
/// assert_eq!(time!(14:00) - 2.hours(), time!(12:00));
|
||||
/// assert_eq!(time!(23:59:59) - (-2).seconds(), time!(0:00:01));
|
||||
/// ```
|
||||
|
@ -711,7 +713,8 @@ impl Sub<StdDuration> for Time {
|
|||
/// Subtract the sub-day time of the [`std::time::Duration`] from the `Time`. Wraps on overflow.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::{ext::NumericalStdDuration, macros::time};
|
||||
/// # use time::ext::NumericalStdDuration;
|
||||
/// # use time_macros::time;
|
||||
/// assert_eq!(time!(14:00) - 2.std_hours(), time!(12:00));
|
||||
/// assert_eq!(time!(0:00:01) - 2.std_seconds(), time!(23:59:59));
|
||||
/// ```
|
||||
|
@ -729,7 +732,8 @@ impl Sub for Time {
|
|||
/// the same calendar day.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::{ext::NumericalDuration, macros::time};
|
||||
/// # use time::ext::NumericalDuration;
|
||||
/// # use time_macros::time;
|
||||
/// assert_eq!(time!(0:00) - time!(0:00), 0.seconds());
|
||||
/// assert_eq!(time!(1:00) - time!(0:00), 1.hours());
|
||||
/// assert_eq!(time!(0:00) - time!(1:00), (-1).hours());
|
||||
|
@ -738,15 +742,20 @@ impl Sub for Time {
|
|||
fn sub(self, rhs: Self) -> Self::Output {
|
||||
let hour_diff = (self.hour as i8) - (rhs.hour as i8);
|
||||
let minute_diff = (self.minute as i8) - (rhs.minute as i8);
|
||||
let mut second_diff = (self.second as i8) - (rhs.second as i8);
|
||||
let mut nanosecond_diff = (self.nanosecond as i32) - (rhs.nanosecond as i32);
|
||||
let second_diff = (self.second as i8) - (rhs.second as i8);
|
||||
let nanosecond_diff = (self.nanosecond as i32) - (rhs.nanosecond as i32);
|
||||
|
||||
cascade!(nanosecond_diff in 0..1_000_000_000 => second_diff);
|
||||
let seconds = hour_diff as i64 * 3_600 + minute_diff as i64 * 60 + second_diff as i64;
|
||||
|
||||
Duration::new_unchecked(
|
||||
hour_diff as i64 * 3_600 + minute_diff as i64 * 60 + second_diff as i64,
|
||||
nanosecond_diff,
|
||||
)
|
||||
let (seconds, nanoseconds) = if seconds > 0 && nanosecond_diff < 0 {
|
||||
(seconds - 1, nanosecond_diff + 1_000_000_000)
|
||||
} else if seconds < 0 && nanosecond_diff > 0 {
|
||||
(seconds + 1, nanosecond_diff - 1_000_000_000)
|
||||
} else {
|
||||
(seconds, nanosecond_diff)
|
||||
};
|
||||
|
||||
Duration::new_unchecked(seconds, nanoseconds)
|
||||
}
|
||||
}
|
||||
// endregion trait impls
|
||||
|
|
|
@ -20,7 +20,7 @@ use crate::OffsetDateTime;
|
|||
/// This struct can store values up to ±23:59:59. If you need support outside this range, please
|
||||
/// file an issue with your use case.
|
||||
// All three components _must_ have the same sign.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct UtcOffset {
|
||||
#[allow(clippy::missing_docs_in_private_items)]
|
||||
hours: i8,
|
||||
|
@ -34,7 +34,8 @@ impl UtcOffset {
|
|||
/// A `UtcOffset` that is UTC.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::{UtcOffset, macros::offset};
|
||||
/// # use time::UtcOffset;
|
||||
/// # use time_macros::offset;
|
||||
/// assert_eq!(UtcOffset::UTC, offset!(UTC));
|
||||
/// ```
|
||||
pub const UTC: Self = Self::__from_hms_unchecked(0, 0, 0);
|
||||
|
@ -45,6 +46,22 @@ impl UtcOffset {
|
|||
/// sign.
|
||||
#[doc(hidden)]
|
||||
pub const fn __from_hms_unchecked(hours: i8, minutes: i8, seconds: i8) -> Self {
|
||||
if hours < 0 {
|
||||
debug_assert!(minutes <= 0);
|
||||
debug_assert!(seconds <= 0);
|
||||
} else if hours > 0 {
|
||||
debug_assert!(minutes >= 0);
|
||||
debug_assert!(seconds >= 0);
|
||||
}
|
||||
if minutes < 0 {
|
||||
debug_assert!(seconds <= 0);
|
||||
} else if minutes > 0 {
|
||||
debug_assert!(seconds >= 0);
|
||||
}
|
||||
debug_assert!(hours.unsigned_abs() < 24);
|
||||
debug_assert!(minutes.unsigned_abs() < 60);
|
||||
debug_assert!(seconds.unsigned_abs() < 60);
|
||||
|
||||
Self {
|
||||
hours,
|
||||
minutes,
|
||||
|
@ -110,7 +127,7 @@ impl UtcOffset {
|
|||
/// will always match. A positive value indicates an offset to the east; a negative to the west.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::offset;
|
||||
/// # use time_macros::offset;
|
||||
/// assert_eq!(offset!(+1:02:03).as_hms(), (1, 2, 3));
|
||||
/// assert_eq!(offset!(-1:02:03).as_hms(), (-1, -2, -3));
|
||||
/// ```
|
||||
|
@ -122,7 +139,7 @@ impl UtcOffset {
|
|||
/// offset to the east; a negative to the west.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::offset;
|
||||
/// # use time_macros::offset;
|
||||
/// assert_eq!(offset!(+1:02:03).whole_hours(), 1);
|
||||
/// assert_eq!(offset!(-1:02:03).whole_hours(), -1);
|
||||
/// ```
|
||||
|
@ -134,7 +151,7 @@ impl UtcOffset {
|
|||
/// offset to the east; a negative to the west.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::offset;
|
||||
/// # use time_macros::offset;
|
||||
/// assert_eq!(offset!(+1:02:03).whole_minutes(), 62);
|
||||
/// assert_eq!(offset!(-1:02:03).whole_minutes(), -62);
|
||||
/// ```
|
||||
|
@ -146,7 +163,7 @@ impl UtcOffset {
|
|||
/// indicates an offset to the east; a negative to the west.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::offset;
|
||||
/// # use time_macros::offset;
|
||||
/// assert_eq!(offset!(+1:02:03).minutes_past_hour(), 2);
|
||||
/// assert_eq!(offset!(-1:02:03).minutes_past_hour(), -2);
|
||||
/// ```
|
||||
|
@ -158,7 +175,7 @@ impl UtcOffset {
|
|||
/// offset to the east; a negative to the west.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::offset;
|
||||
/// # use time_macros::offset;
|
||||
/// assert_eq!(offset!(+1:02:03).whole_seconds(), 3723);
|
||||
/// assert_eq!(offset!(-1:02:03).whole_seconds(), -3723);
|
||||
/// ```
|
||||
|
@ -172,7 +189,7 @@ impl UtcOffset {
|
|||
/// indicates an offset to the east; a negative to the west.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::offset;
|
||||
/// # use time_macros::offset;
|
||||
/// assert_eq!(offset!(+1:02:03).seconds_past_minute(), 3);
|
||||
/// assert_eq!(offset!(-1:02:03).seconds_past_minute(), -3);
|
||||
/// ```
|
||||
|
@ -186,7 +203,7 @@ impl UtcOffset {
|
|||
///
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::offset;
|
||||
/// # use time_macros::offset;
|
||||
/// assert!(!offset!(+1:02:03).is_utc());
|
||||
/// assert!(!offset!(-1:02:03).is_utc());
|
||||
/// assert!(offset!(UTC).is_utc());
|
||||
|
@ -198,7 +215,7 @@ impl UtcOffset {
|
|||
/// Check if the offset is positive, or east of UTC.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::offset;
|
||||
/// # use time_macros::offset;
|
||||
/// assert!(offset!(+1:02:03).is_positive());
|
||||
/// assert!(!offset!(-1:02:03).is_positive());
|
||||
/// assert!(!offset!(UTC).is_positive());
|
||||
|
@ -210,7 +227,7 @@ impl UtcOffset {
|
|||
/// Check if the offset is negative, or west of UTC.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::macros::offset;
|
||||
/// # use time_macros::offset;
|
||||
/// assert!(!offset!(+1:02:03).is_negative());
|
||||
/// assert!(offset!(-1:02:03).is_negative());
|
||||
/// assert!(!offset!(UTC).is_negative());
|
||||
|
@ -269,7 +286,8 @@ impl UtcOffset {
|
|||
/// Format the `UtcOffset` using the provided [format description](crate::format_description).
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::{format_description, macros::offset};
|
||||
/// # use time::format_description;
|
||||
/// # use time_macros::offset;
|
||||
/// let format = format_description::parse("[offset_hour sign:mandatory]:[offset_minute]")?;
|
||||
/// assert_eq!(offset!(+1).format(&format)?, "+01:00");
|
||||
/// # Ok::<_, time::Error>(())
|
||||
|
@ -285,8 +303,9 @@ impl UtcOffset {
|
|||
/// description](crate::format_description).
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::{format_description, macros::offset, UtcOffset};
|
||||
/// let format = format_description::parse("[offset_hour]:[offset_minute]")?;
|
||||
/// # use time::UtcOffset;
|
||||
/// # use time_macros::{offset, format_description};
|
||||
/// let format = format_description!("[offset_hour]:[offset_minute]");
|
||||
/// assert_eq!(UtcOffset::parse("-03:42", &format)?, offset!(-3:42));
|
||||
/// # Ok::<_, time::Error>(())
|
||||
/// ```
|
||||
|
@ -310,6 +329,12 @@ impl fmt::Display for UtcOffset {
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for UtcOffset {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Display::fmt(self, f)
|
||||
}
|
||||
}
|
||||
// endregion formatting & parsing
|
||||
|
||||
impl Neg for UtcOffset {
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
//! Utility functions.
|
||||
|
||||
pub use time_core::util::{days_in_year, is_leap_year, weeks_in_year};
|
||||
|
||||
use crate::Month;
|
||||
|
||||
/// Whether to adjust the date, and in which direction. Useful when implementing arithmetic.
|
||||
|
@ -27,54 +29,3 @@ pub const fn days_in_year_month(year: i32, month: Month) -> u8 {
|
|||
February => 28,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns if the provided year is a leap year in the proleptic Gregorian calendar. Uses
|
||||
/// [astronomical year numbering](https://en.wikipedia.org/wiki/Astronomical_year_numbering).
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::util::is_leap_year;
|
||||
/// assert!(!is_leap_year(1900));
|
||||
/// assert!(is_leap_year(2000));
|
||||
/// assert!(is_leap_year(2004));
|
||||
/// assert!(!is_leap_year(2005));
|
||||
/// assert!(!is_leap_year(2100));
|
||||
/// ```
|
||||
pub const fn is_leap_year(year: i32) -> bool {
|
||||
year % 4 == 0 && (year % 25 != 0 || year % 16 == 0)
|
||||
}
|
||||
|
||||
/// Get the number of calendar days in a given year.
|
||||
///
|
||||
/// The returned value will always be either 365 or 366.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::util::days_in_year;
|
||||
/// assert_eq!(days_in_year(1900), 365);
|
||||
/// assert_eq!(days_in_year(2000), 366);
|
||||
/// assert_eq!(days_in_year(2004), 366);
|
||||
/// assert_eq!(days_in_year(2005), 365);
|
||||
/// assert_eq!(days_in_year(2100), 365);
|
||||
/// ```
|
||||
pub const fn days_in_year(year: i32) -> u16 {
|
||||
if is_leap_year(year) { 366 } else { 365 }
|
||||
}
|
||||
|
||||
/// Get the number of weeks in the ISO year.
|
||||
///
|
||||
/// The returned value will always be either 52 or 53.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use time::util::weeks_in_year;
|
||||
/// assert_eq!(weeks_in_year(2019), 52);
|
||||
/// assert_eq!(weeks_in_year(2020), 53);
|
||||
/// ```
|
||||
pub const fn weeks_in_year(year: i32) -> u8 {
|
||||
match year.rem_euclid(400) {
|
||||
4 | 9 | 15 | 20 | 26 | 32 | 37 | 43 | 48 | 54 | 60 | 65 | 71 | 76 | 82 | 88 | 93 | 99
|
||||
| 105 | 111 | 116 | 122 | 128 | 133 | 139 | 144 | 150 | 156 | 161 | 167 | 172 | 178
|
||||
| 184 | 189 | 195 | 201 | 207 | 212 | 218 | 224 | 229 | 235 | 240 | 246 | 252 | 257
|
||||
| 263 | 268 | 274 | 280 | 285 | 291 | 296 | 303 | 308 | 314 | 320 | 325 | 331 | 336
|
||||
| 342 | 348 | 353 | 359 | 364 | 370 | 376 | 381 | 387 | 392 | 398 => 53,
|
||||
_ => 52,
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче