@@ -137,7 +137,126 @@ impl TimeZone for Local {
137137mod tests {
138138 use super :: Local ;
139139 use crate :: offset:: TimeZone ;
140- use crate :: Datelike ;
140+ use crate :: { Datelike , Duration , NaiveDate , NaiveDateTime , Timelike } ;
141+
142+ use std:: { path, process} ;
143+
144+ #[ cfg( unix) ]
145+ fn verify_against_date_command_local ( path : & ' static str , dt : NaiveDateTime ) {
146+ let output = process:: Command :: new ( path)
147+ . arg ( "-d" )
148+ . arg ( format ! ( "{}-{:02}-{:02} {:02}:05:01" , dt. year( ) , dt. month( ) , dt. day( ) , dt. hour( ) ) )
149+ . arg ( "+%Y-%m-%d %H:%M:%S %:z" )
150+ . output ( )
151+ . unwrap ( ) ;
152+
153+ let date_command_str = String :: from_utf8 ( output. stdout ) . unwrap ( ) ;
154+
155+ // The below would be preferred. At this stage neither earliest() or latest()
156+ // seems to be consistent with the output of the `date` command, so we simply
157+ // compare both.
158+ // let local = Local
159+ // .from_local_datetime(&NaiveDate::from_ymd(year, month, day).and_hms(hour, 5, 1))
160+ // // looks like the "date" command always returns a given time when it is ambiguous
161+ // .earliest();
162+
163+ // if let Some(local) = local {
164+ // assert_eq!(format!("{}\n", local), date_command_str);
165+ // } else {
166+ // // we are in a "Spring forward gap" due to DST, and so date also returns ""
167+ // assert_eq!("", date_command_str);
168+ // }
169+
170+ // This is used while a decision is made wheter the `date` output needs to
171+ // be exactly matched, or whether LocalResult::Ambigious should be handled
172+ // differently
173+ match Local . from_local_datetime (
174+ & NaiveDate :: from_ymd ( dt. year ( ) , dt. month ( ) , dt. day ( ) ) . and_hms ( dt. hour ( ) , 5 , 1 ) ,
175+ ) {
176+ crate :: LocalResult :: Ambiguous ( a, b) => {
177+ assert ! (
178+ format!( "{}\n " , a) == date_command_str
179+ || format!( "{}\n " , b) == date_command_str
180+ )
181+ }
182+ crate :: LocalResult :: Single ( a) => {
183+ assert_eq ! ( format!( "{}\n " , a) , date_command_str) ;
184+ }
185+ crate :: LocalResult :: None => {
186+ assert_eq ! ( "" , date_command_str) ;
187+ }
188+ }
189+ }
190+
191+ #[ test]
192+ #[ cfg( unix) ]
193+ fn try_verify_against_date_command ( ) {
194+ let date_path = "/usr/bin/date" ;
195+
196+ if !path:: Path :: new ( date_path) . exists ( ) {
197+ // date command not found, skipping
198+ // avoid running this on macOS, which has path /bin/date
199+ // as the required CLI arguments are not present in the
200+ // macOS build.
201+ return ;
202+ }
203+
204+ let mut date = NaiveDate :: from_ymd ( 1975 , 1 , 1 ) . and_hms ( 0 , 0 , 0 ) ;
205+
206+ while date. year ( ) < 2078 {
207+ if ( 1975 ..=1977 ) . contains ( & date. year ( ) )
208+ || ( 2020 ..=2022 ) . contains ( & date. year ( ) )
209+ || ( 2073 ..=2077 ) . contains ( & date. year ( ) )
210+ {
211+ verify_against_date_command_local ( date_path, date) ;
212+ }
213+
214+ date += crate :: Duration :: hours ( 1 ) ;
215+ }
216+ }
217+
218+ #[ test]
219+ fn verify_correct_offsets ( ) {
220+ let now = Local :: now ( ) ;
221+ let from_local = Local . from_local_datetime ( & now. naive_local ( ) ) . unwrap ( ) ;
222+ let from_utc = Local . from_utc_datetime ( & now. naive_utc ( ) ) ;
223+
224+ assert_eq ! ( now. offset( ) . local_minus_utc( ) , from_local. offset( ) . local_minus_utc( ) ) ;
225+ assert_eq ! ( now. offset( ) . local_minus_utc( ) , from_utc. offset( ) . local_minus_utc( ) ) ;
226+
227+ assert_eq ! ( now, from_local) ;
228+ assert_eq ! ( now, from_utc) ;
229+ }
230+
231+ #[ test]
232+ fn verify_correct_offsets_distant_past ( ) {
233+ // let distant_past = Local::now() - Duration::days(365 * 100);
234+ let distant_past = Local :: now ( ) - Duration :: days ( 250 * 31 ) ;
235+ let from_local = Local . from_local_datetime ( & distant_past. naive_local ( ) ) . unwrap ( ) ;
236+ let from_utc = Local . from_utc_datetime ( & distant_past. naive_utc ( ) ) ;
237+
238+ assert_eq ! ( distant_past. offset( ) . local_minus_utc( ) , from_local. offset( ) . local_minus_utc( ) ) ;
239+ assert_eq ! ( distant_past. offset( ) . local_minus_utc( ) , from_utc. offset( ) . local_minus_utc( ) ) ;
240+
241+ assert_eq ! ( distant_past, from_local) ;
242+ assert_eq ! ( distant_past, from_utc) ;
243+ }
244+
245+ #[ test]
246+ fn verify_correct_offsets_distant_future ( ) {
247+ let distant_future = Local :: now ( ) + Duration :: days ( 250 * 31 ) ;
248+ let from_local = Local . from_local_datetime ( & distant_future. naive_local ( ) ) . unwrap ( ) ;
249+ let from_utc = Local . from_utc_datetime ( & distant_future. naive_utc ( ) ) ;
250+
251+ assert_eq ! (
252+ distant_future. offset( ) . local_minus_utc( ) ,
253+ from_local. offset( ) . local_minus_utc( )
254+ ) ;
255+ assert_eq ! ( distant_future. offset( ) . local_minus_utc( ) , from_utc. offset( ) . local_minus_utc( ) ) ;
256+
257+ assert_eq ! ( distant_future, from_local) ;
258+ assert_eq ! ( distant_future, from_utc) ;
259+ }
141260
142261 #[ test]
143262 fn test_local_date_sanity_check ( ) {
0 commit comments