jiff/fmt/temporal/
pieces.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
use crate::{
    civil::{Date, DateTime, Time},
    error::Error,
    tz::{Offset, TimeZone, TimeZoneDatabase},
    util::borrow::StringCow,
    Timestamp, Zoned,
};

/// 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:
///
/// ```text
/// {date}T{time}{offset}[{time-zone-annotation}]
/// ```
///
/// For example:
///
/// ```text
/// 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);
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// 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]");
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// 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")?));
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// 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]");
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// 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]");
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// 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`](crate::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]");
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// 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][infer-time-zone], 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`](crate::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,
/// ]);
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// 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.
///
/// [infer-time-zone]: https://github.com/BurntSushi/jiff/discussions/181#discussioncomment-11729435
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct Pieces<'n> {
    date: Date,
    time: Option<Time>,
    offset: Option<PiecesOffset>,
    time_zone_annotation: Option<TimeZoneAnnotation<'n>>,
}

impl<'n> Pieces<'n> {
    /// Parses a Temporal ISO 8601 datetime string into a `Pieces`.
    ///
    /// This is a convenience routine for
    /// [`DateTimeParser::parses_pieces`](crate::fmt::temporal::DateTimeParser::parse_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")?));
    ///
    /// # Ok::<(), Box<dyn std::error::Error>>(())
    /// ```
    #[inline]
    pub fn parse<I: ?Sized + AsRef<[u8]> + 'n>(
        input: &'n I,
    ) -> Result<Pieces<'n>, Error> {
        let input = input.as_ref();
        super::DEFAULT_DATETIME_PARSER.parse_pieces(input)
    }

    /// 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));
    ///
    /// # Ok::<(), Box<dyn std::error::Error>>(())
    /// ```
    #[inline]
    pub fn date(&self) -> Date {
        self.date
    }

    /// Returns the civil time in this `Pieces`.
    ///
    /// The time component is optional. In
    /// [`DateTimeParser`](crate::fmt::temporal::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));
    ///
    /// # Ok::<(), Box<dyn std::error::Error>>(())
    /// ```
    #[inline]
    pub fn time(&self) -> Option<Time> {
        self.time
    }

    /// 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);
    ///
    /// # Ok::<(), Box<dyn std::error::Error>>(())
    /// ```
    #[inline]
    pub fn offset(&self) -> Option<PiecesOffset> {
        self.offset
    }

    /// Returns the time zone annotation in this `Pieces`.
    ///
    /// A time zone annotation is optional. The higher level
    /// [`DateTimeParser`](crate::fmt::temporal::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)),
    /// );
    ///
    /// # Ok::<(), Box<dyn std::error::Error>>(())
    /// ```
    #[inline]
    pub fn time_zone_annotation(&self) -> Option<&TimeZoneAnnotation<'n>> {
        self.time_zone_annotation.as_ref()
    }

    /// 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));
    ///
    /// # Ok::<(), Box<dyn std::error::Error>>(())
    /// ```
    #[inline]
    pub fn to_numeric_offset(&self) -> Option<Offset> {
        self.offset().map(|poffset| poffset.to_numeric_offset())
    }

    /// 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",
    /// );
    ///
    /// # Ok::<(), Box<dyn std::error::Error>>(())
    /// ```
    #[inline]
    pub fn to_time_zone(&self) -> Result<Option<TimeZone>, Error> {
        self.to_time_zone_with(crate::tz::db())
    }

    /// 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")?),
    /// );
    ///
    /// # Ok::<(), Box<dyn std::error::Error>>(())
    /// ```
    #[inline]
    pub fn to_time_zone_with(
        &self,
        db: &TimeZoneDatabase,
    ) -> Result<Option<TimeZone>, Error> {
        let Some(ann) = self.time_zone_annotation() else { return Ok(None) };
        ann.to_time_zone_with(db).map(Some)
    }

    /// 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");
    ///
    /// # Ok::<(), Box<dyn std::error::Error>>(())
    /// ```
    #[inline]
    pub fn with_date(self, date: Date) -> Pieces<'n> {
        Pieces { date, ..self }
    }

    /// 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");
    ///
    /// # Ok::<(), Box<dyn std::error::Error>>(())
    /// ```
    #[inline]
    pub fn with_time(self, time: Time) -> Pieces<'n> {
        Pieces { time: Some(time), ..self }
    }

    /// 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]");
    ///
    /// # Ok::<(), Box<dyn std::error::Error>>(())
    /// ```
    ///
    /// 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().fieldwise(),
    /// );
    ///
    /// # Ok::<(), Box<dyn std::error::Error>>(())
    /// ```
    ///
    /// 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));
    ///
    /// # Ok::<(), Box<dyn std::error::Error>>(())
    /// ```
    ///
    /// 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");
    /// ```
    ///
    /// [RFC 3339]: https://www.rfc-editor.org/rfc/rfc3339
    #[inline]
    pub fn with_offset<T: Into<PiecesOffset>>(self, offset: T) -> Pieces<'n> {
        Pieces { offset: Some(offset.into()), ..self }
    }

    /// 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]");
    ///
    /// # Ok::<(), Box<dyn std::error::Error>>(())
    /// ```
    ///
    /// This exemplifies that `Pieces` is a mostly "dumb" type that passes
    /// through the data it contains, even if it doesn't make sense.
    #[inline]
    pub fn with_time_zone_name<'a>(self, name: &'a str) -> Pieces<'a> {
        self.with_time_zone_annotation(TimeZoneAnnotation::from(name))
    }

    /// 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]");
    ///
    /// # Ok::<(), Box<dyn std::error::Error>>(())
    /// ```
    ///
    /// This exemplifies that `Pieces` is a mostly "dumb" type that passes
    /// through the data it contains, even if it doesn't make sense.
    #[inline]
    pub fn with_time_zone_offset(self, offset: Offset) -> Pieces<'static> {
        self.with_time_zone_annotation(TimeZoneAnnotation::from(offset))
    }

    /// 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]");
    ///
    /// # Ok::<(), Box<dyn std::error::Error>>(())
    /// ```
    ///
    /// This exemplifies that `Pieces` is a mostly "dumb" type that passes
    /// through the data it contains, even if it doesn't make sense.
    #[inline]
    pub fn with_time_zone_annotation<'a>(
        self,
        ann: TimeZoneAnnotation<'a>,
    ) -> Pieces<'a> {
        Pieces { time_zone_annotation: Some(ann), ..self }
    }

    /// 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.
    #[cfg(feature = "alloc")]
    #[inline]
    pub fn into_owned(self) -> Pieces<'static> {
        Pieces {
            date: self.date,
            time: self.time,
            offset: self.offset,
            time_zone_annotation: self
                .time_zone_annotation
                .map(|ann| ann.into_owned()),
        }
    }
}

