• 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

91.43
/components/datetime/src/provider/packed_pattern.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
//! Data structures for packing of datetime patterns.
6

7
use super::pattern::{
8
    runtime::{Pattern, PatternBorrowed, PatternMetadata},
9
    PatternItem,
10
};
11
use crate::{size_test_macro::size_test, Length};
12
use alloc::vec::Vec;
13
use icu_plurals::{
14
    provider::{FourBitMetadata, PluralElementsPackedULE},
15
    PluralElements,
16
};
17
use icu_provider::prelude::*;
18
use zerovec::{VarZeroVec, ZeroSlice};
19

20
/// A field of [`PackedPatternsBuilder`].
21
#[derive(Debug, Clone, PartialEq, Eq)]
10,690✔
22
pub struct LengthPluralElements<T> {
23
    /// The "long" length pattern plural elements.
24
    pub long: PluralElements<T>,
5,345✔
25
    /// The "medium" length pattern plural elements.
26
    pub medium: PluralElements<T>,
4,632✔
27
    /// The "short" length pattern plural elements.
28
    pub short: PluralElements<T>,
5,345✔
29
}
30

31
/// A builder for a [`PackedPatterns`].
32
#[derive(Debug, Clone, PartialEq, Eq)]
32✔
33
pub struct PackedPatternsBuilder<'a> {
34
    /// Patterns always available.
35
    pub standard: LengthPluralElements<Pattern<'a>>,
16✔
36
    /// Patterns for variant 0. If `None`, falls back to standard.
37
    pub variant0: Option<LengthPluralElements<Pattern<'a>>>,
16✔
38
    /// Patterns for variant 1. If `None`, falls back to standard.
39
    pub variant1: Option<LengthPluralElements<Pattern<'a>>>,
16✔
40
}
41

42
size_test!(PackedPatterns, packed_skeleton_data_size, 32);
43

44
icu_provider::data_marker!(
45
    /// `BuddhistDateNeoSkeletonPatternsV1`
46
    BuddhistDateNeoSkeletonPatternsV1,
47
    PackedPatterns<'static>
48
);
49
icu_provider::data_marker!(
50
    /// `ChineseDateNeoSkeletonPatternsV1`
51
    ChineseDateNeoSkeletonPatternsV1,
52
    PackedPatterns<'static>
53
);
54
icu_provider::data_marker!(
55
    /// `CopticDateNeoSkeletonPatternsV1`
56
    CopticDateNeoSkeletonPatternsV1,
57
    PackedPatterns<'static>
58
);
59
icu_provider::data_marker!(
60
    /// `DangiDateNeoSkeletonPatternsV1`
61
    DangiDateNeoSkeletonPatternsV1,
62
    PackedPatterns<'static>
63
);
64
icu_provider::data_marker!(
65
    /// `EthiopianDateNeoSkeletonPatternsV1`
66
    EthiopianDateNeoSkeletonPatternsV1,
67
    PackedPatterns<'static>
68
);
69
icu_provider::data_marker!(
70
    /// `GregorianDateNeoSkeletonPatternsV1`
71
    GregorianDateNeoSkeletonPatternsV1,
72
    PackedPatterns<'static>
73
);
74
icu_provider::data_marker!(
75
    /// `HebrewDateNeoSkeletonPatternsV1`
76
    HebrewDateNeoSkeletonPatternsV1,
77
    PackedPatterns<'static>
78
);
79
icu_provider::data_marker!(
80
    /// `IndianDateNeoSkeletonPatternsV1`
81
    IndianDateNeoSkeletonPatternsV1,
82
    PackedPatterns<'static>
83
);
84
icu_provider::data_marker!(
85
    /// `HijriDateNeoSkeletonPatternsV1`
86
    HijriDateNeoSkeletonPatternsV1,
87
    PackedPatterns<'static>
88
);
89
icu_provider::data_marker!(
90
    /// `JapaneseDateNeoSkeletonPatternsV1`
91
    JapaneseDateNeoSkeletonPatternsV1,
92
    PackedPatterns<'static>
93
);
94
icu_provider::data_marker!(
95
    /// `JapaneseExtendedDateNeoSkeletonPatternsV1`
96
    JapaneseExtendedDateNeoSkeletonPatternsV1,
97
    PackedPatterns<'static>
98
);
99
icu_provider::data_marker!(
100
    /// `PersianDateNeoSkeletonPatternsV1`
101
    PersianDateNeoSkeletonPatternsV1,
102
    PackedPatterns<'static>
103
);
104
icu_provider::data_marker!(
105
    /// `RocDateNeoSkeletonPatternsV1`
106
    RocDateNeoSkeletonPatternsV1,
107
    PackedPatterns<'static>
108
);
109
icu_provider::data_marker!(
110
    /// `TimeNeoSkeletonPatternsV1`
111
    TimeNeoSkeletonPatternsV1,
112
    PackedPatterns<'static>
113
);
114

