@@ -14,7 +14,7 @@ pub enum Error {
14
14
pub ( crate ) mod function {
15
15
use std:: { str:: FromStr , time:: SystemTime } ;
16
16
17
- use time :: { format_description :: well_known , Date , OffsetDateTime } ;
17
+ use jiff :: { civil :: Date , fmt :: rfc2822 , tz :: TimeZone , Zoned } ;
18
18
19
19
use crate :: {
20
20
parse:: { relative, Error } ,
@@ -32,27 +32,27 @@ pub(crate) mod function {
32
32
return Ok ( Time :: new ( 42 , 1800 ) ) ;
33
33
}
34
34
35
- Ok ( if let Ok ( val) = Date :: parse ( input , SHORT . 0 ) {
36
- let val = val. with_hms ( 0 , 0 , 0 ) . expect ( "date is in range" ) . assume_utc ( ) ;
37
- Time :: new ( val. unix_timestamp ( ) , val. offset ( ) . whole_seconds ( ) )
38
- } else if let Ok ( val) = OffsetDateTime :: parse ( input, & well_known :: Rfc2822 ) {
39
- Time :: new ( val. unix_timestamp ( ) , val. offset ( ) . whole_seconds ( ) )
40
- } else if let Ok ( val) = OffsetDateTime :: parse ( input , ISO8601 . 0 ) {
41
- Time :: new ( val. unix_timestamp ( ) , val. offset ( ) . whole_seconds ( ) )
42
- } else if let Ok ( val) = OffsetDateTime :: parse ( input , ISO8601_STRICT . 0 ) {
43
- Time :: new ( val. unix_timestamp ( ) , val. offset ( ) . whole_seconds ( ) )
44
- } else if let Ok ( val) = OffsetDateTime :: parse ( input , GITOXIDE . 0 ) {
45
- Time :: new ( val. unix_timestamp ( ) , val. offset ( ) . whole_seconds ( ) )
46
- } else if let Ok ( val) = OffsetDateTime :: parse ( input , DEFAULT . 0 ) {
47
- Time :: new ( val. unix_timestamp ( ) , val. offset ( ) . whole_seconds ( ) )
35
+ Ok ( if let Ok ( val) = Date :: strptime ( SHORT . 0 , input ) {
36
+ let val = val. to_zoned ( TimeZone :: UTC ) . expect ( "date is in range" ) ;
37
+ Time :: new ( val. timestamp ( ) . as_second ( ) , val. offset ( ) . seconds ( ) )
38
+ } else if let Ok ( val) = rfc2822_relaxed ( input) {
39
+ Time :: new ( val. timestamp ( ) . as_second ( ) , val. offset ( ) . seconds ( ) )
40
+ } else if let Ok ( val) = strptime_relaxed ( ISO8601 . 0 , input ) {
41
+ Time :: new ( val. timestamp ( ) . as_second ( ) , val. offset ( ) . seconds ( ) )
42
+ } else if let Ok ( val) = strptime_relaxed ( ISO8601_STRICT . 0 , input ) {
43
+ Time :: new ( val. timestamp ( ) . as_second ( ) , val. offset ( ) . seconds ( ) )
44
+ } else if let Ok ( val) = strptime_relaxed ( GITOXIDE . 0 , input ) {
45
+ Time :: new ( val. timestamp ( ) . as_second ( ) , val. offset ( ) . seconds ( ) )
46
+ } else if let Ok ( val) = strptime_relaxed ( DEFAULT . 0 , input ) {
47
+ Time :: new ( val. timestamp ( ) . as_second ( ) , val. offset ( ) . seconds ( ) )
48
48
} else if let Ok ( val) = SecondsSinceUnixEpoch :: from_str ( input) {
49
49
// Format::Unix
50
50
Time :: new ( val, 0 )
51
51
} else if let Some ( val) = parse_raw ( input) {
52
52
// Format::Raw
53
53
val
54
- } else if let Some ( time ) = relative:: parse ( input, now) . transpose ( ) ? {
55
- Time :: new ( time . unix_timestamp ( ) , time . offset ( ) . whole_seconds ( ) )
54
+ } else if let Some ( val ) = relative:: parse ( input, now) . transpose ( ) ? {
55
+ Time :: new ( val . timestamp ( ) . as_second ( ) , val . offset ( ) . seconds ( ) )
56
56
} else {
57
57
return Err ( Error :: InvalidDateString { input : input. into ( ) } ) ;
58
58
} )
@@ -83,52 +83,79 @@ pub(crate) mod function {
83
83
} ;
84
84
Some ( time)
85
85
}
86
+
87
+ /// This is just like `Zoned::strptime`, but it allows parsing datetimes
88
+ /// whose weekdays are inconsistent with the date. While the day-of-week
89
+ /// still must be parsed, it is otherwise ignored. This seems to be
90
+ /// consistent with how `git` behaves.
91
+ fn strptime_relaxed ( fmt : & str , input : & str ) -> Result < Zoned , jiff:: Error > {
92
+ let mut tm = jiff:: fmt:: strtime:: parse ( fmt, input) ?;
93
+ tm. set_weekday ( None ) ;
94
+ tm. to_zoned ( )
95
+ }
96
+
97
+ /// This is just like strptime_relaxed, except for RFC 2822 parsing.
98
+ /// Namely, it permits the weekday to be inconsistent with the date.
99
+ fn rfc2822_relaxed ( input : & str ) -> Result < Zoned , jiff:: Error > {
100
+ static P : rfc2822:: DateTimeParser = rfc2822:: DateTimeParser :: new ( ) . relaxed_weekday ( true ) ;
101
+ P . parse_zoned ( input)
102
+ }
86
103
}
87
104
88
105
mod relative {
89
106
use std:: { str:: FromStr , time:: SystemTime } ;
90
107
91
- use time :: { Duration , OffsetDateTime } ;
108
+ use jiff :: { tz :: TimeZone , Span , Timestamp , Zoned } ;
92
109
93
110
use crate :: parse:: Error ;
94
111
95
- fn parse_inner ( input : & str ) -> Option < Duration > {
112
+ fn parse_inner ( input : & str ) -> Option < Result < Span , Error > > {
96
113
let mut split = input. split_whitespace ( ) ;
97
- let multiplier = i64:: from_str ( split. next ( ) ?) . ok ( ) ?;
114
+ let units = i64:: from_str ( split. next ( ) ?) . ok ( ) ?;
98
115
let period = split. next ( ) ?;
99
116
if split. next ( ) ? != "ago" {
100
117
return None ;
101
118
}
102
- duration ( period, multiplier )
119
+ span ( period, units )
103
120
}
104
121
105
- pub ( crate ) fn parse ( input : & str , now : Option < SystemTime > ) -> Option < Result < OffsetDateTime , Error > > {
106
- parse_inner ( input) . map ( |offset| {
107
- let offset = std:: time:: Duration :: from_secs ( offset. whole_seconds ( ) . try_into ( ) ?) ;
122
+ pub ( crate ) fn parse ( input : & str , now : Option < SystemTime > ) -> Option < Result < Zoned , Error > > {
123
+ parse_inner ( input) . map ( |result| {
124
+ let span = result?;
125
+ // This was an error case in a previous version of this code, where
126
+ // it would fail when converting from a negative signed integer
127
+ // to an unsigned integer. This preserves that failure case even
128
+ // though the code below handles it okay.
129
+ if span. is_negative ( ) {
130
+ return Err ( Error :: RelativeTimeConversion ) ;
131
+ }
108
132
now. ok_or ( Error :: MissingCurrentTime ) . and_then ( |now| {
109
- std:: panic:: catch_unwind ( || {
110
- now. checked_sub ( offset)
111
- . expect ( "BUG: values can't be large enough to cause underflow" )
112
- . into ( )
113
- } )
114
- . map_err ( |_| Error :: RelativeTimeConversion )
133
+ let ts = Timestamp :: try_from ( now) . map_err ( |_| Error :: RelativeTimeConversion ) ?;
134
+ // N.B. This matches the behavior of this code when it was
135
+ // written with `time`, but we might consider using the system
136
+ // time zone here. If we did, then it would implement "1 day
137
+ // ago" correctly, even when it crosses DST transitions. Since
138
+ // we're in the UTC time zone here, which has no DST, 1 day is
139
+ // in practice always 24 hours. ---AG
140
+ let zdt = ts. to_zoned ( TimeZone :: UTC ) ;
141
+ zdt. checked_sub ( span) . map_err ( |_| Error :: RelativeTimeConversion )
115
142
} )
116
143
} )
117
144
}
118
145
119
- fn duration ( period : & str , multiplier : i64 ) -> Option < Duration > {
146
+ fn span ( period : & str , units : i64 ) -> Option < Result < Span , Error > > {
120
147
let period = period. strip_suffix ( 's' ) . unwrap_or ( period) ;
121
- let seconds : i64 = match period {
122
- "second" => 1 ,
123
- "minute" => 60 ,
124
- "hour" => 60 * 60 ,
125
- "day" => 24 * 60 * 60 ,
126
- "week" => 7 * 24 * 60 * 60 ,
148
+ let result = match period {
149
+ "second" => Span :: new ( ) . try_seconds ( units ) ,
150
+ "minute" => Span :: new ( ) . try_minutes ( units ) ,
151
+ "hour" => Span :: new ( ) . try_hours ( units ) ,
152
+ "day" => Span :: new ( ) . try_days ( units ) ,
153
+ "week" => Span :: new ( ) . try_weeks ( units ) ,
127
154
// TODO months & years? YES
128
155
// Ignore values you don't know, assume seconds then (so does git)
129
156
_ => return None ,
130
157
} ;
131
- seconds . checked_mul ( multiplier ) . map ( Duration :: seconds )
158
+ Some ( result . map_err ( |_| Error :: RelativeTimeConversion ) )
132
159
}
133
160
134
161
#[ cfg( test) ]
@@ -137,7 +164,7 @@ mod relative {
137
164
138
165
#[ test]
139
166
fn two_weeks_ago ( ) {
140
- assert_eq ! ( parse_inner( "2 weeks ago" ) , Some ( Duration :: weeks( 2 ) ) ) ;
167
+ assert_eq ! ( parse_inner( "2 weeks ago" ) . unwrap ( ) . unwrap ( ) , Span :: new ( ) . weeks( 2 ) ) ;
141
168
}
142
169
}
143
170
}
0 commit comments