impl From<Date> for Pieces<'static> {
    #[inline]
    fn from(date: Date) -> Pieces<'static> {
        Pieces { date, time: None, offset: None, time_zone_annotation: None }
    }
}

impl From<DateTime> for Pieces<'static> {
    #[inline]
    fn from(dt: DateTime) -> Pieces<'static> {
        Pieces::from(dt.date()).with_time(dt.time())
    }
}

impl From<Timestamp> for Pieces<'static> {
    #[inline]
    fn from(ts: Timestamp) -> Pieces<'static> {
        let dt = Offset::UTC.to_datetime(ts);
        Pieces::from(dt).with_offset(PiecesOffset::Zulu)
    }
}

impl From<(Timestamp, Offset)> for Pieces<'static> {
    #[inline]
    fn from((ts, offset): (Timestamp, Offset)) -> Pieces<'static> {
        Pieces::from(offset.to_datetime(ts)).with_offset(offset)
    }
}

impl<'a> From<&'a Zoned> for Pieces<'a> {
    #[inline]
    fn from(zdt: &'a Zoned) -> Pieces<'a> {
        let mut pieces =
            Pieces::from(zdt.datetime()).with_offset(zdt.offset());
        if let Some(name) = zdt.time_zone().iana_name() {
            pieces = pieces.with_time_zone_name(name);
        } else {
            pieces = pieces.with_time_zone_offset(zdt.offset());
        }
        pieces
    }
}

