• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

zbraniecki / icu4x / 13958601093

19 Mar 2025 04:17PM UTC coverage: 74.164% (-1.5%) from 75.71%
13958601093

push

github

web-flow
Clean up properties docs (#6315)

58056 of 78281 relevant lines covered (74.16%)

819371.32 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

74.92
/components/datetime/src/format/datetime.rs
1
// This file is part of ICU4X. For terms of use, please see the file
2
// called LICENSE at the top level of the ICU4X source tree
3
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
4

5
use super::time_zone::{FormatTimeZone, FormatTimeZoneError, Iso8601Format, TimeZoneFormatterUnit};
6
use crate::error::{DateTimeWriteError, ErrorField};
7
use crate::format::DateTimeInputUnchecked;
8
use crate::provider::fields::{self, FieldLength, FieldSymbol, Second, Year};
9
use crate::provider::pattern::runtime::PatternMetadata;
10
use crate::provider::pattern::PatternItem;
11
use crate::{parts, pattern::*};
12

13
use core::fmt::{self, Write};
14
use fixed_decimal::Decimal;
15
use icu_calendar::types::{DayOfWeekInMonth, Weekday};
16
use icu_decimal::DecimalFormatter;
17
use writeable::{Part, PartsWrite, Writeable};
18

19
/// Apply length to input number and write to result using decimal_formatter.
20
fn try_write_number<W>(
2,954✔
21
    part: Part,
22
    w: &mut W,
23
    decimal_formatter: Option<&DecimalFormatter>,
24
    mut num: Decimal,
25
    length: FieldLength,
26
) -> Result<Result<(), DateTimeWriteError>, fmt::Error>
27
where
28
    W: writeable::PartsWrite + ?Sized,
29
{
30
    num.pad_start(length.to_len() as i16);
2,954✔
31

32
    if let Some(fdf) = decimal_formatter {
5,892✔
33
        w.with_part(part, |w| fdf.format(&num).write_to_parts(w))?;
5,888✔
34
        Ok(Ok(()))
2,943✔
35
    } else {
36
        w.with_part(part, |w| {
2,960✔
37
            w.with_part(writeable::Part::ERROR, |r| num.write_to_parts(r))
6✔
38
        })?;
3✔
39
        Ok(Err(DateTimeWriteError::DecimalFormatterNotLoaded))
3✔
40
    }
41
}
2,946✔
42

43
/// Apply length to input number and write to result using decimal_formatter.
44
/// Don't annotate it with a part.
45
fn try_write_number_without_part<W>(
20✔
46
    w: &mut W,
47
    decimal_formatter: Option<&DecimalFormatter>,
48
    mut num: Decimal,
49
    length: FieldLength,
50
) -> Result<Result<(), DateTimeWriteError>, fmt::Error>
51
where
52
    W: writeable::PartsWrite + ?Sized,
53
{
54
    num.pad_start(length.to_len() as i16);
20✔
55

56
    if let Some(fdf) = decimal_formatter {
40✔
57
        fdf.format(&num).write_to(w)?;
20✔
58
        Ok(Ok(()))
20✔
59
    } else {
60
        w.with_part(writeable::Part::ERROR, |r| num.write_to(r))?;
20✔
61
        Ok(Err(DateTimeWriteError::DecimalFormatterNotLoaded))
×
62
    }
63
}
20✔
64

65
#[allow(clippy::too_many_arguments)]
66
pub(crate) fn try_write_pattern_items<W>(
1,592✔
67
    pattern_metadata: PatternMetadata,
68
    pattern_items: impl Iterator<Item = PatternItem>,
69
    input: &DateTimeInputUnchecked,
70
    datetime_names: &RawDateTimeNamesBorrowed,
71
    decimal_formatter: Option<&DecimalFormatter>,
72
    w: &mut W,
73
) -> Result<Result<(), DateTimeWriteError>, fmt::Error>
74
where
75
    W: writeable::PartsWrite + ?Sized,
76
{
77
    let mut r = Ok(());
1,592✔
78
    for item in pattern_items {
11,428✔
79
        match item {
9,838✔
80
            PatternItem::Literal(ch) => w.write_char(ch)?,
4,828✔
81
            PatternItem::Field(field) => {
5,010✔
82
                r = r.and(try_write_field(
5,010✔
83
                    field,
84
                    pattern_metadata,
85
                    input,
86
                    datetime_names,
87
                    decimal_formatter,
88
                    w,
89
                )?);
1,592✔
90
            }
5,008✔
91
        }
92
    }
×
93
    Ok(r)
1,572✔
94
}
1,572✔
95

96
// This function assumes that the correct decision has been
97
// made regarding availability of symbols in the caller.
98
//
99
// When modifying the list of fields using symbols,
100
// update the matching query in `analyze_pattern` function.
101
fn try_write_field<W>(
5,129✔
102
    field: fields::Field,
103
    pattern_metadata: PatternMetadata,
104
    input: &DateTimeInputUnchecked,
105
    datetime_names: &RawDateTimeNamesBorrowed,
106
    decimal_formatter: Option<&DecimalFormatter>,
107
    w: &mut W,
108
) -> Result<Result<(), DateTimeWriteError>, fmt::Error>
109
where
110
    W: writeable::PartsWrite + ?Sized,
111
{
112
    macro_rules! input {
113
        // Get the input. If not found, write a replacement string but do NOT write a part.
114
        (_, $name:ident = $input:expr) => {
115
            let Some($name) = $input else {
×
116
                write_value_missing(w, field)?;
×
117
                return Ok(Err(DateTimeWriteError::MissingInputField(stringify!(
×
118
                    $name
119
                ))));
120
            };
121
        };
122
        // Get the input. If not found, write a replacement string and a part.
123
        ($part:ident, $name:ident = $input:expr) => {
124
            let Some($name) = $input else {
4,603✔
125
                w.with_part($part, |w| write_value_missing(w, field))?;
34✔
126
                return Ok(Err(DateTimeWriteError::MissingInputField(stringify!(
17✔
127
                    $name
128
                ))));
129
            };
130
        };
131
    }
132

133
    Ok(match (field.symbol, field.length) {
10,241✔
134
        (FieldSymbol::Era, l) => {
313✔
135
            const PART: Part = parts::ERA;
136
            input!(PART, year = input.year);
313✔
137
            input!(PART, era = year.formatting_era());
310✔
138
            let era_symbol = datetime_names
310✔
139
                .get_name_for_era(l, era)
140
                .map_err(|e| match e {
1✔
141
                    GetNameForEraError::InvalidEraCode => DateTimeWriteError::InvalidEra(era),
×
142
                    GetNameForEraError::InvalidFieldLength => {
143
                        DateTimeWriteError::UnsupportedLength(ErrorField(field))
×
144
                    }
145
                    GetNameForEraError::NotLoaded => {
146
                        DateTimeWriteError::NamesNotLoaded(ErrorField(field))
1✔
147
                    }
148
                });
1✔
149
            match era_symbol {
310✔
150
                Err(e) => {
1✔
151
                    w.with_part(PART, |w| {
2✔
152
                        w.with_part(Part::ERROR, |w| w.write_str(&era.fallback_name()))
2✔
153
                    })?;
1✔
154
                    Err(e)
1✔
155
                }
1✔
156
                Ok(era) => Ok(w.with_part(PART, |w| w.write_str(era))?),
618✔
157
            }
158
        }
159
        (FieldSymbol::Year(Year::Calendar), l) => {
612✔
160
            const PART: Part = parts::YEAR;
161
            input!(PART, year = input.year);
612✔
162
            let mut year = Decimal::from(year.era_year_or_extended());
609✔
163
            if matches!(l, FieldLength::Two) {
609✔
164
                // 'yy' and 'YY' truncate
165
                year.set_max_position(2);
48✔
166
            }
167
            try_write_number(PART, w, decimal_formatter, year, l)?
609✔
168
        }
609✔
169
        (FieldSymbol::Year(Year::Cyclic), l) => {
42✔
170
            const PART: Part = parts::YEAR_NAME;
171
            input!(PART, year = input.year);
42✔
172
            input!(PART, cyclic = year.cyclic());
42✔
173

174
            match datetime_names.get_name_for_cyclic(l, cyclic) {
42✔
175
                Ok(name) => Ok(w.with_part(PART, |w| w.write_str(name))?),
84✔
176
                Err(e) => {
×
177
                    w.with_part(PART, |w| {
×
178
                        w.with_part(Part::ERROR, |w| {
×
179
                            try_write_number_without_part(
×
180
                                w,
181
                                decimal_formatter,
×
182
                                year.era_year_or_extended().into(),
×
183
                                FieldLength::One,
×
184
                            )
185
                            .map(|_| ())
×
186
                        })
×
187
                    })?;
×
188
                    return Ok(Err(match e {
×
189
                        GetNameForCyclicYearError::InvalidYearNumber { max } => {
×
190
                            DateTimeWriteError::InvalidCyclicYear {
×
191
                                value: cyclic.get() as usize,
×
192
                                max,
193
                            }
194
                        }
×
195
                        GetNameForCyclicYearError::InvalidFieldLength => {
196
                            DateTimeWriteError::UnsupportedLength(ErrorField(field))
×
197
                        }
198
                        GetNameForCyclicYearError::NotLoaded => {
199
                            DateTimeWriteError::NamesNotLoaded(ErrorField(field))
×
200
                        }
201
                    }));
202
                }
203
            }
204
        }
42✔
205
        (FieldSymbol::Year(Year::RelatedIso), l) => {
42✔
206
            const PART: Part = parts::RELATED_YEAR;
207
            input!(PART, year = input.year);
42✔
208
            input!(PART, related_iso = year.related_iso());
42✔
209

210
            // Always in latin digits according to spec
211
            w.with_part(PART, |w| {
84✔
212
                let mut num = Decimal::from(related_iso);
42✔
213
                num.pad_start(l.to_len() as i16);
42✔
214
                num.write_to(w)
42✔
215
            })?;
42✔
216
            Ok(())
42✔
217
        }
42✔
218
        (FieldSymbol::Month(_), l @ (FieldLength::One | FieldLength::Two)) => {
192✔
219
            const PART: Part = parts::MONTH;
220
            input!(PART, month = input.month);
192✔
221
            try_write_number(PART, w, decimal_formatter, month.ordinal.into(), l)?
192✔
222
        }
192✔
223
        (FieldSymbol::Month(symbol), l) => {
482✔
224
            const PART: Part = parts::MONTH;
225
            input!(PART, month = input.month);
482✔
226
            match datetime_names.get_name_for_month(symbol, l, month.formatting_code) {
479✔
227
                Ok(MonthPlaceholderValue::PlainString(symbol)) => {
478✔
228
                    w.with_part(PART, |w| w.write_str(symbol))?;
956✔
229
                    Ok(())
478✔
230
                }
478✔
231
                Ok(MonthPlaceholderValue::Numeric) => {
232
                    debug_assert!(l == FieldLength::One);
×
233
                    try_write_number(PART, w, decimal_formatter, month.ordinal.into(), l)?
×
234
                }
×
235
                Ok(MonthPlaceholderValue::NumericPattern(substitution_pattern)) => {
×
236
                    debug_assert!(l == FieldLength::One);
×
237
                    if let Some(formatter) = decimal_formatter {
×
238
                        let mut num = Decimal::from(month.ordinal);
×
239
                        num.pad_start(l.to_len() as i16);
×
240
                        w.with_part(PART, |w| {
×
241
                            substitution_pattern
×
242
                                .interpolate([formatter.format(&num)])
×
243
                                .write_to(w)
244
                        })?;
×
245
                        Ok(())
×
246
                    } else {
×
247
                        w.with_part(PART, |w| {
×
248
                            w.with_part(Part::ERROR, |w| {
×
249
                                substitution_pattern
×
250
                                    .interpolate([{
×
251
                                        let mut num = Decimal::from(month.ordinal);
×
252
                                        num.pad_start(l.to_len() as i16);
×
253
                                        num
×
254
                                    }])
×
255
                                    .write_to(w)
256
                            })
×
257
                        })?;
×
258
                        Err(DateTimeWriteError::DecimalFormatterNotLoaded)
×
259
                    }
260
                }
261
                Err(e) => {
1✔
262
                    w.with_part(PART, |w| {
2✔
263
                        w.with_part(Part::ERROR, |w| w.write_str(&month.formatting_code.0))
2✔
264
                    })?;
1✔
265
                    Err(match e {
2✔
266
                        GetNameForMonthError::InvalidMonthCode => {
267
                            DateTimeWriteError::InvalidMonthCode(month.formatting_code)
×
268
                        }
269
                        GetNameForMonthError::InvalidFieldLength => {
270
                            DateTimeWriteError::UnsupportedLength(ErrorField(field))
×
271
                        }
272
                        GetNameForMonthError::NotLoaded => {
273
                            DateTimeWriteError::NamesNotLoaded(ErrorField(field))
1✔
274
                        }
275
                    })
276
                }
1✔
277
            }
278
        }
279
        (FieldSymbol::Week(w), _) => match w {},
280
        (FieldSymbol::Weekday(weekday), l) => {
393✔
281
            const PART: Part = parts::WEEKDAY;
282
            input!(PART, iso_weekday = input.iso_weekday);
393✔
283
            match datetime_names
392✔
284
                .get_name_for_weekday(weekday, l, iso_weekday)
285
                .map_err(|e| match e {
1✔
286
                    GetNameForWeekdayError::InvalidFieldLength => {
287
                        DateTimeWriteError::UnsupportedLength(ErrorField(field))
×
288
                    }
289
                    GetNameForWeekdayError::NotLoaded => {
290
                        DateTimeWriteError::NamesNotLoaded(ErrorField(field))
1✔
291
                    }
292
                }) {
1✔
293
                Err(e) => {
1✔
294
                    w.with_part(PART, |w| {
2✔
295
                        w.with_part(Part::ERROR, |w| {
2✔
296
                            w.write_str(match iso_weekday {
2✔
297
                                Weekday::Monday => "mon",
1✔
298
                                Weekday::Tuesday => "tue",
×
299
                                Weekday::Wednesday => "wed",
×
300
                                Weekday::Thursday => "thu",
×
301
                                Weekday::Friday => "fri",
×
302
                                Weekday::Saturday => "sat",
×
303
                                Weekday::Sunday => "sun",
×
304
                            })
305
                        })
1✔
306
                    })?;
1✔
307
                    Err(e)
1✔
308
                }
1✔
309
                Ok(s) => Ok(w.with_part(PART, |w| w.write_str(s))?),
782✔
310
            }
311
        }
312
        (FieldSymbol::Day(fields::Day::DayOfMonth), l) => {
652✔
313
            const PART: Part = parts::DAY;
314
            input!(PART, day_of_month = input.day_of_month);
652✔
315
            try_write_number(PART, w, decimal_formatter, day_of_month.0.into(), l)?
649✔
316
        }
649✔
317
        (FieldSymbol::Day(fields::Day::DayOfWeekInMonth), l) => {
×
318
            input!(_, day_of_month = input.day_of_month);
×
319
            try_write_number_without_part(
×
320
                w,
321
                decimal_formatter,
322
                DayOfWeekInMonth::from(day_of_month).0.into(),
×
323
                l,
324
            )?
325
        }
×
326
        (FieldSymbol::Day(fields::Day::DayOfYear), l) => {
×
327
            input!(_, day_of_year = input.day_of_year);
×
328
            try_write_number_without_part(w, decimal_formatter, day_of_year.day_of_year.into(), l)?
×
329
        }
×
330
        (FieldSymbol::Hour(symbol), l) => {
602✔
331
            const PART: Part = parts::HOUR;
332
            input!(PART, hour = input.hour);
602✔
333
            let h = hour.number();
601✔
334
            let h = match symbol {
601✔
335
                fields::Hour::H11 => h % 12,
21✔
336
                fields::Hour::H12 => {
337
                    let v = h % 12;
241✔
338
                    if v == 0 {
241✔
339
                        12
64✔
340
                    } else {
341
                        v
177✔
342
                    }
343
                }
344
                fields::Hour::H23 => h,
319✔
345
                fields::Hour::H24 => {
346
                    if h == 0 {
20✔
347
                        24
20✔
348
                    } else {
349
                        h
×
350
                    }
351
                }
352
            };
353
            try_write_number(PART, w, decimal_formatter, h.into(), l)?
601✔
354
        }
601✔
355
        (FieldSymbol::Minute, l) => {
540✔
356
            const PART: Part = parts::MINUTE;
357
            input!(PART, minute = input.minute);
540✔
358
            try_write_number(PART, w, decimal_formatter, minute.number().into(), l)?
539✔
359
        }
539✔
360
        (FieldSymbol::Second(Second::Second), l) => {
342✔
361
            const PART: Part = parts::SECOND;
362
            input!(PART, second = input.second);
342✔
363
            try_write_number(PART, w, decimal_formatter, second.number().into(), l)?
342✔
364
        }
342✔
365
        (FieldSymbol::Second(Second::MillisInDay), l) => {
×
366
            input!(_, hour = input.hour);
×
367
            input!(_, minute = input.minute);
×
368
            input!(_, second = input.second);
×
369
            input!(_, subsecond = input.subsecond);
×
370

371
            let milliseconds = (((hour.number() as u32 * 60) + minute.number() as u32) * 60
×
372
                + second.number() as u32)
×
373
                * 1000
374
                + subsecond.number() / 1_000_000;
×
375
            try_write_number_without_part(w, decimal_formatter, milliseconds.into(), l)?
×
376
        }
×
377
        (FieldSymbol::DecimalSecond(decimal_second), l) => {
18✔
378
            const PART: Part = parts::SECOND;
379
            input!(PART, second = input.second);
18✔
380
            input!(PART, subsecond = input.subsecond);
17✔
381

382
            // Formatting with fractional seconds
383
            let mut s = Decimal::from(second.number());
17✔
384
            let _infallible = s.concatenate_end(
34✔
385
                Decimal::from(subsecond.number())
17✔
386
                    .absolute
387
                    .multiplied_pow10(-9),
388
            );
389
            debug_assert!(_infallible.is_ok());
17✔
390
            let position = -(decimal_second as i16);
17✔
391
            s.trunc(position);
17✔
392
            s.pad_end(position);
17✔
393
            try_write_number(PART, w, decimal_formatter, s, l)?
17✔
394
        }
17✔
395
        (FieldSymbol::DayPeriod(period), l) => {
373✔
396
            const PART: Part = parts::DAY_PERIOD;
397
            input!(PART, hour = input.hour);
373✔
398

399
            match datetime_names.get_name_for_day_period(
372✔
400
                period,
401
                l,
402
                hour,
403
                pattern_metadata.time_granularity().is_top_of_hour(
744✔
404
                    input.minute.unwrap_or_default().number(),
372✔
405
                    input.second.unwrap_or_default().number(),
372✔
406
                    input.subsecond.unwrap_or_default().number(),
372✔
407
                ),
408
            ) {
409
                Err(e) => {
1✔
410
                    w.with_part(PART, |w| {
2✔
411
                        w.with_part(Part::ERROR, |w| {
2✔
412
                            w.write_str(if hour.number() < 12 { "AM" } else { "PM" })
1✔
413
                        })
1✔
414
                    })?;
1✔
415
                    Err(match e {
2✔
416
                        GetNameForDayPeriodError::InvalidFieldLength => {
417
                            DateTimeWriteError::UnsupportedLength(ErrorField(field))
×
418
                        }
419
                        GetNameForDayPeriodError::NotLoaded => {
420
                            DateTimeWriteError::NamesNotLoaded(ErrorField(field))
1✔
421
                        }
422
                    })
423
                }
1✔
424
                Ok(s) => Ok(w.with_part(PART, |w| w.write_str(s))?),
742✔
425
            }
426
        }
427
        (FieldSymbol::TimeZone(fields::TimeZone::SpecificNonLocation), FieldLength::Four) => {
428
            perform_timezone_fallback(
62✔
429
                w,
430
                input,
431
                datetime_names,
432
                decimal_formatter,
433
                field,
434
                &[
435
                    TimeZoneFormatterUnit::SpecificNonLocation(FieldLength::Four),
436
                    TimeZoneFormatterUnit::SpecificLocation,
437
                    TimeZoneFormatterUnit::LocalizedOffset(FieldLength::Four),
438
                ],
439
            )?
440
        }
62✔
441
        (FieldSymbol::TimeZone(fields::TimeZone::SpecificNonLocation), l) => {
62✔
442
            perform_timezone_fallback(
62✔
443
                w,
444
                input,
445
                datetime_names,
446
                decimal_formatter,
447
                field,
448
                &[
62✔
449
                    TimeZoneFormatterUnit::SpecificNonLocation(l),
62✔
450
                    TimeZoneFormatterUnit::LocalizedOffset(l),
62✔
451
                ],
452
            )?
453
        }
62✔
454
        (FieldSymbol::TimeZone(fields::TimeZone::GenericNonLocation), l) => {
130✔
455
            perform_timezone_fallback(
130✔
456
                w,
457
                input,
458
                datetime_names,
459
                decimal_formatter,
460
                field,
461
                &[
130✔
462
                    TimeZoneFormatterUnit::GenericNonLocation(l),
130✔
463
                    TimeZoneFormatterUnit::GenericLocation,
130✔
464
                    TimeZoneFormatterUnit::LocalizedOffset(l),
130✔
465
                ],
466
            )?
467
        }
130✔
468
        (FieldSymbol::TimeZone(fields::TimeZone::Location), FieldLength::Four) => {
469
            perform_timezone_fallback(
59✔
470
                w,
471
                input,
472
                datetime_names,
473
                decimal_formatter,
474
                field,
475
                &[
476
                    TimeZoneFormatterUnit::GenericLocation,
477
                    TimeZoneFormatterUnit::LocalizedOffset(FieldLength::Four),
478
                ],
479
            )?
480
        }
59✔
481
        (FieldSymbol::TimeZone(fields::TimeZone::Location), FieldLength::Three) => {
482
            perform_timezone_fallback(
29✔
483
                w,
484
                input,
485
                datetime_names,
486
                decimal_formatter,
487
                field,
488
                &[TimeZoneFormatterUnit::ExemplarCity],
489
            )?
490
        }
29✔
491
        (FieldSymbol::TimeZone(fields::TimeZone::LocalizedOffset), l) => perform_timezone_fallback(
264✔
492
            w,
493
            input,
494
            datetime_names,
495
            decimal_formatter,
496
            field,
497
            &[TimeZoneFormatterUnit::LocalizedOffset(l)],
132✔
498
        )?,
132✔
499
        (FieldSymbol::TimeZone(fields::TimeZone::Location), _) => perform_timezone_fallback(
1✔
500
            w,
501
            input,
502
            datetime_names,
503
            decimal_formatter,
504
            field,
505
            &[TimeZoneFormatterUnit::Bcp47Id],
506
        )?,
1✔
507
        (FieldSymbol::TimeZone(fields::TimeZone::IsoWithZ), l) => perform_timezone_fallback(
52✔
508
            w,
509
            input,
510
            datetime_names,
511
            decimal_formatter,
512
            field,
513
            &[TimeZoneFormatterUnit::Iso8601(Iso8601Format::with_z(l))],
26✔
514
        )?,
26✔
515
        (FieldSymbol::TimeZone(fields::TimeZone::Iso), l) => perform_timezone_fallback(
50✔
516
            w,
517
            input,
518
            datetime_names,
519
            decimal_formatter,
520
            field,
521
            &[TimeZoneFormatterUnit::Iso8601(Iso8601Format::without_z(l))],
25✔
522
        )?,
5,154✔
523
    })
524
}
5,129✔
525

526
// Writes an error string for the given symbol
527
fn write_value_missing(
18✔
528
    w: &mut (impl writeable::PartsWrite + ?Sized),
529
    field: fields::Field,
530
) -> Result<(), fmt::Error> {
531
    w.with_part(Part::ERROR, |w| {
36✔
532
        "{".write_to(w)?;
18✔
533
        char::from(field.symbol).write_to(w)?;
18✔
534
        "}".write_to(w)
18✔
535
    })
18✔
536
}
18✔
537

538
fn perform_timezone_fallback(
526✔
539
    w: &mut (impl writeable::PartsWrite + ?Sized),
540
    input: &DateTimeInputUnchecked,
541
    datetime_names: &RawDateTimeNamesBorrowed,
542
    fdf: Option<&DecimalFormatter>,
543
    field: fields::Field,
544
    units: &[TimeZoneFormatterUnit],
545
) -> Result<Result<(), DateTimeWriteError>, core::fmt::Error> {
546
    const PART: Part = parts::TIME_ZONE_NAME;
547
    let payloads = datetime_names.get_payloads();
526✔
548
    let mut r = Err(FormatTimeZoneError::Fallback);
526✔
549
    for unit in units {
720✔
550
        let mut inner_result = None;
720✔
551
        w.with_part(PART, |w| {
1,440✔
552
            inner_result = Some(unit.format(w, input, payloads, fdf)?);
720✔
553
            Ok(())
720✔
554
        })?;
720✔
555
        match inner_result {
720✔
556
            Some(Err(FormatTimeZoneError::Fallback)) => {
557
                // Expected, try the next unit
558
                continue;
559
            }
560
            Some(r2) => {
526✔
561
                r = r2;
526✔
562
                break;
563
            }
564
            None => {
565
                debug_assert!(false, "unreachable");
×
566
                return Err(fmt::Error);
567
            }
568
        }
569
    }
570

571
    Ok(match r {
1,052✔
572
        Ok(()) => Ok(()),
524✔
573
        Err(e) => {
2✔
574
            if let Some(offset) = input.offset {
2✔
575
                w.with_part(PART, |w| {
2✔
576
                    w.with_part(Part::ERROR, |w| {
2✔
577
                        Iso8601Format::without_z(field.length).format_infallible(w, offset)
1✔
578
                    })
1✔
579
                })?;
1✔
580
            } else {
581
                w.with_part(PART, |w| write_value_missing(w, field))?;
528✔
582
            }
583
            match e {
2✔
584
                FormatTimeZoneError::DecimalFormatterNotLoaded => {
585
                    Err(DateTimeWriteError::DecimalFormatterNotLoaded)
×
586
                }
587
                FormatTimeZoneError::NamesNotLoaded => {
588
                    Err(DateTimeWriteError::NamesNotLoaded(ErrorField(field)))
1✔
589
                }
590
                FormatTimeZoneError::MissingInputField(f) => {
1✔
591
                    Err(DateTimeWriteError::MissingInputField(f))
1✔
592
                }
1✔
593
                FormatTimeZoneError::Fallback => {
594
                    debug_assert!(false, "timezone fallback chain fell through {input:?}");
×
595
                    Ok(())
596
                }
597
            }
598
        }
599
    })
600
}
526✔
601

602
#[cfg(test)]
603
#[allow(unused_imports)]
604
#[cfg(feature = "compiled_data")]
605
mod tests {
606
    use super::*;
607
    use icu_decimal::options::{DecimalFormatterOptions, GroupingStrategy};
608

609
    #[test]
610
    fn test_format_number() {
2✔
611
        let values = &[2, 20, 201, 2017, 20173];
1✔
612
        let samples = &[
1✔
613
            (FieldLength::One, ["2", "20", "201", "2017", "20173"]),
614
            (FieldLength::Two, ["02", "20", "201", "2017", "20173"]),
615
            (FieldLength::Three, ["002", "020", "201", "2017", "20173"]),
616
            (FieldLength::Four, ["0002", "0020", "0201", "2017", "20173"]),
617
        ];
618

619
        let mut decimal_formatter_options = DecimalFormatterOptions::default();
1✔
620
        decimal_formatter_options.grouping_strategy = Some(GroupingStrategy::Never);
1✔
621
        let decimal_formatter = DecimalFormatter::try_new(
1✔
622
            icu_locale_core::locale!("en").into(),
1✔
623
            decimal_formatter_options,
1✔
624
        )
625
        .unwrap();
626

627
        for (length, expected) in samples {
5✔
628
            for (value, expected) in values.iter().zip(expected) {
4✔
629
                let mut s = String::new();
20✔
630
                try_write_number_without_part(
20✔
631
                    &mut writeable::adapters::CoreWriteAsPartsWrite(&mut s),
20✔
632
                    Some(&decimal_formatter),
20✔
633
                    Decimal::from(*value),
20✔
634
                    *length,
20✔
635
                )
636
                .unwrap()
637
                .unwrap();
638
                assert_eq!(s, *expected);
20✔
639
            }
20✔
640
        }
641
    }
32✔
642
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc