Skip to content

Commit e2dc83b

Browse files
authored
Add more strict time zone formatting tests (#5707)
#5533
1 parent 85e83d6 commit e2dc83b

File tree

1 file changed

+246
-1
lines changed

1 file changed

+246
-1
lines changed

components/datetime/src/neo_marker.rs

Lines changed: 246 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,17 @@
313313
//! tzf.format(&time_zone),
314314
//! "HST"
315315
//! );
316+
//!
317+
//! // Mis-spelling of "America/Chicago" results in a fallback to GMT format
318+
//! let time_zone = TimeZoneInfo::from_id_and_offset(
319+
//! mapper.as_borrowed().iana_to_bcp47("America/Chigagou"),
320+
//! UtcOffset::from_eighths_of_hour(-5 * 8),
321+
//! )
322+
//! .at_time((Date::try_new_iso(2022, 8, 29).unwrap(), Time::midnight()));
323+
//! assert_try_writeable_eq!(
324+
//! tzf.format(&time_zone),
325+
//! "GMT-5"
326+
//! );
316327
//! ```
317328
318329
#[cfg(doc)]
@@ -2059,6 +2070,8 @@ macro_rules! impl_zone_marker {
20592070
$(zone_specific_short = $zone_specific_short_yes:ident,)?
20602071
// Whether metazone periods are needed
20612072
$(metazone_periods = $metazone_periods_yes:ident,)?
2073+
// Whether to require the TimeZoneBcp47Id
2074+
$(input_tzid = $tzid_input_yes:ident,)?
20622075
// Whether to require the ZoneVariant
20632076
$(input_variant = $variant_input_yes:ident,)?
20642077
// Whether to require the Local Time
@@ -2155,7 +2168,7 @@ macro_rules! impl_zone_marker {
21552168
const COMPONENT: NeoTimeZoneStyle = $components;
21562169
}
21572170
impl ZoneMarkers for $type {
2158-
type TimeZoneIdInput = datetime_marker_helper!(@input/timezone/id, yes);
2171+
type TimeZoneIdInput = datetime_marker_helper!(@input/timezone/id, $($tzid_input_yes)?);
21592172
type TimeZoneOffsetInput = datetime_marker_helper!(@input/timezone/offset, yes);
21602173
type TimeZoneVariantInput = datetime_marker_helper!(@input/timezone/variant, $($variant_input_yes)?);
21612174
type TimeZoneLocalTimeInput = datetime_marker_helper!(@input/timezone/local_time, $($localtime_input_yes)?);
@@ -2467,6 +2480,35 @@ impl_zone_marker!(
24672480
/// "GMT-3"
24682481
/// );
24692482
/// ```
2483+
///
2484+
/// Only a full time zone info can be formatted with this style.
2485+
/// For example, AtTime cannot be formatted.
2486+
///
2487+
/// ```compile_fail
2488+
/// use icu::calendar::{DateTime, Iso};
2489+
/// use icu::datetime::neo::TypedNeoFormatter;
2490+
/// use icu::datetime::neo_marker::NeoTimeZoneSpecificMarker;
2491+
/// use icu::datetime::neo_skeleton::NeoSkeletonLength;
2492+
/// use icu::timezone::{TimeZoneBcp47Id, UtcOffset, ZoneVariant};
2493+
/// use tinystr::tinystr;
2494+
/// use icu::locale::locale;
2495+
/// use writeable::assert_try_writeable_eq;
2496+
///
2497+
/// let datetime = DateTime::try_new_gregorian(2024, 10, 18, 0, 0, 0).unwrap();
2498+
/// let utc_offset = UtcOffset::from_eighths_of_hour(-6 * 8);
2499+
/// let time_zone_basic = utc_offset.with_id(TimeZoneBcp47Id(tinystr!(8, "uschi")));
2500+
/// let time_zone_at_time = time_zone_basic.at_time((datetime.date.to_iso(), datetime.time));
2501+
///
2502+
/// let formatter = TypedNeoFormatter::try_new(
2503+
/// &locale!("en-US").into(),
2504+
/// NeoTimeZoneSpecificMarker::with_length(NeoSkeletonLength::Medium),
2505+
/// )
2506+
/// .unwrap();
2507+
///
2508+
/// // error[E0271]: type mismatch resolving `<AtTime as TimeZoneModel>::ZoneVariant == ZoneVariant`
2509+
/// // note: required by a bound in `TypedNeoFormatter::<C, FSet>::format`
2510+
/// formatter.format(&time_zone_at_time);
2511+
/// ```
24702512
NeoTimeZoneSpecificMarker,
24712513
NeoTimeZoneStyle::Specific,
24722514
description = "specific time zone, or raw offset if unavailable",
@@ -2476,6 +2518,7 @@ impl_zone_marker!(
24762518
zone_specific_long = yes,
24772519
zone_specific_short = yes,
24782520
metazone_periods = yes,
2521+
input_tzid = yes,
24792522
input_variant = yes,
24802523
input_localtime = yes,
24812524
);
@@ -2534,6 +2577,35 @@ impl_zone_marker!(
25342577
///
25352578
/// assert!(matches!(result, Err(LoadError::TypeTooNarrow(_))));
25362579
/// ```
2580+
///
2581+
/// Only a full time zone info can be formatted with this style.
2582+
/// For example, AtTime cannot be formatted.
2583+
///
2584+
/// ```compile_fail
2585+
/// use icu::calendar::{DateTime, Iso};
2586+
/// use icu::datetime::neo::TypedNeoFormatter;
2587+
/// use icu::datetime::neo_marker::NeoTimeZoneSpecificShortMarker;
2588+
/// use icu::datetime::neo_skeleton::NeoSkeletonLength;
2589+
/// use icu::timezone::{TimeZoneBcp47Id, UtcOffset, ZoneVariant};
2590+
/// use tinystr::tinystr;
2591+
/// use icu::locale::locale;
2592+
/// use writeable::assert_try_writeable_eq;
2593+
///
2594+
/// let datetime = DateTime::try_new_gregorian(2024, 10, 18, 0, 0, 0).unwrap();
2595+
/// let utc_offset = UtcOffset::from_eighths_of_hour(-6 * 8);
2596+
/// let time_zone_basic = utc_offset.with_id(TimeZoneBcp47Id(tinystr!(8, "uschi")));
2597+
/// let time_zone_at_time = time_zone_basic.at_time((datetime.date.to_iso(), datetime.time));
2598+
///
2599+
/// let formatter = TypedNeoFormatter::try_new(
2600+
/// &locale!("en-US").into(),
2601+
/// NeoTimeZoneSpecificShortMarker::with_length(NeoSkeletonLength::Medium),
2602+
/// )
2603+
/// .unwrap();
2604+
///
2605+
/// // error[E0271]: type mismatch resolving `<AtTime as TimeZoneModel>::ZoneVariant == ZoneVariant`
2606+
/// // note: required by a bound in `TypedNeoFormatter::<C, FSet>::format`
2607+
/// formatter.format(&time_zone_at_time);
2608+
/// ```
25372609
NeoTimeZoneSpecificShortMarker,
25382610
NeoTimeZoneStyle::Specific,
25392611
description = "specific time zone (only short), or raw offset if unavailable",
@@ -2542,11 +2614,104 @@ impl_zone_marker!(
25422614
zone_essentials = yes,
25432615
zone_specific_short = yes,
25442616
metazone_periods = yes,
2617+
input_tzid = yes,
25452618
input_variant = yes,
25462619
input_localtime = yes,
25472620
);
25482621

25492622
impl_zone_marker!(
2623+
/// All shapes of time zones can be formatted with this style.
2624+
///
2625+
/// ```
2626+
/// use icu::calendar::{DateTime, Iso};
2627+
/// use icu::datetime::neo::TypedNeoFormatter;
2628+
/// use icu::datetime::neo_marker::NeoTimeZoneOffsetMarker;
2629+
/// use icu::datetime::neo_skeleton::NeoSkeletonLength;
2630+
/// use icu::timezone::{TimeZoneBcp47Id, UtcOffset, ZoneVariant, CustomZonedDateTime};
2631+
/// use tinystr::tinystr;
2632+
/// use icu::locale::locale;
2633+
/// use writeable::assert_try_writeable_eq;
2634+
///
2635+
/// let datetime = DateTime::try_new_gregorian(2024, 10, 18, 0, 0, 0).unwrap();
2636+
///
2637+
/// let utc_offset = UtcOffset::from_eighths_of_hour(-6 * 8);
2638+
///
2639+
/// let zdt_utc_offset = CustomZonedDateTime {
2640+
/// date: datetime.date,
2641+
/// time: datetime.time,
2642+
/// zone: utc_offset
2643+
/// };
2644+
///
2645+
/// let time_zone_basic = utc_offset.with_id(TimeZoneBcp47Id(tinystr!(8, "uschi")));
2646+
///
2647+
/// let zdt_time_zone_basic = CustomZonedDateTime {
2648+
/// date: datetime.date,
2649+
/// time: datetime.time,
2650+
/// zone: time_zone_basic
2651+
/// };
2652+
///
2653+
/// let time_zone_at_time = time_zone_basic.at_time((datetime.date.to_iso(), datetime.time));
2654+
///
2655+
/// let zdt_time_zone_at_time = CustomZonedDateTime {
2656+
/// date: datetime.date,
2657+
/// time: datetime.time,
2658+
/// zone: time_zone_at_time
2659+
/// };
2660+
///
2661+
/// let time_zone_full = time_zone_at_time.with_zone_variant(ZoneVariant::standard());
2662+
///
2663+
/// let zdt_time_zone_full = CustomZonedDateTime {
2664+
/// date: datetime.date,
2665+
/// time: datetime.time,
2666+
/// zone: time_zone_full
2667+
/// };
2668+
///
2669+
/// let formatter = TypedNeoFormatter::try_new(
2670+
/// &locale!("en-US").into(),
2671+
/// NeoTimeZoneOffsetMarker::with_length(NeoSkeletonLength::Medium),
2672+
/// )
2673+
/// .unwrap();
2674+
///
2675+
/// assert_try_writeable_eq!(
2676+
/// formatter.format(&utc_offset),
2677+
/// "GMT-6"
2678+
/// );
2679+
///
2680+
/// assert_try_writeable_eq!(
2681+
/// formatter.format(&zdt_utc_offset),
2682+
/// "GMT-6"
2683+
/// );
2684+
///
2685+
/// assert_try_writeable_eq!(
2686+
/// formatter.format(&time_zone_basic),
2687+
/// "GMT-6"
2688+
/// );
2689+
///
2690+
/// assert_try_writeable_eq!(
2691+
/// formatter.format(&zdt_time_zone_basic),
2692+
/// "GMT-6"
2693+
/// );
2694+
///
2695+
/// assert_try_writeable_eq!(
2696+
/// formatter.format(&time_zone_at_time),
2697+
/// "GMT-6"
2698+
/// );
2699+
///
2700+
/// assert_try_writeable_eq!(
2701+
/// formatter.format(&zdt_time_zone_at_time),
2702+
/// "GMT-6"
2703+
/// );
2704+
///
2705+
/// assert_try_writeable_eq!(
2706+
/// formatter.format(&time_zone_full),
2707+
/// "GMT-6"
2708+
/// );
2709+
///
2710+
/// assert_try_writeable_eq!(
2711+
/// formatter.format(&zdt_time_zone_full),
2712+
/// "GMT-6"
2713+
/// );
2714+
/// ```
25502715
NeoTimeZoneOffsetMarker,
25512716
NeoTimeZoneStyle::Offset,
25522717
description = "UTC offset time zone",
@@ -2588,6 +2753,32 @@ impl_zone_marker!(
25882753
/// "Sao Paulo Time"
25892754
/// );
25902755
/// ```
2756+
///
2757+
/// A time zone requires a reference time to be formatted with this style.
2758+
///
2759+
/// ```compile_fail
2760+
/// use icu::calendar::{DateTime, Iso};
2761+
/// use icu::datetime::neo::TypedNeoFormatter;
2762+
/// use icu::datetime::neo_marker::NeoTimeZoneGenericMarker;
2763+
/// use icu::datetime::neo_skeleton::NeoSkeletonLength;
2764+
/// use icu::timezone::{TimeZoneBcp47Id, UtcOffset};
2765+
/// use tinystr::tinystr;
2766+
/// use icu::locale::locale;
2767+
/// use writeable::assert_try_writeable_eq;
2768+
///
2769+
/// let utc_offset = UtcOffset::from_eighths_of_hour(-6 * 8);
2770+
/// let time_zone_basic = utc_offset.with_id(TimeZoneBcp47Id(tinystr!(8, "uschi")));
2771+
///
2772+
/// let formatter = TypedNeoFormatter::try_new(
2773+
/// &locale!("en-US").into(),
2774+
/// NeoTimeZoneGenericMarker::with_length(NeoSkeletonLength::Medium),
2775+
/// )
2776+
/// .unwrap();
2777+
///
2778+
/// // error[E0271]: type mismatch resolving `<Base as TimeZoneModel>::LocalTime == (Date<Iso>, Time)`
2779+
/// // note: required by a bound in `TypedNeoFormatter::<C, FSet>::format`
2780+
/// formatter.format(&time_zone_basic);
2781+
/// ```
25912782
NeoTimeZoneGenericMarker,
25922783
NeoTimeZoneStyle::Generic,
25932784
description = "generic time zone, or location if unavailable",
@@ -2598,6 +2789,7 @@ impl_zone_marker!(
25982789
zone_generic_long = yes,
25992790
zone_generic_short = yes,
26002791
metazone_periods = yes,
2792+
input_tzid = yes,
26012793
input_localtime = yes,
26022794
);
26032795

@@ -2655,6 +2847,32 @@ impl_zone_marker!(
26552847
///
26562848
/// assert!(matches!(result, Err(LoadError::TypeTooNarrow(_))));
26572849
/// ```
2850+
///
2851+
/// A time zone requires a reference time to be formatted with this style.
2852+
///
2853+
/// ```compile_fail
2854+
/// use icu::calendar::{DateTime, Iso};
2855+
/// use icu::datetime::neo::TypedNeoFormatter;
2856+
/// use icu::datetime::neo_marker::NeoTimeZoneGenericShortMarker;
2857+
/// use icu::datetime::neo_skeleton::NeoSkeletonLength;
2858+
/// use icu::timezone::{TimeZoneBcp47Id, UtcOffset};
2859+
/// use tinystr::tinystr;
2860+
/// use icu::locale::locale;
2861+
/// use writeable::assert_try_writeable_eq;
2862+
///
2863+
/// let utc_offset = UtcOffset::from_eighths_of_hour(-6 * 8);
2864+
/// let time_zone_basic = utc_offset.with_id(TimeZoneBcp47Id(tinystr!(8, "uschi")));
2865+
///
2866+
/// let formatter = TypedNeoFormatter::try_new(
2867+
/// &locale!("en-US").into(),
2868+
/// NeoTimeZoneGenericShortMarker::with_length(NeoSkeletonLength::Medium),
2869+
/// )
2870+
/// .unwrap();
2871+
///
2872+
/// // error[E0271]: type mismatch resolving `<Base as TimeZoneModel>::LocalTime == (Date<Iso>, Time)`
2873+
/// // note: required by a bound in `TypedNeoFormatter::<C, FSet>::format`
2874+
/// formatter.format(&time_zone_basic);
2875+
/// ```
26582876
NeoTimeZoneGenericShortMarker,
26592877
NeoTimeZoneStyle::Generic,
26602878
description = "generic time zone (only short), or location if unavailable",
@@ -2664,17 +2882,44 @@ impl_zone_marker!(
26642882
zone_locations = yes,
26652883
zone_generic_short = yes,
26662884
metazone_periods = yes,
2885+
input_tzid = yes,
26672886
input_localtime = yes,
26682887
);
26692888

26702889
impl_zone_marker!(
2890+
/// A time zone requires a time zone ID to be formatted with this style.
2891+
/// For example, a raw [`UtcOffset`] cannot be used here.
2892+
///
2893+
/// ```compile_fail
2894+
/// use icu::calendar::{DateTime, Iso};
2895+
/// use icu::datetime::neo::TypedNeoFormatter;
2896+
/// use icu::datetime::neo_marker::NeoTimeZoneLocationMarker;
2897+
/// use icu::datetime::neo_skeleton::NeoSkeletonLength;
2898+
/// use icu::timezone::UtcOffset;
2899+
/// use tinystr::tinystr;
2900+
/// use icu::locale::locale;
2901+
/// use writeable::assert_try_writeable_eq;
2902+
///
2903+
/// let utc_offset = UtcOffset::from_eighths_of_hour(-6 * 8);
2904+
///
2905+
/// let formatter = TypedNeoFormatter::try_new(
2906+
/// &locale!("en-US").into(),
2907+
/// NeoTimeZoneLocationMarker::with_length(NeoSkeletonLength::Medium),
2908+
/// )
2909+
/// .unwrap();
2910+
///
2911+
/// // error[E0277]: the trait bound `UtcOffset: AllInputMarkers<NeoTimeZoneLocationMarker>` is not satisfied
2912+
/// // note: required by a bound in `TypedNeoFormatter::<C, FSet>::format`
2913+
/// formatter.format(&utc_offset);
2914+
/// ```
26712915
NeoTimeZoneLocationMarker,
26722916
NeoTimeZoneStyle::Location,
26732917
description = "location time zone",
26742918
sample_length = Long,
26752919
sample = "Chicago Time",
26762920
zone_essentials = yes,
26772921
zone_locations = yes,
2922+
input_tzid = yes,
26782923
);
26792924

26802925
// TODO: Type aliases like this are excessive; make a curated set

0 commit comments

Comments
 (0)