yasna/models/
time.rs

1// Copyright 2016 Masaki Hara
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9use alloc::string::String;
10use alloc::vec::Vec;
11use core::convert::TryFrom;
12use time::{Date, Month, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset};
13
14/// Date and time between 1950-01-01T00:00:00Z and 2049-12-31T23:59:59Z.
15/// It cannot express fractional seconds and leap seconds.
16/// It doesn't carry timezone information.
17///
18/// Corresponds to ASN.1 UTCTime type. Often used in conjunction with
19/// [`GeneralizedTime`].
20///
21/// # Features
22///
23/// This struct is enabled by `time` feature.
24///
25/// ```toml
26/// [dependencies]
27/// yasna = { version = "*", features = ["time"] }
28/// ```
29///
30/// # Examples
31///
32/// ```
33/// # fn main() {
34/// use yasna::models::UTCTime;
35/// let datetime = *UTCTime::parse(b"8201021200Z").unwrap().datetime();
36/// assert_eq!(datetime.year(), 1982);
37/// assert_eq!(datetime.month() as u8, 1);
38/// assert_eq!(datetime.day(), 2);
39/// assert_eq!(datetime.hour(), 12);
40/// assert_eq!(datetime.minute(), 0);
41/// assert_eq!(datetime.second(), 0);
42/// assert_eq!(datetime.nanosecond(), 0);
43/// # }
44/// ```
45#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
46pub struct UTCTime {
47    datetime: OffsetDateTime,
48}
49
50impl UTCTime {
51    /// Parses ASN.1 string representation of UTCTime.
52    ///
53    /// # Examples
54    ///
55    /// ```
56    /// use yasna::models::UTCTime;
57    /// let datetime = UTCTime::parse(b"000229123456Z").unwrap();
58    /// assert_eq!(&datetime.to_string(), "000229123456Z");
59    /// ```
60    ///
61    /// # Errors
62    ///
63    /// It returns `None` if the given string does not specify a correct
64    /// datetime.
65    ///
66    /// # Interpretation
67    ///
68    /// While neither X.680 nor X.690 specify interpretation of 2-digits year,
69    /// X.501 specifies that UTCTime in Time shall be interpreted as between
70    /// 1950 and 2049. This method parses the string according to the X.501
71    /// rule.
72    pub fn parse(buf: &[u8]) -> Option<Self> {
73        if buf.len() < 11 {
74            return None;
75        }
76        // i: a position of [Z+-].
77        let i = if [b'+', b'-', b'Z'].contains(&buf[10]) { 10 } else { 12 };
78        if buf.len() < i+1 || ![b'+', b'-', b'Z'].contains(&buf[i]) {
79            return None;
80        }
81        let len = if buf[i] == b'Z' { i+1 } else { i+5 };
82        if len != buf.len() {
83            return None;
84        }
85        if !buf[..i].iter().all(|&b| b'0' <= b && b <= b'9') ||
86            !buf[i+1..].iter().all(|&b| b'0' <= b && b <= b'9')
87        {
88            return None;
89        }
90        let year_short: i32 =
91            ((buf[0] - b'0') as i32) * 10 + ((buf[1] - b'0') as i32);
92        let year = if year_short < 50 {
93            year_short + 2000
94        } else {
95            year_short + 1900
96        };
97        let month = Month::try_from((buf[2] - b'0') * 10 + (buf[3] - b'0')).ok()?;
98        let day = (buf[4] - b'0') * 10 + (buf[5] - b'0');
99        let hour = (buf[6] - b'0') * 10 + (buf[7] - b'0');
100        let minute = (buf[8] - b'0') * 10 + (buf[9] - b'0');
101        let second = if i == 12 {
102            (buf[10] - b'0') * 10 + (buf[11] - b'0')
103        } else {
104            0
105        };
106        let offset_hour: i8 = if buf[i] == b'Z' {
107            0
108        } else {
109            ((buf[i+1] - b'0') as i8) * 10 + ((buf[i+2] - b'0') as i8)
110        };
111        let offset_minute: i8 = if buf[i] == b'Z' {
112            0
113        } else {
114            ((buf[i+3] - b'0') as i8) * 10 + ((buf[i+4] - b'0') as i8)
115        };
116        let date = Date::from_calendar_date(year, month, day).ok()?;
117        let time = Time::from_hms(hour, minute, second).ok()?;
118        let datetime = PrimitiveDateTime::new(date, time);
119        if !(offset_hour < 24 && offset_minute < 60) {
120            return None;
121        }
122        let offset = if buf[i] == b'+' {
123            UtcOffset::from_hms(offset_hour, offset_minute, 0).ok()?
124        } else {
125            UtcOffset::from_hms(-offset_hour, -offset_minute, 0).ok()?
126        };
127        let datetime = datetime.assume_offset(offset).to_offset(UtcOffset::UTC);
128        // While the given local datatime is in [1950, 2050) by definition,
129        // the UTC datetime can be out of bounds. We check this.
130        if !(1950 <= datetime.year() && datetime.year() < 2050) {
131            return None;
132        }
133        return Some(UTCTime {
134            datetime: datetime,
135        });
136    }
137
138    /// Constructs `UTCTime` from an `OffsetDateTime`.
139    ///
140    /// # Panics
141    ///
142    /// Panics when UTCTime can't represent the datetime. That is:
143    ///
144    /// - The year is not between 1950 and 2049.
145    /// - It is in a leap second.
146    /// - It has a non-zero nanosecond value.
147    pub fn from_datetime(datetime: OffsetDateTime) -> Self {
148        let datetime = datetime.to_offset(UtcOffset::UTC);
149        assert!(1950 <= datetime.year() && datetime.year() < 2050,
150            "Can't express a year {:?} in UTCTime", datetime.year());
151        assert!(datetime.nanosecond() < 1_000_000_000,
152            "Can't express a leap second in UTCTime");
153        assert!(datetime.nanosecond() == 0,
154            "Can't express a non-zero nanosecond in UTCTime");
155        return UTCTime {
156            datetime: datetime,
157        };
158    }
159
160    /// Constructs `UTCTime` from an `OffsetDateTime`.
161    ///
162    /// # Errors
163    ///
164    /// It returns `None` when UTCTime can't represent the datetime. That is:
165    ///
166    /// - The year is not between 1950 and 2049.
167    /// - It is in a leap second.
168    /// - It has a non-zero nanosecond value.
169    pub fn from_datetime_opt(datetime: OffsetDateTime) -> Option<Self> {
170        let datetime = datetime.to_offset(UtcOffset::UTC);
171        if !(1950 <= datetime.year() && datetime.year() < 2050) {
172            return None;
173        }
174        if !(datetime.nanosecond() == 0) {
175            return None;
176        }
177        return Some(UTCTime {
178            datetime: datetime,
179        });
180    }
181
182    /// Returns the `OffsetDateTime` it represents.
183    pub fn datetime(&self) -> &OffsetDateTime {
184        &self.datetime
185    }
186
187    /// Returns ASN.1 canonical representation of the datetime as `Vec<u8>`.
188    pub fn to_bytes(&self) -> Vec<u8> {
189        let mut buf = Vec::with_capacity(13);
190        buf.push((self.datetime.year() / 10 % 10) as u8 + b'0');
191        buf.push((self.datetime.year() % 10) as u8 + b'0');
192        buf.push((self.datetime.month() as u8 / 10 % 10) + b'0');
193        buf.push((self.datetime.month() as u8 % 10) + b'0');
194        buf.push((self.datetime.day() / 10 % 10) as u8 + b'0');
195        buf.push((self.datetime.day() % 10) as u8 + b'0');
196        buf.push((self.datetime.hour() / 10 % 10) as u8 + b'0');
197        buf.push((self.datetime.hour() % 10) as u8 + b'0');
198        buf.push((self.datetime.minute() / 10 % 10) as u8 + b'0');
199        buf.push((self.datetime.minute() % 10) as u8 + b'0');
200        buf.push((self.datetime.second() / 10 % 10) as u8 + b'0');
201        buf.push((self.datetime.second() % 10) as u8 + b'0');
202        buf.push(b'Z');
203        return buf;
204    }
205
206    /// Returns ASN.1 canonical representation of the datetime as `String`.
207    pub fn to_string(&self) -> String {
208        String::from_utf8(self.to_bytes()).unwrap()
209    }
210}
211
212/// Date and time between 0000-01-01T00:00:00Z and 9999-12-31T23:59:60.999...Z.
213///
214/// It can contain arbitrary length of decimal fractional seconds.
215/// However, it doesn't carry accuracy information.
216/// It can also contain leap seconds.
217///
218/// The datetime is canonicalized to UTC.
219/// It doesn't carry timezone information.
220///
221/// Corresponds to ASN.1 GeneralizedTime type. Often used in conjunction with
222/// [`UTCTime`].
223///
224/// # Features
225///
226/// This struct is enabled by `time` feature.
227///
228/// ```toml
229/// [dependencies]
230/// yasna = { version = "*", features = ["time"] }
231/// ```
232///
233/// # Examples
234///
235/// ```
236/// # fn main() {
237/// use yasna::models::GeneralizedTime;
238/// let datetime =
239///     *GeneralizedTime::parse(b"19851106210627.3Z").unwrap().datetime();
240/// assert_eq!(datetime.year(), 1985);
241/// assert_eq!(datetime.month() as u8, 11);
242/// assert_eq!(datetime.day(), 6);
243/// assert_eq!(datetime.hour(), 21);
244/// assert_eq!(datetime.minute(), 6);
245/// assert_eq!(datetime.second(), 27);
246/// assert_eq!(datetime.nanosecond(), 300_000_000);
247/// # }
248/// ```
249#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
250pub struct GeneralizedTime {
251    datetime: OffsetDateTime,
252    sub_nano: Vec<u8>,
253    // TODO: time does not support leap seconds. This is a simple hack to support round-tripping.
254    is_leap_second: bool,
255}
256
257impl GeneralizedTime {
258    /// Almost same as `parse`. It takes `default_offset` however.
259    /// GeneralizedTime value can omit offset in local time.
260    /// In that case, `default_offset` is used instead.
261    fn parse_general(buf: &[u8], default_offset: Option<UtcOffset>) -> Option<Self> {
262        if buf.len() < 10 {
263            return None;
264        }
265        if !buf[..10].iter().all(|&b| b'0' <= b && b <= b'9') {
266            return None;
267        }
268        let year: i32 =
269            ((buf[0] - b'0') as i32) * 1000 + ((buf[1] - b'0') as i32) * 100
270            + ((buf[2] - b'0') as i32) * 10 + ((buf[3] - b'0') as i32);
271        let month = Month::try_from((buf[4] - b'0') * 10 + (buf[5] - b'0')).ok()?;
272        let day = (buf[6] - b'0') * 10 + (buf[7] - b'0');
273        let hour = (buf[8] - b'0') * 10 + (buf[9] - b'0');
274        // i: current position on `buf`
275        let mut i = 10;
276        // The factor to scale the fraction part to nanoseconds.
277        let mut fraction_scale : i64 = 1_000_000_000;
278        let mut minute: u8;
279        if i+2 <= buf.len() &&
280                buf[i..i+2].iter().all(|&b| b'0' <= b && b <= b'9') {
281            minute = (buf[i] - b'0') * 10 + (buf[i + 1] - b'0');
282            i += 2;
283        } else {
284            fraction_scale = 3_600_000_000_000;
285            minute = 0;
286        }
287        let mut second: u8;
288        if i+2 <= buf.len() &&
289                buf[i..i+2].iter().all(|&b| b'0' <= b && b <= b'9') {
290            second = (buf[i] - b'0') * 10 + (buf[i + 1] - b'0');
291            i += 2;
292        } else {
293            if fraction_scale == 1_000_000_000 {
294                fraction_scale = 60_000_000_000;
295            }
296            second = 0;
297        }
298        let mut nanosecond = 0;
299        let mut sub_nano = Vec::new();
300        if i+2 <= buf.len() && (buf[i] == b'.' || buf[i] == b',')
301                && b'0' <= buf[i+1] && buf[i+1] <= b'9' {
302            i += 1;
303            let mut j = 0;
304            while i+j < buf.len() && b'0' <= buf[i+j] && buf[i+j] <= b'9' {
305                sub_nano.push(b'0');
306                j += 1;
307            }
308            let mut carry : i64 = 0;
309            for k in (0..j).rev() {
310                let digit = (buf[i+k] - b'0') as i64;
311                let sum = digit * fraction_scale + carry;
312                carry = sum / 10;
313                sub_nano[k] = b'0' + ((sum % 10) as u8);
314            }
315            nanosecond = (carry % 1_000_000_000) as u32;
316            second += (carry / 1_000_000_000 % 60) as u8;
317            minute += (carry / 60_000_000_000) as u8;
318            while let Some(&digit) = sub_nano.last() {
319                if digit == b'0' {
320                    sub_nano.pop();
321                } else {
322                    break;
323                }
324            }
325            i += j;
326        }
327        let mut is_leap_second = false;
328        if second == 60 {
329            // TODO: `time` doesn't accept leap seconds, so we use a flag to preserve
330            is_leap_second = true;
331            second = 59;
332        }
333        let date = Date::from_calendar_date(year, month, day).ok()?;
334        let time = Time::from_hms_nano(hour, minute, second, nanosecond).ok()?;
335        let naive_datetime = PrimitiveDateTime::new(date, time);
336        let datetime: OffsetDateTime;
337        if i == buf.len() {
338            // Local datetime with no timezone information.
339            datetime = naive_datetime
340                .assume_offset(default_offset?)
341                .to_offset(UtcOffset::UTC);
342        } else if i < buf.len() && buf[i] == b'Z' {
343            // UTC time.
344            datetime = naive_datetime.assume_utc();
345            i += 1;
346        } else if i < buf.len() && (buf[i] == b'+' || buf[i] == b'-') {
347            // Local datetime with offset information.
348            let offset_sign = buf[i];
349            i += 1;
350            if !(i+2 <= buf.len() &&
351                    buf[i..i+2].iter().all(|&b| b'0' <= b && b <= b'9')) {
352                return None;
353            }
354            let offset_hour =
355                ((buf[i] - b'0') as i8) * 10 + ((buf[i+1] - b'0') as i8);
356            i += 2;
357            let offset_minute;
358            if i+2 <= buf.len() &&
359                    buf[i..i+2].iter().all(|&b| b'0' <= b && b <= b'9') {
360                offset_minute =
361                    ((buf[i] - b'0') as i8) * 10 + ((buf[i+1] - b'0') as i8);
362                i += 2;
363            } else {
364                offset_minute = 0;
365            }
366            if !(offset_hour < 24 && offset_minute < 60) {
367                return None;
368            }
369            let offset = if offset_sign == b'+' {
370                UtcOffset::from_hms(offset_hour, offset_minute, 0)
371            } else {
372                UtcOffset::from_hms(-offset_hour, -offset_minute, 0)
373            };
374            datetime = naive_datetime
375                .assume_offset(offset.ok()?)
376                .to_offset(UtcOffset::UTC);
377        } else {
378            return None;
379        }
380        if i != buf.len() {
381            return None;
382        }
383        // While the given local datatime is in [0, 10000) by definition,
384        // the UTC datetime can be out of bounds. We check this.
385        if !(0 <= datetime.year() && datetime.year() < 10000) {
386            return None;
387        }
388        return Some(GeneralizedTime {
389            datetime: datetime,
390            sub_nano: sub_nano,
391            is_leap_second,
392        });
393    }
394
395    /// Parses ASN.1 string representation of GeneralizedTime.
396    ///
397    /// # Examples
398    ///
399    /// ```
400    /// use yasna::models::GeneralizedTime;
401    /// let datetime = GeneralizedTime::parse(b"1985110621.14159Z").unwrap();
402    /// assert_eq!(&datetime.to_string(), "19851106210829.724Z");
403    /// ```
404    ///
405    /// # Errors
406    ///
407    /// It returns `None` if the given string does not specify a correct
408    /// datetime.
409    pub fn parse(buf: &[u8]) -> Option<Self> {
410        Self::parse_general(buf, None)
411    }
412
413    /// Parses ASN.1 string representation of GeneralizedTime, with the
414    /// default timezone for local time given.
415    ///
416    /// # Examples
417    ///
418    /// ```
419    /// use yasna::models::GeneralizedTime;
420    /// let datetime = GeneralizedTime::parse(b"1985110621.14159Z").unwrap();
421    /// assert_eq!(&datetime.to_string(), "19851106210829.724Z");
422    /// ```
423    ///
424    /// # Errors
425    ///
426    /// It returns `None` if the given string does not specify a correct
427    /// datetime.
428    pub fn parse_with_offset(buf: &[u8], default_offset: UtcOffset) -> Option<Self> {
429        Self::parse_general(buf, Some(default_offset))
430    }
431
432    /// Constructs `GeneralizedTime` from an `OffsetDateTime`.
433    ///
434    /// # Panics
435    ///
436    /// Panics when GeneralizedTime can't represent the datetime. That is:
437    ///
438    /// - The year is not between 0 and 9999.
439    pub fn from_datetime(datetime: OffsetDateTime) -> Self {
440        let datetime = datetime.to_offset(UtcOffset::UTC);
441        assert!(0 <= datetime.year() && datetime.year() < 10000,
442            "Can't express a year {:?} in GeneralizedTime", datetime.year());
443        return GeneralizedTime {
444            datetime: datetime,
445            sub_nano: Vec::new(),
446            is_leap_second: false,
447        };
448    }
449
450    /// Constructs `GeneralizedTime` from an `OffsetDateTime`.
451    ///
452    /// # Errors
453    ///
454    /// It returns `None` when GeneralizedTime can't represent the datetime.
455    /// That is:
456    ///
457    /// - The year is not between 0 and 9999.
458    pub fn from_datetime_opt(datetime: OffsetDateTime) -> Option<Self> {
459        let datetime = datetime.to_offset(UtcOffset::UTC);
460        if !(0 <= datetime.year() && datetime.year() < 10000) {
461            return None;
462        }
463        return Some(GeneralizedTime {
464            datetime: datetime,
465            sub_nano: Vec::new(),
466            is_leap_second: false,
467        });
468    }
469
470    /// Constructs `GeneralizedTime` from an `OffsetDateTime` and sub-nanoseconds
471    /// digits.
472    ///
473    /// # Panics
474    ///
475    /// Panics when GeneralizedTime can't represent the datetime. That is:
476    ///
477    /// - The year is not between 0 and 9999.
478    ///
479    /// It also panics if `sub_nano` contains a non-digit character.
480    pub fn from_datetime_and_sub_nano(datetime: OffsetDateTime, sub_nano: &[u8]) -> Self {
481        let datetime = datetime.to_offset(UtcOffset::UTC);
482        assert!(0 <= datetime.year() && datetime.year() < 10000,
483            "Can't express a year {:?} in GeneralizedTime", datetime.year());
484        assert!(sub_nano.iter().all(|&b| b'0' <= b && b <= b'9'),
485            "sub_nano contains a non-digit character");
486        let mut sub_nano = sub_nano.to_vec();
487        while sub_nano.len() > 0 && *sub_nano.last().unwrap() == b'0' {
488            sub_nano.pop();
489        }
490        return GeneralizedTime {
491            datetime: datetime,
492            sub_nano: sub_nano,
493            is_leap_second: false,
494        };
495    }
496
497    /// Constructs `GeneralizedTime` from an `OffsetDateTime` and sub-nanoseconds
498    /// digits.
499    ///
500    /// # Errors
501    ///
502    /// It returns `None` when GeneralizedTime can't represent the datetime.
503    /// That is:
504    ///
505    /// - The year is not between 0 and 9999.
506    ///
507    /// It also returns `None` if `sub_nano` contains a non-digit character.
508    pub fn from_datetime_and_sub_nano_opt(
509        datetime: OffsetDateTime,
510        sub_nano: &[u8],
511    ) -> Option<Self> {
512        let datetime = datetime.to_offset(UtcOffset::UTC);
513        if !(0 <= datetime.year() && datetime.year() < 10000) {
514            return None;
515        }
516        if !(sub_nano.iter().all(|&b| b'0' <= b && b <= b'9')) {
517            return None;
518        }
519        let mut sub_nano = sub_nano.to_vec();
520        while sub_nano.len() > 0 && *sub_nano.last().unwrap() == b'0' {
521            sub_nano.pop();
522        }
523        return Some(GeneralizedTime {
524            datetime: datetime,
525            sub_nano: sub_nano,
526            is_leap_second: false,
527        });
528    }
529
530    /// Returns the `OffsetDateTime` it represents.
531    ///
532    /// Leap seconds and sub-nanoseconds digits will be discarded.
533    pub fn datetime(&self) -> &OffsetDateTime {
534        &self.datetime
535    }
536
537    /// Returns sub-nanoseconds digits of the datetime.
538    pub fn sub_nano(&self) -> &[u8] {
539        &self.sub_nano
540    }
541
542    /// Returns ASN.1 canonical representation of the datetime as `Vec<u8>`.
543    pub fn to_bytes(&self) -> Vec<u8> {
544        let mut buf = Vec::with_capacity(24);
545        buf.push((self.datetime.year() / 1000 % 10) as u8 + b'0');
546        buf.push((self.datetime.year() / 100 % 10) as u8 + b'0');
547        buf.push((self.datetime.year() / 10 % 10) as u8 + b'0');
548        buf.push((self.datetime.year() % 10) as u8 + b'0');
549        buf.push((self.datetime.month() as u8 / 10 % 10) + b'0');
550        buf.push((self.datetime.month() as u8 % 10) + b'0');
551        buf.push((self.datetime.day() / 10 % 10) as u8 + b'0');
552        buf.push((self.datetime.day() % 10) as u8 + b'0');
553        buf.push((self.datetime.hour() / 10 % 10) as u8 + b'0');
554        buf.push((self.datetime.hour() % 10) as u8 + b'0');
555        buf.push((self.datetime.minute() / 10 % 10) as u8 + b'0');
556        buf.push((self.datetime.minute() % 10) as u8 + b'0');
557        let mut second = self.datetime.second();
558        let nanosecond = self.datetime.nanosecond();
559        // Cope with leap seconds.
560        if self.is_leap_second {
561            debug_assert!(second == 59,
562                "is_leap_second is set, but second = {}", second);
563            second += 1;
564        }
565        buf.push((second / 10 % 10) as u8 + b'0');
566        buf.push((second % 10) as u8 + b'0');
567        buf.push(b'.');
568        buf.push((nanosecond / 100_000_000 % 10) as u8 + b'0');
569        buf.push((nanosecond / 10_000_000 % 10) as u8 + b'0');
570        buf.push((nanosecond / 1_000_000 % 10) as u8 + b'0');
571        buf.push((nanosecond / 100_000 % 10) as u8 + b'0');
572        buf.push((nanosecond / 10_000 % 10) as u8 + b'0');
573        buf.push((nanosecond / 1_000 % 10) as u8 + b'0');
574        buf.push((nanosecond / 100 % 10) as u8 + b'0');
575        buf.push((nanosecond / 10 % 10) as u8 + b'0');
576        buf.push((nanosecond % 10) as u8 + b'0');
577        buf.extend_from_slice(&self.sub_nano);
578        // Truncates trailing zeros.
579        while buf.len() > 14 && {
580                let b = *buf.last().unwrap(); b == b'0' || b == b'.' } {
581            buf.pop();
582        }
583        buf.push(b'Z');
584        return buf;
585    }
586
587    /// Returns ASN.1 canonical representation of the datetime as `String`.
588    pub fn to_string(&self) -> String {
589        String::from_utf8(self.to_bytes()).unwrap()
590    }
591}
592
593#[test]
594fn test_utctime_parse() {
595    let datetime = *UTCTime::parse(b"8201021200Z").unwrap().datetime();
596    assert_eq!(datetime.year(), 1982);
597    assert_eq!(datetime.month() as u8, 1);
598    assert_eq!(datetime.day(), 2);
599    assert_eq!(datetime.hour(), 12);
600    assert_eq!(datetime.minute(), 0);
601    assert_eq!(datetime.second(), 0);
602    assert_eq!(datetime.nanosecond(), 0);
603
604    let datetime = *UTCTime::parse(b"0101021200Z").unwrap().datetime();
605    assert_eq!(datetime.year(), 2001);
606    assert_eq!(datetime.month() as u8, 1);
607    assert_eq!(datetime.day(), 2);
608    assert_eq!(datetime.hour(), 12);
609    assert_eq!(datetime.minute(), 0);
610    assert_eq!(datetime.second(), 0);
611    assert_eq!(datetime.nanosecond(), 0);
612
613    let datetime = UTCTime::parse(b"8201021200Z").unwrap();
614    assert_eq!(&datetime.to_string(), "820102120000Z");
615
616    let datetime = UTCTime::parse(b"8201020700-0500").unwrap();
617    assert_eq!(&datetime.to_string(), "820102120000Z");
618
619    let datetime = UTCTime::parse(b"0101021200Z").unwrap();
620    assert_eq!(&datetime.to_string(), "010102120000Z");
621
622    let datetime = UTCTime::parse(b"010102120034Z").unwrap();
623    assert_eq!(&datetime.to_string(), "010102120034Z");
624
625    let datetime = UTCTime::parse(b"000229123456Z").unwrap();
626    assert_eq!(&datetime.to_string(), "000229123456Z");
627}
628
629#[test]
630fn test_generalized_time_parse() {
631    let datetime =
632        *GeneralizedTime::parse(b"19851106210627.3Z").unwrap().datetime();
633    assert_eq!(datetime.year(), 1985);
634    assert_eq!(datetime.month() as u8, 11);
635    assert_eq!(datetime.day(), 6);
636    assert_eq!(datetime.hour(), 21);
637    assert_eq!(datetime.minute(), 6);
638    assert_eq!(datetime.second(), 27);
639    assert_eq!(datetime.nanosecond(), 300_000_000);
640
641    let datetime = GeneralizedTime::parse(b"19851106210627.3-0500").unwrap();
642    assert_eq!(&datetime.to_string(), "19851107020627.3Z");
643
644    let datetime = GeneralizedTime::parse(b"198511062106Z").unwrap();
645    assert_eq!(&datetime.to_string(), "19851106210600Z");
646
647    let datetime = GeneralizedTime::parse(b"198511062106.456Z").unwrap();
648    assert_eq!(&datetime.to_string(), "19851106210627.36Z");
649
650    let datetime = GeneralizedTime::parse(b"1985110621Z").unwrap();
651    assert_eq!(&datetime.to_string(), "19851106210000Z");
652
653    let datetime = GeneralizedTime::parse(b"1985110621.14159Z").unwrap();
654    assert_eq!(&datetime.to_string(), "19851106210829.724Z");
655
656    let datetime =
657        GeneralizedTime::parse(b"19990101085960.1234+0900").unwrap();
658    assert_eq!(&datetime.to_string(), "19981231235960.1234Z");
659
660    let datetime =
661        GeneralizedTime::parse(
662            b"20080229033411.3625431984612391672391625532918636000680000-0500"
663        ).unwrap();
664    assert_eq!(&datetime.to_string(),
665        "20080229083411.362543198461239167239162553291863600068Z");
666}