utcnow/
lib.rs

1// SPDX-License-Identifier: Apache-2.0
2//
3// Copyright 2022 René Kijewski <crates.io@k6i.de>
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16//
17
18#![allow(unknown_lints)]
19#![allow(clippy::doc_markdown)]
20
21//! # utcnow — Get the current unixtime in a no-std context
22//!
23//! [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/Kijewski/utcnow/ci.yml?branch=main)](https://github.com/Kijewski/utcnow/actions/workflows/ci.yml)
24//! [![Crates.io](https://img.shields.io/crates/v/utcnow?logo=rust)](https://crates.io/crates/utcnow)
25//! ![Minimum supported Rust version](https://img.shields.io/badge/rustc-1.48-important?logo=rust "Minimum Supported Rust Version")
26//! [![License](https://img.shields.io/crates/l/utcnow?color=informational&logo=apache)](https://github.com/Kijewski/utcnow/blob/v0.0.0-pre1/LICENSE.md)
27//!
28//! This library solves one question, and one question only: *What's the time?*
29//!
30//! In [UTC](https://en.wikipedia.org/w/index.php?title=Coordinated_Universal_Time&oldid=1099753328 "Coordinated Universal Time"), and
31//! according to the clock of the PC, tablet, toaster … the library runs on,
32//! expressed as seconds + nanoseconds since [`1970-01-01`](https://en.wikipedia.org/w/index.php?title=Unix_time&oldid=1099912565 "Unix time").
33//!
34//! ```rust
35//! # use utcnow::utcnow;
36//! let now = utcnow().unwrap();
37//! let seconds = now.as_secs();
38//! let nanos = now.subsec_nanos();
39//! ```
40//!
41//! For many target platforms this call cannot fail.
42//! If this is true for the current target, then the constant [`INFALLIBLE`] will be `true`.
43//!
44//! If the target platform is not supported, then [`utcnow()`] will always return an error instead of failing to compile.
45//! Use the library with `default-features = false` and without the feature `"fallback"` to get a compile-time error instead.
46//!
47//! The feature `"std"` (enabled by default) is only needed if you need the [`Error`] type to implement [`std::error::Error`].
48//!
49//! ### Supported platforms
50//!
51//! If you have successfully tested one of the untested targets, then please [tell me](https://github.com/Kijewski/utcnow/issues).
52//! And if not, then even more so!
53//!
54//! If you know how to implement another target, then please open a [pull request](https://github.com/Kijewski/utcnow/pulls).
55//!
56//! **Supported and tested:**
57//!
58//! * Android
59//! * Emscripten
60//! * FreeBSD
61//! * Haiku
62//! * Illumos
63//! * Linux
64//! * Linux with Musl
65//! * MacOS
66//! * NetBSD
67//! * WASI
68//! * wasm32
69//! * Windows
70//!
71//! **(Probably) supported, but not actually tested:**
72//!
73//! * Dragonfly
74//! * Fuchsia
75//! * iOS
76//! * OpenBSD
77//! * Redox
78//! * Solaris
79//!
80//! Increasing the <abbr title="Minimum Supported Rust Version">msrv</abbr> for [tier-2](https://doc.rust-lang.org/nightly/rustc/platform-support.html) or
81//! lower platforms will not be indicated as a breaking change to the semver version.
82//!
83//! ### Feature flags
84//!
85//! `utcnow` has the following optional features:
86//!
87//! * `serde`, which implements [`serde::Deserialize`] and [`serde::Serialize`] for [`UtcTime`].
88//!
89//! * `arbitrary`, which implements the [`arbitrary::Arbitrary`] trait for [`UtcTime`].
90//!
91//! * `proptest`, which implements the [`proptest::arbitrary::Arbitrary`] trait for [`UtcTime`].
92//!
93//! * `quickcheck`, which implements the [`quickcheck::Arbitrary`] trait for [`UtcTime`].
94//!
95//! * `rkyv`, which implements the [`rkyv::Archive`], [`rkyv::Serialize`], and [`rkyv::Deserialize`] for [`UtcTime`].
96//!
97//! * `castaway`, which implements the [`castaway::LifetimeFree`] trait for [`UtcTime`].
98//!
99
100#![cfg_attr(not(any(test, feature = "std")), no_std)]
101#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
102#![allow(unused_attributes)]
103#![warn(absolute_paths_not_starting_with_crate)]
104#![warn(elided_lifetimes_in_paths)]
105#![warn(explicit_outlives_requirements)]
106#![warn(meta_variable_misuse)]
107#![warn(missing_copy_implementations)]
108#![warn(missing_debug_implementations)]
109#![warn(missing_docs)]
110#![warn(trivial_casts)]
111#![warn(unreachable_pub)]
112#![warn(unused_extern_crates)]
113#![warn(unused_lifetimes)]
114#![warn(unused_results)]
115
116#[cfg(docsrs)]
117#[cfg_attr(docsrs, doc(cfg(any())))]
118pub mod changelog;
119#[cfg(feature = "arbitrary")]
120mod feat_arbitrary;
121#[cfg(feature = "castaway")]
122mod feat_castaway;
123#[cfg(feature = "proptest")]
124mod feat_proptest;
125#[cfg(feature = "quickcheck")]
126mod feat_quickcheck;
127#[cfg(feature = "rkyv")]
128mod feat_rkyv;
129#[cfg(feature = "serde")]
130mod feat_serde;
131#[cfg_attr(
132    any(
133        target_os = "dragonfly",
134        target_os = "freebsd",
135        target_os = "ios",
136        target_os = "linux",
137        target_os = "macos",
138        target_os = "openbsd",
139        target_os = "redox",
140    ),
141    path = "impl_rustix.rs"
142)]
143#[cfg_attr(
144    any(
145        target_os = "android",
146        target_os = "emscripten",
147        target_os = "fuchsia",
148        target_os = "haiku",
149        target_os = "illumos",
150        target_os = "netbsd",
151        target_os = "solaris",
152    ),
153    path = "impl_libc.rs"
154)]
155#[cfg_attr(target_os = "wasi", path = "impl_wasi.rs")]
156#[cfg_attr(target_os = "windows", path = "impl_winapi.rs")]
157#[cfg_attr(
158    all(target_arch = "wasm32", not(target_os = "wasi")),
159    path = "impl_web.rs"
160)]
161mod platform;
162#[cfg(test)]
163mod test;
164mod u30;
165
166use core::convert::{TryFrom, TryInto};
167use core::fmt;
168use core::time::Duration;
169#[cfg(feature = "std")]
170use std::time::SystemTime;
171
172use crate::platform::OsError;
173use crate::u30::U30;
174
175/// `true` if getting the time is implemented for the target platform
176pub const IMPLEMENTED: bool = platform::IMPLEMENTED;
177
178/// `true` if [`utcnow()`] cannot fail
179pub const INFALLIBLE: bool = platform::INFALLIBLE;
180
181/// A Unix time, i.e. seconds since 1970-01-01 in UTC
182///
183/// Using [`i64`] values as seconds since 1970-01-01, this library will work for the next 292 billion years.
184#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
185pub struct UtcTime {
186    /// Seconds since epoch
187    secs: i64,
188    /// Nanoseconds since epoch
189    nanos: U30,
190}
191
192impl UtcTime {
193    /// Start of the [Unix time](https://en.wikipedia.org/w/index.php?title=Unix_time&oldid=1099912565) epoch, 1970-01-01.
194    pub const EPOCH: UtcTime = UtcTime {
195        secs: 0,
196        nanos: U30::ZERO,
197    };
198
199    /// Get the current time
200    ///
201    /// This method does the same as calling [`utcnow()`].
202    ///
203    /// # Errors
204    ///
205    /// See [`utcnow()`] for further information.
206    ///
207    /// # Example
208    ///
209    /// ```rust
210    /// # use utcnow::UtcTime;
211    /// let now = UtcTime::now().unwrap();
212    /// let seconds = now.as_secs();
213    /// let nanos = now.subsec_nanos();
214    /// ```
215    #[inline]
216    pub fn now() -> Result<Self> {
217        utcnow()
218    }
219
220    /// Build a new [`UtcTime`] without normalization
221    ///
222    /// If the value for `nanos` exceeds `1_000_000_000`, your program is malformed.
223    /// Expect undefined behavior!
224    ///
225    /// # Safety
226    ///
227    /// Expect random and impossible to debug runtime errors if the input is not in range.
228    #[inline]
229    #[must_use]
230    #[const_fn::const_fn("1.56")]
231    pub unsafe fn new_unchecked(secs: i64, nanos: u32) -> Self {
232        let nanos = U30::new_unchecked(nanos);
233        Self { secs, nanos }
234    }
235
236    /// Build a new [`UtcTime`]
237    ///
238    /// `nanos` will be normalized to a values less than `1_000_000_000`, the number of nanoseconds in a second.
239    /// If the resulting number of seconds will exceed [`i64::MAX`], [`None`] is returned.
240    ///
241    /// # Example
242    ///
243    /// ```
244    /// # use utcnow::UtcTime;
245    /// // August 3, 2022, about 19 o'clock in the evening in CEST.
246    /// let timestamp = UtcTime::new(1_659_545_693, 895_531_827).unwrap();
247    /// ```
248    #[must_use]
249    #[const_fn::const_fn("1.56")]
250    pub fn new(secs: i64, nanos: u32) -> Option<Self> {
251        const NANOS_PER_SEC: u32 = 1_000_000_000;
252
253        if nanos < NANOS_PER_SEC {
254            return Some(unsafe { Self::new_unchecked(secs, nanos) });
255        }
256
257        let extra_seconds = nanos.div_euclid(NANOS_PER_SEC);
258        let nanos = nanos.rem_euclid(NANOS_PER_SEC);
259        match secs.checked_add(extra_seconds as i64) {
260            Some(secs) => Some(unsafe { Self::new_unchecked(secs, nanos) }),
261            None => None,
262        }
263    }
264
265    /// Convert a [SystemTime]
266    ///
267    /// # Example
268    ///
269    /// ```
270    /// # #[cfg(feature = "std")] let _: () = {
271    /// # use std::time::SystemTime;
272    /// # use utcnow::UtcTime;
273    /// let system_time = SystemTime::now();
274    /// let now = UtcTime::from_system_time(system_time).unwrap();
275    /// # };
276    /// ```
277    #[must_use]
278    #[cfg(feature = "std")]
279    pub fn from_system_time(value: SystemTime) -> Option<Self> {
280        Self::from_duration(value.duration_since(SystemTime::UNIX_EPOCH).ok()?)
281    }
282
283    /// Convert a [Duration]
284    ///
285    /// The duration is interpreted as seconds since epoch (1970-01-01 in UTC).
286    ///
287    /// # Example
288    ///
289    /// ```
290    /// # use core::time::Duration;
291    /// # use utcnow::UtcTime;
292    /// let duration = Duration::from_secs(42);
293    /// let timestamp = UtcTime::from_duration(duration).unwrap();
294    /// assert_eq!(timestamp.as_nanos(), 42_000_000_000);
295    /// ```
296    #[must_use]
297    #[allow(clippy::cast_possible_wrap)]
298    #[const_fn::const_fn("1.56")]
299    pub fn from_duration(value: Duration) -> Option<Self> {
300        const I64_MAX: u64 = i64::MAX as u64;
301        let secs = match value.as_secs() {
302            secs @ 0..=I64_MAX => secs as i64,
303            _ => return None,
304        };
305        let nanos = value.subsec_nanos();
306        Some(unsafe { Self::new_unchecked(secs, nanos) })
307    }
308
309    /// Total number of whole seconds since epoch (1970-01-01 in UTC)
310    ///
311    /// # Example
312    ///
313    /// ```rust
314    /// # use core::time::Duration;
315    /// # use utcnow::UtcTime;
316    /// let now = UtcTime::now().unwrap();
317    /// let total_secs = now.as_secs();
318    /// assert!(total_secs > 1_658_711_810);
319    /// assert!(total_secs < 1_974_324_043); // update before 2032-07-25
320    /// ```
321    #[must_use]
322    #[inline]
323    pub const fn as_secs(self) -> i64 {
324        self.secs
325    }
326
327    /// Total number of whole milliseconds since epoch (1970-01-01 in UTC)
328    ///
329    /// # Example
330    ///
331    /// ```rust
332    /// # use core::time::Duration;
333    /// # use utcnow::UtcTime;
334    /// let now = UtcTime::now().unwrap();
335    /// let total_millis = now.as_millis();
336    /// assert!(total_millis > 1_658_711_810_802);
337    /// assert!(total_millis < 1_974_324_043_000); // update before 2032-07-25
338    /// ```
339    #[must_use]
340    #[const_fn::const_fn("1.56")]
341    pub fn as_millis(self) -> i128 {
342        (self.secs as i128 * 1_000) + (self.nanos.get() as i128 / 1_000_000)
343    }
344
345    /// Total number of whole microseconds since epoch (1970-01-01 in UTC)
346    ///
347    /// # Example
348    ///
349    /// ```rust
350    /// # use core::time::Duration;
351    /// # use utcnow::UtcTime;
352    /// let now = UtcTime::now().unwrap();
353    /// let total_micros = now.as_micros();
354    /// assert!(total_micros > 1_658_711_810_802_520);
355    /// assert!(total_micros < 1_974_324_043_000_000); // update before 2032-07-25
356    /// ```
357    #[must_use]
358    #[const_fn::const_fn("1.56")]
359    pub fn as_micros(self) -> i128 {
360        (self.secs as i128 * 1_000_000) + (self.nanos.get() as i128 / 1_000)
361    }
362
363    /// Total number of whole nanoseconds since epoch (1970-01-01 in UTC)
364    ///
365    /// # Example
366    ///
367    /// ```rust
368    /// # use core::time::Duration;
369    /// # use utcnow::UtcTime;
370    /// let now = UtcTime::now().unwrap();
371    /// let total_nanos = now.as_nanos();
372    /// assert!(total_nanos > 1_658_711_810_802_520_027);
373    /// assert!(total_nanos < 1_974_324_043_000_000_000); // update before 2032-07-25
374    /// ```
375    #[must_use]
376    #[const_fn::const_fn("1.56")]
377    pub fn as_nanos(self) -> i128 {
378        (self.secs as i128 * 1_000_000_000) + (self.nanos.get() as i128)
379    }
380
381    /// Fractional number of milliseconds since epoch (1970-01-01 in UTC)
382    ///
383    /// # Example
384    ///
385    /// ```rust
386    /// # use core::time::Duration;
387    /// # use utcnow::UtcTime;
388    /// let now = UtcTime::now().unwrap();
389    /// let millis = now.subsec_millis();
390    /// assert!(millis < 1_000);
391    /// ```
392    #[must_use]
393    #[const_fn::const_fn("1.56")]
394    pub fn subsec_millis(self) -> u32 {
395        self.nanos.get() / 1_000_000
396    }
397
398    /// Fractional number of microseconds since epoch (1970-01-01 in UTC)
399    ///
400    /// # Example
401    ///
402    /// ```rust
403    /// # use core::time::Duration;
404    /// # use utcnow::UtcTime;
405    /// let now = UtcTime::now().unwrap();
406    /// let micros = now.subsec_micros();
407    /// assert!(micros < 1_000_000);
408    /// ```
409    #[must_use]
410    #[const_fn::const_fn("1.56")]
411    pub fn subsec_micros(self) -> u32 {
412        self.nanos.get() / 1_000
413    }
414
415    /// Fractional number of nanoseconds since epoch (1970-01-01 in UTC)
416    ///
417    /// # Example
418    ///
419    /// ```rust
420    /// # use core::time::Duration;
421    /// # use utcnow::UtcTime;
422    /// let now = UtcTime::now().unwrap();
423    /// let nanos = now.subsec_nanos();
424    /// assert!(nanos < 1_000_000_000);
425    /// ```
426    #[must_use]
427    #[inline]
428    #[const_fn::const_fn("1.56")]
429    pub fn subsec_nanos(self) -> u32 {
430        self.nanos.get()
431    }
432
433    /// Convert the timestamp to a [Duration] since epoch (1970-01-01 in UTC)
434    ///
435    /// # Errors
436    ///
437    /// Fails if the timestamp lies before 1970-01-01 in UTC.
438    ///
439    /// # Example
440    ///
441    /// ```rust
442    /// # use core::time::Duration;
443    /// # use utcnow::UtcTime;
444    /// let now = UtcTime::now().unwrap();
445    /// let duration = now.into_duration().unwrap();
446    /// ```
447    #[allow(clippy::cast_sign_loss)]
448    #[const_fn::const_fn("1.58")]
449    pub fn into_duration(self) -> Result<Duration, ConversionError> {
450        let secs = match self.secs {
451            secs @ 0..=i64::MAX => secs as u64,
452            _ => return Err(ConversionError),
453        };
454        Ok(Duration::new(secs, self.nanos.get()))
455    }
456
457    /// Convert the timestamp to a [SystemTime]
458    ///
459    /// # Errors
460    ///
461    /// The conversion won't succeed if and only if the stored date is earlier than 1970-01-01.
462    ///
463    /// # Example
464    ///
465    /// ```rust
466    /// # #[cfg(feature = "std")] let _: () = {
467    /// # use std::time::SystemTime;
468    /// # use utcnow::UtcTime;
469    /// let now = UtcTime::now().unwrap();
470    /// let system_time = now.into_system_time().unwrap();
471    /// # };
472    /// ```
473    #[cfg(feature = "std")]
474    pub fn into_system_time(self) -> Result<SystemTime, ConversionError> {
475        SystemTime::UNIX_EPOCH
476            .checked_add(self.into_duration()?)
477            .ok_or(ConversionError)
478    }
479}
480
481impl fmt::Display for UtcTime {
482    #[inline]
483    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
484        write!(f, "{}.{:09}", self.secs, self.nanos)
485    }
486}
487
488impl TryFrom<&str> for UtcTime {
489    type Error = ConversionError;
490
491    fn try_from(value: &str) -> Result<Self, Self::Error> {
492        if matches!(value, "" | ".") || !value.is_ascii() {
493            return Err(ConversionError);
494        }
495
496        // Only present since 1.52:
497        // let (secs, nanos) = value.split_once('.').unwrap_or((value, ""));
498
499        let (secs, nanos) = match value
500            .as_bytes()
501            .iter()
502            .enumerate()
503            .find(|(_, &c)| c == b'.')
504        {
505            Some((idx, _)) => unsafe {
506                // SAFETY: we checked that `value` is ASCII, and we know that the index is valid
507                (value.get_unchecked(..idx), value.get_unchecked(idx + 1..))
508            },
509            None => (value, ""),
510        };
511
512        let secs = match secs {
513            "" => 0,
514            secs => secs.parse().map_err(|_| ConversionError)?,
515        };
516        let nanos = match nanos {
517            "" => 0,
518            nanos => {
519                let (nanos, factor) = if nanos.len() <= 9 {
520                    let factor = match nanos.len() {
521                        8 => 10,
522                        7 => 100,
523                        6 => 1000,
524                        5 => 10000,
525                        4 => 100_000,
526                        3 => 1_000_000,
527                        2 => 10_000_000,
528                        1 => 100_000_000,
529                        _ => 1,
530                    };
531                    (nanos, factor)
532                } else {
533                    // SAFETY: We checked that `value` is ASCII, so every substring is ASCII,
534                    //         and we just checked that `nanos` is longer than 9 bytes.
535                    let nanos = unsafe { nanos.get_unchecked(..9) };
536                    let suffix = unsafe { nanos.get_unchecked(9..) };
537                    if suffix.as_bytes().iter().any(|c| !c.is_ascii_digit()) {
538                        return Err(ConversionError);
539                    }
540                    (nanos, 1)
541                };
542                nanos.parse::<u32>().map_err(|_| ConversionError)? * factor
543            },
544        };
545        Ok(unsafe { Self::new_unchecked(secs, nanos) })
546    }
547}
548
549impl core::str::FromStr for UtcTime {
550    type Err = ConversionError;
551
552    #[inline]
553    fn from_str(s: &str) -> Result<Self, Self::Err> {
554        s.try_into()
555    }
556}
557
558/// Get the current unix time, seconds since 1970-01-01 in UTC
559///
560/// Please see the [module level documentation](crate) for more information.
561///
562/// # Errors
563///
564/// For many target platforms this call cannot fail.
565/// If this is true for the current target, then the constant [`INFALLIBLE`] will be `true`.
566/// Rust will automatically optimize the [`unwrap()`](Result::unwrap) call into a no-op in this case.
567/// Independent of the target platform the [`Error`] type will always be [`Send`] + [`Sync`] + [`Copy`].
568///
569/// # Example
570///
571/// ```rust
572/// let now = utcnow::utcnow().unwrap();
573/// let seconds = now.as_secs();
574/// let nanos = now.subsec_nanos();
575/// ```
576#[inline]
577pub fn utcnow() -> Result<UtcTime> {
578    platform::utcnow()
579}
580
581impl TryFrom<UtcTime> for Duration {
582    type Error = ConversionError;
583
584    #[inline]
585    fn try_from(value: UtcTime) -> Result<Self, ConversionError> {
586        value.into_duration()
587    }
588}
589
590#[cfg(feature = "std")]
591impl TryFrom<UtcTime> for SystemTime {
592    type Error = ConversionError;
593
594    #[inline]
595    fn try_from(value: UtcTime) -> Result<Self, ConversionError> {
596        value.into_system_time()
597    }
598}
599
600impl TryFrom<Duration> for UtcTime {
601    type Error = ConversionError;
602
603    #[inline]
604    fn try_from(value: Duration) -> Result<Self, ConversionError> {
605        Self::from_duration(value).ok_or(ConversionError)
606    }
607}
608
609#[cfg(feature = "std")]
610impl TryFrom<SystemTime> for UtcTime {
611    type Error = ConversionError;
612
613    #[inline]
614    fn try_from(value: SystemTime) -> Result<Self, ConversionError> {
615        Self::from_system_time(value).ok_or(ConversionError)
616    }
617}
618
619/// A result type that default to [`Error`] as error type
620///
621/// For many target platforms [`utcnow()`] cannot fail.
622/// If this is true for the current target, then the constant [`INFALLIBLE`] will be `true`.
623/// Rust will automatically optimize the [`unwrap()`](Result::unwrap) call into a no-op in this case.
624pub type Result<T, E = Error> = core::result::Result<T, E>;
625
626/// Could not query system time
627///
628/// For many target platforms [`utcnow()`] cannot fail.
629/// If this is true for the current target, then the constant [`INFALLIBLE`] will be `true`.
630/// Independent of the target platform the [`Error`] type will always be [`Send`] + [`Sync`] + [`Copy`].
631#[derive(Debug, Clone, Copy)]
632pub struct Error(OsError);
633
634impl fmt::Display for Error {
635    #[inline]
636    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
637        self.0.fmt(f)
638    }
639}
640
641impl From<OsError> for Error {
642    #[inline]
643    fn from(err: OsError) -> Self {
644        Self(err)
645    }
646}
647
648#[rustversion::before(1.81.0)]
649#[cfg(feature = "std")]
650const _: () = {
651    impl std::error::Error for Error {}
652};
653
654#[rustversion::since(1.81.0)]
655const _: () = {
656    #[cfg_attr(docsrs, doc(cfg(any(feature = "std", rustc = "1.81 or higher"))))]
657    impl core::error::Error for Error {}
658};
659
660/// Could not convert from or to a [`UtcTime`]
661///
662/// You cannot convert a negative [`UtcTime`]  (i.e. before 1970-01-01) into a [`SystemTime`] or [`Duration`].
663/// You cannot convert a [`SystemTime`] or [`Duration`] later than year 292 billion into a [`UtcTime`].
664#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
665pub struct ConversionError;
666
667impl fmt::Display for ConversionError {
668    #[inline]
669    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
670        f.write_str("cannot convert a negative UtcTime")
671    }
672}
673
674#[cfg(feature = "std")]
675impl std::error::Error for ConversionError {}
676
677#[cfg(test)]
678const _: () = {
679    trait AutoTraits {
680        const AUTO_TRAITS: bool;
681    }
682
683    impl<T> AutoTraits for T
684    where
685        T: 'static + Send + Sized + Sync + Unpin,
686    {
687        const AUTO_TRAITS: bool = true;
688    }
689
690    const _: bool = ConversionError::AUTO_TRAITS;
691    const _: bool = Error::AUTO_TRAITS;
692    const _: bool = Option::<U30>::AUTO_TRAITS;
693    const _: bool = OsError::AUTO_TRAITS;
694    const _: bool = Result::<U30>::AUTO_TRAITS;
695    const _: bool = U30::AUTO_TRAITS;
696    const _: bool = UtcTime::AUTO_TRAITS;
697};