The DateTime::parse_str(...) constructor supports various different formats, but millisecond-granularity timestamps are parsed incorrectly when they have a fractional part.
Testcase to reproduce:
use speedate::DateTime;
fn timestamp_ms(dt: DateTime) -> i64 {
dt.timestamp() * 1000 + i64::from(dt.time.microsecond / 1000)
}
#[test]
fn from_fractional_float_str() {
assert_eq!(
timestamp_ms(DateTime::parse_str("1711445175471.865").unwrap()),
1711445175471
)
}
Failure:
---- from_fractional_float_str stdout ----
thread 'from_fractional_float_str' panicked at tests/datetime_timestamp_bytes.rs:9:5:
assertion `left == right` failed
left: 1711445176335
right: 1711445175471
Analysis: the difference between the two values is 864, which is close to the fractional part .865. It seems that the fractional part of a float input is always interpreted on the seconds scale, even if the integral part of the input is interpreted on the milliseconds scale.
This is not a bug in the above timestamp_ms() helper. The same problem can be observed when accessing this functionality via Pydantic:
import pydantic # v2.6.4
from datetime import datetime
class Model(pydantic.BaseModel):
x: datetime
Model(x="1711445175471.865").x.timestamp() * 1000
#=> 1711445176335.99
Originally I though this was a bug in the custom float parsing which seems to accumulate rounding errors because it skips the checks in f64::from_str(), but I now think the issue is actually in the usage of the DateTime::from_timestamp(ts, micros) constructor by the parse_bytes_with_config() constructor:
|
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) |
I am not sure how to fix this. It is impossible to call DateTime::from_timestamp(ts, micros) with a non-zero value for micros without already knowing how the ts part will be interpreted.
The
DateTime::parse_str(...)constructor supports various different formats, but millisecond-granularity timestamps are parsed incorrectly when they have a fractional part.Testcase to reproduce:
Failure:
Analysis: the difference between the two values is
864, which is close to the fractional part.865. It seems that the fractional part of a float input is always interpreted on the seconds scale, even if the integral part of the input is interpreted on the milliseconds scale.This is not a bug in the above
timestamp_ms()helper. The same problem can be observed when accessing this functionality via Pydantic:Originally I though this was a bug in the custom float parsing which seems to accumulate rounding errors because it skips the checks in
f64::from_str(), but I now think the issue is actually in the usage of theDateTime::from_timestamp(ts, micros)constructor by theparse_bytes_with_config()constructor:speedate/src/datetime.rs
Lines 344 to 346 in ecbe681
I am not sure how to fix this. It is impossible to call
DateTime::from_timestamp(ts, micros)with a non-zero value formicroswithout already knowing how thetspart will be interpreted.