首页 / 资讯中心 / 文章详情

iOS日期格式化避坑:yyyy与YYYY跨年差异与工程封装

iOS日期格式化避坑:yyyy与YYYY跨年差异与工程封装 ★ FEATURED ARTICLE
「元旦那天的日报数据和昨天一模一样但昨天的日报和前天的又对不上。」这是我在做一款数据看板类 App 时遇到的一个典型问题两个统计模块在跨年前后各错了一天查了半天接口、缓存、时区最后定位到的差异只是格式化字符串里一个小小的字母——有人写成了大写YYYY-MM-dd。iOS 上的日期格式化看起来只是拼一串字母实际上每个字母都对应 Unicode 日期模式规范里一个明确且互不等价的语义大小写不同含义就完全不同。这篇内容围绕yyyy-MM-dd和YYYY-MM-dd的取舍展开把 Calendar、Locale、TimeZone 这几个隐藏变量一起拆开讲给出可以直接复用的封装代码和排查清单。不管你是刚接触 iOS 开发、只会照抄格式串的新手还是已经写过几年业务、想把日期处理彻底理顺的老手下面这些内容应该都能省下你几个晚上的排查时间。1. 从一个“元旦当天数据错乱”的线上问题说起1.1 大写 Y 到底代表什么先把结论摆出来yyyy是calendar year日历年也就是我们日常理解的那个年份YYYY是week-based year周历年也叫周所属年。这两个概念在一年中绝大部分时间是重合的所以你在三月份随手写一个YYYY-MM-dd输出看起来完全正确测试也过代码就这么合进去了。周历年的定义追溯于 ISO 8601 的周编号体系一个“周”不是从 1 月 1 日算起而是从某个固定的星期几算起跨越了自然年的边界。也就是说12 月 31 日所在的那一周如果大部分天数落在下一年那么这一整周包括 12 月 31 日在周历体系里属于下一年。反过来1 月 1 日所在的那一周如果大部分天数落在上一年那 1 月 1 日的周历年就是上一年。用一个生活化的类比假设公司按“周”给销售算业绩年末最后一周横跨了 12 月 31 日和 1 月 1 日。财务不会把这一周劈成两半算进两个年度而是整周归到某一年。周历年就是这套记账逻辑在日期表示上的投影。所以YYYY更接近“这个日子归属于哪个业绩年度”而yyyy是“这个日子在日历上挂着哪一年的牌子”。两种语义都真实存在、都有用武之地问题在于绝大多数场景我们需要的是后者而YYYY的存在感又足够强强到能让代码看起来毫无破绽地跑上大半年。1.2 差异只在跨年周几年数据摆在一起看要理解这个坑有多隐蔽我们得知道它的触发窗口到底有多窄。下面这组数据是我在项目里用en_US_POSIX加东八区实际跑出来的格式串分别是yyyy-MM-dd和YYYY-MM-dd日期星期yyyy-MM-dd 输出YYYY-MM-dd 输出是否一致2024-01-01周一2024-01-012024-01-01一致2023-01-01周日2023-01-012022-01-01不一致2022-01-01周六2022-01-012021-01-01不一致2021-01-01周五2021-01-012020-01-01不一致2020-12-31周四2020-12-312020-12-31一致2018-12-31周一2018-12-312019-12-31不一致2017-01-01周日2017-01-012016-01-01不一致规律非常清楚每年的 1 月 1 日到 1 月 3 日、以及 12 月 29 日到 12 月 31 日这几天是唯一可能出问题的时间窗口。如果你的代码在 3 月写完11 月上线一路平安无事直到元旦凌晨定时任务跑起来才发现落库的分区键全错位了——这就是为什么这个 bug 总能精确地在假期里爆炸。注意 2021-01-01 那一行它是往“回”错的用YYYY输出成 2020比实际年份还小。这个方向的反直觉程度更高因为大家潜意识里觉得“格式化最多是精度问题怎么会往前跳”。实际上 2020 年有 53 个 ISO 周2021 年的 1 月 1 日到 1 月 3 日属于 2020 年的第 53 周整周归 2020所以周历年是 2020。顺便说明上面表格里的结果依赖于 Calendar 的周起始日设定。如果你把 Calendar 换成en_US周日为一周第一天而不是 ISO 体系跨年那几天的归属会发生变化。这意味着YYYY的输出不仅取决于日期本身还取决于 Calendar 配置——这一点我在第 2 章会展开。1.3 整张大小写对照表别只盯着 Y把注意力只放在Y和y上是不够的。Unicode 日期模式串里几乎所有字母都有大小写配对而且每一对的语义都不同。我整理了一份日常最常踩的模式字符含义常见误用与后果y/Y日历年 / 周历年跨年周错位最经典的一个M/m月份 / 分钟把分钟写成MM输出会变成月份数字时间全错d/D月内日 / 年内第几天DD输出如365拼进日期串直接不可读H/h24 小时制 / 12 小时制hh在下午会输出03而不是15且不带 AM/PM 就无法还原W/w月内第几周 / 年内第几周做周报统计时用错维度直接跑偏S毫秒小数秒写SS只有两位高位会被吞掉E/e星期名称 / 本地化的星期序号需要数字星期却拿到“周一”“Monday”其中MM和mm的混用在 iOS 上是第二高频的错误。HH:mm:ss是标准写法但很多人手快写成HH:MM:ss输出会变成类似14:03:27的诡异结果——因为中间的MM被解析成了月份。这种错误最讨厌的地方在于它不一定马上暴露在某些时刻比如分钟数刚好和月份数接近看起来还挺正常等你在别的时刻再测就完全对不上。我记得有个团队排查过一个问题某条日志的时间戳在上午看起来正常下午就不对。查到最后发现格式串是h:mm:ss压根没带aAM/PM标记下午 3 点被格式化成3:20:11和其他系统的 24 小时制数据混在一起排序就全乱了。提示不要试图用记忆去区分这些字母。把所有用到的格式串集中定义在一处常量文件里用有语义的命名比如fullDateOnly、timestampWithMillis每次新增格式串都要经过一次 code review这个成本远低于线上排查。2. 决定格式化结果的四个变量格式串只是其中之一2.1 Locale 与 Calendar佛历、和历与 12 小时制很多人以为dateFormat一旦设定输出就确定了。实际上最终结果由格式串、Locale、Calendar、TimeZone四者共同决定格式串只是其中之一另外三个默认为当前用户环境——而用户环境是不可控的。先说 Locale。DateFormatter如果不显式设置locale默认取Locale.current。这带来两类问题。第一类是 12/24 小时制如果系统设置里开了 24 小时制某些风格的格式化输出会跟着变你在测试机上看着是14:30用户机上变成2:30 PM。第二类更隐蔽某些地区的默认 Calendar 不是公历。用户在设置里把区域切到使用佛历的地方yyyy可能输出 2567 这种数字切到使用和历的区域年份可能变成“令和六年”。这些场景在真实用户中占比很小但只要存在就会出现你完全无从复现的“日期显示错误”反馈。解决方案是统一使用en_US_POSIX这个特殊的 Locale。它的作用不是“变成英文”而是把 Locale 相关的可变因素全部锁死公历、固定 24 小时制、固定数字符号、固定周起始日。它专门为“格式化字符串必须稳定可解析”的场景设计苹果的开发者文档里专门用一节讲过为什么必须这么做凡是格式串固定写死的场合都应该用它。let formatter DateFormatter() formatter.locale Locale(identifier: en_US_POSIX) // 关键锁定区域行为 formatter.dateFormat yyyy-MM-dd HH:mm:ss注意en_US_POSIX只适用于格式串写死的情况。如果你的界面要按用户语言显示“2024年3月5日”这类本地化日期应该用dateStyle/timeStyle让系统去决定格式这时候不要碰dateFormat。再补一个容易忽略的点dateStyle和dateFormat是互斥的后设置的会覆盖先设置的。有人写了formatter.dateStyle .medium然后又设dateFormat结果dateStyle被悄悄清空界面在英文环境下显示成纯数字日期看起来像是“本地化失效了”其实是自己把配置冲掉了。2.2 TimeZone比大小写更沉默的破坏者如果排序一下 iOS 日期问题的破坏力我会把 TimeZone 排第一YYYY排第二——只是因为YYYY更隐蔽所以更有名。DateFormatter的timeZone默认是TimeZone.current这意味着一份 UTC 时间戳在不同时区的用户设备上会被格式化成不同的本地日期串。举个具体场景服务端返回1704038400这个时间戳换算成东八区是 2024-01-01 00:00:00。你在东八区的设备上格式化成2024-01-01完美。但这个用户飞到西五区同一份数据就被格式化成2023-12-31落到本地的“每日打卡”记录里新年第一天的记录消失了跑到上一年去了。这类问题没有一劳永逸的方案只有明确约定。我的做法是把时区分成两类数据类型时区策略理由展示给用户的“本地时间”显式TimeZone.current用户要看的是自己所在时区的钟表时间业务日期生日、账单日、统计日固定业务时区这些日期有唯一业务含义不能随设备漂移日志、文件名、接口报文固定 UTC 或固定业务时区必须全局唯一便于跨系统比对时间戳存储无所谓存 Unix 时间戳时间戳本身是绝对的格式化时才注入时区关键点是显式。不要依赖默认值每一处格式化都问自己一句这个时间代表的是“此刻的绝对时刻”还是“某个业务意义上的日子”如果是后者就必须固定时区。2.3 解析方向的宽容度陷阱上面讲的都是格式化方向Date 转 String解析方向String 转 Date有另一套坑。DateFormatter在解析时默认是宽松模式格式串写yyyy-MM-dd给它2024-02-31这种不存在的日期它不会返回 nil而是自动滚动到 3 月 2 日。更麻烦的是YYYY在解析方向上基本是个宣传单。你写formatter.dateFormat YYYY-MM-dd去解析2024-01-01结果取决于这一天属于哪个周历年如果解析失败date(from:)返回 nil你的代码如果没做判空就会走进if let的 else 分支用一个默认日期兜底然后整条业务链静默错下去。我的处理方式是在封装层里强制开启严格模式formatter.isLenient false开启之后2024-02-31会直接返回 nil 而不是滚动。这是我在做一份历史数据导入功能时被迫加上的——那批数据里有大量 2 月 30 日的手工录入错误宽松解析把它们全变成 3 月初错误一直到对账阶段才被发现返工代价极大。解析方向还有一条经验永远不要用带YYYY的格式串去解析用户输入或接口数据。解析的场景几乎都是“我知道这个字符串是标准日期”这种情况下yyyy是唯一合理的选择。3. 落地实操一套可直接复用的日期工具层3.1 设计原则单一出口、显式时区、格式常量集中管理聊完原理进入实际能抄的部分。先说我在多次重构之后固定下来的三条原则它们是这套封装的基础第一所有格式串必须有名字不许内联。你在业务代码里搜dateFormat 应该搜不到东西。所有格式串定义在一个 enum 里命名体现语义而不是格式比如.businessDateOnly而不是.yearMonthDay。这样将来要调整格式比如业务需要加毫秒只改一处。第二每个格式器实例在创建时就固定 Locale 和 TimeZone之后不再改。这是从性能和线程安全两个角度同时考虑的下面 3.3 会详细说。第三对外只暴露“带明确语义的方法”不暴露 DateFormatter 实例。业务层调用应该是DateTool.string(from: date, format: .businessDateOnly)而不是自己 new 一个 formatter。前者你能保证所有细节一致后者你保证不了。3.2 完整代码实现下面这套代码是我目前项目里在用的精简版可以直接拿走enum DateFormatStyle: String { /// 业务日期例如 2024-03-05 case businessDate yyyy-MM-dd /// 带时间的完整时间戳例如 2024-03-05 14:30:00 case timestamp yyyy-MM-dd HH:mm:ss /// 带毫秒用于日志 case timestampMillis yyyy-MM-dd HH:mm:ss.SSS /// 周维度标识例如 2024-W10 case weekBasedYear YYYY-Www /// 文件名安全格式不含冒号 case fileNameSafe yyyyMMdd_HHmmss } enum DateTool { /// 业务固定时区所有跟业务日期相关的格式化都走这里 static let businessTimeZone: TimeZone TimeZone(secondsFromGMT: 8 * 3600)! private static var cache: [String: DateFormatter] [:] private static let lock NSLock() private static func formatter(for style: DateFormatStyle, timeZone: TimeZone) - DateFormatter { let key style.rawValue | timeZone.identifier lock.lock() defer { lock.unlock() } if let cached cache[key] { return cached } let f DateFormatter() f.locale Locale(identifier: en_US_POSIX) f.timeZone timeZone f.dateFormat style.rawValue f.isLenient false cache[key] f return f } /// Date - String时区必须显式传入 static func string(from date: Date, format style: DateFormatStyle, timeZone: TimeZone businessTimeZone) - String { formatter(for: style, timeZone: timeZone).string(from: date) } /// String - Date失败返回 nil绝不返回兜底日期 static func date(from text: String, format style: DateFormatStyle, timeZone: TimeZone businessTimeZone) - Date? { formatter(for: style, timeZone: timeZone).date(from: text) } /// 判断两个时刻是否落在同一个业务日 static func isSameBusinessDay(_ a: Date, _ b: Date) - Bool { let calendar Calendar(identifier: .gregorian) var cal calendar cal.timeZone businessTimeZone return cal.isDate(a, inSameDayAs: b) } /// 业务日的起止时刻常用于查询区间 static func businessDayRange(of date: Date) - (start: Date, end: Date)? { var cal Calendar(identifier: .gregorian) cal.timeZone businessTimeZone guard let start cal.startOfDay(for: date).date(byAdding: .day, value: 1, to: date) else { return nil } return (cal.startOfDay(for: date), start) } }几个设计点值得说明。weekBasedYear这个 case 用的是YYYY-Www注意中间的单引号——在 Unicode 模式串里单引号包裹的内容会被当作字面量原样输出不会被当作模式字符。如果不加引号直接写W它会被解释成“月内第几周”输出的就不是你想要的东西了。字面量转义是一个高频细节格式化人名、单位、分隔符时都会用到。isLenient false放在统一构造里避免某个调用点忘记设置。DateTool的缓存用NSLock保护。这里其实可以用串行队列或者os_unfair_lock但NSLock的可读性最好锁竞争在这个粒度上几乎不可能成为瓶颈。注意如果你的 App 支持多语言businessDate这种纯数字日期在界面上展示是没问题的但如果要给用户看“3月5日 星期二”这类文案请走DateFormatter的dateStyletimeStyle不要用自定义格式串硬拼否则本地化一定会出问题。3.3 性能与线程安全DateFormatter的创建成本比大多数人想象的高。它内部要初始化 ICU 的格式化引擎首次创建可能耗时到毫秒级后续复用则非常廉价。我做过一个粗略对比在一个 200 行的列表里每行 cell 配置时新建一次DateFormatter滚动时的掉帧肉眼可见换成缓存复用之后完全平滑。不过缓存也要注意粒度。如果按“格式串 时区”做 key组合数量是可控的一般不超过十几种如果按“用户 ID 格式串”做 key 那就失控了。上面代码里 key 的设计就是基于这个考虑。线程安全方面DateFormatter从较早的系统版本起就被官方标记为线程安全的但有一个前提创建配置完成之后不再修改。如果你的代码里出现“取出缓存实例改一下 dateFormat再用”的写法那就等于把线程安全丢掉了。所以我在封装里把所有配置项都封在私有构造方法里外部拿不到可变实例从设计上杜绝这种误用。还有一个常被忽略的开销来源Calendar的计算。isDate(_:inSameDayAs:)、startOfDay(for:)这些方法内部要做时区和日历换算比字符串格式化便宜但也不适合放在滚动列表的高频路径里。如果列表里要显示“今天/昨天”这种相对文案建议在数据层一次性算好把结果放进 ViewModel别在 cell 配置里算。3.4 存量代码的排查与迁移老项目里已有的YYYY-MM-dd怎么写是更现实的问题。我的做法分三步。第一步全局搜索YYYY这个字面量。搜索范围包括 Swift 文件、Objective-C 文件、JSON 配置文件、本地化字符串表。注意别漏掉那些从服务端下发的格式串——有些业务灵活配置的系统会把日期格式写在配置中心这种要单独排查。第二步对每一个搜到的位置判断它到底是“周维度场景”还是“日维度场景”。90% 的情况是日维度直接改成小写。剩下 10% 才是需要仔细确认的比如周报统计、按周分组的销售看板、ISO 周编号展示。第三步写一个校验用例固化下来。比如func testCrossYearWeekBoundary() { var comps DateComponents() comps.year 2021; comps.month 1; comps.day 1 var cal Calendar(identifier: .gregorian) cal.timeZone DateTool.businessTimeZone let date cal.date(from: comps)! XCTAssertEqual(DateTool.string(from: date, format: .businessDate), 2021-01-01) // 周历年场景下1 月 1 日属于上一年最后一周 XCTAssertEqual(DateTool.string(from: date, format: .weekBasedYear), 2020-W53) }把跨年那几天固定成测试用例是防止这个坑复发的唯一有效手段。光靠 code review 靠不住因为YYYY在 90% 的日期上表现正确reviewer 的注意力根本不会落在那里。4. 场景决定选型哪些地方该用 YYYY哪些绝对不行4.1 按周聚合周历年才是正确答案前面花了大量篇幅讲YYYY怎么坑人但它本身并不是错的。在真正的周维度场景里YYYY反而是唯一正确的选择。设想一个销售看板统计口径是“每周一到周日的销售额”。2020 年 12 月 28 日周一到 2021 年 1 月 3 日周日这一周横跨两个自然年。如果按yyyy分组这一周会被劈成两段12 月 28 到 31 号算 2020 年1 月 1 到 3 号算 2021 年你会得到两个不完整周的数据周环比、周同比全部失真。用YYYY-ww分组就完全没问题整周都归到2021-W01数据完整。这时候如果你“为了安全”改成小写yyyy-ww反而会得到2020-W01和2021-W01两个组——因为同一个周编号挂在了两个年份下看起来更离谱。所以这个场景的判断标准很清晰只要分组单位是“周”就用YYYY只要分组单位是“天”就用yyyy。这两者之间没有模糊地带模糊的是你对业务口径的理解。补充一个实操细节做周维度统计时除了年份ww本身也受 Calendar 的周起始日影响。如果你要严格对齐 ISO 8601周一开始、包含 1 月 4 日的周是第 1 周需要在 Calendar 上设置firstWeekday 2和minimumDaysInFirstWeek 4光靠格式串是不够的。这一点很多人不知道用默认的en_US_POSIX时周编号可能和预期的 ISO 编号差一位。var cal Calendar(identifier: .iso8601) // 直接用 ISO 日历避免手动配 cal.timeZone DateTool.businessTimeZone4.2 文件名、日志、接口报文绝对用小写跟周维度场景相反下面这几类场景没有任何讨论空间一律用yyyy。文件名。用yyyyMMdd_HHmmss生成的导出文件名、截图名、备份名本质上是给“这个文件产生的那个日子”打标签不涉及任何周归属。用YYYY的后果是每年跨年那几天文件名里的日期和实际日期对不上用户排查问题时会被误导。另外文件名格式要避开冒号HH:mm:ss里的冒号在部分文件系统里是非法字符这也是为什么我单独加了一个fileNameSafe的 case。日志。日志的时间戳必须全局唯一可排序用YYYY会导致跨年那几天的日志时间倒退——2021 年 1 月 1 日的日志被标记成 2020 年日志分析工具按时间排序时就乱了。日志场景还建议带上毫秒和明确时区标注比如2024-03-05 14:30:00.1230800避免不同机器日志混在一起时无法区分。接口报文。这条是给服务端打交道的硬规矩。跟服务端约定日期格式时永远只约定yyyy-MM-dd HH:mm:ss并且约定时区我一般建议直接约定 UTC或者干脆用时间戳。接口字段里出现大写Y是灾难性的因为解析端的实现可能各不相同有的严格按周历年解析然后报错有的宽松模式直接当成小写处理然后静默不一致排查成本极高。4.3 跨端一致性和安卓、服务端对齐同一份数据在 iOS、安卓、服务端、Web 后台同时被格式化是最容易出隐性不一致的场景。这三端用的日期库不一样对 Unicode 模式串的支持程度也不完全一样。有的库对模式串大小写不敏感——你把yyyy写成YYYY它也能正确解析成日历年。这会让问题更糟iOS 上错、别的端对数据对不上时你会先怀疑 iOS但如果反过来某端写错 iOS 对你排查时就会一头雾水。我的处理办法是定一份“日期格式契约”放进技术规范明确规定三件事能用的格式串白名单、日期字段的默认时区、解析失败的统一行为报错还是丢弃绝不兜底。白名单里只放yyyy-MM-dd、yyyy-MM-dd HH:mm:ss、yyyy-MM-ddTHH:mm:ssZZZZZ这几个任何新的格式串要加进白名单都需要评审。这个规定听起来有点重但实施之后跨端日期问题基本归零了。顺带说一个跨端常吵的点ZZZZZ和Z的区别。Z输出0800ZZZZZ输出08:00带冒号。ISO 8601 标准要求带冒号所以跟 Web 端对接时用ZZZZZ更稳妥。如果对方按严格 ISO 解析收到0800可能直接失败。5. 常见问题排查实录5.1 问题速查表把日常遇到的情况整理成一张表方便对照定位现象最可能的原因排查动作元旦前后日期整体偏移一年格式串用了YYYY检索全部YYYY字面量月份显示正确分钟显示成月份数字HH:MM:ss写错大小写检查格式串中MM的位置下午时间显示成 3 点而不是 15 点用了hh且没带a改成HH日期串里出现365这种三位数误用DD年内第几天改成dd部分用户看到 2567 年用户 Locale 为佛历格式化时锁定en_US_POSIX同一条数据在不同设备显示不同日期未固定 TimeZone显式指定业务时区解析不报错但结果偏了一天宽松解析把非法日期滚动了设置isLenient false界面日期没按语言本地化设置了dateFormat覆盖掉dateStyle二选一别混用列表滚动卡顿高频路径新建 DateFormatter加缓存按格式串复用5.2 三次印象深刻的排查过程第一次是关于“周报重复”的。某次迭代后运营反馈周报里有一周出现了两条记录日期分别是 12 月 30 日和 1 月 1 日。我们一开始怀疑是任务调度重复触发排查了定时器配置、分布式锁、幂等键全都没问题。后来把两条记录的原始时间戳拉出来对比发现是同一个时间点被格式化成了两个不同的周标识。原因是统计代码里有两处格式化一处写的是YYYY另一处写的是yyyy而且当时ww的周起始日配置也不一致双重错位。修复方案是统一到封装层并把周起始日显式设成 ISO 标准。第二次是关于“文件名重复覆盖”的。有个导出功能文件名格式是导出_YYYYMMdd.xlsx用户在同一天多次导出会覆盖上一次的文件。这个问题大半年的时间都没人发现因为覆盖后内容其实差不多。直到某年元旦用户反馈“导出的文件日期是去年”我们才顺着查到YYYY。修复后顺便把文件名改成了带时分秒的格式彻底解决覆盖问题。这件事给我的教训是能用时间戳做文件唯一标识就别用日期串日期串的粒度根本不够。第三次是关于“打卡跨天”的。一个习惯打卡功能记录用户每天是否打卡用业务日期做唯一键。有用户反馈自己在 23 点 50 分打卡结果第二天的记录变成了已打卡状态。排查发现是业务日期的计算用了Calendar.current而该用户设备时区设成了另一个时区导致startOfDay算出来的边界和服务器不一致。最后的方案是所有打卡相关的时间计算都强制走固定业务时区客户端只负责展示判定逻辑放到服务端统一处理。凡是涉及“哪一天”的业务判定判定权应该收归单一位置。5.3 上线前自检清单最后给一份我每次涉及日期模块改动时都会过一遍的清单条目不多但每条都救过我全局搜索YYYY确认每一处都是周维度场景其余全部改小写全局搜索dateFormat确认每一处都在设置后紧跟locale en_US_POSIX确认所有TimeZone都是显式的没有依赖默认值确认所有解析路径都设置了isLenient false且有 nil 分支处理确认没有在同一处同时设置dateStyle和dateFormat确认高频路径列表、循环里没有新建DateFormatter跑一遍跨年测试用例覆盖 12 月 29 日到 1 月 3 日如果涉及周维度确认 Calendar 的firstWeekday和minimumDaysInFirstWeek符合业务口径提示这套检查里最容易被跳过的是最后一条。周起始日不同导致的偏差通常只有一两天肉眼极难发现但在跨年那一周会放大成整整一周的错位属于典型的“平时没事出事就是大事”。我个人在实际操作中的体会是日期处理这件事的难度从来不在“怎么写格式化串”而在于你愿不愿意在写之前停下来想两秒这个时间在我业务里到底代表什么是绝对时刻、是本地钟表时间、还是某个业务意义上的日子这三个问题的答案不同Locale、TimeZone、格式串的选择就完全不同。我见过太多项目的日期工具层是东拼西凑长出来的十几种格式串散落在各个文件里没人说得清哪个用在哪。等到真出问题时排查成本是重构成本的五倍以上。所以如果现在让我给一个建议我会说如果你手上项目的日期格式化还是一盘散沙抽半天时间把它收敛到一处比你现在修任何一个具体 bug 都值。
阅读完成 · 觉得有帮助?
咨询建站