diff --git a/Cargo.toml b/Cargo.toml index 5185872..6fbb591 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,25 +24,26 @@ appveyor = { repository = "chronotope/chrono" } name = "chrono" [features] -default = ["clock"] -clock = ["time"] +default = ["clock", "std"] +alloc = [] +std = [] +clock = ["time", "std"] wasmbind = ["wasm-bindgen", "js-sys"] [dependencies] -libc = { version = "0.2", default-features = false } time = { version = "0.1.39", optional = true } num-integer = { version = "0.1.36", default-features = false } num-traits = { version = "0.2", default-features = false } rustc-serialize = { version = "0.3.20", optional = true } -serde = { version = "1", optional = true } +serde = { version = "1.0.99", default-features = false, optional = true } [target.'cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))'.dependencies] wasm-bindgen = { version = "0.2", optional = true } js-sys = { version = "0.3", optional = true } # contains FFI bindings for the JS Date API [dev-dependencies] -serde_json = { version = "1" } -serde_derive = { version = "1" } +serde_json = { version = "1", default-features = false } +serde_derive = { version = "1", default-features = false } bincode = { version = "0.8.0" } num-iter = { version = "0.1.35", default-features = false } doc-comment = "0.3" diff --git a/Makefile b/Makefile index bb938cc..46b39dd 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,8 @@ # this Makefile is mostly for the packaging convenience. # casual users should use `cargo` to retrieve the appropriate version of Chrono. +CHANNEL=stable + .PHONY: all all: @echo 'Try `cargo build` instead.' @@ -20,11 +22,8 @@ README.md: src/lib.rs .PHONY: test test: - TZ=UTC0 cargo test --features 'serde rustc-serialize bincode' --lib - TZ=ACST-9:30 cargo test --features 'serde rustc-serialize bincode' --lib - TZ=EST4 cargo test --features 'serde rustc-serialize bincode' + CHANNEL=$(CHANNEL) ./ci/travis.sh .PHONY: doc doc: authors readme cargo doc --features 'serde rustc-serialize bincode' - diff --git a/ci/core-test/Cargo.toml b/ci/core-test/Cargo.toml new file mode 100644 index 0000000..b35ff9a --- /dev/null +++ b/ci/core-test/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "core-test" +version = "0.1.0" +authors = [ + "Kang Seonghoon ", + "Brandon W Maister ", +] +edition = "2018" + +[dependencies] +chrono = { path = "../..", default-features = false, features = ["serde"] } + +[features] +alloc = ["chrono/alloc"] \ No newline at end of file diff --git a/ci/core-test/src/lib.rs b/ci/core-test/src/lib.rs new file mode 100644 index 0000000..e311edb --- /dev/null +++ b/ci/core-test/src/lib.rs @@ -0,0 +1,7 @@ +#![no_std] + +use chrono::{TimeZone, Utc}; + +pub fn create_time() { + let _ = Utc.ymd(2019, 1, 1).and_hms(0, 0, 0); +} diff --git a/ci/travis.sh b/ci/travis.sh index 98e25ce..18f639b 100755 --- a/ci/travis.sh +++ b/ci/travis.sh @@ -2,25 +2,50 @@ # This is the script that's executed by travis, you can run it yourself to run # the exact same suite +# +# When running it locally the most important thing to set is the CHANNEL env +# var, otherwise it will run tests against every version of rust that it knows +# about (nightly, beta, stable, 1.13.0): +# +# $ CHANNEL=stable ./ci/travis.sh set -e DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -channel() { - if [ -n "${TRAVIS}" ]; then - if [ "${TRAVIS_RUST_VERSION}" = "${CHANNEL}" ]; then - pwd - (set -x; cargo "$@") - fi - elif [ -n "${APPVEYOR}" ]; then - if [ "${APPVEYOR_RUST_CHANNEL}" = "${CHANNEL}" ]; then - pwd - (set -x; cargo "$@") + +main() { + if [[ -n "$CHANNEL" ]] ; then + if [[ "$CHANNEL" == 1.13.0 ]]; then + banner "Building $CHANNEL" + build_only + else + banner "Building/testing $CHANNEL" + build_and_test + banner "Testing Core $CHANNEL" + build_core_test fi else - pwd - (set -x; cargo "+${CHANNEL}" "$@") + CHANNEL=nightly + matching_banner "Test $CHANNEL" + if [[ "${CLIPPY}" = y ]] ; then + run_clippy + else + build_and_test + fi + + CHANNEL=beta + matching_banner "Test $CHANNEL" + build_and_test + + CHANNEL=stable + matching_banner "Test $CHANNEL" + build_and_test + build_core_test + + CHANNEL=1.13.0 + matching_banner "Test $CHANNEL" + build_only fi } @@ -54,14 +79,20 @@ build_and_test_nonwasm() { TZ=Asia/Katmandu channel test -v --features serde,rustc-serialize # without default "clock" feature - channel build -v --no-default-features + channel build -v --no-default-features --features std TZ=ACST-9:30 channel test -v --no-default-features --lib - channel build -v --no-default-features --features rustc-serialize + channel build -v --no-default-features --features std,rustc-serialize TZ=EST4 channel test -v --no-default-features --features rustc-serialize --lib - channel build -v --no-default-features --features serde + channel build -v --no-default-features --features std,serde TZ=UTC0 channel test -v --no-default-features --features serde --lib - channel build -v --no-default-features --features serde,rustc-serialize - TZ=Asia/Katmandu channel test -v --no-default-features --features serde,rustc-serialize --lib + channel build -v --no-default-features --features std,serde,rustc-serialize + TZ=Asia/Katmandu channel test -v --no-default-features --features std,serde,rustc-serialize --lib + + channel build -v --no-default-features --features 'serde' + TZ=UTC0 channel test -v --no-default-features --features 'serde' --lib + + channel build -v --no-default-features --features 'alloc serde' + TZ=UTC0 channel test -v --no-default-features --features 'alloc serde' --lib } build_and_test_wasm() { @@ -81,8 +112,16 @@ build_only() { cargo clean channel build -v channel build -v --features rustc-serialize - channel build -v --features 'serde bincode' - channel build -v --no-default-features + channel build -v --features serde + channel build -v --no-default-features --features std +} + +build_core_test() { + channel_run rustup target add thumbv6m-none-eabi --toolchain "$CHANNEL" + ( + cd ci/core-test + channel build -v --target thumbv6m-none-eabi + ) } run_clippy() { @@ -92,7 +131,7 @@ run_clippy() { exit fi - cargo clippy --features 'serde bincode rustc-serialize' -- -Dclippy + cargo clippy --features 'serde rustc-serialize' -- -Dclippy } check_readme() { @@ -100,22 +139,70 @@ check_readme() { (set -x; git diff --exit-code -- README.md) ; echo $? } -rustc --version -cargo --version -node --version +# script helpers -CHANNEL=nightly -if [ "x${CLIPPY}" = xy ] ; then - run_clippy -else - build_and_test -fi +banner() { + echo "======================================================================" + echo "$*" + echo "======================================================================" +} -CHANNEL=beta -build_and_test +underline() { + echo "$*" + echo "${*//?/^}" +} -CHANNEL=stable -build_and_test +matching_banner() { + if channel_matches || ! is_ci ; then + banner "$*" + echo_versions + fi +} -CHANNEL=1.13.0 -build_only +echo_versions() { + channel_run rustc --version + channel_run cargo --version + node --version +} + +channel() { + channel_run cargo "$@" +} + +channel_run() { + if channel_matches ; then + local the_cmd="$ $*" + underline "$the_cmd" + "$@" + elif ! is_ci ; then + local cmd="$1" + shift + if [[ $cmd == cargo || $cmd == rustc ]] ; then + underline "$ $cmd +${CHANNEL} $*" + "$cmd" "+${CHANNEL}" "$@" + else + underline "$ $cmd $*" + "$cmd" "$@" + fi + fi +} + +channel_matches() { + if is_ci ; then + if [[ "${TRAVIS_RUST_VERSION}" = "${CHANNEL}" + || "${APPVEYOR_RUST_CHANNEL}" = "${CHANNEL}" ]] ; then + return 0 + fi + fi + return 1 +} + +is_ci() { + if [[ -n "$TRAVIS" || -n "$APPVEYOR" ]] ; then + return 0 + else + return 1 + fi +} + +main diff --git a/src/date.rs b/src/date.rs index f8b59ce..d449009 100644 --- a/src/date.rs +++ b/src/date.rs @@ -3,16 +3,17 @@ //! ISO 8601 calendar date with time zone. -use std::{fmt, hash}; -use std::cmp::Ordering; -use std::ops::{Add, Sub}; +use core::{fmt, hash}; +use core::cmp::Ordering; +use core::ops::{Add, Sub}; use oldtime::Duration as OldDuration; use {Weekday, Datelike}; use offset::{TimeZone, Utc}; use naive::{self, NaiveDate, NaiveTime, IsoWeek}; use DateTime; -use format::{Item, DelayedFormat, StrftimeItems}; +#[cfg(any(feature = "alloc", feature = "std", test))] +use format::{DelayedFormat, Item, StrftimeItems}; /// ISO 8601 calendar date with time zone. /// @@ -255,6 +256,7 @@ fn map_local(d: &Date, mut f: F) -> Option> impl Date where Tz::Offset: fmt::Display { /// Formats the date with the specified formatting items. + #[cfg(any(feature = "alloc", feature = "std", test))] #[inline] pub fn format_with_items<'a, I>(&self, items: I) -> DelayedFormat where I: Iterator> + Clone { @@ -264,6 +266,7 @@ impl Date where Tz::Offset: fmt::Display { /// Formats the date with the specified format string. /// See the [`format::strftime` module](./format/strftime/index.html) /// on the supported escape sequences. + #[cfg(any(feature = "alloc", feature = "std", test))] #[inline] pub fn format<'a>(&self, fmt: &'a str) -> DelayedFormat> { self.format_with_items(StrftimeItems::new(fmt)) diff --git a/src/datetime.rs b/src/datetime.rs index 8c0cc72..3bd6c77 100644 --- a/src/datetime.rs +++ b/src/datetime.rs @@ -3,12 +3,18 @@ //! ISO 8601 date and time with time zone. -use std::{str, fmt, hash}; -use std::cmp::Ordering; -use std::ops::{Add, Sub}; +use core::{str, fmt, hash}; +use core::cmp::Ordering; +use core::ops::{Add, Sub}; +#[cfg(any(feature = "std", test))] use std::time::{SystemTime, UNIX_EPOCH}; use oldtime::Duration as OldDuration; +#[cfg(all(not(feature = "std"), feature = "alloc"))] +use alloc::string::{String, ToString}; +#[cfg(feature = "std")] +use std::string::ToString; + use {Weekday, Timelike, Datelike}; #[cfg(feature="clock")] use offset::Local; @@ -16,7 +22,9 @@ use offset::{TimeZone, Offset, Utc, FixedOffset}; use naive::{NaiveTime, NaiveDateTime, IsoWeek}; use Date; use format::{Item, Numeric, Pad, Fixed}; -use format::{parse, Parsed, ParseError, ParseResult, DelayedFormat, StrftimeItems}; +use format::{parse, Parsed, ParseError, ParseResult, StrftimeItems}; +#[cfg(any(feature = "alloc", feature = "std", test))] +use format::DelayedFormat; /// Specific formatting options for seconds. This may be extended in the /// future, so exhaustive matching in external code is not recommended. @@ -363,12 +371,14 @@ impl DateTime { impl DateTime where Tz::Offset: fmt::Display { /// Returns an RFC 2822 date and time string such as `Tue, 1 Jul 2003 10:52:37 +0200`. + #[cfg(any(feature = "alloc", feature = "std", test))] pub fn to_rfc2822(&self) -> String { const ITEMS: &'static [Item<'static>] = &[Item::Fixed(Fixed::RFC2822)]; self.format_with_items(ITEMS.iter().cloned()).to_string() } /// Returns an RFC 3339 and ISO 8601 date and time string such as `1996-12-19T16:39:57-08:00`. + #[cfg(any(feature = "alloc", feature = "std", test))] pub fn to_rfc3339(&self) -> String { const ITEMS: &'static [Item<'static>] = &[Item::Fixed(Fixed::RFC3339)]; self.format_with_items(ITEMS.iter().cloned()).to_string() @@ -398,6 +408,7 @@ impl DateTime where Tz::Offset: fmt::Display { /// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Secs, true), /// "2018-01-26T10:30:09+08:00"); /// ``` + #[cfg(any(feature = "alloc", feature = "std", test))] pub fn to_rfc3339_opts(&self, secform: SecondsFormat, use_z: bool) -> String { use format::Numeric::*; use format::Pad::Zero; @@ -449,6 +460,7 @@ impl DateTime where Tz::Offset: fmt::Display { } /// Formats the combined date and time with the specified formatting items. + #[cfg(any(feature = "alloc", feature = "std", test))] #[inline] pub fn format_with_items<'a, I>(&self, items: I) -> DelayedFormat where I: Iterator> + Clone { @@ -459,6 +471,7 @@ impl DateTime where Tz::Offset: fmt::Display { /// Formats the combined date and time with the specified format string. /// See the [`format::strftime` module](./format/strftime/index.html) /// on the supported escape sequences. + #[cfg(any(feature = "alloc", feature = "std", test))] #[inline] pub fn format<'a>(&self, fmt: &'a str) -> DelayedFormat> { self.format_with_items(StrftimeItems::new(fmt)) @@ -647,6 +660,7 @@ impl str::FromStr for DateTime { } } +#[cfg(any(feature = "std", test))] impl From for DateTime { fn from(t: SystemTime) -> DateTime { let (sec, nsec) = match t.duration_since(UNIX_EPOCH) { @@ -672,6 +686,7 @@ impl From for DateTime { } } +#[cfg(any(feature = "std", test))] impl From> for SystemTime { fn from(dt: DateTime) -> SystemTime { use std::time::Duration; @@ -699,7 +714,7 @@ fn test_auto_conversion() { fn test_encodable_json(to_string_utc: FUtc, to_string_fixed: FFixed) where FUtc: Fn(&DateTime) -> Result, FFixed: Fn(&DateTime) -> Result, - E: ::std::fmt::Debug + E: ::core::fmt::Debug { assert_eq!(to_string_utc(&Utc.ymd(2014, 7, 24).and_hms(12, 34, 6)).ok(), Some(r#""2014-07-24T12:34:06Z""#.into())); @@ -717,7 +732,7 @@ fn test_decodable_json(utc_from_str: FUtc, where FUtc: Fn(&str) -> Result, E>, FFixed: Fn(&str) -> Result, E>, FLocal: Fn(&str) -> Result, E>, - E: ::std::fmt::Debug + E: ::core::fmt::Debug { // should check against the offset as well (the normal DateTime comparison will ignore them) fn norm(dt: &Option>) -> Option<(&DateTime, &Tz::Offset)> { @@ -754,7 +769,7 @@ fn test_decodable_json_timestamps(utc_from_str: FUtc, where FUtc: Fn(&str) -> Result, E>, FFixed: Fn(&str) -> Result, E>, FLocal: Fn(&str) -> Result, E>, - E: ::std::fmt::Debug + E: ::core::fmt::Debug { fn norm(dt: &Option>) -> Option<(&DateTime, &Tz::Offset)> { dt.as_ref().map(|dt| (dt, dt.offset())) @@ -778,8 +793,8 @@ fn test_decodable_json_timestamps(utc_from_str: FUtc, #[cfg(feature = "rustc-serialize")] pub mod rustc_serialize { - use std::fmt; - use std::ops::Deref; + use core::fmt; + use core::ops::Deref; use super::DateTime; #[cfg(feature="clock")] use offset::Local; @@ -907,12 +922,13 @@ pub mod rustc_serialize { /// documented at re-export site #[cfg(feature = "serde")] pub mod serde { - use std::fmt; + use core::fmt; use super::DateTime; #[cfg(feature="clock")] use offset::Local; use offset::{LocalResult, TimeZone, Utc, FixedOffset}; use serdelib::{ser, de}; + use {SerdeError, ne_timestamp}; #[doc(hidden)] #[derive(Debug)] @@ -928,16 +944,16 @@ pub mod serde { // try!-like function to convert a LocalResult into a serde-ish Result fn serde_from(me: LocalResult, ts: &V) -> Result - where E: de::Error, - V: fmt::Display, - T: fmt::Display, + where + E: de::Error, + V: fmt::Display, + T: fmt::Display, { match me { LocalResult::None => Err(E::custom( - format!("value is not a legal timestamp: {}", ts))), + ne_timestamp(ts))), LocalResult::Ambiguous(min, max) => Err(E::custom( - format!("value is an ambiguous timestamp: {}, could be either of {}, {}", - ts, min, max))), + SerdeError::Ambiguous { timestamp: ts, min: min, max: max })), LocalResult::Single(val) => Ok(val) } } @@ -979,7 +995,7 @@ pub mod serde { /// # fn main() { example().unwrap(); } /// ``` pub mod ts_nanoseconds { - use std::fmt; + use core::fmt; use serdelib::{ser, de}; use {DateTime, Utc}; @@ -1270,7 +1286,7 @@ pub mod serde { /// # fn main() { example().unwrap(); } /// ``` pub mod ts_milliseconds { - use std::fmt; + use core::fmt; use serdelib::{ser, de}; use {DateTime, Utc}; @@ -1561,7 +1577,7 @@ pub mod serde { /// # fn main() { example().unwrap(); } /// ``` pub mod ts_seconds { - use std::fmt; + use core::fmt; use serdelib::{ser, de}; use {DateTime, Utc}; @@ -1847,7 +1863,7 @@ pub mod serde { fn visit_str(self, value: &str) -> Result, E> where E: de::Error { - value.parse().map_err(|err| E::custom(format!("{}", err))) + value.parse().map_err(|err: ::format::ParseError| E::custom(err)) } } diff --git a/src/format/mod.rs b/src/format/mod.rs index 24647b4..b8e6458 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -17,20 +17,30 @@ #![allow(ellipsis_inclusive_range_patterns)] -use std::fmt; -use std::str::FromStr; +use core::fmt; +use core::str::FromStr; +#[cfg(any(feature = "std", test))] use std::error::Error; +#[cfg(feature = "alloc")] +use alloc::boxed::Box; +#[cfg(feature = "alloc")] +use alloc::string::{String, ToString}; -use {Datelike, Timelike, Weekday, ParseWeekdayError}; +#[cfg(any(feature = "alloc", feature = "std", test))] +use {Datelike, Timelike}; +use {Weekday, ParseWeekdayError}; +#[cfg(any(feature = "alloc", feature = "std", test))] use div::{div_floor, mod_floor}; +#[cfg(any(feature = "alloc", feature = "std", test))] use offset::{Offset, FixedOffset}; +#[cfg(any(feature = "alloc", feature = "std", test))] use naive::{NaiveDate, NaiveTime}; pub use self::strftime::StrftimeItems; pub use self::parsed::Parsed; pub use self::parse::parse; -/// An unhabitated type used for `InternalNumeric` and `InternalFixed` below. +/// An uninhabited type used for `InternalNumeric` and `InternalFixed` below. #[derive(Clone, PartialEq, Eq)] enum Void {} @@ -55,7 +65,7 @@ pub enum Pad { /// /// The **parsing width** is the maximal width to be scanned. /// The parser only tries to consume from one to given number of digits (greedily). -/// It also trims the preceding whitespaces if any. +/// It also trims the preceding whitespace if any. /// It cannot parse the negative number, so some date and time cannot be formatted then /// parsed with the same formatting items. #[derive(Clone, PartialEq, Eq, Debug)] @@ -185,13 +195,13 @@ pub enum Fixed { TimezoneName, /// Offset from the local time to UTC (`+09:00` or `-04:00` or `+00:00`). /// - /// In the parser, the colon can be omitted and/or surrounded with any amount of whitespaces. + /// In the parser, the colon can be omitted and/or surrounded with any amount of whitespace. /// The offset is limited from `-24:00` to `+24:00`, /// which is same to [`FixedOffset`](../offset/struct.FixedOffset.html)'s range. TimezoneOffsetColon, /// Offset from the local time to UTC (`+09:00` or `-04:00` or `Z`). /// - /// In the parser, the colon can be omitted and/or surrounded with any amount of whitespaces, + /// In the parser, the colon can be omitted and/or surrounded with any amount of whitespace, /// and `Z` can be either in upper case or in lower case. /// The offset is limited from `-24:00` to `+24:00`, /// which is same to [`FixedOffset`](../offset/struct.FixedOffset.html)'s range. @@ -245,10 +255,12 @@ pub enum Item<'a> { /// A literally printed and parsed text. Literal(&'a str), /// Same to `Literal` but with the string owned by the item. + #[cfg(any(feature = "alloc", feature = "std", test))] OwnedLiteral(Box), /// Whitespace. Prints literally but reads zero or more whitespace. Space(&'a str), /// Same to `Space` but with the string owned by the item. + #[cfg(any(feature = "alloc", feature = "std", test))] OwnedSpace(Box), /// Numeric item. Can be optionally padded to the maximal length (if any) when formatting; /// the parser simply ignores any padded whitespace and zeroes. @@ -305,13 +317,7 @@ enum ParseErrorKind { /// Same to `Result`. pub type ParseResult = Result; -impl fmt::Display for ParseError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.description().fmt(f) - } -} - -impl Error for ParseError { +impl ParseError { fn description(&self) -> &str { match self.0 { ParseErrorKind::OutOfRange => "input is out of range", @@ -325,6 +331,19 @@ impl Error for ParseError { } } +impl fmt::Display for ParseError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.description().fmt(f) + } +} + +#[cfg(any(feature = "std", test))] +impl Error for ParseError { + fn description(&self) -> &str { + self.description() + } +} + // to be used in this module and submodules const OUT_OF_RANGE: ParseError = ParseError(ParseErrorKind::OutOfRange); const IMPOSSIBLE: ParseError = ParseError(ParseErrorKind::Impossible); @@ -336,6 +355,7 @@ const BAD_FORMAT: ParseError = ParseError(ParseErrorKind::BadFormat); /// Tries to format given arguments with given formatting items. /// Internally used by `DelayedFormat`. +#[cfg(any(feature = "alloc", feature = "std", test))] pub fn format<'a, I>( w: &mut fmt::Formatter, date: Option<&NaiveDate>, @@ -356,12 +376,13 @@ pub fn format<'a, I>( static LONG_WEEKDAYS: [&'static str; 7] = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]; - use std::fmt::Write; + use core::fmt::Write; let mut result = String::new(); for item in items { match item { Item::Literal(s) | Item::Space(s) => result.push_str(s), + #[cfg(any(feature = "alloc", feature = "std", test))] Item::OwnedLiteral(ref s) | Item::OwnedSpace(ref s) => result.push_str(s), Item::Numeric(spec, pad) => { @@ -598,6 +619,7 @@ pub mod strftime; /// A *temporary* object which can be used as an argument to `format!` or others. /// This is normally constructed via `format` methods of each date and time type. +#[cfg(any(feature = "alloc", feature = "std", test))] #[derive(Debug)] pub struct DelayedFormat { /// The date view, if any. @@ -610,6 +632,7 @@ pub struct DelayedFormat { items: I, } +#[cfg(any(feature = "alloc", feature = "std", test))] impl<'a, I: Iterator> + Clone> DelayedFormat { /// Makes a new `DelayedFormat` value out of local date and time. pub fn new(date: Option, time: Option, items: I) -> DelayedFormat { @@ -625,6 +648,7 @@ impl<'a, I: Iterator> + Clone> DelayedFormat { } } +#[cfg(any(feature = "alloc", feature = "std", test))] impl<'a, I: Iterator> + Clone> fmt::Display for DelayedFormat { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { format(f, self.date.as_ref(), self.time.as_ref(), self.off.as_ref(), self.items.clone()) diff --git a/src/format/parse.rs b/src/format/parse.rs index 01e326e..e8e0d25 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -6,7 +6,7 @@ #![allow(deprecated)] -use std::usize; +use core::usize; use Weekday; @@ -218,13 +218,19 @@ pub fn parse<'a, I>(parsed: &mut Parsed, mut s: &str, items: I) -> ParseResult<( s = &s[prefix.len()..]; } + #[cfg(any(feature = "alloc", feature = "std", test))] Item::OwnedLiteral(ref prefix) => { if s.len() < prefix.len() { return Err(TOO_SHORT); } if !s.starts_with(&prefix[..]) { return Err(INVALID); } s = &s[prefix.len()..]; } - Item::Space(_) | Item::OwnedSpace(_) => { + Item::Space(_) => { + s = s.trim_left(); + } + + #[cfg(any(feature = "alloc", feature = "std", test))] + Item::OwnedSpace(_) => { s = s.trim_left(); } diff --git a/src/lib.rs b/src/lib.rs index 31f7a77..bbc4324 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -387,6 +387,8 @@ #![deny(missing_docs)] #![deny(missing_debug_implementations)] +#![cfg_attr(not(any(feature = "std", test)), no_std)] + // The explicit 'static lifetimes are still needed for rustc 1.13-16 // backward compatibility, and this appeases clippy. If minimum rustc // becomes 1.17, should be able to remove this, those 'static lifetimes, @@ -403,6 +405,13 @@ trivially_copy_pass_by_ref, ))] +#[cfg(feature = "alloc")] +extern crate alloc; +#[cfg(any(feature = "std", test))] +extern crate std as core; +#[cfg(all(feature = "std", not(feature="alloc")))] +extern crate std as alloc; + #[cfg(feature="clock")] extern crate time as oldtime; extern crate num_integer; @@ -513,6 +522,41 @@ pub mod serde { pub use super::datetime::serde::*; } +// Until rust 1.18 there is no "pub(crate)" so to share this we need it in the root + +#[cfg(feature = "serde")] +enum SerdeError { + NonExistent { timestamp: V }, + Ambiguous { timestamp: V, min: D, max: D }, +} + +/// Construct a [`SerdeError::NonExistent`] +#[cfg(feature = "serde")] +fn ne_timestamp(ts: T) -> SerdeError { + SerdeError::NonExistent:: { timestamp: ts } +} + +#[cfg(feature = "serde")] +impl fmt::Debug for SerdeError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "ChronoSerdeError({})", self) + } +} + +// impl core::error::Error for SerdeError {} +#[cfg(feature = "serde")] +impl fmt::Display for SerdeError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + &SerdeError::NonExistent { ref timestamp } => write!( + f, "value is not a legal timestamp: {}", timestamp), + &SerdeError::Ambiguous { ref timestamp, ref min, ref max } => write!( + f, "value is an ambiguous timestamp: {}, could be either of {}, {}", + timestamp, min, max), + } + } +} + /// The day of week. /// /// The order of the days of week depends on the context. @@ -647,6 +691,20 @@ impl Weekday { } } +impl fmt::Display for Weekday { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str(match *self { + Weekday::Mon => "Mon", + Weekday::Tue => "Tue", + Weekday::Wed => "Wed", + Weekday::Thu => "Thu", + Weekday::Fri => "Fri", + Weekday::Sat => "Sat", + Weekday::Sun => "Sun", + }) + } +} + /// Any weekday can be represented as an integer from 0 to 6, which equals to /// [`Weekday::num_days_from_monday`](#method.num_days_from_monday) in this implementation. /// Do not heavily depend on this though; use explicit methods whenever possible. @@ -680,7 +738,7 @@ impl num_traits::FromPrimitive for Weekday { } } -use std::fmt; +use core::fmt; /// An error resulting from reading `Weekday` value with `FromStr`. #[derive(Clone, PartialEq)] @@ -699,14 +757,14 @@ impl fmt::Debug for ParseWeekdayError { #[cfg(feature = "serde")] mod weekday_serde { use super::Weekday; - use std::fmt; + use core::fmt; use serdelib::{ser, de}; impl ser::Serialize for Weekday { fn serialize(&self, serializer: S) -> Result where S: ser::Serializer { - serializer.serialize_str(&format!("{:?}", self)) + serializer.collect_str(&self) } } diff --git a/src/naive/date.rs b/src/naive/date.rs index 3a0b7e6..f67b670 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -3,8 +3,8 @@ //! ISO 8601 calendar date without timezone. -use std::{str, fmt}; -use std::ops::{Add, Sub, AddAssign, SubAssign}; +use core::{str, fmt}; +use core::ops::{Add, Sub, AddAssign, SubAssign}; use num_traits::ToPrimitive; use oldtime::Duration as OldDuration; @@ -12,7 +12,9 @@ use {Weekday, Datelike}; use div::div_mod_floor; use naive::{NaiveTime, NaiveDateTime, IsoWeek}; use format::{Item, Numeric, Pad}; -use format::{parse, Parsed, ParseError, ParseResult, DelayedFormat, StrftimeItems}; +use format::{parse, Parsed, ParseError, ParseResult, StrftimeItems}; +#[cfg(any(feature = "alloc", feature = "std", test))] +use format::DelayedFormat; use super::isoweek; use super::internals::{self, DateImpl, Of, Mdf, YearFlags}; @@ -916,6 +918,7 @@ impl NaiveDate { /// # let d = NaiveDate::from_ymd(2015, 9, 5); /// assert_eq!(format!("{}", d.format_with_items(fmt)), "2015-09-05"); /// ~~~~ + #[cfg(any(feature = "alloc", feature = "std", test))] #[inline] pub fn format_with_items<'a, I>(&self, items: I) -> DelayedFormat where I: Iterator> + Clone { @@ -954,6 +957,7 @@ impl NaiveDate { /// assert_eq!(format!("{}", d.format("%Y-%m-%d")), "2015-09-05"); /// assert_eq!(format!("{}", d.format("%A, %-d %B, %C%y")), "Saturday, 5 September, 2015"); /// ~~~~ + #[cfg(any(feature = "alloc", feature = "std", test))] #[inline] pub fn format<'a>(&self, fmt: &'a str) -> DelayedFormat> { self.format_with_items(StrftimeItems::new(fmt)) @@ -1387,7 +1391,7 @@ impl SubAssign for NaiveDate { /// Subtracts another `NaiveDate` from the current date. /// Returns a `Duration` of integral numbers. -/// +/// /// This does not overflow or underflow at all, /// as all possible output fits in the range of `Duration`. /// @@ -1600,7 +1604,7 @@ mod rustc_serialize { #[cfg(feature = "serde")] mod serde { - use std::fmt; + use core::fmt; use super::NaiveDate; use serdelib::{ser, de}; @@ -1629,15 +1633,23 @@ mod serde { impl<'de> de::Visitor<'de> for NaiveDateVisitor { type Value = NaiveDate; - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { write!(formatter, "a formatted date string") } + #[cfg(any(feature = "std", test))] fn visit_str(self, value: &str) -> Result where E: de::Error { - value.parse().map_err(|err| E::custom(format!("{}", err))) + value.parse().map_err(E::custom) + } + + #[cfg(not(any(feature = "std", test)))] + fn visit_str(self, value: &str) -> Result + where E: de::Error + { + value.parse().map_err(E::custom) } } diff --git a/src/naive/datetime.rs b/src/naive/datetime.rs index bb9ff8d..2049782 100644 --- a/src/naive/datetime.rs +++ b/src/naive/datetime.rs @@ -3,8 +3,8 @@ //! ISO 8601 date and time without timezone. -use std::{str, fmt, hash}; -use std::ops::{Add, Sub, AddAssign, SubAssign}; +use core::{str, fmt, hash}; +use core::ops::{Add, Sub, AddAssign, SubAssign}; use num_traits::ToPrimitive; use oldtime::Duration as OldDuration; @@ -12,7 +12,9 @@ use {Weekday, Timelike, Datelike}; use div::div_mod_floor; use naive::{NaiveTime, NaiveDate, IsoWeek}; use format::{Item, Numeric, Pad, Fixed}; -use format::{parse, Parsed, ParseError, ParseResult, DelayedFormat, StrftimeItems}; +use format::{parse, Parsed, ParseError, ParseResult, StrftimeItems}; +#[cfg(any(feature = "alloc", feature = "std", test))] +use format::DelayedFormat; /// The tight upper bound guarantees that a duration with `|Duration| >= 2^MAX_SECS_BITS` /// will always overflow the addition with any date and time type. @@ -645,6 +647,7 @@ impl NaiveDateTime { /// # let dt = NaiveDate::from_ymd(2015, 9, 5).and_hms(23, 56, 4); /// assert_eq!(format!("{}", dt.format_with_items(fmt)), "2015-09-05 23:56:04"); /// ~~~~ + #[cfg(any(feature = "alloc", feature = "std", test))] #[inline] pub fn format_with_items<'a, I>(&self, items: I) -> DelayedFormat where I: Iterator> + Clone { @@ -683,6 +686,7 @@ impl NaiveDateTime { /// assert_eq!(format!("{}", dt.format("%Y-%m-%d %H:%M:%S")), "2015-09-05 23:56:04"); /// assert_eq!(format!("{}", dt.format("around %l %p on %b %-d")), "around 11 PM on Sep 5"); /// ~~~~ + #[cfg(any(feature = "alloc", feature = "std", test))] #[inline] pub fn format<'a>(&self, fmt: &'a str) -> DelayedFormat> { self.format_with_items(StrftimeItems::new(fmt)) @@ -1663,7 +1667,7 @@ pub mod rustc_serialize { /// Tools to help serializing/deserializing `NaiveDateTime`s #[cfg(feature = "serde")] pub mod serde { - use std::fmt; + use core::fmt; use super::{NaiveDateTime}; use serdelib::{ser, de}; @@ -1702,7 +1706,7 @@ pub mod serde { fn visit_str(self, value: &str) -> Result where E: de::Error { - value.parse().map_err(|err| E::custom(format!("{}", err))) + value.parse().map_err(E::custom) } } @@ -1750,10 +1754,10 @@ pub mod serde { /// # fn main() { example().unwrap(); } /// ``` pub mod ts_nanoseconds { - use std::fmt; + use core::fmt; use serdelib::{ser, de}; - use NaiveDateTime; + use {NaiveDateTime, ne_timestamp}; /// Serialize a UTC datetime into an integer number of nanoseconds since the epoch /// @@ -1846,7 +1850,7 @@ pub mod serde { { NaiveDateTime::from_timestamp_opt(value / 1_000_000_000, (value % 1_000_000_000) as u32) - .ok_or_else(|| E::custom(format!("value is not a legal timestamp: {}", value))) + .ok_or_else(|| E::custom(ne_timestamp(value))) } fn visit_u64(self, value: u64) -> Result @@ -1854,7 +1858,7 @@ pub mod serde { { NaiveDateTime::from_timestamp_opt(value as i64 / 1_000_000_000, (value as i64 % 1_000_000_000) as u32) - .ok_or_else(|| E::custom(format!("value is not a legal timestamp: {}", value))) + .ok_or_else(|| E::custom(ne_timestamp(value))) } } } @@ -1895,10 +1899,10 @@ pub mod serde { /// # fn main() { example().unwrap(); } /// ``` pub mod ts_milliseconds { - use std::fmt; + use core::fmt; use serdelib::{ser, de}; - use NaiveDateTime; + use {NaiveDateTime, ne_timestamp}; /// Serialize a UTC datetime into an integer number of milliseconds since the epoch /// @@ -1991,7 +1995,7 @@ pub mod serde { { NaiveDateTime::from_timestamp_opt(value / 1000, ((value % 1000) * 1_000_000) as u32) - .ok_or_else(|| E::custom(format!("value is not a legal timestamp: {}", value))) + .ok_or_else(|| E::custom(ne_timestamp(value))) } fn visit_u64(self, value: u64) -> Result @@ -1999,7 +2003,7 @@ pub mod serde { { NaiveDateTime::from_timestamp_opt((value / 1000) as i64, ((value % 1000) * 1_000_000) as u32) - .ok_or_else(|| E::custom(format!("value is not a legal timestamp: {}", value))) + .ok_or_else(|| E::custom(ne_timestamp(value))) } } } @@ -2040,10 +2044,10 @@ pub mod serde { /// # fn main() { example().unwrap(); } /// ``` pub mod ts_seconds { - use std::fmt; + use core::fmt; use serdelib::{ser, de}; - use NaiveDateTime; + use {NaiveDateTime, ne_timestamp}; /// Serialize a UTC datetime into an integer number of seconds since the epoch /// @@ -2135,14 +2139,14 @@ pub mod serde { where E: de::Error { NaiveDateTime::from_timestamp_opt(value, 0) - .ok_or_else(|| E::custom(format!("value is not a legal timestamp: {}", value))) + .ok_or_else(|| E::custom(ne_timestamp(value))) } fn visit_u64(self, value: u64) -> Result where E: de::Error { NaiveDateTime::from_timestamp_opt(value as i64, 0) - .ok_or_else(|| E::custom(format!("value is not a legal timestamp: {}", value))) + .ok_or_else(|| E::custom(ne_timestamp(value))) } } } diff --git a/src/naive/internals.rs b/src/naive/internals.rs index dd9d535..a1cd1f6 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -15,7 +15,7 @@ #![allow(dead_code)] // some internal methods have been left for consistency -use std::{i32, fmt}; +use core::{i32, fmt}; use num_traits::FromPrimitive; use Weekday; use div::{div_rem, mod_floor}; diff --git a/src/naive/isoweek.rs b/src/naive/isoweek.rs index 667cf2f..0aeedb0 100644 --- a/src/naive/isoweek.rs +++ b/src/naive/isoweek.rs @@ -3,7 +3,7 @@ //! ISO 8601 week. -use std::fmt; +use core::fmt; use super::internals::{DateImpl, Of, YearFlags}; diff --git a/src/naive/time.rs b/src/naive/time.rs index 440c8a7..c0272c8 100644 --- a/src/naive/time.rs +++ b/src/naive/time.rs @@ -3,14 +3,16 @@ //! ISO 8601 time without timezone. -use std::{str, fmt, hash}; -use std::ops::{Add, Sub, AddAssign, SubAssign}; +use core::{str, fmt, hash}; +use core::ops::{Add, Sub, AddAssign, SubAssign}; use oldtime::Duration as OldDuration; use Timelike; use div::div_mod_floor; use format::{Item, Numeric, Pad, Fixed}; -use format::{parse, Parsed, ParseError, ParseResult, DelayedFormat, StrftimeItems}; +use format::{parse, Parsed, ParseError, ParseResult, StrftimeItems}; +#[cfg(any(feature = "alloc", feature = "std", test))] +use format::DelayedFormat; /// ISO 8601 time without timezone. /// Allows for the nanosecond precision and optional leap second representation. @@ -681,7 +683,7 @@ impl NaiveTime { // `rhs.frac`|========================================>| // | | | `self - rhs` | | - use std::cmp::Ordering; + use core::cmp::Ordering; let secs = i64::from(self.secs) - i64::from(rhs.secs); let frac = i64::from(self.frac) - i64::from(rhs.frac); @@ -723,6 +725,7 @@ impl NaiveTime { /// # let t = NaiveTime::from_hms(23, 56, 4); /// assert_eq!(format!("{}", t.format_with_items(fmt)), "23:56:04"); /// ~~~~ + #[cfg(any(feature = "alloc", feature = "std", test))] #[inline] pub fn format_with_items<'a, I>(&self, items: I) -> DelayedFormat where I: Iterator> + Clone { @@ -763,6 +766,7 @@ impl NaiveTime { /// assert_eq!(format!("{}", t.format("%H:%M:%S%.6f")), "23:56:04.012345"); /// assert_eq!(format!("{}", t.format("%-I:%M %p")), "11:56 PM"); /// ~~~~ + #[cfg(any(feature = "alloc", feature = "std", test))] #[inline] pub fn format<'a>(&self, fmt: &'a str) -> DelayedFormat> { self.format_with_items(StrftimeItems::new(fmt)) @@ -1411,7 +1415,7 @@ mod rustc_serialize { #[cfg(feature = "serde")] mod serde { - use std::fmt; + use core::fmt; use super::NaiveTime; use serdelib::{ser, de}; @@ -1431,7 +1435,7 @@ mod serde { impl<'de> de::Visitor<'de> for NaiveTimeVisitor { type Value = NaiveTime; - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { write!(formatter, "a formatted time string") } @@ -1439,7 +1443,7 @@ mod serde { fn visit_str(self, value: &str) -> Result where E: de::Error { - value.parse().map_err(|err| E::custom(format!("{}", err))) + value.parse().map_err(E::custom) } } diff --git a/src/offset/fixed.rs b/src/offset/fixed.rs index 5b69ef0..01512c0 100644 --- a/src/offset/fixed.rs +++ b/src/offset/fixed.rs @@ -3,8 +3,8 @@ //! The time zone which has a fixed offset from UTC. -use std::ops::{Add, Sub}; -use std::fmt; +use core::ops::{Add, Sub}; +use core::fmt; use oldtime::Duration as OldDuration; use Timelike; diff --git a/src/offset/mod.rs b/src/offset/mod.rs index 566e427..a255292 100644 --- a/src/offset/mod.rs +++ b/src/offset/mod.rs @@ -18,7 +18,7 @@ //! and provides implementations for 1 and 3. //! An `TimeZone` instance can be reconstructed from the corresponding `Offset` instance. -use std::fmt; +use core::fmt; use format::{parse, ParseResult, Parsed, StrftimeItems}; use naive::{NaiveDate, NaiveDateTime, NaiveTime}; diff --git a/src/offset/utc.rs b/src/offset/utc.rs index 87a4fba..da8de11 100644 --- a/src/offset/utc.rs +++ b/src/offset/utc.rs @@ -3,7 +3,7 @@ //! The UTC (Coordinated Universal Time) time zone. -use std::fmt; +use core::fmt; #[cfg(all(feature="clock", not(all(target_arch = "wasm32", feature = "wasmbind"))))] use oldtime; diff --git a/src/oldtime.rs b/src/oldtime.rs index 2bdc2f6..d523b54 100644 --- a/src/oldtime.rs +++ b/src/oldtime.rs @@ -10,10 +10,11 @@ //! Temporal quantification -use std::{fmt, i64}; +use core::{fmt, i64}; +#[cfg(any(feature = "std", test))] use std::error::Error; -use std::ops::{Add, Sub, Mul, Div, Neg}; -use std::time::Duration as StdDuration; +use core::ops::{Add, Sub, Mul, Div, Neg}; +use core::time::Duration as StdDuration; /// The number of nanoseconds in a microsecond. const NANOS_PER_MICRO: i32 = 1000; @@ -392,15 +393,22 @@ impl fmt::Display for Duration { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct OutOfRangeError(()); +impl OutOfRangeError { + fn description(&self) -> &str { + "Source duration value is out of range for the target type" + } +} + impl fmt::Display for OutOfRangeError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.description()) } } +#[cfg(any(feature = "std", test))] impl Error for OutOfRangeError { fn description(&self) -> &str { - "Source duration value is out of range for the target type" + self.description() } } diff --git a/src/round.rs b/src/round.rs index bf62762..ac5b984 100644 --- a/src/round.rs +++ b/src/round.rs @@ -2,7 +2,7 @@ // See README.md and LICENSE.txt for details. use Timelike; -use std::ops::{Add, Sub}; +use core::ops::{Add, Sub}; use oldtime::Duration; /// Extension trait for subsecond rounding or truncation to a maximum number