summaryrefslogtreecommitdiffstats
path: root/vendor/humantime
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/humantime')
-rw-r--r--vendor/humantime/.cargo-checksum.json1
-rw-r--r--vendor/humantime/Cargo.toml37
-rw-r--r--vendor/humantime/LICENSE-APACHE202
-rw-r--r--vendor/humantime/LICENSE-MIT26
-rw-r--r--vendor/humantime/README.md68
-rw-r--r--vendor/humantime/benches/datetime_format.rs56
-rw-r--r--vendor/humantime/benches/datetime_parse.rs47
-rw-r--r--vendor/humantime/bulk.yaml8
-rw-r--r--vendor/humantime/src/date.rs616
-rw-r--r--vendor/humantime/src/duration.rs449
-rw-r--r--vendor/humantime/src/lib.rs34
-rw-r--r--vendor/humantime/src/wrapper.rs107
-rw-r--r--vendor/humantime/vagga.yaml92
13 files changed, 1743 insertions, 0 deletions
diff --git a/vendor/humantime/.cargo-checksum.json b/vendor/humantime/.cargo-checksum.json
new file mode 100644
index 000000000..a5277e445
--- /dev/null
+++ b/vendor/humantime/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{"Cargo.toml":"74afb84b99fa5ba0f58b483df1ef0749821d4c2b5c5a9df299cb1cc0002c236f","LICENSE-APACHE":"c6596eb7be8581c18be736c846fb9173b69eccf6ef94c5135893ec56bd92ba08","LICENSE-MIT":"f6deca8261a8f4a3403dc74c725c46051157fd36c27cd4b100277eb1f303ad11","README.md":"e4bb65f28ddffb11d7eb337e9585947651f2fc11a5e4290f0ca126e21c582c1e","benches/datetime_format.rs":"ffe2e459e9b48e8fdbfb3686f6297257d66b29369ecd6750ae9fbba527ccc681","benches/datetime_parse.rs":"8039c4bd5f1795dbb54e1e39da5988f1d2df6c86c42d8fd378094fc78074d31e","bulk.yaml":"17c2548388e0cd3a63473021a2f1e4ddedee082d79d9167cb31ad06a1890d3fc","src/date.rs":"3f21e426f449e434e5973db9bd06a09d3f0cd6aa77708dbf0e3230bce7e48f44","src/duration.rs":"5a91e6e94c9ca2078ede527a4c159ddfc1cd67ade0d75a1c1db5474b40d2916f","src/lib.rs":"ad4dbed28080d9a64ef0100c96b20ff4988d9dde908f56e28ece7252f5932990","src/wrapper.rs":"badc640e77379a42b2fcb728337d60a764b7f00a1b5b1d50c7372ddc20941967","vagga.yaml":"8396fe1510117c1c7bc3e896b62290dcf2dd300346071297018b0077ad9e45ce"},"package":"3c1ad908cc71012b7bea4d0c53ba96a8cba9962f048fa68d143376143d863b7a"} \ No newline at end of file
diff --git a/vendor/humantime/Cargo.toml b/vendor/humantime/Cargo.toml
new file mode 100644
index 000000000..8d5838951
--- /dev/null
+++ b/vendor/humantime/Cargo.toml
@@ -0,0 +1,37 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies
+#
+# If you believe there's an error in this file please file an
+# issue against the rust-lang/cargo repository. If you're
+# editing this file be aware that the upstream Cargo.toml
+# will likely look very different (and much more reasonable)
+
+[package]
+edition = "2018"
+name = "humantime"
+version = "2.0.1"
+authors = ["Paul Colomiets <paul@colomiets.name>"]
+description = " A parser and formatter for std::time::{Duration, SystemTime}\n"
+homepage = "https://github.com/tailhook/humantime"
+documentation = "https://docs.rs/humantime"
+readme = "README.md"
+keywords = ["time", "human", "human-friendly", "parser", "duration"]
+categories = ["date-and-time"]
+license = "MIT/Apache-2.0"
+repository = "https://github.com/tailhook/humantime"
+
+[lib]
+name = "humantime"
+path = "src/lib.rs"
+[dev-dependencies.chrono]
+version = "0.4"
+
+[dev-dependencies.rand]
+version = "0.6"
+
+[dev-dependencies.time]
+version = "0.1"
diff --git a/vendor/humantime/LICENSE-APACHE b/vendor/humantime/LICENSE-APACHE
new file mode 100644
index 000000000..8f71f43fe
--- /dev/null
+++ b/vendor/humantime/LICENSE-APACHE
@@ -0,0 +1,202 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
diff --git a/vendor/humantime/LICENSE-MIT b/vendor/humantime/LICENSE-MIT
new file mode 100644
index 000000000..a099fbade
--- /dev/null
+++ b/vendor/humantime/LICENSE-MIT
@@ -0,0 +1,26 @@
+Copyright (c) 2016 The humantime Developers
+
+Includes parts of http date with the following copyright:
+Copyright (c) 2016 Pyfisch
+
+Includes portions of musl libc with the following copyright:
+Copyright © 2005-2013 Rich Felker
+
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/humantime/README.md b/vendor/humantime/README.md
new file mode 100644
index 000000000..39156dcdc
--- /dev/null
+++ b/vendor/humantime/README.md
@@ -0,0 +1,68 @@
+Human Time
+==========
+
+**Status: stable**
+
+[Documentation](https://docs.rs/humantime) |
+[Github](https://github.com/tailhook/humantime) |
+[Crate](https://crates.io/crates/humantime)
+
+
+Features:
+
+* Parses durations in free form like `15days 2min 2s`
+* Formats durations in similar form `2years 2min 12us`
+* Parses and formats timestamp in `rfc3339` format: `2018-01-01T12:53:00Z`
+* Parses timestamps in a weaker format: `2018-01-01 12:53:00`
+
+Timestamp parsing/formatting is super-fast because format is basically
+fixed.
+
+Here are some micro-benchmarks:
+
+```
+test result: ok. 0 passed; 0 failed; 26 ignored; 0 measured; 0 filtered out
+
+ Running target/release/deps/datetime_format-8facb4ac832d9770
+
+running 2 tests
+test rfc3339_chrono ... bench: 737 ns/iter (+/- 37)
+test rfc3339_humantime_seconds ... bench: 73 ns/iter (+/- 2)
+
+test result: ok. 0 passed; 0 failed; 0 ignored; 2 measured; 0 filtered out
+
+ Running target/release/deps/datetime_parse-342628f877d7867c
+
+running 6 tests
+test datetime_utc_parse_millis ... bench: 228 ns/iter (+/- 11)
+test datetime_utc_parse_nanos ... bench: 236 ns/iter (+/- 10)
+test datetime_utc_parse_seconds ... bench: 204 ns/iter (+/- 18)
+test rfc3339_humantime_millis ... bench: 28 ns/iter (+/- 1)
+test rfc3339_humantime_nanos ... bench: 36 ns/iter (+/- 2)
+test rfc3339_humantime_seconds ... bench: 24 ns/iter (+/- 1)
+
+test result: ok. 0 passed; 0 failed; 0 ignored; 6 measured; 0 filtered out
+```
+
+See [humantime-serde] for serde integration (previous crate [serde-humantime] looks unmaintained).
+
+[serde-humantime]: https://docs.rs/serde-humantime/0.1.1/serde_humantime/
+[humantime-serde]: https://docs.rs/humantime-serde
+
+License
+=======
+
+Licensed under either of
+
+* Apache License, Version 2.0, (./LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
+* MIT license (./LICENSE-MIT or http://opensource.org/licenses/MIT)
+
+at your option.
+
+Contribution
+------------
+
+Unless you explicitly state otherwise, any contribution intentionally
+submitted for inclusion in the work by you, as defined in the Apache-2.0
+license, shall be dual licensed as above, without any additional terms or
+conditions.
diff --git a/vendor/humantime/benches/datetime_format.rs b/vendor/humantime/benches/datetime_format.rs
new file mode 100644
index 000000000..77d47667c
--- /dev/null
+++ b/vendor/humantime/benches/datetime_format.rs
@@ -0,0 +1,56 @@
+#![feature(test)]
+extern crate test;
+
+use std::io::Write;
+use std::time::{Duration, UNIX_EPOCH};
+
+use humantime::format_rfc3339;
+
+#[bench]
+fn rfc3339_humantime_seconds(b: &mut test::Bencher) {
+ let time = UNIX_EPOCH + Duration::new(1_483_228_799, 0);
+ let mut buf = Vec::with_capacity(100);
+ b.iter(|| {
+ buf.truncate(0);
+ write!(&mut buf, "{}", format_rfc3339(time)).unwrap()
+ });
+}
+
+#[bench]
+fn rfc3339_chrono(b: &mut test::Bencher) {
+ use chrono::{DateTime, NaiveDateTime, Utc};
+ use chrono::format::Item;
+ use chrono::format::Item::*;
+ use chrono::format::Numeric::*;
+ use chrono::format::Fixed::*;
+ use chrono::format::Pad::*;
+
+ let time = DateTime::<Utc>::from_utc(
+ NaiveDateTime::from_timestamp(1_483_228_799, 0), Utc);
+ let mut buf = Vec::with_capacity(100);
+
+ // formatting code from env_logger
+ const ITEMS: &[Item<'static>] = {
+ &[
+ Numeric(Year, Zero),
+ Literal("-"),
+ Numeric(Month, Zero),
+ Literal("-"),
+ Numeric(Day, Zero),
+ Literal("T"),
+ Numeric(Hour, Zero),
+ Literal(":"),
+ Numeric(Minute, Zero),
+ Literal(":"),
+ Numeric(Second, Zero),
+ Fixed(TimezoneOffsetZ),
+ ]
+ };
+
+
+ b.iter(|| {
+ buf.truncate(0);
+ write!(&mut buf, "{}", time.format_with_items(ITEMS.iter().cloned()))
+ .unwrap()
+ });
+}
diff --git a/vendor/humantime/benches/datetime_parse.rs b/vendor/humantime/benches/datetime_parse.rs
new file mode 100644
index 000000000..4248da281
--- /dev/null
+++ b/vendor/humantime/benches/datetime_parse.rs
@@ -0,0 +1,47 @@
+#![feature(test)]
+extern crate test;
+
+use chrono::{DateTime};
+use humantime::parse_rfc3339;
+
+#[bench]
+fn rfc3339_humantime_seconds(b: &mut test::Bencher) {
+ b.iter(|| {
+ parse_rfc3339("2018-02-13T23:08:32Z").unwrap()
+ });
+}
+
+#[bench]
+fn datetime_utc_parse_seconds(b: &mut test::Bencher) {
+ b.iter(|| {
+ DateTime::parse_from_rfc3339("2018-02-13T23:08:32Z").unwrap()
+ });
+}
+
+#[bench]
+fn rfc3339_humantime_millis(b: &mut test::Bencher) {
+ b.iter(|| {
+ parse_rfc3339("2018-02-13T23:08:32.123Z").unwrap()
+ });
+}
+
+#[bench]
+fn datetime_utc_parse_millis(b: &mut test::Bencher) {
+ b.iter(|| {
+ DateTime::parse_from_rfc3339("2018-02-13T23:08:32.123Z").unwrap()
+ });
+}
+
+#[bench]
+fn rfc3339_humantime_nanos(b: &mut test::Bencher) {
+ b.iter(|| {
+ parse_rfc3339("2018-02-13T23:08:32.123456983Z").unwrap()
+ });
+}
+
+#[bench]
+fn datetime_utc_parse_nanos(b: &mut test::Bencher) {
+ b.iter(|| {
+ DateTime::parse_from_rfc3339("2018-02-13T23:08:32.123456983Z").unwrap()
+ });
+}
diff --git a/vendor/humantime/bulk.yaml b/vendor/humantime/bulk.yaml
new file mode 100644
index 000000000..cdb9763b6
--- /dev/null
+++ b/vendor/humantime/bulk.yaml
@@ -0,0 +1,8 @@
+minimum-bulk: v0.4.5
+
+versions:
+
+- file: Cargo.toml
+ block-start: ^\[package\]
+ block-end: ^\[.*\]
+ regex: ^version\s*=\s*"(\S+)"
diff --git a/vendor/humantime/src/date.rs b/vendor/humantime/src/date.rs
new file mode 100644
index 000000000..0afe1f250
--- /dev/null
+++ b/vendor/humantime/src/date.rs
@@ -0,0 +1,616 @@
+use std::error::Error as StdError;
+use std::fmt;
+use std::str;
+use std::time::{SystemTime, Duration, UNIX_EPOCH};
+
+#[cfg(target_os="cloudabi")]
+mod max {
+ pub const SECONDS: u64 = ::std::u64::MAX / 1_000_000_000;
+ #[allow(unused)]
+ pub const TIMESTAMP: &'static str = "2554-07-21T23:34:33Z";
+}
+#[cfg(all(
+ target_pointer_width="32",
+ not(target_os="cloudabi"),
+ not(target_os="windows"),
+ not(all(target_arch="wasm32", not(target_os="emscripten")))
+))]
+mod max {
+ pub const SECONDS: u64 = ::std::i32::MAX as u64;
+ #[allow(unused)]
+ pub const TIMESTAMP: &'static str = "2038-01-19T03:14:07Z";
+}
+
+#[cfg(any(
+ target_pointer_width="64",
+ target_os="windows",
+ all(target_arch="wasm32", not(target_os="emscripten")),
+))]
+mod max {
+ pub const SECONDS: u64 = 253_402_300_800-1; // last second of year 9999
+ #[allow(unused)]
+ pub const TIMESTAMP: &str = "9999-12-31T23:59:59Z";
+}
+
+/// Error parsing datetime (timestamp)
+#[derive(Debug, PartialEq, Clone, Copy)]
+pub enum Error {
+ /// Numeric component is out of range
+ OutOfRange,
+ /// Bad character where digit is expected
+ InvalidDigit,
+ /// Other formatting errors
+ InvalidFormat,
+}
+
+impl StdError for Error {}
+
+impl fmt::Display for Error {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Error::OutOfRange => write!(f, "numeric component is out of range"),
+ Error::InvalidDigit => write!(f, "bad character where digit is expected"),
+ Error::InvalidFormat => write!(f, "timestamp format is invalid"),
+ }
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+enum Precision {
+ Smart,
+ Seconds,
+ Millis,
+ Micros,
+ Nanos,
+}
+
+/// A wrapper type that allows you to Display a SystemTime
+#[derive(Debug, Clone)]
+pub struct Rfc3339Timestamp(SystemTime, Precision);
+
+#[inline]
+fn two_digits(b1: u8, b2: u8) -> Result<u64, Error> {
+ if b1 < b'0' || b2 < b'0' || b1 > b'9' || b2 > b'9' {
+ return Err(Error::InvalidDigit);
+ }
+ Ok(((b1 - b'0')*10 + (b2 - b'0')) as u64)
+}
+
+/// Parse RFC3339 timestamp `2018-02-14T00:28:07Z`
+///
+/// Supported feature: any precision of fractional
+/// digits `2018-02-14T00:28:07.133Z`.
+///
+/// Unsupported feature: localized timestamps. Only UTC is supported.
+pub fn parse_rfc3339(s: &str) -> Result<SystemTime, Error> {
+ if s.len() < "2018-02-14T00:28:07Z".len() {
+ return Err(Error::InvalidFormat);
+ }
+ let b = s.as_bytes();
+ if b[10] != b'T' || b[b.len()-1] != b'Z' {
+ return Err(Error::InvalidFormat);
+ }
+ parse_rfc3339_weak(s)
+}
+
+/// Parse RFC3339-like timestamp `2018-02-14 00:28:07`
+///
+/// Supported features:
+///
+/// 1. Any precision of fractional digits `2018-02-14 00:28:07.133`.
+/// 2. Supports timestamp with or without either of `T` or `Z`
+/// 3. Anything valid for `parse_3339` is valid for this function
+///
+/// Unsupported feature: localized timestamps. Only UTC is supported, even if
+/// `Z` is not specified.
+///
+/// This function is intended to use for parsing human input. Whereas
+/// `parse_rfc3339` is for strings generated programmatically.
+pub fn parse_rfc3339_weak(s: &str) -> Result<SystemTime, Error> {
+ if s.len() < "2018-02-14T00:28:07".len() {
+ return Err(Error::InvalidFormat);
+ }
+ let b = s.as_bytes(); // for careless slicing
+ if b[4] != b'-' || b[7] != b'-' || (b[10] != b'T' && b[10] != b' ') ||
+ b[13] != b':' || b[16] != b':'
+ {
+ return Err(Error::InvalidFormat);
+ }
+ let year = two_digits(b[0], b[1])? * 100 + two_digits(b[2], b[3])?;
+ let month = two_digits(b[5], b[6])?;
+ let day = two_digits(b[8], b[9])?;
+ let hour = two_digits(b[11], b[12])?;
+ let minute = two_digits(b[14], b[15])?;
+ let mut second = two_digits(b[17], b[18])?;
+
+ if year < 1970 || hour > 23 || minute > 59 || second > 60 {
+ return Err(Error::OutOfRange);
+ }
+ // TODO(tailhook) should we check that leaps second is only on midnight ?
+ if second == 60 {
+ second = 59
+ };
+ let leap_years = ((year - 1) - 1968) / 4 - ((year - 1) - 1900) / 100 +
+ ((year - 1) - 1600) / 400;
+ let leap = is_leap_year(year);
+ let (mut ydays, mdays) = match month {
+ 1 => (0, 31),
+ 2 if leap => (31, 29),
+ 2 => (31, 28),
+ 3 => (59, 31),
+ 4 => (90, 30),
+ 5 => (120, 31),
+ 6 => (151, 30),
+ 7 => (181, 31),
+ 8 => (212, 31),
+ 9 => (243, 30),
+ 10 => (273, 31),
+ 11 => (304, 30),
+ 12 => (334, 31),
+ _ => return Err(Error::OutOfRange),
+ };
+ if day > mdays || day == 0 {
+ return Err(Error::OutOfRange);
+ }
+ ydays += day - 1;
+ if leap && month > 2 {
+ ydays += 1;
+ }
+ let days = (year - 1970) * 365 + leap_years + ydays;
+
+ let time = second + minute * 60 + hour * 3600;
+
+ let mut nanos = 0;
+ let mut mult = 100_000_000;
+ if b.get(19) == Some(&b'.') {
+ for idx in 20..b.len() {
+ if b[idx] == b'Z' {
+ if idx == b.len()-1 {
+ break;
+ } else {
+ return Err(Error::InvalidDigit);
+ }
+ }
+ if b[idx] < b'0' || b[idx] > b'9' {
+ return Err(Error::InvalidDigit);
+ }
+ nanos += mult * (b[idx] - b'0') as u32;
+ mult /= 10;
+ }
+ } else if b.len() != 19 && (b.len() > 20 || b[19] != b'Z') {
+ return Err(Error::InvalidFormat);
+ }
+
+ let total_seconds = time + days * 86400;
+ if total_seconds > max::SECONDS {
+ return Err(Error::OutOfRange);
+ }
+
+ Ok(UNIX_EPOCH + Duration::new(total_seconds, nanos))
+}
+
+fn is_leap_year(y: u64) -> bool {
+ y % 4 == 0 && (y % 100 != 0 || y % 400 == 0)
+}
+
+/// Format an RFC3339 timestamp `2018-02-14T00:28:07Z`
+///
+/// This function formats timestamp with smart precision: i.e. if it has no
+/// fractional seconds, they aren't written at all. And up to nine digits if
+/// they are.
+///
+/// The value is always UTC and ignores system timezone.
+pub fn format_rfc3339(system_time: SystemTime) -> Rfc3339Timestamp {
+ Rfc3339Timestamp(system_time, Precision::Smart)
+}
+
+/// Format an RFC3339 timestamp `2018-02-14T00:28:07Z`
+///
+/// This format always shows timestamp without fractional seconds.
+///
+/// The value is always UTC and ignores system timezone.
+pub fn format_rfc3339_seconds(system_time: SystemTime) -> Rfc3339Timestamp {
+ Rfc3339Timestamp(system_time, Precision::Seconds)
+}
+
+/// Format an RFC3339 timestamp `2018-02-14T00:28:07.000Z`
+///
+/// This format always shows milliseconds even if millisecond value is zero.
+///
+/// The value is always UTC and ignores system timezone.
+pub fn format_rfc3339_millis(system_time: SystemTime) -> Rfc3339Timestamp {
+ Rfc3339Timestamp(system_time, Precision::Millis)
+}
+
+/// Format an RFC3339 timestamp `2018-02-14T00:28:07.000000Z`
+///
+/// This format always shows microseconds even if microsecond value is zero.
+///
+/// The value is always UTC and ignores system timezone.
+pub fn format_rfc3339_micros(system_time: SystemTime) -> Rfc3339Timestamp {
+ Rfc3339Timestamp(system_time, Precision::Micros)
+}
+
+/// Format an RFC3339 timestamp `2018-02-14T00:28:07.000000000Z`
+///
+/// This format always shows nanoseconds even if nanosecond value is zero.
+///
+/// The value is always UTC and ignores system timezone.
+pub fn format_rfc3339_nanos(system_time: SystemTime) -> Rfc3339Timestamp {
+ Rfc3339Timestamp(system_time, Precision::Nanos)
+}
+
+impl fmt::Display for Rfc3339Timestamp {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ use self::Precision::*;
+
+ let dur = self.0.duration_since(UNIX_EPOCH)
+ .expect("all times should be after the epoch");
+ let secs_since_epoch = dur.as_secs();
+ let nanos = dur.subsec_nanos();
+
+ if secs_since_epoch >= 253_402_300_800 { // year 9999
+ return Err(fmt::Error);
+ }
+
+ /* 2000-03-01 (mod 400 year, immediately after feb29 */
+ const LEAPOCH: i64 = 11017;
+ const DAYS_PER_400Y: i64 = 365*400 + 97;
+ const DAYS_PER_100Y: i64 = 365*100 + 24;
+ const DAYS_PER_4Y: i64 = 365*4 + 1;
+
+ let days = (secs_since_epoch / 86400) as i64 - LEAPOCH;
+ let secs_of_day = secs_since_epoch % 86400;
+
+ let mut qc_cycles = days / DAYS_PER_400Y;
+ let mut remdays = days % DAYS_PER_400Y;
+
+ if remdays < 0 {
+ remdays += DAYS_PER_400Y;
+ qc_cycles -= 1;
+ }
+
+ let mut c_cycles = remdays / DAYS_PER_100Y;
+ if c_cycles == 4 { c_cycles -= 1; }
+ remdays -= c_cycles * DAYS_PER_100Y;
+
+ let mut q_cycles = remdays / DAYS_PER_4Y;
+ if q_cycles == 25 { q_cycles -= 1; }
+ remdays -= q_cycles * DAYS_PER_4Y;
+
+ let mut remyears = remdays / 365;
+ if remyears == 4 { remyears -= 1; }
+ remdays -= remyears * 365;
+
+ let mut year = 2000 +
+ remyears + 4*q_cycles + 100*c_cycles + 400*qc_cycles;
+
+ let months = [31,30,31,30,31,31,30,31,30,31,31,29];
+ let mut mon = 0;
+ for mon_len in months.iter() {
+ mon += 1;
+ if remdays < *mon_len {
+ break;
+ }
+ remdays -= *mon_len;
+ }
+ let mday = remdays+1;
+ let mon = if mon + 2 > 12 {
+ year += 1;
+ mon - 10
+ } else {
+ mon + 2
+ };
+
+ let mut buf: [u8; 30] = [
+ // Too long to write as: b"0000-00-00T00:00:00.000000000Z"
+ b'0', b'0', b'0', b'0', b'-', b'0', b'0', b'-', b'0', b'0', b'T',
+ b'0', b'0', b':', b'0', b'0', b':', b'0', b'0',
+ b'.', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'Z',
+ ];
+ buf[0] = b'0' + (year / 1000) as u8;
+ buf[1] = b'0' + (year / 100 % 10) as u8;
+ buf[2] = b'0' + (year / 10 % 10) as u8;
+ buf[3] = b'0' + (year % 10) as u8;
+ buf[5] = b'0' + (mon / 10) as u8;
+ buf[6] = b'0' + (mon % 10) as u8;
+ buf[8] = b'0' + (mday / 10) as u8;
+ buf[9] = b'0' + (mday % 10) as u8;
+ buf[11] = b'0' + (secs_of_day / 3600 / 10) as u8;
+ buf[12] = b'0' + (secs_of_day / 3600 % 10) as u8;
+ buf[14] = b'0' + (secs_of_day / 60 / 10 % 6) as u8;
+ buf[15] = b'0' + (secs_of_day / 60 % 10) as u8;
+ buf[17] = b'0' + (secs_of_day / 10 % 6) as u8;
+ buf[18] = b'0' + (secs_of_day % 10) as u8;
+
+ let offset = if self.1 == Seconds || nanos == 0 && self.1 == Smart {
+ buf[19] = b'Z';
+ 19
+ } else if self.1 == Millis {
+ buf[20] = b'0' + (nanos / 100_000_000) as u8;
+ buf[21] = b'0' + (nanos / 10_000_000 % 10) as u8;
+ buf[22] = b'0' + (nanos / 1_000_000 % 10) as u8;
+ buf[23] = b'Z';
+ 23
+ } else if self.1 == Micros {
+ buf[20] = b'0' + (nanos / 100_000_000) as u8;
+ buf[21] = b'0' + (nanos / 10_000_000 % 10) as u8;
+ buf[22] = b'0' + (nanos / 1_000_000 % 10) as u8;
+ buf[23] = b'0' + (nanos / 100_000 % 10) as u8;
+ buf[24] = b'0' + (nanos / 10_000 % 10) as u8;
+ buf[25] = b'0' + (nanos / 1_000 % 10) as u8;
+ buf[26] = b'Z';
+ 26
+ } else {
+ buf[20] = b'0' + (nanos / 100_000_000) as u8;
+ buf[21] = b'0' + (nanos / 10_000_000 % 10) as u8;
+ buf[22] = b'0' + (nanos / 1_000_000 % 10) as u8;
+ buf[23] = b'0' + (nanos / 100_000 % 10) as u8;
+ buf[24] = b'0' + (nanos / 10_000 % 10) as u8;
+ buf[25] = b'0' + (nanos / 1_000 % 10) as u8;
+ buf[26] = b'0' + (nanos / 100 % 10) as u8;
+ buf[27] = b'0' + (nanos / 10 % 10) as u8;
+ buf[28] = b'0' + (nanos / 1 % 10) as u8;
+ // 29th is 'Z'
+ 29
+ };
+
+ // we know our chars are all ascii
+ f.write_str(str::from_utf8(&buf[..=offset]).expect("Conversion to utf8 failed"))
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use std::str::from_utf8;
+ use std::time::{UNIX_EPOCH, SystemTime, Duration};
+
+ use rand::Rng;
+
+ use super::{parse_rfc3339, parse_rfc3339_weak, format_rfc3339};
+ use super::{format_rfc3339_millis, format_rfc3339_micros};
+ use super::{format_rfc3339_nanos};
+ use super::max;
+
+ fn from_sec(sec: u64) -> (String, SystemTime) {
+ let s = time::at_utc(time::Timespec { sec: sec as i64, nsec: 0 })
+ .rfc3339().to_string();
+ let time = UNIX_EPOCH + Duration::new(sec, 0);
+ (s, time)
+ }
+
+ #[test]
+ #[cfg(all(target_pointer_width="32", target_os="linux"))]
+ fn year_after_2038_fails_gracefully() {
+ // next second
+ assert_eq!(parse_rfc3339("2038-01-19T03:14:08Z").unwrap_err(),
+ super::Error::OutOfRange);
+ assert_eq!(parse_rfc3339("9999-12-31T23:59:59Z").unwrap_err(),
+ super::Error::OutOfRange);
+ }
+
+ #[test]
+ fn smoke_tests_parse() {
+ assert_eq!(parse_rfc3339("1970-01-01T00:00:00Z").unwrap(),
+ UNIX_EPOCH + Duration::new(0, 0));
+ assert_eq!(parse_rfc3339("1970-01-01T00:00:01Z").unwrap(),
+ UNIX_EPOCH + Duration::new(1, 0));
+ assert_eq!(parse_rfc3339("2018-02-13T23:08:32Z").unwrap(),
+ UNIX_EPOCH + Duration::new(1_518_563_312, 0));
+ assert_eq!(parse_rfc3339("2012-01-01T00:00:00Z").unwrap(),
+ UNIX_EPOCH + Duration::new(1_325_376_000, 0));
+ }
+
+ #[test]
+ fn smoke_tests_format() {
+ assert_eq!(
+ format_rfc3339(UNIX_EPOCH + Duration::new(0, 0)).to_string(),
+ "1970-01-01T00:00:00Z");
+ assert_eq!(
+ format_rfc3339(UNIX_EPOCH + Duration::new(1, 0)).to_string(),
+ "1970-01-01T00:00:01Z");
+ assert_eq!(
+ format_rfc3339(UNIX_EPOCH + Duration::new(1_518_563_312, 0)).to_string(),
+ "2018-02-13T23:08:32Z");
+ assert_eq!(
+ format_rfc3339(UNIX_EPOCH + Duration::new(1_325_376_000, 0)).to_string(),
+ "2012-01-01T00:00:00Z");
+ }
+
+ #[test]
+ fn smoke_tests_format_millis() {
+ assert_eq!(
+ format_rfc3339_millis(UNIX_EPOCH +
+ Duration::new(0, 0)).to_string(),
+ "1970-01-01T00:00:00.000Z");
+ assert_eq!(
+ format_rfc3339_millis(UNIX_EPOCH +
+ Duration::new(1_518_563_312, 123_000_000)).to_string(),
+ "2018-02-13T23:08:32.123Z");
+ }
+
+ #[test]
+ fn smoke_tests_format_micros() {
+ assert_eq!(
+ format_rfc3339_micros(UNIX_EPOCH +
+ Duration::new(0, 0)).to_string(),
+ "1970-01-01T00:00:00.000000Z");
+ assert_eq!(
+ format_rfc3339_micros(UNIX_EPOCH +
+ Duration::new(1_518_563_312, 123_000_000)).to_string(),
+ "2018-02-13T23:08:32.123000Z");
+ assert_eq!(
+ format_rfc3339_micros(UNIX_EPOCH +
+ Duration::new(1_518_563_312, 456_123_000)).to_string(),
+ "2018-02-13T23:08:32.456123Z");
+ }
+
+ #[test]
+ fn smoke_tests_format_nanos() {
+ assert_eq!(
+ format_rfc3339_nanos(UNIX_EPOCH +
+ Duration::new(0, 0)).to_string(),
+ "1970-01-01T00:00:00.000000000Z");
+ assert_eq!(
+ format_rfc3339_nanos(UNIX_EPOCH +
+ Duration::new(1_518_563_312, 123_000_000)).to_string(),
+ "2018-02-13T23:08:32.123000000Z");
+ assert_eq!(
+ format_rfc3339_nanos(UNIX_EPOCH +
+ Duration::new(1_518_563_312, 789_456_123)).to_string(),
+ "2018-02-13T23:08:32.789456123Z");
+ }
+
+ #[test]
+ fn upper_bound() {
+ let max = UNIX_EPOCH + Duration::new(max::SECONDS, 0);
+ assert_eq!(parse_rfc3339(&max::TIMESTAMP).unwrap(), max);
+ assert_eq!(format_rfc3339(max).to_string(), max::TIMESTAMP);
+ }
+
+ #[test]
+ fn leap_second() {
+ assert_eq!(parse_rfc3339("2016-12-31T23:59:60Z").unwrap(),
+ UNIX_EPOCH + Duration::new(1_483_228_799, 0));
+ }
+
+ #[test]
+ fn first_731_days() {
+ let year_start = 0; // 1970
+ for day in 0..= 365 * 2 { // scan leap year and non-leap year
+ let (s, time) = from_sec(year_start + day * 86400);
+ assert_eq!(parse_rfc3339(&s).unwrap(), time);
+ assert_eq!(format_rfc3339(time).to_string(), s);
+ }
+ }
+
+ #[test]
+ fn the_731_consecutive_days() {
+ let year_start = 1_325_376_000; // 2012
+ for day in 0..= 365 * 2 { // scan leap year and non-leap year
+ let (s, time) = from_sec(year_start + day * 86400);
+ assert_eq!(parse_rfc3339(&s).unwrap(), time);
+ assert_eq!(format_rfc3339(time).to_string(), s);
+ }
+ }
+
+ #[test]
+ fn all_86400_seconds() {
+ let day_start = 1_325_376_000;
+ for second in 0..86400 { // scan leap year and non-leap year
+ let (s, time) = from_sec(day_start + second);
+ assert_eq!(parse_rfc3339(&s).unwrap(), time);
+ assert_eq!(format_rfc3339(time).to_string(), s);
+ }
+ }
+
+ #[test]
+ fn random_past() {
+ let upper = SystemTime::now().duration_since(UNIX_EPOCH).unwrap()
+ .as_secs();
+ for _ in 0..10000 {
+ let sec = rand::thread_rng().gen_range(0, upper);
+ let (s, time) = from_sec(sec);
+ assert_eq!(parse_rfc3339(&s).unwrap(), time);
+ assert_eq!(format_rfc3339(time).to_string(), s);
+ }
+ }
+
+ #[test]
+ fn random_wide_range() {
+ for _ in 0..100_000 {
+ let sec = rand::thread_rng().gen_range(0, max::SECONDS);
+ let (s, time) = from_sec(sec);
+ assert_eq!(parse_rfc3339(&s).unwrap(), time);
+ assert_eq!(format_rfc3339(time).to_string(), s);
+ }
+ }
+
+ #[test]
+ fn milliseconds() {
+ assert_eq!(parse_rfc3339("1970-01-01T00:00:00.123Z").unwrap(),
+ UNIX_EPOCH + Duration::new(0, 123_000_000));
+ assert_eq!(format_rfc3339(UNIX_EPOCH + Duration::new(0, 123_000_000))
+ .to_string(), "1970-01-01T00:00:00.123000000Z");
+ }
+
+ #[test]
+ #[should_panic(expected="OutOfRange")]
+ fn zero_month() {
+ parse_rfc3339("1970-00-01T00:00:00Z").unwrap();
+ }
+
+ #[test]
+ #[should_panic(expected="OutOfRange")]
+ fn big_month() {
+ parse_rfc3339("1970-32-01T00:00:00Z").unwrap();
+ }
+
+ #[test]
+ #[should_panic(expected="OutOfRange")]
+ fn zero_day() {
+ parse_rfc3339("1970-01-00T00:00:00Z").unwrap();
+ }
+
+ #[test]
+ #[should_panic(expected="OutOfRange")]
+ fn big_day() {
+ parse_rfc3339("1970-12-35T00:00:00Z").unwrap();
+ }
+
+ #[test]
+ #[should_panic(expected="OutOfRange")]
+ fn big_day2() {
+ parse_rfc3339("1970-02-30T00:00:00Z").unwrap();
+ }
+
+ #[test]
+ #[should_panic(expected="OutOfRange")]
+ fn big_second() {
+ parse_rfc3339("1970-12-30T00:00:78Z").unwrap();
+ }
+
+ #[test]
+ #[should_panic(expected="OutOfRange")]
+ fn big_minute() {
+ parse_rfc3339("1970-12-30T00:78:00Z").unwrap();
+ }
+
+ #[test]
+ #[should_panic(expected="OutOfRange")]
+ fn big_hour() {
+ parse_rfc3339("1970-12-30T24:00:00Z").unwrap();
+ }
+
+ #[test]
+ fn break_data() {
+ for pos in 0.."2016-12-31T23:59:60Z".len() {
+ let mut s = b"2016-12-31T23:59:60Z".to_vec();
+ s[pos] = b'x';
+ parse_rfc3339(from_utf8(&s).unwrap()).unwrap_err();
+ }
+ }
+
+ #[test]
+ fn weak_smoke_tests() {
+ assert_eq!(parse_rfc3339_weak("1970-01-01 00:00:00").unwrap(),
+ UNIX_EPOCH + Duration::new(0, 0));
+ parse_rfc3339("1970-01-01 00:00:00").unwrap_err();
+
+ assert_eq!(parse_rfc3339_weak("1970-01-01 00:00:00.000123").unwrap(),
+ UNIX_EPOCH + Duration::new(0, 123_000));
+ parse_rfc3339("1970-01-01 00:00:00.000123").unwrap_err();
+
+ assert_eq!(parse_rfc3339_weak("1970-01-01T00:00:00.000123").unwrap(),
+ UNIX_EPOCH + Duration::new(0, 123_000));
+ parse_rfc3339("1970-01-01T00:00:00.000123").unwrap_err();
+
+ assert_eq!(parse_rfc3339_weak("1970-01-01 00:00:00.000123Z").unwrap(),
+ UNIX_EPOCH + Duration::new(0, 123_000));
+ parse_rfc3339("1970-01-01 00:00:00.000123Z").unwrap_err();
+
+ assert_eq!(parse_rfc3339_weak("1970-01-01 00:00:00Z").unwrap(),
+ UNIX_EPOCH + Duration::new(0, 0));
+ parse_rfc3339("1970-01-01 00:00:00Z").unwrap_err();
+ }
+}
diff --git a/vendor/humantime/src/duration.rs b/vendor/humantime/src/duration.rs
new file mode 100644
index 000000000..8359b851d
--- /dev/null
+++ b/vendor/humantime/src/duration.rs
@@ -0,0 +1,449 @@
+use std::error::Error as StdError;
+use std::fmt;
+use std::str::Chars;
+use std::time::Duration;
+
+/// Error parsing human-friendly duration
+#[derive(Debug, PartialEq, Clone)]
+pub enum Error {
+ /// Invalid character during parsing
+ ///
+ /// More specifically anything that is not alphanumeric is prohibited
+ ///
+ /// The field is an byte offset of the character in the string.
+ InvalidCharacter(usize),
+ /// Non-numeric value where number is expected
+ ///
+ /// This usually means that either time unit is broken into words,
+ /// e.g. `m sec` instead of `msec`, or just number is omitted,
+ /// for example `2 hours min` instead of `2 hours 1 min`
+ ///
+ /// The field is an byte offset of the errorneous character
+ /// in the string.
+ NumberExpected(usize),
+ /// Unit in the number is not one of allowed units
+ ///
+ /// See documentation of `parse_duration` for the list of supported
+ /// time units.
+ ///
+ /// The two fields are start and end (exclusive) of the slice from
+ /// the original string, containing errorneous value
+ UnknownUnit {
+ /// Start of the invalid unit inside the original string
+ start: usize,
+ /// End of the invalid unit inside the original string
+ end: usize,
+ /// The unit verbatim
+ unit: String,
+ /// A number associated with the unit
+ value: u64,
+ },
+ /// The numeric value is too large
+ ///
+ /// Usually this means value is too large to be useful. If user writes
+ /// data in subsecond units, then the maximum is about 3k years. When
+ /// using seconds, or larger units, the limit is even larger.
+ NumberOverflow,
+ /// The value was an empty string (or consists only whitespace)
+ Empty,
+}
+
+impl StdError for Error {}
+
+impl fmt::Display for Error {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Error::InvalidCharacter(offset) => write!(f, "invalid character at {}", offset),
+ Error::NumberExpected(offset) => write!(f, "expected number at {}", offset),
+ Error::UnknownUnit { unit, value, .. } if &unit == &"" => {
+ write!(f,
+ "time unit needed, for example {0}sec or {0}ms",
+ value,
+ )
+ }
+ Error::UnknownUnit { unit, .. } => {
+ write!(
+ f,
+ "unknown time unit {:?}, \
+ supported units: ns, us, ms, sec, min, hours, days, \
+ weeks, months, years (and few variations)",
+ unit
+ )
+ }
+ Error::NumberOverflow => write!(f, "number is too large"),
+ Error::Empty => write!(f, "value was empty"),
+ }
+ }
+}
+
+/// A wrapper type that allows you to Display a Duration
+#[derive(Debug, Clone)]
+pub struct FormattedDuration(Duration);
+
+trait OverflowOp: Sized {
+ fn mul(self, other: Self) -> Result<Self, Error>;
+ fn add(self, other: Self) -> Result<Self, Error>;
+}
+
+impl OverflowOp for u64 {
+ fn mul(self, other: Self) -> Result<Self, Error> {
+ self.checked_mul(other).ok_or(Error::NumberOverflow)
+ }
+ fn add(self, other: Self) -> Result<Self, Error> {
+ self.checked_add(other).ok_or(Error::NumberOverflow)
+ }
+}
+
+struct Parser<'a> {
+ iter: Chars<'a>,
+ src: &'a str,
+ current: (u64, u64),
+}
+
+impl<'a> Parser<'a> {
+ fn off(&self) -> usize {
+ self.src.len() - self.iter.as_str().len()
+ }
+
+ fn parse_first_char(&mut self) -> Result<Option<u64>, Error> {
+ let off = self.off();
+ for c in self.iter.by_ref() {
+ match c {
+ '0'..='9' => {
+ return Ok(Some(c as u64 - '0' as u64));
+ }
+ c if c.is_whitespace() => continue,
+ _ => {
+ return Err(Error::NumberExpected(off));
+ }
+ }
+ }
+ Ok(None)
+ }
+ fn parse_unit(&mut self, n: u64, start: usize, end: usize)
+ -> Result<(), Error>
+ {
+ let (mut sec, nsec) = match &self.src[start..end] {
+ "nanos" | "nsec" | "ns" => (0u64, n),
+ "usec" | "us" => (0u64, n.mul(1000)?),
+ "millis" | "msec" | "ms" => (0u64, n.mul(1_000_000)?),
+ "seconds" | "second" | "secs" | "sec" | "s" => (n, 0),
+ "minutes" | "minute" | "min" | "mins" | "m"
+ => (n.mul(60)?, 0),
+ "hours" | "hour" | "hr" | "hrs" | "h" => (n.mul(3600)?, 0),
+ "days" | "day" | "d" => (n.mul(86400)?, 0),
+ "weeks" | "week" | "w" => (n.mul(86400*7)?, 0),
+ "months" | "month" | "M" => (n.mul(2_630_016)?, 0), // 30.44d
+ "years" | "year" | "y" => (n.mul(31_557_600)?, 0), // 365.25d
+ _ => {
+ return Err(Error::UnknownUnit {
+ start, end,
+ unit: self.src[start..end].to_string(),
+ value: n,
+ });
+ }
+ };
+ let mut nsec = self.current.1.add(nsec)?;
+ if nsec > 1_000_000_000 {
+ sec = sec.add(nsec / 1_000_000_000)?;
+ nsec %= 1_000_000_000;
+ }
+ sec = self.current.0.add(sec)?;
+ self.current = (sec, nsec);
+ Ok(())
+ }
+
+ fn parse(mut self) -> Result<Duration, Error> {
+ let mut n = self.parse_first_char()?.ok_or(Error::Empty)?;
+ 'outer: loop {
+ let mut off = self.off();
+ while let Some(c) = self.iter.next() {
+ match c {
+ '0'..='9' => {
+ n = n.checked_mul(10)
+ .and_then(|x| x.checked_add(c as u64 - '0' as u64))
+ .ok_or(Error::NumberOverflow)?;
+ }
+ c if c.is_whitespace() => {}
+ 'a'..='z' | 'A'..='Z' => {
+ break;
+ }
+ _ => {
+ return Err(Error::InvalidCharacter(off));
+ }
+ }
+ off = self.off();
+ }
+ let start = off;
+ let mut off = self.off();
+ while let Some(c) = self.iter.next() {
+ match c {
+ '0'..='9' => {
+ self.parse_unit(n, start, off)?;
+ n = c as u64 - '0' as u64;
+ continue 'outer;
+ }
+ c if c.is_whitespace() => break,
+ 'a'..='z' | 'A'..='Z' => {}
+ _ => {
+ return Err(Error::InvalidCharacter(off));
+ }
+ }
+ off = self.off();
+ }
+ self.parse_unit(n, start, off)?;
+ n = match self.parse_first_char()? {
+ Some(n) => n,
+ None => return Ok(
+ Duration::new(self.current.0, self.current.1 as u32)),
+ };
+ }
+ }
+
+}
+
+/// Parse duration object `1hour 12min 5s`
+///
+/// The duration object is a concatenation of time spans. Where each time
+/// span is an integer number and a suffix. Supported suffixes:
+///
+/// * `nsec`, `ns` -- nanoseconds
+/// * `usec`, `us` -- microseconds
+/// * `msec`, `ms` -- milliseconds
+/// * `seconds`, `second`, `sec`, `s`
+/// * `minutes`, `minute`, `min`, `m`
+/// * `hours`, `hour`, `hr`, `h`
+/// * `days`, `day`, `d`
+/// * `weeks`, `week`, `w`
+/// * `months`, `month`, `M` -- defined as 30.44 days
+/// * `years`, `year`, `y` -- defined as 365.25 days
+///
+/// # Examples
+///
+/// ```
+/// use std::time::Duration;
+/// use humantime::parse_duration;
+///
+/// assert_eq!(parse_duration("2h 37min"), Ok(Duration::new(9420, 0)));
+/// assert_eq!(parse_duration("32ms"), Ok(Duration::new(0, 32_000_000)));
+/// ```
+pub fn parse_duration(s: &str) -> Result<Duration, Error> {
+ Parser {
+ iter: s.chars(),
+ src: s,
+ current: (0, 0),
+ }.parse()
+}
+
+/// Formats duration into a human-readable string
+///
+/// Note: this format is guaranteed to have same value when using
+/// parse_duration, but we can change some details of the exact composition
+/// of the value.
+///
+/// # Examples
+///
+/// ```
+/// use std::time::Duration;
+/// use humantime::format_duration;
+///
+/// let val1 = Duration::new(9420, 0);
+/// assert_eq!(format_duration(val1).to_string(), "2h 37m");
+/// let val2 = Duration::new(0, 32_000_000);
+/// assert_eq!(format_duration(val2).to_string(), "32ms");
+/// ```
+pub fn format_duration(val: Duration) -> FormattedDuration {
+ FormattedDuration(val)
+}
+
+fn item_plural(f: &mut fmt::Formatter, started: &mut bool,
+ name: &str, value: u64)
+ -> fmt::Result
+{
+ if value > 0 {
+ if *started {
+ f.write_str(" ")?;
+ }
+ write!(f, "{}{}", value, name)?;
+ if value > 1 {
+ f.write_str("s")?;
+ }
+ *started = true;
+ }
+ Ok(())
+}
+fn item(f: &mut fmt::Formatter, started: &mut bool, name: &str, value: u32)
+ -> fmt::Result
+{
+ if value > 0 {
+ if *started {
+ f.write_str(" ")?;
+ }
+ write!(f, "{}{}", value, name)?;
+ *started = true;
+ }
+ Ok(())
+}
+
+impl fmt::Display for FormattedDuration {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ let secs = self.0.as_secs();
+ let nanos = self.0.subsec_nanos();
+
+ if secs == 0 && nanos == 0 {
+ f.write_str("0s")?;
+ return Ok(());
+ }
+
+ let years = secs / 31_557_600; // 365.25d
+ let ydays = secs % 31_557_600;
+ let months = ydays / 2_630_016; // 30.44d
+ let mdays = ydays % 2_630_016;
+ let days = mdays / 86400;
+ let day_secs = mdays % 86400;
+ let hours = day_secs / 3600;
+ let minutes = day_secs % 3600 / 60;
+ let seconds = day_secs % 60;
+
+ let millis = nanos / 1_000_000;
+ let micros = nanos / 1000 % 1000;
+ let nanosec = nanos % 1000;
+
+ let ref mut started = false;
+ item_plural(f, started, "year", years)?;
+ item_plural(f, started, "month", months)?;
+ item_plural(f, started, "day", days)?;
+ item(f, started, "h", hours as u32)?;
+ item(f, started, "m", minutes as u32)?;
+ item(f, started, "s", seconds as u32)?;
+ item(f, started, "ms", millis)?;
+ item(f, started, "us", micros)?;
+ item(f, started, "ns", nanosec)?;
+ Ok(())
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use std::time::Duration;
+
+ use rand::Rng;
+
+ use super::{parse_duration, format_duration};
+ use super::Error;
+
+ #[test]
+ #[allow(clippy::cognitive_complexity)]
+ fn test_units() {
+ assert_eq!(parse_duration("17nsec"), Ok(Duration::new(0, 17)));
+ assert_eq!(parse_duration("17nanos"), Ok(Duration::new(0, 17)));
+ assert_eq!(parse_duration("33ns"), Ok(Duration::new(0, 33)));
+ assert_eq!(parse_duration("3usec"), Ok(Duration::new(0, 3000)));
+ assert_eq!(parse_duration("78us"), Ok(Duration::new(0, 78000)));
+ assert_eq!(parse_duration("31msec"), Ok(Duration::new(0, 31_000_000)));
+ assert_eq!(parse_duration("31millis"), Ok(Duration::new(0, 31_000_000)));
+ assert_eq!(parse_duration("6ms"), Ok(Duration::new(0, 6_000_000)));
+ assert_eq!(parse_duration("3000s"), Ok(Duration::new(3000, 0)));
+ assert_eq!(parse_duration("300sec"), Ok(Duration::new(300, 0)));
+ assert_eq!(parse_duration("300secs"), Ok(Duration::new(300, 0)));
+ assert_eq!(parse_duration("50seconds"), Ok(Duration::new(50, 0)));
+ assert_eq!(parse_duration("1second"), Ok(Duration::new(1, 0)));
+ assert_eq!(parse_duration("100m"), Ok(Duration::new(6000, 0)));
+ assert_eq!(parse_duration("12min"), Ok(Duration::new(720, 0)));
+ assert_eq!(parse_duration("12mins"), Ok(Duration::new(720, 0)));
+ assert_eq!(parse_duration("1minute"), Ok(Duration::new(60, 0)));
+ assert_eq!(parse_duration("7minutes"), Ok(Duration::new(420, 0)));
+ assert_eq!(parse_duration("2h"), Ok(Duration::new(7200, 0)));
+ assert_eq!(parse_duration("7hr"), Ok(Duration::new(25200, 0)));
+ assert_eq!(parse_duration("7hrs"), Ok(Duration::new(25200, 0)));
+ assert_eq!(parse_duration("1hour"), Ok(Duration::new(3600, 0)));
+ assert_eq!(parse_duration("24hours"), Ok(Duration::new(86400, 0)));
+ assert_eq!(parse_duration("1day"), Ok(Duration::new(86400, 0)));
+ assert_eq!(parse_duration("2days"), Ok(Duration::new(172_800, 0)));
+ assert_eq!(parse_duration("365d"), Ok(Duration::new(31_536_000, 0)));
+ assert_eq!(parse_duration("1week"), Ok(Duration::new(604_800, 0)));
+ assert_eq!(parse_duration("7weeks"), Ok(Duration::new(4_233_600, 0)));
+ assert_eq!(parse_duration("52w"), Ok(Duration::new(31_449_600, 0)));
+ assert_eq!(parse_duration("1month"), Ok(Duration::new(2_630_016, 0)));
+ assert_eq!(parse_duration("3months"), Ok(Duration::new(3*2_630_016, 0)));
+ assert_eq!(parse_duration("12M"), Ok(Duration::new(31_560_192, 0)));
+ assert_eq!(parse_duration("1year"), Ok(Duration::new(31_557_600, 0)));
+ assert_eq!(parse_duration("7years"), Ok(Duration::new(7*31_557_600, 0)));
+ assert_eq!(parse_duration("17y"), Ok(Duration::new(536_479_200, 0)));
+ }
+
+ #[test]
+ fn test_combo() {
+ assert_eq!(parse_duration("20 min 17 nsec "), Ok(Duration::new(1200, 17)));
+ assert_eq!(parse_duration("2h 15m"), Ok(Duration::new(8100, 0)));
+ }
+
+ #[test]
+ fn all_86400_seconds() {
+ for second in 0..86400 { // scan leap year and non-leap year
+ let d = Duration::new(second, 0);
+ assert_eq!(d,
+ parse_duration(&format_duration(d).to_string()).unwrap());
+ }
+ }
+
+ #[test]
+ fn random_second() {
+ for _ in 0..10000 {
+ let sec = rand::thread_rng().gen_range(0, 253_370_764_800);
+ let d = Duration::new(sec, 0);
+ assert_eq!(d,
+ parse_duration(&format_duration(d).to_string()).unwrap());
+ }
+ }
+
+ #[test]
+ fn random_any() {
+ for _ in 0..10000 {
+ let sec = rand::thread_rng().gen_range(0, 253_370_764_800);
+ let nanos = rand::thread_rng().gen_range(0, 1_000_000_000);
+ let d = Duration::new(sec, nanos);
+ assert_eq!(d,
+ parse_duration(&format_duration(d).to_string()).unwrap());
+ }
+ }
+
+ #[test]
+ fn test_overlow() {
+ // Overflow on subseconds is earlier because of how we do conversion
+ // we could fix it, but I don't see any good reason for this
+ assert_eq!(parse_duration("100000000000000000000ns"),
+ Err(Error::NumberOverflow));
+ assert_eq!(parse_duration("100000000000000000us"),
+ Err(Error::NumberOverflow));
+ assert_eq!(parse_duration("100000000000000ms"),
+ Err(Error::NumberOverflow));
+
+ assert_eq!(parse_duration("100000000000000000000s"),
+ Err(Error::NumberOverflow));
+ assert_eq!(parse_duration("10000000000000000000m"),
+ Err(Error::NumberOverflow));
+ assert_eq!(parse_duration("1000000000000000000h"),
+ Err(Error::NumberOverflow));
+ assert_eq!(parse_duration("100000000000000000d"),
+ Err(Error::NumberOverflow));
+ assert_eq!(parse_duration("10000000000000000w"),
+ Err(Error::NumberOverflow));
+ assert_eq!(parse_duration("1000000000000000M"),
+ Err(Error::NumberOverflow));
+ assert_eq!(parse_duration("10000000000000y"),
+ Err(Error::NumberOverflow));
+ }
+
+ #[test]
+ fn test_nice_error_message() {
+ assert_eq!(parse_duration("123").unwrap_err().to_string(),
+ "time unit needed, for example 123sec or 123ms");
+ assert_eq!(parse_duration("10 months 1").unwrap_err().to_string(),
+ "time unit needed, for example 1sec or 1ms");
+ assert_eq!(parse_duration("10nights").unwrap_err().to_string(),
+ "unknown time unit \"nights\", supported units: \
+ ns, us, ms, sec, min, hours, days, weeks, months, \
+ years (and few variations)");
+ }
+}
diff --git a/vendor/humantime/src/lib.rs b/vendor/humantime/src/lib.rs
new file mode 100644
index 000000000..9be6f2285
--- /dev/null
+++ b/vendor/humantime/src/lib.rs
@@ -0,0 +1,34 @@
+//! Human-friendly time parser and formatter
+//!
+//! Features:
+//!
+//! * Parses durations in free form like `15days 2min 2s`
+//! * Formats durations in similar form `2years 2min 12us`
+//! * Parses and formats timestamp in `rfc3339` format: `2018-01-01T12:53:00Z`
+//! * Parses timestamps in a weaker format: `2018-01-01 12:53:00`
+//!
+//! Timestamp parsing/formatting is super-fast because format is basically
+//! fixed.
+//!
+//! See [humantime-serde] for serde integration (previous crate [serde-humantime] looks unmaintained).
+//!
+//! [serde-humantime]: https://docs.rs/serde-humantime/0.1.1/serde_humantime/
+//! [humantime-serde]: https://docs.rs/humantime-serde
+
+#![forbid(unsafe_code)]
+#![warn(missing_debug_implementations)]
+#![warn(missing_docs)]
+
+mod duration;
+mod wrapper;
+mod date;
+
+pub use self::duration::{parse_duration, Error as DurationError};
+pub use self::duration::{format_duration, FormattedDuration};
+pub use self::wrapper::{Duration, Timestamp};
+pub use self::date::{parse_rfc3339, parse_rfc3339_weak, Error as TimestampError};
+pub use self::date::{
+ format_rfc3339, format_rfc3339_micros, format_rfc3339_millis, format_rfc3339_nanos,
+ format_rfc3339_seconds,
+};
+pub use self::date::{Rfc3339Timestamp};
diff --git a/vendor/humantime/src/wrapper.rs b/vendor/humantime/src/wrapper.rs
new file mode 100644
index 000000000..99af6502a
--- /dev/null
+++ b/vendor/humantime/src/wrapper.rs
@@ -0,0 +1,107 @@
+use std::str::FromStr;
+use std::ops::Deref;
+use std::fmt;
+use std::time::{Duration as StdDuration, SystemTime};
+
+use crate::duration::{self, parse_duration, format_duration};
+use crate::date::{self, parse_rfc3339_weak, format_rfc3339};
+
+/// A wrapper for duration that has `FromStr` implementation
+///
+/// This is useful if you want to use it somewhere where `FromStr` is
+/// expected.
+///
+/// See `parse_duration` for the description of the format.
+///
+/// # Example
+///
+/// ```
+/// use std::time::Duration;
+/// let x: Duration;
+/// x = "12h 5min 2ns".parse::<humantime::Duration>().unwrap().into();
+/// assert_eq!(x, Duration::new(12*3600 + 5*60, 2))
+/// ```
+///
+#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
+pub struct Duration(StdDuration);
+
+/// A wrapper for SystemTime that has `FromStr` implementation
+///
+/// This is useful if you want to use it somewhere where `FromStr` is
+/// expected.
+///
+/// See `parse_rfc3339_weak` for the description of the format. The "weak"
+/// format is used as it's more pemissive for human input as this is the
+/// expected use of the type (e.g. command-line parsing).
+///
+/// # Example
+///
+/// ```
+/// use std::time::SystemTime;
+/// let x: SystemTime;
+/// x = "2018-02-16T00:31:37Z".parse::<humantime::Timestamp>().unwrap().into();
+/// assert_eq!(humantime::format_rfc3339(x).to_string(), "2018-02-16T00:31:37Z");
+/// ```
+///
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub struct Timestamp(SystemTime);
+
+impl AsRef<StdDuration> for Duration {
+ fn as_ref(&self) -> &StdDuration { &self.0 }
+}
+
+impl Deref for Duration {
+ type Target = StdDuration;
+ fn deref(&self) -> &StdDuration { &self.0 }
+}
+
+impl Into<StdDuration> for Duration {
+ fn into(self) -> StdDuration { self.0 }
+}
+
+impl From<StdDuration> for Duration {
+ fn from(dur: StdDuration) -> Duration { Duration(dur) }
+}
+
+impl FromStr for Duration {
+ type Err = duration::Error;
+ fn from_str(s: &str) -> Result<Duration, Self::Err> {
+ parse_duration(s).map(Duration)
+ }
+}
+
+impl fmt::Display for Duration {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ format_duration(self.0).fmt(f)
+ }
+}
+
+impl AsRef<SystemTime> for Timestamp {
+ fn as_ref(&self) -> &SystemTime { &self.0 }
+}
+
+impl Deref for Timestamp {
+ type Target = SystemTime;
+ fn deref(&self) -> &SystemTime { &self.0 }
+}
+
+impl Into<SystemTime> for Timestamp {
+ fn into(self) -> SystemTime { self.0 }
+}
+
+impl From<SystemTime> for Timestamp {
+ fn from(dur: SystemTime) -> Timestamp { Timestamp(dur) }
+}
+
+impl FromStr for Timestamp {
+ type Err = date::Error;
+ fn from_str(s: &str) -> Result<Timestamp, Self::Err> {
+ parse_rfc3339_weak(s).map(Timestamp)
+ }
+}
+
+impl fmt::Display for Timestamp {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ format_rfc3339(self.0).fmt(f)
+ }
+}
diff --git a/vendor/humantime/vagga.yaml b/vendor/humantime/vagga.yaml
new file mode 100644
index 000000000..8a6b5847e
--- /dev/null
+++ b/vendor/humantime/vagga.yaml
@@ -0,0 +1,92 @@
+commands:
+
+ cargo: !Command
+ description: Run any cargo command
+ container: ubuntu
+ run: [cargo]
+
+ make: !Command
+ description: Build the library
+ container: ubuntu
+ run: [cargo, build]
+
+ test64: !Command
+ description: Test the 64bit library
+ container: ubuntu
+ environ: { RUST_BACKTRACE: 1 }
+ run: [cargo, test]
+
+ test32: !Command
+ description: Test the 32bit library
+ container: ubuntu32
+ environ: { RUST_BACKTRACE: 1 }
+ run: [cargo, test]
+
+ test: !Command
+ description: Test the 64bit library
+ container: ubuntu
+ environ: { RUST_BACKTRACE: 1 }
+ prerequisites: [test64, test32]
+ run: [echo, okay]
+
+ bench: !Command
+ description: Run benchmarks
+ container: bench
+ environ: { RUST_BACKTRACE: 1 }
+ run: [cargo, bench]
+
+ _bulk: !Command
+ description: Run `bulk` command (for version bookkeeping)
+ container: ubuntu
+ run: [bulk]
+
+containers:
+
+ ubuntu:
+ setup:
+ - !Ubuntu xenial
+ - !UbuntuUniverse
+ - !Install [ca-certificates, build-essential, vim]
+
+ - !TarInstall
+ url: "https://static.rust-lang.org/dist/rust-1.31.0-x86_64-unknown-linux-gnu.tar.gz"
+ script: "./install.sh --prefix=/usr \
+ --components=rustc,rust-std-x86_64-unknown-linux-gnu,cargo"
+ - &bulk !Tar
+ url: "https://github.com/tailhook/bulk/releases/download/v0.4.10/bulk-v0.4.10.tar.gz"
+ sha256: 481513f8a0306a9857d045497fb5b50b50a51e9ff748909ecf7d2bda1de275ab
+ path: /
+
+ environ:
+ HOME: /work/target
+ USER: pc
+
+ ubuntu32:
+ setup:
+ - !UbuntuRelease
+ codename: xenial
+ arch: i386
+ - !UbuntuUniverse
+ - !Install [ca-certificates, build-essential, vim]
+
+ - !TarInstall
+ url: "https://static.rust-lang.org/dist/rust-1.31.0-i686-unknown-linux-gnu.tar.gz"
+ script: "./install.sh --prefix=/usr \
+ --components=rustc,rust-std-i686-unknown-linux-gnu,cargo"
+
+ environ:
+ HOME: /work/target
+ USER: pc
+
+ bench:
+ setup:
+ - !Ubuntu xenial
+ - !Install [ca-certificates, wget, build-essential]
+ - !TarInstall
+ url: https://static.rust-lang.org/dist/rust-nightly-x86_64-unknown-linux-gnu.tar.gz
+ script: |
+ ./install.sh --prefix=/usr \
+ --components=rustc,rust-std-x86_64-unknown-linux-gnu,cargo
+ environ:
+ HOME: /work/target
+ USER: pc