impl<'n> core::fmt::Display for Pieces<'n> {
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
        use crate::fmt::StdFmtWrite;

        let precision =
            f.precision().map(|p| u8::try_from(p).unwrap_or(u8::MAX));
        super::DateTimePrinter::new()
            .precision(precision)
            .print_pieces(self, StdFmtWrite(f))
            .map_err(|_| core::fmt::Error)
    }
}

/// An offset parsed from a Temporal ISO 8601 datetime string, for use with
/// [`Pieces`].
///
/// One can almost think of this as effectively equivalent to an `Offset`. And
/// indeed, all `PiecesOffset` values can be convert to an `Offset`. However,
/// some offsets in a datetime string have a different connotation that can't
/// be captured by an `Offset`.
///
/// For example, the offsets `Z`, `-00:00` and `+00:00` all map to
/// [`Offset::UTC`] after parsing. However, `Z` and `-00:00` generally
/// indicate that the offset from local time is unknown, where as `+00:00`
/// indicates that the offset from local is _known_ and is zero. This type
/// permits callers to inspect what offset was actually written.
///
/// # Example
///
/// This example shows how one can create Temporal ISO 8601 datetime strings
/// with `+00:00`, `-00:00` or `Z` offsets.
///
/// ```
/// use jiff::{
///     fmt::temporal::{Pieces, PiecesNumericOffset},
///     tz::Offset,
///     Timestamp,
/// };
///
/// // If you create a `Pieces` from a `Timestamp` with a UTC offset,
/// // then this is interpreted as "the offset from UTC is known and is
/// // zero."
/// let pieces = Pieces::from((Timestamp::UNIX_EPOCH, Offset::UTC));
/// assert_eq!(pieces.to_string(), "1970-01-01T00:00:00+00:00");
///
/// // Otherwise, if you create a `Pieces` from just a `Timestamp` with
/// // no offset, then it is interpreted as "the offset from UTC is not
/// // known." Typically, this is rendered with `Z` for "Zulu":
/// let pieces = Pieces::from(Timestamp::UNIX_EPOCH);
/// assert_eq!(pieces.to_string(), "1970-01-01T00:00:00Z");
///
/// // But it might be the case that you want to use `-00:00` instead,
/// // perhaps to conform to some existing convention or legacy
/// // applications that require it:
/// let pieces = Pieces::from(Timestamp::UNIX_EPOCH)
///     .with_offset(
///         PiecesNumericOffset::from(Offset::UTC).with_negative_zero(),
///     );
/// assert_eq!(pieces.to_string(), "1970-01-01T00:00:00-00:00");
/// ```
///
/// Without `Pieces`, it's not otherwise possible to emit a `-00:00` offset.
/// For example,
/// [`DateTimePrinter::print_timestamp`](crate::fmt::temporal::DateTimePrinter::print_timestamp)
/// will always emit `Z`, which is consider semantically identical to `-00:00`
/// by [RFC 9557]. There's no specific use case where it's expected that you
/// should need to write `-00:00` instead of `Z`, but it's conceivable legacy
/// or otherwise inflexible applications might want it. Or perhaps, in some
/// systems, there is a distinction to draw between `Z` and `-00:00`.
///
/// [RFC 9557]: https://www.rfc-editor.org/rfc/rfc9557.html
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[non_exhaustive]
pub enum PiecesOffset {
    /// The "Zulu" offset, corresponding to UTC in a context where the offset
    /// for civil time is unknown or unavailable.
    ///
    /// [RFC 9557] defines this as equivalent in semantic meaning to `-00:00`:
    ///
    /// > If the time in UTC is known, but the offset to local time is unknown,
    /// > this can be represented with an offset of `Z`. (The original version
    /// > of this specification provided `-00:00` for this purpose, which is
    /// > not allowed by ISO-8601:2000 and therefore is less interoperable;
    /// > Section 3.3 of RFC 5322 describes a related convention for email,
    /// > which does not have this problem). This differs semantically from an
    /// > offset of `+00:00`, which implies that UTC is the preferred reference
    /// > point for the specified time.
    ///
    /// [RFC 9557]: https://www.rfc-editor.org/rfc/rfc9557
    Zulu,
    /// A specific numeric offset, including whether the parsed sign is
    /// negative.
    ///
    /// The sign is usually redundant, since an `Offset` is itself signed. But
    /// it can be used to distinguish between `+00:00` (`+00` is the preferred
    /// offset) and `-00:00` (`+00` is what should be used, but only because
    /// the offset to local time is not known). Generally speaking, one should
    /// regard `-00:00` as equivalent to `Z`, per RFC 9557.
    Numeric(PiecesNumericOffset),
}

