Skip to content

Commit

Permalink
fix millisecond fraction being handled with wrong scale
Browse files Browse the repository at this point in the history
  • Loading branch information
davidhewitt committed Jun 25, 2024
1 parent 6a79b44 commit a624253
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 16 deletions.
18 changes: 11 additions & 7 deletions src/date.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ impl FromStr for Date {

// 2e10 if greater than this, the number is in ms, if less than or equal, it's in seconds
// (in seconds this is 11th October 2603, in ms it's 20th August 1970)
const MS_WATERSHED: i64 = 20_000_000_000;
pub(crate) const MS_WATERSHED: i64 = 20_000_000_000;
// 1600-01-01 as a unix timestamp used for from_timestamp below
const UNIX_1600: i64 = -11_676_096_000;
// 9999-12-31T23:59:59 as a unix timestamp, used as max allowed value below
Expand Down Expand Up @@ -206,9 +206,13 @@ impl Date {
/// assert_eq!(d.to_string(), "2022-06-07");
/// ```
pub fn from_timestamp(timestamp: i64, require_exact: bool) -> Result<Self, ParseError> {
let (timestamp_second, _) = Self::timestamp_watershed(timestamp)?;
let (timestamp_second, millis) = Self::timestamp_watershed(timestamp)?;
let d = Self::from_timestamp_calc(timestamp_second)?;
if require_exact {
if millis != 0 {
return Err(ParseError::DateNotExact);
}

let time_second = timestamp_second.rem_euclid(86_400);
if time_second != 0 {
return Err(ParseError::DateNotExact);
Expand Down Expand Up @@ -275,11 +279,11 @@ impl Date {

pub(crate) fn timestamp_watershed(timestamp: i64) -> Result<(i64, u32), ParseError> {
let ts_abs = timestamp.checked_abs().ok_or(ParseError::DateTooSmall)?;
let (mut seconds, mut microseconds) = if ts_abs > MS_WATERSHED {
(timestamp / 1_000, timestamp % 1_000 * 1000)
} else {
(timestamp, 0)
};
if ts_abs <= MS_WATERSHED {
return Ok((timestamp, 0));
}
let mut seconds = timestamp / 1_000;
let mut microseconds = ((timestamp % 1_000) * 1000) as i32;
if microseconds < 0 {
seconds -= 1;
microseconds += 1_000_000;
Expand Down
40 changes: 31 additions & 9 deletions src/datetime.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::numbers::{float_parse_bytes, IntFloat};
use crate::TimeConfigBuilder;
use crate::date::MS_WATERSHED;
use crate::{int_parse_bytes, TimeConfigBuilder};
use crate::{time::TimeConfig, Date, ParseError, Time};
use std::cmp::Ordering;
use std::fmt;
Expand Down Expand Up @@ -339,14 +339,36 @@ impl DateTime {
pub fn parse_bytes_with_config(bytes: &[u8], config: &TimeConfig) -> Result<Self, ParseError> {
match Self::parse_bytes_rfc3339_with_config(bytes, config) {
Ok(d) => Ok(d),
Err(e) => match float_parse_bytes(bytes) {
IntFloat::Int(int) => Self::from_timestamp_with_config(int, 0, config),
IntFloat::Float(float) => {
let micro = (float.fract() * 1_000_000_f64).round() as u32;
Self::from_timestamp_with_config(float.floor() as i64, micro, config)
Err(e) => {
let mut split = bytes.splitn(2, |&b| b == b'.');
let Some(timestamp) =
int_parse_bytes(split.next().expect("splitn always returns at least one element"))
else {
return Err(e);
};
let float_fraction = split.next();
debug_assert!(split.next().is_none()); // at most two elements
match float_fraction {
Some(fract) => {
// fraction is either:
// - up to 3 digits of millisecond fractions, i.e. microseconds
// - or up to 6 digits of second fractions, i.e. milliseconds
let max_digits = if timestamp > MS_WATERSHED { 3 } else { 6 };
let Some(fract_integers) = int_parse_bytes(fract) else {
return Err(e);
};
let multiple = 10f64.powf(max_digits as f64 - fract.len() as f64);
Self::from_timestamp_with_config(
timestamp,
// FIXME should we error if the fraction is too long?
// We have TimeConfig truncate / error option.
(fract_integers as f64 * multiple).round() as u32,
config,
)
}
None => Self::from_timestamp_with_config(timestamp, 0, config),
}
IntFloat::Err => Err(e),
},
}
}
}

Expand Down

0 comments on commit a624253

Please sign in to comment.