Skip to content

Commit

Permalink
Add fractional second support to str{p,f}time
Browse files Browse the repository at this point in the history
The ISO 8601 standard does not mandate any specific precision for
fractional seconds, so this accepts input of any length, ignoring the
part after the nanoseconds place. It may be more correct to round with
the tenths of nanoseconds digit, but then we'd have to deal with
carrying the round through the entire Tm struct (e.g. for a time like
Dec 31 11:59.999999999999).

%f is the format specifier that Python's datetime library uses for
0-padded microseconds so it seemed appropriate here.

cc rust-lang#2350
  • Loading branch information
sfackler committed Sep 6, 2013
1 parent a980f28 commit 3c30ecb
Showing 1 changed file with 37 additions and 3 deletions.
40 changes: 37 additions & 3 deletions src/libextra/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,33 @@ fn do_strptime(s: &str, format: &str) -> Result<Tm, ~str> {
Some((value, pos))
}

fn match_fractional_seconds(ss: &str, pos: uint) -> (i32, uint) {
let len = ss.len();
let mut value = 0_i32;
let mut multiplier = NSEC_PER_SEC / 10;
let mut pos = pos;

loop {
if pos >= len {
break;
}
let range = ss.char_range_at(pos);

match range.ch {
'0' .. '9' => {
pos = range.next;
// This will drop digits after the nanoseconds place
let digit = range.ch as i32 - '0' as i32;
value += digit * multiplier;
multiplier /= 10;
}
_ => break
}
}

(value, pos)
}

fn match_digits_in_range(ss: &str, pos: uint, digits: uint, ws: bool,
min: i32, max: i32) -> Option<(i32, uint)> {
match match_digits(ss, pos, digits, ws) {
Expand Down Expand Up @@ -441,6 +468,11 @@ fn do_strptime(s: &str, format: &str) -> Result<Tm, ~str> {
Some(item) => { let (v, pos) = item; tm.tm_mday = v; Ok(pos) }
None => Err(~"Invalid day of the month")
},
'f' => {
let (val, pos) = match_fractional_seconds(s, pos);
tm.tm_nsec = val;
Ok(pos)
}
'F' => {
parse_type(s, pos, 'Y', &mut *tm)
.chain(|pos| parse_char(s, pos, '-'))
Expand Down Expand Up @@ -773,6 +805,7 @@ fn do_strftime(format: &str, tm: &Tm) -> ~str {
}
'd' => fmt!("%02d", tm.tm_mday as int),
'e' => fmt!("%2d", tm.tm_mday as int),
'f' => fmt!("%09d", tm.tm_nsec as int),
'F' => {
fmt!("%s-%s-%s",
parse_type('Y', tm),
Expand Down Expand Up @@ -1011,12 +1044,12 @@ mod tests {
Err(_) => ()
}
let format = "%a %b %e %T %Y";
let format = "%a %b %e %T.%f %Y";
assert_eq!(strptime("", format), Err(~"Invalid time"));
assert!(strptime("Fri Feb 13 15:31:30", format)
== Err(~"Invalid time"));
match strptime("Fri Feb 13 15:31:30 2009", format) {
match strptime("Fri Feb 13 15:31:30.01234 2009", format) {
Err(e) => fail!(e),
Ok(ref tm) => {
assert!(tm.tm_sec == 30_i32);
Expand All @@ -1030,7 +1063,7 @@ mod tests {
assert!(tm.tm_isdst == 0_i32);
assert!(tm.tm_gmtoff == 0_i32);
assert!(tm.tm_zone == ~"");
assert!(tm.tm_nsec == 0_i32);
assert!(tm.tm_nsec == 12340000_i32);
}
}
Expand Down Expand Up @@ -1187,6 +1220,7 @@ mod tests {
assert_eq!(local.strftime("%D"), ~"02/13/09");
assert_eq!(local.strftime("%d"), ~"13");
assert_eq!(local.strftime("%e"), ~"13");
assert_eq!(local.strftime("%f"), ~"000054321");
assert_eq!(local.strftime("%F"), ~"2009-02-13");
// assert!(local.strftime("%G") == "2009");
// assert!(local.strftime("%g") == "09");
Expand Down

2 comments on commit 3c30ecb

@alexcrichton
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

r+, thanks!

I think we're OK just chopping it off for now,

@alexcrichton
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bors: retry

Please sign in to comment.