Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions src/datetime/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,7 @@ impl<Tz: TimeZone> DateTime<Tz> {
.checked_add_months(rhs)?
.and_local_timezone(Tz::from_offset(&self.offset))
.single()
.ok()
}

/// Subtracts given `TimeDelta` from the current date and time.
Expand Down Expand Up @@ -399,6 +400,7 @@ impl<Tz: TimeZone> DateTime<Tz> {
.checked_sub_months(rhs)?
.and_local_timezone(Tz::from_offset(&self.offset))
.single()
.ok()
}

/// Add a duration in [`Days`] to the date part of the `DateTime`.
Expand All @@ -415,6 +417,7 @@ impl<Tz: TimeZone> DateTime<Tz> {
.checked_add_days(days)?
.and_local_timezone(TimeZone::from_offset(&self.offset))
.single()
.ok()
}

/// Subtract a duration in [`Days`] from the date part of the `DateTime`.
Expand All @@ -431,6 +434,7 @@ impl<Tz: TimeZone> DateTime<Tz> {
.checked_sub_days(days)?
.and_local_timezone(TimeZone::from_offset(&self.offset))
.single()
.ok()
}

/// Subtracts another `DateTime` from the current date and time.
Expand Down Expand Up @@ -732,7 +736,7 @@ where
F: FnMut(NaiveDateTime) -> Option<NaiveDateTime>,
{
f(dt.overflowing_naive_local())
.and_then(|datetime| dt.timezone().from_local_datetime(&datetime).single())
.and_then(|datetime| dt.timezone().from_local_datetime(&datetime).single().ok())
.filter(|dt| dt >= &DateTime::<Utc>::MIN_UTC && dt <= &DateTime::<Utc>::MAX_UTC)
}

Expand Down Expand Up @@ -1645,7 +1649,7 @@ impl TryFrom<SystemTime> for DateTime<Utc> {
}
}
};
Utc.timestamp(sec, nsec).single().ok_or(OutOfRange::new())
Utc.timestamp(sec, nsec).single().map_err(|_| OutOfRange::new())
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/datetime/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ impl TimeZone for DstTester {
} else if *local >= local_to_summer_transition_start
&& *local < local_to_summer_transition_end
{
LocalResult::None
LocalResult::InGap
} else {
panic!("Unexpected local time {}", local)
}
Expand Down
13 changes: 13 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
//! Error type
use core::fmt;

use crate::offset::TzLookupError;

/// Error type for date and time operations.
#[non_exhaustive]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum Error {
/// There is not enough information to create a date/time.
///
/// An example is parsing a string with not enough date/time fields, or the result of a
/// time that is ambiguous during a time zone transitions (due to for example DST).
Ambiguous,

/// A date or datetime does not exist.
///
/// Examples are:
Expand All @@ -25,14 +33,19 @@ pub enum Error {
/// An example is a date for the year 500.000, which is out of the range supported by chrono's
/// types.
OutOfRange,

/// Lookup of a local datetime in a timezone failed.
TzLookupFailure(TzLookupError),
}

impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Error::Ambiguous => write!(f, "not enough information for a concrete date/time"),
Error::DoesNotExist => write!(f, "date or datetime does not exist"),
Error::InvalidArgument => write!(f, "invalid parameter"),
Error::OutOfRange => write!(f, "date outside of the supported range"),
Error::TzLookupFailure(e) => fmt::Display::fmt(&e, f),
}
}
}
Expand Down
8 changes: 2 additions & 6 deletions src/format/parsed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -629,11 +629,7 @@ impl Parsed {
let datetime = self.to_naive_datetime_with_offset(offset)?;
let offset = FixedOffset::east(offset).ok_or(OUT_OF_RANGE)?;

match offset.from_local_datetime(&datetime) {
LocalResult::None => Err(IMPOSSIBLE),
LocalResult::Single(t) => Ok(t),
LocalResult::Ambiguous(..) => Err(NOT_ENOUGH),
}
Ok(offset.from_local_datetime(&datetime).single().unwrap())
}

