1
0
mirror of https://github.com/bspeice/dtparse synced 2024-11-14 09:58:09 -05:00

#40: Nanosecond precision

This commit is contained in:
Bradlee Speice 2023-03-25 02:12:57 +00:00
parent ed919e84ef
commit 3a9e2674a6
8 changed files with 29 additions and 150 deletions

View File

@ -1,40 +0,0 @@
language: rust
jobs:
include:
- rust: stable
os: linux
- rust: 1.28.0
os: linux
env: DISABLE_TOOLS=true
- rust: stable
os: osx
- rust: stable-msvc
os: windows
- rust: stable
os: windows
cache:
- cargo
before_script:
- rustup show
# CMake doesn't like the `sh.exe` provided by Git being in PATH
- if [[ "$TRAVIS_OS_NAME" == "windows" ]]; then rm "C:/Program Files/Git/usr/bin/sh.exe"; fi
- if [[ "$DISABLE_TOOLS" == "" ]]; then rustup component add clippy; rustup component add rustfmt; fi
script:
- if [[ "$DISABLE_TOOLS" == "" ]]; then cargo clippy --all && cargo fmt --all -- --check; fi
# For default build, split up compilation and tests so we can track build times
- cargo test --no-run
- cargo test
- cargo test --release --no-run
- cargo test --release
branches:
only:
- master
- staging
- trying

View File

@ -1,6 +1,6 @@
[package] [package]
name = "dtparse" name = "dtparse"
version = "1.3.0" version = "1.4.0"
authors = ["Bradlee Speice <bradlee@speice.io>"] authors = ["Bradlee Speice <bradlee@speice.io>"]
description = "A dateutil-compatible timestamp parser for Rust" description = "A dateutil-compatible timestamp parser for Rust"
repository = "https://github.com/bspeice/dtparse.git" repository = "https://github.com/bspeice/dtparse.git"
@ -16,10 +16,10 @@ maintenance = { status = "passively-maintained" }
name = "dtparse" name = "dtparse"
[dependencies] [dependencies]
chrono = "0.4" chrono = "0.4.24"
lazy_static = "1.1" lazy_static = "1.4.0"
num-traits = "0.2" num-traits = "0.2.15"
rust_decimal = { version = "1.17.0", default-features = false } rust_decimal = { version = "1.29.1", default-features = false }
[dev-dependencies] [dev-dependencies]
base64 = "0.13" base64 = "0.21.0"

View File

