iOS 时间字符串format跟随系统语言地区

前言:

公司App做了个时间的改动。以前显示出来的时间使我们自己format出来的,也就是说,时间样式是我们自己定义的。比如,年份在月份前面,月份在日子前面。但是不同国家地区的人,他们有自己的日期显示习惯,

有的是:日/月/年

有的是:月/日/年

有的是:年-月-日

有的是:年.月.日.

各种顺序分隔符都有。为了符合当地人习惯,我们不再指定显示格式,而是根据手机系统的格式来显示。

需求:在App里显示的时间样式,根据用户的系统样式即可。也就是说,系统把时间显示成什么样,我们也显示成什么样。但是我们有选择地显示,有些地方只要 年月日,有些地方要 年月日时分秒,有些地方只要 时分。样式仍旧按照系统的来。

代码地址:https://gitee.com/yuency/Autolayout
示例代码类名 【LocalizeDateToPhoneSystemViewController】

部分示例截图:


示例.png

直接上结论代码,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

结语

又是这么一个小小的时间。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容