/// Returns a parsed timezone-aware date and time out of given fields,
Expand Down Expand Up @@ -670,7 +666,6 @@ impl Parsed {
// it will be 0 otherwise, but this is fine as the algorithm ignores offset for that case.
let datetime = self.to_naive_datetime_with_offset(guessed_offset)?;
match tz.from_local_datetime(&datetime) {
LocalResult::None => Err(IMPOSSIBLE),
LocalResult::Single(t) => {
if check_offset(&t) {
Ok(t)
Expand All @@ -687,6 +682,7 @@ impl Parsed {
(true, true) => Err(NOT_ENOUGH),
}
}
LocalResult::InGap | LocalResult::Error(_) => Err(IMPOSSIBLE),
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@
//!
#![cfg_attr(not(feature = "now"), doc = "```ignore")]
#![cfg_attr(feature = "now", doc = "```rust")]
//! use chrono::offset::LocalResult;
//! use chrono::offset::{LocalResult, TzLookupError};
//! use chrono::prelude::*;
//!
//! # fn doctest() -> Option<()> {
Expand All @@ -143,8 +143,8 @@
//! // dynamic verification
//! assert_eq!(Utc.with_ymd_and_hms(2014, 7, 8, 21, 15, 33),
//! LocalResult::Single(NaiveDate::from_ymd(2014, 7, 8).unwrap().and_hms(21, 15, 33).unwrap().and_utc()));
//! assert_eq!(Utc.with_ymd_and_hms(2014, 7, 8, 80, 15, 33), LocalResult::None);
//! assert_eq!(Utc.with_ymd_and_hms(2014, 7, 38, 21, 15, 33), LocalResult::None);
//! assert_eq!(Utc.with_ymd_and_hms(2014, 7, 8, 80, 15, 33), LocalResult::Error(TzLookupError::Other));
//! assert_eq!(Utc.with_ymd_and_hms(2014, 7, 38, 21, 15, 33), LocalResult::Error(TzLookupError::Other));
//!
//! # #[cfg(feature = "clock")] {
//! // other time zone objects can be used to construct a local datetime.
Expand Down
5 changes: 3 additions & 2 deletions src/naive/datetime/tests.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use super::NaiveDateTime;
use crate::offset::TzLookupError;
use crate::{Datelike, FixedOffset, LocalResult, NaiveDate, TimeDelta, Utc};

#[test]
Expand Down Expand Up @@ -563,13 +564,13 @@ fn test_and_timezone_min_max_dates() {
if offset_hour >= 0 {
assert_eq!(local_max.unwrap().naive_local(), NaiveDateTime::MAX);
} else {
assert_eq!(local_max, LocalResult::None);
assert_eq!(local_max, LocalResult::Error(TzLookupError::OutOfRange));
}
let local_min = NaiveDateTime::MIN.and_local_timezone(offset);
if offset_hour <= 0 {
assert_eq!(local_min.unwrap().naive_local(), NaiveDateTime::MIN);
} else {
assert_eq!(local_min, LocalResult::None);
assert_eq!(local_min, LocalResult::Error(TzLookupError::OutOfRange));
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/offset/local/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ fn lookup_with_dst_transitions(
} else if dt == wall_latest {
LocalResult::Single(t.offset_after)
} else {
LocalResult::None
LocalResult::InGap
}
}
};
Expand Down
25 changes: 25 additions & 0 deletions src/offset/local/tz_info/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ use std::{error, fmt, io};
mod timezone;
pub(crate) use timezone::TimeZone;

use crate::offset::TzLookupError;

mod parser;
mod rule;

Expand Down Expand Up @@ -100,6 +102,29 @@ impl From<Utf8Error> for Error {
}
}

impl From<Error> for TzLookupError {
fn from(error: Error) -> TzLookupError {
match error {
Error::DateTime(_) => TzLookupError::InvalidTimeZoneData,
Error::FindLocalTimeType(_) => TzLookupError::InvalidTimeZoneData,
Error::LocalTimeType(_) => TzLookupError::InvalidTimeZoneData,
Error::InvalidSlice(_) => TzLookupError::InvalidTimeZoneData,
Error::InvalidTzString(_) => TzLookupError::InvalidTzString,
Error::InvalidTzFile(_) => TzLookupError::InvalidTimeZoneData,
Error::Io(_) => TzLookupError::InvalidTimeZoneData,
Error::OutOfRange(_) => TzLookupError::InvalidTimeZoneData,
Error::ParseInt(_) => TzLookupError::InvalidTimeZoneData,
Error::ProjectDateTime(_) => TzLookupError::InvalidTimeZoneData,
Error::SystemTime(_) => TzLookupError::InvalidTimeZoneData,
Error::TransitionRule(_) => TzLookupError::InvalidTimeZoneData,
Error::TimeZone(_) => TzLookupError::InvalidTimeZoneData,
Error::UnsupportedTzFile(_) => TzLookupError::InvalidTimeZoneData,
Error::UnsupportedTzString(_) => TzLookupError::InvalidTzString,
Error::Utf8(_) => TzLookupError::InvalidTimeZoneData,
}
}
}

/// Number of hours in one day
const HOURS_PER_DAY: i64 = 24;
/// Number of seconds in one hour
Expand Down
8 changes: 4 additions & 4 deletions src/offset/local/tz_info/rule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ impl AlternateTime {
} else if local_time > dst_start_transition_start
&& local_time < dst_start_transition_end
{
Ok(crate::LocalResult::None)
Ok(crate::LocalResult::InGap)
} else if local_time >= dst_start_transition_end
&& local_time < dst_end_transition_end
{
Expand Down Expand Up @@ -292,7 +292,7 @@ impl AlternateTime {
} else if local_time >= dst_start_transition_start
&& local_time < dst_start_transition_end
{
Ok(crate::LocalResult::None)
Ok(crate::LocalResult::InGap)
} else {
Ok(crate::LocalResult::Single(self.dst))
}
Expand All @@ -317,7 +317,7 @@ impl AlternateTime {
} else if local_time >= dst_end_transition_start
&& local_time < dst_end_transition_end
{
Ok(crate::LocalResult::None)
Ok(crate::LocalResult::InGap)
} else {
Ok(crate::LocalResult::Single(self.std))
}
Expand All @@ -329,7 +329,7 @@ impl AlternateTime {
} else if local_time > dst_end_transition_start
&& local_time < dst_end_transition_end
{
Ok(crate::LocalResult::None)
Ok(crate::LocalResult::InGap)
} else if local_time >= dst_end_transition_end
&& local_time < dst_start_transition_end
{
Expand Down
2 changes: 1 addition & 1 deletion src/offset/local/tz_info/timezone.rs
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ impl<'a> TimeZoneRef<'a> {
if local_leap_time <= transition_start {
return Ok(crate::LocalResult::Single(prev));
} else if local_leap_time < transition_end {
return Ok(crate::LocalResult::None);
return Ok(crate::LocalResult::InGap);
} else if local_leap_time == transition_end {
return Ok(crate::LocalResult::Single(after_ltt));
}
Expand Down
43 changes: 24 additions & 19 deletions src/offset/local/unix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use std::{cell::RefCell, collections::hash_map, env, fs, hash::Hasher, time::Sys
use super::tz_info::TimeZone;
use super::{FixedOffset, NaiveDateTime};
use crate::{Datelike, LocalResult};
use crate::offset::TzLookupError;

pub(super) fn offset_from_utc_datetime(utc: &NaiveDateTime) -> LocalResult<FixedOffset> {
offset(utc, false)
Expand Down Expand Up @@ -66,7 +67,7 @@ impl Source {
}

struct Cache {
zone: TimeZone,
zone: Result<TimeZone, TzLookupError>,
source: Source,
last_checked: SystemTime,
}
Expand All @@ -77,13 +78,13 @@ const TZDB_LOCATION: &str = "/usr/share/lib/zoneinfo";
#[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()?;
fn fallback_timezone() -> Result<TimeZone, TzLookupError> {
let tz_name = iana_time_zone::get_timezone().map_err(|_| TzLookupError::TimeZoneUnknown)?;
#[cfg(not(target_os = "android"))]
let bytes = fs::read(format!("{}/{}", TZDB_LOCATION, tz_name)).ok()?;
let bytes = fs::read(format!("{}/{}", TZDB_LOCATION, tz_name)).map_err(|_| TzLookupError::TimeZoneNotFound)?;
#[cfg(target_os = "android")]
let bytes = android_tzdata::find_tz_data(&tz_name).ok()?;
TimeZone::from_tz_data(&bytes).ok()
let bytes = android_tzdata::find_tz_data(&tz_name).map_err(|_| TzLookupError::TimeZoneNotFound)?;
TimeZone::from_tz_data(&bytes).map_err(|_| TzLookupError::InvalidTimeZoneData)
}

impl Default for Cache {
Expand All @@ -99,8 +100,8 @@ impl Default for Cache {
}
}

fn current_zone(var: Option<&str>) -> TimeZone {
TimeZone::local(var).ok().or_else(fallback_timezone).unwrap_or_else(TimeZone::utc)
fn current_zone(var: Option<&str>) -> Result<TimeZone, TzLookupError> {
TimeZone::local(var).or_else(|_| fallback_timezone())
}

impl Cache {
Expand Down Expand Up @@ -148,24 +149,28 @@ impl Cache {
}
}

if !local {
let offset = self
.zone
.find_local_time_type(d.timestamp())
.expect("unable to select local time type")
.offset();
let zone = match self.zone.as_ref() {
Ok(zone) => zone,
Err(e) => return LocalResult::Error(*e),
};

if !local {
let offset = match zone.find_local_time_type(d.timestamp()) {
Ok(ltt) => ltt.offset(),
Err(e) => return LocalResult::Error(e.into()),
};
return match FixedOffset::east(offset) {
Some(offset) => LocalResult::Single(offset),
None => LocalResult::None,
None => LocalResult::Error(TzLookupError::OutOfRange), // or invalid?
};
}

// 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 anyway.
self.zone
.find_local_time_type_from_local(d.timestamp(), d.year())
.expect("unable to select local time type")
.map(|o| FixedOffset::east(o.offset()).unwrap())
let ltt = match zone.find_local_time_type_from_local(d.timestamp(), d.year()) {
Ok(ltt) => ltt,
Err(e) => return LocalResult::Error(e.into()),
};
ltt.map(|o| FixedOffset::east(o.offset()).unwrap()) // FIXME: report out of range offset
}
}
Loading