Struct Pieces
pub struct Pieces<'n> { /* private fields */ }
dep_jiff
and alloc
only.Expand description
A low level representation of a parsed Temporal ISO 8601 datetime string.
Most users should not need to use or care about this type. Its purpose is to represent the individual components of a datetime string for more flexible parsing when use cases call for it.
One can parse into Pieces
via Pieces::parse
. Its date, time
(optional), offset (optional) and time zone annotation (optional) can be
queried independently. Each component corresponds to the following in a
datetime string:
{date}T{time}{offset}[{time-zone-annotation}]
For example:
2025-01-03T19:54-05[America/New_York]
A date is the only required component.
A Pieces
can also be constructed from structured values via its From
trait implementations. The From
trait has the following implementations
available:
From<Date>
creates aPieces
with just a civilDate
. All other components are left empty.From<DateTime>
creates aPieces
with a civilDate
andTime
. The offset and time zone annotation are left empty.From<Timestamp>
creates aPieces
from aTimestamp
using a Zulu offset. This signifies that the precise instant is known, but the local time’s offset from UTC is unknown. TheDate
andTime
are determined viaOffset::UTC.to_datetime(timestamp)
. The time zone annotation is left empty.From<(Timestamp, Offset)>
creates aPieces
from aTimestamp
and anOffset
. TheDate
andTime
are determined viaoffset.to_datetime(timestamp)
. The time zone annotation is left empty.From<&Zoned>
creates aPieces
from aZoned
. This populates all fields of aPieces
.
A Pieces
can be converted to a Temporal ISO 8601 string via its Display
trait implementation.
§Example: distinguishing between Z
, +00:00
and -00:00
With Pieces
, it’s possible to parse a datetime string and inspect the
“type” of its offset when it is zero. This makes use of the
PiecesOffset
and PiecesNumericOffset
auxiliary types.
use jiff::{
fmt::temporal::{Pieces, PiecesNumericOffset, PiecesOffset},
tz::Offset,
};
let pieces = Pieces::parse("1970-01-01T00:00:00Z")?;
let off = pieces.offset().unwrap();
// Parsed as Zulu.
assert_eq!(off, PiecesOffset::Zulu);
// Gets converted from Zulu to UTC, i.e., just zero.
assert_eq!(off.to_numeric_offset(), Offset::UTC);
let pieces = Pieces::parse("1970-01-01T00:00:00-00:00")?;
let off = pieces.offset().unwrap();
// Parsed as a negative zero.
assert_eq!(off, PiecesOffset::from(
PiecesNumericOffset::from(Offset::UTC).with_negative_zero(),
));
// Gets converted from -00:00 to UTC, i.e., just zero.
assert_eq!(off.to_numeric_offset(), Offset::UTC);
let pieces = Pieces::parse("1970-01-01T00:00:00+00:00")?;
let off = pieces.offset().unwrap();
// Parsed as a positive zero.
assert_eq!(off, PiecesOffset::from(
PiecesNumericOffset::from(Offset::UTC),
));
// Gets converted from -00:00 to UTC, i.e., just zero.
assert_eq!(off.to_numeric_offset(), Offset::UTC);
It’s rare to need to care about these differences, but the above example
demonstrates that Pieces
doesn’t try to do any automatic translation for
you.
§Example: it is very easy to misuse Pieces
This example shows how easily you can shoot yourself in the foot with
Pieces
:
use jiff::{fmt::temporal::{Pieces, TimeZoneAnnotation}, tz};
let mut pieces = Pieces::parse("2025-01-03T07:55+02[Africa/Cairo]")?;
pieces = pieces.with_offset(tz::offset(-10));
// This is nonsense because the offset isn't compatible with the time zone!
// Moreover, the actual instant that this timestamp represents has changed.
assert_eq!(pieces.to_string(), "2025-01-03T07:55:00-10:00[Africa/Cairo]");
In the above example, we take a parsed Pieces
, change its offset and
then format it back into a string. There are no speed bumps or errors.
A Pieces
will just blindly follow your instruction, even if it produces
a nonsense result. Nonsense results are still parsable back into Pieces
:
use jiff::{civil, fmt::temporal::Pieces, tz::{TimeZone, offset}};
let pieces = Pieces::parse("2025-01-03T07:55:00-10:00[Africa/Cairo]")?;
assert_eq!(pieces.date(), civil::date(2025, 1, 3));
assert_eq!(pieces.time(), Some(civil::time(7, 55, 0, 0)));
assert_eq!(pieces.to_numeric_offset(), Some(offset(-10)));
assert_eq!(pieces.to_time_zone()?, Some(TimeZone::get("Africa/Cairo")?));
This exemplifies that Pieces
is a mostly “dumb” type that passes
through the data it contains, even if it doesn’t make sense.
§Case study: how to parse 2025-01-03T17:28-05
into Zoned
One thing in particular that Pieces
enables callers to do is side-step
some of the stricter requirements placed on the higher level parsing
functions (such as Zoned
’s FromStr
trait implementation). For example,
parsing a datetime string into a Zoned
requires that the string contain
a time zone annotation. Namely, parsing 2025-01-03T17:28-05
into a
Zoned
will fail:
use jiff::Zoned;
assert_eq!(
"2025-01-03T17:28-05".parse::<Zoned>().unwrap_err().to_string(),
"failed to find time zone in square brackets in \
\"2025-01-03T17:28-05\", which is required for \
parsing a zoned instant",
);
The above fails because an RFC 3339 timestamp only contains an offset,
not a time zone, and thus the resulting Zoned
could never do time zone
aware arithmetic.
However, in some cases, you might want to bypass these protections and
creat a Zoned
value with a fixed offset time zone anyway. For example,
perhaps your use cases don’t need time zone aware arithmetic, but want to
preserve the offset anyway. This can be accomplished with Pieces
:
use jiff::{fmt::temporal::Pieces, tz::TimeZone};
let pieces = Pieces::parse("2025-01-03T17:28-05")?;
let time = pieces.time().unwrap_or_else(jiff::civil::Time::midnight);
let dt = pieces.date().to_datetime(time);
let Some(offset) = pieces.to_numeric_offset() else {
let msg = format!(
"datetime string has no offset, \
and thus cannot be parsed into an instant",
);
return Err(msg.into());
};
let zdt = TimeZone::fixed(offset).to_zoned(dt)?;
assert_eq!(zdt.to_string(), "2025-01-03T17:28:00-05:00[-05:00]");
One problem with the above code snippet is that it completely ignores if
a time zone annotation is present. If it is, it probably makes sense to use
it, but “fall back” to a fixed offset time zone if it isn’t (which the
higher level Zoned
parsing function won’t do for you):
use jiff::{fmt::temporal::Pieces, tz::TimeZone};
let timestamp = "2025-01-02T15:13-05";
let pieces = Pieces::parse(timestamp)?;
let time = pieces.time().unwrap_or_else(jiff::civil::Time::midnight);
let dt = pieces.date().to_datetime(time);
let tz = match pieces.to_time_zone()? {
Some(tz) => tz,
None => {
let Some(offset) = pieces.to_numeric_offset() else {
let msg = format!(
"timestamp `{timestamp}` has no time zone \
or offset, and thus cannot be parsed into \
an instant",
);
return Err(msg.into());
};
TimeZone::fixed(offset)
}
};
// We don't bother with offset conflict resolution. And note that
// this uses automatic "compatible" disambiguation in the case of
// discontinuities. Of course, this is all moot if `TimeZone` is
// fixed. The above code handles the case where it isn't!
let zdt = tz.to_zoned(dt)?;
assert_eq!(zdt.to_string(), "2025-01-02T15:13:00-05:00[-05:00]");
This is mostly the same as above, but if an annotation is present, we use
a TimeZone
derived from that over the offset present.
However, this still doesn’t quite capture what happens when parsing into a
Zoned
value. In particular, parsing into a Zoned
is also doing offset
conflict resolution for you. An offset conflict occurs when there is a
mismatch between the offset in an RFC 3339 timestamp and the time zone in
an RFC 9557 time zone annotation.
For example, 2024-06-14T17:30-05[America/New_York]
has a mismatch
since the date is in daylight saving time, but the offset, -05
, is the
offset for standard time in America/New_York
. If this datetime were
fed to the above code, then the -05
offset would be completely ignored
and America/New_York
would resolve the datetime based on its rules. In
this case, you’d get 2024-06-14T17:30-04
, which is a different instant
than the original datetime!
You can either implement your own conflict resolution or use
tz::OffsetConflict
to do it for you.
use jiff::{fmt::temporal::Pieces, tz::{OffsetConflict, TimeZone}};
let timestamp = "2024-06-14T17:30-05[America/New_York]";
// The default for conflict resolution when parsing into a `Zoned` is
// actually `Reject`, but we use `AlwaysOffset` here to show a different
// strategy. You'll want to pick the conflict resolution that suits your
// needs. The `Reject` strategy is what you should pick if you aren't
// sure.
let conflict_resolution = OffsetConflict::AlwaysOffset;
let pieces = Pieces::parse(timestamp)?;
let time = pieces.time().unwrap_or_else(jiff::civil::Time::midnight);
let dt = pieces.date().to_datetime(time);
let ambiguous_zdt = match pieces.to_time_zone()? {
Some(tz) => {
match pieces.to_numeric_offset() {
None => tz.into_ambiguous_zoned(dt),
Some(offset) => {
conflict_resolution.resolve(dt, offset, tz)?
}
}
}
None => {
let Some(offset) = pieces.to_numeric_offset() else {
let msg = format!(
"timestamp `{timestamp}` has no time zone \
or offset, and thus cannot be parsed into \
an instant",
);
return Err(msg.into());
};
// Won't even be ambiguous, but gets us the same
// type as the branch above.
TimeZone::fixed(offset).into_ambiguous_zoned(dt)
}
};
// We do compatible disambiguation here like we do in the previous
// examples, but you could choose any strategy. As with offset conflict
// resolution, if you aren't sure what to pick, a safe choice here would
// be `ambiguous_zdt.unambiguous()`, which will return an error if the
// datetime is ambiguous in any way. Then, if you ever hit an error, you
// can examine the case to see if it should be handled in a different way.
let zdt = ambiguous_zdt.compatible()?;
// Notice that we now have a different civil time and offset, but the
// instant it corresponds to is the same as the one we started with.
assert_eq!(zdt.to_string(), "2024-06-14T18:30:00-04:00[America/New_York]");
The above has effectively completely rebuilt the higher level Zoned
parsing routine, but with a fallback to a fixed time zone when a time zone
annotation is not present.
§Case study: inferring the time zone of RFC 3339 timestamps
As one real world use case details, it might be desirable to try and infer the time zone of RFC 3339 timestamps with varying offsets. This might be applicable when:
- You have out-of-band information, possibly contextual, that indicates the timestamps have to come from a fixed set of time zones.
- The time zones have different standard offsets.
- You have a specific desire or need to use a
Zoned
value for its ergonomics and time zone aware handling. After all, in this case, you believe the timestamps to actually be generated from a specific time zone, but the interchange format doesn’t support carrying that information. Or the source data simply omits it.
In other words, you might be trying to make the best of a bad situation.
A Pieces
can help you accomplish this because it gives you access to each
component of a parsed datetime, and thus lets you implement arbitrary logic
for how to translate that into a Zoned
. In this case, there is
contextual information that Jiff can’t possibly know about.
The general approach we take here is to make use of
tz::OffsetConflict
to query whether a
timestamp has a fixed offset compatible with a particular time zone. And if
so, we can probably assume it comes from that time zone. One hitch is
that it’s possible for the timestamp to be valid for multiple time zones,
so we check that as well.
In the use case linked above, we have fixed offset timestamps from
America/Chicago
and America/New_York
. So let’s try implementing the
above strategy. Note that we assume our inputs are RFC 3339 fixed offset
timestamps and error otherwise. This is just to keep things simple. To
handle data that is more varied, see the previous case study where we
respect a time zone annotation if it’s present, and fall back to a fixed
offset time zone if it isn’t.
use jiff::{fmt::temporal::Pieces, tz::{OffsetConflict, TimeZone}, Zoned};
// The time zones we're allowed to choose from.
let tzs = &[
TimeZone::get("America/New_York")?,
TimeZone::get("America/Chicago")?,
];
// Here's our data that lacks time zones. The task is to assign a time zone
// from `tzs` to each below and convert it to a `Zoned`. If we fail on any
// one, then we substitute `None`.
let data = &[
"2024-01-13T10:33-05",
"2024-01-25T12:15-06",
"2024-03-10T02:30-05",
"2024-06-08T14:01-05",
"2024-06-12T11:46-04",
"2024-11-03T01:30-05",
];
// Our answers.
let mut zdts: Vec<Option<Zoned>> = vec![];
for string in data {
// Parse and gather up the data that we can from the input.
// In this case, that's a civil datetime and an offset from UTC.
let pieces = Pieces::parse(string)?;
let time = pieces.time().unwrap_or_else(jiff::civil::Time::midnight);
let dt = pieces.date().to_datetime(time);
let Some(offset) = pieces.to_numeric_offset() else {
// A robust implementation should use a TZ annotation if present.
return Err("missing offset".into());
};
// Now collect all time zones that are valid for this timestamp.
let mut candidates = vec![];
for tz in tzs {
let result = OffsetConflict::Reject.resolve(dt, offset, tz.clone());
// The parsed offset isn't valid for this time zone, so reject it.
let Ok(ambiguous_zdt) = result else { continue };
// This can never fail because we used the "reject" conflict
// resolution strategy. It will never return an ambiguous
// `Zoned` since we always have a valid offset that does
// disambiguation for us.
let zdt = ambiguous_zdt.unambiguous().unwrap();
candidates.push(zdt);
}
if candidates.len() == 1 {
zdts.push(Some(candidates.pop().unwrap()));
} else {
zdts.push(None);
}
}
assert_eq!(zdts, vec![
Some("2024-01-13T10:33-05[America/New_York]".parse()?),
Some("2024-01-25T12:15-06[America/Chicago]".parse()?),
// Failed because the clock time falls in a gap in the
// transition to daylight saving time, and it could be
// valid for either America/New_York or America/Chicago.
None,
Some("2024-06-08T14:01-05[America/Chicago]".parse()?),
Some("2024-06-12T11:46-04[America/New_York]".parse()?),
// Failed because the clock time falls in a fold in the
// transition out of daylight saving time, and it could be
// valid for either America/New_York or America/Chicago.
None,
]);
The one hitch here is that if the time zones are close to each
geographically and both have daylight saving time, then there are some
RFC 3339 timestamps that are truly ambiguous. For example,
2024-11-03T01:30-05
is perfectly valid for both America/New_York
and
America/Chicago
. In this case, there is no way to tell which time zone
the timestamp belongs to. It might be reasonable to return an error in
this case or omit the timestamp. It depends on what you need to do.
With more effort, it would also be possible to optimize the above routine
by utilizing TimeZone::preceding
and TimeZone::following
to get
the exact boundaries of each time zone transition. Then you could use an
offset lookup table for each range to determine the appropriate time zone.
Implementations§
§impl<'n> Pieces<'n>
impl<'n> Pieces<'n>
pub fn parse<I>(input: &'n I) -> Result<Pieces<'n>, Error> ⓘ
pub fn parse<I>(input: &'n I) -> Result<Pieces<'n>, Error> ⓘ
Parses a Temporal ISO 8601 datetime string into a Pieces
.
This is a convenience routine for
DateTimeParser::parses_pieces
.
Note that the Pieces
returned is parameterized by the lifetime of
input
. This is because it might borrow a sub-slice of input
for
a time zone annotation name. For example,
Canada/Yukon
in 2025-01-03T16:42-07[Canada/Yukon]
.
§Example
use jiff::{civil, fmt::temporal::Pieces, tz::TimeZone};
let pieces = Pieces::parse("2025-01-03T16:42[Canada/Yukon]")?;
assert_eq!(pieces.date(), civil::date(2025, 1, 3));
assert_eq!(pieces.time(), Some(civil::time(16, 42, 0, 0)));
assert_eq!(pieces.to_numeric_offset(), None);
assert_eq!(pieces.to_time_zone()?, Some(TimeZone::get("Canada/Yukon")?));
pub fn date(&self) -> Date
pub fn date(&self) -> Date
Returns the civil date in this Pieces
.
Note that every Pieces
value is guaranteed to have a Date
.
§Example
use jiff::{civil, fmt::temporal::Pieces};
let pieces = Pieces::parse("2025-01-03")?;
assert_eq!(pieces.date(), civil::date(2025, 1, 3));
pub fn time(&self) -> Option<Time> ⓘ
pub fn time(&self) -> Option<Time> ⓘ
Returns the civil time in this Pieces
.
The time component is optional. In
DateTimeParser
, parsing
into types that require a time (like DateTime
) when a time is
missing automatically set the time to midnight. (Or, more precisely,
the first instant of the day.)
§Example
use jiff::{civil, fmt::temporal::Pieces, Zoned};
let pieces = Pieces::parse("2025-01-03T14:49:01")?;
assert_eq!(pieces.date(), civil::date(2025, 1, 3));
assert_eq!(pieces.time(), Some(civil::time(14, 49, 1, 0)));
// tricksy tricksy, the first instant of 2015-10-18 in Sao Paulo is
// not midnight!
let pieces = Pieces::parse("2015-10-18[America/Sao_Paulo]")?;
// Parsing into pieces just gives us the component parts, so no time:
assert_eq!(pieces.time(), None);
// But if this uses higher level routines to parse into a `Zoned`,
// then we can see that the missing time implies the first instant
// of the day:
let zdt: Zoned = "2015-10-18[America/Sao_Paulo]".parse()?;
assert_eq!(zdt.time(), jiff::civil::time(1, 0, 0, 0));
pub fn offset(&self) -> Option<PiecesOffset> ⓘ
pub fn offset(&self) -> Option<PiecesOffset> ⓘ
Returns the offset in this Pieces
.
The offset returned can be infallibly converted to a numeric offset,
i.e., Offset
. But it also includes extra data to indicate whether
a Z
or a -00:00
was parsed. (Neither of which are representable by
an Offset
, which doesn’t distinguish between Zulu and UTC and doesn’t
represent negative and positive zero differently.)
§Example
This example shows how different flavors of Offset::UTC
can be parsed
and inspected.
use jiff::{
fmt::temporal::{Pieces, PiecesNumericOffset, PiecesOffset},
tz::Offset,
};
let pieces = Pieces::parse("1970-01-01T00:00:00Z")?;
let off = pieces.offset().unwrap();
// Parsed as Zulu.
assert_eq!(off, PiecesOffset::Zulu);
// Gets converted from Zulu to UTC, i.e., just zero.
assert_eq!(off.to_numeric_offset(), Offset::UTC);
let pieces = Pieces::parse("1970-01-01T00:00:00-00:00")?;
let off = pieces.offset().unwrap();
// Parsed as a negative zero.
assert_eq!(off, PiecesOffset::from(
PiecesNumericOffset::from(Offset::UTC).with_negative_zero(),
));
// Gets converted from -00:00 to UTC, i.e., just zero.
assert_eq!(off.to_numeric_offset(), Offset::UTC);
let pieces = Pieces::parse("1970-01-01T00:00:00+00:00")?;
let off = pieces.offset().unwrap();
// Parsed as a positive zero.
assert_eq!(off, PiecesOffset::from(
PiecesNumericOffset::from(Offset::UTC),
));
// Gets converted from -00:00 to UTC, i.e., just zero.
assert_eq!(off.to_numeric_offset(), Offset::UTC);
pub fn time_zone_annotation(&self) -> Option<&TimeZoneAnnotation<'n>> ⓘ
pub fn time_zone_annotation(&self) -> Option<&TimeZoneAnnotation<'n>> ⓘ
Returns the time zone annotation in this Pieces
.
A time zone annotation is optional. The higher level
DateTimeParser
requires a time zone annotation when parsing into a Zoned
.
A time zone annotation is either an offset, or more commonly, an IANA time zone identifier.
§Example
use jiff::{fmt::temporal::{Pieces, TimeZoneAnnotation}, tz::offset};
// A time zone annotation from a name:
let pieces = Pieces::parse("2025-01-02T16:47-05[America/New_York]")?;
assert_eq!(
pieces.time_zone_annotation().unwrap(),
&TimeZoneAnnotation::from("America/New_York"),
);
// A time zone annotation from an offset:
let pieces = Pieces::parse("2025-01-02T16:47-05[-05:00]")?;
assert_eq!(
pieces.time_zone_annotation().unwrap(),
&TimeZoneAnnotation::from(offset(-5)),
);
pub fn to_numeric_offset(&self) -> Option<Offset> ⓘ
pub fn to_numeric_offset(&self) -> Option<Offset> ⓘ
A convenience routine for converting an offset on this Pieces
,
if present, to a numeric Offset
.
This collapses the offsets Z
, -00:00
and +00:00
all to
Offset::UTC
. If you need to distinguish between them, then use
Pieces::offset
.
§Example
This example shows how Z
, -00:00
and +00:00
all map to the same
Offset
value:
use jiff::{fmt::temporal::Pieces, tz::Offset};
let pieces = Pieces::parse("1970-01-01T00:00:00Z")?;
assert_eq!(pieces.to_numeric_offset(), Some(Offset::UTC));
let pieces = Pieces::parse("1970-01-01T00:00:00-00:00")?;
assert_eq!(pieces.to_numeric_offset(), Some(Offset::UTC));
let pieces = Pieces::parse("1970-01-01T00:00:00+00:00")?;
assert_eq!(pieces.to_numeric_offset(), Some(Offset::UTC));
pub fn to_time_zone(&self) -> Result<Option<TimeZone>, Error> ⓘ
pub fn to_time_zone(&self) -> Result<Option<TimeZone>, Error> ⓘ
A convenience routine for converting a time zone annotation, if
present, into a TimeZone
.
If no annotation is on this Pieces
, then this returns Ok(None)
.
This may return an error if the time zone annotation is a name and it couldn’t be found in Jiff’s global time zone database.
§Example
use jiff::{fmt::temporal::Pieces, tz::{TimeZone, offset}};
// No time zone annotations means you get `Ok(None)`:
let pieces = Pieces::parse("2025-01-03T17:13-05")?;
assert_eq!(pieces.to_time_zone()?, None);
// An offset time zone annotation gets you a fixed offset `TimeZone`:
let pieces = Pieces::parse("2025-01-03T17:13-05[-05]")?;
assert_eq!(pieces.to_time_zone()?, Some(TimeZone::fixed(offset(-5))));
// A time zone annotation name gets you a IANA time zone:
let pieces = Pieces::parse("2025-01-03T17:13-05[America/New_York]")?;
assert_eq!(pieces.to_time_zone()?, Some(TimeZone::get("America/New_York")?));
// A time zone annotation name that doesn't exist gives you an error:
let pieces = Pieces::parse("2025-01-03T17:13-05[Australia/Bluey]")?;
assert_eq!(
pieces.to_time_zone().unwrap_err().to_string(),
"failed to find time zone `Australia/Bluey` in time zone database",
);
pub fn to_time_zone_with(
&self,
db: &TimeZoneDatabase,
) -> Result<Option<TimeZone>, Error> ⓘ
pub fn to_time_zone_with( &self, db: &TimeZoneDatabase, ) -> Result<Option<TimeZone>, Error> ⓘ
A convenience routine for converting a time zone annotation, if
present, into a TimeZone
using the given TimeZoneDatabase
.
If no annotation is on this Pieces
, then this returns Ok(None)
.
This may return an error if the time zone annotation is a name and it couldn’t be found in Jiff’s global time zone database.
§Example
use jiff::{fmt::temporal::Pieces, tz::TimeZone};
// A time zone annotation name gets you a IANA time zone:
let pieces = Pieces::parse("2025-01-03T17:13-05[America/New_York]")?;
assert_eq!(
pieces.to_time_zone_with(jiff::tz::db())?,
Some(TimeZone::get("America/New_York")?),
);
pub fn with_date(self, date: Date) -> Pieces<'n>
pub fn with_date(self, date: Date) -> Pieces<'n>
Set the date on this Pieces
to the one given.
A Date
is the minimal piece of information necessary to create a
Pieces
. This method will override any previous setting.
§Example
use jiff::{civil, fmt::temporal::Pieces, Timestamp};
let pieces = Pieces::from(civil::date(2025, 1, 3));
assert_eq!(pieces.to_string(), "2025-01-03");
// Alternatively, build a `Pieces` from another data type, and the
// date field will be automatically populated.
let pieces = Pieces::from(Timestamp::from_second(1735930208)?);
assert_eq!(pieces.date(), civil::date(2025, 1, 3));
assert_eq!(pieces.to_string(), "2025-01-03T18:50:08Z");
pub fn with_time(self, time: Time) -> Pieces<'n>
pub fn with_time(self, time: Time) -> Pieces<'n>
Set the time on this Pieces
to the one given.
Setting a Time
on Pieces
is optional. When formatting a
Pieces
to a string, a missing Time
may be omitted from the datetime
string in some cases. See Pieces::with_offset
for more details.
§Example
use jiff::{civil, fmt::temporal::Pieces};
let pieces = Pieces::from(civil::date(2025, 1, 3))
.with_time(civil::time(13, 48, 0, 0));
assert_eq!(pieces.to_string(), "2025-01-03T13:48:00");
// Alternatively, build a `Pieces` from a `DateTime` directly:
let pieces = Pieces::from(civil::date(2025, 1, 3).at(13, 48, 0, 0));
assert_eq!(pieces.to_string(), "2025-01-03T13:48:00");
pub fn with_offset<T>(self, offset: T) -> Pieces<'n>where
T: Into<PiecesOffset>,
pub fn with_offset<T>(self, offset: T) -> Pieces<'n>where
T: Into<PiecesOffset>,
Set the offset on this Pieces
to the one given.
Setting the offset on Pieces
is optional.
The type of offset is polymorphic, and includes anything that can be
infallibly converted into a PiecesOffset
. This includes an
Offset
.
This refers to the offset in the RFC 3339 component of a Temporal ISO 8601 datetime string.
Since a string like 2025-01-03+11
is not valid, if a Pieces
has
an offset set but no Time
set, then formatting the Pieces
will
write an explicit Time
set to midnight.
Note that this is distinct from Pieces::with_time_zone_offset
.
This routine sets the offset on the datetime, while
Pieces::with_time_zone_offset
sets the offset inside the time zone
annotation. When the timestamp offset and the time zone annotation
offset are both present, then they must be equivalent or else the
datetime string is not a valid Temporal ISO 8601 string. However, a
Pieces
will let you format a string with mismatching offsets.
§Example
This example shows how easily you can shoot yourself in the foot with this routine:
use jiff::{fmt::temporal::{Pieces, TimeZoneAnnotation}, tz};
let mut pieces = Pieces::parse("2025-01-03T07:55+02[+02]")?;
pieces = pieces.with_offset(tz::offset(-10));
// This is nonsense because the offsets don't match!
// And notice also that the instant that this timestamp refers to has
// changed.
assert_eq!(pieces.to_string(), "2025-01-03T07:55:00-10:00[+02:00]");
This exemplifies that Pieces
is a mostly “dumb” type that passes
through the data it contains, even if it doesn’t make sense.
§Example: changing the offset can change the instant
Consider this case where a Pieces
is created directly from a
Timestamp
, and then the offset is changed.
use jiff::{fmt::temporal::Pieces, tz, Timestamp};
let pieces = Pieces::from(Timestamp::UNIX_EPOCH)
.with_offset(tz::offset(-5));
assert_eq!(pieces.to_string(), "1970-01-01T00:00:00-05:00");
You might do this naively as a way of printing the timestamp of the
Unix epoch with an offset of -05
from UTC. But the above does not
correspond to the Unix epoch:
use jiff::{Timestamp, ToSpan, Unit};
let ts: Timestamp = "1970-01-01T00:00:00-05:00".parse()?;
assert_eq!(ts.since((Unit::Hour, Timestamp::UNIX_EPOCH))?, 5.hours());
This further exemplifies how Pieces
is just a “dumb” type that
passes through the data it contains.
This specific example is also why Pieces
has a From
trait
implementation for (Timestamp, Offset)
, which correspond more to
what you want:
use jiff::{fmt::temporal::Pieces, tz, Timestamp};
let pieces = Pieces::from((Timestamp::UNIX_EPOCH, tz::offset(-5)));
assert_eq!(pieces.to_string(), "1969-12-31T19:00:00-05:00");
A decent mental model of Pieces
is that setting fields on Pieces
can’t change the values in memory of other fields.
§Example: setting an offset forces a time to be written
Consider these cases where formatting a Pieces
won’t write a
Time
:
use jiff::fmt::temporal::Pieces;
let pieces = Pieces::from(jiff::civil::date(2025, 1, 3));
assert_eq!(pieces.to_string(), "2025-01-03");
let pieces = Pieces::from(jiff::civil::date(2025, 1, 3))
.with_time_zone_name("Africa/Cairo");
assert_eq!(pieces.to_string(), "2025-01-03[Africa/Cairo]");
This works because the resulting strings are valid. In particular, when
one parses a 2025-01-03[Africa/Cairo]
into a Zoned
, it results in a
time component of midnight automatically (or more precisely, the first
instead of the corresponding day):
use jiff::{civil::Time, Zoned};
let zdt: Zoned = "2025-01-03[Africa/Cairo]".parse()?;
assert_eq!(zdt.time(), Time::midnight());
// tricksy tricksy, the first instant of 2015-10-18 in Sao Paulo is
// not midnight!
let zdt: Zoned = "2015-10-18[America/Sao_Paulo]".parse()?;
assert_eq!(zdt.time(), jiff::civil::time(1, 0, 0, 0));
// This happens because midnight didn't appear on the clocks in
// Sao Paulo on 2015-10-18. So if you try to parse a datetime with
// midnight, automatic disambiguation kicks in and chooses the time
// after the gap automatically:
let zdt: Zoned = "2015-10-18T00:00:00[America/Sao_Paulo]".parse()?;
assert_eq!(zdt.time(), jiff::civil::time(1, 0, 0, 0));
However, if you have a date and an offset, then since things like
2025-01-03+10
aren’t valid Temporal ISO 8601 datetime strings, the
default midnight time is automatically written:
use jiff::{fmt::temporal::Pieces, tz};
let pieces = Pieces::from(jiff::civil::date(2025, 1, 3))
.with_offset(tz::offset(-5));
assert_eq!(pieces.to_string(), "2025-01-03T00:00:00-05:00");
let pieces = Pieces::from(jiff::civil::date(2025, 1, 3))
.with_offset(tz::offset(2))
.with_time_zone_name("Africa/Cairo");
assert_eq!(pieces.to_string(), "2025-01-03T00:00:00+02:00[Africa/Cairo]");
§Example: formatting a Zulu or -00:00
offset
A PiecesOffset
encapsulates not just a numeric offset, but also
whether a Z
or a signed zero are used. While it’s uncommon to need
this, this permits one to format a Pieces
using either of these
constructs:
use jiff::{
civil,
fmt::temporal::{Pieces, PiecesNumericOffset, PiecesOffset},
tz::Offset,
};
let pieces = Pieces::from(civil::date(1970, 1, 1).at(0, 0, 0, 0))
.with_offset(Offset::UTC);
assert_eq!(pieces.to_string(), "1970-01-01T00:00:00+00:00");
let pieces = Pieces::from(civil::date(1970, 1, 1).at(0, 0, 0, 0))
.with_offset(PiecesOffset::Zulu);
assert_eq!(pieces.to_string(), "1970-01-01T00:00:00Z");
let pieces = Pieces::from(civil::date(1970, 1, 1).at(0, 0, 0, 0))
.with_offset(PiecesNumericOffset::from(Offset::UTC).with_negative_zero());
assert_eq!(pieces.to_string(), "1970-01-01T00:00:00-00:00");
pub fn with_time_zone_name<'a>(self, name: &'a str) -> Pieces<'a>
pub fn with_time_zone_name<'a>(self, name: &'a str) -> Pieces<'a>
Sets the time zone annotation on this Pieces
to the given time zone
name.
Setting a time zone annotation on Pieces
is optional.
This is a convenience routine for using
Pieces::with_time_zone_annotation
with an explicitly constructed
TimeZoneAnnotation
for a time zone name.
§Example
This example shows how easily you can shoot yourself in the foot with this routine:
use jiff::fmt::temporal::{Pieces, TimeZoneAnnotation};
let mut pieces = Pieces::parse("2025-01-03T07:55+02[Africa/Cairo]")?;
pieces = pieces.with_time_zone_name("Australia/Bluey");
// This is nonsense because `Australia/Bluey` isn't a valid time zone!
assert_eq!(pieces.to_string(), "2025-01-03T07:55:00+02:00[Australia/Bluey]");
This exemplifies that Pieces
is a mostly “dumb” type that passes
through the data it contains, even if it doesn’t make sense.
pub fn with_time_zone_offset(self, offset: Offset) -> Pieces<'static>
pub fn with_time_zone_offset(self, offset: Offset) -> Pieces<'static>
Sets the time zone annotation on this Pieces
to the given offset.
Setting a time zone annotation on Pieces
is optional.
This is a convenience routine for using
Pieces::with_time_zone_annotation
with an explicitly constructed
TimeZoneAnnotation
for a time zone offset.
Note that this is distinct from Pieces::with_offset
. This
routine sets the offset inside the time zone annotation, while
Pieces::with_offset
sets the offset on the timestamp itself. When the
timestamp offset and the time zone annotation offset are both present,
then they must be equivalent or else the datetime string is not a valid
Temporal ISO 8601 string. However, a Pieces
will let you format a
string with mismatching offsets.
§Example
This example shows how easily you can shoot yourself in the foot with this routine:
use jiff::{fmt::temporal::{Pieces, TimeZoneAnnotation}, tz};
let mut pieces = Pieces::parse("2025-01-03T07:55+02[Africa/Cairo]")?;
pieces = pieces.with_time_zone_offset(tz::offset(-7));
// This is nonsense because the offset `+02` does not match `-07`.
assert_eq!(pieces.to_string(), "2025-01-03T07:55:00+02:00[-07:00]");
This exemplifies that Pieces
is a mostly “dumb” type that passes
through the data it contains, even if it doesn’t make sense.
pub fn with_time_zone_annotation<'a>(
self,
ann: TimeZoneAnnotation<'a>,
) -> Pieces<'a>
pub fn with_time_zone_annotation<'a>( self, ann: TimeZoneAnnotation<'a>, ) -> Pieces<'a>
Returns a new Pieces
with the given time zone annotation.
Setting a time zone annotation on Pieces
is optional.
You may find it more convenient to use
Pieces::with_time_zone_name
or Pieces::with_time_zone_offset
.
§Example
This example shows how easily you can shoot yourself in the foot with this routine:
use jiff::fmt::temporal::{Pieces, TimeZoneAnnotation};
let mut pieces = Pieces::parse("2025-01-03T07:55+02[Africa/Cairo]")?;
pieces = pieces.with_time_zone_annotation(
TimeZoneAnnotation::from("Canada/Yukon"),
);
// This is nonsense because the offset `+02` is never valid for the
// `Canada/Yukon` time zone.
assert_eq!(pieces.to_string(), "2025-01-03T07:55:00+02:00[Canada/Yukon]");
This exemplifies that Pieces
is a mostly “dumb” type that passes
through the data it contains, even if it doesn’t make sense.
pub fn into_owned(self) -> Pieces<'static>
pub fn into_owned(self) -> Pieces<'static>
Converts this Pieces
into an “owned” value whose lifetime is
'static
.
Ths “owned” value in this context refers to the time zone annotation
name, if present. For example, Canada/Yukon
in
2025-01-03T07:55-07[Canada/Yukon]
. When parsing into a Pieces
,
the time zone annotation name is borrowed. But callers may find it more
convenient to work with an owned value. By calling this method, the
borrowed string internally will be copied into a new string heap
allocation.
If Pieces
doesn’t have a time zone annotation, is already owned or
the time zone annotation is an offset, then this is a no-op.
Trait Implementations§
impl<'n> Eq for Pieces<'n>
impl<'n> StructuralPartialEq for Pieces<'n>
Auto Trait Implementations§
impl<'n> Freeze for Pieces<'n>
impl<'n> RefUnwindSafe for Pieces<'n>
impl<'n> Send for Pieces<'n>
impl<'n> Sync for Pieces<'n>
impl<'n> Unpin for Pieces<'n>
impl<'n> UnwindSafe for Pieces<'n>
Blanket Implementations§
§impl<T> ArchivePointee for T
impl<T> ArchivePointee for T
§type ArchivedMetadata = ()
type ArchivedMetadata = ()
§fn pointer_metadata(
_: &<T as ArchivePointee>::ArchivedMetadata,
) -> <T as Pointee>::Metadata
fn pointer_metadata( _: &<T as ArchivePointee>::ArchivedMetadata, ) -> <T as Pointee>::Metadata
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> ByteSized for T
impl<T> ByteSized for T
Source§const BYTE_ALIGN: usize = _
const BYTE_ALIGN: usize = _
Source§fn byte_align(&self) -> usize ⓘ
fn byte_align(&self) -> usize ⓘ
Source§fn ptr_size_ratio(&self) -> [usize; 2]
fn ptr_size_ratio(&self) -> [usize; 2]
Source§impl<T, R> Chain<R> for Twhere
T: ?Sized,
impl<T, R> Chain<R> for Twhere
T: ?Sized,
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
§fn equivalent(&self, key: &K) -> bool
fn equivalent(&self, key: &K) -> bool
key
and return true
if they are equal.Source§impl<T> ExtAny for T
impl<T> ExtAny for T
Source§fn as_any_mut(&mut self) -> &mut dyn Anywhere
Self: Sized,
fn as_any_mut(&mut self) -> &mut dyn Anywhere
Self: Sized,
Source§impl<T> ExtMem for Twhere
T: ?Sized,
impl<T> ExtMem for Twhere
T: ?Sized,
Source§const NEEDS_DROP: bool = _
const NEEDS_DROP: bool = _
Source§fn mem_align_of_val(&self) -> usize ⓘ
fn mem_align_of_val(&self) -> usize ⓘ
Source§fn mem_size_of_val(&self) -> usize ⓘ
fn mem_size_of_val(&self) -> usize ⓘ
Source§fn mem_needs_drop(&self) -> bool
fn mem_needs_drop(&self) -> bool
true
if dropping values of this type matters. Read moreSource§fn mem_forget(self)where
Self: Sized,
fn mem_forget(self)where
Self: Sized,
self
without running its destructor. Read moreSource§fn mem_replace(&mut self, other: Self) -> Selfwhere
Self: Sized,
fn mem_replace(&mut self, other: Self) -> Selfwhere
Self: Sized,
Source§unsafe fn mem_zeroed<T>() -> T
unsafe fn mem_zeroed<T>() -> T
unsafe_layout
only.T
represented by the all-zero byte-pattern. Read moreSource§unsafe fn mem_transmute_copy<Src, Dst>(src: &Src) -> Dst
unsafe fn mem_transmute_copy<Src, Dst>(src: &Src) -> Dst
unsafe_layout
only.T
represented by the all-zero byte-pattern. Read moreSource§fn mem_as_bytes(&self) -> &[u8] ⓘ
fn mem_as_bytes(&self) -> &[u8] ⓘ
unsafe_slice
only.§impl<S> FromSample<S> for S
impl<S> FromSample<S> for S
fn from_sample_(s: S) -> S
Source§impl<T> Hook for T
impl<T> Hook for T
§impl<T> Instrument for T
impl<T> Instrument for T
§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self
into a Left
variant of Either<Self, Self>
if into_left
is true
.
Converts self
into a Right
variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self
into a Left
variant of Either<Self, Self>
if into_left(&self)
returns true
.
Converts self
into a Right
variant of Either<Self, Self>
otherwise. Read more§impl<F, T> IntoSample<T> for Fwhere
T: FromSample<F>,
impl<F, T> IntoSample<T> for Fwhere
T: FromSample<F>,
fn into_sample(self) -> T
§impl<T> LayoutRaw for T
impl<T> LayoutRaw for T
§fn layout_raw(_: <T as Pointee>::Metadata) -> Result<Layout, LayoutError> ⓘ
fn layout_raw(_: <T as Pointee>::Metadata) -> Result<Layout, LayoutError> ⓘ
§impl<T, N1, N2> Niching<NichedOption<T, N1>> for N2
impl<T, N1, N2> Niching<NichedOption<T, N1>> for N2
§unsafe fn is_niched(niched: *const NichedOption<T, N1>) -> bool
unsafe fn is_niched(niched: *const NichedOption<T, N1>) -> bool
§fn resolve_niched(out: Place<NichedOption<T, N1>>)
fn resolve_niched(out: Place<NichedOption<T, N1>>)
out
indicating that a T
is niched.