115
// Main data struct for packed datetime patterns.
116
#[doc = packed_skeleton_data_size!()]
117
///
118
/// ## Variants
119
///
120
/// This supports a set of "standard" patterns plus up to two "variants".
121
/// The variants are currently used by year formatting:
122
///
123
/// - Standard: Year, which could be partial precision (2-digit Gregorian)
124
/// - Variant 0: Full Year, which is always full precision
125
/// - Variant 1: Year With Era
126
///
127
/// And by time formatting:
128
///
129
/// - Standard: Hour only
130
/// - Variant 0: Hour and minute
131
/// - Variant 1: Hour, minute, and second
132
///
133
/// Variants should be used when the pattern could depend on the value being
134
/// formatted. For example, with [`YearStyle::Auto`], any of these three
135
/// patterns could be selected based on the year value.
136
///
137
/// ## Representation
138
///
139
/// Currently, there are at most 9 patterns that need to be stored together,
140
/// named according to this table:
141
///
142
/// |        | Standard | Variant 0 | Variant 1 |
143
/// |--------|----------|-----------|-----------|
144
/// | Long   | La       | Lb        | Lc        |
145
/// | Medium | Ma       | Mb        | Mc        |
146
/// | Short  | Sa       | Sb        | Sc        |
147
///
148
/// The header byte encodes which pattern in the patterns array corresponds to
149
/// a particular cell in the table. It contains the following information:
150
///
151
/// - Bits 0-1: "LMS" value of the standard column
152
/// - Bit 2: "Q" value: 1 for directly-indexed variants; 0 for per-cell offsets
153
/// - Bits 3-20: Packed offset into patterns table for each variant cell
154
/// - Bits 21-31: unused/reserved
155
///
156
/// The LMS value determines which pattern index is used for the first column:
157
///
158
/// | LMS Value   | Long Index | Medium Index | Short Index |
159
/// |-------------|------------|--------------|-------------|
160
/// | 0 (L=M=S)   | 0          | 0            | 0           |
161
/// | 1 (L, M=S)  | 0          | 1            | 1           |
162
/// | 2 (L=M, S)  | 0          | 0            | 1           |
163
/// | 3 (L, M, S) | 0          | 1            | 2           |
164
///
165
/// If bit 2 is 1 (Q=1), it means there is one pattern per table cell,
166
/// with the index offset by the short index `S` from the table above.
167
/// However, this requires storing multiple, possibly duplicate, patterns in
168
/// the packed structure. The more common case is Q=0 and then to store
169
/// per-cell offsets in chunks of 3 bits per cell:
170
///
171
/// - Chunk = 0: Inherit according to the table below
172
/// - Chunk = 1-7: Use pattern index Chunk - 1
173
///
174
/// This is summarized below:
175
///
176
/// | Cell in Table | Q=1 Pattern Index | Q=0 Header Bits | Inheritance |
177
/// |---------------|-------------------|-----------------|-------------|
178
/// | Lb            | S + 1             | 3-5             | La          |
179
/// | Mb            | S + 2             | 6-8             | Ma          |
180
/// | Sb            | S + 3             | 9-11            | Sa          |
181
/// | Lc            | S + 4             | 12-14           | La          |
182
/// | Mc            | S + 5             | 15-17           | Ma          |
183
/// | Sc            | S + 6             | 18-20           | Sa          |
184
///
185
/// As a result, if there are no variants, bits 2 and higher will be all zero,
186
/// making the header int suitable for varint packing, such as that used by
187
/// postcard and other size-optimized serialization formats.
188
///
189
/// [`YearStyle::Auto`]: crate::options::YearStyle::Auto
190
#[derive(Debug, PartialEq, Eq, Clone, yoke::Yokeable, zerofrom::ZeroFrom)]
16,952✔
191
#[cfg_attr(feature = "datagen", derive(databake::Bake))]
×
192
#[cfg_attr(feature = "datagen", databake(path = icu_datetime::provider))]
193
pub struct PackedPatterns<'data> {
194
    /// An encoding of which standard/variant cell corresponds to which entry
195
    /// in the patterns table. See class docs.
196
    pub header: u32,
2,628✔
197
    /// The list of patterns. Length should be between 1 and 9,
198
    /// depending on the header.
199
    pub elements: VarZeroVec<'data, PluralElementsPackedULE<ZeroSlice<PatternItem>>>,
2,628✔
200
}
201

202
icu_provider::data_struct!(
203
    PackedPatterns<'_>,
204
    #[cfg(feature = "datagen")]
205
);
206

207
mod constants {
208
    /// Value when standard long, medium, and short are all the same
209
    pub(super) const LMS: u32 = 0;
210
    /// Value when standard medium is the same as short but not long
211
    pub(super) const L_MS: u32 = 1;
212
    /// Value when standard medium is the same as long but not short
213
    pub(super) const LM_S: u32 = 2;
214
    /// Bit that indicates that standard medium differs from standard long
215
    pub(super) const M_DIFFERS: u32 = 0x1;
216
    /// Bit that indicates that standard short differs from standard medium
217
    pub(super) const S_DIFFERS: u32 = 0x2;
218
    /// Bitmask over all LMS values
219
    pub(super) const LMS_MASK: u32 = 0x3;
220
    /// Bit that indicates whether there are per-cell chunks
221
    pub(super) const Q_BIT: u32 = 0x4;
222
    /// A mask applied to individual chunks (the largest possible chunk)
223
    pub(super) const CHUNK_MASK: u32 = 0x7;
224
}
225

