写国际化的程序, 经常会遇到两种问题: 字符编码, 时间问题. 今天我们就聊聊程序中如何处理时间问题.
首先, 要了解一些基本的概念, 只有对概念有清晰的掌握, 才能明白解决方法.
基本概念
GMT 时间: 格林尼治标准时间 (英语: Greenwich Mean Time,GMT) 是指位于英国伦敦郊区的皇家格林尼治天文台当地的平太阳时, 因为本初子午线被定义为通过那里的经线.
由于地球每天的自转是有些不规则的, 而且正在缓慢减速, 因此格林尼治平时基于天文观测本身的缺陷, 已经被原子钟报时的协调世界时 (UTC) 所取代.
UTC 时间: 协调世界时 (英语: Coordinated Universal Time, 法语: Temps Universel Coordonné, 简称 UTC) 是最主要的世界时间标准, 其以原子时秒长为基础, 在时刻上尽量接近于格林尼治标准时间.
对于大多数用途来说, UTC 时间被认为能与 GMT 时间互换, 但 GMT 时间已不再被科学界所确定.
UNIX 时间戳: Unix time 又叫 POSIX time 或 UNIX Epoch time, 是从 UTC 时间 1970 年 1 月 1 日起到现在的秒数, 不考虑闰秒, 一天有 86400 秒.
时区: 时区是地球上的区域使用同一个时间定义. 世界各个国家位于地球不同位置上, 因此不同国家特别是东西跨度大的国家日出, 日落时间必定有所偏差. 这些偏差就是所谓的时差.
闰秒: 闰秒是在协调世界时 (UTC) 中增加或减少一秒, 使它与平太阳时贴近所做调整. 在 UTC 时间中, 有时会出现一分钟有 59 秒或 61 秒.
夏令时: 美国原本于每年 4 月的第一个星期日凌晨 2 时起至 10 月的最后一个星期日凌晨 2 时实施夏时制; 但经美国国会 2005 年通过的能源法案, 自 2007 年起延长夏时制, 开始日期从每年 4 月的第一个星期日, 提前到 3 月的第二个星期日, 结束日期从每年 10 月的最后一个星期日, 延后到 11 月的第一个星期日. 美国夏时制实行与否, 完全由各州各郡自己决定.
时间格式的标准: 参考 ISO_8601 日期格式标准 https://zh.wikipedia.org/wiki/ISO_8601. 例如: 2004-05-03T17:30:08+08:00 在时间前面加上大些字母 T, 要标明偏移的时区时间.
概念解读
通过上面的概念介绍了解到, GMT 就是 0 时区的时间, 以前是标准, 但现在国际上已经用 UTC 取代他了. 在写程序时, 可以认为 UTC 和 GMT 是等价的. 为了严谨只需要关心 UTC 时间.
UNIX 时间戳是程序中最常用的, 他的特点是和 UTC 时间的 1970 年 1 月 1 日到现在的秒数, 和时区无关, 无论在地球上的那个角落, 同一时刻, UNIX 时间戳都是一样的 . 是一个通用的时间偏移度量, 计算每个时区当地时间时, 都可以用时间戳推算出来.
不同时区的时间, 都用 UTC 时间的偏移来计算. 例如北京是东八区, 比 UTC 时间快 8 个小时, 所以计算北京时间, 就在 UTC 时间的基础上加 8 个小时实现.
我们在调用系统函数展示时间时, 底层是根据 UNIX 时间戳转换为 UTC 时间, 再加上偏移的小时数, 就得出了程序要用的当地时间.
UNIX 时间戳可以映射到每个时区的当地时间, 如果程序涉及到两个时区的时间转换, 最好的方法是存储 UNIX 时间戳, 在使用的时候再做转换.
在各种语言的函数库中, 都已经定义了时间时区转换的函数. 在使用时, 还有一点要注意「时区偏移(time offset)」和「时区地区(time zone)」是两个不同的概念.
偏移是一个数学上的值, 直接能计算出时间. 时区地区, 会根据当地的法律规则, 来得出最终的时间, 混入了人为的规则.
例如:
在夏令时时, 北京和纽约时差是 12 个小时, 但是当夏令时结束时, 北京和纽约的时差是 11 个小时. 如果一直用固定的时间偏移, 就会计算出错. 如果用指定的地区当参数, 就会根据当地规则返回正确时间.
具体例子见代码:
- <?PHP
- date_default_timezone_set('Asia/Shanghai');
- $d=strtotime("2018-11-04 13:00:00");
- echo "Beijing" . date("Y-m-d h:i:sa", $d) . "\n";
- date_default_timezone_set('America/New_York');
- echo "NewYork" . date("Y-m-d h:i:sa", $d) . "\n";
- echo "\n";
- date_default_timezone_set('Asia/Shanghai');
- $d=strtotime("2018-11-04 14:00:00");
- echo "Beijing" . date("Y-m-d h:i:sa", $d) . "\n";
- date_default_timezone_set('America/New_York');
- echo "NewYork" . date("Y-m-d h:i:sa", $d) . "\n";
- echo "\n";
- date_default_timezone_set('Asia/Shanghai');
- $d=strtotime("2018-11-04 15:00:00");
- echo "Beijing" . date("Y-m-d h:i:sa", $d) . "\n";
- date_default_timezone_set('America/New_York');
- echo "NewYork" . date("Y-m-d h:i:sa", $d) . "\n";
输出结果
- Beijing 2018-11-04 01:00:00pm // 没结束夏令时时, 时差 12 个小时
- NewYork 2018-11-04 01:00:00am
- Beijing 2018-11-04 02:00:00pm // 夏令时切换, 时差为 11 个小时
- NewYork 2018-11-04 01:00:00am
- Beijing 2018-11-04 03:00:00pm
- NewYork 2018-11-04 02:00:00am
总结
涉及到多个时区的转换, 统一使用 unix 时间戳存储或交互, 或者使用带有时区信息的字符串.
尽量在上层的代码层面修改时区配置, 不要修改系统或软件的配置, 防止其他程序因为修改受到影响.
本质: 时区概念是上层人为转换的概念, 程序的逻辑不要依赖于他, 要有个统一的时刻值概念来衡量真实的时间(例如 UNIX 时间戳), 然后在上层做转换.
参考
- https://zh.wikipedia.org/wiki/格林尼治標準時間
- https://en.wikipedia.org/wiki/Unix_time
- https://zh.wikipedia.org/wiki/协调世界时
- https://zh.wikipedia.org/wiki/时区
- https://www.cnblogs.com/zihanxing/articles/6224263.html
来源: http://www.tuicool.com/articles/qY7JvuR