Introduction
time
is a date and time library for Rust. It is:
- Easy and safe.
time
has a straightforward API without footguns. - space optimal and efficient.
time
provides support for dates in the ±9999 year range, with nanosecond precision; ranges up to ±999,999 are supported with thelarge-dates
feature. serde
ready. Supports ISO8601, RFC2822 and RFC3339 with theserde-well-known
feature. Not in the list? Make your format.const
ready. A majority of the API isconst
, making it ready for resource-constrained applications, with optional macros for easy date creation.no-std
support withalloc
andstd
features.- numeric traits. Use durations easily:
2.seconds()
. - Supports Windows, Linux, macOS, WebAssembly targets among others.
And more...
Getting started
This short tutorial describes basic usage of time
, to get operational quickly.
- Install
time
. Add it to yourCargo.toml
. We'll enablemacros
:
[dependencies]
time = { version = "0.3", features = ["macros"] }
- Get the current time With the crate feature
std
a UTC offset (OffsetDateTime
) is available, but with the crate featurelocal-offset
, we can also get the local time.
use time::OffsetDateTime;
let now = OffsetDateTime::now_utc();
// let local = OffsetDateTime::now_local();
println!("{now}");
- Create dates and times. We can create dates
(
Date
), dates with times (PrimitiveDateTime
) and date times with an UTC offset (OffsetDateTime
). A simpleTime
is also available.
use time::{Date, PrimitiveDateTime, OffsetDateTime, UtcOffset};
use time::Weekday::Wednesday;
let date = Date::from_iso_week_date(2022, 1, Wednesday).unwrap();
let datetime = date.with_hms(13, 0, 55).unwrap();
let datetime_off = datetime.assume_offset(UtcOffset::from_hms(1, 2, 3).unwrap());
println!("{date}, {datetime}, {datetime_off}");
// 2022-01-01, 2022-01-01 13:00:55.0, 2022-01-01 13:00:55.0 +01:02:03
With the macros
feature:
use time::macros::{date, datetime};
let date = date!(2022-01-01);
let datetime = datetime!(2022-01-01 13:00:55);
let datetime_off = datetime!(2022-01-01 13:00:55 +1:02:03);
println!("{date}, {datetime}, {datetime_off}");
// 2022-01-01, 2022-01-01 13:00:55.0, 2022-01-01 13:00:55.0 +01:02:03
- Manipulate dates and use
Duration
s:
use time::Duration;
use time::macros::{datetime};
let a = datetime!(2022-01-01 10:00:55);
let b = datetime!(2022-01-01 13:00:00);
let duration: Duration = b - a;
println!("{}", b - a);
// 2h59m5s
time
vs chrono
time 0.1 was originally a thin wrapper around libc time functions. Because it was relatively
barebones, chrono
was developed as a richer API on top
of time 0.1.
Around 2019, the time crate, which was unmaintained since August 2016, was picked up for maintenance
again. time
has since been rewritten as of time 0.2, and is incompatible with the 0.1 version.
Today:
time
has been rewritten from 0.1,and is actively developed.chrono
depends on time 0.1, an old version unrelated with currenttime
, and is actively developed as well.
Since they are incompatible with each other, please choose the library that fits your needs.
Create dates and times
time
provides a const
-ready API. All functions here are const
: values can be computed
at compile-time if you pass constants, with no difference if used at runtime.
For convenience, macros are provided with feature macros
. They let us restrict
parameters further, avoiding the need to unwrap()
at the cost of compilation time.
Creating Date
s
From a constant value, with feature macros
:
use time::macros::date;
let _ = date!(2022-01-02);
From a calendar date:
use time::{Date, Month};
let _ = Date::from_calendar_date(2022, Month::January, 2).unwrap();
Creating PrimitiveDateTime
s
A PrimitiveDateTime
is both a date and a time. We can create them directly:
use time::macros::datetime;
let _ = datetime!(2022-01-02 11:12:13.123_456_789);
or use an existing Date
:
use time::macros::{date, time};
use time::Time;
let date = date!(2022-01-02);
// A date with 00:00:00 time
let _ = date.midnight();
// You can also provide a desired time...
let _ = date.with_hms(11, 12, 13).unwrap();
// or pass an existing `Time`
let _ = date.with_time(Time::from_hms_nano(11, 12, 13, 123_456_789).unwrap());
// with macros:
let _ = date.with_time(time!(11:12:13.123_456_789));
Creating OffsetDateTime
s
An OffsetDateTime
is a date, time and UTC offset
. Use it if you deal with timezones:
use time::macros::datetime;
// When we pass an offset at the end to `datetime!`, it will return an
// `OffsetDateTime` instead of an `PrimitiveDateTime`
let _ = datetime!(2022-01-02 11:12:13 UTC);
// With a positive offset:
let _ = datetime!(2022-01-02 11:12:13 +1);
// and a negative offset:
let _ = datetime!(2022-01-02 11:12:13.123_456_789 -2:34:56);
or, using an existing PrimitiveDateTime
, with
UtcOffset
:
use time::macros::{datetime, offset};
use time::UtcOffset;
let dt = datetime!(2022-01-02 11:12:13);
// With UTC:
let _ = dt.assume_utc();
// or with another offset:
let _ = dt.assume_offset(UtcOffset::from_hms(1, 2, 3));
// with macros:
let _ = dt.assume_offset(offset!(-11));
Parse dates
time
has can parse datetimes from strings, in any given format.
Parse a date from a string
-
Enable feature
parsing
. -
Using the common ISO 8601 format, via the
Iso8601
format description:
use time::format_description::well_known::Iso8601;
use time::PrimitiveDateTime;
let date = PrimitiveDateTime::parse("2022-01-02T11:12:13", &Iso8601::DEFAULT)
.unwrap();
Parsing custom formats
time
supports a few common formats, that we call well-known formats. We support
arbitrary formats as well.
-
Enable feature
macros
andparsing
.macros
are used to callformat_description!
, but you can also call the equivalent function. -
Create a format and parse:
use time::macros::format_description;
use time::Time;
let my_format = format_description!("h=[hour],m=[minute],s=[second]");
let time = Time::parse("h=11,m=12,s=13", &my_format).unwrap();
Reference for format descriptions can be found here.
Parsing into structs with serde
For convenience, you can use Serde with time
.
-
Enable [feature
serde-well-known
][serde-well-known]. -
Create a struct and parse from a format, eg. JSON using
serde-json
:
use time::macros::format_description;
use time::{OffsetDateTime, Time};
use serde::{Deserialize};
#[derive(Deserialize)]
struct Notification {
message: String,
#[serde(with = "time::serde::iso8601")]
timestamp: OffsetDateTime,
}
fn main() {
let input = r#"{
"message": "foo",
"timestamp": "2022-01-02T11:12:13Z"
}"#;
let notification: Notification = serde_json::from_str(input).unwrap();
println!("{:?}", notification.timestamp);
}
Construction
Conversion between types
Arithmetic
Format description
A format description is the manner in which the time crate knows how a value should be formatted and parsed.
While it is possible to construct a format description manually, this is generally not recommended,
as it is more tedious and less readable than the alternative. Unless you are doing this, you will
likely never need to know anything about FormatItem
other than that it is produced by the
format_description!
macro or any of the various parsing methods.
If the format description is statically known, you should use the format_description!
macro. This
is identical to the format_description::parse
method, but runs at compile-time, throwing an error
if the format description is invalid. If you do not know the desired format statically (such as if
you are using one provided by the user), you should use the format_description::parse_owned
method
(or similar method in the format_description
module), which is fallible.
Format descriptions have components and literals. Literals are formatted and parsed as-is.
Components are the mechanism by which values (such as a Time
or Date
) are dynamically formatted
and parsed. They have significant flexibility, allowing for differences in padding, variable widths
for subsecond values, numerical or textual representations, and more.
Either a literal or a component may be present at the start of the format description. It is valid to have both consecutive literals and consecutive components. Components must be fully contained between brackets with optional whitespace. Escaping behavior varies by version, and is described below.
Modifiers
Many of the components have optional modifiers to change how it is formatted or parsed.
The padding
modifier, such as used on the hour
and month
components can be padded
with space
, zero
or none
. In the case of none
leading zeros are still accepted during
parsing.
Traits
A format description is not a single type; it is instead represented by two internal traits (one for formatting and one for parsing) that are implemented by a number of types. Currently, all types that implement one trait also implement the other, but this is not guaranteed.
The following types currently implement both the Formattable
and Parsable
traits:
FormatItem<'_>
[FormatItem<'_>]
T where <T as Deref>::Target: Formattable
(orParsable
)- All well known formats
Versioning
There are multiple versions of the format description syntax in time
. Similar to Rust editions,
all versions are and will remain supported indefinitely. Some features may only be available in
newer versions for technical reasons.
In most cases, you do not need to worry about the version of the format description. However, there are some differences.
Differences
Literal | Version 1 | Version 2 |
---|---|---|
[ | [[ | \[ |
] | ] | \] |
\ | \ | \\ |
[first]
and [optional]
are supported in both version 1 and version 2, but individual methods may
prevent their use. This is because some methods return a format description that is entirely
borrowed. However, when parsing [first]
and [optional]
, the generated sequence is necessarily
owned. For this reason, you will need to use the format_description::parse_owned
method or the
format_description!
macro to use these components.
Version used
format_description::parse
uses version 1 unconditionally. This is the only method that has a
non-configurable version. format_description::parse_borrowed
and
format_description::parse_owned
require the user to specify the version. If the version is not
valid, compilation will fail. format_description!
defaults to version 1, but can be configured
to use a different version.
Configuring format_description!
For backwards-compatibility reasons, the format_description!
macro defaults to version 1. If you
want to use a different version, you can do so by setting the version
parameter to 2
. Note that
this is only necessary if you are relying on a difference in behavior between the versions.
use time::macros::format_description;
let _ = format_description!("[hour]:[minute]:[second]"); // Version 1 is implied.
let _ = format_description!(version = 1, "[hour]:[minute]:[second]");
let _ = format_description!(version = 2, "[hour]:[minute]:[second]");
Attempting to provide an invalid version will result in a compile-time error.
use time::macros::format_description;
// 0 is not a valid version, so compilation will fail.
let _ = format_description!(version = 0, "[hour]:[minute]:[second]");
Version 1
[[
produces a literal [
. No other character must be escaped.
Version 2
\
is used to begin an escape sequence. Currently, the only valid escape sequences are \[
, \]
,
and \\
. Any other character following \
is invalid.
Components
Follows is the syntax for all components in alphabetical order. Any of the following may be used where component is present in the above diagram. "Whitespace" refers to any non-empty sequence of ASCII whitespace characters.
-
Day of month:
[day]
The padded value has a width of 2. You can choose between padding with zeroes, spaces, or having no padding at all. The default is to pad the value with zeroes.
-
First item:
[first]
A series of
FormatItem
s (orOwnedFormatItem
s) where, when parsing, the first successful parse is used. When formatting, the first item is used.format_description
refers to a complete format description that is nested; whitespace (including leading and trailing) is significant. -
End of input:
[end]
This component indicates the end of the input. When formatting, it is a no-op. When parsing, it will only succeed if there is no further input. It does not consume any input.
There is no customization available for this component.
-
Clock hour:
[hour]
The padded value has a width of 2. You can choose between padding with zeroes, spacing, or having no padding at all. The default is to pad the value with zeroes.
Users have the option to choose between two representations. One is the 12-hour clock, frequently used in the Anglosphere, while the alternative (the 24-hour clock) is frequently used elsewhere. The 12-hour clock is typically used in conjunction with AM/PM.
-
Ignore:
[ignore count:X]
When parsing, this ignores the indicated number of bytes. This component is a no-op when formatting. The
count
modifier is mandatory. Its value must be a positive integer. -
Minute within the clock hour:
[minute]
The padded value has a width of 2. You can choose between padding with zeroes, spaces, or having no padding at all. The default is to pad the value with zeroes.
-
Month:
[month]
The padded value has a width of 2. You can choose between padding with zeroes, spaces, or having no padding at all. The default is to pad the value with zeroes.
Users have the option to choose between three representations. The default is numerical. Alternatives are long and short, both of which are textual formats and have no padding. The long format is the full English name of the month, while the short format is the first three letters of it.
When parsing, there is the option to consume text-based formats case-insensitively.
-
Whole hours offset from UTC:
[offset_hour]
The padded value has a width of 2. You can choose between padding with zeroes, spaces, or having no padding at all. The default is to pad the value with zeroes.
Users have the option to choose whether the sign is automatic (the default) or mandatory. If the sign is automatic, it will only be present when the value is negative. If mandatory, it will always be present.
-
Minutes within the hour offset from UTC:
[offset_minute]
The padded value has a width of 2. You can choose between padding with zeroes, spaces, or having no padding at all. The default is to pad the value with zeroes.
This value is always positive. As such, it has no sign.
-
Seconds within the minute offset from UTC:
[offset_second]
The padded value has a width of 2. You can choose between padding with zeroes, spaces, or having no padding at all. The default is to pad the value with zeroes.
This value is always positive. As such, it has no sign.
-
Optional items:
[optional]
An item that may or may not be present while parsing. While formatting, the value is always present.
format_description
refers to a complete format description that is nested; whitespace (including leading and trailing) is significant. -
Day of year:
[ordinal]
The padded value has a width of 3. You can choose between padding with zeroes, spaces, or having no padding at all. The default is to pad the value with zeroes.
-
AM/PM:
[period]
Users have the option to choose whether the value is uppercase or lowercase. This component is typically used in conjunction with the hour of the day with
repr:12
.When parsing, there is the option to consume text-based formats case-insensitively.
-
Second within the clock minute:
[second]
The padded value has a width of 2. You can choose between padding with zeroes, spaces, or having no padding at all. The default is to pad the value with zeroes.
-
Subsecond within the clock second:
[subsecond]
Users have the choice of how many digits should be displayed or parsed. By default, this is one or more, where the minimum number of digits will be used when formatting and any nonzero number of digits are accepted by the parser (though digits after the ninth will be discarded). There is the option to require a fixed number of digits between one and nine. When formatting, the value is not rounded if more digits would otherwise be present.
-
Unix timestamp:
[unix_timestamp]
Users can choose between four levels of precision: second (the default), millisecond, microsecond, and nanosecond. The sign can also be made mandatory rather than optional.
-
Week of the year:
[week_number]
The padded value has a width of 2. You can choose between padding with zeroes, spaces, or having no padding at all. The default is to pad the value with zeroes.
Users can choose between three representations: iso (the default), sunday, and monday. ISO week numbers are in between 1 and 53, while others are between 0 and 53. ISO week one is the Monday-to-Sunday week that contains January 4. Week one of other representations begins on the first instance of that day in the calendar year (e.g. Sunday-based week numbering has week one start on the first Sunday of the year).
-
Day of the week:
[weekday]
Users can choose between a number of representations for the day of the week. There are long (the default) and short, both of which are textual representations; the long representation is the weekday's full name in English, while the short is the first three letters. There are also sunday and monday representations, which are numerical. These formats are either zero to six or one to seven (depending on whether
one_indexed
is false or true, respectively), with the named day being at the start of that range. The default forone_indexed
is true.When parsing, there is the option to consume text-based formats case-insensitively.
-
Year:
[year]
The padded value has a width of 4. You can choose between padding with zeroes, spaces, or having no padding at all. The default is to pad the value with zeroes.
Users can choose between two representations: the full year (the default) and the last two digits of the year. This should be relatively straightforward. Note that when parsing, if only the last two digits of the year are present, the value returned may not be what was expected — if the return is successful at all (it's not guaranteed).
There are two bases for the year: calendar and iso_week. The former is what you want if using the month, day, ordinal, or similar. You likely only want to use
iso_week
if you are using the week number withrepr:iso
. Don't be like Twitter; know which should be used when.Users have the option to choose whether the sign is automatic (the default) or mandatory. If the sign is automatic, it will only be present when the value is negative or if the
large-dates
feature is enabled and the value contains more than four digits. If mandatory, it will always be present.When the
large-dates
feature is enabled, ambiguities may exist when parsing. For example, if a year is immediately followed by the week number, the parser will eagerly consume six digits even if the year should only be four and the week number the remaining two.
Well-known format descriptions
A number of well-known format descriptions are provided by time
. These are intended to be used
when you need to be fully compliant with a specification. Many specifications have various
edge-cases that are difficult to model with a custom format description. Using a well-known format
description allows you to handle all relevant edge cases.
Guarantees
The guarantees provided by well-known formats are deliberately minimal. The only guarantees, unless otherwise documented, are:
- When formatting, the output will be valid according to the specification.
- Parsing will succeed if and only if the input is valid according to the specification.
If you are expecting a specific output and not just any valid output, you should use a custom format.
Mutual agreement
Some well-known format descriptions require mutual agreement for certain behavior. For example, ISO
8601 requires that the year be four digits unless additional digits are mutually agreed upon. time
inherently has no way to enforce mutual agreement. As such, it is assumed to be present. If you are
using the default configuration, you must ensure that the other party is able to accept the
formatted value.
ISO 8601
The format described in ISO 8601. It can be found in the api documentation here.
The examples and format description shown below are for the default configuration. Various options are available for this format which you can change. Additional information can be found in the configuration api documentation.
Note: When using the time::serde::iso8601
module in conjunction with serde
, the default
configuration is different. In that case, the years are always six digits and preceded by a sign.
Examples:
1997-11-12T09:55:06.000000000-06:00
2022-09-08T13:55:24.000000000+02:00
2010-03-14T18:32:03.000000000Z
Approximate format description:
[year]-[month]-[day]T[hour]:[minute]:[second].[subsecond digits:9][offset_hour]:[offset_minute]
Approximate format description when the time zone is UTC:
[year]-[month]-[day]T[hour]:[minute]:[second].[subsecond digits:9]Z
RFC 2822
The format described in RFC 2822. It can be found in the api documentation here.
Examples:
Wed, 12 Nov 1997 09:55:06 -0600
Thu, 08 Sep 2022 13:55:24 +0200
Sun, 14 Mar 2010 18:32:03 +0000
Approximate format description:
[weekday repr:short], [day] [month repr:short] [year] [hour]:[minute]:[second] [offset_hour][offset_minute]
RFC 3339
The format described in RFC 3339. It can be found in the api documentation here.
Examples:
1997-11-12T09:55:06-06:00
2022-09-08T13:55:24+02:00
2010-03-14T18:32:03Z
Approximate format description:
[year]-[month]-[day]T[hour]:[minute]:[second][offset_hour]:[offset_minute]
Approximate format description when the time zone is UTC:
[year]-[month]-[day]T[hour]:[minute]:[second]Z
Formatting
Parsing
Macros
Extension traits
The time crate contains two extension traits: time::ext::NumericalDuration
and
time::ext::NumericalStdDuration
. These traits exist to make writing code involving durations (both
from the time crate and the standard library) cleaner to read. Rather than writing
Duration::seconds(5)
, it is possible to write 5.seconds()
. It is possible to use floating point
literals such that 1.5.weeks()
is equivalent to 3.days() + 12.hours()
.
NumericalDuration
provides the following methods that return a time::Duration
:
.nanoseconds()
.microseconds()
.milliseconds()
.seconds()
.minutes()
.hours()
.days()
.weeks()
NumericalStdDuration
provides the following methods that return a core::time::Duration
:
.std_nanoseconds()
.std_microseconds()
.std_milliseconds()
.std_seconds()
.std_minutes()
.std_hours()
.std_days()
.std_weeks()
The NumericalDuration
trait is implemented for i64
and f64
, such that both integer and float
literals are able to use the methods. The NumericalStdDuration
trait is implemented for u64
and
f64
for the same reasons, though the latter will perform a runtime check ensuring the value is
non-negative.
While it is possible to use these extension methods on non-literals, such usage is discouraged for ease of reading.