@@ -241,7 +241,7 @@ pub mod http_date {
241
241
mod test_http_date {
242
242
use proptest:: prelude:: * ;
243
243
244
- use crate :: instant:: format:: { http_date, iso_8601 , DateParseError } ;
244
+ use crate :: instant:: format:: { http_date, rfc3339 , DateParseError } ;
245
245
use crate :: Instant ;
246
246
247
247
#[ test]
@@ -353,21 +353,21 @@ mod test_http_date {
353
353
fn valid_iso_date ( ) {
354
354
let date = "1985-04-12T23:20:50.52Z" ;
355
355
let expected = Instant :: from_secs_and_nanos ( 482196050 , 520000000 ) ;
356
- assert_eq ! ( iso_8601 :: parse( date) , Ok ( expected) ) ;
356
+ assert_eq ! ( rfc3339 :: parse( date) , Ok ( expected) ) ;
357
357
}
358
358
359
359
#[ test]
360
360
fn iso_date_no_fractional ( ) {
361
361
let date = "1985-04-12T23:20:50Z" ;
362
362
let expected = Instant :: from_secs_and_nanos ( 482196050 , 0 ) ;
363
- assert_eq ! ( iso_8601 :: parse( date) , Ok ( expected) ) ;
363
+ assert_eq ! ( rfc3339 :: parse( date) , Ok ( expected) ) ;
364
364
}
365
365
366
366
#[ test]
367
367
fn read_iso_date_comma_split ( ) {
368
368
let date = "1985-04-12T23:20:50Z,1985-04-12T23:20:51Z" ;
369
- let ( e1, date) = iso_8601 :: read ( date) . expect ( "should succeed" ) ;
370
- let ( e2, date2) = iso_8601 :: read ( & date[ 1 ..] ) . expect ( "should succeed" ) ;
369
+ let ( e1, date) = rfc3339 :: read ( date) . expect ( "should succeed" ) ;
370
+ let ( e2, date2) = rfc3339 :: read ( & date[ 1 ..] ) . expect ( "should succeed" ) ;
371
371
assert_eq ! ( date2, "" ) ;
372
372
assert_eq ! ( date, ",1985-04-12T23:20:51Z" ) ;
373
373
let expected = Instant :: from_secs_and_nanos ( 482196050 , 0 ) ;
@@ -386,7 +386,7 @@ mod test_http_date {
386
386
}
387
387
}
388
388
389
- pub mod iso_8601 {
389
+ pub mod rfc3339 {
390
390
use chrono:: format;
391
391
392
392
use crate :: instant:: format:: DateParseError ;
@@ -403,7 +403,7 @@ pub mod iso_8601 {
403
403
let format = format:: StrftimeItems :: new ( "%Y-%m-%dT%H:%M:%S%.fZ" ) ;
404
404
// TODO: it may be helpful for debugging to keep these errors around
405
405
chrono:: format:: parse ( & mut date, s, format)
406
- . map_err ( |_| DateParseError :: Invalid ( "invalid iso8601 date" ) ) ?;
406
+ . map_err ( |_| DateParseError :: Invalid ( "invalid rfc3339 date" ) ) ?;
407
407
let utc_date = date
408
408
. to_naive_datetime_with_offset ( 0 )
409
409
. map_err ( |_| DateParseError :: Invalid ( "invalid date" ) ) ?;
@@ -413,14 +413,14 @@ pub mod iso_8601 {
413
413
) )
414
414
}
415
415
416
- /// Read 1 ISO8601 date from &str and return the remaining str
416
+ /// Read 1 RFC-3339 date from &str and return the remaining str
417
417
pub fn read ( s : & str ) -> Result < ( Instant , & str ) , DateParseError > {
418
418
let delim = s. find ( 'Z' ) . map ( |idx| idx + 1 ) . unwrap_or_else ( || s. len ( ) ) ;
419
419
let ( head, rest) = s. split_at ( delim) ;
420
420
Ok ( ( parse ( head) ?, & rest) )
421
421
}
422
422
423
- /// Format an [Instant] in the ISO-8601 date format
423
+ /// Format an [Instant] in the RFC-3339 date format
424
424
pub fn format ( instant : & Instant ) -> String {
425
425
use std:: fmt:: Write ;
426
426
let ( year, month, day, hour, minute, second, nanos) = {
@@ -436,79 +436,48 @@ pub mod iso_8601 {
436
436
)
437
437
} ;
438
438
439
+ // This is stated in the assumptions for RFC-3339. ISO-8601 allows for years
440
+ // between -99,999 and 99,999 inclusive, but RFC-3339 is bound between 0 and 9,999.
439
441
assert ! (
440
- year . abs ( ) <= 99_999 ,
441
- "years greater than 5 digits are not supported by ISO-8601 "
442
+ ( 0 ..= 9_999 ) . contains ( & year ) ,
443
+ "years must be between 0 and 9,999 in RFC-3339 "
442
444
) ;
443
445
444
446
let mut out = String :: with_capacity ( 33 ) ;
445
- if ( 0 ..=9999 ) . contains ( & year) {
446
- write ! ( out, "{:04}" , year) . unwrap ( ) ;
447
- } else if year < 0 {
448
- write ! ( out, "{:05}" , year) . unwrap ( ) ;
449
- } else {
450
- write ! ( out, "+{:05}" , year) . unwrap ( ) ;
451
- }
452
447
write ! (
453
448
out,
454
- "-{:02}-{:02}T{:02}:{:02}:{:02}" ,
455
- month, day, hour, minute, second
449
+ "{:04} -{:02}-{:02}T{:02}:{:02}:{:02}" ,
450
+ year , month, day, hour, minute, second
456
451
)
457
452
. unwrap ( ) ;
458
453
format_nanos ( & mut out, nanos) ;
459
454
out. push ( 'Z' ) ;
460
455
out
461
456
}
462
457
458
+ /// Formats sub-second nanos for RFC-3339 (including the '.').
459
+ /// Expects to be called with a number of `nanos` between 0 and 999_999_999 inclusive.
463
460
fn format_nanos ( into : & mut String , nanos : u32 ) {
461
+ debug_assert ! ( nanos < 1_000_000_000 ) ;
464
462
if nanos > 0 {
465
463
into. push ( '.' ) ;
466
- let mut place = 100_000_000 ;
467
- let mut pushed_non_zero = false ;
468
- while place > 0 {
469
- let digit = ( nanos / place) % 10 ;
470
- if pushed_non_zero && digit == 0 {
471
- return ;
472
- }
473
- pushed_non_zero = digit > 0 ;
464
+ let ( mut remaining, mut place) = ( nanos, 100_000_000 ) ;
465
+ while remaining > 0 {
466
+ let digit = ( remaining / place) % 10 ;
474
467
into. push ( char:: from ( b'0' + ( digit as u8 ) ) ) ;
468
+ remaining -= digit * place;
475
469
place /= 10 ;
476
470
}
477
471
}
478
472
}
479
473
}
480
474
481
475
#[ cfg( test) ]
482
- mod test_iso_8601 {
483
- use super :: iso_8601 :: format;
476
+ mod test {
477
+ use super :: rfc3339 :: format;
484
478
use crate :: Instant ;
485
- use chrono:: SecondsFormat ;
486
479
use proptest:: proptest;
487
480
488
- #[ test]
489
- fn year_edge_cases ( ) {
490
- assert_eq ! (
491
- "-0001-12-31T18:22:50Z" ,
492
- format( & Instant :: from_epoch_seconds( -62167239430 ) )
493
- ) ;
494
- assert_eq ! (
495
- "0001-05-06T02:15:00Z" ,
496
- format( & Instant :: from_epoch_seconds( -62124788700 ) )
497
- ) ;
498
- assert_eq ! (
499
- "+33658-09-27T01:46:40Z" ,
500
- format( & Instant :: from_epoch_seconds( 1_000_000_000_000 ) )
501
- ) ;
502
- assert_eq ! (
503
- "-29719-04-05T22:13:20Z" ,
504
- format( & Instant :: from_epoch_seconds( -1_000_000_000_000 ) )
505
- ) ;
506
- assert_eq ! (
507
- "-1199-02-15T14:13:20Z" ,
508
- format( & Instant :: from_epoch_seconds( -100_000_000_000 ) )
509
- ) ;
510
- }
511
-
512
481
#[ test]
513
482
fn no_nanos ( ) {
514
483
assert_eq ! (
@@ -555,16 +524,26 @@ mod test_iso_8601 {
555
524
"1970-01-01T00:00:00.000000001Z" ,
556
525
format( & Instant :: from_secs_and_nanos( 0 , 000_000_001 ) )
557
526
) ;
527
+ assert_eq ! (
528
+ "1970-01-01T00:00:00.101Z" ,
529
+ format( & Instant :: from_secs_and_nanos( 0 , 101_000_000 ) )
530
+ ) ;
558
531
}
559
532
560
533
proptest ! {
561
- // Sanity test against chrono (excluding nanos, which format differently)
534
+ // Sanity test against chrono
562
535
#[ test]
563
- fn proptest_iso_8601( seconds in -1_000_000_000_000 ..1_000_000_000_000i64 ) {
564
- let instant = Instant :: from_epoch_seconds( seconds) ;
565
- let chrono_formatted = instant. to_chrono_internal( ) . to_rfc3339_opts( SecondsFormat :: AutoSi , true ) ;
536
+ #[ cfg( feature = "chrono-conversions" ) ]
537
+ fn proptest_rfc3339(
538
+ seconds in 0 ..253_402_300_799i64 , // 0 to 9999-12-31T23:59:59
539
+ nanos in 0 ..1_000_000_000u32
540
+ ) {
541
+ use chrono:: DateTime ;
542
+
543
+ let instant = Instant :: from_secs_and_nanos( seconds, nanos) ;
566
544
let formatted = format( & instant) ;
567
- assert_eq!( chrono_formatted, formatted) ;
545
+ let parsed: Instant = DateTime :: parse_from_rfc3339( & formatted) . unwrap( ) . into( ) ;
546
+ assert_eq!( instant, parsed) ;
568
547
}
569
548
}
570
549
}
0 commit comments