impl PiecesOffset {
    /// Converts this offset to a concrete numeric offset in all cases.
    ///
    /// If this was a `Z` or a `-00:00` offset, then `Offset::UTC` is returned.
    /// In all other cases, the underlying numeric offset is returned as-is.
    ///
    /// # Example
    ///
    /// ```
    /// 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);
    ///
    /// # Ok::<(), Box<dyn std::error::Error>>(())
    /// ```
    #[inline]
    pub fn to_numeric_offset(&self) -> Offset {
        match *self {
            PiecesOffset::Zulu => Offset::UTC,
            // -00:00 and +00:00 both collapse to zero here.
            PiecesOffset::Numeric(ref noffset) => noffset.offset(),
        }
    }
}

impl From<Offset> for PiecesOffset {
    #[inline]
    fn from(offset: Offset) -> PiecesOffset {
        PiecesOffset::from(PiecesNumericOffset::from(offset))
    }
}

impl From<PiecesNumericOffset> for PiecesOffset {
    #[inline]
    fn from(offset: PiecesNumericOffset) -> PiecesOffset {
        PiecesOffset::Numeric(offset)
    }
}

/// A specific numeric offset, including the sign of the offset, for use with
/// [`Pieces`].
///
/// # Signedness
///
/// The sign attached to this type is usually redundant, since the underlying
/// [`Offset`] is itself signed. But it can be used to distinguish between
/// `+00:00` (`+00` is the preferred offset) and `-00:00` (`+00` is what should
/// be used, but only because the offset to local time is not known). Generally
/// speaking, one should regard `-00:00` as equivalent to `Z`, per [RFC 9557].
///
/// [RFC 9557]: https://www.rfc-editor.org/rfc/rfc9557
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct PiecesNumericOffset {
    offset: Offset,
    is_negative: bool,
}

impl PiecesNumericOffset {
    /// Returns the numeric offset.
    ///
    /// # Example
    ///
    /// ```
    /// use jiff::{
    ///     fmt::temporal::{Pieces, PiecesOffset},
    ///     tz::Offset,
    /// };
    ///
    /// let pieces = Pieces::parse("1970-01-01T00:00:00-05:30")?;
    /// let off = match pieces.offset().unwrap() {
    ///     PiecesOffset::Numeric(off) => off,
    ///     _ => unreachable!(),
    /// };
    /// // This is really only useful if you care that an actual
    /// // numeric offset was written and not, e.g., `Z`. Otherwise,
    /// // you could just use `PiecesOffset::to_numeric_offset`.
    /// assert_eq!(
    ///     off.offset(),
    ///     Offset::from_seconds(-5 * 60 * 60 - 30 * 60).unwrap(),
    /// );
    ///
    /// # Ok::<(), Box<dyn std::error::Error>>(())
    /// ```
    #[inline]
    pub fn offset(&self) -> Offset {
        self.offset
    }

