/*!
# UTC2K
*/
#![allow(clippy::shadow_unrelated)]
pub(super) mod parse;
use crate::{
Abacus,
DAY_IN_SECONDS,
HOUR_IN_SECONDS,
macros,
MINUTE_IN_SECONDS,
Month,
unixtime,
Utc2kError,
Weekday,
};
use std::{
cmp::Ordering,
ffi::OsStr,
fmt,
ops::{
Add,
AddAssign,
Deref,
Sub,
SubAssign,
},
};
/// # Double-Digit ASCII.
const DD: &[u8; 200] = b"\
0001020304050607080910111213141516171819\
2021222324252627282930313233343536373839\
4041424344454647484950515253545556575859\
6061626364656667686970717273747576777879\
8081828384858687888990919293949596979899";
/// # Helper: `TryFrom` Unixtime For Non-u32 Formats.
macro_rules! try_from_unixtime {
($($ty:ty),+) => ($(
impl TryFrom<$ty> for Utc2k {
type Error = Utc2kError;
fn try_from(src: $ty) -> Result<Self, Self::Error> {
u32::try_from(src)
.map(Self::from)
.map_err(|_| Utc2kError::Invalid)
}
}
impl TryFrom<$ty> for FmtUtc2k {
type Error = Utc2kError;
#[inline]
fn try_from(src: $ty) -> Result<Self, Self::Error> {
Utc2k::try_from(src).map(Self::from)
}
}
)+);
}
#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
/// # Formatted UTC2K.
///
/// This is the formatted companion to [`Utc2k`]. You can use it to obtain a
/// string version of the date, print it, etc.
///
/// While this acts essentially as a glorified `String`, it is sized exactly
/// and therefore requires less memory to represent. It also implements `Copy`.
///
/// It follows the simple Unix date format of `YYYY-MM-DD HH:MM:SS`.
///
/// Speaking of, you can obtain an `&str` using `Deref`, `AsRef<str>`,
/// `Borrow<str>`, or [`FmtUtc2k::as_str`].
///
/// If you only want the date or time half, call [`FmtUtc2k::date`] or
/// [`FmtUtc2k::time`] respectively.
///
/// ## Examples
///
/// Generally it makes more sense to initialize a [`Utc2k`] first, but you can
/// skip straight to a `FmtUtc2k` instead:
///
/// ```
/// use utc2k::{FmtUtc2k, Utc2k};
///
/// // Start with the current date/time.
/// let date = FmtUtc2k::now();
///
/// // Source from a specific timestamp.
/// let date = FmtUtc2k::from(946_684_800_u32);
/// assert_eq!(date.as_str(), "2000-01-01 00:00:00");
///
/// // Source from a `Utc2k`.
/// let utc_date = Utc2k::from(946_684_800_u32);
/// assert_eq!(FmtUtc2k::from(utc_date), utc_date.formatted());
/// ```
pub struct FmtUtc2k([u8; 19]);
macros::as_ref_borrow_cast!(
FmtUtc2k:
as_bytes [u8],
as_str str,
);
impl Default for FmtUtc2k {
#[inline]
fn default() -> Self { Self::min() }
}
impl Deref for FmtUtc2k {
type Target = str;
#[inline]
fn deref(&self) -> &Self::Target { self.as_str() }
}
macros::display_str!(as_str FmtUtc2k);
impl From<u32> for FmtUtc2k {
#[inline]
fn from(src: u32) -> Self { Self::from(Utc2k::from(src)) }
}
impl From<&Utc2k> for FmtUtc2k {
#[inline]
fn from(src: &Utc2k) -> Self { Self::from(*src) }
}
impl From<Utc2k> for FmtUtc2k {
fn from(src: Utc2k) -> Self {
let mut out = Self::default();
out.set_datetime(src);
out
}
}
impl Ord for FmtUtc2k {
#[inline]
fn cmp(&self, other: &Self) -> Ordering { self.0.cmp(&other.0) }
}
macros::partial_eq_cast!(deref FmtUtc2k: as_str &str, as_str &String);
macros::partial_eq_cast!(FmtUtc2k: as_str str, as_str String);
impl PartialOrd for FmtUtc2k {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.cmp(other)) }
}
impl TryFrom<&OsStr> for FmtUtc2k {
type Error = Utc2kError;
#[inline]
/// # From `OsStr`.
///
/// ```
/// use std::ffi::OsStr;
/// use utc2k::FmtUtc2k;
///
/// assert_eq!(
/// FmtUtc2k::try_from(OsStr::new("2013-12-15 21:30:02")).unwrap().as_str(),
/// "2013-12-15 21:30:02"
/// );
/// assert_eq!(
/// FmtUtc2k::try_from(OsStr::new("2013-12-15")).unwrap().as_str(),
/// "2013-12-15 00:00:00"
/// );
/// ```
fn try_from(src: &OsStr) -> Result<Self, Self::Error> {
Utc2k::try_from(src).map(Self::from)
}
}
impl TryFrom<&[u8]> for FmtUtc2k {
type Error = Utc2kError;
#[inline]
fn try_from(src: &[u8]) -> Result<Self, Self::Error> {
Utc2k::try_from(src).map(Self::from)
}
}
impl TryFrom<&str> for FmtUtc2k {
type Error = Utc2kError;
#[inline]
fn try_from(src: &str) -> Result<Self, Self::Error> {
Utc2k::try_from(src).map(Self::from)
}
}
/// ## Min/Max.
impl FmtUtc2k {
/// # Minimum Date/Time.
pub(crate) const MIN: [u8; 19] = *b"2000-01-01 00:00:00";
/// # Maximum Date/Time.
pub(crate) const MAX: [u8; 19] = *b"2099-12-31 23:59:59";
#[inline]
#[must_use]
/// # Minimum Value.
///
/// This is equivalent to `2000-01-01 00:00:00`.
///
/// ## Examples
///
/// ```
/// use utc2k::FmtUtc2k;
///
/// let date = FmtUtc2k::min();
/// assert_eq!(date.as_str(), "2000-01-01 00:00:00");
/// ```
pub const fn min() -> Self { Self(Self::MIN) }
#[inline]
#[must_use]
/// # Maximum Value.
///
/// This is equivalent to `2099-12-31 23:59:59`.
///
/// ## Examples
///
/// ```
/// use utc2k::FmtUtc2k;
///
/// let date = FmtUtc2k::max();
/// assert_eq!(date.as_str(), "2099-12-31 23:59:59");
/// ```
pub const fn max() -> Self { Self(Self::MAX) }
}
/// ## Instantiation/Reuse.
impl FmtUtc2k {
#[inline]
#[must_use]
/// # Now.
///
/// This returns an instance using the current unixtime as the seed.
pub fn now() -> Self { Self::from(Utc2k::now()) }
#[cfg(feature = "local")]
#[cfg_attr(feature = "docsrs", doc(cfg(feature = "local")))]
#[must_use]
/// # Now (Local).
///
/// This returns an instance using the current, local time as the seed. If
/// no local offset can be determined, this is equivalent to [`FmtUtc2k::now`].
///
/// Refer to [`LocalOffset`](crate::LocalOffset) for important caveats and
/// limitations.
pub fn now_local() -> Self {
Self::from(crate::LocalOffset::now())
}
#[allow(clippy::cast_possible_truncation)] // It fits.
/// # Set Date/Time.
///
/// This can be used to recycle an existing buffer.
///
/// As with all other part-based operations, overflows and underflows will
/// be adjusted automatically, with minimum and maximum dates capped to
/// [`FmtUtc2k::min`] and [`FmtUtc2k::max`] respectively.
///
/// ## Examples
///
/// ```
/// use utc2k::{FmtUtc2k, Utc2k};
///
/// let mut fmt = FmtUtc2k::default();
/// assert_eq!(fmt.as_str(), "2000-01-01 00:00:00");
///
/// fmt.set_datetime(Utc2k::from(Utc2k::MAX_UNIXTIME));
/// assert_eq!(fmt.as_str(), "2099-12-31 23:59:59");
/// ```
pub fn set_datetime(&mut self, src: Utc2k) {
let (y, m, d, hh, mm, ss) = src.parts();
self.set_parts_unchecked((y - 2000) as u8, m, d, hh, mm, ss);
}
/// # Set Parts.
///
/// This can be used to recycle an existing buffer.
///
/// As with all other part-based operations, overflows and underflows will
/// be adjusted automatically, with minimum and maximum dates capped to
/// [`FmtUtc2k::min`] and [`FmtUtc2k::max`] respectively.
///
/// ## Examples
///
/// ```
/// use utc2k::{FmtUtc2k, Utc2k};
///
/// let mut fmt = FmtUtc2k::default();
/// assert_eq!(fmt.as_str(), "2000-01-01 00:00:00");
///
/// fmt.set_parts(2010, 10, 31, 12, 33, 59);
/// assert_eq!(fmt.as_str(), "2010-10-31 12:33:59");
///
/// // And if you do something weird with the dates...
/// fmt.set_parts(2010, 10, 32, 12, 33, 59);
/// assert_eq!(fmt.as_str(), "2010-11-01 12:33:59");
/// ```
pub fn set_parts(&mut self, y: u16, m: u8, d: u8, hh: u8, mm: u8, ss: u8) {
let (y, m, d, hh, mm, ss) = Abacus::new(y, m, d, hh, mm, ss).parts();
self.set_parts_unchecked(y, m, d, hh, mm, ss);
}
#[allow(unsafe_code)]
/// # Set Parts (Unchecked).
///
/// Carry-overs, saturating, and 4-to-2-digit year-chopping have already
/// been applied by the time this method is called.
///
/// From here, it's just straight ASCII-writing.
fn set_parts_unchecked(&mut self, y: u8, m: u8, d: u8, hh: u8, mm: u8, ss: u8) {
use std::ptr::copy_nonoverlapping;
let src = DD.as_ptr();
let dst = self.0.as_mut_ptr();
// Safety: Abacus will have already normalized all ranges, so the
// indices will be present in DD.
unsafe {
copy_nonoverlapping(src.add((y << 1) as usize), dst.add(2), 2);
copy_nonoverlapping(src.add((m << 1) as usize), dst.add(5), 2);
copy_nonoverlapping(src.add((d << 1) as usize), dst.add(8), 2);
copy_nonoverlapping(src.add((hh << 1) as usize), dst.add(11), 2);
copy_nonoverlapping(src.add((mm << 1) as usize), dst.add(14), 2);
copy_nonoverlapping(src.add((ss << 1) as usize), dst.add(17), 2);
}
}
#[inline]
/// # Set Unixtime.
///
/// This can be used to recycle an existing buffer.
///
/// As with all other part-based operations, overflows and underflows will
/// be adjusted automatically, with minimum and maximum dates capped to
/// [`Utc2k::MIN_UNIXTIME`] and [`Utc2k::MAX_UNIXTIME`] respectively.
///
/// ## Examples
///
/// ```
/// use utc2k::{FmtUtc2k, Utc2k};
///
/// let mut fmt = FmtUtc2k::from(Utc2k::MIN_UNIXTIME);
/// assert_eq!(fmt.as_str(), "2000-01-01 00:00:00");
///
/// fmt.set_unixtime(Utc2k::MAX_UNIXTIME);
/// assert_eq!(fmt.as_str(), "2099-12-31 23:59:59");
/// ```
pub fn set_unixtime(&mut self, src: u32) { self.set_datetime(Utc2k::from(src)); }
}
/// ## Getters.
impl FmtUtc2k {
#[inline]
#[must_use]
/// # As Bytes.
///
/// Return a byte string slice in `YYYY-MM-DD HH:MM:SS` format.
///
/// A byte slice can also be obtained using [`FmtUtc2k::as_ref`].
///
/// ## Examples
///
/// ```
/// use utc2k::FmtUtc2k;
///
/// let fmt = FmtUtc2k::max();
/// assert_eq!(fmt.as_bytes(), b"2099-12-31 23:59:59");
/// ```
pub const fn as_bytes(&self) -> &[u8] { &self.0 }
#[allow(unsafe_code)]
#[inline]
#[must_use]
/// # As Str.
///
/// Return a string slice in `YYYY-MM-DD HH:MM:SS` format.
///
/// A string slice can also be obtained using [`FmtUtc2k::as_ref`] or
/// through dereferencing.
///
/// ## Examples
///
/// ```
/// use utc2k::FmtUtc2k;
///
/// let fmt = FmtUtc2k::max();
/// assert_eq!(fmt.as_str(), "2099-12-31 23:59:59");
/// ```
pub const fn as_str(&self) -> &str {
// Safety: datetimes are valid ASCII.
unsafe { std::str::from_utf8_unchecked(&self.0) }
}
#[allow(unsafe_code)]
#[inline]
#[must_use]
/// # Just the Date Bits.
///
/// This returns the date as a string slice in `YYYY-MM-DD` format.
///
/// ## Examples
///
/// ```
/// use utc2k::{FmtUtc2k, Utc2k};
///
/// let fmt = FmtUtc2k::from(Utc2k::MAX_UNIXTIME);
/// assert_eq!(fmt.as_str(), "2099-12-31 23:59:59");
/// assert_eq!(fmt.date(), "2099-12-31");
/// ```
pub fn date(&self) -> &str {
// Safety: datetimes are valid ASCII.
unsafe { std::str::from_utf8_unchecked(&self.0[..10]) }
}
#[allow(unsafe_code)]
#[inline]
#[must_use]
/// # Just the Year Bit.
///
/// This returns the year as a string slice.
///
/// ## Examples
///
/// ```
/// use utc2k::{FmtUtc2k, Utc2k};
///
/// let fmt = FmtUtc2k::from(Utc2k::MAX_UNIXTIME);
/// assert_eq!(fmt.as_str(), "2099-12-31 23:59:59");
/// assert_eq!(fmt.year(), "2099");
/// ```
pub fn year(&self) -> &str {
// Safety: datetimes are valid ASCII.
unsafe { std::str::from_utf8_unchecked(&self.0[..4]) }
}
#[allow(unsafe_code)]
#[inline]
#[must_use]
/// # Just the Time Bits.
///
/// This returns the time as a string slice in `HH:MM:SS` format.
///
/// ## Examples
///
/// ```
/// use utc2k::{FmtUtc2k, Utc2k};
///
/// let fmt = FmtUtc2k::from(Utc2k::MAX_UNIXTIME);
/// assert_eq!(fmt.as_str(), "2099-12-31 23:59:59");
/// assert_eq!(fmt.time(), "23:59:59");
/// ```
pub fn time(&self) -> &str {
// Safety: datetimes are valid ASCII.
unsafe { std::str::from_utf8_unchecked(&self.0[11..]) }
}
}
/// ## Formatting.
impl FmtUtc2k {
#[must_use]
/// # To RFC3339.
///
/// Return a string formatted according to [RFC3339](https://datatracker.ietf.org/doc/html/rfc3339).
///
/// Note: this method is allocating.
///
/// ## Examples
///
/// ```
/// use utc2k::{FmtUtc2k, Utc2k};
///
/// let mut fmt = FmtUtc2k::from(Utc2k::MIN_UNIXTIME);
/// assert_eq!(fmt.to_rfc3339(), "2000-01-01T00:00:00Z");
///
/// fmt.set_unixtime(Utc2k::MAX_UNIXTIME);
/// assert_eq!(fmt.to_rfc3339(), "2099-12-31T23:59:59Z");
/// ```
pub fn to_rfc3339(&self) -> String {
let mut out = String::with_capacity(20);
out.push_str(self.date());
out.push('T');
out.push_str(self.time());
out.push('Z');
out
}
#[inline]
/// # From RFC2822.
///
/// This method can be used to construct a `FmtUtc2k` from an RFC2822-formatted
/// string. Variations with and without a leading weekday, and with and
/// without a trailing offset, are supported. If an offset is included, the
/// datetime will be adjusted accordingly to make it properly UTC.
///
/// Note: missing offsets are meant to imply "localized" time, but as this
/// library has no timezone handling, strings without any "+HHMM" at the
/// end will be parsed as if they were already in UTC.
///
/// ## Examples
///
/// ```
/// use utc2k::{FmtUtc2k, Utc2k};
///
/// assert_eq!(
/// FmtUtc2k::from_rfc2822("Tue, 1 Jul 2003 10:52:37 +0000"),
/// FmtUtc2k::try_from("2003-07-01 10:52:37").ok(),
/// );
///
/// assert_eq!(
/// FmtUtc2k::from_rfc2822("Tue, 01 Jul 2003 10:52:37 +0000"),
/// FmtUtc2k::try_from("2003-07-01 10:52:37").ok(),
/// );
///
/// assert_eq!(
/// FmtUtc2k::from_rfc2822("1 Jul 2003 10:52:37"),
/// FmtUtc2k::try_from("2003-07-01 10:52:37").ok(),
/// );
///
/// assert_eq!(
/// FmtUtc2k::from_rfc2822("01 Jul 2003 10:52:37"),
/// FmtUtc2k::try_from("2003-07-01 10:52:37").ok(),
/// );
///
/// assert_eq!(
/// FmtUtc2k::from_rfc2822("Tue, 10 Jul 2003 10:52:37 -0700"),
/// FmtUtc2k::try_from("2003-07-10 17:52:37").ok(),
/// );
///
/// assert_eq!(
/// FmtUtc2k::from_rfc2822("Tue, 1 Jul 2003 10:52:37 +0430"),
/// FmtUtc2k::try_from("2003-07-01 06:22:37").ok(),
/// );
/// ```
pub fn from_rfc2822<S>(src: S) -> Option<Self>
where S: AsRef<str> {
Utc2k::from_rfc2822(src).map(Self::from)
}
#[allow(unsafe_code)]
#[must_use]
/// # To RFC2822.
///
/// Return a string formatted according to [RFC2822](https://datatracker.ietf.org/doc/html/rfc2822).
///
/// There are a couple things to consider:
/// * This method is allocating;
/// * The length of the resulting string will either be `30` or `31` depending on whether the day is double-digit;
///
/// ## Examples
///
/// ```
/// use utc2k::{FmtUtc2k, Utc2k};
///
/// let date = FmtUtc2k::from(Utc2k::new(2003, 7, 1, 10, 52, 37));
/// assert_eq!(date.to_rfc2822(), "Tue, 01 Jul 2003 10:52:37 +0000");
/// assert_eq!(date.to_rfc2822(), Utc2k::new(2003, 7, 1, 10, 52, 37).to_rfc2822());
///
/// let date = FmtUtc2k::from(Utc2k::new(2020, 6, 13, 8, 8, 8));
/// assert_eq!(date.to_rfc2822(), "Sat, 13 Jun 2020 08:08:08 +0000");
/// ```
pub fn to_rfc2822(&self) -> String {
let utc = Utc2k::from(self);
let weekday: [u8; 3] = utc.weekday().abbreviation_bytes();
let month: [u8; 3] = utc.month_enum().abbreviation_bytes();
// Working from bytes is ugly, but performs much better than any
// string-based operations.
let out: Vec<u8> = vec![
weekday[0], weekday[1], weekday[2],
b',', b' ',
self.0[8], self.0[9],
b' ',
month[0], month[1], month[2],
b' ',
b'2', b'0', self.0[2], self.0[3],
b' ',
self.0[11], self.0[12], self.0[13], self.0[14], self.0[15], self.0[16], self.0[17], self.0[18],
b' ', b'+', b'0', b'0', b'0', b'0'
];
// Safety: datetimes are valid ASCII.
unsafe { String::from_utf8_unchecked(out) }
}
}
#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
/// # UTC2K.
///
/// This is a lightweight date/time object for UTC date ranges within the
/// current century (e.g. `2000-01-01 00:00:00` to `2099-12-31 23:59:59`).
///
/// Values outside this range are saturated to fit, unless using
/// [`Utc2k::checked_from_unixtime`].
///
/// To instantiate from a UTC unix timestamp, use `From<u32>`. To try to parse
/// from a `YYYY-MM-DD HH:MM:SS` string, use `TryFrom<&str>`.
///
/// To manually construct from individual parts, you can just call [`Utc2k::new`].
///
/// A `Utc2k` object can be turned back into its constituent parts via
/// [`Utc2k::parts`], or the individual methods like [`Utc2k::year`], [`Utc2k::month`],
/// etc.
///
/// It can be converted into a unix timestamp with [`Utc2k::unixtime`].
///
/// ## Examples
///
/// ```
/// use utc2k::Utc2k;
///
/// let date = Utc2k::default(); // 2000-01-01 00:00:00
/// let date = Utc2k::now(); // The current time.
/// let date = Utc2k::from(4_102_444_799_u32); // 2099-12-31 23:59:59
///
/// // String parsing is fallible, but flexible. So long as the numbers we
/// // need are in the right place, it will be fine.
/// assert!(Utc2k::try_from("2099-12-31 23:59:59").is_ok()); // Fine.
/// assert!(Utc2k::try_from("2099-12-31T23:59:59.0000").is_ok()); // Also fine.
/// assert!(Utc2k::try_from("2099-12-31").is_ok()); // Also fine, but midnight.
/// assert!(Utc2k::try_from("January 1, 2010").is_err()); // Nope!
/// ```
pub struct Utc2k {
y: u8,
m: u8,
d: u8,
hh: u8,
mm: u8,
ss: u8,
}
impl Add<u32> for Utc2k {
type Output = Self;
#[inline]
fn add(self, other: u32) -> Self { Self::from(Abacus::from(self) + other) }
}
impl AddAssign<u32> for Utc2k {
#[inline]
fn add_assign(&mut self, other: u32) { *self = *self + other; }
}
impl Default for Utc2k {
#[inline]
fn default() -> Self { Self::min() }
}
impl fmt::Display for Utc2k {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let buf = FmtUtc2k::from(*self);
f.write_str(buf.as_str())
}
}
impl From<u32> for Utc2k {
#[allow(clippy::integer_division)]
/// # From Timestamp.
///
/// Note, this will saturate to [`Utc2k::MIN_UNIXTIME`] and
/// [`Utc2k::MAX_UNIXTIME`] if the timestamp is out of range.
///
/// ## Examples
///
/// ```
/// use utc2k::Utc2k;
///
/// assert_eq!(Utc2k::from(0).to_string(), "2000-01-01 00:00:00");
/// assert_eq!(Utc2k::from(u32::MAX).to_string(), "2099-12-31 23:59:59");
/// ```
fn from(src: u32) -> Self {
if src <= Self::MIN_UNIXTIME { Self::min() }
else if src >= Self::MAX_UNIXTIME { Self::max() }
else {
// Tease out the date parts with a lot of terrible math.
let (y, m, d) = parse::date_seconds(src / DAY_IN_SECONDS);
let (hh, mm, ss) = parse::time_seconds(src % DAY_IN_SECONDS);
Self { y, m, d, hh, mm, ss }
}
}
}
impl From<Abacus> for Utc2k {
fn from(src: Abacus) -> Self {
let (y, m, d, hh, mm, ss) = src.parts();
Self { y, m, d, hh, mm, ss }
}
}
impl From<&FmtUtc2k> for Utc2k {
#[inline]
fn from(src: &FmtUtc2k) -> Self { Self::from(*src) }
}
impl From<FmtUtc2k> for Utc2k {
fn from(src: FmtUtc2k) -> Self {
Self::new(
2000 + u16::from((src.0[2] & 0x0f) * 10 + (src.0[3] & 0x0f)),
(src.0[5] & 0x0f) * 10 + (src.0[6] & 0x0f),
(src.0[8] & 0x0f) * 10 + (src.0[9] & 0x0f),
(src.0[11] & 0x0f) * 10 + (src.0[12] & 0x0f),
(src.0[14] & 0x0f) * 10 + (src.0[15] & 0x0f),
(src.0[17] & 0x0f) * 10 + (src.0[18] & 0x0f),
)
}
}
impl Ord for Utc2k {
/// # Compare.
///
/// Compare two dates.
///
/// ## Examples
///
/// ```
/// use utc2k::Utc2k;
///
/// let date1 = Utc2k::new(2020, 10, 15, 20, 25, 30);
/// let date2 = Utc2k::new(2020, 10, 15, 0, 0, 0);
/// let date3 = Utc2k::new(2022, 10, 15, 0, 0, 0);
///
/// assert!(date1 > date2);
/// assert!(date1 < date3);
/// ```
fn cmp(&self, other: &Self) -> Ordering {
let other = *other;
match self.cmp_date(other) {
Ordering::Equal => self.cmp_time(other),
cmp => cmp,
}
}
}
impl PartialOrd for Utc2k {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.cmp(other)) }
}
impl Sub<u32> for Utc2k {
type Output = Self;
#[allow(clippy::cast_possible_truncation)] // It fits.
/// # Subtraction.
///
/// This method returns a new `Utc2k` object reduced by a given number of
/// seconds.
///
/// ## Examples
///
/// ```
/// use utc2k::Utc2k;
///
/// assert_eq!(
/// Utc2k::new(2020, 1, 5, 0, 0, 0) - 86_400_u32,
/// Utc2k::new(2020, 1, 4, 0, 0, 0),
/// );
///
/// assert_eq!(
/// Utc2k::new(2020, 1, 5, 0, 0, 0) - 86_399_u32,
/// Utc2k::new(2020, 1, 4, 0, 0, 1),
/// );
///
/// assert_eq!(
/// Utc2k::new(2020, 1, 5, 3, 10, 20) - 14_400_u32,
/// Utc2k::new(2020, 1, 4, 23, 10, 20),
/// );
///
/// assert_eq!(
/// Utc2k::new(2020, 1, 1, 3, 10, 20) - 14_400_u32,
/// Utc2k::new(2019, 12, 31, 23, 10, 20),
/// );
/// ```
fn sub(self, other: u32) -> Self {
// Count up the "easy" seconds. If we're subtracting less than this
// amount, we can handle the subtraction without any month boundary or
// leap year shenanigans.
let mut easy: u32 =
u32::from(self.d - 1) * DAY_IN_SECONDS +
u32::from(self.hh) * HOUR_IN_SECONDS +
u32::from(self.mm) * MINUTE_IN_SECONDS +
u32::from(self.ss);
if other <= easy {
easy -= other;
let d: u8 =
if easy >= DAY_IN_SECONDS {
let d = easy.wrapping_div(DAY_IN_SECONDS);
easy -= d * DAY_IN_SECONDS;
d as u8 + 1
}
else { 1 };
let (hh, mm, ss) = parse::time_seconds(easy);
Self {
y: self.y,
m: self.m,
d,
hh,
mm,
ss,
}
}
// Otherwise it is best to convert to unixtime, perform the
// subtraction, and convert it back.
else {
Self::from(self.unixtime().saturating_sub(other))
}
}
}
impl SubAssign<u32> for Utc2k {
#[inline]
fn sub_assign(&mut self, other: u32) { *self = *self - other; }
}
try_from_unixtime!(i32, u64, i64, usize, isize);
impl TryFrom<&OsStr> for Utc2k {
type Error = Utc2kError;
/// # From `OsStr`.
///
/// ```
/// use std::ffi::OsStr;
/// use utc2k::Utc2k;
///
/// assert_eq!(
/// Utc2k::try_from(OsStr::new("2013-12-15 21:30:02")).unwrap().to_string(),
/// "2013-12-15 21:30:02"
/// );
/// assert_eq!(
/// Utc2k::try_from(OsStr::new("2013-12-15")).unwrap().to_string(),
/// "2013-12-15 00:00:00"
/// );
/// ```
fn try_from(src: &OsStr) -> Result<Self, Self::Error> {
let src: &str = src.to_str().ok_or(Utc2kError::Invalid)?;
Self::try_from(src)
}
}
impl TryFrom<&[u8]> for Utc2k {
type Error = Utc2kError;
#[allow(clippy::option_if_let_else)] // No.
/// # Parse Slice.
///
/// This will attempt to construct a [`Utc2k`] from a date/time or date
/// slice. See [`Utc2k::from_datetime_str`] and [`Utc2k::from_date_str`] for more
/// information.
///
/// ## Examples
///
/// ```
/// use utc2k::Utc2k;
///
/// let date = Utc2k::try_from(&b"2021/06/25"[..]).unwrap();
/// assert_eq!(date.to_string(), "2021-06-25 00:00:00");
///
/// let date = Utc2k::try_from(&b"2021-06-25 13:15:25.0000"[..]).unwrap();
/// assert_eq!(date.to_string(), "2021-06-25 13:15:25");
///
/// assert!(Utc2k::try_from(&b"2021-06-applesauces"[..]).is_err());
/// ```
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
if let Some(b) = bytes.get(..19) {
parse::parts_from_datetime(b)
}
else if let Some(b) = bytes.get(..10) {
parse::parts_from_date(b)
}
else { Err(Utc2kError::Invalid) }
}
}
impl TryFrom<&str> for Utc2k {
type Error = Utc2kError;
/// # Parse String.
///
/// This will attempt to construct a [`Utc2k`] from a date/time or date
/// string. Parsing is naive; only the positions where numbers are
/// expected will be looked at.
///
/// String length is used to determine whether or not the value should be
/// parsed as a full date/time (19) or just a date (10).
///
/// See [`Utc2k::from_datetime_str`] and [`Utc2k::from_date_str`] for more
/// information.
///
/// ## Examples
///
/// ```
/// use utc2k::Utc2k;
///
/// let date = Utc2k::try_from("2021/06/25").unwrap();
/// assert_eq!(date.to_string(), "2021-06-25 00:00:00");
///
/// let date = Utc2k::try_from("2021-06-25 13:15:25.0000").unwrap();
/// assert_eq!(date.to_string(), "2021-06-25 13:15:25");
///
/// assert!(Utc2k::try_from("2021-06-applesauces").is_err());
/// ```
fn try_from(src: &str) -> Result<Self, Self::Error> {
Self::try_from(src.as_bytes())
}
}
/// ## Min/Max.
impl Utc2k {
/// # Minimum Timestamp.
pub const MIN_UNIXTIME: u32 = 946_684_800;
/// # Maximum Timestamp.
pub const MAX_UNIXTIME: u32 = 4_102_444_799;
#[inline]
#[must_use]
/// # Minimum Value.
///
/// This is equivalent to `2000-01-01 00:00:00`.
///
/// ## Examples
///
/// ```
/// use utc2k::Utc2k;
///
/// let date = Utc2k::min();
/// assert_eq!(date.to_string(), "2000-01-01 00:00:00");
/// ```
pub const fn min() -> Self { Self { y: 0, m: 1, d: 1, hh: 0, mm: 0, ss: 0 } }
#[inline]
#[must_use]
/// # Maximum Value.
///
/// This is equivalent to `2099-12-31 23:59:59`.
///
/// ## Examples
///
/// ```
/// use utc2k::Utc2k;
///
/// let date = Utc2k::max();
/// assert_eq!(date.to_string(), "2099-12-31 23:59:59");
/// ```
pub const fn max() -> Self { Self { y: 99, m: 12, d: 31, hh: 23, mm: 59, ss: 59 } }
}
/// ## Instantiation.
impl Utc2k {
#[inline]
#[must_use]
/// # New (From Parts).
///
/// This will create a new instance from individual year, month, etc.,
/// parts.
///
/// Overflowing units will be carried over where appropriate, so for
/// example, 13 months becomes 1 year and 1 month.
///
/// Dates prior to 2000 or after 2099 will be saturated to fit.
///
/// ## Examples
///
/// ```
/// use utc2k::Utc2k;
///
/// let date = Utc2k::new(2010, 5, 5, 16, 30, 1);
/// assert_eq!(date.to_string(), "2010-05-05 16:30:01");
/// ```
pub fn new(y: u16, m: u8, d: u8, hh: u8, mm: u8, ss: u8) -> Self {
Self::from(Abacus::new(y, m, d, hh, mm, ss))
}
#[inline]
#[must_use]
/// # Now.
///
/// Create a new instance representing the current UTC time.
pub fn now() -> Self { Self::from(unixtime()) }
#[cfg(feature = "local")]
#[cfg_attr(feature = "docsrs", doc(cfg(feature = "local")))]
#[must_use]
/// # Now (Local).
///
/// This returns an instance using the current, local time as the seed. If
/// no local offset can be determined, this is equivalent to [`Utc2k::now`].
///
/// Refer to [`LocalOffset`](crate::LocalOffset) for important caveats and
/// limitations.
pub fn now_local() -> Self {
Self::from(crate::LocalOffset::now())
}
#[inline]
#[must_use]
/// # Tomorrow.
///
/// Create a new instance representing one day from now (present time).
///
/// ## Examples
///
/// ```
/// use utc2k::Utc2k;
///
/// assert_eq!(Utc2k::tomorrow(), Utc2k::now() + 86_400_u32);
/// ```
pub fn tomorrow() -> Self { Self::from(unixtime() + DAY_IN_SECONDS) }
#[inline]
#[must_use]
/// # Yesterday.
///
/// Create a new instance representing one day ago (present time).
///
/// ## Examples
///
/// ```
/// use utc2k::Utc2k;
///
/// assert_eq!(Utc2k::yesterday(), Utc2k::now() - 86_400_u32);
/// ```
pub fn yesterday() -> Self { Self::from(unixtime() - DAY_IN_SECONDS) }
}
/// ## String Parsing.
impl Utc2k {
/// # From Date/Time.
///
/// Parse a string containing a date/time in `YYYY-MM-DD HH:MM:SS` format.
/// This operation is naive and only looks at the positions where numbers
/// are expected.
///
/// In other words, `2020-01-01 00:00:00` will parse the same as
/// `2020/01/01 00:00:00` or even `2020-01-01 00:00:00.0000 PDT`.
///
/// As with all the other methods, dates outside the `2000..=2099` range
/// will be saturated (non-failing), and overflows will be carried over to
/// the appropriate unit (e.g. 13 months will become +1 year and 1 month).
///
/// ## Examples
///
/// ```
/// use utc2k::Utc2k;
///
/// // This isn't long enough.
/// assert!(Utc2k::from_datetime_str("2021/06/25").is_err());
///
/// // This is fine.
/// let date = Utc2k::from_datetime_str("2021-06-25 13:15:25.0000").unwrap();
/// assert_eq!(date.to_string(), "2021-06-25 13:15:25");
///
/// // This is all wrong.
/// assert!(Utc2k::from_datetime_str("Applebutter").is_err());
/// ```
///
/// ## Errors
///
/// If any of the digits fail to parse, or if the string is insufficiently
/// sized, an error will be returned.
pub fn from_datetime_str<B>(src: B) -> Result<Self, Utc2kError>
where B: AsRef<[u8]> {
src.as_ref().get(..19)
.ok_or(Utc2kError::Invalid)
.and_then(parse::parts_from_datetime)
}
/// # From Date/Time (Smooshed).
///
/// This is just like [`Utc2k::from_datetime_str`] for "smooshed" datetime
/// strings, i.e. `YYYYMMDDHHMMSS` (no separators).
///
/// ## Examples
///
/// ```
/// use utc2k::Utc2k;
///
/// // This isn't long enough.
/// assert!(Utc2k::from_smooshed_datetime_str("20210625").is_err());
///
/// // This is fine.
/// let date = Utc2k::from_smooshed_datetime_str("20210625131525").unwrap();
/// assert_eq!(date.to_string(), "2021-06-25 13:15:25");
///
/// // This *won't* work because there are separators in the way.
/// assert!(Utc2k::from_smooshed_datetime_str("2021-06-25 13:15:25").is_err());
///
/// // This is all wrong.
/// assert!(Utc2k::from_smooshed_datetime_str("Applebutterful").is_err());
/// ```
///
/// ## Errors
///
/// If any of the digits fail to parse, or if the string is insufficiently
/// sized, an error will be returned.
pub fn from_smooshed_datetime_str<B>(src: B) -> Result<Self, Utc2kError>
where B: AsRef<[u8]> {
src.as_ref().get(..14)
.ok_or(Utc2kError::Invalid)
.and_then(parse::parts_from_smooshed_datetime)
}
/// # From Date.
///
/// Parse a string containing a date/time in `YYYY-MM-DD` format. This
/// operation is naive and only looks at the positions where numbers are
/// expected.
///
/// In other words, `2020-01-01` will parse the same as `2020/01/01` or
/// even `2020-01-01 13:03:33.5900 PDT`.
///
/// As with all the other methods, dates outside the `2000..=2099` range
/// will be saturated (non-failing), and overflows will be carried over to
/// the appropriate unit (e.g. 13 months will become +1 year and 1 month).
///
/// The time will always be set to midnight when using this method.
///
/// ## Examples
///
/// ```
/// use utc2k::Utc2k;
///
/// // This is fine.
/// let date = Utc2k::from_date_str("2021/06/25").unwrap();
/// assert_eq!(date.to_string(), "2021-06-25 00:00:00");
///
/// // This is fine, but the time will be ignored.
/// let date = Utc2k::from_date_str("2021-06-25 13:15:25.0000").unwrap();
/// assert_eq!(date.to_string(), "2021-06-25 00:00:00");
///
/// // This is all wrong.
/// assert!(Utc2k::from_date_str("Applebutter").is_err());
/// ```
///
/// ## Errors
///
/// If any of the digits fail to parse, or if the string is insufficiently
/// sized, an error will be returned.
pub fn from_date_str<B>(src: B) -> Result<Self, Utc2kError>
where B: AsRef<[u8]> {
src.as_ref().get(..10)
.ok_or(Utc2kError::Invalid)
.and_then(parse::parts_from_date)
}
/// # From Date (Smooshed).
///
/// This is just like [`Utc2k::from_date_str`] for "smooshed" date strings,
/// i.e. `YYYYMMDD` (no separators).
///
/// ## Examples
///
/// ```
/// use utc2k::Utc2k;
///
/// // This is fine.
/// let date = Utc2k::from_smooshed_date_str("20210625").unwrap();
/// assert_eq!(date.to_string(), "2021-06-25 00:00:00");
///
/// // This is fine, but the time will be ignored.
/// let date = Utc2k::from_smooshed_date_str("20210625131525").unwrap();
/// assert_eq!(date.to_string(), "2021-06-25 00:00:00");
///
/// // This *won't* work because it has dashes in the way.
/// assert!(Utc2k::from_smooshed_date_str("2021-06-25").is_err());
///
/// // This is all wrong.
/// assert!(Utc2k::from_smooshed_date_str("Applebutter").is_err());
/// ```
///
/// ## Errors
///
/// If any of the digits fail to parse, or if the string is insufficiently
/// sized, an error will be returned.
pub fn from_smooshed_date_str<B>(src: B) -> Result<Self, Utc2kError>
where B: AsRef<[u8]> {
src.as_ref().get(..8)
.ok_or(Utc2kError::Invalid)
.and_then(parse::parts_from_smooshed_date)
}
/// # Parse Time.
///
/// This method attempts to parse a time string in the `HH:MM:SS` format,
/// returning the hours, minutes, and seconds as integers.
///
/// As with other methods in this library, only positions where numbers are
/// expected will be looked at. `01:02:03` will parse the same way as
/// `01-02-03`.
///
/// ## Examples
///
/// ```
/// use utc2k::Utc2k;
///
/// assert_eq!(
/// Utc2k::parse_time_str("15:35:47"),
/// Ok((15, 35, 47))
/// );
///
/// // The hours are out of range.
/// assert!(Utc2k::parse_time_str("30:35:47").is_err());
/// ```
///
/// ## Errors
///
/// This method will return an error if any of the numeric bits are invalid
/// or out of range (hours must be < 24, minutes and seconds < 60).
pub fn parse_time_str<B>(src: B) -> Result<(u8, u8, u8), Utc2kError>
where B: AsRef<[u8]> {
if let Some(b) = src.as_ref().get(..8) {
let (hh, mm, ss) = parse::hms(b)?;
if hh < 24 && mm < 60 && ss < 60 {
return Ok((hh, mm, ss));
}
}
Err(Utc2kError::Invalid)
}
}
/// ## Get Parts.
impl Utc2k {
#[inline]
#[must_use]
/// # Parts.
///
/// Return the year, month, etc., parts.
///
/// Alternatively, if you only want the date bits, use [`Utc2k::ymd`], or
/// if you only want the time bits, use [`Utc2k::hms`].
///
/// ## Examples
///
/// ```
/// use utc2k::Utc2k;
///
/// let date = Utc2k::new(2010, 5, 4, 16, 30, 1);
/// assert_eq!(date.parts(), (2010, 5, 4, 16, 30, 1));
/// ```
pub const fn parts(self) -> (u16, u8, u8, u8, u8, u8) {
(
self.year(),
self.m,
self.d,
self.hh,
self.mm,
self.ss,
)
}
#[inline]
#[must_use]
/// # Date Parts.
///
/// Return the year, month, and day.
///
/// If you want the time too, call [`Utc2k::parts`] instead.
///
/// ## Examples
///
/// ```
/// use utc2k::Utc2k;
///
/// let date = Utc2k::new(2010, 5, 5, 16, 30, 1);
/// assert_eq!(date.ymd(), (2010, 5, 5));
/// ```
pub const fn ymd(self) -> (u16, u8, u8) { (self.year(), self.m, self.d) }
#[inline]
#[must_use]
/// # Time Parts.
///
/// Return the hours, minutes, and seconds.
///
/// If you want the date too, call [`Utc2k::parts`] instead.
///
/// ## Examples
///
/// ```
/// use utc2k::Utc2k;
///
/// let date = Utc2k::new(2010, 5, 5, 16, 30, 1);
/// assert_eq!(date.hms(), (16, 30, 1));
/// ```
pub const fn hms(self) -> (u8, u8, u8) { (self.hh, self.mm, self.ss) }
#[inline]
#[must_use]
/// # Year.
///
/// This returns the year value.
///
/// ## Examples
///
/// ```
/// use utc2k::Utc2k;
///
/// let date = Utc2k::new(2010, 5, 15, 16, 30, 1);
/// assert_eq!(date.year(), 2010);
/// ```
pub const fn year(self) -> u16 { self.y as u16 + 2000 }
#[inline]
#[must_use]
/// # Month.
///
/// This returns the month value.
///
/// ## Examples
///
/// ```
/// use utc2k::Utc2k;
///
/// let date = Utc2k::new(2010, 5, 15, 16, 30, 1);
/// assert_eq!(date.month(), 5);
/// ```
pub const fn month(self) -> u8 { self.m }
#[allow(unsafe_code)]
#[inline]
#[must_use]
/// # Month (enum).
///
/// This returns the month value as a [`Month`].
///
/// ## Examples
///
/// ```
/// use utc2k::{Month, Utc2k};
///
/// let date = Utc2k::new(2010, 5, 15, 16, 30, 1);
/// assert_eq!(date.month_enum(), Month::May);
/// ```
pub const fn month_enum(self) -> Month {
// Safety: the month is validated during construction.
unsafe { Month::from_u8_unchecked(self.m) }
}
#[inline]
#[must_use]
/// # Day.
///
/// This returns the day value.
///
/// ## Examples
///
/// ```
/// use utc2k::Utc2k;
///
/// let date = Utc2k::new(2010, 5, 15, 16, 30, 1);
/// assert_eq!(date.day(), 15);
/// ```
pub const fn day(self) -> u8 { self.d }
#[inline]
#[must_use]
/// # Hour.
///
/// This returns the hour value.
///
/// ## Examples
///
/// ```
/// use utc2k::Utc2k;
///
/// let date = Utc2k::new(2010, 5, 15, 16, 30, 1);
/// assert_eq!(date.hour(), 16);
/// ```
pub const fn hour(self) -> u8 { self.hh }
#[inline]
#[must_use]
/// # Minute.
///
/// This returns the minute value.
///
/// ## Examples
///
/// ```
/// use utc2k::Utc2k;
///
/// let date = Utc2k::new(2010, 5, 15, 16, 30, 1);
/// assert_eq!(date.minute(), 30);
/// ```
pub const fn minute(self) -> u8 { self.mm }
#[inline]
#[must_use]
/// # Second.
///
/// This returns the second value.
///
/// ## Examples
///
/// ```
/// use utc2k::Utc2k;
///
/// let date = Utc2k::new(2010, 5, 15, 16, 30, 1);
/// assert_eq!(date.second(), 1);
/// ```
pub const fn second(self) -> u8 { self.ss }
}
/// ## Other Getters.
impl Utc2k {
#[must_use]
/// # Is Leap Year?
///
/// This returns `true` if this date is/was in a leap year.
///
/// ## Examples
///
/// ```
/// use utc2k::Utc2k;
///
/// let date = Utc2k::try_from("2020-05-10 00:00:00").unwrap();
/// assert!(date.leap_year());
///
/// let date = Utc2k::try_from("2021-03-15 00:00:00").unwrap();
/// assert!(! date.leap_year());
/// ```
pub const fn leap_year(self) -> bool {
// Leap years this century.
const LEAP_YEARS: [bool; 100] = [true, false, false, false, true, false, false, false, true, false, false, false, true, false, false, false, true, false, false, false, true, false, false, false, true, false, false, false, true, false, false, false, true, false, false, false, true, false, false, false, true, false, false, false, true, false, false, false, true, false, false, false, true, false, false, false, true, false, false, false, true, false, false, false, true, false, false, false, true, false, false, false, true, false, false, false, true, false, false, false, true, false, false, false, true, false, false, false, true, false, false, false, true, false, false, false, true, false, false, false];
LEAP_YEARS[self.y as usize]
}
#[inline]
#[must_use]
/// # Abbreviated Month Name.
///
/// Return the abbreviated name of the month, nice and pretty.
///
/// ## Examples
///
/// ```
/// use utc2k::Utc2k;
///
/// let date = Utc2k::try_from("2020-06-24 20:19:30").unwrap();
/// assert_eq!(date.month_abbreviation(), "Jun");
/// ```
pub const fn month_abbreviation(self) -> &'static str {
self.month_enum().abbreviation()
}
#[inline]
#[must_use]
/// # Month Name.
///
/// Return the name of the month, nice and pretty.
///
/// ## Examples
///
/// ```
/// use utc2k::Utc2k;
///
/// let date = Utc2k::try_from("2020-06-24 20:19:30").unwrap();
/// assert_eq!(date.month_name(), "June");
/// ```
pub const fn month_name(self) -> &'static str {
self.month_enum().as_str()
}
#[must_use]
/// # Month Size (Days).
///
/// This returns the total number of days this month could hold, or put
/// another way, the last day of this month.
///
/// The value will always be between `28..=31`, with leap Februaries
/// returning `29`.
///
/// ## Examples
///
/// ```
/// use utc2k::Utc2k;
///
/// let date = Utc2k::try_from("2021-07-08 13:22:01").unwrap();
/// assert_eq!(date.month_size(), 31);
/// ```
pub const fn month_size(self) -> u8 {
if self.m == 2 && self.leap_year() { 29 }
else { self.month_enum().days() }
}
#[must_use]
/// # Ordinal.
///
/// Return the day-of-year value. This will be between `1..=365` (or `1..=366`
/// for leap years).
///
/// ## Examples
///
/// ```
/// use utc2k::Utc2k;
///
/// let date = Utc2k::try_from("2020-05-10 00:00:00").unwrap();
/// assert_eq!(date.ordinal(), 131);
///
/// let date = Utc2k::try_from("2021-01-15 00:00:00").unwrap();
/// assert_eq!(date.ordinal(), 15);
/// ```
pub const fn ordinal(self) -> u16 {
let days = self.d as u16 +
match self.m {
2 => 31,
3 => 59,
4 => 90,
5 => 120,
6 => 151,
7 => 181,
8 => 212,
9 => 243,
10 => 273,
11 => 304,
12 => 334,
_ => 0,
};
if 2 < self.m && self.leap_year() { days + 1 }
else { days }
}
#[inline]
#[must_use]
/// # Seconds From Midnight.
///
/// Return the number of seconds since midnight. In other words, this adds
/// up all of the time bits.
///
/// ## Examples
///
/// ```
/// use utc2k::Utc2k;
///
/// let date = Utc2k::new(2010, 11, 30, 0, 0, 0);
/// assert_eq!(date.seconds_from_midnight(), 0);
///
/// let date = Utc2k::new(2010, 11, 30, 0, 0, 30);
/// assert_eq!(date.seconds_from_midnight(), 30);
///
/// let date = Utc2k::new(2010, 11, 30, 0, 1, 30);
/// assert_eq!(date.seconds_from_midnight(), 90);
///
/// let date = Utc2k::new(2010, 11, 30, 12, 30, 10);
/// assert_eq!(date.seconds_from_midnight(), 45_010);
/// ```
pub const fn seconds_from_midnight(self) -> u32 {
self.ss as u32 +
self.mm as u32 * MINUTE_IN_SECONDS +
self.hh as u32 * HOUR_IN_SECONDS
}
#[must_use]
/// # Weekday.
///
/// Return the [`Weekday`] corresponding to the given date.
///
/// ## Examples
///
/// ```
/// use utc2k::{Utc2k, Weekday};
///
/// let date = Utc2k::try_from("2021-07-08 13:22:01").unwrap();
/// assert_eq!(date.weekday(), Weekday::Thursday);
/// assert_eq!(date.weekday().as_ref(), "Thursday");
/// ```
pub fn weekday(self) -> Weekday {
Weekday::year_begins_on(self.y) + (self.ordinal() - 1)
}
}
/// ## Conversion.
impl Utc2k {
#[inline]
#[must_use]
/// # Formatted.
///
/// This returns a [`FmtUtc2k`] and is equivalent to calling
/// `FmtUtc2k::from(self)`.
///
/// ## Examples
///
/// ```
/// use utc2k::{FmtUtc2k, Utc2k};
///
/// let date = Utc2k::new(2010, 5, 15, 16, 30, 1);
/// assert_eq!(date.formatted(), FmtUtc2k::from(date));
/// ```
pub fn formatted(self) -> FmtUtc2k { FmtUtc2k::from(self) }
#[inline]
#[must_use]
/// # To RFC3339.
///
/// Return a string formatted according to [RFC3339](https://datatracker.ietf.org/doc/html/rfc3339).
///
/// Note: this method is allocating.
///
/// ## Examples
///
/// ```
/// use utc2k::Utc2k;
///
/// let date = Utc2k::new(2021, 12, 13, 11, 56, 1);
/// assert_eq!(date.to_rfc3339(), "2021-12-13T11:56:01Z");
/// ```
pub fn to_rfc3339(&self) -> String { FmtUtc2k::from(*self).to_rfc3339() }
#[allow(unsafe_code)]
#[must_use]
/// # To RFC2822.
///
/// Return a string formatted according to [RFC2822](https://datatracker.ietf.org/doc/html/rfc2822).
///
/// There are a couple things to consider:
/// * This method is allocating;
/// * The length of the resulting string will either be `30` or `31` depending on whether the day is double-digit;
///
/// ## Examples
///
/// ```
/// use utc2k::Utc2k;
///
/// let date = Utc2k::new(2003, 7, 1, 10, 52, 37);
/// assert_eq!(date.to_rfc2822(), "Tue, 01 Jul 2003 10:52:37 +0000");
///
/// let date = Utc2k::new(2036, 12, 15, 16, 30, 55);
/// assert_eq!(date.to_rfc2822(), "Mon, 15 Dec 2036 16:30:55 +0000");
/// ```
pub fn to_rfc2822(&self) -> String {
let weekday: [u8; 3] = self.weekday().abbreviation_bytes();
let month: [u8; 3] = self.month_enum().abbreviation_bytes();
let d_idx = (self.d << 1) as usize;
let y_idx = (self.y << 1) as usize;
let hh_idx = (self.hh << 1) as usize;
let mm_idx = (self.mm << 1) as usize;
let ss_idx = (self.ss << 1) as usize;
// Working from bytes is ugly, but performs much better than any
// string-based operations.
let out: Vec<u8> = vec![
weekday[0], weekday[1], weekday[2],
b',', b' ',
DD[d_idx], DD[d_idx + 1],
b' ',
month[0], month[1], month[2],
b' ',
b'2', b'0', DD[y_idx], DD[y_idx + 1],
b' ',
DD[hh_idx], DD[hh_idx + 1], b':', DD[mm_idx], DD[mm_idx + 1], b':', DD[ss_idx], DD[ss_idx + 1],
b' ', b'+', b'0', b'0', b'0', b'0'
];
// Safety: datetimes are valid ASCII.
unsafe { String::from_utf8_unchecked(out) }
}
/// # From RFC2822.
///
/// This method can be used to construct a `Utc2k` from an RFC2822-formatted
/// string. Variations with and without a leading weekday, and with and
/// without a trailing offset, are supported. If an offset is included, the
/// datetime will be adjusted accordingly to make it properly UTC.
///
/// Note: missing offsets are meant to imply "localized" time, but as this
/// library has no timezone handling, strings without any "+HHMM" at the
/// end will be parsed as if they were already in UTC.
///
/// ## Examples
///
/// ```
/// use utc2k::Utc2k;
///
/// assert_eq!(
/// Utc2k::from_rfc2822("Tue, 1 Jul 2003 10:52:37 +0000"),
/// Some(Utc2k::new(2003, 7, 1, 10, 52, 37)),
/// );
///
/// assert_eq!(
/// Utc2k::from_rfc2822("Tue, 01 Jul 2003 10:52:37 +0000"),
/// Some(Utc2k::new(2003, 7, 1, 10, 52, 37)),
/// );
///
/// assert_eq!(
/// Utc2k::from_rfc2822("1 Jul 2003 10:52:37"),
/// Some(Utc2k::new(2003, 7, 1, 10, 52, 37)),
/// );
///
/// assert_eq!(
/// Utc2k::from_rfc2822("01 Jul 2003 10:52:37"),
/// Some(Utc2k::new(2003, 7, 1, 10, 52, 37)),
/// );
///
/// assert_eq!(
/// Utc2k::from_rfc2822("Tue, 10 Jul 2003 10:52:37 -0700"),
/// Some(Utc2k::new(2003, 7, 10, 17, 52, 37)),
/// );
///
/// assert_eq!(
/// Utc2k::from_rfc2822("Tue, 1 Jul 2003 10:52:37 +0430"),
/// Some(Utc2k::new(2003, 7, 1, 6, 22, 37)),
/// );
/// ```
pub fn from_rfc2822<S>(src: S) -> Option<Self>
where S: AsRef<str> {
let src: &[u8] = src.as_ref().trim().as_bytes();
if 19 <= src.len() {
// Strip off the optional weekday, if any, so we can parse the day
// from a predictable starting place.
if src[0].is_ascii_alphabetic() { parse::rfc2822_day(&src[5..]) }
else { parse::rfc2822_day(src) }
}
else { None }
}
#[must_use]
/// # To Midnight.
///
/// Return a new instance with zeroed-out time pieces, i.e. truncated to
/// the date's midnight.
///
/// ## Examples
///
/// ```
/// use utc2k::Utc2k;
///
/// let date1 = Utc2k::new(2022, 7, 22, 20, 52, 41);
/// assert_eq!(date1.to_midnight(), date1.with_time(0, 0, 0));
/// ```
pub const fn to_midnight(self) -> Self {
Self {
y: self.y,
m: self.m,
d: self.d,
hh: 0,
mm: 0,
ss: 0,
}
}
#[must_use]
/// # Unix Timestamp.
///
/// Return the unix timestamp for this object.
///
/// ## Examples
///
/// ```
/// use utc2k::Utc2k;
///
/// let date = Utc2k::default(); // 2000-01-01 00:00:00
/// assert_eq!(date.unixtime(), Utc2k::MIN_UNIXTIME);
/// ```
pub fn unixtime(self) -> u32 {
// Seconds from the new year up to the start of the month.
static MONTH_SECONDS: [u32; 12] = [0, 2_678_400, 5_097_600, 7_776_000, 10_368_000, 13_046_400, 15_638_400, 18_316_800, 20_995_200, 23_587_200, 26_265_600, 28_857_600];
// Seconds *before* the new year.
static YEAR_SECONDS: [u32; 100] = [946_684_800, 978_307_200, 1_009_843_200, 1_041_379_200, 1_072_915_200, 1_104_537_600, 1_136_073_600, 1_167_609_600, 1_199_145_600, 1_230_768_000, 1_262_304_000, 1_293_840_000, 1_325_376_000, 1_356_998_400, 1_388_534_400, 1_420_070_400, 1_451_606_400, 1_483_228_800, 1_514_764_800, 1_546_300_800, 1_577_836_800, 1_609_459_200, 1_640_995_200, 1_672_531_200, 1_704_067_200, 1_735_689_600, 1_767_225_600, 1_798_761_600, 1_830_297_600, 1_861_920_000, 1_893_456_000, 1_924_992_000, 1_956_528_000, 1_988_150_400, 2_019_686_400, 2_051_222_400, 2_082_758_400, 2_114_380_800, 2_145_916_800, 2_177_452_800, 2_208_988_800, 2_240_611_200, 2_272_147_200, 2_303_683_200, 2_335_219_200, 2_366_841_600, 2_398_377_600, 2_429_913_600, 2_461_449_600, 2_493_072_000, 2_524_608_000, 2_556_144_000, 2_587_680_000, 2_619_302_400, 2_650_838_400, 2_682_374_400, 2_713_910_400, 2_745_532_800, 2_777_068_800, 2_808_604_800, 2_840_140_800, 2_871_763_200, 2_903_299_200, 2_934_835_200, 2_966_371_200, 2_997_993_600, 3_029_529_600, 3_061_065_600, 3_092_601_600, 3_124_224_000, 3_155_760_000, 3_187_296_000, 3_218_832_000, 3_250_454_400, 3_281_990_400, 3_313_526_400, 3_345_062_400, 3_376_684_800, 3_408_220_800, 3_439_756_800, 3_471_292_800, 3_502_915_200, 3_534_451_200, 3_565_987_200, 3_597_523_200, 3_629_145_600, 3_660_681_600, 3_692_217_600, 3_723_753_600, 3_755_376_000, 3_786_912_000, 3_818_448_000, 3_849_984_000, 3_881_606_400, 3_913_142_400, 3_944_678_400, 3_976_214_400, 4_007_836_800, 4_039_372_800, 4_070_908_800];
// Add up everything as it would be in a non-leap year.
let time = YEAR_SECONDS[usize::from(self.y)] +
MONTH_SECONDS[usize::from(self.m - 1)] +
self.seconds_from_midnight() +
DAY_IN_SECONDS * u32::from(self.d - 1);
// Add a day's worth of seconds if we need to.
if 2 < self.m && self.leap_year() { time + DAY_IN_SECONDS }
else { time }
}
#[must_use]
/// # Change Time.
///
/// Return a new [`Utc2k`] instance with the original date — unless there
/// is carry-over needed — and a new time.
///
/// ## Examples
///
/// ```
/// use utc2k::Utc2k;
///
/// let date = Utc2k::default();
/// assert_eq!(date.to_string(), "2000-01-01 00:00:00");
///
/// // Change the time bits.
/// assert_eq!(date.with_time(13, 14, 15).to_string(), "2000-01-01 13:14:15");
/// ```
pub fn with_time(self, hh: u8, mm: u8, ss: u8) -> Self {
Self::from(Abacus::new(self.year(), self.month(), self.day(), hh, mm, ss))
}
}
/// ## Checked Operations.
impl Utc2k {
/// # Checked Add.
///
/// Return a new [`Utc2k`] instance set _n_ seconds into the future from
/// this one, returning `none` (rather than saturating) on overflow.
///
/// If you'd rather saturate addition, you can just use [`std::ops::Add`].
///
/// ## Examples
///
/// ```
/// use utc2k::Utc2k;
///
/// let date = Utc2k::max();
/// assert!(date.checked_add(1).is_none());
///
/// let date = Utc2k::new(2010, 1, 1, 0, 0, 0);
/// let added = date.checked_add(86_413).unwrap();
/// assert_eq!(added.to_string(), "2010-01-02 00:00:13");
/// ```
pub fn checked_add(self, secs: u32) -> Option<Self> {
self.unixtime().checked_add(secs)
.filter(|s| s <= &Self::MAX_UNIXTIME)
.map(Self::from)
}
/// # From Unixtime (Checked).
///
/// This can be used instead of the usual `From<u32>` if you'd like to
/// trigger an error when the timestamp is out of range (rather than just
/// saturating it).
///
/// ## Errors
///
/// An error will be returned if the timestamp is less than [`Utc2k::MIN_UNIXTIME`]
/// or greater than [`Utc2k::MAX_UNIXTIME`].
///
/// ## Examples
///
/// ```
/// use utc2k::Utc2k;
///
/// // Too old.
/// assert!(Utc2k::checked_from_unixtime(0).is_err());
///
/// // Too new.
/// assert!(Utc2k::checked_from_unixtime(u32::MAX).is_err());
///
/// // This fits.
/// assert!(Utc2k::checked_from_unixtime(Utc2k::MIN_UNIXTIME).is_ok());
/// ```
pub fn checked_from_unixtime(src: u32) -> Result<Self, Utc2kError> {
if src < Self::MIN_UNIXTIME { Err(Utc2kError::Underflow) }
else if src > Self::MAX_UNIXTIME { Err(Utc2kError::Overflow) }
else { Ok(Self::from(src)) }
}
/// # Checked Sub.
///
/// Return a new [`Utc2k`] instance set _n_ seconds before this one,
/// returning `none` (rather than saturating) on overflow.
///
/// If you'd rather saturate subtraction, you can just use [`std::ops::Sub`].
///
/// ## Examples
///
/// ```
/// use utc2k::Utc2k;
///
/// let date = Utc2k::min();
/// assert!(date.checked_sub(1).is_none());
///
/// let date = Utc2k::new(2010, 1, 1, 0, 0, 0);
/// let subbed = date.checked_sub(86_413).unwrap();
/// assert_eq!(subbed.to_string(), "2009-12-30 23:59:47");
/// ```
pub fn checked_sub(self, secs: u32) -> Option<Self> {
self.unixtime().checked_sub(secs)
.filter(|s| s >= &Self::MIN_UNIXTIME)
.map(Self::from)
}
}
/// # Comparison.
impl Utc2k {
#[must_use]
/// # Absolute Difference.
///
/// This returns the (absolute) number of seconds between two datetimes.
///
/// ## Examples.
///
/// ```
/// use utc2k::Utc2k;
///
/// let date1 = Utc2k::new(2022, 10, 15, 11, 30, 0);
/// let date2 = Utc2k::new(2022, 10, 15, 11, 31, 0);
///
/// // ABS means the ordering does not matter.
/// assert_eq!(date1.abs_diff(date2), 60);
/// assert_eq!(date2.abs_diff(date1), 60);
///
/// // If the dates are equal, the difference is zero.
/// assert_eq!(date1.abs_diff(date1), 0);
///
/// // Because we're only dealing with a single century, there is an
/// // upper limit to the possible return values…
/// assert_eq!(Utc2k::min().abs_diff(Utc2k::max()), 3_155_759_999);
/// ```
pub fn abs_diff(self, other: Self) -> u32 {
self.unixtime().abs_diff(other.unixtime())
}
#[must_use]
/// # Compare (Only) Dates.
///
/// Compare `self` to another `Utc2k` instance, ignoring the time
/// components of each.
///
/// ## Examples
///
/// ```
/// use utc2k::Utc2k;
/// use std::cmp::Ordering;
///
/// // The times are different, but the dates match.
/// let date1 = Utc2k::new(2020, 3, 15, 0, 0, 0);
/// let date2 = Utc2k::new(2020, 3, 15, 16, 30, 20);
/// assert_eq!(date1.cmp_date(date2), Ordering::Equal);
///
/// // If the dates don't match, it's what you'd expect.
/// let date3 = Utc2k::new(2022, 10, 31, 0, 0, 0);
/// assert_eq!(date1.cmp_date(date3), Ordering::Less);
/// ```
pub fn cmp_date(self, other: Self) -> Ordering {
match self.y.cmp(&other.y) {
Ordering::Equal => match self.m.cmp(&other.m) {
Ordering::Equal => self.d.cmp(&other.d),
cmp => cmp,
},
cmp => cmp,
}
}
#[must_use]
/// # Compare (Only) Times.
///
/// Compare `self` to another `Utc2k` instance, ignoring the date
/// components of each.
///
/// ## Examples
///
/// ```
/// use utc2k::Utc2k;
/// use std::cmp::Ordering;
///
/// // The dates match, but the times are different.
/// let date1 = Utc2k::new(2020, 3, 15, 0, 0, 0);
/// let date2 = Utc2k::new(2020, 3, 15, 16, 30, 20);
/// assert_eq!(date1.cmp_time(date2), Ordering::Less);
///
/// // If the times match, it's what you'd expect.
/// let date3 = Utc2k::new(2022, 10, 31, 0, 0, 0);
/// assert_eq!(date1.cmp_time(date3), Ordering::Equal);
/// ```
pub fn cmp_time(self, other: Self) -> Ordering {
match self.hh.cmp(&other.hh) {
Ordering::Equal => match self.mm.cmp(&other.mm) {
Ordering::Equal => self.ss.cmp(&other.ss),
cmp => cmp,
},
cmp => cmp,
}
}
}
impl From<Utc2k> for u32 {
#[inline]
fn from(src: Utc2k) -> Self { src.unixtime() }
}
#[cfg(test)]
mod tests {
use super::*;
use brunch as _;
use time::OffsetDateTime;
#[cfg(not(miri))]
const SAMPLE_SIZE: usize = 1_000_000;
#[cfg(miri)]
const SAMPLE_SIZE: usize = 1000; // Miri runs way too slow for a million tests.
macro_rules! range_test {
($buf:ident, $i:ident, $format:ident) => (
let u = Utc2k::from($i);
let f = FmtUtc2k::from(u);
let c = OffsetDateTime::from_unix_timestamp($i as i64)
.expect("Unable to create time::OffsetDateTime.");
$buf.set_datetime(u);
// Make sure the timestamp comes back the same.
assert_eq!($i, u.unixtime(), "Timestamp out does not match timestamp in!");
// Make sure back-and-forth froms work as expected.
assert_eq!(Utc2k::from(f), u);
// Test RFC2822 back and forth.
assert_eq!(Some(u), Utc2k::from_rfc2822(u.to_rfc2822()));
assert_eq!(u.year(), c.year() as u16, "Year mismatch for unixtime {}", $i);
assert_eq!(u.month(), u8::from(c.month()), "Month mismatch for unixtime {}", $i);
assert_eq!(u.day(), c.day(), "Day mismatch for unixtime {}", $i);
assert_eq!(u.hour(), c.hour(), "Hour mismatch for unixtime {}", $i);
assert_eq!(u.minute(), c.minute(), "Minute mismatch for unixtime {}", $i);
assert_eq!(u.second(), c.second(), "Second mismatch for unixtime {}", $i);
assert_eq!(u.ordinal(), c.ordinal(), "Ordinal mismatch for unixtime {}", $i);
// Make sure the weekdays match.
assert_eq!(u.weekday().as_ref(), c.weekday().to_string());
// Test string conversion.
assert_eq!(
$buf.as_str(),
&c.format(&$format).expect("Unable to format datetime."),
"Date mismatch for unixtime {}",
$i
);
);
}
#[test]
#[ignore]
/// # Full Range Unixtime Test.
///
/// This compares our objects against `chrono` to ensure conversions line
/// up as expected for the supported unixtime range.
///
/// With billions of seconds to check, this takes a very long time to
/// complete.
fn full_unixtime() {
let mut buf = FmtUtc2k::default();
let format = time::format_description::parse(
"[year]-[month]-[day] [hour]:[minute]:[second]",
).expect("Unable to parse datetime format.");
for i in Utc2k::MIN_UNIXTIME..=Utc2k::MAX_UNIXTIME {
range_test!(buf, i, format);
}
}
#[test]
/// # Limited Range Unixtime Test.
///
/// This performs the same tests as [`full_unixtime`], but applies them
/// against 5 million random entries from the range rather than the whole
/// thing.
///
/// This provides reasonable coverage in reasonable time.
fn limited_unixtime() {
let mut buf = FmtUtc2k::default();
let format = time::format_description::parse(
"[year]-[month]-[day] [hour]:[minute]:[second]",
).expect("Unable to parse datetime format.");
let rng = fastrand::Rng::new();
for i in std::iter::repeat_with(|| rng.u32(Utc2k::MIN_UNIXTIME..=Utc2k::MAX_UNIXTIME)).take(SAMPLE_SIZE) {
range_test!(buf, i, format);
}
}
#[test]
/// # Leap Years.
fn leap_years() {
for y in 2000..2100 {
let date = Utc2k::new(y, 1, 1, 0, 0, 0);
assert_eq!(date.year(), y);
assert_eq!(
date.leap_year(),
y.trailing_zeros() >= 2 && ((y % 100) != 0 || (y % 400) == 0)
);
}
}
#[test]
/// # Test Ordering.
fn ordering() {
let expected = vec![
Utc2k::try_from("2000-01-01 00:00:00").unwrap(),
Utc2k::try_from("2010-05-31 01:02:03").unwrap(),
Utc2k::try_from("2010-05-31 02:02:03").unwrap(),
Utc2k::try_from("2020-10-10 10:10:10").unwrap(),
Utc2k::try_from("2020-10-10 10:11:10").unwrap(),
Utc2k::try_from("2020-10-10 10:11:11").unwrap(),
];
let mut shuffled = vec![
Utc2k::try_from("2010-05-31 01:02:03").unwrap(),
Utc2k::try_from("2020-10-10 10:11:11").unwrap(),
Utc2k::try_from("2010-05-31 02:02:03").unwrap(),
Utc2k::try_from("2000-01-01 00:00:00").unwrap(),
Utc2k::try_from("2020-10-10 10:11:10").unwrap(),
Utc2k::try_from("2020-10-10 10:10:10").unwrap(),
];
let f_expected: Vec<FmtUtc2k> = expected.iter().copied().map(FmtUtc2k::from).collect();
let mut f_shuffled: Vec<FmtUtc2k> = shuffled.iter().copied().map(FmtUtc2k::from).collect();
// Both sets should be not equal to start.
assert_ne!(expected, shuffled);
assert_ne!(f_expected, f_shuffled);
// Sort 'em.
shuffled.sort();
f_shuffled.sort();
// Now they should match.
assert_eq!(expected, shuffled);
assert_eq!(f_expected, f_shuffled);
}
}