Date 是一种特有的日期数据类型。包含年月日,时分秒数据。
// 声明方法: now 代表当前日期时间
var wakeUp = Date.now
// 声明方法: distantPast 代表至少 2000 年以前
var wakeUp = Date.distantPast
Date 类型支持加减运算,其单位是秒。
var actualSleep = 48880.0
let sleepTime = wakeUp - actualSleep
// 2023-11-27 12:57:16 +0000
print(sleepTime)
Date 格式用文本展示时,经常需要用到格式化器。
private let itemFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "MMM dd"
formatter.locale = Locale(identifier: "en_US")
formatter.calendar = Calendar(identifier: .gregorian)
formatter.timeZone = TimeZone.current
return formatter
}()
如果需要支持多语言,最好使用 setLocalizedDateFormatFromTemplate 方法
// 声明格式化器
let dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.setLocalizedDateFormatFromTemplate("MMMd")
return formatter
}()
// 不同模板的效果(以美国英语为例)
// formatter.setLocalizedDateFormatFromTemplate("MMMd") // Oct 31
// formatter.setLocalizedDateFormatFromTemplate("MMMMd") // October 31
// formatter.setLocalizedDateFormatFromTemplate("yMMMd") // Oct 31, 2024
// formatter.setLocalizedDateFormatFromTemplate("yMMMMd") // October 31, 2024
// formatter.setLocalizedDateFormatFromTemplate("Md") // 10/31
// formatter.setLocalizedDateFormatFromTemplate("yMd") // 10/31/24
// 同一个模板在不同地区的显示效果
// "MMMd" 模板在不同 locale 下的显示:
// formatter.locale = Locale(identifier: "en_US") // Oct 31
// formatter.locale = Locale(identifier: "zh_CN") // 10月31日
// formatter.locale = Locale(identifier: "fr_FR") // 31 oct.
// formatter.locale = Locale(identifier: "de_DE") // 31. Okt
// formatter.locale = Locale(identifier: "ja_JP") // 10月31日
// 使用时
Text(dateFormatter.string(from: date))
Text(date, formatter: dateFormatter)
locale 属性的设置:在使用时间生成文件名时,en_US_POSIX 是一个特殊的区域设置标识符,它对于生成稳定、一致、可供程序处理的日期字符串至关重要。
en_US: 这部分代表 “英语(美国)” 的区域设置。POSIX: 这是最关键的部分。POSIX 是一个计算机标准,当它附加到区域设置后面时,它会强制 DateFormatter 表现出一种特殊的、固定的、非本地化的行为。它会忽略用户设备上所有自定义的区域和语言设置(比如12/24小时制、日历类型是公历还是佛历等)。let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyyMMddHHmm"
// 使用 "en_US_POSIX" locale 是至关重要的,它可以确保日期格式不受用户设备地区和语言设置的影响,从而在所有设备上生成一致的格式。否则在某些地区可能会得到非预期的字符
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
如果不设置 locale = Locale(identifier: "en_US_POSIX") 会发生什么:
"202510140615PM"。"202510141815"。"256810141815"。这会导致你生成的文件名格式完全不统一,如果你日后想根据文件名来解析时间,就会变得异常困难和不可靠。通过设置 en_US_POSIX,你等于在告诉 DateFormatter:“不要管用户手机上怎么设置的,就严格按照我给你的 dateFormat = "yyyyMMddHHmm" 模板来生成字符串。” 这样,无论在世界的哪个角落,哪个用户的设备上,你生成的日期字符串都会是 202510141815 这种完全一致的格式。
对于任何需要进行数据交换、序列化(如存入JSON或数据库)、或者生成固定格式文件名的场景,使用 en_US_POSIX 不仅仅是主流,更是被 Apple 官方和整个社区所推荐的最佳实践 (Best Practice)。它能从根本上避免因用户设备设置不同而引发的各种奇怪的 bug。
| 获取从现在起往前 48 小时的时间点 | Date(timeIntervalSinceNow: -60 * 60 * 48) |
Swift 提供了 Date 处理日期的功能,它封装了年、月、日、时、分、秒、时区等。然而,我们不想考虑其中的大部分,我们想说“给我一个早上 8 点的起床时间,不管今天是哪一天。
为此,Swift 提供了一个类型称为 DateComponents ,它允许我们读取或写入日期的特定部分,而不是整个日期。DateComponents 会将日期所需的所有部分存储为单独的值,这意味着我们可以读取小时、分钟部分,而忽略其余部分。然后我们需要做的就是将分钟乘以 60(得到秒而不是分钟),将小时乘以 60 和 60(得到秒而不是小时)。
如果想要设置一个今天上午 8 点的日期,可以通过 DateComponents 进行具体设置。
//1. 创建一个 DateComponents 对象
var components = DateComponents()
//2. 按需要设置 DateComponents 对象的具体属性
components.hour = 8
components.minute = 0
//3. 使用 Calendar 的方法,传入 DateComponents 参数,生成 Date
let date = Calendar.current.date(from: components)
//由于日期验证的困难,date(from:) 方法实际上返回一个Optional的值
//因此最好使用 nil 合并来表示“如果失败,只需将当前日期还给我”,如下所示:
let date = Calendar.current.date(from: components) ?? .now
我们还可以要求 iOS 提供特定日期的 DateComponents ,然后读出其中具体的属性。我们会返回一个实例,其中包含其所有属性的可选值。虽然我们知道小时和分钟会在那里(因为这些是我们要求的),但仍然需要解开可选值或提供默认值。
//使用 Calendar 的方法,传入 Date 类型给 from 参数,生成 dateComponents
let components = Calendar.current.dateComponents([.hour, .minute], from: someDate)
//用点语法读取 dateComponents 的属性
let hour = components.hour ?? 0
let minute = components.minute ?? 0
依靠非常有效的 format 参数,可以读取我们想要显示日期的任何部分。
//例如,如果我们只想要一个Date的时间,可以这样写:
Text(Date.now, format: .dateTime.hour().minute())
//或者,如果我们想要日、月和年,我们会这样写:
Text(Date.now, format: .dateTime.day().month().year())
//您可能想知道程序如何适应处理不同的日期格式——例如,在英国使用日/月/年,但在其他一些国家/地区,使用月/日/年。神奇的是,我们不需要担心这一点:当我们编写 day().month().year() 时,我们要求的是该数据,而不是排列它,iOS 将使用用户的偏好自动格式化该数据。
//作为替代方案,我们可以直接在日期上使用该 formatted() 方法,传入配置选项,用于格式化日期和时间,如下所示:
Text(Date.now.formatted(date: .long, time: .shortened))
DatePicker 日历选择器 是一个收缩型视图。
//1. 直接使用字符串作为 label
DatePicker(
"Label",
selection: $nextFullMoonDate
)
//2. 使用 label 视图
DatePicker(
selection: $nextFullMoonDate,
label: {
Text("Label")
}
)
**隐藏 label :**如果直接将 DatePicker 的 label 位置的字符串,设置为空。虽然能起到隐藏 label 的作用,但是有两个问题
因此更好的替代方法是使用 labelsHidden() 修饰符,如下所示:
DatePicker("Please enter a date", selection: $wakeUp)
.labelsHidden()
使用 DatePicker 时,需要绑定 Date 类型的状态参数。
//需要先设定一个用于绑定的Date类型参数
@State private var nextFullMoonDate = Date()
@State private var nextFullMoonDate = Date.now
DatePicker(
selection: $nextFullMoonDate,
label: { Text("Label") }
)
通过 displayedComponents 参数,可以设置日期选择器的显示形式。
// 1.默认:如果不提供此参数,用户将看到天、小时和分钟
DatePicker("",
selection: $arrivalDate,
displayedComponents: [.date, .hourAndMinute]
)
// 2.如果使用 .date ,用户会看到月、日和年
DatePicker("",
selection: $theDateAndTime,
displayedComponents: .date
)
// 3.如果使用 .hourAndMinute ,用户只能看到小时和分钟组件
DatePicker("",
selection: $justTime,
displayedComponents: .hourAndMinute
)
in 参数的工作方式与 Stepper 类似。我们可以为其提供一个日期范围,日期选择器将确保用户无法选择超出该范围的日期范围。
//支持从某一天开始选到永远
@State private var arrivalDate = Date()
let fromToday = Calendar.current.date(byAdding: .minute, value: -1, to: Date())!
DatePicker("",
selection: $arrivalDate,
in: fromToday...,
displayedComponents: .date
)
//支持选到某一天截止
@State private var arrivalDate = Date()
DatePicker("",
selection: $arrivalDate,
in: ...Date(),
displayedComponents: [.date, .hourAndMinute]
)
//还可以制定一个具体范围,传入给 in 使用
//例如:创建今天到明天一天
func exampleDates() {
// 根据距离第一个时间点的具体时间,创建第二个时间点(单位为秒)
let tomorrow = Date.now.addingTimeInterval(86400)
// 创建一个时间范围 range
let range = Date.now...tomorrow
}
//例如:创建今天到未来30天的范围
//通过计算属性设定一个范围(高级)
var withinNext30Days: ClosedRange<Date> {
let today = Calendar.current.date(byAdding: .minute, value: -1, to: Date())!
let next30Days = Calendar.current.date(byAdding: .day, value: 30, to: Date())!
return today...next30Days
}
DatePicker("",
selection: $nextFullMoonDate,
in: withinNext30Days
)
从 iOS 14 开始,您可以使用新的 GraphicalDatePickerStyle() 修饰符来获得高级日期选择器,该选择器显示日历以及用于输入精确时间的空格:
struct ContentView: View {
@State private var date = Date.now
var body: some View {
VStack {
Text("Enter your birthday")
.font(.largeTitle)
DatePicker("Enter your birthday", selection: $date)
.datePickerStyle(GraphicalDatePickerStyle())
.frame(maxHeight: 400)
}
}
}