2013-10-12 53 views
20

有沒有辦法從大多數linux發行版上分佈的時區數據庫中提取歷史性閏秒?我正在尋找python的解決方案,但在命令行上工作的任何東西都可以。從tzdata中提取歷史性閏秒

我的用例是在GPS時間(基本上是自1980年第一顆GPS衛星開啓以來的秒數)和UTC或本地時間之間進行轉換。 UTC不時調整閏秒,而GPS時間線性增加。這相當於在UTC和TAI之間轉換。 TAI也忽略了閏秒,因此TAI和GPS時間總是應該以相同的偏移量進行演變。在工作中,我們使用全球定位系統時間作爲同步世界各地天文觀測的時間標準。

我有工作,全球定位系統,時間和UTC之間轉換的功能,但我不得不硬編碼的閏秒的表,我得到here(文件tzdata2013xx.tar.gz包含名爲leapseconds文件)。我必須每隔幾年手動更新一次這個文件,當一個新的突發事件宣佈時。我寧願從標準的tzdata中獲取這些信息,tzdata每年通過系統更新自動更新。

我很確定這些信息隱藏在/usr/share/zoneinfo/某處的某些二進制文件中。我已經能夠使用struct.unpackman tzfile提供有關格式的一些信息)提取一些信息,但我從來沒有完全實現它。有沒有可以訪問這些信息的標準軟件包?我知道pytz,它似乎從同一個數據庫獲得標準的DST信息,但它不能訪問閏秒。我也發現tai64n,但看着它的源代碼,它只包含一個硬編碼表。

編輯

通過steveha答案,並在pytz/tzfile.py一些代碼的啓發,我終於得到了一個有效的解決方案(上py2.5和py2.7測試):

from struct import unpack, calcsize 
from datetime import datetime 

def print_leap(tzfile = '/usr/share/zoneinfo/right/UTC'): 
    with open(tzfile, 'rb') as f: 
     # read header 
     fmt = '>4s c 15x 6l' 
     (magic, format, ttisgmtcnt, ttisstdcnt,leapcnt, timecnt, 
      typecnt, charcnt) = unpack(fmt, f.read(calcsize(fmt))) 
     assert magic == 'TZif'.encode('US-ASCII'), 'Not a timezone file' 
     print 'Found %i leapseconds:' % leapcnt 

     # skip over some uninteresting data 
     fmt = '>%(timecnt)dl %(timecnt)dB %(ttinfo)s %(charcnt)ds' % dict(
      timecnt=timecnt, ttinfo='lBB'*typecnt, charcnt=charcnt) 
     f.read(calcsize(fmt)) 

     #read leap-seconds 
     fmt = '>2l' 
     for i in xrange(leapcnt): 
      tleap, nleap = unpack(fmt, f.read(calcsize(fmt))) 
      print datetime.utcfromtimestamp(tleap-nleap+1) 

與結果

In [2]: print_leap() 
Found 25 leapseconds: 
1972-07-01 00:00:00 
1973-01-01 00:00:00 
1974-01-01 00:00:00 
... 
2006-01-01 00:00:00 
2009-01-01 00:00:00 
2012-07-01 00:00:00 

雖然這確實解決了我的問題,我可能不會去這個解決方案。相反,我將按照Matt Johnson的建議,將leap-seconds.list包含在我的代碼中。這似乎是用作tzdata來源的權威列表,並且可能每年兩次由NIST更新。這意味着我將不得不手動更新,但是這個文件很容易解析並且包含到期日期(這個tzdata似乎缺失)。

+2

