1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
#![warn(clippy::all)]
#![warn(clippy::pedantic)]
#![warn(clippy::cargo)]
#![warn(clippy::needless_borrow)]
#![allow(clippy::let_underscore_drop)]
// https://github.com/rust-lang/rust-clippy/pull/5998#issuecomment-731855891
#![allow(clippy::map_err_ignore)]
#![allow(clippy::option_if_let_else)]
#![allow(unknown_lints)]
#![warn(missing_docs)]
#![warn(missing_debug_implementations)]
#![warn(missing_copy_implementations)]
#![warn(rust_2018_idioms)]
#![warn(trivial_casts, trivial_numeric_casts)]
#![warn(unused_qualifications)]
#![warn(variant_size_differences)]
#![forbid(unsafe_code)]
// Enable feature callouts in generated documentation:
// https://doc.rust-lang.org/beta/unstable-book/language-features/doc-cfg.html
//
// This approach is borrowed from tokio.
#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(docsrs, feature(doc_alias))]

//! Time is an abstraction of dates and times.
//!
//! This module implements the [`Time`] class from Ruby Core.
//!
//! In Artichoke, Time is represented as a 64-bit signed integer of seconds
//! since January 1, 1970 UTC (the Unix Epoch) and an unsigned 32-bit integer of
//! subsecond nanoseconds. This allows representing roughly 584 billion years.
//!
//! You can use this class in your application by accessing it directly. As a
//! Core class, it is globally available:
//!
//! ```ruby
//! Time.now
//! ```
//!
//! This implementation of `Time` supports the system clock via the
//! [`chrono`] crate.
//!
//! # Crate features
//!
//! This crate requires [`std`], the Rust Standard Library.
//!
//! [`Time`]: https://ruby-doc.org/core-2.6.3/Time.html
//! [`chrono`]: https://crates.io/crates/chrono

// Ensure code blocks in README.md compile
#[cfg(doctest)]
macro_rules! readme {
    ($x:expr) => {
        #[doc = $x]
        mod readme {}
    };
    () => {
        readme!(include_str!("../README.md"));
    };
}
#[cfg(doctest)]
readme!();

use core::fmt;
use core::time::Duration;
use std::error::Error;

mod time;

pub use time::chrono::{Offset, Time, ToA};

/// Number of nanoseconds in one second.
#[allow(clippy::cast_possible_truncation)] // 1e9 < u32::MAX
pub const NANOS_IN_SECOND: u32 = Duration::from_secs(1).as_nanos() as u32;

/// Number of microseconds in one nanosecond.
#[allow(clippy::cast_possible_truncation)] // 1000 < u32::MAX
pub const MICROS_IN_NANO: u32 = Duration::from_micros(1).as_nanos() as u32;

/// Error returned when constructing a [`Time`] from a [`ToA`].
///
/// This error is returned when a time component in the `ToA` exeeds the maximum
/// permissible value for a datetime. For example, invalid values include a
/// datetime 5000 days or 301 seconds.
///
/// # Examples
///
/// Invalid date component:
///
/// ```
/// # use core::convert::TryFrom;
/// # use spinoso_time::{Offset, Time, ToA, ComponentOutOfRangeError};
/// let to_a = ToA {
///     sec: 21,
///     min: 3,
///     hour: 23,
///     day: 5000,
///     month: 4,
///     year: 2020,
///     wday: 0,
///     yday: 96,
///     isdst: true,
///     zone: Offset::Local,
/// };
/// let time = Time::try_from(to_a);
/// assert_eq!(time, Err(ComponentOutOfRangeError::Date));
/// ```
///
/// Invalid time component:
///
/// ```
/// # use core::convert::TryFrom;
/// # use spinoso_time::{Offset, Time, ToA, ComponentOutOfRangeError};
/// let to_a = ToA {
///     sec: 301,
///     min: 3,
///     hour: 23,
///     day: 5,
///     month: 4,
///     year: 2020,
///     wday: 0,
///     yday: 96,
///     isdst: true,
///     zone: Offset::Local,
/// };
/// let time = Time::try_from(to_a);
/// assert_eq!(time, Err(ComponentOutOfRangeError::Time));
/// ```
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub enum ComponentOutOfRangeError {
    /// Date component (year, month, day) out of range.
    Date,
    /// Time component (hour, minute, second) out of range.
    Time,
}

impl fmt::Display for ComponentOutOfRangeError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Date => f.write_str("Date component (year, month, day) out of range"),
            Self::Time => f.write_str("Time component (hour, minute, second) out of range"),
        }
    }
}

impl Error for ComponentOutOfRangeError {}