33
44use chrono:: { DateTime , Datelike , FixedOffset , NaiveDate , TimeZone , Timelike } ;
55
6- use super :: { date, relative, time, weekday} ;
6+ use super :: { date, relative, time, timezone , weekday, year } ;
77
88/// The builder is used to construct a DateTime object from various components.
99/// The parser creates a `DateTimeBuilder` object with the parsed components,
1010/// but without the baseline date and time. So you normally need to set the base
1111/// date and time using the `set_base()` method before calling `build()`, or
1212/// leave it unset to use the current date and time as the base.
1313#[ derive( Debug , Default ) ]
14- pub struct DateTimeBuilder {
14+ pub ( crate ) struct DateTimeBuilder {
1515 base : Option < DateTime < FixedOffset > > ,
1616 timestamp : Option < f64 > ,
1717 date : Option < date:: Date > ,
1818 time : Option < time:: Time > ,
1919 weekday : Option < weekday:: Weekday > ,
20- timezone : Option < time :: Offset > ,
20+ timezone : Option < timezone :: Offset > ,
2121 conversion_timezone : Option < FixedOffset > ,
2222 relative : Vec < relative:: Relative > ,
2323}
@@ -34,69 +34,70 @@ impl DateTimeBuilder {
3434 self
3535 }
3636
37- /// Timestamp value is exclusive to other date/time components. Caller of
38- /// the builder must ensure that it is not combined with other items .
37+ /// Sets a timestamp value. Timestamp values are exclusive to other date/time
38+ /// items (date, time, weekday, timezone, relative adjustments) .
3939 pub ( super ) fn set_timestamp ( mut self , ts : f64 ) -> Result < Self , & ' static str > {
40+ if self . timestamp . is_some ( ) {
41+ return Err ( "timestamp cannot appear more than once" ) ;
42+ } else if self . date . is_some ( )
43+ || self . time . is_some ( )
44+ || self . weekday . is_some ( )
45+ || self . timezone . is_some ( )
46+ || !self . relative . is_empty ( )
47+ {
48+ return Err ( "timestamp cannot be combined with other date/time items" ) ;
49+ }
50+
4051 self . timestamp = Some ( ts) ;
4152 Ok ( self )
4253 }
4354
44- pub ( super ) fn set_year ( mut self , year : u32 ) -> Result < Self , & ' static str > {
45- if let Some ( date) = self . date . as_mut ( ) {
46- if date. year . is_some ( ) {
47- Err ( "year cannot appear more than once" )
48- } else {
49- date. year = Some ( year) ;
50- Ok ( self )
51- }
52- } else {
53- self . date = Some ( date:: Date {
54- day : 1 ,
55- month : 1 ,
56- year : Some ( year) ,
57- } ) ;
58- Ok ( self )
59- }
60- }
61-
6255 pub ( super ) fn set_date ( mut self , date : date:: Date ) -> Result < Self , & ' static str > {
63- if self . date . is_some ( ) || self . timestamp . is_some ( ) {
64- Err ( "date cannot appear more than once" )
65- } else {
66- self . date = Some ( date) ;
67- Ok ( self )
56+ if self . timestamp . is_some ( ) {
57+ return Err ( "timestamp cannot be combined with other date/time items" ) ;
58+ } else if self . date . is_some ( ) {
59+ return Err ( "date cannot appear more than once" ) ;
6860 }
61+
62+ self . date = Some ( date) ;
63+ Ok ( self )
6964 }
7065
7166 pub ( super ) fn set_time ( mut self , time : time:: Time ) -> Result < Self , & ' static str > {
72- if self . time . is_some ( ) || self . timestamp . is_some ( ) {
73- Err ( "time cannot appear more than once" )
67+ if self . timestamp . is_some ( ) {
68+ return Err ( "timestamp cannot be combined with other date/time items" ) ;
69+ } else if self . time . is_some ( ) {
70+ return Err ( "time cannot appear more than once" ) ;
7471 } else if self . timezone . is_some ( ) && time. offset . is_some ( ) {
75- Err ( "time offset and timezone are mutually exclusive" )
76- } else {
77- self . time = Some ( time) ;
78- Ok ( self )
72+ return Err ( "time offset and timezone are mutually exclusive" ) ;
7973 }
74+
75+ self . time = Some ( time) ;
76+ Ok ( self )
8077 }
8178
8279 pub ( super ) fn set_weekday ( mut self , weekday : weekday:: Weekday ) -> Result < Self , & ' static str > {
83- if self . weekday . is_some ( ) {
84- Err ( "weekday cannot appear more than once" )
85- } else {
86- self . weekday = Some ( weekday) ;
87- Ok ( self )
80+ if self . timestamp . is_some ( ) {
81+ return Err ( "timestamp cannot be combined with other date/time items" ) ;
82+ } else if self . weekday . is_some ( ) {
83+ return Err ( "weekday cannot appear more than once" ) ;
8884 }
85+
86+ self . weekday = Some ( weekday) ;
87+ Ok ( self )
8988 }
9089
91- pub ( super ) fn set_timezone ( mut self , timezone : time:: Offset ) -> Result < Self , & ' static str > {
92- if self . timezone . is_some ( ) {
93- Err ( "timezone cannot appear more than once" )
90+ pub ( super ) fn set_timezone ( mut self , timezone : timezone:: Offset ) -> Result < Self , & ' static str > {
91+ if self . timestamp . is_some ( ) {
92+ return Err ( "timestamp cannot be combined with other date/time items" ) ;
93+ } else if self . timezone . is_some ( ) {
94+ return Err ( "timezone cannot appear more than once" ) ;
9495 } else if self . time . as_ref ( ) . and_then ( |t| t. offset . as_ref ( ) ) . is_some ( ) {
95- Err ( "time offset and timezone are mutually exclusive" )
96- } else {
97- self . timezone = Some ( timezone) ;
98- Ok ( self )
96+ return Err ( "time offset and timezone are mutually exclusive" ) ;
9997 }
98+
99+ self . timezone = Some ( timezone) ;
100+ Ok ( self )
100101 }
101102
102103 pub ( super ) fn set_conversion_timezone (
@@ -111,14 +112,77 @@ impl DateTimeBuilder {
111112 }
112113 }
113114
114- pub ( super ) fn push_relative ( mut self , relative : relative:: Relative ) -> Self {
115+ pub ( super ) fn push_relative (
116+ mut self ,
117+ relative : relative:: Relative ,
118+ ) -> Result < Self , & ' static str > {
119+ if self . timestamp . is_some ( ) {
120+ return Err ( "timestamp cannot be combined with other date/time items" ) ;
121+ }
115122 self . relative . push ( relative) ;
116- self
123+ Ok ( self )
124+ }
125+
126+ /// Sets a pure number that can be interpreted as either a year or time
127+ /// depending on the current state of the builder.
128+ ///
129+ /// If a date is already set but lacks a year, the number is interpreted as
130+ /// a year. Otherwise, it's interpreted as a time in HHMM, HMM, HH, or H
131+ /// format.
132+ pub ( super ) fn set_pure ( mut self , pure : String ) -> Result < Self , & ' static str > {
133+ if self . timestamp . is_some ( ) {
134+ return Err ( "timestamp cannot be combined with other date/time items" ) ;
135+ }
136+
137+ if let Some ( date) = self . date . as_mut ( ) {
138+ if date. year . is_none ( ) {
139+ date. year = Some ( year:: year_from_str ( & pure) ?) ;
140+ return Ok ( self ) ;
141+ }
142+ }
143+
144+ let ( mut hour_str, mut minute_str) = match pure. len ( ) {
145+ 1 ..=2 => ( pure. as_str ( ) , "0" ) ,
146+ 3 ..=4 => pure. split_at ( pure. len ( ) - 2 ) ,
147+ _ => {
148+ return Err ( "pure number must be 1-4 digits when interpreted as time" ) ;
149+ }
150+ } ;
151+
152+ let hour = time:: hour24 ( & mut hour_str) . map_err ( |_| "invalid hour in pure number" ) ?;
153+ let minute = time:: minute ( & mut minute_str) . map_err ( |_| "invalid minute in pure number" ) ?;
154+
155+ let time = time:: Time {
156+ hour,
157+ minute,
158+ ..Default :: default ( )
159+ } ;
160+ self . set_time ( time)
161+ }
162+
163+ fn build_from_timestamp ( ts : f64 , tz : & FixedOffset ) -> Option < DateTime < FixedOffset > > {
164+ // TODO: How to make the fract -> nanosecond conversion more precise?
165+ // Maybe considering using the
166+ // [rust_decimal](https://crates.io/crates/rust_decimal) crate?
167+ match chrono:: Utc . timestamp_opt ( ts as i64 , ( ts. fract ( ) * 10f64 . powi ( 9 ) ) . round ( ) as u32 ) {
168+ chrono:: MappedLocalTime :: Single ( t) => Some ( t. with_timezone ( tz) ) ,
169+ chrono:: MappedLocalTime :: Ambiguous ( earliest, _latest) => {
170+ // TODO: When there is a fold in the local time, which value
171+ // do we choose? For now, we use the earliest one.
172+ Some ( earliest. with_timezone ( tz) )
173+ }
174+ chrono:: MappedLocalTime :: None => None , // Invalid timestamp
175+ }
117176 }
118177
119178 pub ( super ) fn build ( self ) -> Option < DateTime < FixedOffset > > {
120179 let base = self . base . unwrap_or_else ( || chrono:: Local :: now ( ) . into ( ) ) ;
121180
181+ // If a timestamp is set, we use it to build the DateTime object.
182+ if let Some ( ts) = self . timestamp {
183+ return Self :: build_from_timestamp ( ts, base. offset ( ) ) ;
184+ }
185+
122186 // If any of the following items are set, we truncate the time portion
123187 // of the base date to zero; otherwise, we use the base date as is.
124188 let mut dt = if self . timestamp . is_none ( )
@@ -141,27 +205,6 @@ impl DateTimeBuilder {
141205 ) ?
142206 } ;
143207
144- if let Some ( ts) = self . timestamp {
145- // TODO: How to make the fract -> nanosecond conversion more precise?
146- // Maybe considering using the
147- // [rust_decimal](https://crates.io/crates/rust_decimal) crate?
148- match chrono:: Utc . timestamp_opt ( ts as i64 , ( ts. fract ( ) * 10f64 . powi ( 9 ) ) . round ( ) as u32 )
149- {
150- chrono:: MappedLocalTime :: Single ( t) => {
151- // If the timestamp is valid, we can use it directly.
152- dt = t. with_timezone ( & dt. timezone ( ) ) ;
153- }
154- chrono:: MappedLocalTime :: Ambiguous ( earliest, _latest) => {
155- // TODO: When there is a fold in the local time, which value
156- // do we choose? For now, we use the earliest one.
157- dt = earliest. with_timezone ( & dt. timezone ( ) ) ;
158- }
159- chrono:: MappedLocalTime :: None => {
160- return None ; // Invalid timestamp
161- }
162- }
163- }
164-
165208 if let Some ( date:: Date { year, month, day } ) = self . date {
166209 dt = new_date (
167210 year. map ( |x| x as i32 ) . unwrap_or ( dt. year ( ) ) ,
@@ -292,7 +335,7 @@ impl DateTimeBuilder {
292335 }
293336}
294337
295- #[ allow( clippy:: too_many_arguments) ]
338+ #[ allow( clippy:: too_many_arguments, deprecated ) ]
296339fn new_date (
297340 year : i32 ,
298341 month : u32 ,
0 commit comments