Vendor dependencies
Let's see how I like this workflow.
This commit is contained in:
parent
34d1830413
commit
9c435dc440
7500 changed files with 1665121 additions and 99 deletions
284
vendor/chrono/src/offset/fixed.rs
vendored
Normal file
284
vendor/chrono/src/offset/fixed.rs
vendored
Normal file
|
|
@ -0,0 +1,284 @@
|
|||
// This is a part of Chrono.
|
||||
// See README.md and LICENSE.txt for details.
|
||||
|
||||
//! The time zone which has a fixed offset from UTC.
|
||||
|
||||
use core::fmt;
|
||||
use core::ops::{Add, Sub};
|
||||
|
||||
use num_integer::div_mod_floor;
|
||||
#[cfg(feature = "rkyv")]
|
||||
use rkyv::{Archive, Deserialize, Serialize};
|
||||
|
||||
use super::{LocalResult, Offset, TimeZone};
|
||||
use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime};
|
||||
use crate::oldtime::Duration as OldDuration;
|
||||
use crate::DateTime;
|
||||
use crate::Timelike;
|
||||
|
||||
/// The time zone with fixed offset, from UTC-23:59:59 to UTC+23:59:59.
|
||||
///
|
||||
/// Using the [`TimeZone`](./trait.TimeZone.html) methods
|
||||
/// on a `FixedOffset` struct is the preferred way to construct
|
||||
/// `DateTime<FixedOffset>` instances. See the [`east`](#method.east) and
|
||||
/// [`west`](#method.west) methods for examples.
|
||||
#[derive(PartialEq, Eq, Hash, Copy, Clone)]
|
||||
#[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))]
|
||||
pub struct FixedOffset {
|
||||
local_minus_utc: i32,
|
||||
}
|
||||
|
||||
impl FixedOffset {
|
||||
/// Makes a new `FixedOffset` for the Eastern Hemisphere with given timezone difference.
|
||||
/// The negative `secs` means the Western Hemisphere.
|
||||
///
|
||||
/// Panics on the out-of-bound `secs`.
|
||||
#[deprecated(since = "0.4.23", note = "use `east_opt()` instead")]
|
||||
pub fn east(secs: i32) -> FixedOffset {
|
||||
FixedOffset::east_opt(secs).expect("FixedOffset::east out of bounds")
|
||||
}
|
||||
|
||||
/// Makes a new `FixedOffset` for the Eastern Hemisphere with given timezone difference.
|
||||
/// The negative `secs` means the Western Hemisphere.
|
||||
///
|
||||
/// Returns `None` on the out-of-bound `secs`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use chrono::{FixedOffset, TimeZone};
|
||||
/// let hour = 3600;
|
||||
/// let datetime = FixedOffset::east_opt(5 * hour).unwrap().ymd_opt(2016, 11, 08).unwrap()
|
||||
/// .and_hms_opt(0, 0, 0).unwrap();
|
||||
/// assert_eq!(&datetime.to_rfc3339(), "2016-11-08T00:00:00+05:00")
|
||||
/// ```
|
||||
pub fn east_opt(secs: i32) -> Option<FixedOffset> {
|
||||
if -86_400 < secs && secs < 86_400 {
|
||||
Some(FixedOffset { local_minus_utc: secs })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Makes a new `FixedOffset` for the Western Hemisphere with given timezone difference.
|
||||
/// The negative `secs` means the Eastern Hemisphere.
|
||||
///
|
||||
/// Panics on the out-of-bound `secs`.
|
||||
#[deprecated(since = "0.4.23", note = "use `west_opt()` instead")]
|
||||
pub fn west(secs: i32) -> FixedOffset {
|
||||
FixedOffset::west_opt(secs).expect("FixedOffset::west out of bounds")
|
||||
}
|
||||
|
||||
/// Makes a new `FixedOffset` for the Western Hemisphere with given timezone difference.
|
||||
/// The negative `secs` means the Eastern Hemisphere.
|
||||
///
|
||||
/// Returns `None` on the out-of-bound `secs`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use chrono::{FixedOffset, TimeZone};
|
||||
/// let hour = 3600;
|
||||
/// let datetime = FixedOffset::west_opt(5 * hour).unwrap().ymd_opt(2016, 11, 08).unwrap()
|
||||
/// .and_hms_opt(0, 0, 0).unwrap();
|
||||
/// assert_eq!(&datetime.to_rfc3339(), "2016-11-08T00:00:00-05:00")
|
||||
/// ```
|
||||
pub fn west_opt(secs: i32) -> Option<FixedOffset> {
|
||||
if -86_400 < secs && secs < 86_400 {
|
||||
Some(FixedOffset { local_minus_utc: -secs })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the number of seconds to add to convert from UTC to the local time.
|
||||
#[inline]
|
||||
pub fn local_minus_utc(&self) -> i32 {
|
||||
self.local_minus_utc
|
||||
}
|
||||
|
||||
/// Returns the number of seconds to add to convert from the local time to UTC.
|
||||
#[inline]
|
||||
pub fn utc_minus_local(&self) -> i32 {
|
||||
-self.local_minus_utc
|
||||
}
|
||||
}
|
||||
|
||||
impl TimeZone for FixedOffset {
|
||||
type Offset = FixedOffset;
|
||||
|
||||
fn from_offset(offset: &FixedOffset) -> FixedOffset {
|
||||
*offset
|
||||
}
|
||||
|
||||
fn offset_from_local_date(&self, _local: &NaiveDate) -> LocalResult<FixedOffset> {
|
||||
LocalResult::Single(*self)
|
||||
}
|
||||
fn offset_from_local_datetime(&self, _local: &NaiveDateTime) -> LocalResult<FixedOffset> {
|
||||
LocalResult::Single(*self)
|
||||
}
|
||||
|
||||
fn offset_from_utc_date(&self, _utc: &NaiveDate) -> FixedOffset {
|
||||
*self
|
||||
}
|
||||
fn offset_from_utc_datetime(&self, _utc: &NaiveDateTime) -> FixedOffset {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl Offset for FixedOffset {
|
||||
fn fix(&self) -> FixedOffset {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for FixedOffset {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let offset = self.local_minus_utc;
|
||||
let (sign, offset) = if offset < 0 { ('-', -offset) } else { ('+', offset) };
|
||||
let (mins, sec) = div_mod_floor(offset, 60);
|
||||
let (hour, min) = div_mod_floor(mins, 60);
|
||||
if sec == 0 {
|
||||
write!(f, "{}{:02}:{:02}", sign, hour, min)
|
||||
} else {
|
||||
write!(f, "{}{:02}:{:02}:{:02}", sign, hour, min, sec)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for FixedOffset {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
fmt::Debug::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "arbitrary")]
|
||||
impl arbitrary::Arbitrary<'_> for FixedOffset {
|
||||
fn arbitrary(u: &mut arbitrary::Unstructured) -> arbitrary::Result<FixedOffset> {
|
||||
let secs = u.int_in_range(-86_399..=86_399)?;
|
||||
let fixed_offset = FixedOffset::east_opt(secs)
|
||||
.expect("Could not generate a valid chrono::FixedOffset. It looks like implementation of Arbitrary for FixedOffset is erroneous.");
|
||||
Ok(fixed_offset)
|
||||
}
|
||||
}
|
||||
|
||||
// addition or subtraction of FixedOffset to/from Timelike values is the same as
|
||||
// adding or subtracting the offset's local_minus_utc value
|
||||
// but keep keeps the leap second information.
|
||||
// this should be implemented more efficiently, but for the time being, this is generic right now.
|
||||
|
||||
fn add_with_leapsecond<T>(lhs: &T, rhs: i32) -> T
|
||||
where
|
||||
T: Timelike + Add<OldDuration, Output = T>,
|
||||
{
|
||||
// extract and temporarily remove the fractional part and later recover it
|
||||
let nanos = lhs.nanosecond();
|
||||
let lhs = lhs.with_nanosecond(0).unwrap();
|
||||
(lhs + OldDuration::seconds(i64::from(rhs))).with_nanosecond(nanos).unwrap()
|
||||
}
|
||||
|
||||
impl Add<FixedOffset> for NaiveTime {
|
||||
type Output = NaiveTime;
|
||||
|
||||
#[inline]
|
||||
fn add(self, rhs: FixedOffset) -> NaiveTime {
|
||||
add_with_leapsecond(&self, rhs.local_minus_utc)
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<FixedOffset> for NaiveTime {
|
||||
type Output = NaiveTime;
|
||||
|
||||
#[inline]
|
||||
fn sub(self, rhs: FixedOffset) -> NaiveTime {
|
||||
add_with_leapsecond(&self, -rhs.local_minus_utc)
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<FixedOffset> for NaiveDateTime {
|
||||
type Output = NaiveDateTime;
|
||||
|
||||
#[inline]
|
||||
fn add(self, rhs: FixedOffset) -> NaiveDateTime {
|
||||
add_with_leapsecond(&self, rhs.local_minus_utc)
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<FixedOffset> for NaiveDateTime {
|
||||
type Output = NaiveDateTime;
|
||||
|
||||
#[inline]
|
||||
fn sub(self, rhs: FixedOffset) -> NaiveDateTime {
|
||||
add_with_leapsecond(&self, -rhs.local_minus_utc)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Tz: TimeZone> Add<FixedOffset> for DateTime<Tz> {
|
||||
type Output = DateTime<Tz>;
|
||||
|
||||
#[inline]
|
||||
fn add(self, rhs: FixedOffset) -> DateTime<Tz> {
|
||||
add_with_leapsecond(&self, rhs.local_minus_utc)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Tz: TimeZone> Sub<FixedOffset> for DateTime<Tz> {
|
||||
type Output = DateTime<Tz>;
|
||||
|
||||
#[inline]
|
||||
fn sub(self, rhs: FixedOffset) -> DateTime<Tz> {
|
||||
add_with_leapsecond(&self, -rhs.local_minus_utc)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::FixedOffset;
|
||||
use crate::offset::TimeZone;
|
||||
|
||||
#[test]
|
||||
fn test_date_extreme_offset() {
|
||||
// starting from 0.3 we don't have an offset exceeding one day.
|
||||
// this makes everything easier!
|
||||
assert_eq!(
|
||||
format!(
|
||||
"{:?}",
|
||||
FixedOffset::east_opt(86399)
|
||||
.unwrap()
|
||||
.with_ymd_and_hms(2012, 2, 29, 5, 6, 7)
|
||||
.unwrap()
|
||||
),
|
||||
"2012-02-29T05:06:07+23:59:59".to_string()
|
||||
);
|
||||
assert_eq!(
|
||||
format!(
|
||||
"{:?}",
|
||||
FixedOffset::east_opt(86399)
|
||||
.unwrap()
|
||||
.with_ymd_and_hms(2012, 2, 29, 5, 6, 7)
|
||||
.unwrap()
|
||||
),
|
||||
"2012-02-29T05:06:07+23:59:59".to_string()
|
||||
);
|
||||
assert_eq!(
|
||||
format!(
|
||||
"{:?}",
|
||||
FixedOffset::west_opt(86399)
|
||||
.unwrap()
|
||||
.with_ymd_and_hms(2012, 3, 4, 5, 6, 7)
|
||||
.unwrap()
|
||||
),
|
||||
"2012-03-04T05:06:07-23:59:59".to_string()
|
||||
);
|
||||
assert_eq!(
|
||||
format!(
|
||||
"{:?}",
|
||||
FixedOffset::west_opt(86399)
|
||||
.unwrap()
|
||||
.with_ymd_and_hms(2012, 3, 4, 5, 6, 7)
|
||||
.unwrap()
|
||||
),
|
||||
"2012-03-04T05:06:07-23:59:59".to_string()
|
||||
);
|
||||
}
|
||||
}
|
||||
260
vendor/chrono/src/offset/local/mod.rs
vendored
Normal file
260
vendor/chrono/src/offset/local/mod.rs
vendored
Normal file
|
|
@ -0,0 +1,260 @@
|
|||
// This is a part of Chrono.
|
||||
// See README.md and LICENSE.txt for details.
|
||||
|
||||
//! The local (system) time zone.
|
||||
|
||||
#[cfg(feature = "rkyv")]
|
||||
use rkyv::{Archive, Deserialize, Serialize};
|
||||
|
||||
use super::fixed::FixedOffset;
|
||||
use super::{LocalResult, TimeZone};
|
||||
use crate::naive::{NaiveDate, NaiveDateTime};
|
||||
#[allow(deprecated)]
|
||||
use crate::{Date, DateTime};
|
||||
|
||||
// we don't want `stub.rs` when the target_os is not wasi or emscripten
|
||||
// as we use js-sys to get the date instead
|
||||
#[cfg(all(
|
||||
not(unix),
|
||||
not(windows),
|
||||
not(all(
|
||||
target_arch = "wasm32",
|
||||
feature = "wasmbind",
|
||||
not(any(target_os = "emscripten", target_os = "wasi"))
|
||||
))
|
||||
))]
|
||||
#[path = "stub.rs"]
|
||||
mod inner;
|
||||
|
||||
#[cfg(unix)]
|
||||
#[path = "unix.rs"]
|
||||
mod inner;
|
||||
|
||||
#[cfg(windows)]
|
||||
#[path = "windows.rs"]
|
||||
mod inner;
|
||||
|
||||
#[cfg(unix)]
|
||||
mod tz_info;
|
||||
|
||||
/// The local timescale. This is implemented via the standard `time` crate.
|
||||
///
|
||||
/// Using the [`TimeZone`](./trait.TimeZone.html) methods
|
||||
/// on the Local struct is the preferred way to construct `DateTime<Local>`
|
||||
/// instances.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use chrono::{Local, DateTime, TimeZone};
|
||||
///
|
||||
/// let dt: DateTime<Local> = Local::now();
|
||||
/// let dt: DateTime<Local> = Local.timestamp(0, 0);
|
||||
/// ```
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))]
|
||||
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
||||
pub struct Local;
|
||||
|
||||
impl Local {
|
||||
/// Returns a `Date` which corresponds to the current date.
|
||||
#[deprecated(since = "0.4.23", note = "use `Local::now()` instead")]
|
||||
#[allow(deprecated)]
|
||||
pub fn today() -> Date<Local> {
|
||||
Local::now().date()
|
||||
}
|
||||
|
||||
/// Returns a `DateTime` which corresponds to the current date and time.
|
||||
#[cfg(not(all(
|
||||
target_arch = "wasm32",
|
||||
feature = "wasmbind",
|
||||
not(any(target_os = "emscripten", target_os = "wasi"))
|
||||
)))]
|
||||
pub fn now() -> DateTime<Local> {
|
||||
inner::now()
|
||||
}
|
||||
|
||||
/// Returns a `DateTime` which corresponds to the current date and time.
|
||||
#[cfg(all(
|
||||
target_arch = "wasm32",
|
||||
feature = "wasmbind",
|
||||
not(any(target_os = "emscripten", target_os = "wasi"))
|
||||
))]
|
||||
pub fn now() -> DateTime<Local> {
|
||||
use super::Utc;
|
||||
let now: DateTime<Utc> = super::Utc::now();
|
||||
|
||||
// Workaround missing timezone logic in `time` crate
|
||||
let offset =
|
||||
FixedOffset::west_opt((js_sys::Date::new_0().get_timezone_offset() as i32) * 60)
|
||||
.unwrap();
|
||||
DateTime::from_utc(now.naive_utc(), offset)
|
||||
}
|
||||
}
|
||||
|
||||
impl TimeZone for Local {
|
||||
type Offset = FixedOffset;
|
||||
|
||||
fn from_offset(_offset: &FixedOffset) -> Local {
|
||||
Local
|
||||
}
|
||||
|
||||
// they are easier to define in terms of the finished date and time unlike other offsets
|
||||
#[allow(deprecated)]
|
||||
fn offset_from_local_date(&self, local: &NaiveDate) -> LocalResult<FixedOffset> {
|
||||
self.from_local_date(local).map(|date| *date.offset())
|
||||
}
|
||||
|
||||
fn offset_from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult<FixedOffset> {
|
||||
self.from_local_datetime(local).map(|datetime| *datetime.offset())
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
fn offset_from_utc_date(&self, utc: &NaiveDate) -> FixedOffset {
|
||||
*self.from_utc_date(utc).offset()
|
||||
}
|
||||
|
||||
fn offset_from_utc_datetime(&self, utc: &NaiveDateTime) -> FixedOffset {
|
||||
*self.from_utc_datetime(utc).offset()
|
||||
}
|
||||
|
||||
// override them for avoiding redundant works
|
||||
#[allow(deprecated)]
|
||||
fn from_local_date(&self, local: &NaiveDate) -> LocalResult<Date<Local>> {
|
||||
// this sounds very strange, but required for keeping `TimeZone::ymd` sane.
|
||||
// in the other words, we use the offset at the local midnight
|
||||
// but keep the actual date unaltered (much like `FixedOffset`).
|
||||
let midnight = self.from_local_datetime(&local.and_hms_opt(0, 0, 0).unwrap());
|
||||
midnight.map(|datetime| Date::from_utc(*local, *datetime.offset()))
|
||||
}
|
||||
|
||||
#[cfg(all(
|
||||
target_arch = "wasm32",
|
||||
feature = "wasmbind",
|
||||
not(any(target_os = "emscripten", target_os = "wasi"))
|
||||
))]
|
||||
fn from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult<DateTime<Local>> {
|
||||
let mut local = local.clone();
|
||||
// Get the offset from the js runtime
|
||||
let offset =
|
||||
FixedOffset::west_opt((js_sys::Date::new_0().get_timezone_offset() as i32) * 60)
|
||||
.unwrap();
|
||||
local -= crate::Duration::seconds(offset.local_minus_utc() as i64);
|
||||
LocalResult::Single(DateTime::from_utc(local, offset))
|
||||
}
|
||||
|
||||
#[cfg(not(all(
|
||||
target_arch = "wasm32",
|
||||
feature = "wasmbind",
|
||||
not(any(target_os = "emscripten", target_os = "wasi"))
|
||||
)))]
|
||||
fn from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult<DateTime<Local>> {
|
||||
inner::naive_to_local(local, true)
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
fn from_utc_date(&self, utc: &NaiveDate) -> Date<Local> {
|
||||
let midnight = self.from_utc_datetime(&utc.and_hms_opt(0, 0, 0).unwrap());
|
||||
Date::from_utc(*utc, *midnight.offset())
|
||||
}
|
||||
|
||||
#[cfg(all(
|
||||
target_arch = "wasm32",
|
||||
feature = "wasmbind",
|
||||
not(any(target_os = "emscripten", target_os = "wasi"))
|
||||
))]
|
||||
fn from_utc_datetime(&self, utc: &NaiveDateTime) -> DateTime<Local> {
|
||||
// Get the offset from the js runtime
|
||||
let offset =
|
||||
FixedOffset::west_opt((js_sys::Date::new_0().get_timezone_offset() as i32) * 60)
|
||||
.unwrap();
|
||||
DateTime::from_utc(*utc, offset)
|
||||
}
|
||||
|
||||
#[cfg(not(all(
|
||||
target_arch = "wasm32",
|
||||
feature = "wasmbind",
|
||||
not(any(target_os = "emscripten", target_os = "wasi"))
|
||||
)))]
|
||||
fn from_utc_datetime(&self, utc: &NaiveDateTime) -> DateTime<Local> {
|
||||
// this is OK to unwrap as getting local time from a UTC
|
||||
// timestamp is never ambiguous
|
||||
inner::naive_to_local(utc, false).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Local;
|
||||
use crate::offset::TimeZone;
|
||||
use crate::{Datelike, Duration, Utc};
|
||||
|
||||
#[test]
|
||||
fn verify_correct_offsets() {
|
||||
let now = Local::now();
|
||||
let from_local = Local.from_local_datetime(&now.naive_local()).unwrap();
|
||||
let from_utc = Local.from_utc_datetime(&now.naive_utc());
|
||||
|
||||
assert_eq!(now.offset().local_minus_utc(), from_local.offset().local_minus_utc());
|
||||
assert_eq!(now.offset().local_minus_utc(), from_utc.offset().local_minus_utc());
|
||||
|
||||
assert_eq!(now, from_local);
|
||||
assert_eq!(now, from_utc);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_correct_offsets_distant_past() {
|
||||
// let distant_past = Local::now() - Duration::days(365 * 100);
|
||||
let distant_past = Local::now() - Duration::days(250 * 31);
|
||||
let from_local = Local.from_local_datetime(&distant_past.naive_local()).unwrap();
|
||||
let from_utc = Local.from_utc_datetime(&distant_past.naive_utc());
|
||||
|
||||
assert_eq!(distant_past.offset().local_minus_utc(), from_local.offset().local_minus_utc());
|
||||
assert_eq!(distant_past.offset().local_minus_utc(), from_utc.offset().local_minus_utc());
|
||||
|
||||
assert_eq!(distant_past, from_local);
|
||||
assert_eq!(distant_past, from_utc);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_correct_offsets_distant_future() {
|
||||
let distant_future = Local::now() + Duration::days(250 * 31);
|
||||
let from_local = Local.from_local_datetime(&distant_future.naive_local()).unwrap();
|
||||
let from_utc = Local.from_utc_datetime(&distant_future.naive_utc());
|
||||
|
||||
assert_eq!(
|
||||
distant_future.offset().local_minus_utc(),
|
||||
from_local.offset().local_minus_utc()
|
||||
);
|
||||
assert_eq!(distant_future.offset().local_minus_utc(), from_utc.offset().local_minus_utc());
|
||||
|
||||
assert_eq!(distant_future, from_local);
|
||||
assert_eq!(distant_future, from_utc);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_local_date_sanity_check() {
|
||||
// issue #27
|
||||
assert_eq!(Local.with_ymd_and_hms(2999, 12, 28, 0, 0, 0).unwrap().day(), 28);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_leap_second() {
|
||||
// issue #123
|
||||
let today = Utc::now().date_naive();
|
||||
|
||||
let dt = today.and_hms_milli_opt(1, 2, 59, 1000).unwrap();
|
||||
let timestr = dt.time().to_string();
|
||||
// the OS API may or may not support the leap second,
|
||||
// but there are only two sensible options.
|
||||
assert!(timestr == "01:02:60" || timestr == "01:03:00", "unexpected timestr {:?}", timestr);
|
||||
|
||||
let dt = today.and_hms_milli_opt(1, 2, 3, 1234).unwrap();
|
||||
let timestr = dt.time().to_string();
|
||||
assert!(
|
||||
timestr == "01:02:03.234" || timestr == "01:02:04.234",
|
||||
"unexpected timestr {:?}",
|
||||
timestr
|
||||
);
|
||||
}
|
||||
}
|
||||
236
vendor/chrono/src/offset/local/stub.rs
vendored
Normal file
236
vendor/chrono/src/offset/local/stub.rs
vendored
Normal file
|
|
@ -0,0 +1,236 @@
|
|||
// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution and at
|
||||
// http://rust-lang.org/COPYRIGHT.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use super::{FixedOffset, Local};
|
||||
use crate::{DateTime, Datelike, LocalResult, NaiveDate, NaiveDateTime, NaiveTime, Timelike};
|
||||
|
||||
pub(super) fn now() -> DateTime<Local> {
|
||||
tm_to_datetime(Timespec::now().local())
|
||||
}
|
||||
|
||||
/// Converts a local `NaiveDateTime` to the `time::Timespec`.
|
||||
#[cfg(not(all(
|
||||
target_arch = "wasm32",
|
||||
feature = "wasmbind",
|
||||
not(any(target_os = "emscripten", target_os = "wasi"))
|
||||
)))]
|
||||
pub(super) fn naive_to_local(d: &NaiveDateTime, local: bool) -> LocalResult<DateTime<Local>> {
|
||||
let tm = Tm {
|
||||
tm_sec: d.second() as i32,
|
||||
tm_min: d.minute() as i32,
|
||||
tm_hour: d.hour() as i32,
|
||||
tm_mday: d.day() as i32,
|
||||
tm_mon: d.month0() as i32, // yes, C is that strange...
|
||||
tm_year: d.year() - 1900, // this doesn't underflow, we know that d is `NaiveDateTime`.
|
||||
tm_wday: 0, // to_local ignores this
|
||||
tm_yday: 0, // and this
|
||||
tm_isdst: -1,
|
||||
// This seems pretty fake?
|
||||
tm_utcoff: if local { 1 } else { 0 },
|
||||
// do not set this, OS APIs are heavily inconsistent in terms of leap second handling
|
||||
tm_nsec: 0,
|
||||
};
|
||||
|
||||
let spec = Timespec {
|
||||
sec: match local {
|
||||
false => utc_tm_to_time(&tm),
|
||||
true => local_tm_to_time(&tm),
|
||||
},
|
||||
nsec: tm.tm_nsec,
|
||||
};
|
||||
|
||||
// Adjust for leap seconds
|
||||
let mut tm = spec.local();
|
||||
assert_eq!(tm.tm_nsec, 0);
|
||||
tm.tm_nsec = d.nanosecond() as i32;
|
||||
|
||||
LocalResult::Single(tm_to_datetime(tm))
|
||||
}
|
||||
|
||||
/// Converts a `time::Tm` struct into the timezone-aware `DateTime`.
|
||||
/// This assumes that `time` is working correctly, i.e. any error is fatal.
|
||||
#[cfg(not(all(
|
||||
target_arch = "wasm32",
|
||||
feature = "wasmbind",
|
||||
not(any(target_os = "emscripten", target_os = "wasi"))
|
||||
)))]
|
||||
fn tm_to_datetime(mut tm: Tm) -> DateTime<Local> {
|
||||
if tm.tm_sec >= 60 {
|
||||
tm.tm_nsec += (tm.tm_sec - 59) * 1_000_000_000;
|
||||
tm.tm_sec = 59;
|
||||
}
|
||||
|
||||
let date = NaiveDate::from_yo(tm.tm_year + 1900, tm.tm_yday as u32 + 1);
|
||||
let time = NaiveTime::from_hms_nano(
|
||||
tm.tm_hour as u32,
|
||||
tm.tm_min as u32,
|
||||
tm.tm_sec as u32,
|
||||
tm.tm_nsec as u32,
|
||||
);
|
||||
|
||||
let offset = FixedOffset::east_opt(tm.tm_utcoff).unwrap();
|
||||
DateTime::from_utc(date.and_time(time) - offset, offset)
|
||||
}
|
||||
|
||||
/// A record specifying a time value in seconds and nanoseconds, where
|
||||
/// nanoseconds represent the offset from the given second.
|
||||
///
|
||||
/// For example a timespec of 1.2 seconds after the beginning of the epoch would
|
||||
/// be represented as {sec: 1, nsec: 200000000}.
|
||||
struct Timespec {
|
||||
sec: i64,
|
||||
nsec: i32,
|
||||
}
|
||||
|
||||
impl Timespec {
|
||||
/// Constructs a timespec representing the current time in UTC.
|
||||
fn now() -> Timespec {
|
||||
let st =
|
||||
SystemTime::now().duration_since(UNIX_EPOCH).expect("system time before Unix epoch");
|
||||
Timespec { sec: st.as_secs() as i64, nsec: st.subsec_nanos() as i32 }
|
||||
}
|
||||
|
||||
/// Converts this timespec into the system's local time.
|
||||
fn local(self) -> Tm {
|
||||
let mut tm = Tm {
|
||||
tm_sec: 0,
|
||||
tm_min: 0,
|
||||
tm_hour: 0,
|
||||
tm_mday: 0,
|
||||
tm_mon: 0,
|
||||
tm_year: 0,
|
||||
tm_wday: 0,
|
||||
tm_yday: 0,
|
||||
tm_isdst: 0,
|
||||
tm_utcoff: 0,
|
||||
tm_nsec: 0,
|
||||
};
|
||||
time_to_local_tm(self.sec, &mut tm);
|
||||
tm.tm_nsec = self.nsec;
|
||||
tm
|
||||
}
|
||||
}
|
||||
|
||||
/// Holds a calendar date and time broken down into its components (year, month,
|
||||
/// day, and so on), also called a broken-down time value.
|
||||
// FIXME: use c_int instead of i32?
|
||||
#[repr(C)]
|
||||
pub(super) struct Tm {
|
||||
/// Seconds after the minute - [0, 60]
|
||||
tm_sec: i32,
|
||||
|
||||
/// Minutes after the hour - [0, 59]
|
||||
tm_min: i32,
|
||||
|
||||
/// Hours after midnight - [0, 23]
|
||||
tm_hour: i32,
|
||||
|
||||
/// Day of the month - [1, 31]
|
||||
tm_mday: i32,
|
||||
|
||||
/// Months since January - [0, 11]
|
||||
tm_mon: i32,
|
||||
|
||||
/// Years since 1900
|
||||
tm_year: i32,
|
||||
|
||||
/// Days since Sunday - [0, 6]. 0 = Sunday, 1 = Monday, ..., 6 = Saturday.
|
||||
tm_wday: i32,
|
||||
|
||||
/// Days since January 1 - [0, 365]
|
||||
tm_yday: i32,
|
||||
|
||||
/// Daylight Saving Time flag.
|
||||
///
|
||||
/// This value is positive if Daylight Saving Time is in effect, zero if
|
||||
/// Daylight Saving Time is not in effect, and negative if this information
|
||||
/// is not available.
|
||||
tm_isdst: i32,
|
||||
|
||||
/// Identifies the time zone that was used to compute this broken-down time
|
||||
/// value, including any adjustment for Daylight Saving Time. This is the
|
||||
/// number of seconds east of UTC. For example, for U.S. Pacific Daylight
|
||||
/// Time, the value is `-7*60*60 = -25200`.
|
||||
tm_utcoff: i32,
|
||||
|
||||
/// Nanoseconds after the second - [0, 10<sup>9</sup> - 1]
|
||||
tm_nsec: i32,
|
||||
}
|
||||
|
||||
fn time_to_tm(ts: i64, tm: &mut Tm) {
|
||||
let leapyear = |year| -> bool { year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) };
|
||||
|
||||
static YTAB: [[i64; 12]; 2] = [
|
||||
[31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
|
||||
[31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
|
||||
];
|
||||
|
||||
let mut year = 1970;
|
||||
|
||||
let dayclock = ts % 86400;
|
||||
let mut dayno = ts / 86400;
|
||||
|
||||
tm.tm_sec = (dayclock % 60) as i32;
|
||||
tm.tm_min = ((dayclock % 3600) / 60) as i32;
|
||||
tm.tm_hour = (dayclock / 3600) as i32;
|
||||
tm.tm_wday = ((dayno + 4) % 7) as i32;
|
||||
loop {
|
||||
let yearsize = if leapyear(year) { 366 } else { 365 };
|
||||
if dayno >= yearsize {
|
||||
dayno -= yearsize;
|
||||
year += 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
tm.tm_year = (year - 1900) as i32;
|
||||
tm.tm_yday = dayno as i32;
|
||||
let mut mon = 0;
|
||||
while dayno >= YTAB[if leapyear(year) { 1 } else { 0 }][mon] {
|
||||
dayno -= YTAB[if leapyear(year) { 1 } else { 0 }][mon];
|
||||
mon += 1;
|
||||
}
|
||||
tm.tm_mon = mon as i32;
|
||||
tm.tm_mday = dayno as i32 + 1;
|
||||
tm.tm_isdst = 0;
|
||||
}
|
||||
|
||||
fn tm_to_time(tm: &Tm) -> i64 {
|
||||
let mut y = tm.tm_year as i64 + 1900;
|
||||
let mut m = tm.tm_mon as i64 + 1;
|
||||
if m <= 2 {
|
||||
y -= 1;
|
||||
m += 12;
|
||||
}
|
||||
let d = tm.tm_mday as i64;
|
||||
let h = tm.tm_hour as i64;
|
||||
let mi = tm.tm_min as i64;
|
||||
let s = tm.tm_sec as i64;
|
||||
(365 * y + y / 4 - y / 100 + y / 400 + 3 * (m + 1) / 5 + 30 * m + d - 719561) * 86400
|
||||
+ 3600 * h
|
||||
+ 60 * mi
|
||||
+ s
|
||||
}
|
||||
|
||||
pub(super) fn time_to_local_tm(sec: i64, tm: &mut Tm) {
|
||||
// FIXME: Add timezone logic
|
||||
time_to_tm(sec, tm);
|
||||
}
|
||||
|
||||
pub(super) fn utc_tm_to_time(tm: &Tm) -> i64 {
|
||||
tm_to_time(tm)
|
||||
}
|
||||
|
||||
pub(super) fn local_tm_to_time(tm: &Tm) -> i64 {
|
||||
// FIXME: Add timezone logic
|
||||
tm_to_time(tm)
|
||||
}
|
||||
131
vendor/chrono/src/offset/local/tz_info/mod.rs
vendored
Normal file
131
vendor/chrono/src/offset/local/tz_info/mod.rs
vendored
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
#![deny(missing_docs)]
|
||||
#![allow(dead_code)]
|
||||
#![warn(unreachable_pub)]
|
||||
|
||||
use std::num::ParseIntError;
|
||||
use std::str::Utf8Error;
|
||||
use std::time::SystemTimeError;
|
||||
use std::{error, fmt, io};
|
||||
|
||||
mod timezone;
|
||||
pub(crate) use timezone::TimeZone;
|
||||
|
||||
mod parser;
|
||||
mod rule;
|
||||
|
||||
/// Unified error type for everything in the crate
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum Error {
|
||||
/// Date time error
|
||||
DateTime(&'static str),
|
||||
/// Local time type search error
|
||||
FindLocalTimeType(&'static str),
|
||||
/// Local time type error
|
||||
LocalTimeType(&'static str),
|
||||
/// Invalid slice for integer conversion
|
||||
InvalidSlice(&'static str),
|
||||
/// Invalid Tzif file
|
||||
InvalidTzFile(&'static str),
|
||||
/// Invalid TZ string
|
||||
InvalidTzString(&'static str),
|
||||
/// I/O error
|
||||
Io(io::Error),
|
||||
/// Out of range error
|
||||
OutOfRange(&'static str),
|
||||
/// Integer parsing error
|
||||
ParseInt(ParseIntError),
|
||||
/// Date time projection error
|
||||
ProjectDateTime(&'static str),
|
||||
/// System time error
|
||||
SystemTime(SystemTimeError),
|
||||
/// Time zone error
|
||||
TimeZone(&'static str),
|
||||
/// Transition rule error
|
||||
TransitionRule(&'static str),
|
||||
/// Unsupported Tzif file
|
||||
UnsupportedTzFile(&'static str),
|
||||
/// Unsupported TZ string
|
||||
UnsupportedTzString(&'static str),
|
||||
/// UTF-8 error
|
||||
Utf8(Utf8Error),
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
use Error::*;
|
||||
match self {
|
||||
DateTime(error) => write!(f, "invalid date time: {}", error),
|
||||
FindLocalTimeType(error) => error.fmt(f),
|
||||
LocalTimeType(error) => write!(f, "invalid local time type: {}", error),
|
||||
InvalidSlice(error) => error.fmt(f),
|
||||
InvalidTzString(error) => write!(f, "invalid TZ string: {}", error),
|
||||
InvalidTzFile(error) => error.fmt(f),
|
||||
Io(error) => error.fmt(f),
|
||||
OutOfRange(error) => error.fmt(f),
|
||||
ParseInt(error) => error.fmt(f),
|
||||
ProjectDateTime(error) => error.fmt(f),
|
||||
SystemTime(error) => error.fmt(f),
|
||||
TransitionRule(error) => write!(f, "invalid transition rule: {}", error),
|
||||
TimeZone(error) => write!(f, "invalid time zone: {}", error),
|
||||
UnsupportedTzFile(error) => error.fmt(f),
|
||||
UnsupportedTzString(error) => write!(f, "unsupported TZ string: {}", error),
|
||||
Utf8(error) => error.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for Error {}
|
||||
|
||||
impl From<io::Error> for Error {
|
||||
fn from(error: io::Error) -> Self {
|
||||
Error::Io(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParseIntError> for Error {
|
||||
fn from(error: ParseIntError) -> Self {
|
||||
Error::ParseInt(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SystemTimeError> for Error {
|
||||
fn from(error: SystemTimeError) -> Self {
|
||||
Error::SystemTime(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Utf8Error> for Error {
|
||||
fn from(error: Utf8Error) -> Self {
|
||||
Error::Utf8(error)
|
||||
}
|
||||
}
|
||||
|
||||
// MSRV: 1.38
|
||||
#[inline]
|
||||
fn rem_euclid(v: i64, rhs: i64) -> i64 {
|
||||
let r = v % rhs;
|
||||
if r < 0 {
|
||||
if rhs < 0 {
|
||||
r - rhs
|
||||
} else {
|
||||
r + rhs
|
||||
}
|
||||
} else {
|
||||
r
|
||||
}
|
||||
}
|
||||
|
||||
/// Number of hours in one day
|
||||
const HOURS_PER_DAY: i64 = 24;
|
||||
/// Number of seconds in one hour
|
||||
const SECONDS_PER_HOUR: i64 = 3600;
|
||||
/// Number of seconds in one day
|
||||
const SECONDS_PER_DAY: i64 = SECONDS_PER_HOUR * HOURS_PER_DAY;
|
||||
/// Number of days in one week
|
||||
const DAYS_PER_WEEK: i64 = 7;
|
||||
|
||||
/// Month days in a normal year
|
||||
const DAY_IN_MONTHS_NORMAL_YEAR: [i64; 12] = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
||||
/// Cumulated month days in a normal year
|
||||
const CUMUL_DAY_IN_MONTHS_NORMAL_YEAR: [i64; 12] =
|
||||
[0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
|
||||
334
vendor/chrono/src/offset/local/tz_info/parser.rs
vendored
Normal file
334
vendor/chrono/src/offset/local/tz_info/parser.rs
vendored
Normal file
|
|
@ -0,0 +1,334 @@
|
|||
use std::io::{self, ErrorKind};
|
||||
use std::iter;
|
||||
use std::num::ParseIntError;
|
||||
use std::str::{self, FromStr};
|
||||
|
||||
use super::rule::TransitionRule;
|
||||
use super::timezone::{LeapSecond, LocalTimeType, TimeZone, Transition};
|
||||
use super::Error;
|
||||
|
||||
#[allow(clippy::map_clone)] // MSRV: 1.36
|
||||
pub(super) fn parse(bytes: &[u8]) -> Result<TimeZone, Error> {
|
||||
let mut cursor = Cursor::new(bytes);
|
||||
let state = State::new(&mut cursor, true)?;
|
||||
let (state, footer) = match state.header.version {
|
||||
Version::V1 => match cursor.is_empty() {
|
||||
true => (state, None),
|
||||
false => {
|
||||
return Err(Error::InvalidTzFile("remaining data after end of TZif v1 data block"))
|
||||
}
|
||||
},
|
||||
Version::V2 | Version::V3 => {
|
||||
let state = State::new(&mut cursor, false)?;
|
||||
(state, Some(cursor.remaining()))
|
||||
}
|
||||
};
|
||||
|
||||
let mut transitions = Vec::with_capacity(state.header.transition_count);
|
||||
for (arr_time, &local_time_type_index) in
|
||||
state.transition_times.chunks_exact(state.time_size).zip(state.transition_types)
|
||||
{
|
||||
let unix_leap_time =
|
||||
state.parse_time(&arr_time[0..state.time_size], state.header.version)?;
|
||||
let local_time_type_index = local_time_type_index as usize;
|
||||
transitions.push(Transition::new(unix_leap_time, local_time_type_index));
|
||||
}
|
||||
|
||||
let mut local_time_types = Vec::with_capacity(state.header.type_count);
|
||||
for arr in state.local_time_types.chunks_exact(6) {
|
||||
let ut_offset = read_be_i32(&arr[..4])?;
|
||||
|
||||
let is_dst = match arr[4] {
|
||||
0 => false,
|
||||
1 => true,
|
||||
_ => return Err(Error::InvalidTzFile("invalid DST indicator")),
|
||||
};
|
||||
|
||||
let char_index = arr[5] as usize;
|
||||
if char_index >= state.header.char_count {
|
||||
return Err(Error::InvalidTzFile("invalid time zone name char index"));
|
||||
}
|
||||
|
||||
let position = match state.names[char_index..].iter().position(|&c| c == b'\0') {
|
||||
Some(position) => position,
|
||||
None => return Err(Error::InvalidTzFile("invalid time zone name char index")),
|
||||
};
|
||||
|
||||
let name = &state.names[char_index..char_index + position];
|
||||
let name = if !name.is_empty() { Some(name) } else { None };
|
||||
local_time_types.push(LocalTimeType::new(ut_offset, is_dst, name)?);
|
||||
}
|
||||
|
||||
let mut leap_seconds = Vec::with_capacity(state.header.leap_count);
|
||||
for arr in state.leap_seconds.chunks_exact(state.time_size + 4) {
|
||||
let unix_leap_time = state.parse_time(&arr[0..state.time_size], state.header.version)?;
|
||||
let correction = read_be_i32(&arr[state.time_size..state.time_size + 4])?;
|
||||
leap_seconds.push(LeapSecond::new(unix_leap_time, correction));
|
||||
}
|
||||
|
||||
let std_walls_iter = state.std_walls.iter().map(|&i| i).chain(iter::repeat(0));
|
||||
let ut_locals_iter = state.ut_locals.iter().map(|&i| i).chain(iter::repeat(0));
|
||||
if std_walls_iter.zip(ut_locals_iter).take(state.header.type_count).any(|pair| pair == (0, 1)) {
|
||||
return Err(Error::InvalidTzFile(
|
||||
"invalid couple of standard/wall and UT/local indicators",
|
||||
));
|
||||
}
|
||||
|
||||
let extra_rule = match footer {
|
||||
Some(footer) => {
|
||||
let footer = str::from_utf8(footer)?;
|
||||
if !(footer.starts_with('\n') && footer.ends_with('\n')) {
|
||||
return Err(Error::InvalidTzFile("invalid footer"));
|
||||
}
|
||||
|
||||
let tz_string = footer.trim_matches(|c: char| c.is_ascii_whitespace());
|
||||
if tz_string.starts_with(':') || tz_string.contains('\0') {
|
||||
return Err(Error::InvalidTzFile("invalid footer"));
|
||||
}
|
||||
|
||||
match tz_string.is_empty() {
|
||||
true => None,
|
||||
false => Some(TransitionRule::from_tz_string(
|
||||
tz_string.as_bytes(),
|
||||
state.header.version == Version::V3,
|
||||
)?),
|
||||
}
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
|
||||
TimeZone::new(transitions, local_time_types, leap_seconds, extra_rule)
|
||||
}
|
||||
|
||||
/// TZif data blocks
|
||||
struct State<'a> {
|
||||
header: Header,
|
||||
/// Time size in bytes
|
||||
time_size: usize,
|
||||
/// Transition times data block
|
||||
transition_times: &'a [u8],
|
||||
/// Transition types data block
|
||||
transition_types: &'a [u8],
|
||||
/// Local time types data block
|
||||
local_time_types: &'a [u8],
|
||||
/// Time zone names data block
|
||||
names: &'a [u8],
|
||||
/// Leap seconds data block
|
||||
leap_seconds: &'a [u8],
|
||||
/// UT/local indicators data block
|
||||
std_walls: &'a [u8],
|
||||
/// Standard/wall indicators data block
|
||||
ut_locals: &'a [u8],
|
||||
}
|
||||
|
||||
impl<'a> State<'a> {
|
||||
/// Read TZif data blocks
|
||||
fn new(cursor: &mut Cursor<'a>, first: bool) -> Result<Self, Error> {
|
||||
let header = Header::new(cursor)?;
|
||||
let time_size = match first {
|
||||
true => 4, // We always parse V1 first
|
||||
false => 8,
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
time_size,
|
||||
transition_times: cursor.read_exact(header.transition_count * time_size)?,
|
||||
transition_types: cursor.read_exact(header.transition_count)?,
|
||||
local_time_types: cursor.read_exact(header.type_count * 6)?,
|
||||
names: cursor.read_exact(header.char_count)?,
|
||||
leap_seconds: cursor.read_exact(header.leap_count * (time_size + 4))?,
|
||||
std_walls: cursor.read_exact(header.std_wall_count)?,
|
||||
ut_locals: cursor.read_exact(header.ut_local_count)?,
|
||||
header,
|
||||
})
|
||||
}
|
||||
|
||||
/// Parse time values
|
||||
fn parse_time(&self, arr: &[u8], version: Version) -> Result<i64, Error> {
|
||||
match version {
|
||||
Version::V1 => Ok(read_be_i32(&arr[..4])?.into()),
|
||||
Version::V2 | Version::V3 => read_be_i64(arr),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// TZif header
|
||||
#[derive(Debug)]
|
||||
struct Header {
|
||||
/// TZif version
|
||||
version: Version,
|
||||
/// Number of UT/local indicators
|
||||
ut_local_count: usize,
|
||||
/// Number of standard/wall indicators
|
||||
std_wall_count: usize,
|
||||
/// Number of leap-second records
|
||||
leap_count: usize,
|
||||
/// Number of transition times
|
||||
transition_count: usize,
|
||||
/// Number of local time type records
|
||||
type_count: usize,
|
||||
/// Number of time zone names bytes
|
||||
char_count: usize,
|
||||
}
|
||||
|
||||
impl Header {
|
||||
fn new(cursor: &mut Cursor) -> Result<Self, Error> {
|
||||
let magic = cursor.read_exact(4)?;
|
||||
if magic != *b"TZif" {
|
||||
return Err(Error::InvalidTzFile("invalid magic number"));
|
||||
}
|
||||
|
||||
let version = match cursor.read_exact(1)? {
|
||||
[0x00] => Version::V1,
|
||||
[0x32] => Version::V2,
|
||||
[0x33] => Version::V3,
|
||||
_ => return Err(Error::UnsupportedTzFile("unsupported TZif version")),
|
||||
};
|
||||
|
||||
cursor.read_exact(15)?;
|
||||
let ut_local_count = cursor.read_be_u32()?;
|
||||
let std_wall_count = cursor.read_be_u32()?;
|
||||
let leap_count = cursor.read_be_u32()?;
|
||||
let transition_count = cursor.read_be_u32()?;
|
||||
let type_count = cursor.read_be_u32()?;
|
||||
let char_count = cursor.read_be_u32()?;
|
||||
|
||||
if !(type_count != 0
|
||||
&& char_count != 0
|
||||
&& (ut_local_count == 0 || ut_local_count == type_count)
|
||||
&& (std_wall_count == 0 || std_wall_count == type_count))
|
||||
{
|
||||
return Err(Error::InvalidTzFile("invalid header"));
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
version,
|
||||
ut_local_count: ut_local_count as usize,
|
||||
std_wall_count: std_wall_count as usize,
|
||||
leap_count: leap_count as usize,
|
||||
transition_count: transition_count as usize,
|
||||
type_count: type_count as usize,
|
||||
char_count: char_count as usize,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A `Cursor` contains a slice of a buffer and a read count.
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub(crate) struct Cursor<'a> {
|
||||
/// Slice representing the remaining data to be read
|
||||
remaining: &'a [u8],
|
||||
/// Number of already read bytes
|
||||
read_count: usize,
|
||||
}
|
||||
|
||||
impl<'a> Cursor<'a> {
|
||||
/// Construct a new `Cursor` from remaining data
|
||||
pub(crate) fn new(remaining: &'a [u8]) -> Self {
|
||||
Self { remaining, read_count: 0 }
|
||||
}
|
||||
|
||||
pub(crate) fn peek(&self) -> Option<&u8> {
|
||||
self.remaining().first()
|
||||
}
|
||||
|
||||
/// Returns remaining data
|
||||
pub(crate) fn remaining(&self) -> &'a [u8] {
|
||||
self.remaining
|
||||
}
|
||||
|
||||
/// Returns `true` if data is remaining
|
||||
pub(crate) fn is_empty(&self) -> bool {
|
||||
self.remaining.is_empty()
|
||||
}
|
||||
|
||||
pub(crate) fn read_be_u32(&mut self) -> Result<u32, Error> {
|
||||
let mut buf = [0; 4];
|
||||
buf.copy_from_slice(self.read_exact(4)?);
|
||||
Ok(u32::from_be_bytes(buf))
|
||||
}
|
||||
|
||||
/// Read exactly `count` bytes, reducing remaining data and incrementing read count
|
||||
pub(crate) fn read_exact(&mut self, count: usize) -> Result<&'a [u8], io::Error> {
|
||||
match (self.remaining.get(..count), self.remaining.get(count..)) {
|
||||
(Some(result), Some(remaining)) => {
|
||||
self.remaining = remaining;
|
||||
self.read_count += count;
|
||||
Ok(result)
|
||||
}
|
||||
_ => Err(io::Error::from(ErrorKind::UnexpectedEof)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Read bytes and compare them to the provided tag
|
||||
pub(crate) fn read_tag(&mut self, tag: &[u8]) -> Result<(), io::Error> {
|
||||
if self.read_exact(tag.len())? == tag {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(io::Error::from(ErrorKind::InvalidData))
|
||||
}
|
||||
}
|
||||
|
||||
/// Read bytes if the remaining data is prefixed by the provided tag
|
||||
pub(crate) fn read_optional_tag(&mut self, tag: &[u8]) -> Result<bool, io::Error> {
|
||||
if self.remaining.starts_with(tag) {
|
||||
self.read_exact(tag.len())?;
|
||||
Ok(true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
/// Read bytes as long as the provided predicate is true
|
||||
pub(crate) fn read_while<F: Fn(&u8) -> bool>(&mut self, f: F) -> Result<&'a [u8], io::Error> {
|
||||
match self.remaining.iter().position(|x| !f(x)) {
|
||||
None => self.read_exact(self.remaining.len()),
|
||||
Some(position) => self.read_exact(position),
|
||||
}
|
||||
}
|
||||
|
||||
// Parse an integer out of the ASCII digits
|
||||
pub(crate) fn read_int<T: FromStr<Err = ParseIntError>>(&mut self) -> Result<T, Error> {
|
||||
let bytes = self.read_while(u8::is_ascii_digit)?;
|
||||
Ok(str::from_utf8(bytes)?.parse()?)
|
||||
}
|
||||
|
||||
/// Read bytes until the provided predicate is true
|
||||
pub(crate) fn read_until<F: Fn(&u8) -> bool>(&mut self, f: F) -> Result<&'a [u8], io::Error> {
|
||||
match self.remaining.iter().position(f) {
|
||||
None => self.read_exact(self.remaining.len()),
|
||||
Some(position) => self.read_exact(position),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn read_be_i32(bytes: &[u8]) -> Result<i32, Error> {
|
||||
if bytes.len() != 4 {
|
||||
return Err(Error::InvalidSlice("too short for i32"));
|
||||
}
|
||||
|
||||
let mut buf = [0; 4];
|
||||
buf.copy_from_slice(bytes);
|
||||
Ok(i32::from_be_bytes(buf))
|
||||
}
|
||||
|
||||
pub(crate) fn read_be_i64(bytes: &[u8]) -> Result<i64, Error> {
|
||||
if bytes.len() != 8 {
|
||||
return Err(Error::InvalidSlice("too short for i64"));
|
||||
}
|
||||
|
||||
let mut buf = [0; 8];
|
||||
buf.copy_from_slice(bytes);
|
||||
Ok(i64::from_be_bytes(buf))
|
||||
}
|
||||
|
||||
/// TZif version
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
enum Version {
|
||||
/// Version 1
|
||||
V1,
|
||||
/// Version 2
|
||||
V2,
|
||||
/// Version 3
|
||||
V3,
|
||||
}
|
||||
1046
vendor/chrono/src/offset/local/tz_info/rule.rs
vendored
Normal file
1046
vendor/chrono/src/offset/local/tz_info/rule.rs
vendored
Normal file
File diff suppressed because it is too large
Load diff
904
vendor/chrono/src/offset/local/tz_info/timezone.rs
vendored
Normal file
904
vendor/chrono/src/offset/local/tz_info/timezone.rs
vendored
Normal file
|
|
@ -0,0 +1,904 @@
|
|||
//! Types related to a time zone.
|
||||
|
||||
use std::fs::{self, File};
|
||||
use std::io::{self, Read};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::{cmp::Ordering, fmt, str};
|
||||
|
||||
use super::rule::{AlternateTime, TransitionRule};
|
||||
use super::{parser, Error, DAYS_PER_WEEK, SECONDS_PER_DAY};
|
||||
|
||||
/// Time zone
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub(crate) struct TimeZone {
|
||||
/// List of transitions
|
||||
transitions: Vec<Transition>,
|
||||
/// List of local time types (cannot be empty)
|
||||
local_time_types: Vec<LocalTimeType>,
|
||||
/// List of leap seconds
|
||||
leap_seconds: Vec<LeapSecond>,
|
||||
/// Extra transition rule applicable after the last transition
|
||||
extra_rule: Option<TransitionRule>,
|
||||
}
|
||||
|
||||
impl TimeZone {
|
||||
/// Returns local time zone.
|
||||
///
|
||||
/// This method in not supported on non-UNIX platforms, and returns the UTC time zone instead.
|
||||
///
|
||||
pub(crate) fn local(env_tz: Option<&str>) -> Result<Self, Error> {
|
||||
match env_tz {
|
||||
Some(tz) => Self::from_posix_tz(tz),
|
||||
None => Self::from_posix_tz("localtime"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct a time zone from a POSIX TZ string, as described in [the POSIX documentation of the `TZ` environment variable](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html).
|
||||
fn from_posix_tz(tz_string: &str) -> Result<Self, Error> {
|
||||
if tz_string.is_empty() {
|
||||
return Err(Error::InvalidTzString("empty TZ string"));
|
||||
}
|
||||
|
||||
if tz_string == "localtime" {
|
||||
return Self::from_tz_data(&fs::read("/etc/localtime")?);
|
||||
}
|
||||
|
||||
let mut chars = tz_string.chars();
|
||||
if chars.next() == Some(':') {
|
||||
return Self::from_file(&mut find_tz_file(chars.as_str())?);
|
||||
}
|
||||
|
||||
if let Ok(mut file) = find_tz_file(tz_string) {
|
||||
return Self::from_file(&mut file);
|
||||
}
|
||||
|
||||
// TZ string extensions are not allowed
|
||||
let tz_string = tz_string.trim_matches(|c: char| c.is_ascii_whitespace());
|
||||
let rule = TransitionRule::from_tz_string(tz_string.as_bytes(), false)?;
|
||||
Self::new(
|
||||
vec![],
|
||||
match rule {
|
||||
TransitionRule::Fixed(local_time_type) => vec![local_time_type],
|
||||
TransitionRule::Alternate(AlternateTime { std, dst, .. }) => vec![std, dst],
|
||||
},
|
||||
vec![],
|
||||
Some(rule),
|
||||
)
|
||||
}
|
||||
|
||||
/// Construct a time zone
|
||||
pub(super) fn new(
|
||||
transitions: Vec<Transition>,
|
||||
local_time_types: Vec<LocalTimeType>,
|
||||
leap_seconds: Vec<LeapSecond>,
|
||||
extra_rule: Option<TransitionRule>,
|
||||
) -> Result<Self, Error> {
|
||||
let new = Self { transitions, local_time_types, leap_seconds, extra_rule };
|
||||
new.as_ref().validate()?;
|
||||
Ok(new)
|
||||
}
|
||||
|
||||
/// Construct a time zone from the contents of a time zone file
|
||||
fn from_file(file: &mut File) -> Result<Self, Error> {
|
||||
let mut bytes = Vec::new();
|
||||
file.read_to_end(&mut bytes)?;
|
||||
Self::from_tz_data(&bytes)
|
||||
}
|
||||
|
||||
/// Construct a time zone from the contents of a time zone file
|
||||
///
|
||||
/// Parse TZif data as described in [RFC 8536](https://datatracker.ietf.org/doc/html/rfc8536).
|
||||
pub(crate) fn from_tz_data(bytes: &[u8]) -> Result<Self, Error> {
|
||||
parser::parse(bytes)
|
||||
}
|
||||
|
||||
/// Construct a time zone with the specified UTC offset in seconds
|
||||
fn fixed(ut_offset: i32) -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
transitions: Vec::new(),
|
||||
local_time_types: vec![LocalTimeType::with_offset(ut_offset)?],
|
||||
leap_seconds: Vec::new(),
|
||||
extra_rule: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Construct the time zone associated to UTC
|
||||
pub(crate) fn utc() -> Self {
|
||||
Self {
|
||||
transitions: Vec::new(),
|
||||
local_time_types: vec![LocalTimeType::UTC],
|
||||
leap_seconds: Vec::new(),
|
||||
extra_rule: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Find the local time type associated to the time zone at the specified Unix time in seconds
|
||||
pub(crate) fn find_local_time_type(&self, unix_time: i64) -> Result<&LocalTimeType, Error> {
|
||||
self.as_ref().find_local_time_type(unix_time)
|
||||
}
|
||||
|
||||
// should we pass NaiveDateTime all the way through to this fn?
|
||||
pub(crate) fn find_local_time_type_from_local(
|
||||
&self,
|
||||
local_time: i64,
|
||||
year: i32,
|
||||
) -> Result<crate::LocalResult<LocalTimeType>, Error> {
|
||||
self.as_ref().find_local_time_type_from_local(local_time, year)
|
||||
}
|
||||
|
||||
/// Returns a reference to the time zone
|
||||
fn as_ref(&self) -> TimeZoneRef {
|
||||
TimeZoneRef {
|
||||
transitions: &self.transitions,
|
||||
local_time_types: &self.local_time_types,
|
||||
leap_seconds: &self.leap_seconds,
|
||||
extra_rule: &self.extra_rule,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Reference to a time zone
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub(crate) struct TimeZoneRef<'a> {
|
||||
/// List of transitions
|
||||
transitions: &'a [Transition],
|
||||
/// List of local time types (cannot be empty)
|
||||
local_time_types: &'a [LocalTimeType],
|
||||
/// List of leap seconds
|
||||
leap_seconds: &'a [LeapSecond],
|
||||
/// Extra transition rule applicable after the last transition
|
||||
extra_rule: &'a Option<TransitionRule>,
|
||||
}
|
||||
|
||||
impl<'a> TimeZoneRef<'a> {
|
||||
/// Find the local time type associated to the time zone at the specified Unix time in seconds
|
||||
pub(crate) fn find_local_time_type(&self, unix_time: i64) -> Result<&'a LocalTimeType, Error> {
|
||||
let extra_rule = match self.transitions.last() {
|
||||
None => match self.extra_rule {
|
||||
Some(extra_rule) => extra_rule,
|
||||
None => return Ok(&self.local_time_types[0]),
|
||||
},
|
||||
Some(last_transition) => {
|
||||
let unix_leap_time = match self.unix_time_to_unix_leap_time(unix_time) {
|
||||
Ok(unix_leap_time) => unix_leap_time,
|
||||
Err(Error::OutOfRange(error)) => return Err(Error::FindLocalTimeType(error)),
|
||||
Err(err) => return Err(err),
|
||||
};
|
||||
|
||||
if unix_leap_time >= last_transition.unix_leap_time {
|
||||
match self.extra_rule {
|
||||
Some(extra_rule) => extra_rule,
|
||||
None => {
|
||||
return Err(Error::FindLocalTimeType(
|
||||
"no local time type is available for the specified timestamp",
|
||||
))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let index = match self
|
||||
.transitions
|
||||
.binary_search_by_key(&unix_leap_time, Transition::unix_leap_time)
|
||||
{
|
||||
Ok(x) => x + 1,
|
||||
Err(x) => x,
|
||||
};
|
||||
|
||||
let local_time_type_index = if index > 0 {
|
||||
self.transitions[index - 1].local_time_type_index
|
||||
} else {
|
||||
0
|
||||
};
|
||||
return Ok(&self.local_time_types[local_time_type_index]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
match extra_rule.find_local_time_type(unix_time) {
|
||||
Ok(local_time_type) => Ok(local_time_type),
|
||||
Err(Error::OutOfRange(error)) => Err(Error::FindLocalTimeType(error)),
|
||||
err => err,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn find_local_time_type_from_local(
|
||||
&self,
|
||||
local_time: i64,
|
||||
year: i32,
|
||||
) -> Result<crate::LocalResult<LocalTimeType>, Error> {
|
||||
// #TODO: this is wrong as we need 'local_time_to_local_leap_time ?
|
||||
// but ... does the local time even include leap seconds ??
|
||||
// let unix_leap_time = match self.unix_time_to_unix_leap_time(local_time) {
|
||||
// Ok(unix_leap_time) => unix_leap_time,
|
||||
// Err(Error::OutOfRange(error)) => return Err(Error::FindLocalTimeType(error)),
|
||||
// Err(err) => return Err(err),
|
||||
// };
|
||||
let local_leap_time = local_time;
|
||||
|
||||
// if we have at least one transition,
|
||||
// we must check _all_ of them, incase of any Overlapping (LocalResult::Ambiguous) or Skipping (LocalResult::None) transitions
|
||||
if !self.transitions.is_empty() {
|
||||
let mut prev = Some(self.local_time_types[0]);
|
||||
|
||||
for transition in self.transitions {
|
||||
let after_ltt = self.local_time_types[transition.local_time_type_index];
|
||||
|
||||
// the end and start here refers to where the time starts prior to the transition
|
||||
// and where it ends up after. not the temporal relationship.
|
||||
let transition_end = transition.unix_leap_time + i64::from(after_ltt.ut_offset);
|
||||
let transition_start =
|
||||
transition.unix_leap_time + i64::from(prev.unwrap().ut_offset);
|
||||
|
||||
match transition_start.cmp(&transition_end) {
|
||||
Ordering::Greater => {
|
||||
// bakwards transition, eg from DST to regular
|
||||
// this means a given local time could have one of two possible offsets
|
||||
if local_leap_time < transition_end {
|
||||
return Ok(crate::LocalResult::Single(prev.unwrap()));
|
||||
} else if local_leap_time >= transition_end
|
||||
&& local_leap_time <= transition_start
|
||||
{
|
||||
if prev.unwrap().ut_offset < after_ltt.ut_offset {
|
||||
return Ok(crate::LocalResult::Ambiguous(prev.unwrap(), after_ltt));
|
||||
} else {
|
||||
return Ok(crate::LocalResult::Ambiguous(after_ltt, prev.unwrap()));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ordering::Equal => {
|
||||
// should this ever happen? presumably we have to handle it anyway.
|
||||
if local_leap_time < transition_start {
|
||||
return Ok(crate::LocalResult::Single(prev.unwrap()));
|
||||
} else if local_leap_time == transition_end {
|
||||
if prev.unwrap().ut_offset < after_ltt.ut_offset {
|
||||
return Ok(crate::LocalResult::Ambiguous(prev.unwrap(), after_ltt));
|
||||
} else {
|
||||
return Ok(crate::LocalResult::Ambiguous(after_ltt, prev.unwrap()));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ordering::Less => {
|
||||
// forwards transition, eg from regular to DST
|
||||
// this means that times that are skipped are invalid local times
|
||||
if local_leap_time <= transition_start {
|
||||
return Ok(crate::LocalResult::Single(prev.unwrap()));
|
||||
} else if local_leap_time < transition_end {
|
||||
return Ok(crate::LocalResult::None);
|
||||
} else if local_leap_time == transition_end {
|
||||
return Ok(crate::LocalResult::Single(after_ltt));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// try the next transition, we are fully after this one
|
||||
prev = Some(after_ltt);
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(extra_rule) = self.extra_rule {
|
||||
match extra_rule.find_local_time_type_from_local(local_time, year) {
|
||||
Ok(local_time_type) => Ok(local_time_type),
|
||||
Err(Error::OutOfRange(error)) => Err(Error::FindLocalTimeType(error)),
|
||||
err => err,
|
||||
}
|
||||
} else {
|
||||
Ok(crate::LocalResult::Single(self.local_time_types[0]))
|
||||
}
|
||||
}
|
||||
|
||||
/// Check time zone inputs
|
||||
fn validate(&self) -> Result<(), Error> {
|
||||
// Check local time types
|
||||
let local_time_types_size = self.local_time_types.len();
|
||||
if local_time_types_size == 0 {
|
||||
return Err(Error::TimeZone("list of local time types must not be empty"));
|
||||
}
|
||||
|
||||
// Check transitions
|
||||
let mut i_transition = 0;
|
||||
while i_transition < self.transitions.len() {
|
||||
if self.transitions[i_transition].local_time_type_index >= local_time_types_size {
|
||||
return Err(Error::TimeZone("invalid local time type index"));
|
||||
}
|
||||
|
||||
if i_transition + 1 < self.transitions.len()
|
||||
&& self.transitions[i_transition].unix_leap_time
|
||||
>= self.transitions[i_transition + 1].unix_leap_time
|
||||
{
|
||||
return Err(Error::TimeZone("invalid transition"));
|
||||
}
|
||||
|
||||
i_transition += 1;
|
||||
}
|
||||
|
||||
// Check leap seconds
|
||||
if !(self.leap_seconds.is_empty()
|
||||
|| self.leap_seconds[0].unix_leap_time >= 0
|
||||
&& saturating_abs(self.leap_seconds[0].correction) == 1)
|
||||
{
|
||||
return Err(Error::TimeZone("invalid leap second"));
|
||||
}
|
||||
|
||||
let min_interval = SECONDS_PER_28_DAYS - 1;
|
||||
|
||||
let mut i_leap_second = 0;
|
||||
while i_leap_second < self.leap_seconds.len() {
|
||||
if i_leap_second + 1 < self.leap_seconds.len() {
|
||||
let x0 = &self.leap_seconds[i_leap_second];
|
||||
let x1 = &self.leap_seconds[i_leap_second + 1];
|
||||
|
||||
let diff_unix_leap_time = x1.unix_leap_time.saturating_sub(x0.unix_leap_time);
|
||||
let abs_diff_correction =
|
||||
saturating_abs(x1.correction.saturating_sub(x0.correction));
|
||||
|
||||
if !(diff_unix_leap_time >= min_interval && abs_diff_correction == 1) {
|
||||
return Err(Error::TimeZone("invalid leap second"));
|
||||
}
|
||||
}
|
||||
i_leap_second += 1;
|
||||
}
|
||||
|
||||
// Check extra rule
|
||||
let (extra_rule, last_transition) = match (&self.extra_rule, self.transitions.last()) {
|
||||
(Some(rule), Some(trans)) => (rule, trans),
|
||||
_ => return Ok(()),
|
||||
};
|
||||
|
||||
let last_local_time_type = &self.local_time_types[last_transition.local_time_type_index];
|
||||
let unix_time = match self.unix_leap_time_to_unix_time(last_transition.unix_leap_time) {
|
||||
Ok(unix_time) => unix_time,
|
||||
Err(Error::OutOfRange(error)) => return Err(Error::TimeZone(error)),
|
||||
Err(err) => return Err(err),
|
||||
};
|
||||
|
||||
let rule_local_time_type = match extra_rule.find_local_time_type(unix_time) {
|
||||
Ok(rule_local_time_type) => rule_local_time_type,
|
||||
Err(Error::OutOfRange(error)) => return Err(Error::TimeZone(error)),
|
||||
Err(err) => return Err(err),
|
||||
};
|
||||
|
||||
let check = last_local_time_type.ut_offset == rule_local_time_type.ut_offset
|
||||
&& last_local_time_type.is_dst == rule_local_time_type.is_dst
|
||||
&& match (&last_local_time_type.name, &rule_local_time_type.name) {
|
||||
(Some(x), Some(y)) => x.equal(y),
|
||||
(None, None) => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
if !check {
|
||||
return Err(Error::TimeZone(
|
||||
"extra transition rule is inconsistent with the last transition",
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Convert Unix time to Unix leap time, from the list of leap seconds in a time zone
|
||||
fn unix_time_to_unix_leap_time(&self, unix_time: i64) -> Result<i64, Error> {
|
||||
let mut unix_leap_time = unix_time;
|
||||
|
||||
let mut i = 0;
|
||||
while i < self.leap_seconds.len() {
|
||||
let leap_second = &self.leap_seconds[i];
|
||||
|
||||
if unix_leap_time < leap_second.unix_leap_time {
|
||||
break;
|
||||
}
|
||||
|
||||
unix_leap_time = match unix_time.checked_add(leap_second.correction as i64) {
|
||||
Some(unix_leap_time) => unix_leap_time,
|
||||
None => return Err(Error::OutOfRange("out of range operation")),
|
||||
};
|
||||
|
||||
i += 1;
|
||||
}
|
||||
|
||||
Ok(unix_leap_time)
|
||||
}
|
||||
|
||||
/// Convert Unix leap time to Unix time, from the list of leap seconds in a time zone
|
||||
fn unix_leap_time_to_unix_time(&self, unix_leap_time: i64) -> Result<i64, Error> {
|
||||
if unix_leap_time == i64::min_value() {
|
||||
return Err(Error::OutOfRange("out of range operation"));
|
||||
}
|
||||
|
||||
let index = match self
|
||||
.leap_seconds
|
||||
.binary_search_by_key(&(unix_leap_time - 1), LeapSecond::unix_leap_time)
|
||||
{
|
||||
Ok(x) => x + 1,
|
||||
Err(x) => x,
|
||||
};
|
||||
|
||||
let correction = if index > 0 { self.leap_seconds[index - 1].correction } else { 0 };
|
||||
|
||||
match unix_leap_time.checked_sub(correction as i64) {
|
||||
Some(unix_time) => Ok(unix_time),
|
||||
None => Err(Error::OutOfRange("out of range operation")),
|
||||
}
|
||||
}
|
||||
|
||||
/// The UTC time zone
|
||||
const UTC: TimeZoneRef<'static> = TimeZoneRef {
|
||||
transitions: &[],
|
||||
local_time_types: &[LocalTimeType::UTC],
|
||||
leap_seconds: &[],
|
||||
extra_rule: &None,
|
||||
};
|
||||
}
|
||||
|
||||
/// Transition of a TZif file
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub(super) struct Transition {
|
||||
/// Unix leap time
|
||||
unix_leap_time: i64,
|
||||
/// Index specifying the local time type of the transition
|
||||
local_time_type_index: usize,
|
||||
}
|
||||
|
||||
impl Transition {
|
||||
/// Construct a TZif file transition
|
||||
pub(super) fn new(unix_leap_time: i64, local_time_type_index: usize) -> Self {
|
||||
Self { unix_leap_time, local_time_type_index }
|
||||
}
|
||||
|
||||
/// Returns Unix leap time
|
||||
fn unix_leap_time(&self) -> i64 {
|
||||
self.unix_leap_time
|
||||
}
|
||||
}
|
||||
|
||||
/// Leap second of a TZif file
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub(super) struct LeapSecond {
|
||||
/// Unix leap time
|
||||
unix_leap_time: i64,
|
||||
/// Leap second correction
|
||||
correction: i32,
|
||||
}
|
||||
|
||||
impl LeapSecond {
|
||||
/// Construct a TZif file leap second
|
||||
pub(super) fn new(unix_leap_time: i64, correction: i32) -> Self {
|
||||
Self { unix_leap_time, correction }
|
||||
}
|
||||
|
||||
/// Returns Unix leap time
|
||||
fn unix_leap_time(&self) -> i64 {
|
||||
self.unix_leap_time
|
||||
}
|
||||
}
|
||||
|
||||
/// ASCII-encoded fixed-capacity string, used for storing time zone names
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
struct TimeZoneName {
|
||||
/// Length-prefixed string buffer
|
||||
bytes: [u8; 8],
|
||||
}
|
||||
|
||||
impl TimeZoneName {
|
||||
/// Construct a time zone name
|
||||
fn new(input: &[u8]) -> Result<Self, Error> {
|
||||
let len = input.len();
|
||||
|
||||
if !(3..=7).contains(&len) {
|
||||
return Err(Error::LocalTimeType(
|
||||
"time zone name must have between 3 and 7 characters",
|
||||
));
|
||||
}
|
||||
|
||||
let mut bytes = [0; 8];
|
||||
bytes[0] = input.len() as u8;
|
||||
|
||||
let mut i = 0;
|
||||
while i < len {
|
||||
let b = input[i];
|
||||
match b {
|
||||
b'0'..=b'9' | b'A'..=b'Z' | b'a'..=b'z' | b'+' | b'-' => {}
|
||||
_ => return Err(Error::LocalTimeType("invalid characters in time zone name")),
|
||||
}
|
||||
|
||||
bytes[i + 1] = b;
|
||||
i += 1;
|
||||
}
|
||||
|
||||
Ok(Self { bytes })
|
||||
}
|
||||
|
||||
/// Returns time zone name as a byte slice
|
||||
fn as_bytes(&self) -> &[u8] {
|
||||
match self.bytes[0] {
|
||||
3 => &self.bytes[1..4],
|
||||
4 => &self.bytes[1..5],
|
||||
5 => &self.bytes[1..6],
|
||||
6 => &self.bytes[1..7],
|
||||
7 => &self.bytes[1..8],
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if two time zone names are equal
|
||||
fn equal(&self, other: &Self) -> bool {
|
||||
self.bytes == other.bytes
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<str> for TimeZoneName {
|
||||
fn as_ref(&self) -> &str {
|
||||
// SAFETY: ASCII is valid UTF-8
|
||||
unsafe { str::from_utf8_unchecked(self.as_bytes()) }
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for TimeZoneName {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
self.as_ref().fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
/// Local time type associated to a time zone
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub(crate) struct LocalTimeType {
|
||||
/// Offset from UTC in seconds
|
||||
pub(super) ut_offset: i32,
|
||||
/// Daylight Saving Time indicator
|
||||
is_dst: bool,
|
||||
/// Time zone name
|
||||
name: Option<TimeZoneName>,
|
||||
}
|
||||
|
||||
impl LocalTimeType {
|
||||
/// Construct a local time type
|
||||
pub(super) fn new(ut_offset: i32, is_dst: bool, name: Option<&[u8]>) -> Result<Self, Error> {
|
||||
if ut_offset == i32::min_value() {
|
||||
return Err(Error::LocalTimeType("invalid UTC offset"));
|
||||
}
|
||||
|
||||
let name = match name {
|
||||
Some(name) => TimeZoneName::new(name)?,
|
||||
None => return Ok(Self { ut_offset, is_dst, name: None }),
|
||||
};
|
||||
|
||||
Ok(Self { ut_offset, is_dst, name: Some(name) })
|
||||
}
|
||||
|
||||
/// Construct a local time type with the specified UTC offset in seconds
|
||||
pub(super) fn with_offset(ut_offset: i32) -> Result<Self, Error> {
|
||||
if ut_offset == i32::min_value() {
|
||||
return Err(Error::LocalTimeType("invalid UTC offset"));
|
||||
}
|
||||
|
||||
Ok(Self { ut_offset, is_dst: false, name: None })
|
||||
}
|
||||
|
||||
/// Returns offset from UTC in seconds
|
||||
pub(crate) fn offset(&self) -> i32 {
|
||||
self.ut_offset
|
||||
}
|
||||
|
||||
/// Returns daylight saving time indicator
|
||||
pub(super) fn is_dst(&self) -> bool {
|
||||
self.is_dst
|
||||
}
|
||||
|
||||
pub(super) const UTC: LocalTimeType = Self { ut_offset: 0, is_dst: false, name: None };
|
||||
}
|
||||
|
||||
/// Open the TZif file corresponding to a TZ string
|
||||
fn find_tz_file(path: impl AsRef<Path>) -> Result<File, Error> {
|
||||
// Don't check system timezone directories on non-UNIX platforms
|
||||
#[cfg(not(unix))]
|
||||
return Ok(File::open(path)?);
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
let path = path.as_ref();
|
||||
if path.is_absolute() {
|
||||
return Ok(File::open(path)?);
|
||||
}
|
||||
|
||||
for folder in &ZONE_INFO_DIRECTORIES {
|
||||
if let Ok(file) = File::open(PathBuf::from(folder).join(path)) {
|
||||
return Ok(file);
|
||||
}
|
||||
}
|
||||
|
||||
Err(Error::Io(io::ErrorKind::NotFound.into()))
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn saturating_abs(v: i32) -> i32 {
|
||||
if v.is_positive() {
|
||||
v
|
||||
} else if v == i32::min_value() {
|
||||
i32::max_value()
|
||||
} else {
|
||||
-v
|
||||
}
|
||||
}
|
||||
|
||||
// Possible system timezone directories
|
||||
#[cfg(unix)]
|
||||
const ZONE_INFO_DIRECTORIES: [&str; 3] =
|
||||
["/usr/share/zoneinfo", "/share/zoneinfo", "/etc/zoneinfo"];
|
||||
|
||||
/// Number of seconds in one week
|
||||
pub(crate) const SECONDS_PER_WEEK: i64 = SECONDS_PER_DAY * DAYS_PER_WEEK;
|
||||
/// Number of seconds in 28 days
|
||||
const SECONDS_PER_28_DAYS: i64 = SECONDS_PER_DAY * 28;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::super::Error;
|
||||
use super::{LeapSecond, LocalTimeType, TimeZone, TimeZoneName, Transition, TransitionRule};
|
||||
use crate::matches;
|
||||
|
||||
#[test]
|
||||
fn test_no_dst() -> Result<(), Error> {
|
||||
let tz_string = b"HST10";
|
||||
let transition_rule = TransitionRule::from_tz_string(tz_string, false)?;
|
||||
assert_eq!(transition_rule, LocalTimeType::new(-36000, false, Some(b"HST"))?.into());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_error() -> Result<(), Error> {
|
||||
assert!(matches!(
|
||||
TransitionRule::from_tz_string(b"IST-1GMT0", false),
|
||||
Err(Error::UnsupportedTzString(_))
|
||||
));
|
||||
assert!(matches!(
|
||||
TransitionRule::from_tz_string(b"EET-2EEST", false),
|
||||
Err(Error::UnsupportedTzString(_))
|
||||
));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_v1_file_with_leap_seconds() -> Result<(), Error> {
|
||||
let bytes = b"TZif\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x01\0\0\0\x01\0\0\0\x1b\0\0\0\0\0\0\0\x01\0\0\0\x04\0\0\0\0\0\0UTC\0\x04\xb2\x58\0\0\0\0\x01\x05\xa4\xec\x01\0\0\0\x02\x07\x86\x1f\x82\0\0\0\x03\x09\x67\x53\x03\0\0\0\x04\x0b\x48\x86\x84\0\0\0\x05\x0d\x2b\x0b\x85\0\0\0\x06\x0f\x0c\x3f\x06\0\0\0\x07\x10\xed\x72\x87\0\0\0\x08\x12\xce\xa6\x08\0\0\0\x09\x15\x9f\xca\x89\0\0\0\x0a\x17\x80\xfe\x0a\0\0\0\x0b\x19\x62\x31\x8b\0\0\0\x0c\x1d\x25\xea\x0c\0\0\0\x0d\x21\xda\xe5\x0d\0\0\0\x0e\x25\x9e\x9d\x8e\0\0\0\x0f\x27\x7f\xd1\x0f\0\0\0\x10\x2a\x50\xf5\x90\0\0\0\x11\x2c\x32\x29\x11\0\0\0\x12\x2e\x13\x5c\x92\0\0\0\x13\x30\xe7\x24\x13\0\0\0\x14\x33\xb8\x48\x94\0\0\0\x15\x36\x8c\x10\x15\0\0\0\x16\x43\xb7\x1b\x96\0\0\0\x17\x49\x5c\x07\x97\0\0\0\x18\x4f\xef\x93\x18\0\0\0\x19\x55\x93\x2d\x99\0\0\0\x1a\x58\x68\x46\x9a\0\0\0\x1b\0\0";
|
||||
|
||||
let time_zone = TimeZone::from_tz_data(bytes)?;
|
||||
|
||||
let time_zone_result = TimeZone::new(
|
||||
Vec::new(),
|
||||
vec![LocalTimeType::new(0, false, Some(b"UTC"))?],
|
||||
vec![
|
||||
LeapSecond::new(78796800, 1),
|
||||
LeapSecond::new(94694401, 2),
|
||||
LeapSecond::new(126230402, 3),
|
||||
LeapSecond::new(157766403, 4),
|
||||
LeapSecond::new(189302404, 5),
|
||||
LeapSecond::new(220924805, 6),
|
||||
LeapSecond::new(252460806, 7),
|
||||
LeapSecond::new(283996807, 8),
|
||||
LeapSecond::new(315532808, 9),
|
||||
LeapSecond::new(362793609, 10),
|
||||
LeapSecond::new(394329610, 11),
|
||||
LeapSecond::new(425865611, 12),
|
||||
LeapSecond::new(489024012, 13),
|
||||
LeapSecond::new(567993613, 14),
|
||||
LeapSecond::new(631152014, 15),
|
||||
LeapSecond::new(662688015, 16),
|
||||
LeapSecond::new(709948816, 17),
|
||||
LeapSecond::new(741484817, 18),
|
||||
LeapSecond::new(773020818, 19),
|
||||
LeapSecond::new(820454419, 20),
|
||||
LeapSecond::new(867715220, 21),
|
||||
LeapSecond::new(915148821, 22),
|
||||
LeapSecond::new(1136073622, 23),
|
||||
LeapSecond::new(1230768023, 24),
|
||||
LeapSecond::new(1341100824, 25),
|
||||
LeapSecond::new(1435708825, 26),
|
||||
LeapSecond::new(1483228826, 27),
|
||||
],
|
||||
None,
|
||||
)?;
|
||||
|
||||
assert_eq!(time_zone, time_zone_result);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_v2_file() -> Result<(), Error> {
|
||||
let bytes = b"TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x06\0\0\0\x06\0\0\0\0\0\0\0\x07\0\0\0\x06\0\0\0\x14\x80\0\0\0\xbb\x05\x43\x48\xbb\x21\x71\x58\xcb\x89\x3d\xc8\xd2\x23\xf4\x70\xd2\x61\x49\x38\xd5\x8d\x73\x48\x01\x02\x01\x03\x04\x01\x05\xff\xff\x6c\x02\0\0\xff\xff\x6c\x58\0\x04\xff\xff\x7a\x68\x01\x08\xff\xff\x7a\x68\x01\x0c\xff\xff\x7a\x68\x01\x10\xff\xff\x73\x60\0\x04LMT\0HST\0HDT\0HWT\0HPT\0\0\0\0\0\x01\0\0\0\0\0\x01\0TZif2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x06\0\0\0\x06\0\0\0\0\0\0\0\x07\0\0\0\x06\0\0\0\x14\xff\xff\xff\xff\x74\xe0\x70\xbe\xff\xff\xff\xff\xbb\x05\x43\x48\xff\xff\xff\xff\xbb\x21\x71\x58\xff\xff\xff\xff\xcb\x89\x3d\xc8\xff\xff\xff\xff\xd2\x23\xf4\x70\xff\xff\xff\xff\xd2\x61\x49\x38\xff\xff\xff\xff\xd5\x8d\x73\x48\x01\x02\x01\x03\x04\x01\x05\xff\xff\x6c\x02\0\0\xff\xff\x6c\x58\0\x04\xff\xff\x7a\x68\x01\x08\xff\xff\x7a\x68\x01\x0c\xff\xff\x7a\x68\x01\x10\xff\xff\x73\x60\0\x04LMT\0HST\0HDT\0HWT\0HPT\0\0\0\0\0\x01\0\0\0\0\0\x01\0\x0aHST10\x0a";
|
||||
|
||||
let time_zone = TimeZone::from_tz_data(bytes)?;
|
||||
|
||||
let time_zone_result = TimeZone::new(
|
||||
vec![
|
||||
Transition::new(-2334101314, 1),
|
||||
Transition::new(-1157283000, 2),
|
||||
Transition::new(-1155436200, 1),
|
||||
Transition::new(-880198200, 3),
|
||||
Transition::new(-769395600, 4),
|
||||
Transition::new(-765376200, 1),
|
||||
Transition::new(-712150200, 5),
|
||||
],
|
||||
vec![
|
||||
LocalTimeType::new(-37886, false, Some(b"LMT"))?,
|
||||
LocalTimeType::new(-37800, false, Some(b"HST"))?,
|
||||
LocalTimeType::new(-34200, true, Some(b"HDT"))?,
|
||||
LocalTimeType::new(-34200, true, Some(b"HWT"))?,
|
||||
LocalTimeType::new(-34200, true, Some(b"HPT"))?,
|
||||
LocalTimeType::new(-36000, false, Some(b"HST"))?,
|
||||
],
|
||||
Vec::new(),
|
||||
Some(TransitionRule::from(LocalTimeType::new(-36000, false, Some(b"HST"))?)),
|
||||
)?;
|
||||
|
||||
assert_eq!(time_zone, time_zone_result);
|
||||
|
||||
assert_eq!(
|
||||
*time_zone.find_local_time_type(-1156939200)?,
|
||||
LocalTimeType::new(-34200, true, Some(b"HDT"))?
|
||||
);
|
||||
assert_eq!(
|
||||
*time_zone.find_local_time_type(1546300800)?,
|
||||
LocalTimeType::new(-36000, false, Some(b"HST"))?
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tz_ascii_str() -> Result<(), Error> {
|
||||
assert!(matches!(TimeZoneName::new(b""), Err(Error::LocalTimeType(_))));
|
||||
assert!(matches!(TimeZoneName::new(b"1"), Err(Error::LocalTimeType(_))));
|
||||
assert!(matches!(TimeZoneName::new(b"12"), Err(Error::LocalTimeType(_))));
|
||||
assert_eq!(TimeZoneName::new(b"123")?.as_bytes(), b"123");
|
||||
assert_eq!(TimeZoneName::new(b"1234")?.as_bytes(), b"1234");
|
||||
assert_eq!(TimeZoneName::new(b"12345")?.as_bytes(), b"12345");
|
||||
assert_eq!(TimeZoneName::new(b"123456")?.as_bytes(), b"123456");
|
||||
assert_eq!(TimeZoneName::new(b"1234567")?.as_bytes(), b"1234567");
|
||||
assert!(matches!(TimeZoneName::new(b"12345678"), Err(Error::LocalTimeType(_))));
|
||||
assert!(matches!(TimeZoneName::new(b"123456789"), Err(Error::LocalTimeType(_))));
|
||||
assert!(matches!(TimeZoneName::new(b"1234567890"), Err(Error::LocalTimeType(_))));
|
||||
|
||||
assert!(matches!(TimeZoneName::new(b"123\0\0\0"), Err(Error::LocalTimeType(_))));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_time_zone() -> Result<(), Error> {
|
||||
let utc = LocalTimeType::UTC;
|
||||
let cet = LocalTimeType::with_offset(3600)?;
|
||||
|
||||
let utc_local_time_types = vec![utc];
|
||||
let fixed_extra_rule = TransitionRule::from(cet);
|
||||
|
||||
let time_zone_1 = TimeZone::new(vec![], utc_local_time_types.clone(), vec![], None)?;
|
||||
let time_zone_2 =
|
||||
TimeZone::new(vec![], utc_local_time_types.clone(), vec![], Some(fixed_extra_rule))?;
|
||||
let time_zone_3 =
|
||||
TimeZone::new(vec![Transition::new(0, 0)], utc_local_time_types.clone(), vec![], None)?;
|
||||
let time_zone_4 = TimeZone::new(
|
||||
vec![Transition::new(i32::min_value().into(), 0), Transition::new(0, 1)],
|
||||
vec![utc, cet],
|
||||
Vec::new(),
|
||||
Some(fixed_extra_rule),
|
||||
)?;
|
||||
|
||||
assert_eq!(*time_zone_1.find_local_time_type(0)?, utc);
|
||||
assert_eq!(*time_zone_2.find_local_time_type(0)?, cet);
|
||||
|
||||
assert_eq!(*time_zone_3.find_local_time_type(-1)?, utc);
|
||||
assert!(matches!(time_zone_3.find_local_time_type(0), Err(Error::FindLocalTimeType(_))));
|
||||
|
||||
assert_eq!(*time_zone_4.find_local_time_type(-1)?, utc);
|
||||
assert_eq!(*time_zone_4.find_local_time_type(0)?, cet);
|
||||
|
||||
let time_zone_err = TimeZone::new(
|
||||
vec![Transition::new(0, 0)],
|
||||
utc_local_time_types,
|
||||
vec![],
|
||||
Some(fixed_extra_rule),
|
||||
);
|
||||
assert!(time_zone_err.is_err());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_time_zone_from_posix_tz() -> Result<(), Error> {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
// if the TZ var is set, this essentially _overrides_ the
|
||||
// time set by the localtime symlink
|
||||
// so just ensure that ::local() acts as expected
|
||||
// in this case
|
||||
if let Ok(tz) = std::env::var("TZ") {
|
||||
let time_zone_local = TimeZone::local(Some(tz.as_str()))?;
|
||||
let time_zone_local_1 = TimeZone::from_posix_tz(&tz)?;
|
||||
assert_eq!(time_zone_local, time_zone_local_1);
|
||||
}
|
||||
|
||||
let time_zone_utc = TimeZone::from_posix_tz("UTC")?;
|
||||
assert_eq!(time_zone_utc.find_local_time_type(0)?.offset(), 0);
|
||||
}
|
||||
|
||||
assert!(TimeZone::from_posix_tz("EST5EDT,0/0,J365/25").is_err());
|
||||
assert!(TimeZone::from_posix_tz("").is_err());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_leap_seconds() -> Result<(), Error> {
|
||||
let time_zone = TimeZone::new(
|
||||
Vec::new(),
|
||||
vec![LocalTimeType::new(0, false, Some(b"UTC"))?],
|
||||
vec![
|
||||
LeapSecond::new(78796800, 1),
|
||||
LeapSecond::new(94694401, 2),
|
||||
LeapSecond::new(126230402, 3),
|
||||
LeapSecond::new(157766403, 4),
|
||||
LeapSecond::new(189302404, 5),
|
||||
LeapSecond::new(220924805, 6),
|
||||
LeapSecond::new(252460806, 7),
|
||||
LeapSecond::new(283996807, 8),
|
||||
LeapSecond::new(315532808, 9),
|
||||
LeapSecond::new(362793609, 10),
|
||||
LeapSecond::new(394329610, 11),
|
||||
LeapSecond::new(425865611, 12),
|
||||
LeapSecond::new(489024012, 13),
|
||||
LeapSecond::new(567993613, 14),
|
||||
LeapSecond::new(631152014, 15),
|
||||
LeapSecond::new(662688015, 16),
|
||||
LeapSecond::new(709948816, 17),
|
||||
LeapSecond::new(741484817, 18),
|
||||
LeapSecond::new(773020818, 19),
|
||||
LeapSecond::new(820454419, 20),
|
||||
LeapSecond::new(867715220, 21),
|
||||
LeapSecond::new(915148821, 22),
|
||||
LeapSecond::new(1136073622, 23),
|
||||
LeapSecond::new(1230768023, 24),
|
||||
LeapSecond::new(1341100824, 25),
|
||||
LeapSecond::new(1435708825, 26),
|
||||
LeapSecond::new(1483228826, 27),
|
||||
],
|
||||
None,
|
||||
)?;
|
||||
|
||||
let time_zone_ref = time_zone.as_ref();
|
||||
|
||||
assert!(matches!(time_zone_ref.unix_leap_time_to_unix_time(1136073621), Ok(1136073599)));
|
||||
assert!(matches!(time_zone_ref.unix_leap_time_to_unix_time(1136073622), Ok(1136073600)));
|
||||
assert!(matches!(time_zone_ref.unix_leap_time_to_unix_time(1136073623), Ok(1136073600)));
|
||||
assert!(matches!(time_zone_ref.unix_leap_time_to_unix_time(1136073624), Ok(1136073601)));
|
||||
|
||||
assert!(matches!(time_zone_ref.unix_time_to_unix_leap_time(1136073599), Ok(1136073621)));
|
||||
assert!(matches!(time_zone_ref.unix_time_to_unix_leap_time(1136073600), Ok(1136073623)));
|
||||
assert!(matches!(time_zone_ref.unix_time_to_unix_leap_time(1136073601), Ok(1136073624)));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_leap_seconds_overflow() -> Result<(), Error> {
|
||||
let time_zone_err = TimeZone::new(
|
||||
vec![Transition::new(i64::min_value(), 0)],
|
||||
vec![LocalTimeType::UTC],
|
||||
vec![LeapSecond::new(0, 1)],
|
||||
Some(TransitionRule::from(LocalTimeType::UTC)),
|
||||
);
|
||||
assert!(time_zone_err.is_err());
|
||||
|
||||
let time_zone = TimeZone::new(
|
||||
vec![Transition::new(i64::max_value(), 0)],
|
||||
vec![LocalTimeType::UTC],
|
||||
vec![LeapSecond::new(0, 1)],
|
||||
None,
|
||||
)?;
|
||||
assert!(matches!(
|
||||
time_zone.find_local_time_type(i64::max_value()),
|
||||
Err(Error::FindLocalTimeType(_))
|
||||
));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
185
vendor/chrono/src/offset/local/unix.rs
vendored
Normal file
185
vendor/chrono/src/offset/local/unix.rs
vendored
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution and at
|
||||
// http://rust-lang.org/COPYRIGHT.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
use std::{cell::RefCell, collections::hash_map, env, fs, hash::Hasher, time::SystemTime};
|
||||
|
||||
use super::tz_info::TimeZone;
|
||||
use super::{DateTime, FixedOffset, Local, NaiveDateTime};
|
||||
use crate::{Datelike, LocalResult, Utc};
|
||||
|
||||
pub(super) fn now() -> DateTime<Local> {
|
||||
let now = Utc::now().naive_utc();
|
||||
naive_to_local(&now, false).unwrap()
|
||||
}
|
||||
|
||||
pub(super) fn naive_to_local(d: &NaiveDateTime, local: bool) -> LocalResult<DateTime<Local>> {
|
||||
TZ_INFO.with(|maybe_cache| {
|
||||
maybe_cache.borrow_mut().get_or_insert_with(Cache::default).offset(*d, local)
|
||||
})
|
||||
}
|
||||
|
||||
// we have to store the `Cache` in an option as it can't
|
||||
// be initalized in a static context.
|
||||
thread_local! {
|
||||
static TZ_INFO: RefCell<Option<Cache>> = Default::default();
|
||||
}
|
||||
|
||||
enum Source {
|
||||
LocalTime { mtime: SystemTime },
|
||||
Environment { hash: u64 },
|
||||
}
|
||||
|
||||
impl Source {
|
||||
fn new(env_tz: Option<&str>) -> Source {
|
||||
match env_tz {
|
||||
Some(tz) => {
|
||||
let mut hasher = hash_map::DefaultHasher::new();
|
||||
hasher.write(tz.as_bytes());
|
||||
let hash = hasher.finish();
|
||||
Source::Environment { hash }
|
||||
}
|
||||
None => match fs::symlink_metadata("/etc/localtime") {
|
||||
Ok(data) => Source::LocalTime {
|
||||
// we have to pick a sensible default when the mtime fails
|
||||
// by picking SystemTime::now() we raise the probability of
|
||||
// the cache being invalidated if/when the mtime starts working
|
||||
mtime: data.modified().unwrap_or_else(|_| SystemTime::now()),
|
||||
},
|
||||
Err(_) => {
|
||||
// as above, now() should be a better default than some constant
|
||||
// TODO: see if we can improve caching in the case where the fallback is a valid timezone
|
||||
Source::LocalTime { mtime: SystemTime::now() }
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Cache {
|
||||
zone: TimeZone,
|
||||
source: Source,
|
||||
last_checked: SystemTime,
|
||||
}
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
const TZDB_LOCATION: &str = " /system/usr/share/zoneinfo";
|
||||
|
||||
#[cfg(target_os = "aix")]
|
||||
const TZDB_LOCATION: &str = "/usr/share/lib/zoneinfo";
|
||||
|
||||
#[allow(dead_code)] // keeps the cfg simpler
|
||||
#[cfg(not(any(target_os = "android", target_os = "aix")))]
|
||||
const TZDB_LOCATION: &str = "/usr/share/zoneinfo";
|
||||
|
||||
fn fallback_timezone() -> Option<TimeZone> {
|
||||
let tz_name = iana_time_zone::get_timezone().ok()?;
|
||||
let bytes = fs::read(format!("{}/{}", TZDB_LOCATION, tz_name)).ok()?;
|
||||
TimeZone::from_tz_data(&bytes).ok()
|
||||
}
|
||||
|
||||
impl Default for Cache {
|
||||
fn default() -> Cache {
|
||||
// default to UTC if no local timezone can be found
|
||||
let env_tz = env::var("TZ").ok();
|
||||
let env_ref = env_tz.as_ref().map(|s| s.as_str());
|
||||
Cache {
|
||||
last_checked: SystemTime::now(),
|
||||
source: Source::new(env_ref),
|
||||
zone: current_zone(env_ref),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn current_zone(var: Option<&str>) -> TimeZone {
|
||||
TimeZone::local(var).ok().or_else(fallback_timezone).unwrap_or_else(TimeZone::utc)
|
||||
}
|
||||
|
||||
impl Cache {
|
||||
fn offset(&mut self, d: NaiveDateTime, local: bool) -> LocalResult<DateTime<Local>> {
|
||||
let now = SystemTime::now();
|
||||
|
||||
match now.duration_since(self.last_checked) {
|
||||
// If the cache has been around for less than a second then we reuse it
|
||||
// unconditionally. This is a reasonable tradeoff because the timezone
|
||||
// generally won't be changing _that_ often, but if the time zone does
|
||||
// change, it will reflect sufficiently quickly from an application
|
||||
// user's perspective.
|
||||
Ok(d) if d.as_secs() < 1 => (),
|
||||
Ok(_) | Err(_) => {
|
||||
let env_tz = env::var("TZ").ok();
|
||||
let env_ref = env_tz.as_ref().map(|s| s.as_str());
|
||||
let new_source = Source::new(env_ref);
|
||||
|
||||
let out_of_date = match (&self.source, &new_source) {
|
||||
// change from env to file or file to env, must recreate the zone
|
||||
(Source::Environment { .. }, Source::LocalTime { .. })
|
||||
| (Source::LocalTime { .. }, Source::Environment { .. }) => true,
|
||||
// stay as file, but mtime has changed
|
||||
(Source::LocalTime { mtime: old_mtime }, Source::LocalTime { mtime })
|
||||
if old_mtime != mtime =>
|
||||
{
|
||||
true
|
||||
}
|
||||
// stay as env, but hash of variable has changed
|
||||
(Source::Environment { hash: old_hash }, Source::Environment { hash })
|
||||
if old_hash != hash =>
|
||||
{
|
||||
true
|
||||
}
|
||||
// cache can be reused
|
||||
_ => false,
|
||||
};
|
||||
|
||||
if out_of_date {
|
||||
self.zone = current_zone(env_ref);
|
||||
}
|
||||
|
||||
self.last_checked = now;
|
||||
self.source = new_source;
|
||||
}
|
||||
}
|
||||
|
||||
if !local {
|
||||
let offset = self
|
||||
.zone
|
||||
.find_local_time_type(d.timestamp())
|
||||
.expect("unable to select local time type")
|
||||
.offset();
|
||||
|
||||
return match FixedOffset::east_opt(offset) {
|
||||
Some(offset) => LocalResult::Single(DateTime::from_utc(d, offset)),
|
||||
None => LocalResult::None,
|
||||
};
|
||||
}
|
||||
|
||||
// we pass through the year as the year of a local point in time must either be valid in that locale, or
|
||||
// the entire time was skipped in which case we will return LocalResult::None anywa.
|
||||
match self
|
||||
.zone
|
||||
.find_local_time_type_from_local(d.timestamp(), d.year())
|
||||
.expect("unable to select local time type")
|
||||
{
|
||||
LocalResult::None => LocalResult::None,
|
||||
LocalResult::Ambiguous(early, late) => {
|
||||
let early_offset = FixedOffset::east_opt(early.offset()).unwrap();
|
||||
let late_offset = FixedOffset::east_opt(late.offset()).unwrap();
|
||||
|
||||
LocalResult::Ambiguous(
|
||||
DateTime::from_utc(d - early_offset, early_offset),
|
||||
DateTime::from_utc(d - late_offset, late_offset),
|
||||
)
|
||||
}
|
||||
LocalResult::Single(tt) => {
|
||||
let offset = FixedOffset::east_opt(tt.offset()).unwrap();
|
||||
LocalResult::Single(DateTime::from_utc(d - offset, offset))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
278
vendor/chrono/src/offset/local/windows.rs
vendored
Normal file
278
vendor/chrono/src/offset/local/windows.rs
vendored
Normal file
|
|
@ -0,0 +1,278 @@
|
|||
// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution and at
|
||||
// http://rust-lang.org/COPYRIGHT.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
use std::io;
|
||||
use std::mem;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use winapi::shared::minwindef::*;
|
||||
use winapi::um::minwinbase::SYSTEMTIME;
|
||||
use winapi::um::timezoneapi::*;
|
||||
|
||||
use super::{FixedOffset, Local};
|
||||
use crate::{DateTime, Datelike, LocalResult, NaiveDate, NaiveDateTime, NaiveTime, Timelike};
|
||||
|
||||
pub(super) fn now() -> DateTime<Local> {
|
||||
tm_to_datetime(Timespec::now().local())
|
||||
}
|
||||
|
||||
/// Converts a local `NaiveDateTime` to the `time::Timespec`.
|
||||
pub(super) fn naive_to_local(d: &NaiveDateTime, local: bool) -> LocalResult<DateTime<Local>> {
|
||||
let tm = Tm {
|
||||
tm_sec: d.second() as i32,
|
||||
tm_min: d.minute() as i32,
|
||||
tm_hour: d.hour() as i32,
|
||||
tm_mday: d.day() as i32,
|
||||
tm_mon: d.month0() as i32, // yes, C is that strange...
|
||||
tm_year: d.year() - 1900, // this doesn't underflow, we know that d is `NaiveDateTime`.
|
||||
tm_wday: 0, // to_local ignores this
|
||||
tm_yday: 0, // and this
|
||||
tm_isdst: -1,
|
||||
// This seems pretty fake?
|
||||
tm_utcoff: if local { 1 } else { 0 },
|
||||
// do not set this, OS APIs are heavily inconsistent in terms of leap second handling
|
||||
tm_nsec: 0,
|
||||
};
|
||||
|
||||
let spec = Timespec {
|
||||
sec: match local {
|
||||
false => utc_tm_to_time(&tm),
|
||||
true => local_tm_to_time(&tm),
|
||||
},
|
||||
nsec: tm.tm_nsec,
|
||||
};
|
||||
|
||||
// Adjust for leap seconds
|
||||
let mut tm = spec.local();
|
||||
assert_eq!(tm.tm_nsec, 0);
|
||||
tm.tm_nsec = d.nanosecond() as i32;
|
||||
|
||||
// #TODO - there should be ambiguous cases, investigate?
|
||||
LocalResult::Single(tm_to_datetime(tm))
|
||||
}
|
||||
|
||||
/// Converts a `time::Tm` struct into the timezone-aware `DateTime`.
|
||||
fn tm_to_datetime(mut tm: Tm) -> DateTime<Local> {
|
||||
if tm.tm_sec >= 60 {
|
||||
tm.tm_nsec += (tm.tm_sec - 59) * 1_000_000_000;
|
||||
tm.tm_sec = 59;
|
||||
}
|
||||
|
||||
let date = NaiveDate::from_ymd_opt(tm.tm_year + 1900, tm.tm_mon as u32 + 1, tm.tm_mday as u32)
|
||||
.unwrap();
|
||||
let time = NaiveTime::from_hms_nano(
|
||||
tm.tm_hour as u32,
|
||||
tm.tm_min as u32,
|
||||
tm.tm_sec as u32,
|
||||
tm.tm_nsec as u32,
|
||||
);
|
||||
|
||||
let offset = FixedOffset::east_opt(tm.tm_utcoff).unwrap();
|
||||
DateTime::from_utc(date.and_time(time) - offset, offset)
|
||||
}
|
||||
|
||||
/// A record specifying a time value in seconds and nanoseconds, where
|
||||
/// nanoseconds represent the offset from the given second.
|
||||
///
|
||||
/// For example a timespec of 1.2 seconds after the beginning of the epoch would
|
||||
/// be represented as {sec: 1, nsec: 200000000}.
|
||||
struct Timespec {
|
||||
sec: i64,
|
||||
nsec: i32,
|
||||
}
|
||||
|
||||
impl Timespec {
|
||||
/// Constructs a timespec representing the current time in UTC.
|
||||
fn now() -> Timespec {
|
||||
let st =
|
||||
SystemTime::now().duration_since(UNIX_EPOCH).expect("system time before Unix epoch");
|
||||
Timespec { sec: st.as_secs() as i64, nsec: st.subsec_nanos() as i32 }
|
||||
}
|
||||
|
||||
/// Converts this timespec into the system's local time.
|
||||
fn local(self) -> Tm {
|
||||
let mut tm = Tm {
|
||||
tm_sec: 0,
|
||||
tm_min: 0,
|
||||
tm_hour: 0,
|
||||
tm_mday: 0,
|
||||
tm_mon: 0,
|
||||
tm_year: 0,
|
||||
tm_wday: 0,
|
||||
tm_yday: 0,
|
||||
tm_isdst: 0,
|
||||
tm_utcoff: 0,
|
||||
tm_nsec: 0,
|
||||
};
|
||||
time_to_local_tm(self.sec, &mut tm);
|
||||
tm.tm_nsec = self.nsec;
|
||||
tm
|
||||
}
|
||||
}
|
||||
|
||||
/// Holds a calendar date and time broken down into its components (year, month,
|
||||
/// day, and so on), also called a broken-down time value.
|
||||
// FIXME: use c_int instead of i32?
|
||||
#[repr(C)]
|
||||
struct Tm {
|
||||
/// Seconds after the minute - [0, 60]
|
||||
tm_sec: i32,
|
||||
|
||||
/// Minutes after the hour - [0, 59]
|
||||
tm_min: i32,
|
||||
|
||||
/// Hours after midnight - [0, 23]
|
||||
tm_hour: i32,
|
||||
|
||||
/// Day of the month - [1, 31]
|
||||
tm_mday: i32,
|
||||
|
||||
/// Months since January - [0, 11]
|
||||
tm_mon: i32,
|
||||
|
||||
/// Years since 1900
|
||||
tm_year: i32,
|
||||
|
||||
/// Days since Sunday - [0, 6]. 0 = Sunday, 1 = Monday, ..., 6 = Saturday.
|
||||
tm_wday: i32,
|
||||
|
||||
/// Days since January 1 - [0, 365]
|
||||
tm_yday: i32,
|
||||
|
||||
/// Daylight Saving Time flag.
|
||||
///
|
||||
/// This value is positive if Daylight Saving Time is in effect, zero if
|
||||
/// Daylight Saving Time is not in effect, and negative if this information
|
||||
/// is not available.
|
||||
tm_isdst: i32,
|
||||
|
||||
/// Identifies the time zone that was used to compute this broken-down time
|
||||
/// value, including any adjustment for Daylight Saving Time. This is the
|
||||
/// number of seconds east of UTC. For example, for U.S. Pacific Daylight
|
||||
/// Time, the value is `-7*60*60 = -25200`.
|
||||
tm_utcoff: i32,
|
||||
|
||||
/// Nanoseconds after the second - [0, 10<sup>9</sup> - 1]
|
||||
tm_nsec: i32,
|
||||
}
|
||||
|
||||
const HECTONANOSECS_IN_SEC: i64 = 10_000_000;
|
||||
const HECTONANOSEC_TO_UNIX_EPOCH: i64 = 11_644_473_600 * HECTONANOSECS_IN_SEC;
|
||||
|
||||
fn time_to_file_time(sec: i64) -> FILETIME {
|
||||
let t = ((sec * HECTONANOSECS_IN_SEC) + HECTONANOSEC_TO_UNIX_EPOCH) as u64;
|
||||
FILETIME { dwLowDateTime: t as DWORD, dwHighDateTime: (t >> 32) as DWORD }
|
||||
}
|
||||
|
||||
fn file_time_as_u64(ft: &FILETIME) -> u64 {
|
||||
((ft.dwHighDateTime as u64) << 32) | (ft.dwLowDateTime as u64)
|
||||
}
|
||||
|
||||
fn file_time_to_unix_seconds(ft: &FILETIME) -> i64 {
|
||||
let t = file_time_as_u64(ft) as i64;
|
||||
((t - HECTONANOSEC_TO_UNIX_EPOCH) / HECTONANOSECS_IN_SEC) as i64
|
||||
}
|
||||
|
||||
fn system_time_to_file_time(sys: &SYSTEMTIME) -> FILETIME {
|
||||
unsafe {
|
||||
let mut ft = mem::zeroed();
|
||||
SystemTimeToFileTime(sys, &mut ft);
|
||||
ft
|
||||
}
|
||||
}
|
||||
|
||||
fn tm_to_system_time(tm: &Tm) -> SYSTEMTIME {
|
||||
let mut sys: SYSTEMTIME = unsafe { mem::zeroed() };
|
||||
sys.wSecond = tm.tm_sec as WORD;
|
||||
sys.wMinute = tm.tm_min as WORD;
|
||||
sys.wHour = tm.tm_hour as WORD;
|
||||
sys.wDay = tm.tm_mday as WORD;
|
||||
sys.wDayOfWeek = tm.tm_wday as WORD;
|
||||
sys.wMonth = (tm.tm_mon + 1) as WORD;
|
||||
sys.wYear = (tm.tm_year + 1900) as WORD;
|
||||
sys
|
||||
}
|
||||
|
||||
fn system_time_to_tm(sys: &SYSTEMTIME, tm: &mut Tm) {
|
||||
tm.tm_sec = sys.wSecond as i32;
|
||||
tm.tm_min = sys.wMinute as i32;
|
||||
tm.tm_hour = sys.wHour as i32;
|
||||
tm.tm_mday = sys.wDay as i32;
|
||||
tm.tm_wday = sys.wDayOfWeek as i32;
|
||||
tm.tm_mon = (sys.wMonth - 1) as i32;
|
||||
tm.tm_year = (sys.wYear - 1900) as i32;
|
||||
tm.tm_yday = yday(tm.tm_year, tm.tm_mon + 1, tm.tm_mday);
|
||||
|
||||
fn yday(year: i32, month: i32, day: i32) -> i32 {
|
||||
let leap = if month > 2 {
|
||||
if year % 4 == 0 {
|
||||
1
|
||||
} else {
|
||||
2
|
||||
}
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let july = if month > 7 { 1 } else { 0 };
|
||||
|
||||
(month - 1) * 30 + month / 2 + (day - 1) - leap + july
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! call {
|
||||
($name:ident($($arg:expr),*)) => {
|
||||
if $name($($arg),*) == 0 {
|
||||
panic!(concat!(stringify!($name), " failed with: {}"),
|
||||
io::Error::last_os_error());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn time_to_local_tm(sec: i64, tm: &mut Tm) {
|
||||
let ft = time_to_file_time(sec);
|
||||
unsafe {
|
||||
let mut utc = mem::zeroed();
|
||||
let mut local = mem::zeroed();
|
||||
call!(FileTimeToSystemTime(&ft, &mut utc));
|
||||
call!(SystemTimeToTzSpecificLocalTime(0 as *const _, &mut utc, &mut local));
|
||||
system_time_to_tm(&local, tm);
|
||||
|
||||
let local = system_time_to_file_time(&local);
|
||||
let local_sec = file_time_to_unix_seconds(&local);
|
||||
|
||||
let mut tz = mem::zeroed();
|
||||
GetTimeZoneInformation(&mut tz);
|
||||
|
||||
// SystemTimeToTzSpecificLocalTime already applied the biases so
|
||||
// check if it non standard
|
||||
tm.tm_utcoff = (local_sec - sec) as i32;
|
||||
tm.tm_isdst = if tm.tm_utcoff == -60 * (tz.Bias + tz.StandardBias) { 0 } else { 1 };
|
||||
}
|
||||
}
|
||||
|
||||
fn utc_tm_to_time(tm: &Tm) -> i64 {
|
||||
unsafe {
|
||||
let mut ft = mem::zeroed();
|
||||
let sys_time = tm_to_system_time(tm);
|
||||
call!(SystemTimeToFileTime(&sys_time, &mut ft));
|
||||
file_time_to_unix_seconds(&ft)
|
||||
}
|
||||
}
|
||||
|
||||
fn local_tm_to_time(tm: &Tm) -> i64 {
|
||||
unsafe {
|
||||
let mut ft = mem::zeroed();
|
||||
let mut utc = mem::zeroed();
|
||||
let mut sys_time = tm_to_system_time(tm);
|
||||
call!(TzSpecificLocalTimeToSystemTime(0 as *mut _, &mut sys_time, &mut utc));
|
||||
call!(SystemTimeToFileTime(&utc, &mut ft));
|
||||
file_time_to_unix_seconds(&ft)
|
||||
}
|
||||
}
|
||||
549
vendor/chrono/src/offset/mod.rs
vendored
Normal file
549
vendor/chrono/src/offset/mod.rs
vendored
Normal file
|
|
@ -0,0 +1,549 @@
|
|||
// This is a part of Chrono.
|
||||
// See README.md and LICENSE.txt for details.
|
||||
|
||||
//! The time zone, which calculates offsets from the local time to UTC.
|
||||
//!
|
||||
//! There are four operations provided by the `TimeZone` trait:
|
||||
//!
|
||||
//! 1. Converting the local `NaiveDateTime` to `DateTime<Tz>`
|
||||
//! 2. Converting the UTC `NaiveDateTime` to `DateTime<Tz>`
|
||||
//! 3. Converting `DateTime<Tz>` to the local `NaiveDateTime`
|
||||
//! 4. Constructing `DateTime<Tz>` objects from various offsets
|
||||
//!
|
||||
//! 1 is used for constructors. 2 is used for the `with_timezone` method of date and time types.
|
||||
//! 3 is used for other methods, e.g. `year()` or `format()`, and provided by an associated type
|
||||
//! which implements `Offset` (which then passed to `TimeZone` for actual implementations).
|
||||
//! Technically speaking `TimeZone` has a total knowledge about given timescale,
|
||||
//! but `Offset` is used as a cache to avoid the repeated conversion
|
||||
//! and provides implementations for 1 and 3.
|
||||
//! An `TimeZone` instance can be reconstructed from the corresponding `Offset` instance.
|
||||
|
||||
use core::fmt;
|
||||
|
||||
use crate::format::{parse, ParseResult, Parsed, StrftimeItems};
|
||||
use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime};
|
||||
use crate::Weekday;
|
||||
#[allow(deprecated)]
|
||||
use crate::{Date, DateTime};
|
||||
|
||||
mod fixed;
|
||||
pub use self::fixed::FixedOffset;
|
||||
|
||||
#[cfg(feature = "clock")]
|
||||
mod local;
|
||||
#[cfg(feature = "clock")]
|
||||
pub use self::local::Local;
|
||||
|
||||
mod utc;
|
||||
pub use self::utc::Utc;
|
||||
|
||||
/// The conversion result from the local time to the timezone-aware datetime types.
|
||||
#[derive(Clone, PartialEq, Debug, Copy, Eq, Hash)]
|
||||
pub enum LocalResult<T> {
|
||||
/// Given local time representation is invalid.
|
||||
/// This can occur when, for example, the positive timezone transition.
|
||||
None,
|
||||
/// Given local time representation has a single unique result.
|
||||
Single(T),
|
||||
/// Given local time representation has multiple results and thus ambiguous.
|
||||
/// This can occur when, for example, the negative timezone transition.
|
||||
Ambiguous(T /*min*/, T /*max*/),
|
||||
}
|
||||
|
||||
impl<T> LocalResult<T> {
|
||||
/// Returns `Some` only when the conversion result is unique, or `None` otherwise.
|
||||
pub fn single(self) -> Option<T> {
|
||||
match self {
|
||||
LocalResult::Single(t) => Some(t),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `Some` for the earliest possible conversion result, or `None` if none.
|
||||
pub fn earliest(self) -> Option<T> {
|
||||
match self {
|
||||
LocalResult::Single(t) | LocalResult::Ambiguous(t, _) => Some(t),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `Some` for the latest possible conversion result, or `None` if none.
|
||||
pub fn latest(self) -> Option<T> {
|
||||
match self {
|
||||
LocalResult::Single(t) | LocalResult::Ambiguous(_, t) => Some(t),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Maps a `LocalResult<T>` into `LocalResult<U>` with given function.
|
||||
pub fn map<U, F: FnMut(T) -> U>(self, mut f: F) -> LocalResult<U> {
|
||||
match self {
|
||||
LocalResult::None => LocalResult::None,
|
||||
LocalResult::Single(v) => LocalResult::Single(f(v)),
|
||||
LocalResult::Ambiguous(min, max) => LocalResult::Ambiguous(f(min), f(max)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl<Tz: TimeZone> LocalResult<Date<Tz>> {
|
||||
/// Makes a new `DateTime` from the current date and given `NaiveTime`.
|
||||
/// The offset in the current date is preserved.
|
||||
///
|
||||
/// Propagates any error. Ambiguous result would be discarded.
|
||||
#[inline]
|
||||
pub fn and_time(self, time: NaiveTime) -> LocalResult<DateTime<Tz>> {
|
||||
match self {
|
||||
LocalResult::Single(d) => {
|
||||
d.and_time(time).map_or(LocalResult::None, LocalResult::Single)
|
||||
}
|
||||
_ => LocalResult::None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Makes a new `DateTime` from the current date, hour, minute and second.
|
||||
/// The offset in the current date is preserved.
|
||||
///
|
||||
/// Propagates any error. Ambiguous result would be discarded.
|
||||
#[inline]
|
||||
pub fn and_hms_opt(self, hour: u32, min: u32, sec: u32) -> LocalResult<DateTime<Tz>> {
|
||||
match self {
|
||||
LocalResult::Single(d) => {
|
||||
d.and_hms_opt(hour, min, sec).map_or(LocalResult::None, LocalResult::Single)
|
||||
}
|
||||
_ => LocalResult::None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Makes a new `DateTime` from the current date, hour, minute, second and millisecond.
|
||||
/// The millisecond part can exceed 1,000 in order to represent the leap second.
|
||||
/// The offset in the current date is preserved.
|
||||
///
|
||||
/// Propagates any error. Ambiguous result would be discarded.
|
||||
#[inline]
|
||||
pub fn and_hms_milli_opt(
|
||||
self,
|
||||
hour: u32,
|
||||
min: u32,
|
||||
sec: u32,
|
||||
milli: u32,
|
||||
) -> LocalResult<DateTime<Tz>> {
|
||||
match self {
|
||||
LocalResult::Single(d) => d
|
||||
.and_hms_milli_opt(hour, min, sec, milli)
|
||||
.map_or(LocalResult::None, LocalResult::Single),
|
||||
_ => LocalResult::None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Makes a new `DateTime` from the current date, hour, minute, second and microsecond.
|
||||
/// The microsecond part can exceed 1,000,000 in order to represent the leap second.
|
||||
/// The offset in the current date is preserved.
|
||||
///
|
||||
/// Propagates any error. Ambiguous result would be discarded.
|
||||
#[inline]
|
||||
pub fn and_hms_micro_opt(
|
||||
self,
|
||||
hour: u32,
|
||||
min: u32,
|
||||
sec: u32,
|
||||
micro: u32,
|
||||
) -> LocalResult<DateTime<Tz>> {
|
||||
match self {
|
||||
LocalResult::Single(d) => d
|
||||
.and_hms_micro_opt(hour, min, sec, micro)
|
||||
.map_or(LocalResult::None, LocalResult::Single),
|
||||
_ => LocalResult::None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Makes a new `DateTime` from the current date, hour, minute, second and nanosecond.
|
||||
/// The nanosecond part can exceed 1,000,000,000 in order to represent the leap second.
|
||||
/// The offset in the current date is preserved.
|
||||
///
|
||||
/// Propagates any error. Ambiguous result would be discarded.
|
||||
#[inline]
|
||||
pub fn and_hms_nano_opt(
|
||||
self,
|
||||
hour: u32,
|
||||
min: u32,
|
||||
sec: u32,
|
||||
nano: u32,
|
||||
) -> LocalResult<DateTime<Tz>> {
|
||||
match self {
|
||||
LocalResult::Single(d) => d
|
||||
.and_hms_nano_opt(hour, min, sec, nano)
|
||||
.map_or(LocalResult::None, LocalResult::Single),
|
||||
_ => LocalResult::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: fmt::Debug> LocalResult<T> {
|
||||
/// Returns the single unique conversion result, or panics accordingly.
|
||||
pub fn unwrap(self) -> T {
|
||||
match self {
|
||||
LocalResult::None => panic!("No such local time"),
|
||||
LocalResult::Single(t) => t,
|
||||
LocalResult::Ambiguous(t1, t2) => {
|
||||
panic!("Ambiguous local time, ranging from {:?} to {:?}", t1, t2)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The offset from the local time to UTC.
|
||||
pub trait Offset: Sized + Clone + fmt::Debug {
|
||||
/// Returns the fixed offset from UTC to the local time stored.
|
||||
fn fix(&self) -> FixedOffset;
|
||||
}
|
||||
|
||||
/// The time zone.
|
||||
///
|
||||
/// The methods here are the primarily constructors for [`Date`](../struct.Date.html) and
|
||||
/// [`DateTime`](../struct.DateTime.html) types.
|
||||
pub trait TimeZone: Sized + Clone {
|
||||
/// An associated offset type.
|
||||
/// This type is used to store the actual offset in date and time types.
|
||||
/// The original `TimeZone` value can be recovered via `TimeZone::from_offset`.
|
||||
type Offset: Offset;
|
||||
|
||||
/// Make a new `DateTime` from year, month, day, time components and current time zone.
|
||||
///
|
||||
/// This assumes the proleptic Gregorian calendar, with the year 0 being 1 BCE.
|
||||
///
|
||||
/// Returns `LocalResult::None` on invalid input data.
|
||||
fn with_ymd_and_hms(
|
||||
&self,
|
||||
year: i32,
|
||||
month: u32,
|
||||
day: u32,
|
||||
hour: u32,
|
||||
min: u32,
|
||||
sec: u32,
|
||||
) -> LocalResult<DateTime<Self>> {
|
||||
match NaiveDate::from_ymd_opt(year, month, day).and_then(|d| d.and_hms_opt(hour, min, sec))
|
||||
{
|
||||
Some(dt) => self.from_local_datetime(&dt),
|
||||
None => LocalResult::None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Makes a new `Date` from year, month, day and the current time zone.
|
||||
/// This assumes the proleptic Gregorian calendar, with the year 0 being 1 BCE.
|
||||
///
|
||||
/// The time zone normally does not affect the date (unless it is between UTC-24 and UTC+24),
|
||||
/// but it will propagate to the `DateTime` values constructed via this date.
|
||||
///
|
||||
/// Panics on the out-of-range date, invalid month and/or day.
|
||||
#[deprecated(since = "0.4.23", note = "use `with_ymd_and_hms()` instead")]
|
||||
#[allow(deprecated)]
|
||||
fn ymd(&self, year: i32, month: u32, day: u32) -> Date<Self> {
|
||||
self.ymd_opt(year, month, day).unwrap()
|
||||
}
|
||||
|
||||
/// Makes a new `Date` from year, month, day and the current time zone.
|
||||
/// This assumes the proleptic Gregorian calendar, with the year 0 being 1 BCE.
|
||||
///
|
||||
/// The time zone normally does not affect the date (unless it is between UTC-24 and UTC+24),
|
||||
/// but it will propagate to the `DateTime` values constructed via this date.
|
||||
///
|
||||
/// Returns `None` on the out-of-range date, invalid month and/or day.
|
||||
#[deprecated(since = "0.4.23", note = "use `with_ymd_and_hms()` instead")]
|
||||
#[allow(deprecated)]
|
||||
fn ymd_opt(&self, year: i32, month: u32, day: u32) -> LocalResult<Date<Self>> {
|
||||
match NaiveDate::from_ymd_opt(year, month, day) {
|
||||
Some(d) => self.from_local_date(&d),
|
||||
None => LocalResult::None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Makes a new `Date` from year, day of year (DOY or "ordinal") and the current time zone.
|
||||
/// This assumes the proleptic Gregorian calendar, with the year 0 being 1 BCE.
|
||||
///
|
||||
/// The time zone normally does not affect the date (unless it is between UTC-24 and UTC+24),
|
||||
/// but it will propagate to the `DateTime` values constructed via this date.
|
||||
///
|
||||
/// Panics on the out-of-range date and/or invalid DOY.
|
||||
#[deprecated(
|
||||
since = "0.4.23",
|
||||
note = "use `from_local_datetime()` with a `NaiveDateTime` instead"
|
||||
)]
|
||||
#[allow(deprecated)]
|
||||
fn yo(&self, year: i32, ordinal: u32) -> Date<Self> {
|
||||
self.yo_opt(year, ordinal).unwrap()
|
||||
}
|
||||
|
||||
/// Makes a new `Date` from year, day of year (DOY or "ordinal") and the current time zone.
|
||||
/// This assumes the proleptic Gregorian calendar, with the year 0 being 1 BCE.
|
||||
///
|
||||
/// The time zone normally does not affect the date (unless it is between UTC-24 and UTC+24),
|
||||
/// but it will propagate to the `DateTime` values constructed via this date.
|
||||
///
|
||||
/// Returns `None` on the out-of-range date and/or invalid DOY.
|
||||
#[deprecated(
|
||||
since = "0.4.23",
|
||||
note = "use `from_local_datetime()` with a `NaiveDateTime` instead"
|
||||
)]
|
||||
#[allow(deprecated)]
|
||||
fn yo_opt(&self, year: i32, ordinal: u32) -> LocalResult<Date<Self>> {
|
||||
match NaiveDate::from_yo_opt(year, ordinal) {
|
||||
Some(d) => self.from_local_date(&d),
|
||||
None => LocalResult::None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Makes a new `Date` from ISO week date (year and week number), day of the week (DOW) and
|
||||
/// the current time zone.
|
||||
/// This assumes the proleptic Gregorian calendar, with the year 0 being 1 BCE.
|
||||
/// The resulting `Date` may have a different year from the input year.
|
||||
///
|
||||
/// The time zone normally does not affect the date (unless it is between UTC-24 and UTC+24),
|
||||
/// but it will propagate to the `DateTime` values constructed via this date.
|
||||
///
|
||||
/// Panics on the out-of-range date and/or invalid week number.
|
||||
#[deprecated(
|
||||
since = "0.4.23",
|
||||
note = "use `from_local_datetime()` with a `NaiveDateTime` instead"
|
||||
)]
|
||||
#[allow(deprecated)]
|
||||
fn isoywd(&self, year: i32, week: u32, weekday: Weekday) -> Date<Self> {
|
||||
self.isoywd_opt(year, week, weekday).unwrap()
|
||||
}
|
||||
|
||||
/// Makes a new `Date` from ISO week date (year and week number), day of the week (DOW) and
|
||||
/// the current time zone.
|
||||
/// This assumes the proleptic Gregorian calendar, with the year 0 being 1 BCE.
|
||||
/// The resulting `Date` may have a different year from the input year.
|
||||
///
|
||||
/// The time zone normally does not affect the date (unless it is between UTC-24 and UTC+24),
|
||||
/// but it will propagate to the `DateTime` values constructed via this date.
|
||||
///
|
||||
/// Returns `None` on the out-of-range date and/or invalid week number.
|
||||
#[deprecated(
|
||||
since = "0.4.23",
|
||||
note = "use `from_local_datetime()` with a `NaiveDateTime` instead"
|
||||
)]
|
||||
#[allow(deprecated)]
|
||||
fn isoywd_opt(&self, year: i32, week: u32, weekday: Weekday) -> LocalResult<Date<Self>> {
|
||||
match NaiveDate::from_isoywd_opt(year, week, weekday) {
|
||||
Some(d) => self.from_local_date(&d),
|
||||
None => LocalResult::None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Makes a new `DateTime` from the number of non-leap seconds
|
||||
/// since January 1, 1970 0:00:00 UTC (aka "UNIX timestamp")
|
||||
/// and the number of nanoseconds since the last whole non-leap second.
|
||||
///
|
||||
/// Panics on the out-of-range number of seconds and/or invalid nanosecond,
|
||||
/// for a non-panicking version see [`timestamp_opt`](#method.timestamp_opt).
|
||||
#[deprecated(since = "0.4.23", note = "use `timestamp_opt()` instead")]
|
||||
fn timestamp(&self, secs: i64, nsecs: u32) -> DateTime<Self> {
|
||||
self.timestamp_opt(secs, nsecs).unwrap()
|
||||
}
|
||||
|
||||
/// Makes a new `DateTime` from the number of non-leap seconds
|
||||
/// since January 1, 1970 0:00:00 UTC (aka "UNIX timestamp")
|
||||
/// and the number of nanoseconds since the last whole non-leap second.
|
||||
///
|
||||
/// Returns `LocalResult::None` on out-of-range number of seconds and/or
|
||||
/// invalid nanosecond, otherwise always returns `LocalResult::Single`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use chrono::{Utc, TimeZone};
|
||||
///
|
||||
/// assert_eq!(Utc.timestamp_opt(1431648000, 0).unwrap().to_string(), "2015-05-15 00:00:00 UTC");
|
||||
/// ```
|
||||
fn timestamp_opt(&self, secs: i64, nsecs: u32) -> LocalResult<DateTime<Self>> {
|
||||
match NaiveDateTime::from_timestamp_opt(secs, nsecs) {
|
||||
Some(dt) => LocalResult::Single(self.from_utc_datetime(&dt)),
|
||||
None => LocalResult::None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Makes a new `DateTime` from the number of non-leap milliseconds
|
||||
/// since January 1, 1970 0:00:00 UTC (aka "UNIX timestamp").
|
||||
///
|
||||
/// Panics on out-of-range number of milliseconds for a non-panicking
|
||||
/// version see [`timestamp_millis_opt`](#method.timestamp_millis_opt).
|
||||
#[deprecated(since = "0.4.23", note = "use `timestamp_millis_opt()` instead")]
|
||||
fn timestamp_millis(&self, millis: i64) -> DateTime<Self> {
|
||||
self.timestamp_millis_opt(millis).unwrap()
|
||||
}
|
||||
|
||||
/// Makes a new `DateTime` from the number of non-leap milliseconds
|
||||
/// since January 1, 1970 0:00:00 UTC (aka "UNIX timestamp").
|
||||
///
|
||||
///
|
||||
/// Returns `LocalResult::None` on out-of-range number of milliseconds
|
||||
/// and/or invalid nanosecond, otherwise always returns
|
||||
/// `LocalResult::Single`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use chrono::{Utc, TimeZone, LocalResult};
|
||||
/// match Utc.timestamp_millis_opt(1431648000) {
|
||||
/// LocalResult::Single(dt) => assert_eq!(dt.timestamp(), 1431648),
|
||||
/// _ => panic!("Incorrect timestamp_millis"),
|
||||
/// };
|
||||
/// ```
|
||||
fn timestamp_millis_opt(&self, millis: i64) -> LocalResult<DateTime<Self>> {
|
||||
let (mut secs, mut millis) = (millis / 1000, millis % 1000);
|
||||
if millis < 0 {
|
||||
secs -= 1;
|
||||
millis += 1000;
|
||||
}
|
||||
self.timestamp_opt(secs, millis as u32 * 1_000_000)
|
||||
}
|
||||
|
||||
/// Makes a new `DateTime` from the number of non-leap nanoseconds
|
||||
/// since January 1, 1970 0:00:00 UTC (aka "UNIX timestamp").
|
||||
///
|
||||
/// Unlike [`timestamp_millis`](#method.timestamp_millis), this never
|
||||
/// panics.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use chrono::{Utc, TimeZone};
|
||||
///
|
||||
/// assert_eq!(Utc.timestamp_nanos(1431648000000000).timestamp(), 1431648);
|
||||
/// ```
|
||||
fn timestamp_nanos(&self, nanos: i64) -> DateTime<Self> {
|
||||
let (mut secs, mut nanos) = (nanos / 1_000_000_000, nanos % 1_000_000_000);
|
||||
if nanos < 0 {
|
||||
secs -= 1;
|
||||
nanos += 1_000_000_000;
|
||||
}
|
||||
self.timestamp_opt(secs, nanos as u32).unwrap()
|
||||
}
|
||||
|
||||
/// Parses a string with the specified format string and returns a
|
||||
/// `DateTime` with the current offset.
|
||||
///
|
||||
/// See the [`crate::format::strftime`] module on the
|
||||
/// supported escape sequences.
|
||||
///
|
||||
/// If the to-be-parsed string includes an offset, it *must* match the
|
||||
/// offset of the TimeZone, otherwise an error will be returned.
|
||||
///
|
||||
/// See also [`DateTime::parse_from_str`] which gives a [`DateTime`] with
|
||||
/// parsed [`FixedOffset`].
|
||||
fn datetime_from_str(&self, s: &str, fmt: &str) -> ParseResult<DateTime<Self>> {
|
||||
let mut parsed = Parsed::new();
|
||||
parse(&mut parsed, s, StrftimeItems::new(fmt))?;
|
||||
parsed.to_datetime_with_timezone(self)
|
||||
}
|
||||
|
||||
/// Reconstructs the time zone from the offset.
|
||||
fn from_offset(offset: &Self::Offset) -> Self;
|
||||
|
||||
/// Creates the offset(s) for given local `NaiveDate` if possible.
|
||||
fn offset_from_local_date(&self, local: &NaiveDate) -> LocalResult<Self::Offset>;
|
||||
|
||||
/// Creates the offset(s) for given local `NaiveDateTime` if possible.
|
||||
fn offset_from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult<Self::Offset>;
|
||||
|
||||
/// Converts the local `NaiveDate` to the timezone-aware `Date` if possible.
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
#[deprecated(since = "0.4.23", note = "use `from_local_datetime()` instead")]
|
||||
#[allow(deprecated)]
|
||||
fn from_local_date(&self, local: &NaiveDate) -> LocalResult<Date<Self>> {
|
||||
self.offset_from_local_date(local).map(|offset| {
|
||||
// since FixedOffset is within +/- 1 day, the date is never affected
|
||||
Date::from_utc(*local, offset)
|
||||
})
|
||||
}
|
||||
|
||||
/// Converts the local `NaiveDateTime` to the timezone-aware `DateTime` if possible.
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
fn from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult<DateTime<Self>> {
|
||||
self.offset_from_local_datetime(local)
|
||||
.map(|offset| DateTime::from_utc(*local - offset.fix(), offset))
|
||||
}
|
||||
|
||||
/// Creates the offset for given UTC `NaiveDate`. This cannot fail.
|
||||
fn offset_from_utc_date(&self, utc: &NaiveDate) -> Self::Offset;
|
||||
|
||||
/// Creates the offset for given UTC `NaiveDateTime`. This cannot fail.
|
||||
fn offset_from_utc_datetime(&self, utc: &NaiveDateTime) -> Self::Offset;
|
||||
|
||||
/// Converts the UTC `NaiveDate` to the local time.
|
||||
/// The UTC is continuous and thus this cannot fail (but can give the duplicate local time).
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
#[deprecated(since = "0.4.23", note = "use `from_utc_datetime()` instead")]
|
||||
#[allow(deprecated)]
|
||||
fn from_utc_date(&self, utc: &NaiveDate) -> Date<Self> {
|
||||
Date::from_utc(*utc, self.offset_from_utc_date(utc))
|
||||
}
|
||||
|
||||
/// Converts the UTC `NaiveDateTime` to the local time.
|
||||
/// The UTC is continuous and thus this cannot fail (but can give the duplicate local time).
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
fn from_utc_datetime(&self, utc: &NaiveDateTime) -> DateTime<Self> {
|
||||
DateTime::from_utc(*utc, self.offset_from_utc_datetime(utc))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_negative_millis() {
|
||||
let dt = Utc.timestamp_millis_opt(-1000).unwrap();
|
||||
assert_eq!(dt.to_string(), "1969-12-31 23:59:59 UTC");
|
||||
let dt = Utc.timestamp_millis_opt(-7000).unwrap();
|
||||
assert_eq!(dt.to_string(), "1969-12-31 23:59:53 UTC");
|
||||
let dt = Utc.timestamp_millis_opt(-7001).unwrap();
|
||||
assert_eq!(dt.to_string(), "1969-12-31 23:59:52.999 UTC");
|
||||
let dt = Utc.timestamp_millis_opt(-7003).unwrap();
|
||||
assert_eq!(dt.to_string(), "1969-12-31 23:59:52.997 UTC");
|
||||
let dt = Utc.timestamp_millis_opt(-999).unwrap();
|
||||
assert_eq!(dt.to_string(), "1969-12-31 23:59:59.001 UTC");
|
||||
let dt = Utc.timestamp_millis_opt(-1).unwrap();
|
||||
assert_eq!(dt.to_string(), "1969-12-31 23:59:59.999 UTC");
|
||||
let dt = Utc.timestamp_millis_opt(-60000).unwrap();
|
||||
assert_eq!(dt.to_string(), "1969-12-31 23:59:00 UTC");
|
||||
let dt = Utc.timestamp_millis_opt(-3600000).unwrap();
|
||||
assert_eq!(dt.to_string(), "1969-12-31 23:00:00 UTC");
|
||||
|
||||
for (millis, expected) in &[
|
||||
(-7000, "1969-12-31 23:59:53 UTC"),
|
||||
(-7001, "1969-12-31 23:59:52.999 UTC"),
|
||||
(-7003, "1969-12-31 23:59:52.997 UTC"),
|
||||
] {
|
||||
match Utc.timestamp_millis_opt(*millis) {
|
||||
LocalResult::Single(dt) => {
|
||||
assert_eq!(dt.to_string(), *expected);
|
||||
}
|
||||
e => panic!("Got {:?} instead of an okay answer", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_negative_nanos() {
|
||||
let dt = Utc.timestamp_nanos(-1_000_000_000);
|
||||
assert_eq!(dt.to_string(), "1969-12-31 23:59:59 UTC");
|
||||
let dt = Utc.timestamp_nanos(-999_999_999);
|
||||
assert_eq!(dt.to_string(), "1969-12-31 23:59:59.000000001 UTC");
|
||||
let dt = Utc.timestamp_nanos(-1);
|
||||
assert_eq!(dt.to_string(), "1969-12-31 23:59:59.999999999 UTC");
|
||||
let dt = Utc.timestamp_nanos(-60_000_000_000);
|
||||
assert_eq!(dt.to_string(), "1969-12-31 23:59:00 UTC");
|
||||
let dt = Utc.timestamp_nanos(-3_600_000_000_000);
|
||||
assert_eq!(dt.to_string(), "1969-12-31 23:00:00 UTC");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nanos_never_panics() {
|
||||
Utc.timestamp_nanos(i64::max_value());
|
||||
Utc.timestamp_nanos(i64::default());
|
||||
Utc.timestamp_nanos(i64::min_value());
|
||||
}
|
||||
}
|
||||
126
vendor/chrono/src/offset/utc.rs
vendored
Normal file
126
vendor/chrono/src/offset/utc.rs
vendored
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
// This is a part of Chrono.
|
||||
// See README.md and LICENSE.txt for details.
|
||||
|
||||
//! The UTC (Coordinated Universal Time) time zone.
|
||||
|
||||
use core::fmt;
|
||||
#[cfg(all(
|
||||
feature = "clock",
|
||||
not(all(
|
||||
target_arch = "wasm32",
|
||||
feature = "wasmbind",
|
||||
not(any(target_os = "emscripten", target_os = "wasi"))
|
||||
))
|
||||
))]
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
#[cfg(feature = "rkyv")]
|
||||
use rkyv::{Archive, Deserialize, Serialize};
|
||||
|
||||
use super::{FixedOffset, LocalResult, Offset, TimeZone};
|
||||
use crate::naive::{NaiveDate, NaiveDateTime};
|
||||
#[cfg(feature = "clock")]
|
||||
#[allow(deprecated)]
|
||||
use crate::{Date, DateTime};
|
||||
|
||||
/// The UTC time zone. This is the most efficient time zone when you don't need the local time.
|
||||
/// It is also used as an offset (which is also a dummy type).
|
||||
///
|
||||
/// Using the [`TimeZone`](./trait.TimeZone.html) methods
|
||||
/// on the UTC struct is the preferred way to construct `DateTime<Utc>`
|
||||
/// instances.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use chrono::{DateTime, TimeZone, NaiveDateTime, Utc};
|
||||
///
|
||||
/// let dt = DateTime::<Utc>::from_utc(NaiveDateTime::from_timestamp(61, 0), Utc);
|
||||
///
|
||||
/// assert_eq!(Utc.timestamp(61, 0), dt);
|
||||
/// assert_eq!(Utc.with_ymd_and_hms(1970, 1, 1, 0, 1, 1).unwrap(), dt);
|
||||
/// ```
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))]
|
||||
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
||||
pub struct Utc;
|
||||
|
||||
#[cfg(feature = "clock")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "clock")))]
|
||||
impl Utc {
|
||||
/// Returns a `Date` which corresponds to the current date.
|
||||
#[deprecated(
|
||||
since = "0.4.23",
|
||||
note = "use `Utc::now()` instead, potentially with `.date_naive()`"
|
||||
)]
|
||||
#[allow(deprecated)]
|
||||
pub fn today() -> Date<Utc> {
|
||||
Utc::now().date()
|
||||
}
|
||||
|
||||
/// Returns a `DateTime` which corresponds to the current date and time.
|
||||
#[cfg(not(all(
|
||||
target_arch = "wasm32",
|
||||
feature = "wasmbind",
|
||||
not(any(target_os = "emscripten", target_os = "wasi"))
|
||||
)))]
|
||||
pub fn now() -> DateTime<Utc> {
|
||||
let now =
|
||||
SystemTime::now().duration_since(UNIX_EPOCH).expect("system time before Unix epoch");
|
||||
let naive =
|
||||
NaiveDateTime::from_timestamp_opt(now.as_secs() as i64, now.subsec_nanos() as u32)
|
||||
.unwrap();
|
||||
DateTime::from_utc(naive, Utc)
|
||||
}
|
||||
|
||||
/// Returns a `DateTime` which corresponds to the current date and time.
|
||||
#[cfg(all(
|
||||
target_arch = "wasm32",
|
||||
feature = "wasmbind",
|
||||
not(any(target_os = "emscripten", target_os = "wasi"))
|
||||
))]
|
||||
pub fn now() -> DateTime<Utc> {
|
||||
let now = js_sys::Date::new_0();
|
||||
DateTime::<Utc>::from(now)
|
||||
}
|
||||
}
|
||||
|
||||
impl TimeZone for Utc {
|
||||
type Offset = Utc;
|
||||
|
||||
fn from_offset(_state: &Utc) -> Utc {
|
||||
Utc
|
||||
}
|
||||
|
||||
fn offset_from_local_date(&self, _local: &NaiveDate) -> LocalResult<Utc> {
|
||||
LocalResult::Single(Utc)
|
||||
}
|
||||
fn offset_from_local_datetime(&self, _local: &NaiveDateTime) -> LocalResult<Utc> {
|
||||
LocalResult::Single(Utc)
|
||||
}
|
||||
|
||||
fn offset_from_utc_date(&self, _utc: &NaiveDate) -> Utc {
|
||||
Utc
|
||||
}
|
||||
fn offset_from_utc_datetime(&self, _utc: &NaiveDateTime) -> Utc {
|
||||
Utc
|
||||
}
|
||||
}
|
||||
|
||||
impl Offset for Utc {
|
||||
fn fix(&self) -> FixedOffset {
|
||||
FixedOffset::east_opt(0).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Utc {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "Z")
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Utc {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "UTC")
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue