@@ -11,6 +11,7 @@ use core::{fmt, str::FromStr, time::Duration};
11
11
#[ cfg( feature = "std" ) ]
12
12
use std:: time:: { SystemTime , UNIX_EPOCH } ;
13
13
14
+ use const_range:: const_contains_u8;
14
15
#[ cfg( feature = "time" ) ]
15
16
use time:: PrimitiveDateTime ;
16
17
@@ -69,18 +70,42 @@ impl DateTime {
69
70
} ;
70
71
71
72
/// Create a new [`DateTime`] from the given UTC time components.
73
+ pub const fn new (
74
+ year : u16 ,
75
+ month : u8 ,
76
+ day : u8 ,
77
+ hour : u8 ,
78
+ minutes : u8 ,
79
+ seconds : u8 ,
80
+ ) -> Result < Self > {
81
+ match Self :: from_ymd_hms ( year, month, day, hour, minutes, seconds) {
82
+ Some ( date) => Ok ( date) ,
83
+ None => Err ( Error :: from_kind ( ErrorKind :: DateTime ) ) ,
84
+ }
85
+ }
86
+
87
+ /// Create a new [`DateTime`] from the given UTC time components.
88
+ ///
89
+ /// Returns `None` if the value is outside the supported date range.
72
90
// TODO(tarcieri): checked arithmetic
73
91
#[ allow( clippy:: arithmetic_side_effects) ]
74
- pub fn new ( year : u16 , month : u8 , day : u8 , hour : u8 , minutes : u8 , seconds : u8 ) -> Result < Self > {
92
+ pub ( crate ) const fn from_ymd_hms (
93
+ year : u16 ,
94
+ month : u8 ,
95
+ day : u8 ,
96
+ hour : u8 ,
97
+ minutes : u8 ,
98
+ seconds : u8 ,
99
+ ) -> Option < Self > {
75
100
// Basic validation of the components.
76
101
if year < MIN_YEAR
77
- || !( 1 ..=12 ) . contains ( & month)
78
- || !( 1 ..=31 ) . contains ( & day)
79
- || !( 0 ..=23 ) . contains ( & hour)
80
- || !( 0 ..=59 ) . contains ( & minutes)
81
- || !( 0 ..=59 ) . contains ( & seconds)
102
+ || !const_contains_u8 ( 1 ..=12 , month)
103
+ || !const_contains_u8 ( 1 ..=31 , day)
104
+ || !const_contains_u8 ( 0 ..=23 , hour)
105
+ || !const_contains_u8 ( 0 ..=59 , minutes)
106
+ || !const_contains_u8 ( 0 ..=59 , seconds)
82
107
{
83
- return Err ( ErrorKind :: DateTime . into ( ) ) ;
108
+ return None ;
84
109
}
85
110
86
111
let leap_years =
@@ -102,28 +127,28 @@ impl DateTime {
102
127
10 => ( 273 , 31 ) ,
103
128
11 => ( 304 , 30 ) ,
104
129
12 => ( 334 , 31 ) ,
105
- _ => return Err ( ErrorKind :: DateTime . into ( ) ) ,
130
+ _ => return None ,
106
131
} ;
107
132
108
133
if day > mdays || day == 0 {
109
- return Err ( ErrorKind :: DateTime . into ( ) ) ;
134
+ return None ;
110
135
}
111
136
112
- ydays += u16 :: from ( day) - 1 ;
137
+ ydays += day as u16 - 1 ;
113
138
114
139
if is_leap_year && month > 2 {
115
140
ydays += 1 ;
116
141
}
117
142
118
- let days = u64 :: from ( year - 1970 ) * 365 + u64 :: from ( leap_years) + u64:: from ( ydays) ;
119
- let time = u64 :: from ( seconds) + ( u64 :: from ( minutes) * 60 ) + ( u64 :: from ( hour) * 3600 ) ;
143
+ let days = ( ( year - 1970 ) as u64 ) * 365 + leap_years as u64 + ydays as u64 ;
144
+ let time = seconds as u64 + ( minutes as u64 * 60 ) + ( hour as u64 * 3600 ) ;
120
145
let unix_duration = Duration :: from_secs ( time + days * 86400 ) ;
121
146
122
- if unix_duration > MAX_UNIX_DURATION {
123
- return Err ( ErrorKind :: DateTime . into ( ) ) ;
147
+ if unix_duration. as_secs ( ) > MAX_UNIX_DURATION . as_secs ( ) {
148
+ return None ;
124
149
}
125
150
126
- Ok ( Self {
151
+ Some ( Self {
127
152
year,
128
153
month,
129
154
day,
@@ -136,7 +161,7 @@ impl DateTime {
136
161
137
162
/// Compute a [`DateTime`] from the given [`Duration`] since the `UNIX_EPOCH`.
138
163
///
139
- /// Returns `None ` if the value is outside the supported date range.
164
+ /// Returns `Err ` if the value is outside the supported date range.
140
165
// TODO(tarcieri): checked arithmetic
141
166
#[ allow( clippy:: arithmetic_side_effects) ]
142
167
pub fn from_unix_duration ( unix_duration : Duration ) -> Result < Self > {
@@ -430,6 +455,16 @@ fn decode_year(year: &[u8; 4]) -> Result<u16> {
430
455
Ok ( u16:: from ( hi) * 100 + u16:: from ( lo) )
431
456
}
432
457
458
+ mod const_range {
459
+ use core:: ops:: RangeInclusive ;
460
+
461
+ /// const [`RangeInclusive::contains`]
462
+ #[ inline]
463
+ pub const fn const_contains_u8 ( range : RangeInclusive < u8 > , item : u8 ) -> bool {
464
+ item >= * range. start ( ) && item <= * range. end ( )
465
+ }
466
+ }
467
+
433
468
#[ cfg( test) ]
434
469
#[ allow( clippy:: unwrap_used) ]
435
470
mod tests {
@@ -447,6 +482,29 @@ mod tests {
447
482
assert ! ( !is_date_valid( 2100 , 2 , 29 , 0 , 0 , 0 ) ) ;
448
483
}
449
484
485
+ #[ test]
486
+ fn invalid_dates ( ) {
487
+ assert ! ( !is_date_valid( 2 , 3 , 25 , 0 , 0 , 0 ) ) ;
488
+
489
+ assert ! ( is_date_valid( 1970 , 1 , 26 , 0 , 0 , 0 ) ) ;
490
+ assert ! ( !is_date_valid( 1969 , 1 , 26 , 0 , 0 , 0 ) ) ;
491
+ assert ! ( !is_date_valid( 1968 , 1 , 26 , 0 , 0 , 0 ) ) ;
492
+ assert ! ( !is_date_valid( 1600 , 1 , 26 , 0 , 0 , 0 ) ) ;
493
+
494
+ assert ! ( is_date_valid( 2039 , 2 , 27 , 0 , 0 , 0 ) ) ;
495
+ assert ! ( !is_date_valid( 2039 , 2 , 27 , 255 , 0 , 0 ) ) ;
496
+ assert ! ( !is_date_valid( 2039 , 2 , 27 , 0 , 255 , 0 ) ) ;
497
+ assert ! ( !is_date_valid( 2039 , 2 , 27 , 0 , 0 , 255 ) ) ;
498
+
499
+ assert ! ( is_date_valid( 2055 , 12 , 31 , 0 , 0 , 0 ) ) ;
500
+ assert ! ( is_date_valid( 2055 , 12 , 31 , 23 , 0 , 0 ) ) ;
501
+ assert ! ( !is_date_valid( 2055 , 12 , 31 , 24 , 0 , 0 ) ) ;
502
+ assert ! ( is_date_valid( 2055 , 12 , 31 , 0 , 59 , 0 ) ) ;
503
+ assert ! ( !is_date_valid( 2055 , 12 , 31 , 0 , 60 , 0 ) ) ;
504
+ assert ! ( is_date_valid( 2055 , 12 , 31 , 0 , 0 , 59 ) ) ;
505
+ assert ! ( !is_date_valid( 2055 , 12 , 31 , 0 , 0 , 60 ) ) ;
506
+ }
507
+
450
508
#[ test]
451
509
fn from_str ( ) {
452
510
let datetime = "2001-01-02T12:13:14Z" . parse :: < DateTime > ( ) . unwrap ( ) ;
0 commit comments