226
struct UnpackedPatterns<'a> {
227
    pub(super) has_explicit_medium: bool,
228
    pub(super) has_explicit_short: bool,
229
    pub(super) variant_indices: VariantIndices,
230
    pub(super) elements: Vec<PluralElements<Pattern<'a>>>,
231
}
232

233
#[repr(u8)]
234
#[derive(Copy, Clone)]
×
235
enum VariantPatternIndex {
236
    Inherit = 0,
237
    I0 = 1,
238
    I1 = 2,
239
    I2 = 3,
240
    I3 = 4,
241
    I4 = 5,
242
    I5 = 6,
243
    I6 = 7,
244
}
245

246
impl VariantPatternIndex {
247
    #[cfg(feature = "datagen")]
248
    pub(super) fn from_header_with_shift(header: u32, shift: u32) -> Self {
24✔
249
        match Self::try_from_u32((header >> shift) & constants::CHUNK_MASK) {
24✔
250
            Some(x) => x,
24✔
251
            None => {
252
                debug_assert!(false, "unreachable");
×
253
                Self::Inherit
254
            }
255
        }
256
    }
24✔
257

258
    fn try_from_u32(u: u32) -> Option<Self> {
15,857✔
259
        match u {
15,857✔
260
            0 => Some(Self::Inherit),
13,595✔
261
            1 => Some(Self::I0),
11✔
262
            2 => Some(Self::I1),
317✔
263
            3 => Some(Self::I2),
463✔
264
            4 => Some(Self::I3),
622✔
265
            5 => Some(Self::I4),
306✔
266
            6 => Some(Self::I5),
529✔
267
            7 => Some(Self::I6),
12✔
268
            _ => None,
2✔
269
        }
270
    }
15,857✔
271

272
    pub(super) fn try_from_chunks_u32(chunks: [u32; 6]) -> Option<[Self; 6]> {
2,639✔
273
        let [c0, c1, c2, c3, c4, c5] = chunks;
2,639✔
274
        Some([
2,637✔
275
            Self::try_from_u32(c0)?,
2,639✔
276
            Self::try_from_u32(c1)?,
2,639✔
277
            Self::try_from_u32(c2)?,
2,639✔
278
            Self::try_from_u32(c3)?,
2,639✔
279
            Self::try_from_u32(c4)?,
2,639✔
280
            Self::try_from_u32(c5)?,
2,638✔
281
        ])
282
    }
2,639✔
283

284
    pub(super) fn to_chunks_u32(chunks: [Self; 6]) -> [u32; 6] {
2,641✔
285
        let [c0, c1, c2, c3, c4, c5] = chunks;
2,641✔
286
        [
2,641✔
287
            c0 as u32, c1 as u32, c2 as u32, c3 as u32, c4 as u32, c5 as u32,
2,641✔
288
        ]
289
    }
2,641✔
290
}
291

292
enum VariantIndices {
293
    OnePatternPerVariant,
294
    IndicesPerVariant([VariantPatternIndex; 6]),
295
}
296

297
impl<'a> UnpackedPatterns<'a> {
298
    pub(super) fn build(&self) -> PackedPatterns<'static> {
2,639✔
299
        let mut header = 0u32;
2,639✔
300
        if self.has_explicit_medium {
2,639✔
301
            header |= constants::M_DIFFERS;
2,083✔
302
        }
303
        if self.has_explicit_short {
2,639✔
304
            header |= constants::S_DIFFERS;
1,428✔
305
        }
306
        match self.variant_indices {
2,639✔
307
            VariantIndices::OnePatternPerVariant => {
2✔
308
                header |= constants::Q_BIT;
2✔
309
            }
310
            VariantIndices::IndicesPerVariant(chunks) => {
2,637✔
311
                let mut shift = 3;
2,637✔
312
                for chunk_u32 in VariantPatternIndex::to_chunks_u32(chunks).iter() {
18,459✔
313
                    debug_assert!(*chunk_u32 <= constants::CHUNK_MASK);
15,822✔
314
                    header |= *chunk_u32 << shift;
15,822✔
315
                    shift += 3;
15,822✔
316
                }
317
            }
318
        }
319
        let elements: Vec<PluralElements<(FourBitMetadata, &ZeroSlice<PatternItem>)>> = self
2,639✔
320
            .elements
321
            .iter()
322
            .map(|plural_elements| {
7,511✔
323
                plural_elements.as_ref().map(|pattern| {
15,022✔
324
                    (
7,511✔
325
                        pattern.metadata.to_four_bit_metadata(),
7,511✔
326
                        pattern.items.as_slice(),
7,511✔
327
                    )
328
                })
7,511✔
329
            })
7,511✔
330
            .collect();
331
        PackedPatterns {
2,639✔
332
            header,
2,639✔
333
            elements: elements.as_slice().into(),
2,639✔
334
        }
335
    }
2,639✔
336

337
    #[cfg(feature = "datagen")]