    /// Returns whether the sign of the offset is negative or not.
    ///
    /// When formatting a [`Pieces`] to a string, this is _only_ used to
    /// determine the rendered sign when the [`Offset`] is itself zero. In
    /// all other cases, the sign rendered matches the sign of the `Offset`.
    ///
    /// Since `Offset` does not keep track of a sign when its value is zero,
    /// when using the `From<Offset>` trait implementation for this type,
    /// `is_negative` is always set to `false` when the offset is zero.
    ///
    /// # Example
    ///
    /// ```
    /// use jiff::{
    ///     fmt::temporal::{Pieces, PiecesOffset},
    ///     tz::Offset,
    /// };
    ///
    /// let pieces = Pieces::parse("1970-01-01T00:00:00-00:00")?;
    /// let off = match pieces.offset().unwrap() {
    ///     PiecesOffset::Numeric(off) => off,
    ///     _ => unreachable!(),
    /// };
    /// // The numeric offset component in this case is
    /// // indistiguisable from `Offset::UTC`. This is
    /// // because an `Offset` does not use different
    /// // representations for negative and positive zero.
    /// assert_eq!(off.offset(), Offset::UTC);
    /// // This is where `is_negative` comes in handy:
    /// assert_eq!(off.is_negative(), true);
    ///
    /// # Ok::<(), Box<dyn std::error::Error>>(())
    /// ```
    #[inline]
    pub fn is_negative(&self) -> bool {
        self.is_negative
    }

    /// Sets this numeric offset to use `-00:00` if and only if the offset
    /// is zero.
    ///
    /// # Example
    ///
    /// ```
    /// use jiff::{
    ///     fmt::temporal::{Pieces, PiecesNumericOffset},
    ///     tz::Offset,
    ///     Timestamp,
    /// };
    ///
    /// // If you create a `Pieces` from a `Timestamp` with a UTC offset,
    /// // then this is interpreted as "the offset from UTC is known and is
    /// // zero."
    /// let pieces = Pieces::from((Timestamp::UNIX_EPOCH, Offset::UTC));
    /// assert_eq!(pieces.to_string(), "1970-01-01T00:00:00+00:00");
    ///
    /// // Otherwise, if you create a `Pieces` from just a `Timestamp` with
    /// // no offset, then it is interpreted as "the offset from UTC is not
    /// // known." Typically, this is rendered with `Z` for "Zulu":
    /// let pieces = Pieces::from(Timestamp::UNIX_EPOCH);
    /// assert_eq!(pieces.to_string(), "1970-01-01T00:00:00Z");
    ///
    /// // But it might be the case that you want to use `-00:00` instead,
    /// // perhaps to conform to some existing convention or legacy
    /// // applications that require it:
    /// let pieces = Pieces::from(Timestamp::UNIX_EPOCH)
    ///     .with_offset(
    ///         PiecesNumericOffset::from(Offset::UTC).with_negative_zero(),
    ///     );
    /// assert_eq!(pieces.to_string(), "1970-01-01T00:00:00-00:00");
    /// ```
    #[inline]
    pub fn with_negative_zero(self) -> PiecesNumericOffset {
        PiecesNumericOffset { is_negative: true, ..self }
    }
}

impl From<Offset> for PiecesNumericOffset {
    #[inline]
    fn from(offset: Offset) -> PiecesNumericOffset {
        // This can of course never return a -00:00 offset, only +00:00.
        PiecesNumericOffset { offset, is_negative: offset.is_negative() }
    }
}