@ -1,6 +1,5 @@
# dtparse # dtparse
[![travisci](https://travis-ci.org/bspeice/dtparse.svg?branch=master)](https://travis-ci.org/bspeice/dtparse)
[![crates.io](https://img.shields.io/crates/v/dtparse.svg)](https://crates.io/crates/dtparse) [![crates.io](https://img.shields.io/crates/v/dtparse.svg)](https://crates.io/crates/dtparse)
[![docs.rs](https://docs.rs/dtparse/badge.svg)](https://docs.rs/dtparse/) [![docs.rs](https://docs.rs/dtparse/badge.svg)](https://docs.rs/dtparse/)

View File

@ -1,4 +0,0 @@
status = [
"continuous-integration/travis-ci/push",
]
delete_merged_branches = true

View File

@ -1,47 +0,0 @@
set -ex
main() {
local target=
if [ $TRAVIS_OS_NAME = linux ]; then
target=x86_64-unknown-linux-musl
sort=sort
else
target=x86_64-apple-darwin
sort=gsort # for `sort --sort-version`, from brew's coreutils.
fi
# Builds for iOS are done on OSX, but require the specific target to be
# installed.
case $TARGET in
aarch64-apple-ios)
rustup target install aarch64-apple-ios
;;
armv7-apple-ios)
rustup target install armv7-apple-ios
;;
armv7s-apple-ios)
rustup target install armv7s-apple-ios
;;
i386-apple-ios)
rustup target install i386-apple-ios
;;
x86_64-apple-ios)
rustup target install x86_64-apple-ios
;;
esac
# This fetches latest stable release
local tag=$(git ls-remote --tags --refs --exit-code https://github.com/japaric/cross \
| cut -d/ -f3 \
| grep -E '^v[0.1.0-9.]+$' \
| $sort --version-sort \
| tail -n1)
curl -LSfs https://japaric.github.io/trust/install.sh | \
sh -s -- \
--force \
--git japaric/cross \
--tag $tag \
--target $target
}
main

View File

@ -1,40 +0,0 @@
# This script takes care of testing your crate
set -ex
main() {
cross build --target $TARGET
cross build --target $TARGET --release
if [ ! -z $DISABLE_TESTS ]; then
return
fi
cross test --target $TARGET
cross test --target $TARGET --release
}
main_web() {
CARGO_WEB_RELEASE="$(curl -L -s -H 'Accept: application/json' https://github.com/koute/cargo-web/releases/latest)"
CARGO_WEB_VERSION="$(echo $CARGO_WEB_RELEASE | sed -e 's/.*"tag_name":"\([^"]*\)".*/\1/')"
CARGO_WEB_URL="https://github.com/koute/cargo-web/releases/download/$CARGO_WEB_VERSION/cargo-web-x86_64-unknown-linux-gnu.gz"
echo "Downloading cargo-web from: $CARGO_WEB_URL"
curl -L "$CARGO_WEB_URL" | gzip -d > cargo-web
chmod +x cargo-web
mkdir -p ~/.cargo/bin
mv cargo-web ~/.cargo/bin
cargo web build --target $TARGET
cargo web test --target $TARGET --release
}
# we don't run the "test phase" when doing deploys
if [ -z $TRAVIS_TAG ]; then
if [ -z "$USE_CARGO_WEB" ]; then
main
else
main_web
fi
fi

View File

@ -617,7 +617,7 @@ struct ParsingResult {
hour: Option<i32>, hour: Option<i32>,
minute: Option<i32>, minute: Option<i32>,
second: Option<i32>, second: Option<i32>,
microsecond: Option<i32>, nanosecond: Option<i64>,
tzname: Option<String>, tzname: Option<String>,
tzoffset: Option<i32>, tzoffset: Option<i32>,
ampm: Option<bool>, ampm: Option<bool>,
@ -644,7 +644,7 @@ impl ParsingResult {
+ option_len!(self.hour) + option_len!(self.hour)
+ option_len!(self.minute) + option_len!(self.minute)
+ option_len!(self.second) + option_len!(self.second)
+ option_len!(self.microsecond) + option_len!(self.nanosecond)
+ option_len!(self.tzname) + option_len!(self.tzname)
+ option_len!(self.ampm) + option_len!(self.ampm)
} }
@ -976,18 +976,18 @@ impl Parser {
let hour = res.hour.unwrap_or(default.hour() as i32) as u32; let hour = res.hour.unwrap_or(default.hour() as i32) as u32;
let minute = res.minute.unwrap_or(default.minute() as i32) as u32; let minute = res.minute.unwrap_or(default.minute() as i32) as u32;
let second = res.second.unwrap_or(default.second() as i32) as u32; let second = res.second.unwrap_or(default.second() as i32) as u32;
let microsecond = res let nanosecond = res
.microsecond .nanosecond
.unwrap_or(default.timestamp_subsec_micros() as i32) as u32; .unwrap_or(default.timestamp_subsec_nanos() as i64) as u32;
let t = let t =
NaiveTime::from_hms_micro_opt(hour, minute, second, microsecond).ok_or_else(|| { NaiveTime::from_hms_nano_opt(hour, minute, second, nanosecond).ok_or_else(|| {
if hour >= 24 { if hour >= 24 {
ParseError::ImpossibleTimestamp("Invalid hour") ParseError::ImpossibleTimestamp("Invalid hour")
} else if minute >= 60 { } else if minute >= 60 {
ParseError::ImpossibleTimestamp("Invalid minute") ParseError::ImpossibleTimestamp("Invalid minute")
} else if second >= 60 { } else if second >= 60 {
ParseError::ImpossibleTimestamp("Invalid second") ParseError::ImpossibleTimestamp("Invalid second")
} else if microsecond >= 2_000_000 { } else if nanosecond >= 2_000_000_000 {
ParseError::ImpossibleTimestamp("Invalid microsecond") ParseError::ImpossibleTimestamp("Invalid microsecond")
} else { } else {
unreachable!(); unreachable!();
@ -1071,7 +1071,7 @@ impl Parser {
let t = self.parsems(&s[4..])?; let t = self.parsems(&s[4..])?;
res.second = Some(t.0); res.second = Some(t.0);
res.microsecond = Some(t.1); res.nanosecond = Some(t.1);
} }
} else if vec![8, 12, 14].contains(&len_li) { } else if vec![8, 12, 14].contains(&len_li) {
// YYMMDD // YYMMDD
@ -1109,7 +1109,7 @@ impl Parser {
// TODO: (x, y) = (a, b) syntax? // TODO: (x, y) = (a, b) syntax?
let ms = self.parsems(&tokens[idx + 4]).unwrap(); let ms = self.parsems(&tokens[idx + 4]).unwrap();
res.second = Some(ms.0); res.second = Some(ms.0);
res.microsecond = Some(ms.1); res.nanosecond = Some(ms.1);
idx += 2; idx += 2;
} }
@ -1183,13 +1183,13 @@ impl Parser {
} }
} }
fn parsems(&self, seconds_str: &str) -> ParseResult<(i32, i32)> { fn parsems(&self, seconds_str: &str) -> ParseResult<(i32, i64)> {
if seconds_str.contains('.') { if seconds_str.contains('.') {
let split: Vec<&str> = seconds_str.split('.').collect(); let split: Vec<&str> = seconds_str.split('.').collect();
let (i, f): (&str, &str) = (split[0], split[1]); let (i, f): (&str, &str) = (split[0], split[1]);
let i_parse = i.parse::<i32>()?; let i_parse = i.parse::<i32>()?;
let f_parse = ljust(f, 6, '0').parse::<i32>()?; let f_parse = ljust(f, 9, '0').parse::<i64>()?;
Ok((i_parse, f_parse)) Ok((i_parse, f_parse))
} else { } else {
Ok((seconds_str.parse::<i32>()?, 0)) Ok((seconds_str.parse::<i32>()?, 0))
@ -1281,7 +1281,7 @@ impl Parser {
} else if hms == 2 { } else if hms == 2 {
let (sec, micro) = self.parsems(value_repr).unwrap(); let (sec, micro) = self.parsems(value_repr).unwrap();
res.second = Some(sec); res.second = Some(sec);
res.microsecond = Some(micro); res.nanosecond = Some(micro);
} }
Ok(()) Ok(())

View File

@ -1,3 +1,14 @@
mod fuzzing; mod fuzzing;
mod pycompat_parser; mod pycompat_parser;
mod pycompat_tokenizer; mod pycompat_tokenizer;
use chrono::NaiveDate;
use crate::parse;
#[test]
fn nanosecond_precision() {
assert_eq!(
parse("2008.12.29T08:09:10.123456789").unwrap(),
(NaiveDate::from_ymd_opt(2008, 12, 29).unwrap().and_hms_nano_opt(8, 9, 10, 123_456_789).unwrap(), None)
)
}