338
    pub(super) fn from_packed(packed: &'a PackedPatterns<'_>) -> Self {
4✔
339
        let variant_indices = if (packed.header & constants::Q_BIT) != 0 {
4✔
340
            VariantIndices::OnePatternPerVariant
×
341
        } else {
342
            VariantIndices::IndicesPerVariant([
4✔
343
                VariantPatternIndex::from_header_with_shift(packed.header, 3),
4✔
344
                VariantPatternIndex::from_header_with_shift(packed.header, 6),
4✔
345
                VariantPatternIndex::from_header_with_shift(packed.header, 9),
4✔
346
                VariantPatternIndex::from_header_with_shift(packed.header, 12),
4✔
347
                VariantPatternIndex::from_header_with_shift(packed.header, 15),
4✔
348
                VariantPatternIndex::from_header_with_shift(packed.header, 18),
4✔
349
            ])
350
        };
351
        let elements = packed
4✔
352
            .elements
353
            .iter()
354
            .map(|plural_elements| {
21✔
355
                plural_elements.decode().map(|(metadata, items)| {
42✔
356
                    PatternBorrowed {
21✔
357
                        metadata: PatternMetadata::from_u8(metadata.get()),
21✔
358
                        items,
359
                    }
360
                    .as_pattern()
361
                })
21✔
362
            })
21✔
363
            .collect();
364
        Self {
4✔
365
            has_explicit_medium: (packed.header & constants::M_DIFFERS) != 0,
4✔
366
            has_explicit_short: (packed.header & constants::S_DIFFERS) != 0,
4✔
367
            variant_indices,
4✔
368
            elements,
369
        }
370
    }
4✔
371
}
372

373
impl PackedPatternsBuilder<'_> {
374
    /// Builds a packed pattern representation from the builder.
375
    pub fn build(mut self) -> PackedPatterns<'static> {
2,638✔
376
        self.simplify();
2,638✔
377

378
        // Initialize the elements vector with the standard patterns.
379
        let mut elements = Vec::new();
2,638✔
380
        let mut has_explicit_medium = false;
2,638✔
381
        let mut has_explicit_short = false;
2,638✔
382
        elements.push(self.standard.long.as_ref().map(Pattern::as_ref));
2,638✔
383
        let mut s_offset = 0;
2,638✔
384
        if self.standard.medium != self.standard.long {
4,721✔
385
            elements.push(self.standard.medium.as_ref().map(Pattern::as_ref));
2,083✔
386
            has_explicit_medium = true;
2,083✔
387
            s_offset += 1;
2,083✔
388
        }
389
        if self.standard.short != self.standard.medium {
4,065✔
390
            elements.push(self.standard.short.as_ref().map(Pattern::as_ref));
1,427✔
391
            has_explicit_short = true;
1,427✔
392
            s_offset += 1;
1,427✔
393
        }
394

395
        // Fill in the variant patterns
396
        let variant_patterns = [
2,638✔
397
            self.variant0.as_ref().map(|v| &v.long),
2,966✔
398
            self.variant0.as_ref().map(|v| &v.medium),
2,966✔
399
            self.variant0.as_ref().map(|v| &v.short),
2,966✔
400
            self.variant1.as_ref().map(|v| &v.long),
3,140✔
401
            self.variant1.as_ref().map(|v| &v.medium),
3,140✔
402
            self.variant1.as_ref().map(|v| &v.short),
3,140✔
403
        ];
404
        let fallbacks = [
2,638✔
405
            &self.standard.long,
406
            &self.standard.medium,
2,638✔
407
            &self.standard.short,
2,638✔
408
            &self.standard.long,
409
            &self.standard.medium,
2,638✔
410
            &self.standard.short,
2,638✔
411
        ];
412
        let mut chunks = [0u32; 6]; // per-cell chunk values
2,638✔
413
        for ((pattern, fallback), chunk) in variant_patterns
21,104✔
414
            .iter()
415
            .zip(fallbacks.iter())
2,638✔
416
            .zip(chunks.iter_mut())
2,638✔
417
        {
418
            if let Some(pattern) = pattern {
15,828✔
419
                if pattern != fallback {
4,731✔
420
                    *chunk = match elements.iter().position(|p| p == *pattern) {
12,370✔
421
                        Some(i) => i as u32 + 1,
883✔
422
                        None => {
423
                            elements.push(pattern.as_ref().map(Pattern::as_ref));
1,358✔
424
                            elements.len() as u32
1,358✔
425
                        }
426
                    }
427
                }
428
            }
429
        }
430

431
        // Check to see if we need to switch to Q=1 mode. We need to do this
432
        // if any of the calculated chunk values is too big (larger than 7).
433
        let variant_indices = if let Some(chunks) = VariantPatternIndex::try_from_chunks_u32(chunks)
2,640✔
434
        {
435
            // per-cell offsets
436
            VariantIndices::IndicesPerVariant(chunks)
2,636✔
437
        } else {
438
            // one pattern per table cell
439
            elements.truncate(s_offset + 1);
2✔
440
            elements.extend(variant_patterns.into_iter().zip(fallbacks.iter()).map(
2✔
441
                |(pattern, fallback)| pattern.unwrap_or(fallback).as_ref().map(Pattern::as_ref),
12✔
442
            ));
443
            VariantIndices::OnePatternPerVariant
2✔
444
        };
445

446
        // Now we can build the data representation
447
        let unpacked = UnpackedPatterns {
2,638✔
448
            has_explicit_medium,
2,638✔
449
            has_explicit_short,
2,638✔
450
            variant_indices,
2,638✔
451
            elements,
2,638✔
452
        };
