oracle/sql_type/
timestamp.rs

1// Rust-oracle - Rust binding for Oracle database
2//
3// URL: https://github.com/kubo/rust-oracle
4//
5//-----------------------------------------------------------------------------
6// Copyright (c) 2017-2018 Kubo Takehiro <kubo@jiubao.org>. All rights reserved.
7// This program is free software: you can modify it and/or redistribute it
8// under the terms of:
9//
10// (i)  the Universal Permissive License v 1.0 or at your option, any
11//      later version (http://oss.oracle.com/licenses/upl); and/or
12//
13// (ii) the Apache License v 2.0. (http://www.apache.org/licenses/LICENSE-2.0)
14//-----------------------------------------------------------------------------
15
16use crate::sql_type::OracleType;
17use crate::util::Scanner;
18use crate::Error;
19use crate::ParseOracleTypeError;
20use crate::Result;
21use odpic_sys::dpiTimestamp;
22use std::cmp::{self, Ordering};
23use std::fmt;
24use std::result;
25use std::str;
26
27/// Oracle-specific [Datetime][] data type
28///
29/// [Datetime]: https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=GUID-3A1B7AC6-2EDB-4DDC-9C9D-223D4C72AC74
30///
31/// This struct doesn't have arithmetic methods and they won't be added to avoid
32/// reinventing the wheel. If you need methods such as adding an interval to a
33/// timestamp, enable `chrono` feature and use [chrono::Date][], [chrono::DateTime][],
34/// [chrono::naive::NaiveDate][] or [chrono::naive::NaiveDateTime][] instead.
35///
36/// [chrono::Date]: https://docs.rs/chrono/0.4/chrono/struct.Date.html
37/// [chrono::DateTime]: https://docs.rs/chrono/0.4/chrono/struct.DateTime.html
38/// [chrono::naive::NaiveDate]: https://docs.rs/chrono/0.4/chrono/naive/struct.NaiveDate.html
39/// [chrono::naive::NaiveDateTime]: https://docs.rs/chrono/0.4/chrono/naive/struct.NaiveDateTime.html
40///
41/// # Formatting Parameters
42///
43/// The following format parameters are supported since 0.7.0.
44/// * `"{:.n}"` where `n` is 0~9 specifies the number of fractional seconds displayed.
45/// * `"{:#}"` formats timestamps in ISO 8601 extended format.
46/// * `"{:-#}"` formats timestamps in ISO 8601 basic format.
47///
48/// # Examples
49///
50/// ```
51/// # use oracle::*; use oracle::sql_type::*;
52/// // Create a timestamp.
53/// let ts1 = Timestamp::new(2017, 8, 9, 11, 22, 33, 500000000)?;
54///
55/// // Convert to string.
56/// assert_eq!(ts1.to_string(), "2017-08-09 11:22:33.500000000");
57///
58/// // Create a timestamp with time zone (-8:00).
59/// let ts2 = Timestamp::new(2017, 8, 9, 11, 22, 33, 500000000)?.and_tz_hm_offset(-8, 0)?;
60///
61/// // Convert to string.
62/// assert_eq!(ts2.to_string(), "2017-08-09 11:22:33.500000000 -08:00");
63///
64/// // Create a timestamp with precision
65/// let ts3 = Timestamp::new(2017, 8, 9, 11, 22, 33, 500000000)?.and_prec(3)?;
66///
67/// // The string representation depends on the precision.
68/// assert_eq!(ts3.to_string(), "2017-08-09 11:22:33.500");
69///
70/// // The precision can be specified in format parameter.
71/// assert_eq!(format!("{:.6}", ts3), "2017-08-09 11:22:33.500000");
72///
73/// // ISO 8601 extended format
74/// assert_eq!(format!("{:#}", ts3), "2017-08-09T11:22:33.500");
75///
76/// // ISO 8601 basic format
77/// assert_eq!(format!("{:-#}", ts3), "20170809T112233.500");
78///
79/// // Precisions are ignored when intervals are compared.
80/// assert_eq!(ts1, ts3);
81///
82/// // Create a timestamp from string.
83/// let ts4: Timestamp = "2017-08-09 11:22:33.500 -08:00".parse()?;
84///
85/// // The precision is determined by number of decimal digits in the string.
86/// assert_eq!(ts4.precision(), 3);
87/// # Ok::<(), Error>(())
88/// ```
89///
90/// Fetch and bind interval values.
91///
92/// ```
93/// # use oracle::*; use oracle::sql_type::*;
94/// # let conn = test_util::connect()?;
95///
96/// // Fetch Timestamp
97/// let sql = "select TIMESTAMP '2017-08-09 11:22:33.500' from dual";
98/// let ts = conn.query_row_as::<Timestamp>(sql, &[])?;
99/// assert_eq!(ts.to_string(), "2017-08-09 11:22:33.500000000");
100///
101/// // Bind Timestamp
102/// let sql = "begin \
103///              :outval := :inval + interval '+1 02:03:04.5' day to second; \
104///            end;";
105/// let mut stmt = conn.statement(sql).build()?;
106/// stmt.execute(&[&OracleType::Timestamp(3), // bind null as timestamp(3)
107///                &ts, // bind the ts variable
108///               ])?;
109/// let outval: Timestamp = stmt.bind_value(1)?; // get the first bind value.
110/// // ts + (1 day, 2 hours, 3 minutes and 4.5 seconds)
111/// assert_eq!(outval.to_string(), "2017-08-10 13:25:38.000");
112/// # Ok::<(), Error>(())
113/// ```
114#[derive(Debug, Clone, Copy)]
115pub struct Timestamp {
116    pub(crate) ts: dpiTimestamp,
117    precision: u8,
118    with_tz: bool,
119}
120
121impl Timestamp {
122    fn check_ymd_hms_ns(
123        year: i32,
124        month: u32,
125        day: u32,
126        hour: u32,
127        minute: u32,
128        second: u32,
129        nanosecond: u32,
130    ) -> Result<()> {
131        let mut errmsg = "";
132        if !(-4713..=9999).contains(&year) {
133            errmsg = "year must be between -4713 and 9999";
134        } else if !(1..=12).contains(&month) {
135            errmsg = "month must be between 1 and 12";
136        } else if !(1..=31).contains(&day) {
137            errmsg = "day must be between 1 and 31";
138        } else if !(0..=23).contains(&hour) {
139            errmsg = "hour must be between 0 and 23";
140        } else if !(0..=59).contains(&minute) {
141            errmsg = "minute must be between 0 and 59";
142        } else if !(0..=59).contains(&second) {
143            errmsg = "second must be between 0 and 59";
144        } else if !(0..=999999999).contains(&nanosecond) {
145            errmsg = "nanosecond must be between 0 and 999_999_999";
146        }
147        if errmsg.is_empty() {
148            Ok(())
149        } else {
150            let year_width = if year >= 0 { 4 } else { 5 }; // width including sign
151            Err(Error::out_of_range(format!(
152                "{errmsg} but {year:0year_width$}-{month:02}-{day:02} {hour:02}:{minute:02}:{second:02}.{nanosecond:09}",
153            )))
154        }
155    }
156
157    fn check_tz_hm_offset(hour_offset: i32, minute_offset: i32) -> Result<()> {
158        if !(-59..=59).contains(&minute_offset) {
159            Err(Error::out_of_range(format!(
160                "minute_offset must be between -59 and 59 but {}",
161                minute_offset
162            )))
163        } else if hour_offset < 0 && minute_offset > 0 {
164            Err(Error::out_of_range(
165                "hour_offset is negative but minimum is positive",
166            ))
167        } else if hour_offset > 0 && minute_offset < 0 {
168            Err(Error::out_of_range(
169                "hour_offset is positive but minimum is negative",
170            ))
171        } else {
172            Ok(())
173        }
174    }
175
176    pub(crate) fn from_dpi_timestamp(ts: &dpiTimestamp, oratype: &OracleType) -> Timestamp {
177        let (precision, with_tz) = match *oratype {
178            OracleType::Timestamp(prec) => (prec, false),
179            OracleType::TimestampTZ(prec) => (prec, true),
180            OracleType::TimestampLTZ(prec) => (prec, true),
181            _ => (0, false),
182        };
183        Timestamp {
184            ts: *ts,
185            precision,
186            with_tz,
187        }
188    }
189
190    /// Creates a timestamp.
191    ///
192    /// Valid values are:
193    ///
194    /// | argument | valid values |
195    /// |---|---|
196    /// | `year` | -4713 to 9999 |
197    /// | `month` | 1 to 12 |
198    /// | `day` | 1 to 31 |
199    /// | `hour` | 0 to 23 |
200    /// | `minute` | 0 to 59 |
201    /// | `second` | 0 to 59 |
202    /// | `nanosecond` | 0 to 999,999,999 |
203    ///
204    pub fn new(
205        year: i32,
206        month: u32,
207        day: u32,
208        hour: u32,
209        minute: u32,
210        second: u32,
211        nanosecond: u32,
212    ) -> Result<Timestamp> {
213        Self::check_ymd_hms_ns(year, month, day, hour, minute, second, nanosecond)?;
214        Ok(Timestamp {
215            ts: dpiTimestamp {
216                year: year as i16,
217                month: month as u8,
218                day: day as u8,
219                hour: hour as u8,
220                minute: minute as u8,
221                second: second as u8,
222                fsecond: nanosecond,
223                tzHourOffset: 0,
224                tzMinuteOffset: 0,
225            },
226            precision: 9,
227            with_tz: false,
228        })
229    }
230
231    /// Creates a timestamp with time zone.
232    ///
233    /// `offset` is time zone offset seconds from UTC.
234    #[inline]
235    pub fn and_tz_offset(&self, offset: i32) -> Result<Timestamp> {
236        self.and_tz_hm_offset(offset / 3600, offset % 3600 / 60)
237    }
238
239    /// Creates a timestamp with time zone.
240    ///
241    /// `hour_offset` and `minute_offset` are time zone offset in hours and minutes from UTC.
242    /// All arguments must be zero or positive in the eastern hemisphere. They must be
243    /// zero or negative in the western hemisphere.
244    #[inline]
245    pub fn and_tz_hm_offset(&self, hour_offset: i32, minute_offset: i32) -> Result<Timestamp> {
246        Self::check_tz_hm_offset(hour_offset, minute_offset)?;
247        Ok(Timestamp {
248            ts: dpiTimestamp {
249                tzHourOffset: hour_offset as i8,
250                tzMinuteOffset: minute_offset as i8,
251                ..self.ts
252            },
253            with_tz: true,
254            ..*self
255        })
256    }
257
258    /// Creates a timestamp with precision.
259    ///
260    /// The precision affects text representation of Timestamp.
261    /// It doesn't affect comparison.
262    #[inline]
263    pub fn and_prec(&self, precision: u8) -> Result<Timestamp> {
264        if precision > 9 {
265            Err(Error::out_of_range(format!(
266                "precision must be 0 to 9 but {}",
267                precision
268            )))
269        } else {
270            Ok(Timestamp { precision, ..*self })
271        }
272    }
273
274    /// Returns the year number from -4713 to 9999.
275    pub fn year(&self) -> i32 {
276        self.ts.year.into()
277    }
278
279    /// Returns the month number from 1 to 12.
280    pub fn month(&self) -> u32 {
281        self.ts.month.into()
282    }
283
284    /// Returns the day number from 1 to 31.
285    pub fn day(&self) -> u32 {
286        self.ts.day.into()
287    }
288
289    /// Returns the hour number from 0 to 23.
290    pub fn hour(&self) -> u32 {
291        self.ts.hour.into()
292    }
293
294    /// Returns the minute number from 0 to 59.
295    pub fn minute(&self) -> u32 {
296        self.ts.minute.into()
297    }
298
299    /// Returns the second number from 0 to 59.
300    pub fn second(&self) -> u32 {
301        self.ts.second.into()
302    }
303
304    /// Returns the nanosecond number from 0 to 999,999,999.
305    pub fn nanosecond(&self) -> u32 {
306        self.ts.fsecond
307    }
308
309    /// Returns hour component of time zone.
310    pub fn tz_hour_offset(&self) -> i32 {
311        self.ts.tzHourOffset.into()
312    }
313
314    /// Returns minute component of time zone.
315    pub fn tz_minute_offset(&self) -> i32 {
316        self.ts.tzMinuteOffset.into()
317    }
318
319    /// Returns precision
320    pub fn precision(&self) -> u8 {
321        self.precision
322    }
323
324    /// Returns true when the timestamp's text representation includes time zone information.
325    /// Otherwise, false.
326    pub fn with_tz(&self) -> bool {
327        self.with_tz
328    }
329
330    /// Returns total time zone offset from UTC in seconds.
331    pub fn tz_offset(&self) -> i32 {
332        self.ts.tzHourOffset as i32 * 3600 + self.ts.tzMinuteOffset as i32 * 60
333    }
334}
335
336impl cmp::PartialEq for Timestamp {
337    fn eq(&self, other: &Self) -> bool {
338        self.ts.year == other.ts.year
339            && self.ts.month == other.ts.month
340            && self.ts.day == other.ts.day
341            && self.ts.hour == other.ts.hour
342            && self.ts.minute == other.ts.minute
343            && self.ts.second == other.ts.second
344            && self.ts.fsecond == other.ts.fsecond
345            && self.ts.tzHourOffset == other.ts.tzHourOffset
346            && self.ts.tzMinuteOffset == other.ts.tzMinuteOffset
347    }
348}
349
350impl fmt::Display for Timestamp {
351    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
352        let (ymd_sep, ymd_hms_sep, hms_sep, hms_tz_sep) = if !f.alternate() {
353            // Default format    : YYYY-MM-DD hh:mm:ss.ssssss ±hh:mm
354            ("-", ' ', ":", " ")
355        } else if !f.sign_minus() {
356            // Extended ISO 8601 : YYYY-MM-DDThh:mm:ss.ssssss±hh:mm
357            ("-", 'T', ":", "")
358        } else {
359            // Basic ISO 8601    : YYYYMMDDThhmmss.ssssss±hhmm
360            ("", 'T', "", "")
361        };
362        let ts = &self.ts;
363        let year_width = if ts.year >= 0 { 4 } else { 5 };
364        write!(
365            f,
366            "{:0year_width$}{ymd_sep}{:02}{ymd_sep}{:02}{ymd_hms_sep}{:02}{hms_sep}{:02}{hms_sep}{:02}",
367            ts.year, ts.month, ts.day,
368            ts.hour, ts.minute, ts.second
369        )?;
370        let prec = cmp::min(f.precision().unwrap_or(self.precision.into()), 9);
371        if prec != 0 {
372            const SUBSEC_DIV: [u32; 9] = [
373                100000000, 10000000, 1000000, 100000, 10000, 1000, 100, 10, 1,
374            ];
375            write!(f, ".{:0prec$}", self.ts.fsecond / SUBSEC_DIV[prec - 1])?;
376        }
377        if self.with_tz {
378            let sign = if self.ts.tzHourOffset < 0 || self.ts.tzMinuteOffset < 0 {
379                '-'
380            } else {
381                '+'
382            };
383            write!(
384                f,
385                "{hms_tz_sep}{sign}{:02}{hms_sep}{:02}",
386                self.ts.tzHourOffset.abs(),
387                self.ts.tzMinuteOffset.abs()
388            )?;
389        }
390        Ok(())
391    }
392}
393
394impl str::FromStr for Timestamp {
395    type Err = ParseOracleTypeError;
396
397    fn from_str(s: &str) -> result::Result<Self, Self::Err> {
398        let err = || ParseOracleTypeError::new("Timestamp");
399        let mut s = Scanner::new(s);
400        let minus = if let Some('-') = s.char() {
401            s.next();
402            true
403        } else {
404            false
405        };
406        let mut year = s.read_digits().ok_or_else(err)?;
407        let mut month = 1;
408        let mut day = 1;
409        match s.char() {
410            Some('T') | Some(' ') | None => {
411                if year > 10000 {
412                    day = year % 100;
413                    month = (year / 100) % 100;
414                    year /= 10000;
415                }
416            }
417            Some('-') => {
418                s.next();
419                month = s.read_digits().ok_or_else(err)?;
420                if let Some('-') = s.char() {
421                    s.next();
422                    day = s.read_digits().ok_or_else(err)?
423                }
424            }
425            _ => return Err(err()),
426        }
427        let mut hour = 0;
428        let mut min = 0;
429        let mut sec = 0;
430        let mut nsec = 0;
431        let mut tz_hour: i32 = 0;
432        let mut tz_min: i32 = 0;
433        let mut precision = 0;
434        let mut with_tz = false;
435        if let Some(c) = s.char() {
436            match c {
437                'T' | ' ' => {
438                    s.next();
439                    hour = s.read_digits().ok_or_else(err)?;
440                    if let Some(':') = s.char() {
441                        s.next();
442                        min = s.read_digits().ok_or_else(err)?;
443                        if let Some(':') = s.char() {
444                            s.next();
445                            sec = s.read_digits().ok_or_else(err)?;
446                        }
447                    } else if s.ndigits() == 6 {
448                        // 123456 -> 12:34:56
449                        sec = hour % 100;
450                        min = (hour / 100) % 100;
451                        hour /= 10000;
452                    } else {
453                        return Err(err());
454                    }
455                }
456                _ => return Err(err()),
457            }
458            if let Some('.') = s.char() {
459                s.next();
460                nsec = s.read_digits().ok_or_else(err)?;
461                let ndigit = s.ndigits();
462                precision = ndigit;
463                match ndigit.cmp(&9) {
464                    Ordering::Less => nsec *= 10u64.pow(9 - ndigit),
465                    Ordering::Equal => (),
466                    Ordering::Greater => {
467                        nsec /= 10u64.pow(ndigit - 9);
468                        precision = 9;
469                    }
470                }
471            }
472            if let Some(' ') = s.char() {
473                s.next();
474            }
475            match s.char() {
476                Some('+') => {
477                    s.next();
478                    tz_hour = s.read_digits().ok_or_else(err)? as i32;
479                    if let Some(':') = s.char() {
480                        s.next();
481                        tz_min = s.read_digits().ok_or_else(err)? as i32;
482                    } else {
483                        tz_min = tz_hour % 100;
484                        tz_hour /= 100;
485                    }
486                    with_tz = true;
487                }
488                Some('-') => {
489                    s.next();
490                    tz_hour = s.read_digits().ok_or_else(err)? as i32;
491                    if let Some(':') = s.char() {
492                        s.next();
493                        tz_min = s.read_digits().ok_or_else(err)? as i32;
494                    } else {
495                        tz_min = tz_hour % 100;
496                        tz_hour /= 100;
497                    }
498                    tz_hour = -tz_hour;
499                    tz_min = -tz_min;
500                    with_tz = true;
501                }
502                Some('Z') => {
503                    s.next();
504                    with_tz = true;
505                }
506                _ => (),
507            }
508            if s.char().is_some() {
509                return Err(err());
510            }
511        }
512        let mut ts = Timestamp::new(
513            if minus { -(year as i32) } else { year as i32 },
514            month as u32,
515            day as u32,
516            hour as u32,
517            min as u32,
518            sec as u32,
519            nsec as u32,
520        )
521        .map_err(|_| err())?;
522        ts.precision = precision as u8;
523        if with_tz {
524            ts = ts.and_tz_hm_offset(tz_hour, tz_min).map_err(|_| err())?;
525        }
526        Ok(ts)
527    }
528}
529
530#[cfg(test)]
531mod tests {
532    use super::*;
533
534    #[test]
535    fn format_year() -> Result<()> {
536        let ts = Timestamp::new(1234, 3, 4, 5, 6, 7, 0)?;
537        assert_eq!(format!("{}", ts), "1234-03-04 05:06:07.000000000");
538        let ts = Timestamp::new(123, 3, 4, 5, 6, 7, 0)?;
539        assert_eq!(format!("{}", ts), "0123-03-04 05:06:07.000000000");
540        let ts = Timestamp::new(12, 3, 4, 5, 6, 7, 0)?;
541        assert_eq!(format!("{}", ts), "0012-03-04 05:06:07.000000000");
542        let ts = Timestamp::new(1, 3, 4, 5, 6, 7, 0)?;
543        assert_eq!(format!("{}", ts), "0001-03-04 05:06:07.000000000");
544        let ts = Timestamp::new(-1234, 3, 4, 5, 6, 7, 0)?;
545        assert_eq!(format!("{}", ts), "-1234-03-04 05:06:07.000000000");
546        let ts = Timestamp::new(-123, 3, 4, 5, 6, 7, 0)?;
547        assert_eq!(format!("{}", ts), "-0123-03-04 05:06:07.000000000");
548        let ts = Timestamp::new(-12, 3, 4, 5, 6, 7, 0)?;
549        assert_eq!(format!("{}", ts), "-0012-03-04 05:06:07.000000000");
550        let ts = Timestamp::new(-1, 3, 4, 5, 6, 7, 0)?;
551        assert_eq!(format!("{}", ts), "-0001-03-04 05:06:07.000000000");
552        Ok(())
553    }
554
555    #[test]
556    fn format_with_prec_in_fmt_param() -> Result<()> {
557        let ts = Timestamp::new(2012, 3, 4, 5, 6, 7, 0)?;
558        // precision in formatting parameters
559        assert_eq!(format!("{:.0}", ts), "2012-03-04 05:06:07");
560        assert_eq!(format!("{:.1}", ts), "2012-03-04 05:06:07.0");
561        assert_eq!(format!("{:.2}", ts), "2012-03-04 05:06:07.00");
562        assert_eq!(format!("{:.3}", ts), "2012-03-04 05:06:07.000");
563        assert_eq!(format!("{:.4}", ts), "2012-03-04 05:06:07.0000");
564        assert_eq!(format!("{:.5}", ts), "2012-03-04 05:06:07.00000");
565        assert_eq!(format!("{:.6}", ts), "2012-03-04 05:06:07.000000");
566        assert_eq!(format!("{:.7}", ts), "2012-03-04 05:06:07.0000000");
567        assert_eq!(format!("{:.8}", ts), "2012-03-04 05:06:07.00000000");
568        assert_eq!(format!("{:.9}", ts), "2012-03-04 05:06:07.000000000");
569        // The precision is up to 9.
570        assert_eq!(format!("{:.10}", ts), "2012-03-04 05:06:07.000000000");
571        assert_eq!(format!("{:.11}", ts), "2012-03-04 05:06:07.000000000");
572        Ok(())
573    }
574
575    #[test]
576    fn format_with_prec_in_type() -> Result<()> {
577        let ts = Timestamp::new(2012, 3, 4, 5, 6, 7, 0)?;
578        assert_eq!(format!("{}", ts.and_prec(0)?), "2012-03-04 05:06:07");
579        assert_eq!(format!("{}", ts.and_prec(1)?), "2012-03-04 05:06:07.0");
580        assert_eq!(format!("{}", ts.and_prec(2)?), "2012-03-04 05:06:07.00");
581        assert_eq!(format!("{}", ts.and_prec(3)?), "2012-03-04 05:06:07.000");
582        assert_eq!(format!("{}", ts.and_prec(4)?), "2012-03-04 05:06:07.0000");
583        assert_eq!(format!("{}", ts.and_prec(5)?), "2012-03-04 05:06:07.00000");
584        assert_eq!(format!("{}", ts.and_prec(6)?), "2012-03-04 05:06:07.000000");
585        assert_eq!(
586            format!("{}", ts.and_prec(7)?),
587            "2012-03-04 05:06:07.0000000"
588        );
589        assert_eq!(
590            format!("{}", ts.and_prec(8)?),
591            "2012-03-04 05:06:07.00000000"
592        );
593        assert_eq!(
594            format!("{}", ts.and_prec(9)?),
595            "2012-03-04 05:06:07.000000000"
596        );
597        Ok(())
598    }
599
600    #[test]
601    fn format_as_iso_8601() -> Result<()> {
602        let ts = Timestamp::new(2012, 3, 4, 5, 6, 7, 0)?.and_prec(0)?;
603        assert_eq!(format!("{}", ts), "2012-03-04 05:06:07");
604        assert_eq!(format!("{:#}", ts), "2012-03-04T05:06:07");
605        assert_eq!(format!("{:-#}", ts), "20120304T050607");
606        let ts = ts.and_prec(3)?;
607        assert_eq!(format!("{}", ts), "2012-03-04 05:06:07.000");
608        assert_eq!(format!("{:#}", ts), "2012-03-04T05:06:07.000");
609        assert_eq!(format!("{:-#}", ts), "20120304T050607.000");
610        let ts = ts.and_tz_hm_offset(9, 30)?;
611        assert_eq!(format!("{}", ts), "2012-03-04 05:06:07.000 +09:30");
612        assert_eq!(format!("{:#}", ts), "2012-03-04T05:06:07.000+09:30");
613        assert_eq!(format!("{:-#}", ts), "20120304T050607.000+0930");
614        let ts = ts.and_tz_hm_offset(-9, -30)?;
615        assert_eq!(format!("{}", ts), "2012-03-04 05:06:07.000 -09:30");
616        assert_eq!(format!("{:#}", ts), "2012-03-04T05:06:07.000-09:30");
617        assert_eq!(format!("{:-#}", ts), "20120304T050607.000-0930");
618        Ok(())
619    }
620
621    #[test]
622    fn parse() -> Result<()> {
623        let ts = Timestamp::new(2012, 1, 1, 0, 0, 0, 0)?;
624        assert_eq!("2012".parse(), Ok(ts));
625        assert_eq!("2012-01-01".parse(), Ok(ts));
626        assert_eq!("20120101".parse(), Ok(ts));
627        let ts = Timestamp::new(2012, 3, 4, 0, 0, 0, 0)?;
628        assert_eq!("2012-03-04".parse(), Ok(ts));
629        assert_eq!("20120304".parse(), Ok(ts));
630        let ts = Timestamp::new(2012, 3, 4, 5, 6, 7, 0)?;
631        assert_eq!("2012-03-04 05:06:07".parse(), Ok(ts));
632        assert_eq!("2012-03-04T05:06:07".parse(), Ok(ts));
633        assert_eq!("20120304T050607".parse(), Ok(ts));
634        let ts = Timestamp::new(2012, 3, 4, 5, 6, 7, 800_000_000)?;
635        assert_eq!("2012-03-04 05:06:07.8".parse(), Ok(ts));
636        assert_eq!("2012-03-04T05:06:07.8".parse(), Ok(ts));
637        assert_eq!("20120304T050607.8".parse(), Ok(ts));
638        let ts = Timestamp::new(2012, 3, 4, 5, 6, 7, 890_000_000)?;
639        assert_eq!("2012-03-04 05:06:07.89".parse(), Ok(ts));
640        assert_eq!("2012-03-04T05:06:07.89".parse(), Ok(ts));
641        assert_eq!("20120304T050607.89".parse(), Ok(ts));
642        let ts = Timestamp::new(2012, 3, 4, 5, 6, 7, 890_123_456)?;
643        assert_eq!("2012-03-04 05:06:07.890123456".parse(), Ok(ts));
644        assert_eq!("2012-03-04T05:06:07.890123456".parse(), Ok(ts));
645        assert_eq!("20120304T050607.890123456".parse(), Ok(ts));
646        let ts = Timestamp::new(2012, 3, 4, 5, 6, 7, 0)?.and_tz_hm_offset(0, 0)?;
647        assert_eq!("2012-03-04 05:06:07Z".parse(), Ok(ts));
648        assert_eq!("2012-03-04 05:06:07+00:00".parse(), Ok(ts));
649        assert_eq!("2012-03-04 05:06:07 +00:00".parse(), Ok(ts));
650        assert_eq!("2012-03-04 05:06:07+0000".parse(), Ok(ts));
651        assert_eq!("2012-03-04 05:06:07 +0000".parse(), Ok(ts));
652        let ts = Timestamp::new(2012, 3, 4, 5, 6, 7, 0)?.and_tz_hm_offset(9, 30)?;
653        assert_eq!("2012-03-04 05:06:07+09:30".parse(), Ok(ts));
654        assert_eq!("2012-03-04 05:06:07 +09:30".parse(), Ok(ts));
655        assert_eq!("2012-03-04 05:06:07+0930".parse(), Ok(ts));
656        assert_eq!("2012-03-04 05:06:07 +0930".parse(), Ok(ts));
657        let ts = Timestamp::new(2012, 3, 4, 5, 6, 7, 0)?.and_tz_hm_offset(-9, -30)?;
658        assert_eq!("2012-03-04 05:06:07-09:30".parse(), Ok(ts));
659        assert_eq!("2012-03-04 05:06:07 -09:30".parse(), Ok(ts));
660        assert_eq!("2012-03-04 05:06:07-0930".parse(), Ok(ts));
661        assert_eq!("2012-03-04 05:06:07 -0930".parse(), Ok(ts));
662        let ts = Timestamp::new(2012, 3, 4, 5, 6, 7, 123_000_000)?.and_tz_hm_offset(-9, -30)?;
663        assert_eq!("2012-03-04 05:06:07.123-09:30".parse(), Ok(ts));
664        assert_eq!("2012-03-04 05:06:07.123 -09:30".parse(), Ok(ts));
665        assert_eq!("2012-03-04 05:06:07.123-0930".parse(), Ok(ts));
666        assert_eq!("2012-03-04 05:06:07.123 -0930".parse(), Ok(ts));
667        Ok(())
668    }
669}