Новый подход к форматированию даты в 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.
Оригинал