453
        unpacked.build()
2,638✔
454
    }
2,638✔
455

456
    fn simplify(&mut self) {
2,647✔
457
        if self.variant0.as_ref() == Some(&self.standard) {
4,959✔
458
            self.variant0 = None;
2,312✔
459
        }
460
        if self.variant1.as_ref() == Some(&self.standard) {
4,784✔
461
            self.variant1 = None;
2,137✔
462
        }
463
    }
2,647✔
464
}
465

466
/// Which pattern to select. For details, see [`PackedPatterns`].
467
pub(crate) enum PackedSkeletonVariant {
468
    /// Default-precision year OR hours only
469
    Standard,
470
    /// Full-precision year OR hours and minutes
471
    Variant0,
472
    /// Year with era OR hours, minutes, and seconds
473
    Variant1,
474
}
475

476
impl PackedPatterns<'_> {
477
    pub(crate) fn get(&self, length: Length, variant: PackedSkeletonVariant) -> PatternBorrowed {
1,603✔
478
        use Length::*;
479
        use PackedSkeletonVariant::*;
480
        let lms = self.header & constants::LMS_MASK;
1,603✔
481
        let pattern_index = if matches!(variant, Standard) {
1,603✔
482
            // Standard pattern (first column)
483
            match (length, lms) {
568✔
484
                (Long, _) => 0,
265✔
485
                (Medium, constants::LMS | constants::LM_S) => 0,
41✔
486
                (Medium, _) => 1,
133✔
487
                (Short, constants::LMS) => 0,
39✔
488
                (Short, constants::L_MS | constants::LM_S) => 1,
14✔
489
                (Short, _) => 2,
76✔
490
            }
491
        } else {
492
            let s_offset = match lms {
1,035✔
493
                constants::LMS => 0,
463✔
494
                constants::L_MS | constants::LM_S => 1,
157✔
495
                _ => 2,
415✔
496
            };
497
            let q = self.header & constants::Q_BIT;
1,035✔
498
            if q == 0 {
1,721✔
499
                // per-cell offsets
500
                let chunk_in_low_bits = match (length, variant) {
1,023✔
501
                    (Long, Variant0) => self.header >> 3,
40✔
502
                    (Medium, Variant0) => self.header >> 6,
33✔
503
                    (Short, Variant0) => self.header >> 9,
91✔
504
                    (Long, Variant1) => self.header >> 12,
483✔
505
                    (Medium, Variant1) => self.header >> 15,
201✔
506
                    (Short, Variant1) => self.header >> 18,
175✔
507
                    (_, Standard) => {
508
                        debug_assert!(false, "unreachable");
×
509
                        return PatternBorrowed::DEFAULT;
510
                    }
511
                };
512
                let chunk = chunk_in_low_bits & constants::CHUNK_MASK;
1,023✔
513
                if chunk == 0 {
1,023✔
514
                    // Fall back to standard with the same length
515
                    return self.get(length, Standard);
349✔
516
                }
517
                chunk - 1
674✔
518
            } else {
519
                // one pattern per table cell
520
                let additional_offset = match (length, variant) {
12✔
521
                    (Long, Variant0) => 1,
2✔
522
                    (Medium, Variant0) => 2,
2✔
523
                    (Short, Variant0) => 3,
2✔
524
                    (Long, Variant1) => 4,
2✔
525
                    (Medium, Variant1) => 5,
2✔
526
                    (Short, Variant1) => 6,
2✔
527
                    (_, Standard) => {
528
                        debug_assert!(false, "unreachable");
×
529
                        return PatternBorrowed::DEFAULT;
530
                    }
531
                };
532
                s_offset + additional_offset
12✔
533
            }
534
        };
535
        let Some(plural_elements) = self.elements.get(pattern_index as usize) else {
1,254✔
536
            debug_assert!(false, "unreachable");
×
537
            return PatternBorrowed::DEFAULT;
538
        };
539
        let (metadata, items) = plural_elements.get_default();
1,254✔
540
        PatternBorrowed {
1,254✔
541
            metadata: PatternMetadata::from_u8(metadata.get()),
1,254✔
542
            items,
543
        }
544
    }
1,603✔
545

546
    fn get_as_plural_elements(
72✔
547
        &self,
548
        length: Length,
549
        variant: PackedSkeletonVariant,
550
    ) -> PluralElements<Pattern> {
551
        PluralElements::new(self.get(length, variant).as_pattern())
72✔
552
    }
72✔
553

554
    /// Converts this packed data to a builder that can be mutated.
555
    pub fn to_builder(&self) -> PackedPatternsBuilder {
8✔
556
        use Length::*;
557
        use PackedSkeletonVariant::*;
558
        let mut builder = PackedPatternsBuilder {
8✔
559
            standard: LengthPluralElements {
8✔
560
                long: self.get_as_plural_elements(Long, Standard),
8✔
561
                medium: self.get_as_plural_elements(Medium, Standard),
8✔
562
                short: self.get_as_plural_elements(Short, Standard),
8✔
563
            },
×
564
            variant0: Some(LengthPluralElements {
8✔
565
                long: self.get_as_plural_elements(Long, Variant0),
8✔
566
                medium: self.get_as_plural_elements(Medium, Variant0),
8✔
567
                short: self.get_as_plural_elements(Short, Variant0),
8✔
568
            }),
×
569
            variant1: Some(LengthPluralElements {
8✔
570
                long: self.get_as_plural_elements(Long, Variant1),
8✔
571
                medium: self.get_as_plural_elements(Medium, Variant1),
8✔
572
                short: self.get_as_plural_elements(Short, Variant1),
8✔
573
            }),
×
574
        };
×
575
        builder.simplify();
8✔
576
        builder
8✔
577
    }