我知道他們也發表[這裏](https://github.com/eggert/tz/blob/master/leap-seconds.list),我也知道,他們正在與'ZIC編譯',所以他們應該在tzdata更新。正如你注意到的,[tzfile](http://man7.org/linux/man-pages/man5/tzfile.5.html)在'tzh_leapcnt'中顯示了它,所以你可能會這樣。目前我沒有更直接的答案。也許別人會。 –

+0

tzdata存儲來自UTC的偏移量。爲什麼它會包含跳躍? – mattexx

+1

@mattexx不要問我爲什麼,但tzdata的二進制文件確實包含閏秒信息,可能正是爲了進行我感興趣的那種時間轉換。維護此數據庫的人(http:// en .wikipedia.org/wiki/Olson_database)在記錄時間定義的歷史性變化時非常細緻,有時每年提供10次更新,因爲一些瘋狂的獨裁者將夏令時移動了一天。由於IERS定期發佈公告,因此保持跟蹤跳躍更容易,並且通常會提前半年公佈。 –

回答

10

我只是做了man 5 tzfile和計算的偏移會發現閏秒信息,然後讀取閏秒信息。

您可以取消註釋「DEBUG:」打印語句以查看它在文件中找到的更多內容。

編輯:程序更新到現在是正確的。它現在使用文件/usr/share/zoneinfo/right/UTC,它現在找到閏秒來打印。

原始程序沒有跳過timezeone縮寫字符,這些字符在手冊頁中有記載,但是有些隱藏(「...和tt_abbrind充當ttinfo結構後面的時區縮寫字符數組索引(s)在文件中。「)。

import datetime 
import struct 

TZFILE_MAGIC = 'TZif'.encode('US-ASCII') 

def leap_seconds(f): 
    """ 
    Return a list of tuples of this format: (timestamp, number_of_seconds) 
     timestamp: a 32-bit timestamp, seconds since the UNIX epoch 
     number_of_seconds: how many leap-seconds occur at timestamp 

    """ 
    fmt = ">4s c 15x 6l" 
    size = struct.calcsize(fmt) 
    (tzfile_magic, tzfile_format, ttisgmtcnt, ttisstdcnt, leapcnt, timecnt, 
     typecnt, charcnt) = struct.unpack(fmt, f.read(size)) 
    #print("DEBUG: tzfile_magic: {} tzfile_format: {} ttisgmtcnt: {} ttisstdcnt: {} leapcnt: {} timecnt: {} typecnt: {} charcnt: {}".format(tzfile_magic, tzfile_format, ttisgmtcnt, ttisstdcnt, leapcnt, timecnt, typecnt, charcnt)) 

    # Make sure it is a tzfile(5) file 
    assert tzfile_magic == TZFILE_MAGIC, (
      "Not a tzfile; file magic was: '{}'".format(tzfile_magic)) 

    # comments below show struct codes such as "l" for 32-bit long integer 
    offset = (timecnt*4 # transition times, each "l" 
     + timecnt*1 # indices tying transition time to ttinfo values, each "B" 
     + typecnt*6 # ttinfo structs, each stored as "lBB" 
     + charcnt*1) # timezone abbreviation chars, each "c" 

    f.seek(offset, 1) # seek offset bytes from current position 

    fmt = '>{}l'.format(leapcnt*2) 
    #print("DEBUG: leapcnt: {} fmt: '{}'".format(leapcnt, fmt)) 
    size = struct.calcsize(fmt) 
    data = struct.unpack(fmt, f.read(size)) 

    lst = [(data[i], data[i+1]) for i in range(0, len(data), 2)] 
    assert all(lst[i][0] < lst[i+1][0] for i in range(len(lst)-1)) 
    assert all(lst[i][1] == lst[i+1][1]-1 for i in range(len(lst)-1)) 

    return lst 

def print_leaps(leap_lst): 
    # leap_lst is tuples: (timestamp, num_leap_seconds) 
    for ts, num_secs in leap_lst: 
     print(datetime.datetime.utcfromtimestamp(ts - num_secs+1)) 

if __name__ == '__main__': 
    import os 
    zoneinfo_fname = '/usr/share/zoneinfo/right/UTC' 
    with open(zoneinfo_fname, 'rb') as f: 
     leap_lst = leap_seconds(f) 
    print_leaps(leap_lst) 
+0

一個人可以擁有自1980年以來的閏秒數的文件,然後每當根據您的'leap_second'函數發生閏秒時,就可以遞增該數字。在Ubuntu上,在tzdata軟件包的啓動板頁面中,可以下載該軟件包的舊版本。 –

+0

感謝steveha,那是我尋找的解決方案。我現在沒有時間了,我會在接下來的日子裏更好地審視你的解決方案。 –

+0

文件'/ usr/share/zoneinfo/right/UTC'包含閏秒。在這個文件上運行你的代碼給出了斷言錯誤,我想是因爲你的f.seek關閉了幾個字節。把它改爲'f.seek(offset + 4,1)'似乎可以解決這個問題。我爲我的問題添加了一些工作代碼。感謝您指點我正確的方向。 –

3

PyEphem具有delta_t函數,該函數返回陸地時間和世界時間(秒)之間的差異。您可以從中減去32.184來獲得閏秒(ref)。

import ephem, datetime 
ephem.delta_t(datetime.datetime.now()) - 32.184 
Out[2]: 35.01972996360122 
+6

感謝您的鏈接,但是看看PyEphem的源代碼,它似乎從'libastro'獲得了它的閏秒信息。看[來源](https://github.com/brandon-rhodes/pyephem/blob/master/libastro-3.7.5/deltat.c),還有一個硬編碼表。最新版本似乎是從2011年開始的,從2012年7月的最後一次閏秒開始已經過時了!這就是爲什麼我想直接使用基於tzdata的東西,因爲它每年都會多次更新。 –

+1

這個計算實際上給出了差異TT - UT1,而不是TT - UTC。 – JPaget