@@ -11,6 +11,7 @@ use core::{fmt, str::FromStr, time::Duration};
1111#[ cfg( feature = "std" ) ]
1212use std:: time:: { SystemTime , UNIX_EPOCH } ;
1313
14+ use const_range:: const_contains_u8;
1415#[ cfg( feature = "time" ) ]
1516use time:: PrimitiveDateTime ;
1617
@@ -69,18 +70,42 @@ impl DateTime {
6970 } ;
7071
7172 /// 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.
7290 // TODO(tarcieri): checked arithmetic
7391 #[ 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 > {
75100 // Basic validation of the components.
76101 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)
82107 {
83- return Err ( ErrorKind :: DateTime . into ( ) ) ;
108+ return None ;
84109 }
85110
86111 let leap_years =
@@ -102,28 +127,28 @@ impl DateTime {
102127 10 => ( 273 , 31 ) ,
103128 11 => ( 304 , 30 ) ,
104129 12 => ( 334 , 31 ) ,
105- _ => return Err ( ErrorKind :: DateTime . into ( ) ) ,
130+ _ => return None ,
106131 } ;
107132
108133 if day > mdays || day == 0 {
109- return Err ( ErrorKind :: DateTime . into ( ) ) ;
134+ return None ;
110135 }
111136
112- ydays += u16 :: from ( day) - 1 ;
137+ ydays += day as u16 - 1 ;
113138
114139 if is_leap_year && month > 2 {
115140 ydays += 1 ;
116141 }
117142
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 ) ;
120145 let unix_duration = Duration :: from_secs ( time + days * 86400 ) ;
121146
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 ;
124149 }
125150
126- Ok ( Self {
151+ Some ( Self {
127152 year,
128153 month,
129154 day,
@@ -136,7 +161,7 @@ impl DateTime {
136161
137162 /// Compute a [`DateTime`] from the given [`Duration`] since the `UNIX_EPOCH`.
138163 ///
139- /// Returns `None ` if the value is outside the supported date range.
164+ /// Returns `Err ` if the value is outside the supported date range.
140165 // TODO(tarcieri): checked arithmetic
141166 #[ allow( clippy:: arithmetic_side_effects) ]
142167 pub fn from_unix_duration ( unix_duration : Duration ) -> Result < Self > {
@@ -430,6 +455,16 @@ fn decode_year(year: &[u8; 4]) -> Result<u16> {
430455 Ok ( u16:: from ( hi) * 100 + u16:: from ( lo) )
431456}
432457
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+
433468#[ cfg( test) ]
434469#[ allow( clippy:: unwrap_used) ]
435470mod tests {
@@ -447,6 +482,29 @@ mod tests {
447482 assert ! ( !is_date_valid( 2100 , 2 , 29 , 0 , 0 , 0 ) ) ;
448483 }
449484
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+
450508 #[ test]
451509 fn from_str ( ) {
452510 let datetime = "2001-01-02T12:13:14Z" . parse :: < DateTime > ( ) . unwrap ( ) ;
0 commit comments