/// An [RFC 9557] time zone annotation, for use with [`Pieces`].
///
/// A time zone annotation is either a time zone name (typically an IANA time
/// zone identifier) like `America/New_York`, or an offset like `-05:00`. This
/// is normally an implementation detail of parsing into a [`Zoned`], but the
/// raw annotation can be accessed via [`Pieces::time_zone_annotation`] after
/// parsing into a [`Pieces`].
///
/// The lifetime parameter refers to the lifetime of the time zone
/// name. The lifetime is static when the time zone annotation is
/// offset or if the name is owned. An owned value can be produced via
/// [`TimeZoneAnnotation::into_owned`] when the `alloc` crate feature is
/// enabled.
///
/// # Construction
///
/// If you're using [`Pieces`], then its [`Pieces::with_time_zone_name`] and
/// [`Pieces::with_time_zone_offset`] methods should absolve you of needing to
/// build values of this type explicitly. But if the need arises, there are
/// `From` impls for `&str` (time zone annotation name) and [`Offset`] (time
/// zone annotation offset) for this type.
///
/// # 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)),
/// );
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// [RFC 9557]: https://www.rfc-editor.org/rfc/rfc9557.html
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct TimeZoneAnnotation<'n> {
    pub(crate) kind: TimeZoneAnnotationKind<'n>,
    /// Whether the annotation is marked as "critical," i.e., with a
    /// `!` prefix. When enabled, it's supposed to make the annotation
    /// un-ignorable.
    ///
    /// This is basically unused. And there's no way for callers to flip this
    /// switch currently. But it can be queried after parsing. Jiff also
    /// doesn't alter its behavior based on this flag. In particular, Jiff
    /// basically always behaves as if `critical` is true.
    pub(crate) critical: bool,
}

