Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 5 additions & 9 deletions src/items/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,20 +148,16 @@ impl DateTimeBuilder {
self.set_time(time)
}

fn build_from_timestamp(ts: epoch::Timestamp, tz: jiff::tz::TimeZone) -> Option<Zoned> {
Some(
jiff::Timestamp::new(ts.second, ts.nanosecond as i32)
.ok()?
.to_zoned(tz),
)
}

pub(super) fn build(self) -> Option<Zoned> {
let base = self.base.unwrap_or(Zoned::now());

// If a timestamp is set, we use it to build the `Zoned` object.
if let Some(ts) = self.timestamp {
return Self::build_from_timestamp(ts, base.offset().to_time_zone());
return Some(
jiff::Timestamp::try_from(ts)
.ok()?
.to_zoned(base.offset().to_time_zone()),
);
}

// If any of the following items are set, we truncate the time portion
Expand Down
130 changes: 52 additions & 78 deletions src/items/epoch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,26 @@ use super::primitive::{dec_uint, s};
/// - Negative timestamps are represented by a negative `second` value and a
/// positive `nanosecond` value.
#[derive(Debug, PartialEq)]
pub(crate) struct Timestamp {
pub(crate) second: i64,
pub(crate) nanosecond: u32,
pub(super) struct Timestamp {
second: i64,
nanosecond: u32,
}

impl TryFrom<Timestamp> for jiff::Timestamp {
type Error = &'static str;

fn try_from(ts: Timestamp) -> Result<Self, Self::Error> {
jiff::Timestamp::new(
ts.second,
i32::try_from(ts.nanosecond).map_err(|_| "nanosecond value exceeds i32::MAX")?,
)
.map_err(|_| "timestamp value is out of valid range")
}
}

/// Parse a timestamp in the form of `@1234567890` or `@-1234567890.12345` or
/// `@1234567890,12345`.
pub(crate) fn parse(input: &mut &str) -> ModalResult<Timestamp> {
pub(super) fn parse(input: &mut &str) -> ModalResult<Timestamp> {
(s("@"), opt(s(one_of(['-', '+']))), sec_and_nsec)
.verify_map(|(_, sign, (sec, nsec))| {
let sec = i64::try_from(sec).ok()?;
Expand Down Expand Up @@ -78,84 +90,46 @@ pub(super) fn sec_and_nsec(input: &mut &str) -> ModalResult<(u64, u32)> {
mod tests {
use super::*;

#[test]
fn sec_and_nsec_test() {
let mut input = "1234567890";
assert_eq!(sec_and_nsec(&mut input).unwrap(), (1234567890, 0));

let mut input = "1234567890.12345";
assert_eq!(sec_and_nsec(&mut input).unwrap(), (1234567890, 123450000));

let mut input = "1234567890,12345";
assert_eq!(sec_and_nsec(&mut input).unwrap(), (1234567890, 123450000));
fn ts(second: i64, nanosecond: u32) -> Timestamp {
Timestamp { second, nanosecond }
}

let mut input = "1234567890.1234567890123";
assert_eq!(sec_and_nsec(&mut input).unwrap(), (1234567890, 123456789));
#[test]
fn parse_sec_and_nsec() {
for (input, expected) in [
("1234567890", (1234567890, 0)), // only seconds
("1234567890.12345", (1234567890, 123450000)), // seconds and nanoseconds, '.' as floating point
("1234567890,12345", (1234567890, 123450000)), // seconds and nanoseconds, ',' as floating point
("1234567890.1234567890123", (1234567890, 123456789)), // nanoseconds with more than 9 digits, truncated
] {
let mut s = input;
assert_eq!(sec_and_nsec(&mut s).unwrap(), expected, "{input}");
}

for input in [
".1234567890", // invalid: no leading seconds
"-1234567890", // invalid: negative input not allowed
] {
let mut s = input;
assert!(sec_and_nsec(&mut s).is_err(), "{input}");
}
}

#[test]
fn timestamp() {
let mut input = "@1234567890";
assert_eq!(
parse(&mut input).unwrap(),
Timestamp {
second: 1234567890,
nanosecond: 0,
}
);

let mut input = "@ 1234567890";
assert_eq!(
parse(&mut input).unwrap(),
Timestamp {
second: 1234567890,
nanosecond: 0,
}
);

let mut input = "@ -1234567890";
assert_eq!(
parse(&mut input).unwrap(),
Timestamp {
second: -1234567890,
nanosecond: 0,
}
);

let mut input = "@ - 1234567890";
assert_eq!(
parse(&mut input).unwrap(),
Timestamp {
second: -1234567890,
nanosecond: 0,
}
);

let mut input = "@1234567890.12345";
assert_eq!(
parse(&mut input).unwrap(),
Timestamp {
second: 1234567890,
nanosecond: 123450000,
}
);

let mut input = "@1234567890,12345";
assert_eq!(
parse(&mut input).unwrap(),
Timestamp {
second: 1234567890,
nanosecond: 123450000,
}
);

let mut input = "@-1234567890.12345";
assert_eq!(
parse(&mut input).unwrap(),
Timestamp {
second: -1234567891,
nanosecond: 876550000,
}
);
for (input, expected) in [
("@1234567890", ts(1234567890, 0)), // positive seconds, no nanoseconds
("@ 1234567890", ts(1234567890, 0)), // space after '@', positive seconds, no nanoseconds
("@-1234567890", ts(-1234567890, 0)), // negative seconds, no nanoseconds
("@ -1234567890", ts(-1234567890, 0)), // space after '@', negative seconds, no nanoseconds
("@ - 1234567890", ts(-1234567890, 0)), // space after '@' and after '-', negative seconds, no nanoseconds
("@1234567890.12345", ts(1234567890, 123450000)), // positive seconds with nanoseconds, '.' as floating point
("@1234567890,12345", ts(1234567890, 123450000)), // positive seconds with nanoseconds, ',' as floating point
("@-1234567890.12345", ts(-1234567891, 876550000)), // negative seconds with nanoseconds, '.' as floating point
("@1234567890.1234567890123", ts(1234567890, 123456789)), // nanoseconds with more than 9 digits, truncated
] {
let mut s = input;
assert_eq!(parse(&mut s).unwrap(), expected, "{input}");
}
Comment on lines +120 to +133
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nice refactoring, it's now much easier to read with the boilerplate code gone.

}
}
Loading