@@ -29,6 +29,64 @@ impl DateTime {
29
29
self . tz_minute
30
30
)
31
31
}
32
+
33
+ /// Returns true if the date is vald
34
+ pub fn is_valid ( & self ) -> bool {
35
+ ( 0 ..23 ) . contains ( & self . tz_hour )
36
+ && ( 0 ..59 ) . contains ( & self . tz_minute )
37
+ && ( 1970 ..2500 ) . contains ( & self . year )
38
+ && ( 1 ..12 ) . contains ( & self . month )
39
+ && ( 1 ..31 ) . contains ( & self . day )
40
+ && ( 0 ..23 ) . contains ( & self . hour )
41
+ && ( 0 ..59 ) . contains ( & self . minute )
42
+ && ( 0 ..59 ) . contains ( & self . second )
43
+ }
44
+
45
+ /// Returns the numbers of seconds since 1970-01-01T00:00:00Z (Unix epoch)
46
+ /// or None if the date is invalid.
47
+ pub fn to_timestamp ( & self ) -> Option < i64 > {
48
+ // Ported from https://github.com/protocolbuffers/upb/blob/22182e6e/upb/json_decode.c#L982-L992
49
+ if self . is_valid ( ) {
50
+ let year_base = 4800 ; /* Before min year, multiple of 400. */
51
+ let m_adj = self . month . overflowing_sub ( 3 ) . 0 ; /* March-based month. */
52
+ let carry = if m_adj > self . month { 1 } else { 0 } ;
53
+ let adjust = if carry > 0 { 12 } else { 0 } ;
54
+ let y_adj = self . year as i64 + year_base - carry;
55
+ let month_days = ( ( m_adj. overflowing_add ( adjust) . 0 ) * 62719 + 769 ) / 2048 ;
56
+ let leap_days = y_adj / 4 - y_adj / 100 + y_adj / 400 ;
57
+ ( ( y_adj * 365 + leap_days + month_days as i64 + ( self . day as i64 - 1 ) - 2472632 )
58
+ * 86400
59
+ + self . hour as i64 * 3600
60
+ + self . minute as i64 * 60
61
+ + self . second as i64
62
+ + ( ( self . tz_hour as i64 * 3600 + self . tz_minute as i64 * 60 )
63
+ * if self . tz_before_gmt { 1 } else { -1 } ) )
64
+ . into ( )
65
+ } else {
66
+ None
67
+ }
68
+ }
69
+ }
70
+
71
+ impl PartialOrd for DateTime {
72
+ fn partial_cmp ( & self , other : & Self ) -> Option < std:: cmp:: Ordering > {
73
+ match self . to_timestamp ( ) ? - other. to_timestamp ( ) ? {
74
+ 0 => std:: cmp:: Ordering :: Equal ,
75
+ x if x > 0 => std:: cmp:: Ordering :: Greater ,
76
+ _ => std:: cmp:: Ordering :: Less ,
77
+ }
78
+ . into ( )
79
+ }
80
+ }
81
+
82
+ impl Ord for DateTime {
83
+ fn cmp ( & self , other : & Self ) -> std:: cmp:: Ordering {
84
+ match self . to_timestamp ( ) . unwrap_or_default ( ) - other. to_timestamp ( ) . unwrap_or_default ( ) {
85
+ 0 => std:: cmp:: Ordering :: Equal ,
86
+ x if x > 0 => std:: cmp:: Ordering :: Greater ,
87
+ _ => std:: cmp:: Ordering :: Less ,
88
+ }
89
+ }
32
90
}
33
91
34
92
impl fmt:: Display for DateTime {
@@ -195,6 +253,8 @@ pub static MONTH_MAP: &[u8; 31] = &[
195
253
196
254
#[ cfg( test) ]
197
255
mod tests {
256
+ use chrono:: { FixedOffset , LocalResult , SecondsFormat , TimeZone , Utc } ;
257
+
198
258
use crate :: {
199
259
parsers:: { fields:: date:: parse_date, message:: MessageStream } ,
200
260
HeaderValue ,
@@ -265,9 +325,35 @@ mod tests {
265
325
for input in inputs {
266
326
let str = input. 0 . to_string ( ) ;
267
327
match parse_date ( & mut MessageStream :: new ( str. as_bytes ( ) ) ) {
268
- HeaderValue :: DateTime ( date) => {
269
- //println!("{} -> {}", input.0.escape_debug(), date.to_iso8601());
270
- assert_eq ! ( input. 1 , date. to_iso8601( ) ) ;
328
+ HeaderValue :: DateTime ( datetime) => {
329
+ assert_eq ! ( input. 1 , datetime. to_iso8601( ) ) ;
330
+
331
+ if datetime. is_valid ( ) {
332
+ if let LocalResult :: Single ( chrono_datetime)
333
+ | LocalResult :: Ambiguous ( chrono_datetime, _) = FixedOffset :: west_opt (
334
+ ( ( datetime. tz_hour as i32 * 3600i32 ) + datetime. tz_minute as i32 * 60 )
335
+ * if datetime. tz_before_gmt { 1i32 } else { -1i32 } ,
336
+ )
337
+ . unwrap_or_else ( || FixedOffset :: east ( 0 ) )
338
+ . ymd_opt ( datetime. year as i32 , datetime. month , datetime. day )
339
+ . and_hms_opt ( datetime. hour , datetime. minute , datetime. second )
340
+ {
341
+ assert_eq ! (
342
+ chrono_datetime. timestamp( ) ,
343
+ datetime. to_timestamp( ) . unwrap( ) ,
344
+ "{} -> {} ({}) -> {} ({})" ,
345
+ input. 0 . escape_debug( ) ,
346
+ datetime. to_timestamp( ) . unwrap( ) ,
347
+ Utc . timestamp_opt( datetime. to_timestamp( ) . unwrap( ) , 0 )
348
+ . unwrap( )
349
+ . to_rfc3339_opts( SecondsFormat :: Secs , true ) ,
350
+ chrono_datetime. timestamp( ) ,
351
+ Utc . timestamp_opt( chrono_datetime. timestamp( ) , 0 )
352
+ . unwrap( )
353
+ . to_rfc3339_opts( SecondsFormat :: Secs , true )
354
+ ) ;
355
+ }
356
+ }
271
357
}
272
358
HeaderValue :: Empty => {
273
359
//println!("{} -> None", input.0.escape_debug());
0 commit comments