impl<'n> TimeZoneAnnotation<'n> {
    /// Returns the "kind" of this annotation. The kind is either a name or an
    /// offset.
    ///
    /// # Example
    ///
    /// ```
    /// use jiff::fmt::temporal::{Pieces, TimeZoneAnnotation};
    ///
    /// // A time zone annotation from a name, which doesn't necessarily have
    /// // to point to a valid IANA time zone.
    /// let pieces = Pieces::parse("2025-01-02T16:47-05[Australia/Bluey]")?;
    /// assert_eq!(
    ///     pieces.time_zone_annotation().unwrap(),
    ///     &TimeZoneAnnotation::from("Australia/Bluey"),
    /// );
    ///
    /// # Ok::<(), Box<dyn std::error::Error>>(())
    /// ```
    #[inline]
    pub fn kind(&self) -> &TimeZoneAnnotationKind<'n> {
        &self.kind
    }

    /// Returns true when this time zone is marked as "critical." This occurs
    /// when the time zone annotation is preceded by a `!`. It is meant to
    /// signify that, basically, implementations should error if the annotation
    /// is invalid in some way. And when it's absent, it's left up to the
    /// implementation's discretion about what to do (including silently
    /// ignoring the invalid annotation).
    ///
    /// Generally speaking, Jiff ignores this altogether for time zone
    /// annotations and behaves as if it's always true. But it's exposed here
    /// for callers to query in case it's useful.
    ///
    /// # Example
    ///
    /// ```
    /// use jiff::fmt::temporal::{Pieces, TimeZoneAnnotation};
    ///
    /// // not critical
    /// let pieces = Pieces::parse("2025-01-02T16:47-05[Australia/Bluey]")?;
    /// assert_eq!(
    ///     Some(false),
    ///     pieces.time_zone_annotation().map(|a| a.is_critical()),
    /// );
    ///
    /// // critical
    /// let pieces = Pieces::parse("2025-01-02T16:47-05[!Australia/Bluey]")?;
    /// assert_eq!(
    ///     Some(true),
    ///     pieces.time_zone_annotation().map(|a| a.is_critical()),
    /// );
    ///
    /// # Ok::<(), Box<dyn std::error::Error>>(())
    /// ```
    #[inline]
    pub fn is_critical(&self) -> bool {
        self.critical
    }

    /// A convenience routine for converting this annotation into a time zone.
    ///
    /// This can fail if the annotation contains a name that couldn't be found
    /// in the global time zone database. If you need to use something other
    /// than the global time zone database, then use
    /// [`TimeZoneAnnotation::to_time_zone_with`].
    ///
    /// Note that it may be more convenient to use
    /// [`Pieces::to_time_zone`].
    ///
    /// # Example
    ///
    /// ```
    /// use jiff::{fmt::temporal::Pieces, tz::TimeZone};
    ///
    /// let pieces = Pieces::parse("2025-01-02T16:47-05[Australia/Tasmania]")?;
    /// let ann = pieces.time_zone_annotation().unwrap();
    /// assert_eq!(
    ///     ann.to_time_zone().unwrap(),
    ///     TimeZone::get("Australia/Tasmania").unwrap(),
    /// );
    ///
    /// let pieces = Pieces::parse("2025-01-02T16:47-05[Australia/Bluey]")?;
    /// let ann = pieces.time_zone_annotation().unwrap();
    /// assert_eq!(
    ///     ann.to_time_zone().unwrap_err().to_string(),
    ///     "failed to find time zone `Australia/Bluey` in time zone database",
    /// );
    ///
    /// # Ok::<(), Box<dyn std::error::Error>>(())
    /// ```
    #[inline]
    pub fn to_time_zone(&self) -> Result<TimeZone, Error> {
        self.to_time_zone_with(crate::tz::db())
    }

    /// This is like [`TimeZoneAnnotation::to_time_zone`], but permits the
    /// caller to pass in their own time zone database.
    ///
    /// This can fail if the annotation contains a name that couldn't be found
    /// in the global time zone database. If you need to use something other
    /// than the global time zone database, then use
    /// [`TimeZoneAnnotation::to_time_zone_with`].
    ///
    /// Note that it may be more convenient to use
    /// [`Pieces::to_time_zone_with`].
    ///
    /// # Example
    ///
    /// ```
    /// use jiff::{fmt::temporal::Pieces, tz::TimeZone};
    ///
    /// let pieces = Pieces::parse("2025-01-02T16:47-05[Australia/Tasmania]")?;
    /// let ann = pieces.time_zone_annotation().unwrap();
    /// assert_eq!(
    ///     ann.to_time_zone_with(jiff::tz::db()).unwrap(),
    ///     TimeZone::get("Australia/Tasmania").unwrap(),
    /// );
    ///
    /// # Ok::<(), Box<dyn std::error::Error>>(())
    /// ```
    #[inline]
    pub fn to_time_zone_with(
        &self,
        db: &TimeZoneDatabase,
    ) -> Result<TimeZone, Error> {
        // NOTE: We don't currently utilize the critical flag here. Temporal
        // seems to ignore it. It's not quite clear what else we'd do with it,
        // particularly given that we provide a way to do conflict resolution
        // between offsets and time zones.
        let tz = match *self.kind() {
            TimeZoneAnnotationKind::Named(ref name) => {
                db.get(name.as_str())?
            }
            TimeZoneAnnotationKind::Offset(offset) => TimeZone::fixed(offset),
        };
        Ok(tz)
    }

    /// Converts this time zone annotation into an "owned" value whose lifetime
    /// is `'static`.
    ///
    /// If this was already an "owned" value or a time zone annotation offset,
    /// then this is a no-op.
    #[cfg(feature = "alloc")]
    #[inline]
    pub fn into_owned(self) -> TimeZoneAnnotation<'static> {
        TimeZoneAnnotation {
            kind: self.kind.into_owned(),
            critical: self.critical,
        }
    }
}

impl<'n> From<&'n str> for TimeZoneAnnotation<'n> {
    fn from(string: &'n str) -> TimeZoneAnnotation<'n> {
        let kind = TimeZoneAnnotationKind::from(string);
        TimeZoneAnnotation { kind, critical: false }
    }
}

