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 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228
/*!
# Utc2k: Local Offsets
*/
use crate::{
FmtUtc2k,
Utc2k,
};
use once_cell::sync::Lazy;
use std::ops::Neg;
use tz::timezone::{
LocalTimeType,
TimeZone,
};
#[cfg_attr(feature = "docsrs", doc(cfg(feature = "local")))]
#[derive(Debug, Clone, Copy, Default, Eq, Hash, PartialEq)]
/// # Local Offset.
///
/// This struct attempts to determine the appropriate UTC offset for the local
/// timezone in a thread-safe manner, but **only for unix systems**.
///
/// Instantiation will never fail, though.
///
/// If the platform isn't supported or no offset can be determined, the
/// "offset" will simply be zero (i.e. as if it were UTC).
///
/// ## Examples
///
/// To obtain the _current_ local offset, use [`LocalOffset::now`].
///
/// To obtain the local offset for a specific unix timestamp, use its `From<u32>`
/// implementation instead.
///
/// ```
/// use utc2k::LocalOffset;
///
/// // The current offset.
/// let now = LocalOffset::now();
///
/// // The offset for a specific time.
/// let then = LocalOffset::from(946_684_800_u32);
/// ```
///
/// If all you want is the offset, you can grab that by calling
/// [`LocalOffset::offset`] afterward.
///
/// This struct can, however, also be used to _trick_ [`FmtUtc2k`] and [`Utc2k`]
/// into representing _local_ rather than UTC datetimes by using their
/// `From<LocalOffset>` implementations, or the [`FmtUtc2k::now_local`]/[`Utc2k::now_local`]
/// shorthands.
///
/// If you do this, however, it is worth noting that comparing tricked and
/// untricked objects makes no logical sense, so you shouldn't mix-and-match if
/// you need to test for equality or ordering.
///
/// Additionally, you should avoid localizing a datetime if you plan on using the
/// `to_rfc2822` or `to_rfc3339` formatting helpers. They always assume they're
/// representing a UTC timestamp, so will add the wrong suffix to their output
/// if your local offset is non-zero.
///
/// Other than that, the trick works perfectly well. ;)
///
/// ```
/// use utc2k::{LocalOffset, Utc2k};
///
/// let now_utc = Utc2k::now();
///
/// // These two are equivalent.
/// let now_local = Utc2k::from(LocalOffset::now());
/// let now_local = Utc2k::now_local();
/// ```
///
/// If you need to convert from a local timestamp into a UTC one, you can
/// leverage [`std::ops::Neg`] to invert the offset, like:
///
/// ```
/// use utc2k::{LocalOffset, Utc2k};
///
/// let offset = LocalOffset::from(946_684_800_u32);
/// let utc = Utc2k::from(-offset);
/// ```
pub struct LocalOffset {
unixtime: u32,
offset: i32,
}
impl From<u32> for LocalOffset {
#[inline]
fn from(unixtime: u32) -> Self {
let offset = offset(unixtime);
Self { unixtime, offset }
}
}
impl From<Utc2k> for LocalOffset {
#[inline]
/// # From `Utc2k`
///
/// Warning: this should only be used for `Utc2k` instances holding honest
/// UTC datetimes. If you call this on a tricked/local instance, the offset
/// will get applied twice!
fn from(src: Utc2k) -> Self { Self::from(src.unixtime()) }
}
impl Neg for LocalOffset {
type Output = Self;
fn neg(self) -> Self::Output {
if self.offset == i32::MIN {
Self {
unixtime: self.unixtime,
offset: i32::MAX,
}
}
else {
Self {
unixtime: self.unixtime,
offset: self.offset.wrapping_neg(),
}
}
}
}
impl LocalOffset {
#[inline]
#[must_use]
/// # Now.
///
/// Return the current, local offset. If no offset can be determined, UTC
/// will be assumed.
///
/// ## Examples
///
/// ```
/// let now = utc2k::LocalOffset::now();
/// ```
pub fn now() -> Self { Self::from(crate::unixtime()) }
#[must_use]
/// # Local Timestamp.
///
/// Return the sum of the unix timestamp and the offset.
pub const fn localtime(self) -> u32 {
if self.offset < 0 {
self.unixtime.saturating_sub(self.offset.abs() as u32)
}
else {
self.unixtime.saturating_add(self.offset.abs() as u32)
}
}
#[inline]
#[must_use]
/// # Offset.
///
/// Return the local offset (in seconds). Zero is returned if there is no
/// offset, or no offset could be determined.
pub const fn offset(self) -> i32 { self.offset }
#[inline]
#[must_use]
/// # Unixtime.
///
/// Return the unix timestamp this instance applies to (i.e. the value it
/// was seeded with).
///
/// ## Examples
///
/// ```
/// use utc2k::LocalOffset;
///
/// let offset = LocalOffset::from(946_684_800_u32);
/// assert_eq!(offset.unixtime(), 946_684_800_u32);
/// ```
pub const fn unixtime(self) -> u32 { self.unixtime }
}
impl From<LocalOffset> for i32 {
fn from(src: LocalOffset) -> Self { src.offset }
}
impl From<LocalOffset> for FmtUtc2k {
fn from(src: LocalOffset) -> Self { Self::from(Utc2k::from(src)) }
}
impl From<LocalOffset> for Utc2k {
#[inline]
fn from(src: LocalOffset) -> Self { Self::from(src.localtime()) }
}
/// # Offset From Unixtime.
///
/// The local timezone details are cached on the first run; subsequent method
/// calls will perform much faster.
fn offset(now: u32) -> i32 {
static TZ: Lazy<TimeZone> = Lazy::new(||
TimeZone::local().unwrap_or_else(|_| TimeZone::utc())
);
TZ.find_local_time_type(i64::from(now)).map_or(0, LocalTimeType::ut_offset)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn neg() {
let off = LocalOffset::now();
let off2 = -off;
assert_eq!(off, -off2); // We should be back to the original.
}
#[test]
fn now() {
// Unless we're one second away from a DST-type change, the offsets
// should match!
let now = crate::unixtime();
assert_eq!(LocalOffset::now().offset, LocalOffset::from(now).offset);
}
}