时区与程序设计
時區(qū)的定義
我們使用經(jīng)緯度[1]來標(biāo)識地球上的任意一個點。
理論時區(qū)
不像緯度有赤道作為自然的起點,經(jīng)度沒有自然的起點而使用經(jīng)過倫敦格林尼治天文臺舊址的子午線作為起點。
理論時區(qū)的寬度是15°,所以一共有 360 / 15 = 24 個時區(qū),一天有 24 小時,所以每個時區(qū)正好對應(yīng)一個小時。自子午線向東,這些時區(qū)的名稱為:中時區(qū)(以子午線為中心的時區(qū))、東一區(qū)、東二區(qū)...東十二區(qū)、西十一區(qū)、西十區(qū)...西一區(qū)[2]。
由于地球的自轉(zhuǎn)方向為自西向東,所以越東的時區(qū)時間越早。
實際時區(qū)
為了避開國界線,有的時區(qū)的形狀并不規(guī)則,而且比較大的國家以國家內(nèi)部行政分界線為時區(qū)界線,這是實際時區(qū),即法定時區(qū)。[2]
同一國家可以有不同的時區(qū),同一國家也可以是同一個時區(qū)。
- 比如美國的夏威夷州是 UTC-10,而加利福尼亞州是 UTC-8
- 整個中國的理論時區(qū)橫跨了從東五區(qū)(UTC+5)到東九區(qū)(UTC+9)共計五個時區(qū),但國家只有一個時區(qū):北京時間
時區(qū)會變化
Why is subtracting these two times (in 1927) giving a strange result?
時區(qū)的 offset 不正確[4]
有人推測是時區(qū)數(shù)據(jù)庫錯了而不是 pytz 的問題。但我在其他編程語言的時區(qū)庫中沒有搜索到相關(guān)問題。
import datetime import pytz shanghai_tz = pytz.timezone('Asia/Shanghai') # 在初始化中傳入的時區(qū)的 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 的源碼復(fù)雜:
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)]總結(jié)一下,對于 pytz,獲取帶時區(qū)的時間要使用 tz.localize(),將一個轉(zhuǎn)換為另一個時區(qū)要 dt_with_tz.astimezone(another_tz)[5]。
程序設(shè)計
由于時區(qū)的最小單位是小時:
- 所以如果要區(qū)分時區(qū),那么儲存的時間必須包含小時,比如你不能只儲存到天2018-01-01
- 所以儲存的時間也要包含時區(qū),比如 MongoDB 儲存的時區(qū)為 UTC
-
The official BSON specification refers to the BSON Date type as the UTC datetime.[3]
-
程序中的時區(qū)不應(yīng)該與機器所在的時區(qū)掛鉤,否則,假如從中國機房遷移到美國機房,那么你的程序就會出問題。
只需要一個時區(qū)
比如對于大部分中國的程序來說,只需要考慮北京時間這一個時區(qū)。這里稱這個時區(qū)為當(dāng)?shù)貢r區(qū)。
我認為在程序中(前端、后端)可以只使用當(dāng)?shù)貢r區(qū)。好處有:
- 增強可讀性,減少混亂。比如調(diào)試時看北京時間肯定比 UTC 時間更直觀
- 避免不必要的時區(qū)轉(zhuǎn)換
如果數(shù)據(jù)庫的時區(qū)可以修改,那么也修改為當(dāng)?shù)貢r區(qū),否則,使用數(shù)據(jù)庫的時區(qū)。
比如 MongoDB 使用 UTC 時區(qū)儲存,不可更改(我沒有搜索到更改的配置),那么如果有按月分表,那么也使用 UTC 劃分月,這樣數(shù)據(jù)庫的時區(qū)就統(tǒng)一為了 UTC;如果使用當(dāng)?shù)貢r區(qū)分月,那么就會造成分歧。
需要多個時區(qū)
在程序內(nèi)部使用 UTC 時區(qū),展示數(shù)據(jù)時使用用戶選擇的時區(qū)。
參考
轉(zhuǎn)載于:https://www.cnblogs.com/jay54520/p/9431333.html
總結(jié)
- 上一篇: linux 端口号查看
- 下一篇: cloudera manager的718