Skip to content

Commit 3c4443a

Browse files
authored
Add formatting options for DateTime (#377)
* Add formatting options for DateTime * Fix CI
1 parent 63a31d9 commit 3c4443a

21 files changed

+695
-54
lines changed

README.md

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -69,18 +69,18 @@ Take a look at <https://bencher.dev/perf/wtx> to see all low-level benchmarks ov
6969

7070
These numbers provide an estimate of the expected waiting times when developing with `WTX`. If desired, you can compare them with other similar Rust projects through the `dev-bench.sh` script.
7171

72-
| Technology | Required Deps¹ | All Deps² | Clean Check | Clean Debug Build | Clean Opt Build | Opt size |
73-
| --------------------- | -------------- | -------------- | ----------- | ----------------- | --------------- | -------- |
74-
| Client API Framework | 0 | 31 | 6.22s | 7.77s | 9.45s | 872K |
75-
| gRPC Client | 2 | 16 | 4.81s | 5.99s | 7.31s | 736K |
76-
| HTTP Client Pool | 2 | 15 | 4.67s | 6.04s | 7.06s | 728K |
77-
| HTTP Server Framework | 2 | 37 | 8.17s | 10.69s | 11.56s | 996K |
78-
| Postgres Client | 13 | 30 | 5.06s | 6.10s | 6.86s | 652K |
79-
| WebSocket Client | 10 | 22 | 4.34s | 4.92s | 5.64s | 560K |
80-
81-
<sup>***¹*** Internal dependencies required by the feature.</sup>
82-
83-
<sup>***²*** The sum of optional and required dependencies used by the associated binaries.</sup>
72+
| Technology | Required Deps[^1] | All Deps[^2] | Clean Check | Clean Debug Build | Clean Opt Build | Opt size |
73+
| --------------------- | ----------------- | ----------------- | ----------- | ----------------- | --------------- | -------- |
74+
| Client API Framework | 0 | 31 | 6.22s | 7.77s | 9.45s | 872K |
75+
| gRPC Client | 2 | 16 | 4.81s | 5.99s | 7.31s | 736K |
76+
| HTTP Client Pool | 2 | 15 | 4.67s | 6.04s | 7.06s | 728K |
77+
| HTTP Server Framework | 2 | 37 | 8.17s | 10.69s | 11.56s | 996K |
78+
| Postgres Client | 13 | 30 | 5.06s | 6.10s | 6.86s | 652K |
79+
| WebSocket Client | 10 | 22 | 4.34s | 4.92s | 5.64s | 560K |
80+
81+
[^1]: Internal dependencies required by the feature.
82+
83+
[^2]: The sum of optional and required dependencies used by the associated binaries.
8484

8585
## Transport Layer Security (TLS)
8686

wtx-fuzz/Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
[[bin]]
2+
name = "date_time"
3+
path = "date_time.rs"
4+
required-features = ["libfuzzer-sys/link_libfuzzer"]
5+
16
[[bin]]
27
name = "deque"
38
path = "deque.rs"

wtx-fuzz/date_time.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
//! DateTime
2+
3+
#![no_main]
4+
5+
use wtx::time::DateTime;
6+
7+
libfuzzer_sys::fuzz_target!(|(value, fmt): (Vec<u8>, Vec<u8>)| {
8+
let _rslt = DateTime::parse(&value, &value);
9+
});

wtx-fuzz/deque.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
//! Deque
22
3-
#![expect(clippy::unwrap_used, reason = "does not matter")]
43
#![no_main]
54

65
use tokio::runtime::Builder;

wtx/src/collection/deque.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -814,7 +814,7 @@ impl<T> Default for Deque<T> {
814814
}
815815

816816
struct ReserveRslt {
817-
/// Starting index where the `additional` number of elements can be inserted.
817+
/// Starting index where the `additional` number of elements can be inserted.
818818
begin: usize,
819819
/// The number os places the head must be shift.
820820
head_shift: usize,

wtx/src/http/cookie.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ pub use same_site::SameSite;
1414
const NONCE_LEN: usize = 12;
1515
const TAG_LEN: usize = 16;
1616

17-
static FMT1: &str = "%a, %d %b %Y %H:%M:%S GMT";
18-
static FMT2: &str = "%A, %d-%b-%y %H:%M:%S GMT";
19-
static FMT3: &str = "%a %b %e %H:%M:%S %Y";
20-
static FMT4: &str = "%a, %d-%b-%Y %H:%M:%S GMT";
17+
static FMT1: &[u8] = b"%a, %d %b %Y %H:%M:%S GMT";
18+
static FMT2: &[u8] = b"%A, %d-%b-%y %H:%M:%S GMT";
19+
static FMT3: &[u8] = b"%a %b %e %H:%M:%S %Y";
20+
static FMT4: &[u8] = b"%a, %d-%b-%Y %H:%M:%S GMT";
2121

2222
#[cfg(feature = "http-cookie-secure")]
2323
pub(crate) fn decrypt<'buffer>(

wtx/src/http/cookie/cookie_generic.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
use crate::{
22
http::cookie::{FMT1, SameSite},
33
misc::Lease,
4+
time::DateTime,
45
};
5-
use chrono::{DateTime, Utc};
66
use core::{
77
fmt::{Display, Formatter},
88
time::Duration,
@@ -11,7 +11,7 @@ use core::{
1111
#[derive(Debug)]
1212
pub(crate) struct CookieGeneric<T, V> {
1313
pub(crate) domain: T,
14-
pub(crate) expires: Option<DateTime<Utc>>,
14+
pub(crate) expires: Option<DateTime>,
1515
pub(crate) http_only: bool,
1616
pub(crate) max_age: Option<Duration>,
1717
pub(crate) name: T,
@@ -53,7 +53,10 @@ where
5353
f.write_fmt(format_args!("; Domain={}", self.domain.lease()))?;
5454
}
5555
if let Some(elem) = self.expires {
56-
f.write_fmt(format_args!("; Expires={}", elem.format(FMT1)))?;
56+
f.write_fmt(format_args!(
57+
"; Expires={}",
58+
elem.format(FMT1).map_err(|_err| core::fmt::Error)?.as_str()
59+
))?;
5760
}
5861
if self.http_only {
5962
f.write_str("; HttpOnly")?;

wtx/src/http/cookie/cookie_str.rs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ use crate::{
55
cookie::{FMT1, FMT2, FMT3, FMT4, SameSite, cookie_generic::CookieGeneric, make_lowercase},
66
},
77
misc::{PercentDecode, str_split_once1, str_split1},
8+
time::DateTime,
89
};
9-
use chrono::{DateTime, NaiveDateTime, Utc};
1010
use core::{str, time::Duration};
1111

1212
/// A cookie is a small piece of data a server sends to a user's web browser.
@@ -81,11 +81,10 @@ impl<'str> CookieStr<'str> {
8181
cookie.domain = value;
8282
}
8383
(b"expires", [_, ..]) => {
84-
if let Ok(elem) = NaiveDateTime::parse_from_str(value, FMT1)
85-
.or_else(|_| NaiveDateTime::parse_from_str(value, FMT2))
86-
.or_else(|_| NaiveDateTime::parse_from_str(value, FMT3))
87-
.or_else(|_| NaiveDateTime::parse_from_str(value, FMT4))
88-
.map(|elem| DateTime::from_naive_utc_and_offset(elem, Utc))
84+
if let Ok(elem) = DateTime::parse(value.as_bytes(), FMT1)
85+
.or_else(|_| DateTime::parse(value.as_bytes(), FMT2))
86+
.or_else(|_| DateTime::parse(value.as_bytes(), FMT3))
87+
.or_else(|_| DateTime::parse(value.as_bytes(), FMT4))
8988
{
9089
cookie.expires = Some(elem)
9190
}

wtx/src/http/session/session_manager.rs

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,8 @@ use crate::{
88
},
99
misc::{Lease, LeaseMut, Lock},
1010
rng::Rng,
11-
time::TimeError,
11+
time::{DateTime, Instant},
1212
};
13-
use chrono::{DateTime, TimeDelta, Utc};
1413
use core::{
1514
fmt::{Debug, Formatter},
1615
marker::PhantomData,
@@ -93,12 +92,8 @@ where
9392
elem
9493
}
9594
(Some(_), Some(max_age)) | (None, Some(max_age)) => {
96-
let Some(expires_at) = TimeDelta::from_std(max_age)
97-
.ok()
98-
.and_then(|element| Utc::now().checked_add_signed(element))
99-
else {
100-
return Err(crate::Error::from(TimeError::InstantNeedsBackend).into());
101-
};
95+
let timestamp = Instant::now().checked_add(max_age)?.timestamp()?.as_secs().cast_signed();
96+
let expires_at = DateTime::from_timestamp_secs(timestamp)?;
10297
let elem = SessionState::new(custom_state, Some(expires_at), session_csrf, session_key);
10398
store.create(&elem).await?;
10499
elem
@@ -143,7 +138,7 @@ where
143138
) -> crate::Result<()> {
144139
let prev_expires = cookie_def.expires;
145140
let prev_max_age = cookie_def.max_age;
146-
cookie_def.expires = Some(DateTime::from_timestamp_nanos(0));
141+
cookie_def.expires = Some(DateTime::EPOCH);
147142
cookie_def.max_age = None;
148143
cookie_def.value.clear();
149144
let rslt = headers.push_from_fmt(Header::from_name_and_value(

wtx/src/http/session/session_manager_builder.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ use crate::{
77
},
88
misc::{Lock, sleep},
99
rng::CryptoRng,
10+
time::DateTime,
1011
};
11-
use chrono::{DateTime, Utc};
1212
use core::{marker::PhantomData, time::Duration};
1313

1414
/// Default and optional parameters for the construction of a [`SessionManager`].
@@ -111,7 +111,7 @@ impl SessionManagerBuilder {
111111
/// If [Self::max_age] is set, then this parameter is ignored when setting the cookie in the
112112
/// header.
113113
#[inline]
114-
pub fn expires(mut self, elem: Option<DateTime<Utc>>) -> Self {
114+
pub fn expires(mut self, elem: Option<DateTime>) -> Self {
115115
self.cookie_def.expires = elem;
116116
self
117117
}

wtx/src/http/session/session_middleware.rs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,9 @@ use crate::{
88
},
99
misc::{Lease, LeaseMut, Lock},
1010
pool::{Pool, ResourceManager},
11-
time::Instant,
11+
time::{DateTime, Instant},
1212
};
1313
use alloc::string::String;
14-
use chrono::DateTime;
1514
use core::ops::ControlFlow;
1615
use serde::de::DeserializeOwned;
1716

@@ -63,8 +62,8 @@ where
6362
let SessionManagerInner { cookie_def, session_secret, .. } = &mut *session_guard;
6463
if let Some(elem) = ca.lease() {
6564
if let Some(expires) = &elem.expires_at {
66-
let millis = i64::try_from(Instant::now_timestamp()?.as_millis()).unwrap_or_default();
67-
let date_time = DateTime::from_timestamp_millis(millis).unwrap_or_default();
65+
let timestamp = Instant::now_timestamp()?.as_secs().cast_signed();
66+
let date_time = DateTime::from_timestamp_secs(timestamp)?;
6867
if expires >= &date_time {
6968
let _rslt =
7069
self.session_store.get(&(), &()).await?.lease_mut().delete(&elem.session_key).await;
@@ -101,8 +100,10 @@ where
101100
cookie_def.value.clear();
102101
json_rslt.map_err(Into::into)?
103102
};
104-
let ss_db_opt =
105-
self.session_store.get(&(), &()).await?.lease_mut().read(ss_des.session_key).await?;
103+
let ss_db_opt = {
104+
let mut lock = self.session_store.get(&(), &()).await?;
105+
lock.lease_mut().read(ss_des.session_key).await?
106+
};
106107
let Some(ss_db) = ss_db_opt else {
107108
return Err(crate::Error::from(SessionError::MissingStoredSession).into());
108109
};

wtx/src/http/session/session_state.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
1-
use crate::http::session::{SessionCsrf, SessionKey};
2-
use chrono::{DateTime, Utc};
1+
use crate::{
2+
http::session::{SessionCsrf, SessionKey},
3+
time::DateTime,
4+
};
35

46
/// Data that is saved in the corresponding store.
57
#[derive(Clone, Copy, Debug, PartialEq, serde::Deserialize, serde::Serialize)]
68
pub struct SessionState<CS> {
79
/// Custom state
810
pub custom_state: CS,
911
/// Cookie expiration
10-
pub expires_at: Option<DateTime<Utc>>,
12+
pub expires_at: Option<DateTime>,
1113
/// CSRF token
1214
pub session_csrf: SessionCsrf,
1315
/// Identifier
@@ -19,7 +21,7 @@ impl<CS> SessionState<CS> {
1921
#[inline]
2022
pub const fn new(
2123
custom_state: CS,
22-
expires_at: Option<DateTime<Utc>>,
24+
expires_at: Option<DateTime>,
2325
session_csrf: SessionCsrf,
2426
session_key: SessionKey,
2527
) -> Self {

wtx/src/misc/num_array.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use crate::collection::ArrayString;
22
use core::ops::{DivAssign, Rem};
33

44
pub(crate) type I16String = ArrayString<6>;
5+
pub(crate) type U8String = ArrayString<3>;
56
pub(crate) type U16String = ArrayString<5>;
67
pub(crate) type U32String = ArrayString<10>;
78
pub(crate) type U64String = ArrayString<20>;
@@ -12,6 +13,12 @@ pub fn i16_string(value: i16) -> I16String {
1213
num_string::<true, 6, 6, i16>(value, i16::abs)
1314
}
1415

16+
/// Transforms an `u8` into an [`ArrayString`].
17+
#[inline]
18+
pub fn u8_string(value: u8) -> U8String {
19+
num_string::<false, 3, 3, u8>(value, |el| el)
20+
}
21+
1522
/// Transforms an `u16` into an [`ArrayString`].
1623
#[inline]
1724
pub fn u16_string(value: u16) -> U16String {

wtx/src/time.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ mod second;
1717
#[allow(clippy::module_inception, reason = "there isn't a better name")]
1818
mod time;
1919
mod time_error;
20+
mod weekday;
2021
mod year;
2122

2223
pub use ce_days::CeDays;
@@ -34,6 +35,7 @@ pub use nanosecond::Nanosecond;
3435
pub use second::Second;
3536
pub use time::Time;
3637
pub use time_error::TimeError;
38+
pub use weekday::Weekday;
3739
pub use year::Year;
3840

3941
pub(crate) const DAYS_PER_4_YEARS: u16 = 1_461;

wtx/src/time/date.rs

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use crate::{
88
misc::{Usize, i16_string},
99
time::{
1010
CeDays, DAYS_OF_MONTHS, DAYS_PER_4_YEARS, DAYS_PER_400_YEARS_I32, DAYS_PER_NON_LEAP_YEAR_I16,
11-
DAYS_PER_NON_LEAP_YEAR_U16, Day, DayOfYear, Month, TimeError, Year,
11+
DAYS_PER_NON_LEAP_YEAR_U16, Day, DayOfYear, Month, TimeError, Weekday, Year,
1212
misc::{boolu16, boolu32, boolusize, i16i32, u8i16, u8u16, u8u32, u8usize, u16i32, u16u32},
1313
},
1414
};
@@ -51,6 +51,12 @@ pub struct Date {
5151
}
5252

5353
impl Date {
54+
/// Instance that refers the common era (0001-01-01).
55+
pub const CE: Self = if let Ok(elem) = Self::new(Year::CE, DayOfYear::MIN) {
56+
elem
57+
} else {
58+
panic!();
59+
};
5460
/// Instance that refers the UNIX epoch (1970-01-01).
5561
pub const EPOCH: Self = if let Ok(elem) = Self::new(Year::EPOCH, DayOfYear::MIN) {
5662
elem
@@ -185,6 +191,20 @@ impl Date {
185191
array
186192
}
187193

194+
/// Day of week.
195+
#[inline]
196+
pub const fn weekday(&self) -> Weekday {
197+
match self.ce_days() % 7 {
198+
-6 | 1 => Weekday::Monday,
199+
-5 | 2 => Weekday::Tuesday,
200+
-4 | 3 => Weekday::Wednesday,
201+
-3 | 4 => Weekday::Thursday,
202+
-2 | 5 => Weekday::Friday,
203+
-1 | 6 => Weekday::Saturday,
204+
_ => Weekday::Sunday,
205+
}
206+
}
207+
188208
/// Year
189209
#[inline]
190210
pub const fn year(self) -> Year {
@@ -256,7 +276,7 @@ const fn years_from_quadricentury_days(days: i32) -> Option<(i16, DayOfYear)> {
256276

257277
#[cfg(test)]
258278
mod tests {
259-
use crate::time::{CeDays, DAYS_PER_400_YEARS_I32, Date, DayOfYear, Year};
279+
use crate::time::{CeDays, DAYS_PER_400_YEARS_I32, Date, DayOfYear, Weekday, Year};
260280

261281
fn _0401_03_02() -> Date {
262282
Date::from_ce_days(CeDays::from_num(DAYS_PER_400_YEARS_I32 + 59 + 2).unwrap()).unwrap()
@@ -268,6 +288,7 @@ mod tests {
268288

269289
#[test]
270290
fn ce_days() {
291+
assert_eq!(Date::CE.ce_days(), 1);
271292
assert_eq!(Date::MIN.ce_days(), -11968265);
272293
assert_eq!(Date::MAX.ce_days(), 11967535);
273294
assert_eq!(_0401_03_02().ce_days(), DAYS_PER_400_YEARS_I32 + 59 + 2);
@@ -314,6 +335,29 @@ mod tests {
314335
assert_eq!(_2025_04_20().to_str().as_str(), "2025-04-20");
315336
}
316337

338+
#[test]
339+
fn weekday() {
340+
assert_eq!(Date::from_ce_days((-9).try_into().unwrap()).unwrap().weekday(), Weekday::Friday);
341+
assert_eq!(Date::from_ce_days((-8).try_into().unwrap()).unwrap().weekday(), Weekday::Saturday);
342+
assert_eq!(Date::from_ce_days((-7).try_into().unwrap()).unwrap().weekday(), Weekday::Sunday);
343+
assert_eq!(Date::from_ce_days((-6).try_into().unwrap()).unwrap().weekday(), Weekday::Monday);
344+
assert_eq!(Date::from_ce_days((-5).try_into().unwrap()).unwrap().weekday(), Weekday::Tuesday);
345+
assert_eq!(Date::from_ce_days((-4).try_into().unwrap()).unwrap().weekday(), Weekday::Wednesday);
346+
assert_eq!(Date::from_ce_days((-3).try_into().unwrap()).unwrap().weekday(), Weekday::Thursday);
347+
assert_eq!(Date::from_ce_days((-2).try_into().unwrap()).unwrap().weekday(), Weekday::Friday);
348+
assert_eq!(Date::from_ce_days((-1).try_into().unwrap()).unwrap().weekday(), Weekday::Saturday);
349+
assert_eq!(Date::from_ce_days(0.try_into().unwrap()).unwrap().weekday(), Weekday::Sunday);
350+
assert_eq!(Date::from_ce_days(1.try_into().unwrap()).unwrap().weekday(), Weekday::Monday);
351+
assert_eq!(Date::from_ce_days(2.try_into().unwrap()).unwrap().weekday(), Weekday::Tuesday);
352+
assert_eq!(Date::from_ce_days(3.try_into().unwrap()).unwrap().weekday(), Weekday::Wednesday);
353+
assert_eq!(Date::from_ce_days(4.try_into().unwrap()).unwrap().weekday(), Weekday::Thursday);
354+
assert_eq!(Date::from_ce_days(5.try_into().unwrap()).unwrap().weekday(), Weekday::Friday);
355+
assert_eq!(Date::from_ce_days(6.try_into().unwrap()).unwrap().weekday(), Weekday::Saturday);
356+
assert_eq!(Date::from_ce_days(7.try_into().unwrap()).unwrap().weekday(), Weekday::Sunday);
357+
assert_eq!(Date::from_ce_days(8.try_into().unwrap()).unwrap().weekday(), Weekday::Monday);
358+
assert_eq!(Date::from_ce_days(9.try_into().unwrap()).unwrap().weekday(), Weekday::Tuesday);
359+
}
360+
317361
#[test]
318362
fn year() {
319363
assert_eq!(Date::MIN.year().num(), -32767);

0 commit comments

Comments
 (0)