File: 0002-additional-configuration-properties-for-formatstyle-implementations.md

package info (click to toggle)
swiftlang 6.0.3-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,519,992 kB
  • sloc: cpp: 9,107,863; ansic: 2,040,022; asm: 1,135,751; python: 296,500; objc: 82,456; f90: 60,502; lisp: 34,951; pascal: 19,946; sh: 18,133; perl: 7,482; ml: 4,937; javascript: 4,117; makefile: 3,840; awk: 3,535; xml: 914; fortran: 619; cs: 573; ruby: 573
file content (611 lines) | stat: -rw-r--r-- 31,657 bytes parent folder | download
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
#  Additional Configuration Properties for Foundation's `FormatStyle` Implementations

* Proposal: [SF-0002](0002-additional-configuration-properties-for-formatstyle-implementations.md)
* Authors: [Max Obermeier](https://github.com/themomax)
* Review Manager: [Tina Liu](https://github.com/itingliu)
* Status:**Accepted**
* Implementation: [apple/swift-foundation#338](https://github.com/apple/swift-foundation/issues/338)
* Review: [Pitch](https://forums.swift.org/t/fou-formatstyle-enhancements/68858)

## Introduction

Foundation defines the `FormatStyle` protocol making it easy to format all kinds of data. However, many of the implementations Foundation provides could offer more customization and do even more to provide access to the configuration after the `FormatStyle` instance has been created. This is critical where the definition of the format and the actual formatting are done in different places or even by different parties.

## Motivation

There are many situations where one only has access to the instance of a `FormatStyle` and is not creating the instance on the spot. Maybe the most important example is the conformance to protocols:

Imagine a UI package that provides a view for rendering a calendar and calendar events. It accepts `FormatStyle`s to configure the way dates are formatted.

```swift
struct CalendarView /* ... */ {
    init<F: FormatStyle>(dateFormat: F, /* ... */) where F.FormatInput == Date, F.FormatOutput == AttributedString
    
    /* ... */
}
```

Instead of requiring the user to configure the `FormatStyle`'s calendar manually, the package wants to define a protocol that allows it to configure the calendar itself (to ensure correctness, or because the view can display different calendars independently of the OS/app calendar):
```swift
protocol CalendarBasedFormatStyle: FormatStyle {
    func calendar(_ calendar: Calendar) -> Self
}
```

The problem is that the UI package cannot implement this protocol for many of Foundation's `FormatStyle` implementations, because they don't expose their calendar property:

```swift
extension Date.AttributedStyle: CalendarBasedFormatStyle {
    func calendar(_ calendar: Calendar) -> Self {
        // there's no way we can implement this because Date.AttributedStyle
        // doesn't expose its base type's calendar
    }
}
```

This, of course, does not only apply to setting the calendar or `Date.AttributedStyle` specifically, but instead, many of Foundation's `FormatStyle` implementations could expose more configuration options and make them accessible after initialization.

## Proposed solution

We extend various format style implementations to expose or add new configuration options.

## Detailed design

### Dynamic Member Lookup for base `Attributed` styles

We already have various `Attributed` derivative `FormatStyle`s, that can be obtained from the `attributed` property of a base `FormatStyle`. However, while we can edit the properties of most base `FormatStyle`s, their `Attributed` variants sometimes neither re-expose their bases' members, nor do they expose the base `FormatStyle` itself. Thus, all those properties can no longer be accessed once the style has been converted to the `attributed` variant.

We thus add a `@dynamicMemberLookup` for existing nested `Attributed` styles that neither expose the base style publicly, nor reproduce its API in a different manner:

```swift
@available(FoundationPreview 0.4, *)
extension Duration.TimeFormatStyle.Attributed {
    public subscript<T>(dynamicMember key: KeyPath<Duration.TimeFormatStyle, T>) -> T { get }
    public subscript<T>(dynamicMember key: WritableKeyPath<Duration.TimeFormatStyle, T>) -> T { get set }
}


@available(FoundationPreview 0.4, *)
extension Duration.UnitsFormatStyle.Attributed {
    public subscript<T>(dynamicMember key: KeyPath<Duration.UnitsFormatStyle, T>) -> T { get }
    public subscript<T>(dynamicMember key: WritableKeyPath<Duration.TimeFormatStyle, T>) -> T { get set }
}

@available(FoundationPreview 0.4, *)
extension Measurement.AttributedStyle {
    public subscript<T>(dynamicMember key: KeyPath<Measurement.FormatStyle, T>) -> T { get }
    public subscript<T>(dynamicMember key: WritableKeyPath<Measurement.FormatStyle, T>) -> T { get set }
}
```

### Allowed fields for `Date.RelativeFormatStyle`

A way to specify that e.g. `.seconds` may not be used. E.g. instead of "in 49 seconds", the output would be "this minute". When using the existing initializer that does not have the `allowedFields` argument, the property is set to include all cases so the current behavior is preserved.

```swift
@available(FoundationPreview 0.4, *)
extension Date.RelativeFormatStyle {
    public typealias Field = Date.ComponentsFormatStyle.Field

    /// The fields that can be used in the formatted output.
    public var allowedFields: Set<Field>

    public init(allowedFields: Set<Field>, presentation: Presentation = .numeric, unitsStyle: UnitsStyle = .wide, locale: Locale = .autoupdatingCurrent, calendar: Calendar = .autoupdatingCurrent, capitalizationContext: FormatStyleCapitalizationContext = .unknown)
```

### A Grouping option for `Duration.TimeFormatStyle`

A configuration for the grouping for large numbers, e.g. setting the `grouping` to `.never` would yield "10000:00" instead of "10,000:00", which is produced currently.

```swift
@available(FoundationPreview 0.4, *)
extension Duration.TimeFormatStyle {
    /// Returns a modified style that applies the given `grouping` rule to the highest field in the
    /// pattern.
    public func grouping(_ grouping: NumberFormatStyleConfiguration.Grouping) -> Self

    /// The `grouping` rule applied to high number values on the largest field in the pattern.
    public var grouping: NumberFormatStyleConfiguration.Grouping { get set }
}

@available(FoundationPreview 0.4, *)
extension Duration.TimeFormatStyle.Attributed {
    /// Returns a modified style that applies the given `grouping` rule to the highest field in the
    /// pattern.
    public func grouping(_ grouping: NumberFormatStyleConfiguration.Grouping) -> Self
}
```

## Removing symbols from `Date.FormatStyle`

When created, the instance of a `Date.FormatStyle` includes a set of default symbols when formatting a date. One can override this default by specifying symbols manually via the function with the respective symbol's name:

```swift
let style = Date.FormatStyle()
style.format(date)                          // 1/1/1970, 12:00 AM (default)
style.hour().format(date)                   // 12 AM (only shows hour)
```

Previously one could only add more symbols after that. We provide a new `.omitted` case for the relevant symbols which can be used to remove a symbol from the formatted output.

```swift
let style = Date.FormatStyle()
style.format(date)                          // 1/1/1970, 12:00 AM (default)
style.minute(.omitted).format(date)         // 1/1/1970, 12 AM (default - minutes)
```

When all have been removed, `format(_:)` returns an empty string:

```swift
let style = Date.FormatStyle()
style.hour().hour(.omitted).format(date).   //  ("")
```

The following lists the added `omitted` symbols.

```swift
@available(FoundationPreview 0.4, *)
extension Date.FormatStyle.Symbol.Era {
    /// The option for not including the symbol in the formatted output.
    public static var omitted: Self { get }
}

@available(FoundationPreview 0.4, *)
extension Date.FormatStyle.Symbol.Year {
    /// The option for not including the symbol in the formatted output.
    public static var omitted: Self { get }
}

@available(FoundationPreview 0.4, *)
extension Date.FormatStyle.Symbol.YearForWeekOfYear {
    /// The option for not including the symbol in the formatted output.
    public static var omitted: Self { get }
}

@available(FoundationPreview 0.4, *)
extension Date.FormatStyle.Symbol.CyclicYear {
    /// The option for not including the symbol in the formatted output.
    public static var omitted: Self { get }
}

@available(FoundationPreview 0.4, *)
extension Date.FormatStyle.Symbol.Quarter {
    /// The option for not including the symbol in the formatted output.
    public static var omitted: Self { get }
}

@available(FoundationPreview 0.4, *)
extension Date.FormatStyle.Symbol.Month {
    /// The option for not including the symbol in the formatted output.
    public static var omitted: Self { get }
}

@available(FoundationPreview 0.4, *)
extension Date.FormatStyle.Symbol.Week {
    /// The option for not including the symbol in the formatted output.
    public static var omitted: Self { get }
}

@available(FoundationPreview 0.4, *)
extension Date.FormatStyle.Symbol.Day {
    /// The option for not including the symbol in the formatted output.
    public static var omitted: Self { get }
}

@available(FoundationPreview 0.4, *)
extension Date.FormatStyle.Symbol.DayOfYear {
    /// The option for not including the symbol in the formatted output.
    public static var omitted: Self { get }
}

@available(FoundationPreview 0.4, *)
extension Date.FormatStyle.Symbol.Weekday {
    /// The option for not including the symbol in the formatted output.
    public static var omitted: Self { get }
}

@available(FoundationPreview 0.4, *)
extension Date.FormatStyle.Symbol.DayPeriod {
    /// The option for not including the symbol in the formatted output.
    public static var omitted: Self { get }
}

@available(FoundationPreview 0.4, *)
extension Date.FormatStyle.Symbol.Hour {
    /// The option for not including the symbol in the formatted output.
    public static var omitted: Self { get }
}

@available(FoundationPreview 0.4, *)
extension Date.FormatStyle.Symbol.Minute {
    /// The option for not including the symbol in the formatted output.
    public static var omitted: Self { get }
}

@available(FoundationPreview 0.4, *)
extension Date.FormatStyle.Symbol.Second {
    /// The option for not including the symbol in the formatted output.
    public static var omitted: Self { get }
}

@available(FoundationPreview 0.4, *)
extension Date.FormatStyle.Symbol.SecondFraction {
    /// The option for not including the symbol in the formatted output.
    public static var omitted: Self { get }
}

@available(FoundationPreview 0.4, *)
extension Date.FormatStyle.Symbol.TimeZone {
    /// The option for not including the symbol in the formatted output.
    public static var omitted: Self { get }
}
```

### Typed `Date.AttributedStyle`

Since `Date.AttributedStyle` can either be a `Date.FormatStyle` or a `Date.VerbatimFormatStyle` under the hood, we cannot implement the generic dynamic member lookup or expose the base format style in a typed manner.

We deprecate this type and the respective `attributed` properties on `Date.FormatStyle` and `Date.VerbatimFormatStyle`.

```swift
@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *)
extension Date {
    @available(macOS, deprecated: 15, introduced: 12, message: "Use Date.FormatStyle.Attributed or Date.VerbatimFormatStyle.Attributed instead")
    @available(iOS, deprecated: 18, introduced: 15, message: "Use Date.FormatStyle.Attributed or Date.VerbatimFormatStyle.Attributed instead")
    @available(tvOS, deprecated: 18, introduced: 15, message: "Use Date.FormatStyle.Attributed or Date.VerbatimFormatStyle.Attributed instead")
    @available(watchOS, deprecated: 11, introduced: 8, message: "Use Date.FormatStyle.Attributed or Date.VerbatimFormatStyle.Attributed instead")
    public struct AttributedStyle : Sendable { /* ... */ }
}

@available(macOS, deprecated: 15, introduced: 12, message: "Use Date.FormatStyle.Attributed or Date.VerbatimFormatStyle.Attributed instead")
@available(iOS, deprecated: 18, introduced: 15, message: "Use Date.FormatStyle.Attributed or Date.VerbatimFormatStyle.Attributed instead")
@available(tvOS, deprecated: 18, introduced: 15, message: "Use Date.FormatStyle.Attributed or Date.VerbatimFormatStyle.Attributed instead")
@available(watchOS, deprecated: 11, introduced: 8, message: "Use Date.FormatStyle.Attributed or Date.VerbatimFormatStyle.Attributed instead")
extension Date.AttributedStyle : FormatStyle {}

@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *)
extension Date {
    public struct VerbatimFormatStyle /* ... */ {
        /* ... */
    
        /// Returns a type erased attributed variant of this style.
        @available(macOS, deprecated: 15, introduced: 12, message: "Use attributedStyle instead")
        @available(iOS, deprecated: 18, introduced: 15, message: "Use attributedStyle instead")
        @available(tvOS, deprecated: 18, introduced: 15, message: "Use attributedStyle instead")
        @available(watchOS, deprecated: 11, introduced: 8, message: "Use attributedStyle instead")
        public var attributed: AttributedStyle { get }
    }
}

@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *)
extension Date {
    public struct FormatStyle /* ... */ {
        /* ... */

        /// Returns a type erased attributed variant of this style.
        @available(macOS, deprecated: 15, introduced: 12, message: "Use attributedStyle instead")
        @available(iOS, deprecated: 18, introduced: 15, message: "Use attributedStyle instead")
        @available(tvOS, deprecated: 18, introduced: 15, message: "Use attributedStyle instead")
        @available(watchOS, deprecated: 11, introduced: 8, message: "Use attributedStyle instead")
        public var attributed: AttributedStyle { get }
    }
}
```

As a replacement we add typed variants `Date.FormatStyle.Attributed` and `Date.VerbatimFormatStyle.Attributed` along with the respective properties on the base types called `attributedStyle`. Both `Attributed` styles provide dynamic member lookup to their base type.

```swift
@available(FoundationPreview 0.4, *)
extension Date.VerbatimFormatStyle {
    /// The type preserving attributed variant of this style.
    ///
    /// This style attributes the formatted date with the `AttributeScopes.FoundationAttributes.DateFormatFieldAttribute`.
    public struct Attributed : FormatStyle, Sendable {
        public subscript<T>(dynamicMember key: KeyPath<Date.VerbatimFormatStyle, T>) -> T { get }

        public subscript<T>(dynamicMember key: WritableKeyPath<Date.VerbatimFormatStyle, T>) -> T { get set }

        public func format(_ value: Date) -> AttributedString

        public func locale(_ locale: Locale) -> Self
    }

    /// Return the type preserving attributed variant of this style.
    ///
    /// This style attributes the formatted date with the `AttributeScopes.FoundationAttributes.DateFormatFieldAttribute`.
    public var attributedStyle: Attributed { get }
}

@available(FoundationPreview 0.4, *)
extension Date.FormatStyle {
    /// The type preserving attributed variant of this style.
    ///
    /// This style attributes the formatted date with the `AttributeScopes.FoundationAttributes.DateFormatFieldAttribute`.
    public struct Attributed : FormatStyle, Sendable {
        public subscript<T>(dynamicMember key: KeyPath<Date.FormatStyle, T>) -> T { get }

        public subscript<T>(dynamicMember key: WritableKeyPath<Date.FormatStyle, T>) -> T { get set }

        public func format(_ value: Date) -> AttributedString

        public func locale(_ locale: Locale) -> Self
    }

    /// Return the type preserving attributed variant of this style.
    ///
    /// This style attributes the formatted date with the `AttributeScopes.FoundationAttributes.DateFormatFieldAttribute`.
    public var attributedStyle: Attributed { get }
}
```

`Date.FormatStyle.Attributed` additionally gets the same functions for specifying the symbols as `Date.FormatStyle`:

```swift
@available(FoundationPreview 0.4, *)
extension Date.FormatStyle.Attributed {
    /// Change the representation of the era in the format.
    ///
    /// - Parameter format: Set the symbol representation or pass `nil` to remove it.
    public func era(_ format: Date.FormatStyle.Symbol.Era = .abbreviated) -> Self

    /// Change the representation of the year in the format.
    ///
    /// - Parameter format: Set the symbol representation or pass `nil` to remove it.
    public func year(_ format: Date.FormatStyle.Symbol.Year = .defaultDigits) -> Self
    
    /// Change the representation of the quarter in the format.
    ///
    /// - Parameter format: Set the symbol representation or pass `nil` to remove it.
    public func quarter(_ format: Date.FormatStyle.Symbol.Quarter = .abbreviated) -> Self

    /// Change the representation of the month in the format.
    ///
    /// - Parameter format: Set the symbol representation or pass `nil` to remove it.
    public func month(_ format: Date.FormatStyle.Symbol.Month = .abbreviated) -> Self

    /// Change the representation of the week in the format.
    ///
    /// - Parameter format: Set the symbol representation or pass `nil` to remove it.
    public func week(_ format: Date.FormatStyle.Symbol.Week = .defaultDigits) -> Self

    /// Change the representation of the day of the month in the format.
    ///
    /// - Parameter format: Set the symbol representation or pass `nil` to remove it.
    public func day(_ format: Date.FormatStyle.Symbol.Day = .defaultDigits) -> Self

    /// Change the representation of the day of the year in the format.
    ///
    /// - Parameter format: Set the symbol representation or pass `nil` to remove it.
    public func dayOfYear(_ format: Date.FormatStyle.Symbol.DayOfYear = .defaultDigits) -> Self

    /// Change the representation of the weekday in the format.
    ///
    /// - Parameter format: Set the symbol representation or pass `nil` to remove it.
    public func weekday(_ format: Date.FormatStyle.Symbol.Weekday = .abbreviated) -> Self

    /// Change the representation of the hour in the format.
    ///
    /// - Parameter format: Set the symbol representation or pass `nil` to remove it.
    public func hour(_ format: Date.FormatStyle.Symbol.Hour = .defaultDigits(amPM: .abbreviated)) -> Self

    /// Change the representation of the minute in the format.
    ///
    /// - Parameter format: Set the symbol representation or pass `nil` to remove it.
    public func minute(_ format: Date.FormatStyle.Symbol.Minute = .defaultDigits) -> Self

    /// Change the representation of the second in the format.
    ///
    /// - Parameter format: Set the symbol representation or pass `nil` to remove it.
    public func second(_ format: Date.FormatStyle.Symbol.Second = .defaultDigits) -> Self

    /// Change the representation of the second fraction in the format.
    ///
    /// - Parameter format: Set the symbol representation or pass `nil` to remove it.
    public func secondFraction(_ format: Date.FormatStyle.Symbol.SecondFraction) -> Self

    /// Change the representation of the time zone in the format.
    ///
    /// - Parameter format: Set the symbol representation or pass `nil` to remove it.
    public func timeZone(_ format: Date.FormatStyle.Symbol.TimeZone = .specificName(.short)) -> Self
}
```

## Source compatibility

All changes are source and ABI compatible. All deprecations are listed under _Detailed design_.

## Implications on adoption

This feature can be freely adopted and un-adopted in source code with no deployment constraints and without affecting source compatibility.

## Future directions

This proposal consists of minor changes to existing API, but brings no significant new ideas that could be spun further. It thus has no future directions.

## Alternatives considered

### Imperative API for removing symbols from `Date.FormatStyle`

The proposed API for removing symbols from `Date.FormatStyle` (just like the existing API for adding symbols) is designed for declaratively adding or removing the symbols in a convenient way. Specifically, when setting a symbol to anything but `.omitted` right after the style was initialized without specifying any symbols, the resulting style only shows the specified symbol:

```swift
let style = Date.FormatStyle()
style.format(date)                          // 1/1/1970, 12:00 AM (default)
style.minute().format(date)                 // 00 (only shows minute) 
```

While this makes the API very convenient to use, it looks strange when compared to the new `.omitted` case, because omitting a symbol results in more symbols to be used than including the symbol:

```swift
style.minute().format(date)                 // 00 (only shows minute) 
style.minute(.omitted).format(date)         // 1/1/1970, 12 AM (default - minutes)
```

A way out of this dilemma could be an imperative API (e.g. a mutable property for each symbol) that does not provide this magic behavior. However, the declarative API still covers all use cases and the scenarios where the imperative API would be advantageous are so limited they do not justify the additional API surface.

#### Removing symbols from `Date.FormatStyle`: `nil` instead of `.omitted`

Adding a `.omitted` case to the respective `Date.FormatStyle.Symbol` types also influences `Date.VerbatimFormatStyle`. E.g. one can now write the following, which produces an empty string:

```swift
Date.VerbatimFormatStyle(format: "\(day: .omitted)", timeZone: .current, calendar: .current)
```

An alternative would be to make the functions on `Date.FormatStyle` accept an optional of their respective symbol as detailed below. We favor the `.omitted` symbol because it has clear meaning, whereas `nil` always comes with some ambiguity. In this case especially, because many of the functions already have default arguments, and the difference between `style.day()` and `style.day(nil)` is not particularly obvious.

```swift
@available(FoundationPreview 0.4, *)
extension Date.FormatStyle {
    /// Change the representation of the era in the format.
    ///
    /// - Parameter format: Set the symbol representation or pass `nil` to remove it.
    public func era(_ format: Symbol.Era? = .abbreviated) -> Self

    /// Change the representation of the year in the format.
    ///
    /// - Parameter format: Set the symbol representation or pass `nil` to remove it.
    public func year(_ format: Symbol.Year? = .defaultDigits) -> Self

    /// Change the representation of the quarter in the format.
    ///
    /// - Parameter format: Set the symbol representation or pass `nil` to remove it.
    public func quarter(_ format: Symbol.Quarter? = .abbreviated) -> Self

    /// Change the representation of the month in the format.
    ///
    /// - Parameter format: Set the symbol representation or pass `nil` to remove it.
    public func month(_ format: Symbol.Month? = .abbreviated) -> Self

    /// Change the representation of the week in the format.
    ///
    /// - Parameter format: Set the symbol representation or pass `nil` to remove it.
    public func week(_ format: Symbol.Week? = .defaultDigits) -> Self

    /// Change the representation of the day of the month in the format.
    ///
    /// - Parameter format: Set the symbol representation or pass `nil` to remove it.
    public func day(_ format: Symbol.Day? = .defaultDigits) -> Self

    /// Change the representation of the day of the year in the format.
    ///
    /// - Parameter format: Set the symbol representation or pass `nil` to remove it.
    public func dayOfYear(_ format: Symbol.DayOfYear? = .defaultDigits) -> Self

    /// Change the representation of the weekday in the format.
    ///
    /// - Parameter format: Set the symbol representation or pass `nil` to remove it.
    public func weekday(_ format: Symbol.Weekday? = .abbreviated) -> Self

    /// Change the representation of the hour in the format.
    ///
    /// - Parameter format: Set the symbol representation or pass `nil` to remove it.
    public func hour(_ format: Symbol.Hour? = .defaultDigits(amPM: .abbreviated)) -> Self
    /// Change the representation of the minute in the format.
    ///
    /// - Parameter format: Set the symbol representation or pass `nil` to remove it.
    public func minute(_ format: Symbol.Minute? = .defaultDigits) -> Self

    /// Change the representation of the second in the format.
    ///
    /// - Parameter format: Set the symbol representation or pass `nil` to remove it.
    public func second(_ format: Symbol.Second? = .defaultDigits) -> Self

    /// Change the representation of the second fraction in the format.
    ///
    /// - Parameter format: Set the symbol representation or pass `nil` to remove it.
    public func secondFraction(_ format: Symbol.SecondFraction?) -> Self
    
    /// Change the representation of the time zone in the format.
    ///
    /// - Parameter format: Set the symbol representation or pass `nil` to remove it.
    public func timeZone(_ format: Symbol.TimeZone? = .specificName(.short)) -> Self
}

@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *)
@available(*, deprecated, message: "Use equivalent function with optional argument instead")
extension Date.FormatStyle {
    @available(*, unavailable, message: "Use equivalent function with optional argument instead")
    public func era(_ format: Symbol.Era = .abbreviated) -> Self
    @available(*, unavailable, message: "Use equivalent function with optional argument instead")
    public func year(_ format: Symbol.Year = .defaultDigits) -> Self
    @available(*, unavailable, message: "Use equivalent function with optional argument instead")
    public func quarter(_ format: Symbol.Quarter = .abbreviated) -> Self
    @available(*, unavailable, message: "Use equivalent function with optional argument instead")
    public func month(_ format: Symbol.Month = .abbreviated) -> Self
    @available(*, unavailable, message: "Use equivalent function with optional argument instead")
    public func week(_ format: Symbol.Week = .defaultDigits) -> Self
    @available(*, unavailable, message: "Use equivalent function with optional argument instead")
    public func day(_ format: Symbol.Day = .defaultDigits) -> Self
    @available(*, unavailable, message: "Use equivalent function with optional argument instead")
    public func dayOfYear(_ format: Symbol.DayOfYear = .defaultDigits) -> Self
    @available(*, unavailable, message: "Use equivalent function with optional argument instead")
    public func weekday(_ format: Symbol.Weekday = .abbreviated) -> Self
    @available(*, unavailable, message: "Use equivalent function with optional argument instead")
    public func hour(_ format: Symbol.Hour = .defaultDigits(amPM: .abbreviated)) -> Self
    @available(*, unavailable, message: "Use equivalent function with optional argument instead")
    public func minute(_ format: Symbol.Minute = .defaultDigits) -> Self
    @available(*, unavailable, message: "Use equivalent function with optional argument instead")
    public func second(_ format: Symbol.Second = .defaultDigits) -> Self
    @available(*, unavailable, message: "Use equivalent function with optional argument instead")
    public func secondFraction(_ format: Symbol.SecondFraction) -> Self
    @available(*, unavailable, message: "Use equivalent function with optional argument instead")
    public func timeZone(_ format: Symbol.TimeZone = .specificName(.short)) -> Self
}
```

#### Removing symbols from `Date.FormatStyle`: `.remove(_:)` function

One could imagine having a `remove(_:)` or `removing(_:)` function that one can pass a calendar field to and which returns the modified style. However, the current API is declarative, where each function should be imagined as the setter for the styling of the respective calendar field. In this mental model, just allowing to pass `.omitted` to the setter feels more natural than having a `remove(x)` function that mutates the property representing the style of `x`.

### Exposing non-attributed base of `Date.AttributedStyle` as an untyped `FormatStyle`

Instead of deprecating `Date.AttributedStyle`, we could provide read-only access to the underlying base style as `any FormatStyle`.

However, in most scenarios, developers need access to the base style in order to _modify_ the style, not to obtain an unattributed version. To do so, developers would have to manually assert for all possible types in which they are not aided by the compiler. Deprecating `Date.AttributedStyle` and introducing typed variants for the two base types also has another benefit. It allows developers to conform one attributed style to a protocol but not the other, which is a reasonable scenario given that the base types' API surfaces are very different.

### Grouping option for `Duration.TimeFormatStyle` as part of the pattern initializer functions

Ultimately the grouping is orthogonal to the pattern. A solution where the grouping is part of the pattern would duplicate a lot of code as can be seen below.

```swift
@available(FoundationPreview 0.4, *)
extension Duration.TimeFormatStyle.Pattern {
    /// Displays a duration in terms of hours and minutes with the specified configurations.
    /// - Parameters:
    ///   - padHourToLength: Padding for the hour field. For example, one hour is formatted as "01:00" in en_US locale when this value is set to 2.
    ///   - roundSeconds: Rounding rule for the remaining second values.
    ///   - grouping: Grouping rule for high hour values.
    /// - Returns: A pattern to format a duration with.
    public static func hourMinute(padHourToLength: Int,
                                  roundSeconds: FloatingPointRoundingRule = .toNearestOrEven,
                                  grouping: NumberFormatStyleConfiguration.Grouping) -> Self

    /// Displays a duration in terms of hours, minutes, and seconds with the specified configurations.
    ///   - padHourToLength: Padding for the hour field. For example, one hour is formatted as "01:00:00" in en_US locale when this value is set to 2.
    ///   - fractionalSecondsLength: The length of the fractional seconds. For example, one hour is formatted as "1:00:00.00" in en_US locale when this value is set to 2.
    ///   - roundFractionalSeconds: Rounding rule for the fractional second values.
    ///   - grouping: Grouping rule for high hour values.
    /// - Returns: A pattern to format a duration with.
    public static func hourMinuteSecond(padHourToLength: Int,
                                        fractionalSecondsLength: Int = 0,
                                        roundFractionalSeconds: FloatingPointRoundingRule = .toNearestOrEven,
                                        grouping: NumberFormatStyleConfiguration.Grouping) -> Self


    /// Displays a duration in minutes and seconds with the specified configurations.
    /// - Parameters:
    ///   - padMinuteToLength: Padding for the minute field. For example, five minutes is formatted as "05:00" in en_US locale when this value is set to 2.
    ///   - fractionalSecondsLength: The length of the fractional seconds. For example, one hour is formatted as "1:00:00.00" in en_US locale when this value is set to 2.
    ///   - roundFractionalSeconds: Rounding rule for the fractional second values.
    ///   - grouping: Grouping rule for high minute values.
    /// - Returns: A pattern to format a duration with.
    public static func minuteSecond(padMinuteToLength: Int,
                                    fractionalSecondsLength: Int = 0,
                                    roundFractionalSeconds: FloatingPointRoundingRule = .toNearestOrEven,
                                    grouping: NumberFormatStyleConfiguration.Grouping) -> Self
}
```

## Acknowledgments

Thanks to [@parkera](https://github.com/parkera), [@spanage](https://github.com/spanage), and [@itingliu](https://github.com/itingliu) for helping me shape this API and polish the proposal.