impl From<Offset> for TimeZoneAnnotation<'static> {
    fn from(offset: Offset) -> TimeZoneAnnotation<'static> {
        let kind = TimeZoneAnnotationKind::from(offset);
        TimeZoneAnnotation { kind, critical: false }
    }
}

/// The kind of time zone found in an [RFC 9557] timestamp, for use with
/// [`Pieces`].
///
/// The lifetime parameter refers to the lifetime of the time zone
/// name. The lifetime is static when the time zone annotation is
/// offset or if the name is owned. An owned value can be produced via
/// [`TimeZoneAnnotation::into_owned`] when the `alloc` crate feature is
/// enabled.
///
/// [RFC 9557]: https://www.rfc-editor.org/rfc/rfc9557.html
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
#[non_exhaustive]
pub enum TimeZoneAnnotationKind<'n> {
    /// The time zone annotation is a name, usually an IANA time zone
    /// identifier. For example, `America/New_York`.
    Named(TimeZoneAnnotationName<'n>),
    /// The time zone annotation is an offset. For example, `-05:00`.
    Offset(Offset),
}

impl<'n> TimeZoneAnnotationKind<'n> {
    /// Converts this time zone annotation kind into an "owned" value whose
    /// lifetime is `'static`.
    ///
    /// If this was already an "owned" value or a time zone annotation offset,
    /// then this is a no-op.
    #[cfg(feature = "alloc")]
    #[inline]
    pub fn into_owned(self) -> TimeZoneAnnotationKind<'static> {
        match self {
            TimeZoneAnnotationKind::Named(named) => {
                TimeZoneAnnotationKind::Named(named.into_owned())
            }
            TimeZoneAnnotationKind::Offset(offset) => {
                TimeZoneAnnotationKind::Offset(offset)
            }
        }
    }
}

impl<'n> From<&'n str> for TimeZoneAnnotationKind<'n> {
    fn from(string: &'n str) -> TimeZoneAnnotationKind<'n> {
        let name = TimeZoneAnnotationName::from(string);
        TimeZoneAnnotationKind::Named(name)
    }
}

impl From<Offset> for TimeZoneAnnotationKind<'static> {
    fn from(offset: Offset) -> TimeZoneAnnotationKind<'static> {
        TimeZoneAnnotationKind::Offset(offset)
    }
}

/// A time zone annotation parsed from a datetime string.
///
/// By default, a time zone annotation name borrows its name from the
/// input it was parsed from. When the `alloc` feature is enabled,
/// callers can de-couple the annotation from the parsed input with
/// [`TimeZoneAnnotationName::into_owned`].
///
/// A value of this type is usually found via [`Pieces::time_zone_annotation`],
/// but callers can also construct one via this type's `From<&str>` trait
/// implementation if necessary.
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct TimeZoneAnnotationName<'n> {
    name: StringCow<'n>,
}

impl<'n> TimeZoneAnnotationName<'n> {
    /// Returns the name of this time zone annotation as a string slice.
    ///
    /// Note that the lifetime of the string slice returned is tied to the
    /// lifetime of this time zone annotation. This may be shorter than the
    /// lifetime of the string, `'n`, in this annotation.
    #[inline]
    pub fn as_str<'a>(&'a self) -> &'a str {
        self.name.as_str()
    }

    /// Converts this time zone annotation name into an "owned" value whose
    /// lifetime is `'static`.
    ///
    /// If this was already an "owned" value, then this is a no-op.
    #[cfg(feature = "alloc")]
    #[inline]
    pub fn into_owned(self) -> TimeZoneAnnotationName<'static> {
        TimeZoneAnnotationName { name: self.name.into_owned() }
    }
}

impl<'n> From<&'n str> for TimeZoneAnnotationName<'n> {
    fn from(string: &'n str) -> TimeZoneAnnotationName<'n> {
        TimeZoneAnnotationName { name: StringCow::from(string) }
    }
}