时区与程序设计
時區的定義
我們使用經緯度[1]來標識地球上的任意一個點。
理論時區
不像緯度有赤道作為自然的起點,經度沒有自然的起點而使用經過倫敦格林尼治天文臺舊址的子午線作為起點。
理論時區的寬度是15°,所以一共有 360 / 15 = 24 個時區,一天有 24 小時,所以每個時區正好對應一個小時。自子午線向東,這些時區的名稱為:中時區(以子午線為中心的時區)、東一區、東二區...東十二區、西十一區、西十區...西一區[2]。
由于地球的自轉方向為自西向東,所以越東的時區時間越早。
實際時區
為了避開國界線,有的時區的形狀并不規則,而且比較大的國家以國家內部行政分界線為時區界線,這是實際時區,即法定時區。[2]
同一國家可以有不同的時區,同一國家也可以是同一個時區。
- 比如美國的夏威夷州是 UTC-10,而加利福尼亞州是 UTC-8
- 整個中國的理論時區橫跨了從東五區(UTC+5)到東九區(UTC+9)共計五個時區,但國家只有一個時區:北京時間
時區會變化
Why is subtracting these two times (in 1927) giving a strange result?
時區的 offset 不正確[4]
有人推測是時區數據庫錯了而不是 pytz 的問題。但我在其他編程語言的時區庫中沒有搜索到相關問題。
import datetime import pytz shanghai_tz = pytz.timezone('Asia/Shanghai') # 在初始化中傳入的時區的 offset 是不準確的 >>> datetime.datetime(2018, 1, 1, tzinfo=shanghai_tz) datetime.datetime(2018, 1, 1, 0, 0, tzinfo=<DstTzInfo 'Asia/Shanghai' LMT+8:06:00 STD>)# 要使用 pytz 文檔中的 localize 才準確 >>> shanghai_tz.localize(datetime.datetime(2018, 1, 1)) datetime.datetime(2018, 1, 1, 0, 0, tzinfo=<DstTzInfo 'Asia/Shanghai' CST+8:00:00 STD>)pytz.tzinfo.localize 的源碼復雜:
def localize(self, dt, is_dst=False):'''Convert naive time to local time.This method should be used to construct localtimes, ratherthan passing a tzinfo argument to a datetime constructor.is_dst is used to determine the correct timezone in the ambigousperiod at the end of daylight saving time.>>> from pytz import timezone>>> fmt = '%Y-%m-%d %H:%M:%S %Z (%z)'>>> amdam = timezone('Europe/Amsterdam')>>> dt = datetime(2004, 10, 31, 2, 0, 0)>>> loc_dt1 = amdam.localize(dt, is_dst=True)>>> loc_dt2 = amdam.localize(dt, is_dst=False)>>> loc_dt1.strftime(fmt)'2004-10-31 02:00:00 CEST (+0200)'>>> loc_dt2.strftime(fmt)'2004-10-31 02:00:00 CET (+0100)'>>> str(loc_dt2 - loc_dt1)'1:00:00'Use is_dst=None to raise an AmbiguousTimeError for ambiguoustimes at the end of daylight saving time>>> try:... loc_dt1 = amdam.localize(dt, is_dst=None)... except AmbiguousTimeError:... print('Ambiguous')Ambiguousis_dst defaults to False>>> amdam.localize(dt) == amdam.localize(dt, False)Trueis_dst is also used to determine the correct timezone in thewallclock times jumped over at the start of daylight saving time.>>> pacific = timezone('US/Pacific')>>> dt = datetime(2008, 3, 9, 2, 0, 0)>>> ploc_dt1 = pacific.localize(dt, is_dst=True)>>> ploc_dt2 = pacific.localize(dt, is_dst=False)>>> ploc_dt1.strftime(fmt)'2008-03-09 02:00:00 PDT (-0700)'>>> ploc_dt2.strftime(fmt)'2008-03-09 02:00:00 PST (-0800)'>>> str(ploc_dt2 - ploc_dt1)'1:00:00'Use is_dst=None to raise a NonExistentTimeError for these skippedtimes.>>> try:... loc_dt1 = pacific.localize(dt, is_dst=None)... except NonExistentTimeError:... print('Non-existent')Non-existent'''if dt.tzinfo is not None:raise ValueError('Not naive datetime (tzinfo is already set)')# Find the two best possibilities.possible_loc_dt = set()for delta in [timedelta(days=-1), timedelta(days=1)]:loc_dt = dt + deltaidx = max(0, bisect_right(self._utc_transition_times, loc_dt) - 1)inf = self._transition_info[idx]tzinfo = self._tzinfos[inf]loc_dt = tzinfo.normalize(dt.replace(tzinfo=tzinfo))if loc_dt.replace(tzinfo=None) == dt:possible_loc_dt.add(loc_dt)if len(possible_loc_dt) == 1:return possible_loc_dt.pop()# If there are no possibly correct timezones, we are attempting# to convert a time that never happened - the time period jumped# during the start-of-DST transition period.if len(possible_loc_dt) == 0:# If we refuse to guess, raise an exception.if is_dst is None:raise NonExistentTimeError(dt)# If we are forcing the pre-DST side of the DST transition, we# obtain the correct timezone by winding the clock forward a few# hours.elif is_dst:return self.localize(dt + timedelta(hours=6), is_dst=True) - timedelta(hours=6)# If we are forcing the post-DST side of the DST transition, we# obtain the correct timezone by winding the clock back.else:return self.localize(dt - timedelta(hours=6),is_dst=False) + timedelta(hours=6)# If we get this far, we have multiple possible timezones - this# is an ambiguous case occuring during the end-of-DST transition.# If told to be strict, raise an exception since we have an# ambiguous caseif is_dst is None:raise AmbiguousTimeError(dt)# Filter out the possiblilities that don't match the requested# is_dstfiltered_possible_loc_dt = [p for p in possible_loc_dt if bool(p.tzinfo._dst) == is_dst]# Hopefully we only have one possibility left. Return it.if len(filtered_possible_loc_dt) == 1:return filtered_possible_loc_dt[0]if len(filtered_possible_loc_dt) == 0:filtered_possible_loc_dt = list(possible_loc_dt)# If we get this far, we have in a wierd timezone transition# where the clocks have been wound back but is_dst is the same# in both (eg. Europe/Warsaw 1915 when they switched to CET).# At this point, we just have to guess unless we allow more# hints to be passed in (such as the UTC offset or abbreviation),# but that is just getting silly.## Choose the earliest (by UTC) applicable timezone if is_dst=True# Choose the latest (by UTC) applicable timezone if is_dst=False# i.e., behave like end-of-DST transitiondates = {} # utc -> localfor local_dt in filtered_possible_loc_dt:utc_time = (local_dt.replace(tzinfo=None) - local_dt.tzinfo._utcoffset)assert utc_time not in datesdates[utc_time] = local_dtreturn dates[[min, max][not is_dst](dates)]總結一下,對于 pytz,獲取帶時區的時間要使用 tz.localize(),將一個轉換為另一個時區要 dt_with_tz.astimezone(another_tz)[5]。
程序設計
由于時區的最小單位是小時:
- 所以如果要區分時區,那么儲存的時間必須包含小時,比如你不能只儲存到天2018-01-01
- 所以儲存的時間也要包含時區,比如 MongoDB 儲存的時區為 UTC
-
The official BSON specification refers to the BSON Date type as the UTC datetime.[3]
-
程序中的時區不應該與機器所在的時區掛鉤,否則,假如從中國機房遷移到美國機房,那么你的程序就會出問題。
只需要一個時區
比如對于大部分中國的程序來說,只需要考慮北京時間這一個時區。這里稱這個時區為當地時區。
我認為在程序中(前端、后端)可以只使用當地時區。好處有:
- 增強可讀性,減少混亂。比如調試時看北京時間肯定比 UTC 時間更直觀
- 避免不必要的時區轉換
如果數據庫的時區可以修改,那么也修改為當地時區,否則,使用數據庫的時區。
比如 MongoDB 使用 UTC 時區儲存,不可更改(我沒有搜索到更改的配置),那么如果有按月分表,那么也使用 UTC 劃分月,這樣數據庫的時區就統一為了 UTC;如果使用當地時區分月,那么就會造成分歧。
需要多個時區
在程序內部使用 UTC 時區,展示數據時使用用戶選擇的時區。
參考
轉載于:https://www.cnblogs.com/jay54520/p/9431333.html
總結
- 上一篇: linux 端口号查看
- 下一篇: cloudera manager的718