8✔
578
}
579

580
#[cfg(feature = "serde")]
581
mod _serde {
582
    use super::*;
583
    use crate::provider::pattern::reference;
584
    use zerovec::VarZeroSlice;
585

586
    #[cfg_attr(feature = "serde", derive(serde::Deserialize))]
5,258✔
587
    #[cfg_attr(feature = "datagen", derive(serde::Serialize))]
2,630✔
588
    struct PackedPatternsMachine<'data> {
589
        pub header: u32,
×
590
        #[serde(borrow)]
591
        pub elements: &'data VarZeroSlice<PluralElementsPackedULE<ZeroSlice<PatternItem>>>,
×
592
    }
593

594
    #[cfg_attr(feature = "serde", derive(serde::Deserialize))]
14✔
595
    #[cfg_attr(feature = "datagen", derive(serde::Serialize))]
4✔
596
    #[derive(Default)]
8✔
597
    struct PackedPatternsHuman {
598
        #[cfg_attr(
×
599
            feature = "serde",
600
            serde(default, skip_serializing_if = "core::ops::Not::not")
601
        )]
602
        pub(super) has_explicit_medium: bool,
7✔
603
        #[cfg_attr(
×
604
            feature = "serde",
605
            serde(default, skip_serializing_if = "core::ops::Not::not")
606
        )]
607
        pub(super) has_explicit_short: bool,
7✔
608
        #[cfg_attr(
×
609
            feature = "serde",
610
            serde(default, skip_serializing_if = "core::ops::Not::not")
611
        )]
612
        pub(super) has_one_pattern_per_variant: bool,
6✔
613
        #[cfg_attr(
×
614
            feature = "serde",
615
            serde(default, skip_serializing_if = "Option::is_none")
616
        )]
617
        pub(super) variant_pattern_indices: Option<[u32; 6]>,
6✔
618
        pub(super) elements: Vec<reference::Pattern>,
5✔
619
    }
620

621
    impl<'de, 'data> serde::Deserialize<'de> for PackedPatterns<'data>
622
    where
623
        'de: 'data,
624
    {
625
        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2,630✔
626
        where
627
            D: serde::Deserializer<'de>,
628
        {
629
            use serde::de::Error as _;
630
            if deserializer.is_human_readable() {
5,259✔
631
                let human = <PackedPatternsHuman>::deserialize(deserializer)?;
1✔
632
                let variant_indices = match (
1✔
633
                    human.has_one_pattern_per_variant,
1✔
634
                    human.variant_pattern_indices,
1✔
635
                ) {
636
                    (true, None) => VariantIndices::OnePatternPerVariant,
×
637
                    (false, Some(chunks)) => VariantIndices::IndicesPerVariant(
2✔
638
                        VariantPatternIndex::try_from_chunks_u32(chunks).ok_or_else(|| {
1✔
639
                            D::Error::custom("variant pattern index out of range")
×
640
                        })?,
×
641
                    ),
1✔
642
                    _ => {
643
                        return Err(D::Error::custom(
×
644
                            "must have either one pattern per variant or indices",
645
                        ))
646
                    }
647
                };
648
                let elements = human
1✔
649
                    .elements
650
                    .iter()
651
                    .map(|pattern| PluralElements::new(pattern.to_runtime_pattern()))
5✔
652
                    .collect();
653
                let unpacked = UnpackedPatterns {
1✔
654
                    has_explicit_medium: human.has_explicit_medium,
1✔
655
                    has_explicit_short: human.has_explicit_short,
1✔
656
                    variant_indices,
1✔
657
                    elements,
658
                };
659
                Ok(unpacked.build())
1✔
660
            } else {
1✔
661
                let machine = <PackedPatternsMachine>::deserialize(deserializer)?;
2,629✔
662
                Ok(Self {
2,629✔
663
                    header: machine.header,
2,629✔
664
                    elements: machine.elements.as_varzerovec(),
2,629✔
665
                })
666
            }
667
        }
2,630✔
668
    }
669

670
    #[cfg(feature = "datagen")]
671
    impl serde::Serialize for PackedPatterns<'_> {
672
        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
2,676✔
673
        where
674
            S: serde::Serializer,
