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//! [](https://github.com/Kijewski/utcnow/actions/workflows/ci.yml)
24//! [](https://crates.io/crates/utcnow)
25//! 
26//! [](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};