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.
|