前言:
公司App做了个时间的改动。以前显示出来的时间使我们自己format出来的,也就是说,时间样式是我们自己定义的。比如,年份在月份前面,月份在日子前面。但是不同国家地区的人,他们有自己的日期显示习惯,
有的是:日/月/年
有的是:月/日/年
有的是:年-月-日
有的是:年.月.日.
各种顺序分隔符都有。为了符合当地人习惯,我们不再指定显示格式,而是根据手机系统的格式来显示。
需求:在App里显示的时间样式,根据用户的系统样式即可。也就是说,系统把时间显示成什么样,我们也显示成什么样。但是我们有选择地显示,有些地方只要 年月日,有些地方要 年月日时分秒,有些地方只要 时分。样式仍旧按照系统的来。
代码地址:https://gitee.com/yuency/Autolayout
示例代码类名 【LocalizeDateToPhoneSystemViewController】
部分示例截图:
直接上结论代码,Command + C,V
func localizeDate(date: Date, destinationTimeZone: TimeZone? = TimeZone.current, destinationTemplate: DateTemplate) -> String {
guard let destinationTimeZone = destinationTimeZone else {
return "时区错误"
}
let formatter = DateFormatter()
var formatTemplate = destinationTemplate.getTemplate()
//对于一个没有国际化的App, 获取手机当前是 12小时制 还是 24小时制 "locale:" 参数一定要使用 Locale(identifier: "en_US_POSIX")
if let aH = DateFormatter.dateFormat(fromTemplate: "j", options: 0, locale: Locale(identifier: "en_US_POSIX")),
formatTemplate.contains("HH"),
aH.contains("a") {
formatTemplate = formatTemplate.replacingOccurrences(of: "HH", with: "hh")
}
//对于一个没有国际化的App, 经过实验, 用Locale.current这个方法拿到的语言地区不正确, 比如我设置的是 "简体中文-法国", Locale.current拿到的却是上一次设置的"英语-法国"
//所以在这里使用 Locale.preferredLanguages 来拿到系统设置里的 "首选语言顺序"
let locale = Locale(identifier: Locale.preferredLanguages.first ?? "en_US")
//用下面这个方法就可获得根据系统语言地区得到的格式化样式
//fromTemplate: 传一个临时的样式,告诉DateFormatter你需要哪些时间字段, 如: "yyyyMMdd", "MMddHHss"等, 无需携带格式.
formatter.dateFormat = DateFormatter.dateFormat(fromTemplate: formatTemplate, options: 0, locale: locale)
formatter.timeZone = destinationTimeZone
formatter.calendar = Calendar(identifier: .gregorian)
//注意, 这里就不能写成 Locale(identifier: "en_US_POSIX")啦. 因为手机调整到 12/24小时制的时候, 在中文显示情况下, "AM/PM" 不会转换成 "上午/下午"
//所以, 这里就和template一样,写成相同的语言地区就可以了
formatter.locale = locale
let dateString = formatter.string(from: date)
return dateString
}
输出打印示例:
20210929134455 -> 13:44:55
20210929134455 -> 29/09/2021
20210929134455 -> 29/09/2021 13:44
20210929134455 -> 29/09/2021 13:44:55
2021-09-29 13:44:56 -> 13:44:56
2021-09-29 13:44:56 -> 29/09/2021
2021-09-29 13:44:56 -> 29/09/2021 13:44
2021-09-29 13:44:56 -> 29/09/2021 13:44:56
2021-09-29T13:44:57Z -> 13:44:57
2021-09-29T13:44:57Z -> 29/09/2021
2021-09-29T13:44:57Z -> 29/09/2021 13:44
2021-09-29T13:44:57Z -> 29/09/2021 13:44:57
2022-09-29 07:44:53 +0000 -> 15:44:53
2022-09-29 07:44:53 +0000 -> 29/09/2022
2022-09-29 07:44:53 +0000 -> 29/09/2022 15:44
2022-09-29 07:44:53 +0000 -> 29/09/2022 15:44:53
完整的测试代码如下:
import UIKit
class LocalizeDateToPhoneSystemViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.white
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 16)
label.backgroundColor = .yellow
label.textAlignment = .left
label.numberOfLines = 0
view.addSubview(label)
label.snp.makeConstraints { make in
make.left.equalTo(10)
make.right.equalTo(-10)
make.centerY.equalToSuperview()
}
label.text = """
不同语言地区组合时间格式化示例 (24小时制)
zh-Hans_GL, 简体中文_格林兰
2021年09月29日 13:44:55
en_GL, 英语_格林兰
2022-09-29, 14:28:42
zh-Hans_FR, 简体中文_法国
29/09/2022 14:13:17
en_FR, 英语_法国
29/09/2022 at 14:14:09
zh-Hans_HR, 简体中文_克罗地亚
29. 09. 2022. 14:15:38
en_HR, 英语_克罗地亚
29.09.2022., 14:20:32
zh-Hans_MX, 简体中文_墨西哥
29/09/2022 14:24:41
zh-Hans_MV, 简体中文_马尔代夫
2022年09月29日 14:25:43
en_MV, 英语_马尔代夫
29-09-2022 14:26:18
fi_FI, 芬兰语_芬兰
29.09.2022 klo 15.21.18
"""
//Locale.current 由两个部分组成, 为: "语言_地区" 用下划线分开语言和地区
//Locale.current 这个取系统语言不是很准确,有时候把语言更换了,但是这里仍然没有改变,地区倒能改变
print("当前的语言地区: \(Locale.current), 当前的时区: \(TimeZone.current)")
print("用首选项 \(Locale.preferredLanguages)")
print("\n\n")
var dateString = "20210929134455"
print("\(dateString) -> \(localizeDate(dateString: dateString, dateFormat: .simple, destinationTemplate: .HHmmss))")
print("\(dateString) -> \(localizeDate(dateString: dateString, dateFormat: .simple, destinationTemplate: .yyyyMMdd))")
print("\(dateString) -> \(localizeDate(dateString: dateString, dateFormat: .simple, destinationTemplate: .yyyyMMddHHmm))")
print("\(dateString) -> \(localizeDate(dateString: dateString, dateFormat: .simple, destinationTemplate: .yyyyMMddHHmmss))")
print("\n\n")
dateString = "2021-09-29 13:44:56"
print("\(dateString) -> \(localizeDate(dateString: dateString, dateFormat: .normal, destinationTemplate: .HHmmss))")
print("\(dateString) -> \(localizeDate(dateString: dateString, dateFormat: .normal, destinationTemplate: .yyyyMMdd))")
print("\(dateString) -> \(localizeDate(dateString: dateString, dateFormat: .normal, destinationTemplate: .yyyyMMddHHmm))")
print("\(dateString) -> \(localizeDate(dateString: dateString, dateFormat: .normal, destinationTemplate: .yyyyMMddHHmmss))")
print("\n\n")
dateString = "2021-09-29T13:44:57Z"
print("\(dateString) -> \(localizeDate(dateString: dateString, dateFormat: .utc, destinationTemplate: .HHmmss))")
print("\(dateString) -> \(localizeDate(dateString: dateString, dateFormat: .utc, destinationTemplate: .yyyyMMdd))")
print("\(dateString) -> \(localizeDate(dateString: dateString, dateFormat: .utc, destinationTemplate: .yyyyMMddHHmm))")
print("\(dateString) -> \(localizeDate(dateString: dateString, dateFormat: .utc, destinationTemplate: .yyyyMMddHHmmss))")
print("\n\n")
let date = Date()
print("\(date) -> \(localizeDate(date: date, destinationTemplate: .HHmmss))")
print("\(date) -> \(localizeDate(date: date, destinationTemplate: .yyyyMMdd))")
print("\(date) -> \(localizeDate(date: date, destinationTemplate: .yyyyMMddHHmm))")
print("\(date) -> \(localizeDate(date: date, destinationTemplate: .yyyyMMddHHmmss))")
// tongjigezhogngeshi()
}
/// 重要!
func localizeDate(date: Date, destinationTimeZone: TimeZone? = TimeZone.current, destinationTemplate: DateTemplate) -> String {
guard let destinationTimeZone = destinationTimeZone else {
return "时区错误"
}
let formatter = DateFormatter()
var formatTemplate = destinationTemplate.getTemplate()
//对于一个没有国际化的App, 获取手机当前是 12小时制 还是 24小时制 "locale:" 参数一定要使用 Locale(identifier: "en_US_POSIX")
if let aH = DateFormatter.dateFormat(fromTemplate: "j", options: 0, locale: Locale(identifier: "en_US_POSIX")),
formatTemplate.contains("HH"),
aH.contains("a") {
formatTemplate = formatTemplate.replacingOccurrences(of: "HH", with: "hh")
}
//对于一个没有国际化的App, 经过实验, 用Locale.current这个方法拿到的语言地区不正确, 比如我设置的是 "简体中文-法国", Locale.current拿到的却是上一次设置的"英语-法国"
//所以在这里使用 Locale.preferredLanguages 来拿到系统设置里的 "首选语言顺序"
let locale = Locale(identifier: Locale.preferredLanguages.first ?? "en_US")
//用下面这个方法就可获得根据系统语言地区得到的格式化样式
//fromTemplate: 传一个临时的样式,告诉DateFormatter你需要哪些时间字段, 如: "yyyyMMdd", "MMddHHss"等, 无需携带格式.
formatter.dateFormat = DateFormatter.dateFormat(fromTemplate: formatTemplate, options: 0, locale: locale)
formatter.timeZone = destinationTimeZone
formatter.calendar = Calendar(identifier: .gregorian)
//注意, 这里就不能写成 Locale(identifier: "en_US_POSIX")啦. 因为手机调整到 12/24小时制的时候, 在中文显示情况下, "AM/PM" 不会转换成 "上午/下午"
//所以, 这里就和template一样,写成相同的语言地区就可以了
formatter.locale = locale
let dateString = formatter.string(from: date)
return dateString
}
/// 重要!
func localizeDate(dateString: String, dateFormat: DateFormat, destinationTimeZone: TimeZone? = TimeZone.current, destinationTemplate: DateTemplate) -> String {
guard let destinationTimeZone = destinationTimeZone else {
return "时区错误"
}
let formatter = DateFormatter()
formatter.dateFormat = dateFormat.getFormat()
//1, 格式化为0时区, 针对服务器返回的时间统一处理为0时区, 那么可以写成GMT, 后面再看
// formatter.timeZone = TimeZone(abbreviation: "GMT")
//2, 这里是不管啥时间串,直接格式化为东八区 (拿着手机跑到美国,看到的依然是东八区)
// formatter.timeZone = TimeZone(secondsFromGMT: 8)
//3, 如果对时间字符串没有什么要求,可以按照当前时区格式化, 东八就东八, 东十就东十, 取决于手机
formatter.timeZone = TimeZone.current
formatter.calendar = Calendar(identifier: .gregorian)
formatter.locale = Locale(identifier: "en_US_POSIX")
guard let gmtDate = formatter.date(from: dateString) else {
return "格式化错误, 时间串:\(dateString), 格式串:\(formatter.dateFormat ?? "")"
}
let convertDateString = localizeDate(date: gmtDate, destinationTimeZone: destinationTimeZone, destinationTemplate: destinationTemplate)
return convertDateString
}
/// 额外研究
func qitajieshao() {
//语言和地区,会互相影响最终的时间格式.
//如果是一个有国际化的APP,那么有可能出现其他的形式,如,月份使用了英文 February 或者 中文 月
//要让时间格式只收到地区影响,就要在 DateFormatter.dateFormat(fromTemplate: formatTemplate, options: 0, locale: NSLocale.current) 这一句里指定地区
print("\n\n")
let diqu = NSLocale.current.identifier.components(separatedBy: "_").last ?? "" //通过分隔符把最后的地域拿到
let yuyan = "en"
let yuyandiqu = "\(yuyan)_\(diqu)" //就硬再前面拼上语言,合成一个完整的 语言地域串
//这个 `fromTemplate` 是可以带上格式的,但是会被忽略掉
let fixedFormat = DateFormatter.dateFormat(fromTemplate: "yyyy MM dd HH mm ss", options: 0, locale: Locale(identifier: yuyandiqu))
print("硬凑的语言地区: \(yuyandiqu), 修正后的格式: \(fixedFormat ?? "错误")")
}
/// 额外研究
func tongjigezhogngeshi() {
print("\n\n")
print("🌩🌩🌩🌩🌩🌩🌩🌩🌩🌩🌩🌩🌩🌩🌩🌩")
print("\n\n")
var dic = [String: [String]]()
//带有下划线的,认为是语言+地区的组合, 实际上对于下面的功能来说, 过滤与否, 结果条数都一样, 59条格式
//var yuyandiquArray = Locale.availableIdentifiers.filter { string in
// string.contains("_")
//}
let yuyandiquArray = Locale.availableIdentifiers
for s in yuyandiquArray {
let fixedFormat = DateFormatter.dateFormat(fromTemplate: "yyyy MM dd HH mm ss", options: 0, locale: Locale(identifier: s)) ?? "错误"
if dic.keys.contains(fixedFormat) == false {
dic[fixedFormat] = [String]()
}
dic[fixedFormat]?.append(s)
}
print("取得格式化结果 \(dic.keys.count) 条")
for (key, array) in dic {
print("\n")
print("所用格式: \(key)")
print("所在地区: \(array)")
}
print("\n\n")
let sortFormat = dic.keys.sorted { a, b in
a.count > b.count
}
print("格式化长短排序 \(sortFormat)") //马其顿稳居第一
}
}
enum DateTemplate: String {
case HHmmss
case yyyyMMdd
case yyyyMMddHHmm
case yyyyMMddHHmmss
func getTemplate() -> String {
switch self {
case .HHmmss:
return "HHmmss"
case .yyyyMMdd:
return "yyyyMMdd"
case .yyyyMMddHHmm:
return "yyyyMMddHHmm"
case .yyyyMMddHHmmss:
return "yyyyMMddHHmmss"
}
}
}
enum DateFormat: String {
case normal
case utc
case simple
func getFormat() -> String {
switch self {
case .normal:
return "yyyy-MM-dd HH:mm:ss"
case .utc:
return "yyyy-MM-dd'T'HH:mm:ss'Z'"
case .simple:
return "yyyyMMddHHmmss"
}
}
}
感谢以下iOS玩家的文章:
iOS15.4 NSDateformatter 12小时制日期格式问题及解决
iOS DateFormatter dateFormat fromTemplate 中允许的格式说明符是什么?
苹果官方
swift DateFormatter
结语
又是这么一个小小的时间。