Новый подход к форматированию даты в Swift

Новый подход к форматированию даты в Swift

9 мая 2023 г.

Что нового в Foundation

В Swift 5.5 и iOS 15 Apple представила новые интерфейсы, которые позволяют разработчикам преобразовывать типы данных из Foundation в локализованные строки и наоборот — FormatStyle, ParseableFormatStyle< /strong> и протоколы ParseStrategy.

Основная цель состоит в том, чтобы предоставить более простой способ создания форматированных отображаемых строк с меньшим количеством настроек вместо использования старого Formatter. подклассы, такие как DateFormatter, DateComponentsFormatter, DateIntervalFormatter, NumberFormatter, MeasurementFormatter, ByteCountFormatter и PersonNameComponentsFormatter.

Этот подход поддерживает даты, диапазоны дат, числа, измерения, последовательности, продолжительность (начиная с iOS 16), URL-адреса (начиная с iOS 16), количество байтов и компоненты имени человека.

В этой статье мы сосредоточимся на том, как преобразовывать даты и диапазоны дат в форматированные локализованные строки для отображения, анализировать объекты Date из строковых констант и создавать собственные стили пользовательского формата для дат.

Форматирование одной даты

Как вы знаете, если вы хотите отображать дату в своем приложении, используя, например, некоторые форматы — короткий, длинный или пользовательский («гггг-ММ-дд»), вам нужно создать экземпляры DateFormatter и установить « свойства dateStyle» или «dateFormat» (для пользовательского стиля):

extension Date {

  func string(formatter: DateFormatter) -> String {
    return formatter.string(from: self)
  }

}
extension DateFormatter {

  static var shortDateFormatter: DateFormatter {
    let formatter = DateFormatter()
    formatter.dateStyle = .short
    return formatter
  }

  static var longDateFormatter: DateFormatter {
    let formatter = DateFormatter()
    formatter.dateStyle = .long
    return formatter
  }

  static var customDateWithDashFormatter: DateFormatter {
    let formatter = DateFormatter()
    formatter.dateFormat = "yyyy-MM-dd"
    return formatter
  }

}
let date = Date()
print(date.string(formatter: .shortDateFormatter)) // 3/12/23
print(date.string(formatter: .longDateFormatter)) // March 12, 2023
print(date.string(formatter: .customDateWithDashFormatter)) // 2023-03-12

На первый взгляд, здесь все хорошо. Но что, если нам нужно добавить еще один пользовательский стиль для отображения даты — «гггг/мм/дд»? Основная проблема заключается в том, что мы должны расширить наши средства форматирования даты, добавив новый:

extension DateFormatter {

  static var customDateWithSlashFormatter: DateFormatter {
    let formatter = DateFormatter()
    formatter.dateFormat = "yyyy/MM/dd"
    return formatter
  }

}
print(Date().string(formatter: .customDateWithSlashFormatter)) // 2023/03/12

Давайте посмотрим, как Apple помогает нам решить эту проблему. Они создали несколько методов, которые помогают нам решить проблему, описанную выше.

public func formatted() -> String


print(Date().formatted()) // 3/12/2023, 2:05 PM

Основной метод, который преобразует Date в локализованную строку, используя стиль преобразования по умолчанию.

public func formatted<F>(_ format: F) -> F.FormatOutput where F : FormatStyle, F.FormatInput == Date


print(Date().formatted(.dateTime.day(.twoDigits).month(.twoDigits).year(.twoDigits))) // 03/12/23

Как мы видим здесь, мы должны передать параметр, соответствующий протоколу FormatStyle. Apple позаботилась и об этом: Swift предлагает нам встроенную структуру, которая соответствует протоколу FormatStyle, и нам не нужно создавать свою собственную.

Кроме того, он имеет статическую переменную «static var dateTime: Date.FormatStyle».

Кроме того, у нас есть возможность создать новый экземпляр Date.FormatStyle:

print(Date().formatted(Date.FormatStyle().day().month(.wide).year())) // March 12, 2023

Date.FormatStyle имеет множество методов, таких как «день(…), месяц(…), год(…) и т. д.»; они имеют свои собственные параметры для настройки и возвращают один и тот же тип (тип Date.FormatStyle). Эти методы экземпляра предлагают нам множество вариантов для создания множества вариантов.

Кроме того, нам не нужно беспокоиться о порядке, в котором мы вызываем функции; Swift позаботится об этом и выберет правильный формат в зависимости от предпочтений пользователя.

public func formatted(date: Date.FormatStyle.DateStyle, time: Date.FormatStyle.TimeStyle) -> String


print(Date().formatted(date: .long, time: .omitted)) // March 12, 2023

Здесь Swift предоставляет некоторые предопределенные стили формата по умолчанию для даты и времени — Date.FormatStyle.DateStyle и Date.FormatStyle.TimeStyle.

public func ISO8601Format(_ style: Date.ISO8601FormatStyle = .init()) -> String


print(Date().ISO8601Format(.iso8601.day().month().year().dateSeparator(.dash))) // 2023-03-12

Он используется для преобразования даты в локализованные строки с использованием формата iso8601. Date.ISO8601FormatStyle также имеет статическую переменную «static var iso8601: Date.ISO8601FormatStyle» и имеет аналогичные методы, такие как Date.FormatStyle. Более того, мы можем использовать метод «formatted(…)», чтобы сделать то же самое:

print(Date().formatted(.iso8601.day().month().year().dateSeparator(.dash))) // 2023-03-12

Форматирование диапазона дат

Точно так же для диапазонов дат легко использовать тот же подход для создания локализованной форматированной строки, потому что Swift предоставляет очень похожие методы, которые у нас есть для даты:

public func formatted() -> String
public func formatted<S>(_ style: S) -> S.FormatOutput where S : FormatStyle, S.FormatInput == Range<Date>
public func formatted(date: Date.IntervalFormatStyle.DateStyle, time: Date.IntervalFormatStyle.TimeStyle) -> String

Мы видим, что у нас уже есть «static var interval: Date.IntervalFormatStyle», и мы можем использовать его так же, как мы используем Date.FormatStyle:

let dateRange = Date(timeInterval: -3600, since: Date())..<Date()
print(dateRange.formatted(.interval.day().month(.wide).year().minute().hour())) // March 12, 2023, 5:18 – 6:18 PM

Или создайте новый экземпляр Date.IntervalFormatStyle:

print(dateRange.formatted(Date.IntervalFormatStyle().day().month(.wide).year().minute().hour())) // March 12, 2023, 5:18 – 6:18 PM

Кроме того, вы можете найти временной разрыв между самой ранней и самой поздней датами в заданном диапазоне дат, используя различные единицы измерения:

print(dateRange.formatted(.components(style: .wide, fields: [.hour]))) // 1 hour

Синтаксический анализ даты

Swift предоставляет несколько вариантов преобразования строки в объект Date.

Давайте создадим строку даты и новый экземпляр Date.FormatStyle с некоторой настройкой, которая ожидает, в каком формате может быть проанализирована наша локализованная строковая константа:

let dateStr = "March 12, 2023"
let formatStyle = Date.FormatStyle().day().month().year()

Apple представила протокол ParseStrategy для выполнения такой задачи. ParseStrategy имеет два связанных типа: Input и Output, для Date вход — это String, а выход — Date. По умолчанию Date.FormatStyle соответствует этому протоколу, поэтому мы можем использовать метод «parse(…)» напрямую:

try? formatStyle.parse(dateStr)

Или мы можем использовать свойство ParseableFormatStyle «parseStrategy» (Date.FormatStyle также соответствует протоколу ParseableFormatStyle по умолчанию):

try? formatStyle.parseStrategy.parse(dateStr)

Swift предоставляет стратегию анализа по умолчанию для Date — Date.ParseStrategy. Его можно использовать с новым инициализатором для даты (для формата мы используем инициализатор интерполяции):

let parseStrategy = Date.ParseStrategy(format: "(month: .wide) (day: .defaultDigits), (year: .defaultDigits)", locale: .current, timeZone: .current)
try? Date(dateStr, strategy: parseStrategy)

Для Date.ISO8601FormatStyle можем проделать те же манипуляции:

let iso8601DateStr = "2023-03-12T18:06:55Z"
let iso8601FormatStyle = Date.ISO8601FormatStyle()
try? iso8601FormatStyle.parse(iso8601DateStr)
try? iso8601FormatStyle.parseStrategy.parse(iso8601DateStr)
try? Date(iso8601DateStr, strategy: iso8601FormatStyle)
try? Date(iso8601DateStr, strategy: iso8601FormatStyle.parseStrategy)

Создание пользовательских стилей формата

Предположим, нам нужно преобразовать некоторые даты в нашем приложении в локализованные строковые константы, используя конкретный календарь или локаль. Мы должны создать собственный объект FormatStyle:

struct UkrainianLocaleFormatStyle: FormatStyle {

  typealias FormatInput = Date
  typealias FormatOutput = String

  private static let customFormatStyle = Date.FormatStyle(date: .long, time: .omitted, locale: Locale(identifier: "uk_UA"), calendar: Calendar(identifier: .gregorian))

  func format(_ value: Date) -> String {
    return Self.customFormatStyle.format(value)
  }

}

Как видим, у нас есть некоторые требования от протокола FormatStyle: для типа Input у нас есть Date, для Output — String, для метода «format(…)» у нас есть создал пользовательский стиль формата. Для использования нашего пользовательского стиля формата мы расширяем FormatStyle:

extension FormatStyle where Self == UkrainianLocaleFormatStyle {

  static var ukrainianLocale: UkrainianLocaleFormatStyle { return UkrainianLocaleFormatStyle() }

}
print(Date().formatted(.ukrainianLocale)) // 12 березня 2023 р.

Заключительные мысли

В этой статье мы сделали краткий обзор того, как преобразовывать типы данных (в нашем примере — Date) в локализованные строки и из них.

Apple представила инструмент с широкими возможностями настройки для форматирования встроенных типов данных, позволяющий разработчикам настраивать правила форматирования в соответствии со своими конкретными потребностями.

Они работают лучше и проще в использовании. Однако есть некоторые ограничения:

* FormatStyle доступен только в iOS 15 и более поздних версиях, поэтому, если ваши проекты имеют более раннюю версию, вам следует использовать один из старых подклассов Formatter.

* FormatStyle не разрешен в Objective-C.


Оригинал