675
        {
676
            use serde::ser::Error as _;
677
            if serializer.is_human_readable() {
2,680✔
678
                let unpacked = UnpackedPatterns::from_packed(self);
4✔
679
                let mut human = PackedPatternsHuman {
46✔
680
                    has_explicit_medium: unpacked.has_explicit_medium,
46✔
681
                    has_explicit_short: unpacked.has_explicit_short,
46✔
682
                    ..Default::default()
46✔
683
                };
684
                match unpacked.variant_indices {
46✔
685
                    VariantIndices::OnePatternPerVariant => {
×
686
                        human.has_one_pattern_per_variant = true;
×
687
                    }
688
                    VariantIndices::IndicesPerVariant(chunks) => {
46✔
689
                        let chunks = VariantPatternIndex::to_chunks_u32(chunks);
46✔
690
                        human.variant_pattern_indices = Some(chunks);
4✔
691
                    }
4✔
692
                }
693
                human.elements = Vec::with_capacity(unpacked.elements.len());
4✔
694
                for pattern_elements in unpacked.elements.into_iter() {
4✔
695
                    let pattern = pattern_elements
21✔
696
                        .try_into_other()
697
                        .ok_or_else(|| S::Error::custom("cannot yet serialize plural patterns"))?;
×
698
                    human.elements.push(reference::Pattern::from(&pattern));
21✔
699
                }
25✔
700
                human.serialize(serializer)
4✔
701
            } else {
4✔
702
                let machine = PackedPatternsMachine {
2,630✔
703
                    header: self.header,
2,630✔
704
                    elements: &self.elements,
2,630✔
705
                };
706
                machine.serialize(serializer)
2,630✔
707
            }
708
        }
2,634✔
709
    }
710
}
711

712
#[cfg(test)]
713
mod tests {
714
    use super::*;
715
    use crate::provider::pattern::reference;
716

717
    const PATTERN_STRS: &[&str] = &[
718
        "M/d/y",
719
        "HH:mm",
720
        "MMM d y G",
721
        "E",
722
        "E MMM d",
723
        "dd.MM.yy",
724
        "h a",
725
        "hh:mm:ss B",
726
        "y MMMM",
727
    ];
728

729
    fn get_patterns() -> Vec<Pattern<'static>> {
2✔
730
        PATTERN_STRS
2✔
731
            .iter()
732
            .map(|s| {
18✔
733
                s.parse::<reference::Pattern>()
18✔
734
                    .unwrap()
735
                    .to_runtime_pattern()
736
            })
18✔
737
            .collect::<Vec<_>>()
738
    }
2✔
739

740
    #[test]
741
    fn test_basic() {
2✔
742
        let patterns = get_patterns();
1✔
743
        let mut it = patterns.iter().cloned();
1✔
744
        let lms0 = LengthPluralElements {
1✔
745
            long: PluralElements::new(it.next().unwrap()),
1✔
746
            medium: PluralElements::new(it.next().unwrap()),
1✔
747
            short: PluralElements::new(it.next().unwrap()),
1✔
748
        };
×
749
        let lms1 = LengthPluralElements {
1✔
750
            long: PluralElements::new(it.next().unwrap()),
1✔
751
            medium: PluralElements::new(it.next().unwrap()),
1✔
752
            short: PluralElements::new(it.next().unwrap()),
1✔
753
        };
×
754
        let lms2 = LengthPluralElements {
1✔
755
            long: PluralElements::new(it.next().unwrap()),
1✔
756
            medium: PluralElements::new(it.next().unwrap()),
1✔
757
            short: PluralElements::new(it.next().unwrap()),
1✔
758
        };
×
759
        let lms0a = LengthPluralElements {
1✔
760
            long: PluralElements::new(patterns[0].clone()),
1✔
761
            medium: PluralElements::new(patterns[0].clone()),
1✔
762
            short: PluralElements::new(patterns[1].clone()),
1✔
763
        };
×
764
        let lms1a = LengthPluralElements {
1✔
765
            long: PluralElements::new(patterns[3].clone()),
1✔
766
            medium: PluralElements::new(patterns[4].clone()),
1✔
767
            short: PluralElements::new(patterns[4].clone()),
1✔
768
        };
×
769

770
        {
771
            // Q = 1
772
            let builder = PackedPatternsBuilder {
1✔
773
                standard: lms0.clone(),
1✔
774
                variant0: Some(lms1.clone()),
1✔
775
                variant1: Some(lms2.clone()),
1✔
776
            };
×
777
            let packed = builder.clone().build();
1✔
778
            assert_eq!(packed.header, 7);
1✔
779
            assert_eq!(packed.elements.len(), 9);
1✔
780
            for (pattern_elements, expected) in packed.elements.iter().zip(patterns.iter()) {
10✔
781
                assert_eq!(pattern_elements.get_default().1, &expected.items);
9✔
782
            }
783
            let recovered_builder = packed.to_builder();
1✔
784
            assert_eq!(builder, recovered_builder);
1✔
785
        }
1✔
786
        {
787
            // Q = 0
788
            let builder = PackedPatternsBuilder {
1✔
789
                standard: lms0.clone(),
1✔
790
                variant0: Some(lms0.clone()),
1✔
791
                variant1: Some(lms2.clone()),
1✔
792
            };
×
793
            let packed = builder.clone().build();
1✔
794
            assert_eq!(packed.header, 0x1AC003);
1✔
795
            assert_eq!(packed.elements.len(), 6);
1✔
796
            let recovered_builder = packed.to_builder();
1✔
797
            assert_ne!(builder, recovered_builder);
1✔
798
            let mut builder = builder;
1✔
799
            builder.simplify();
1✔
800
            assert_eq!(builder, recovered_builder);
1✔
801
        }
1✔
802
        {
803
            // No variants
804
            let builder = PackedPatternsBuilder {
1✔
805
                standard: lms0.clone(),
1✔
806
                variant0: None,
1✔
807
                variant1: None,
1✔
808
            };
809
            let packed = builder.clone().build();
1✔
810
            assert_eq!(packed.header, 3);
1✔
811
            assert_eq!(packed.elements.len(), 3);
1✔
812
            let recovered_builder = packed.to_builder();
1✔
813
            assert_eq!(builder, recovered_builder);
1✔
814
        }
1✔
815
        {
816
            // Some duplicate patterns and inheritance
817
            let builder = PackedPatternsBuilder {
1✔
818
                standard: lms0a.clone(),
1✔
819
                variant0: Some(lms0.clone()),
1✔
820
                variant1: Some(lms1.clone()),
1✔
821
            };
×
822
            let packed = builder.clone().build();
1✔
823
            assert_eq!(packed.header, 0x1AC682);
1✔
824
            assert_eq!(packed.elements.len(), 6);
1✔
825
            let recovered_builder = packed.to_builder();
1✔
826
            assert_eq!(builder, recovered_builder);
1✔
827
        }
1✔
828
        {
829
            // Q = 1 with 8 patterns (min for Q = 1)
830
            let builder = PackedPatternsBuilder {
1✔
831
                standard: lms0a.clone(),
1✔
832
                variant0: Some(lms1.clone()),
1✔
833
                variant1: Some(lms2.clone()),
1✔
834
            };
×
835
            let packed = builder.clone().build();
1✔
836
            assert_eq!(packed.header, 6);
1✔
837
            assert_eq!(packed.elements.len(), 8);
1✔
838
            let recovered_builder = packed.to_builder();
1✔
839
            assert_eq!(builder, recovered_builder);
1✔
840
        }
1✔
841
        {
842
            // Q = 0 with 7 patterns (max for Q = 0)
843
            let builder = PackedPatternsBuilder {
1✔
844
                standard: lms1a.clone(),
1✔
845
                variant0: Some(lms0a.clone()),
1✔
846
                variant1: Some(lms2.clone()),
1✔
847
            };
×
848
            let packed = builder.clone().build();
1✔
849
            assert_eq!(packed.header, 0x1F58D9);
1✔
850
            assert_eq!(packed.elements.len(), 7);
1✔
851
            let recovered_builder = packed.to_builder();
1✔
852
            assert_eq!(builder, recovered_builder);
1✔
853
        }
1✔
854
    }
