devela::_dep::jiff::fmt::temporal

Struct Pieces

pub struct Pieces<'n> { /* private fields */ }
Available on crate features 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 a Pieces with just a civil Date. All other components are left empty.
  • From<DateTime> creates a Pieces with a civil Date and Time. The offset and time zone annotation are left empty.
  • From<Timestamp> creates a Pieces from a Timestamp using a Zulu offset. This signifies that the precise instant is known, but the local time’s offset from UTC is unknown. The Date and Time are determined via Offset::UTC.to_datetime(timestamp). The time zone annotation is left empty.
  • From<(Timestamp, Offset)> creates a Pieces from a Timestamp and an Offset. The Date and Time are determined via offset.to_datetime(timestamp). The time zone annotation is left empty.
  • From<&Zoned> creates a Pieces from a Zoned. This populates all fields of a Pieces.

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>

pub fn parse<I>(input: &'n I) -> Result<Pieces<'n>, Error>
where I: AsRef<[u8]> + 'n + ?Sized,

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

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>

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>

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>>

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>

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>

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>

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>

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>

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>,

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>

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>

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>

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>

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> Clone for Pieces<'n>

§

fn clone(&self) -> Pieces<'n>

Returns a copy of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
§

impl<'n> Debug for Pieces<'n>

§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
§

impl<'n> Display for Pieces<'n>

§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
§

impl<'a> From<&'a Zoned> for Pieces<'a>

§

fn from(zdt: &'a Zoned) -> Pieces<'a>

Converts to this type from the input type.
§

impl From<(Timestamp, Offset)> for Pieces<'static>

§

fn from(_: (Timestamp, Offset)) -> Pieces<'static>

Converts to this type from the input type.
§

impl From<Date> for Pieces<'static>

§

fn from(date: Date) -> Pieces<'static>

Converts to this type from the input type.
§

impl From<DateTime> for Pieces<'static>

§

fn from(dt: DateTime) -> Pieces<'static>

Converts to this type from the input type.
§

impl From<Timestamp> for Pieces<'static>

§

fn from(ts: Timestamp) -> Pieces<'static>

Converts to this type from the input type.
§

impl<'n> Hash for Pieces<'n>

§

fn hash<__H>(&self, state: &mut __H)
where __H: Hasher,

Feeds this value into the given Hasher. Read more
1.3.0 · Source§

fn hash_slice<H>(data: &[Self], state: &mut H)
where H: Hasher, Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
§

impl<'n> PartialEq for Pieces<'n>

§

fn eq(&self, other: &Pieces<'n>) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, and should not be overridden without very good reason.
§

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§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
§

impl<T> ArchivePointee for T

§

type ArchivedMetadata = ()

The archived version of the pointer metadata for this type.
§

fn pointer_metadata( _: &<T as ArchivePointee>::ArchivedMetadata, ) -> <T as Pointee>::Metadata

Converts some archived metadata to the pointer metadata for itself.
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> ByteSized for T

Source§

const BYTE_ALIGN: usize = _

The alignment of this type in bytes.
Source§

const BYTE_SIZE: usize = _

The size of this type in bytes.
Source§

fn byte_align(&self) -> usize

Returns the alignment of this type in bytes.
Source§

fn byte_size(&self) -> usize

Returns the size of this type in bytes. Read more
Source§

fn ptr_size_ratio(&self) -> [usize; 2]

Returns the size ratio between Ptr::BYTES and BYTE_SIZE. Read more
Source§

impl<T, R> Chain<R> for T
where T: ?Sized,

Source§

fn chain<F>(self, f: F) -> R
where F: FnOnce(Self) -> R, Self: Sized,

Chain a function which takes the parameter by value.
Source§

fn chain_ref<F>(&self, f: F) -> R
where F: FnOnce(&Self) -> R,

Chain a function which takes the parameter by shared reference.
Source§

fn chain_mut<F>(&mut self, f: F) -> R
where F: FnOnce(&mut Self) -> R,

Chain a function which takes the parameter by exclusive reference.
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dst: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dst. Read more
§

impl<Q, K> Equivalent<K> for Q
where Q: Eq + ?Sized, K: Borrow<Q> + ?Sized,

§

fn equivalent(&self, key: &K) -> bool

Compare self to key and return true if they are equal.
Source§

impl<T> ExtAny for T
where T: Any + ?Sized,

Source§

fn type_id() -> TypeId

Returns the TypeId of Self. Read more
Source§

fn type_of(&self) -> TypeId

Returns the TypeId of self. Read more
Source§

fn type_name(&self) -> &'static str

Returns the type name of self. Read more
Source§

fn type_is<T: 'static>(&self) -> bool

Returns true if Self is of type T. Read more
Source§

fn as_any_ref(&self) -> &dyn Any
where Self: Sized,

Upcasts &self as &dyn Any. Read more
Source§

fn as_any_mut(&mut self) -> &mut dyn Any
where Self: Sized,

Upcasts &mut self as &mut dyn Any. Read more
Source§

fn as_any_box(self: Box<Self>) -> Box<dyn Any>
where Self: Sized,

Upcasts Box<self> as Box<dyn Any>. Read more
Source§

fn downcast_ref<T: 'static>(&self) -> Option<&T>

Available on crate feature unsafe_layout only.
Returns some shared reference to the inner value if it is of type T. Read more
Source§

fn downcast_mut<T: 'static>(&mut self) -> Option<&mut T>

Available on crate feature unsafe_layout only.
Returns some exclusive reference to the inner value if it is of type T. Read more
Source§

impl<T> ExtMem for T
where T: ?Sized,

Source§

const NEEDS_DROP: bool = _

Know whether dropping values of this type matters, in compile-time.
Source§

fn mem_align_of<T>() -> usize

Returns the minimum alignment of the type in bytes. Read more
Source§

fn mem_align_of_val(&self) -> usize

Returns the alignment of the pointed-to value in bytes. Read more
Source§

fn mem_size_of<T>() -> usize

Returns the size of a type in bytes. Read more
Source§

fn mem_size_of_val(&self) -> usize

Returns the size of the pointed-to value in bytes. Read more
Source§

fn mem_copy(&self) -> Self
where Self: Copy,

Bitwise-copies a value. Read more
Source§

fn mem_needs_drop(&self) -> bool

Returns true if dropping values of this type matters. Read more
Source§

fn mem_drop(self)
where Self: Sized,

Drops self by running its destructor. Read more
Source§

fn mem_forget(self)
where Self: Sized,

Forgets about self without running its destructor. Read more
Source§

fn mem_replace(&mut self, other: Self) -> Self
where Self: Sized,

Replaces self with other, returning the previous value of self. Read more
Source§

fn mem_take(&mut self) -> Self
where Self: Default,

Replaces self with its default value, returning the previous value of self. Read more
Source§

fn mem_swap(&mut self, other: &mut Self)
where Self: Sized,

Swaps the value of self and other without deinitializing either one. Read more
Source§

unsafe fn mem_zeroed<T>() -> T

Available on crate feature unsafe_layout only.
Returns the value of type T represented by the all-zero byte-pattern. Read more
Source§

unsafe fn mem_transmute_copy<Src, Dst>(src: &Src) -> Dst

Available on crate feature unsafe_layout only.
Returns the value of type T represented by the all-zero byte-pattern. Read more
Source§

fn mem_as_bytes(&self) -> &[u8]
where Self: Sync + Unpin,

Available on crate feature unsafe_slice only.
View a Sync + Unpin self as &[u8]. Read more
Source§

fn mem_as_bytes_mut(&mut self) -> &mut [u8]
where Self: Sync + Unpin,

Available on crate feature unsafe_slice only.
View a Sync + Unpin self as &mut [u8]. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

§

impl<S> FromSample<S> for S

§

fn from_sample_(s: S) -> S

Source§

impl<T> Hook for T

Source§

fn hook_ref<F>(self, f: F) -> Self
where F: FnOnce(&Self),

Applies a function which takes the parameter by shared reference, and then returns the (possibly) modified owned value. Read more
Source§

fn hook_mut<F>(self, f: F) -> Self
where F: FnOnce(&mut Self),

Applies a function which takes the parameter by exclusive reference, and then returns the (possibly) modified owned value. Read more
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts 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 more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts 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 F
where T: FromSample<F>,

§

fn into_sample(self) -> T

§

impl<T> LayoutRaw for T

§

fn layout_raw(_: <T as Pointee>::Metadata) -> Result<Layout, LayoutError>

Returns the layout of the type.
§

impl<T, N1, N2> Niching<NichedOption<T, N1>> for N2
where T: SharedNiching<N1, N2>, N1: Niching<T>, N2: Niching<T>,

§

unsafe fn is_niched(niched: *const NichedOption<T, N1>) -> bool

Returns whether the given value has been niched. Read more
§

fn resolve_niched(out: Place<NichedOption<T, N1>>)

Writes data to out indicating that a T is niched.
§

impl<T> Pointable for T

§

const ALIGN: usize

The alignment of pointer.
§

type Init = T

The type for initializers.
§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
§

impl<T> Pointee for T

§

type Metadata = ()

The metadata type for pointers and references to this type.
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
§

impl<T, U> ToSample<U> for T
where U: FromSample<T>,

§

fn to_sample_(self) -> U

Source§

impl<T> ToString for T
where T: Display + ?Sized,

Source§

fn to_string(&self) -> String

Converts the given value to a String. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more
§

impl<S, T> Duplex<S> for T
where T: FromSample<S> + ToSample<S>,

§

impl<T> Ungil for T
where T: Send,