diff --git a/src/text/parsers/timestamp.rs b/src/text/parsers/timestamp.rs index b07153e3..75ad7e84 100644 --- a/src/text/parsers/timestamp.rs +++ b/src/text/parsers/timestamp.rs @@ -14,7 +14,7 @@ use crate::text::parse_result::{ }; use crate::text::parsers::{stop_character, trim_zeros_and_parse_i32, trim_zeros_and_parse_u32}; use crate::text::text_value::TextValue; -use crate::types::{Decimal, Timestamp, TimestampBuilder, HasFractionalSeconds, HasSeconds}; +use crate::types::{Decimal, HasFractionalSeconds, HasSeconds, Timestamp, TimestampBuilder}; /// Matches the text representation of a timestamp value and returns the resulting Timestamp /// as a [TextValue::Timestamp]. diff --git a/src/types/mod.rs b/src/types/mod.rs index 419f3f61..b0227506 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -16,6 +16,7 @@ mod r#struct; mod symbol; mod timestamp; +use crate::ion_data::IonOrd; pub use crate::types::bytes::Bytes; pub use decimal::coefficient::{Coefficient, Sign}; pub use decimal::Decimal; @@ -25,13 +26,12 @@ pub use lob::{Blob, Clob}; pub use r#struct::Struct; pub use sequence::Sequence; pub use sexp::SExp; -pub use string::Str; -pub use symbol::Symbol; -pub use timestamp::{ Mantissa, Precision, Timestamp, TimestampBuilder }; -pub(crate) use timestamp::{ HasSeconds, HasFractionalSeconds }; -use crate::ion_data::IonOrd; use std::cmp::Ordering; use std::fmt; +pub use string::Str; +pub use symbol::Symbol; +pub(crate) use timestamp::{HasFractionalSeconds, HasSeconds}; +pub use timestamp::{Mantissa, Precision, Timestamp, TimestampBuilder}; /// Represents the Ion data type of a given value. To learn more about each data type, /// read [the Ion Data Model](https://amazon-ion.github.io/ion-docs/docs/spec.html#the-ion-data-model) diff --git a/src/types/timestamp.rs b/src/types/timestamp.rs index 32694b2f..f326d802 100644 --- a/src/types/timestamp.rs +++ b/src/types/timestamp.rs @@ -511,9 +511,7 @@ impl Timestamp { /// Creates a TimestampBuilder with the specified year, month, and day. Its precision is /// set to [Precision::Day]. pub fn with_ymd(year: u32, month: u32, day: u32) -> TimestampBuilder { - Timestamp::with_year(year) - .with_month(month) - .with_day(day) + Timestamp::with_year(year).with_month(month).with_day(day) } /// Creates a TimestampBuilder with the specified year, month, day, hour, minute, and second. @@ -526,8 +524,7 @@ impl Timestamp { minute: u32, second: u32, ) -> TimestampBuilder { - Timestamp::with_ymd(year, month, day) - .with_hms(hour, minute, second) + Timestamp::with_ymd(year, month, day).with_hms(hour, minute, second) } /// Creates a TimestampBuilder with the specified year, month, day, hour, minute, second and @@ -816,10 +813,13 @@ impl IonOrd for Timestamp { } /// A Builder object for incrementally configuring and finally instantiating a [Timestamp]. -/// For the time being, this type is not publicly visible. Users are expected to use any of the -/// [TimeUnitSetter] implementations that wrap it. These wrappers expose only those methods which -/// can result in a valid Timestamp. For example, it is not possible to set the `day` field -/// without first setting the `year` and `month` fields. +/// This builder uses the type-state pattern to expose only those methods which can result in a +/// valid Timestamp. For example, it is not possible to set the `day` field without first setting +/// the `year` and `month` fields. +/// +/// The `Buildable` states are `HasYear`, `HasMonth`, `HasDay`, `HasMinute`, `HasSecond`, and +/// `HasFractionalSeconds`. The `BuildableWithOffset` states are `HasMinute`, `HasSecond`, and +/// `HasFractionalSeconds`. // See the unit tests for usage examples. #[derive(Debug, Clone, Default)] pub struct TimestampBuilder { @@ -837,8 +837,14 @@ pub struct TimestampBuilder { nanoseconds: Option, } -pub trait TimestampBuilderCanBuild {} -pub trait TimestampBuilderCanBuildWithOffset: TimestampBuilderCanBuild {} +// The marker traits and the state structs are pub in this module, but they do not appear as types +// in the documentation, they cannot be imported, and they are not nameable from outside this crate. + +/// Marker trait indicating a valid state to build a timestamp +pub trait Buildable {} +/// Marker trait indicating that the state includes a time, and can be built with an offset. +pub trait BuildableWithOffset: Buildable {} + macro_rules! state_with_markers { ($name:ident $(,$trait_name:ident)*)=> { #[derive(Debug, Clone, Default)] @@ -848,32 +854,19 @@ macro_rules! state_with_markers { )* }; } - state_with_markers!(Init); -state_with_markers!(HasYear, TimestampBuilderCanBuild); -state_with_markers!(HasMonth, TimestampBuilderCanBuild); -state_with_markers!(HasDay, TimestampBuilderCanBuild); +state_with_markers!(HasYear, Buildable); +state_with_markers!(HasMonth, Buildable); +state_with_markers!(HasDay, Buildable); state_with_markers!(HasHour); -state_with_markers!(HasMinute, TimestampBuilderCanBuild, TimestampBuilderCanBuildWithOffset); -state_with_markers!(HasSeconds, TimestampBuilderCanBuild, TimestampBuilderCanBuildWithOffset); -state_with_markers!(HasFractionalSeconds, TimestampBuilderCanBuild, TimestampBuilderCanBuildWithOffset); - -impl TimestampBuilder { - pub fn new() -> Self { - TimestampBuilder { ..Default::default() } - } - - fn with_year(mut self, year: u16) -> TimestampBuilder { - self.year = year; - self.precision = Precision::Year; - self.change_state() - } -} - +state_with_markers!(HasMinute, Buildable, BuildableWithOffset); +state_with_markers!(HasSeconds, Buildable, BuildableWithOffset); +state_with_markers!(HasFractionalSeconds, Buildable, BuildableWithOffset); impl TimestampBuilder { - fn change_state(self) -> TimestampBuilder { + // TODO: This entire function could be replaced with one line of unsafe code, but is it worthwhile? + // unsafe { std::mem::transmute(self) } TimestampBuilder { _state: PhantomData::default(), fields_are_utc: self.fields_are_utc, @@ -1006,6 +999,18 @@ impl TimestampBuilder { } } +impl TimestampBuilder { + pub fn new() -> TimestampBuilder { + TimestampBuilder::default() + } + + pub fn with_year(mut self, year: u16) -> TimestampBuilder { + self.year = year; + self.precision = Precision::Year; + self.change_state() + } +} + impl TimestampBuilder { // Libraries have conflicting opinions about whether months should be // 0- or 1-indexed, so Timestamp follows chrono's lead and provides @@ -1043,11 +1048,8 @@ impl TimestampBuilder { } impl TimestampBuilder { - pub fn with_hms(self, hour: u32, minute: u32, second: u32) -> TimestampBuilder { - self.with_hour(hour) - .with_minute(minute) - .with_second(second) + self.with_hour(hour).with_minute(minute).with_second(second) } pub fn with_hour_and_minute(mut self, hour: u32, minute: u32) -> TimestampBuilder { @@ -1111,15 +1113,17 @@ impl TimestampBuilder { self.change_state() } - pub fn with_fractional_seconds(mut self, fractional_seconds: Decimal) -> TimestampBuilder { + pub fn with_fractional_seconds( + mut self, + fractional_seconds: Decimal, + ) -> TimestampBuilder { self.fractional_seconds = Some(Mantissa::Arbitrary(fractional_seconds)); self.nanoseconds = None; self.change_state() } } -impl TimestampBuilder { - +impl TimestampBuilder { /// Attempt to construct a [Timestamp] using the values configured on the [TimestampBuilder]. /// If any of the individual fields are invalid (for example, a `month` value that is greater /// than `12`) or if the resulting timestamp would represent a non-existent point in time @@ -1173,7 +1177,7 @@ impl TimestampBuilder { } } -impl TimestampBuilder { +impl TimestampBuilder { /// Sets the difference, in minutes, from UTC. A positive value indicates /// Eastern Hemisphere, while a negative value indicates Western Hemisphere. // The unit (minutes) could be seconds (which is what the chrono crate uses