2✔
855

856
    #[cfg(feature = "datagen")]
857
    #[test]
858
    fn test_serde() {
2✔
859
        let patterns = get_patterns();
1✔
860
        let lms0a = LengthPluralElements {
1✔
861
            long: PluralElements::new(patterns[0].clone()),
1✔
862
            medium: PluralElements::new(patterns[0].clone()),
1✔
863
            short: PluralElements::new(patterns[1].clone()),
1✔
864
        };
×
865
        let lms1 = LengthPluralElements {
1✔
866
            long: PluralElements::new(patterns[3].clone()),
1✔
867
            medium: PluralElements::new(patterns[4].clone()),
1✔
868
            short: PluralElements::new(patterns[5].clone()),
1✔
869
        };
×
870

871
        let builder = PackedPatternsBuilder {
1✔
872
            standard: lms0a,
1✔
873
            variant0: Some(lms1),
1✔
874
            variant1: None,
1✔
875
        };
876
        let packed = builder.clone().build();
1✔
877

878
        let bincode_bytes = bincode::serialize(&packed).unwrap();
1✔
879
        assert_eq!(
1✔
880
            bincode_bytes.as_slice(),
1✔
881
            &[
1✔
882
                26, 11, 0, 0, 72, 0, 0, 0, 0, 0, 0, 0, 5, 0, 16, 0, 26, 0, 30, 0, 46, 0, 0, 128,
883
                32, 1, 0, 0, 47, 128, 64, 1, 0, 0, 47, 128, 16, 1, 2, 128, 114, 2, 0, 0, 58, 128,
884
                128, 2, 0, 128, 80, 1, 0, 128, 80, 1, 0, 0, 32, 128, 32, 3, 0, 0, 32, 128, 64, 1,
885
                0, 128, 64, 2, 0, 0, 46, 128, 32, 2, 0, 0, 46, 128, 16, 2
886
            ][..]
1✔
887
        );
888
        let bincode_recovered = bincode::deserialize::<PackedPatterns>(&bincode_bytes).unwrap();
1✔
889
        assert_eq!(builder, bincode_recovered.to_builder());
2✔
890

891
        let json_str = serde_json::to_string(&packed).unwrap();
1✔
892
        assert_eq!(json_str, "{\"has_explicit_short\":true,\"variant_pattern_indices\":[3,4,5,0,0,0],\"elements\":[\"M/d/y\",\"HH:mm\",\"E\",\"E MMM d\",\"dd.MM.yy\"]}");
1✔
893
        let json_recovered = serde_json::from_str::<PackedPatterns>(&json_str).unwrap();
1✔
894
        assert_eq!(builder, json_recovered.to_builder());
2✔
895
    }
2✔
896
}
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