程式中的時間議題
簡介
如果要做一個國際化的應用程式,就需要對應各個國家/文化的時間格式。
議題
- 時間的表示法
- 格式、排序
- 年份
- 紀元
- 年號
- 月份
- 星期
- 每個星期的開始日
- 小時
- 時區
- 曆法
- 一段時間
- 時區
- 夏令時間
- 閏年、閏月、閏秒
- 實作
- 資料儲存
- 精度
- 時間同步
- 生日
- 時間段和時間點
時間表示法
參照ISO8601標準,下列以⭐代表標準表示法。
格式
年由4位數字組成YYYY,月、日用兩位數字表示:MM、DD。
只使用數字為基本格式。使用短橫線"-"間隔開年、月、日為擴展格式。
- 日曆日期表示法
- 2022-05-21 (⭐)
- 20220521 (⭐)
- 2022/05/21
- 2022,05,21
- 2022.05.21
- 順序日期表示法:將一年內的天的序數用3位數字表示
- 2022-141 (⭐)
- 2022141 (⭐)
- 星期日曆表示法:用2位數表示年內第幾個日曆星期,再加上一位數表示日曆星期內第幾天,但日曆星期前要加上一個大寫字母W。每個日曆星期從星期一開始,星期日為第7天
- 2022-W21-6 (⭐)
- 2022W216 (⭐)
- 2022-W01-1是從2022年1月3日開始的,前幾天屬於上年的第53個日曆星期
年月日排序
- 年 月 日 (⭐)
- 月 日 年
- 日 月 年
年份
紀元
- 西元/公元
- BC(Before Christ)/AD(Anno Domini)
- BCE(Before Common Era)/CE(Common Era)
- 正負號表示 (⭐)
- 公元1年為0001年,公元前1年為0000年,公元前2年為-0001年
- 年號
- 日本、(中華)民國
- 年號會增加大量轉換成本
❓某民國什麼時候要廢年號
月份
- 01, 02, 03, ... (⭐)
- January, Febuary, March...
- Jan, Feb, Mar, ...
- 1, 2, 3, ...
星期
通常會用當地語言表示
- 1, 2, 3, 4, 5, 6, 7 (⭐)
- Sun, Mon, Tue, ...
- 日、月、火、水、木、金、土 (日本)
- 一、二、三、四、五、六、日
星期的開始日:會影響日曆的顯示
- 星期日(西方國家、日本、港澳)
- 星期一(大多亞州國家,⭐)
- 星期六(埃及)
小時
- 24小時制 (⭐)
- 12小時制
- 12:00是pm還是am
- 我很久以前就有的疑惑
- 一般來說,12:00pm是中午,12:00am是午夜
時區
- 如果時間在零時區,那麼(不加空格地)在時間最後加一個大寫字母Z
- 其他時區用實際時間加時差表示
- 22:30:05+08:00 (⭐)
- 223005+0800 (⭐)
- 223005+08
日期+時間
在時間前面加一大寫字母T
- 2004-05-03T17:30:08+08:00 (⭐)
- 20040503T173008+08 (⭐)
曆法
公曆(格勒哥里曆)之外,目前廣泛使用的曆法:
- 希伯來曆(猶太曆)
- 伊斯蘭曆(回回曆)
- 農曆
- 陰陽合曆:其日期採朔望月以指示月球的相位,年則與太陽相關
一段時間
時間段:前面加一大寫字母P
- P1Y3M5DT6H7M30S(一年三個月五天六小時七分三十秒內) (⭐)
- 19850412/19860101(起始/結束) (⭐)
- 19850412/P6M(起始/一段時間) (⭐)
- P6M/19860101(一段時間/結束) (⭐)
循環時間:前面加上一大寫字母R
R{循環次數}/{開始時間}/{時間間隔}/{結束時間}
- R3/20040506T130000+08/P0Y6M5DT3H0M0S (⭐)
- R/P1Y2M/20250101(無限次循環) (⭐)
時區
- 時區和經度沒有絕對關係
- 夏令時間
- 換日線不是直線
- UTC+8的地區有台北、北京、新加坡、印尼等,但新加坡實際的地理位置在UTC+7
- 中國全境使用相同時區(UTC+8)
所以在選擇時區時,實際上是在選擇國家或地區
夏令時間(Daylight Saving Time)
以實作上來看,這是最噁心的規則
- 有些地區在接近春季開始的時候,會將時間調快一小時,並在秋季調回來
- 這代表春季的某一天會少一個小時,秋季的某一天會多一個小時
- 如2018年的夏令時間就直接跳過了2018年3月25日3:00:00到3:59:59
以上述夏令時間為例,假設用戶設定3:30要執行備份任務,有以下實作方法
- 檢查經過某個時間點
if (nowTime == 3:30) { runTask(); }
- 任務不會執行
- 如果這是一年執行一次的任務,問題就大了
- 檢查超過某個時間點
if (nowTime > 3:30 and !isRunTask) { isRunTask=true; runTask(); }
- 這種情況,任務會在3:00過後馬上執行(也就是顯示為4:00的時候)
- 和用戶想像中不同,並不是3:00再經過30分鐘的時候執行
- 如果這個任務有前提任務,而且在3:10分執行,此時會同時觸發執行
- 可能會因為執行順序不同而出錯
- 如果這個任務有前提任務,而且在3:10分執行,此時會同時觸發執行
- 同理,離開夏令時間時,同一個任務可能會被執行兩次
- 用戶如果設定「12小時後」執行任務,也會發生類似的問題
- 用倒數的方法:實際的12小時後
- 用時鐘時間:時鐘上的12小時後
用TimeStamp也無法避免這個問題,會拿到錯的時間
(有點懷疑是不是我寫錯)
1 | import arrow |
1 | 2018-03-25T02:30:00+02:00 |
所以如果要處理上述問題,我目前想到的方法只有在每一次檢查時間時,額外檢查是否進入/離開夏令時間
閏年、閏月、閏秒
會造成和夏令時間類似的問題
- 閏秒
- 消彌精確的時間(使用原子鐘測量)和不精確的觀測太陽時(UT1)之間的差異
- 因為地球的旋轉速度會隨著氣候和地質事件變化,因此UTC的閏秒間隔不規則且不可預知
- Unix實作:重複23:59:59一次,或新增23:59:60
- 🆕 2022年11月,在第27屆國際計量大會決定於2035年取消閏秒
- 閏日
- 彌補因人為曆法規定的年度天數365日和平均回歸年的大約365.24219日的差距
- 雖然只多一天,不過普遍被叫做閏年,即閏日出現的年份
- 公曆(典型程式題)
- 公元年分非4的倍數,為平年
- 公元年分為4的倍數但非100的倍數,為閏年
- 公元年分為100的倍數但非400的倍數,為平年
- 公元年分為400的倍數為閏年
- 閏月
- 農曆
- 平年比一回歸年少約11天
- 3年一閏,5年二閏,19年七閏
- 閏月加到哪個月,以農曆曆法規則推斷,主要依照與農曆的二十四節氣相符合來確定
- 基本上19年為一周期對應於公曆同一時間,但亦有部分例外
- 每年天數差距過大,不利以年計算的會計 (梁啟超《改用太陽曆法議》)
- 農曆
- 閏年
- 真正的閏年,即於原本的曆法中插入一年
- 古埃及人在1460個地球公轉週期中加入一整年
實作
資料儲存
上下限
- 2038年問題
- 因為儲存時間的格式為int32,只能儲存從1970-01-01到2038-01-19T03:14:07Z的時間
- 受影響的資料
- 使用POSIX時間表示時間的程式
- 使用int儲存時間的程式語言,如C語言
- 使用int儲存時間的檔案格式,如
.zip
精度
- 一個system tick會更新一次時間
- 10MHz的情況下,可得100ns的精度
- 預設的tick rate沒有這麼高
- Linux: tickless kernel
- 沒有詳細研究,看起來是自訂tick rate的工具
時間同步
NTP(Network Time Protocol)
- 校正本地的UTC時間
- 定期輪詢不同網路上的三個或更多伺服器,計算其時間偏移量和來回通訊延遲
Bug
- 千禧蟲問題
- 工程師的技術債
- 自訂年號的國家會更容易遇到,如民國百年蟲問題、日本更換年號問題
- 用戶手動(或惡意)調整時間/時區
- 舊資料的修改時間可能會大於新資料的修改時間
- 解法
- 不要相信任何本機檔案的時間
- 在資料庫中記錄時間
- 不要相信任何本機檔案的時間
生日
雖然上述的時間大多需要考慮時區,但是生日似乎是個例外
不管人在何處,都是在當地的日期過生日
時間段和時間點
通常用Instant代表時間點,Duration代表時間段
套件
- Python
- 標準Library(datetime, time, zoneinfo, calendar, dateutil)
- arrow
- 光陰似箭?
- Javascript
- Moment.js
- Day.js
- Java
java.time
- C#
- Noda Time
- C++
<chrono>
總結
時間相關的問題能夠有效的產生一堆bug。
建議在儲存資料時使用UTC+0時間或TimeStamp,顯示時才切換,以避免許多可能的bug,但是會付出額外的轉換成本。
實際上,使用者也沒在管這些標準;預設格式如果不符合使用者的習慣格式就會被嫌棄。
當然,貨幣、度量衡、標點符號等習慣用法也有和時間一樣的問題,這些被統稱為本地化(Localization)問題。
參考資料
- Wiki:各國家時間表示法
- Wiki:農曆
- Wiki:置閏
- Wiki:夏令時間
- 三分鐘科普夏令時,並用Javascript判斷當前時間是否屬於夏令時
- Wiki:2038年問題
- How precise is the internal clock of a modern PC?
- Precision and accuracy of DateTime
- Wiki: 網路時間協定