Swift 中的新日期格式化程序 API

从 Swift 5.5 和 iOS 15 开始,我们有了一个新的格式化程序 API,它允许我们以更具声明性和直观性的方式显示字符串日期。在深入研究之前,让我们回顾一下实际的格式化程序 API 如何与以下示例一起工作。

extension DateFormatter {   
   static let MMddyy: DateFormatter = {
      let formatter = DateFormatter()
      formatter.timeZone = TimeZone(abbreviation: "UTC") //TimeZone.current
      formatter.dateFormat = "MM/dd/yy"
      return formatter
   }()
}

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

let date = Date()
print(date.formatToString(using: .MMddyy) // 07/18/2021

这已经足够了。我们使用静态工厂模式来创建一个可重用的 DateFormatter,然后我们创建一个实用函数来将 Date 解析为 String,给定特定的格式化程序

新的格式化程序 API 解决了这个问题,让我们可以描述我们想要显示日期的方式,而不是配置 DateFormatter。我们现在有四种格式函数可供使用:

// 1.
func formatted() -> String

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

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

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

让我们从最基本的例子开始,使用选项一。这会将我们的日期转换为默认的字符串格式(日期 + 时间)。

let date = Date.now

print(date.formatted()) // 07/01/2021, 1:38 PM

我们无法进行任何转换,但在某些情况下,这种格式对我们来说已经足够了。如果我们想自定义日期格式,我们需要使用第二个选项。

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

一开始有点棘手,但是一旦您了解如何实际使用它就会变得更容易。
我们需要提供一个 FormatStyle 作为参数才能使用该函数。
此 FormatStyle 是一种协议,它具有两种关联类型:输入和输出。在这种情况下,输入需要是 Date 类型。幸运的是,Swift 已经为我们提供了一个符合我们需要的 FormatStyle 协议的结构体:Date.FormatStyle。这个 FormatStyle 有一个静态变量dateTime: Date.FromatStyle,我们可以直接用作函数的参数。
我们现在有一个 FormatStyle 实例,我们可以使用Date.FormatStyle的实例方法对其进行自定义(您可以查看官方文档中的列表)。让我们看几个例子。

let date = Date.now

var stringDate = 
   date.formatted(
      .dateTime
      .month(.wide)
      .day(.twoDigits)
      .year()
   )

print(stringDate) // July 01, 2021
date.formatted(
   .dateTime
   .month(.narrow)
   .day()
   .year(.twoDigits)
)

这里我们使用 dateTime 变量(它是一个 Date.FromatStyle 实例)并调用月、日和年函数。所有三个函数都返回一个 Date.FromatStyle 类型,这是函数参数所期望的类型。我们只是在操纵格式。我们可以使用这些实例方法实现很多组合。
请注意,我们调用函数的顺序不会影响最终输出。Swift 为我们做了繁重的工作,并根据用户的喜好决定正确的格式。
现在想象一下,我们需要以特定格式向后端发送一个字符串日期?(如“2021-07-18”)。对于这种情况,我们需要使用选项三。与 Date.FormatStyle 一样,ISO8601FormatStyle 有一个静态变量供我们使用,iso8601。这个格式函数与我们之前看到的函数非常相似,但有一些额外的配置可用。

let date = Date.now

let stringDate = 
   date.formatted(
      .iso8601
      .month(.twoDigits)
      .day(.twoDigits)
      .year()
      .dateSeparator(.dash)
   )

print(stringDate) // 2021-07-18

使用 ISO8601FormatStyle 我们可以指定我们想要使用的日期分隔符。
现在,如果我们只对显示日期字符串感兴趣,但我们对格式没有特定要求,我们可以使用最后一个选项(第四个)和一些预定义的格式。Date.FormatStyle.DateStyle 和 Date.FormatStyle.TimeStyle 都为我们提供了几个随时可用的静态常量

let date = Date.now

let stringDate = 
   date.fromatted(date: .long, time: .omitted)

print(stringDate) // July 18, 2021

后缺少的部分是相反的部分,我们如何从具有特定格式的字符串创建日期类型?为此,我们必须使用新的 Date 初始值设定项:

init<T, Value>(_ value: Value, strategy: T) throws 
  where T : ParseStrategy, Value : StringProtocol,
        T.ParseInput == String, T.ParseOutput == Date

我们必须向函数传递一个字符串(它将是具有自定义格式的字符串类型中的日期)和一个解析该字符串的策略。此策略参数必须是ParseStrategy类型。与FormatStyle一样,ParseStrategy也是一个协议,并且还有两个相关的类型(输入和输出)。输入必须是字符串,输出必须是日期。
对我们来说好消息是,像FormatStyle.Date一样,我们已经有一个符合ParseStrategy协议的内置结构:Date.ParseStrategy。我们只需要创建一个新实例即可将其用作 Date 的 init 函数中的参数。

init(
   format: Date.FormatString, 
   locale: Locale? = nil, 
   timeZone: TimeZone, 
   calendar: Calendar = Calendar(identifier: .gregorian), 
   isLenient: Bool = true, 
   twoDigitStartDate: Date = Date(timeIntervalSince1970: 0)
)

假设我们将从后端接收一个日期字符串,格式如下:dd-MM-yyyy(例如 31-01-2021)。让我们首先创建我们的 ParseStrategy 实例,然后使用解析创建一个新的 Date 实例。


let parseStrategy = 
   Date.ParseStrategy(
      format: "\(day: .twoDigits)-\(month: .twoDigits)-\(year: .defaultDigits)",
      locale: Locale(identifier: "es"),
      timeZone: .current
   )

let serverDate = try? Date("01-08-2021", strategy: parseStrategy)

由于格式是 Date.FromatString 类型,我们可以使用插值初始值设定项结合 Date.FormatStyle.Symbol 来创建我们的日期格式。
请注意,在我写这篇文章的时候,所有这些功能都处于测试阶段,正式版本可能会有变化。

加入我们一起学习SwiftUI

QQ:3365059189
SwiftUI技术交流QQ群:518696470
教程网站:www.openswiftui.com

发表回复