2013-10-12 11 views
20

Czy istnieje sposób na wyodrębnienie momentu historycznych sekund przestępnych z bazy danych strefy czasowej, która jest dystrybuowana w większości dystrybucji Linuksa? Szukam rozwiązania w Pythonie, ale wszystko, co działa na linii poleceń byłoby również w porządku.Wyodrębnij historyczne sekundy przestępne z tzdata

Moim przypadkiem użycia jest konwersja pomiędzy czasem GPS (czyli w zasadzie liczbą sekund od pierwszego włączenia GPS-a w 1980 r.) A czasem UTC lub czasem lokalnym. Czas UTC jest korygowany co sekundę, a czas GPS rośnie liniowo. Jest to równoznaczne z konwersją między UTC i TAI. TAI również ignoruje sekundy przestępne, więc czasy TAI i GPS powinny zawsze ewoluować z takim samym przesunięciem. W pracy wykorzystujemy czas GPS jako standard czasowy do synchronizowania obserwacji astronomicznych na całym świecie.

mam funkcje, które przekształcają pomiędzy GPS a UTC czasu pracy, ale musiałem twardym kodu tabelę sekundy przestępne, które dostaję here (plik tzdata2013xx.tar.gz zawiera plik o nazwie leapseconds). Muszę aktualizować ten plik ręcznie co kilka lat, kiedy ogłoszony zostanie nowy leapsecond. Wolałbym uzyskać te informacje ze standardowego tzdata, który jest automatycznie aktualizowany przez aktualizacje systemu kilka razy w roku.

Jestem prawie pewien, że informacje są ukryte w niektórych plikach binarnych gdzieś w /usr/share/zoneinfo/. Byłem w stanie wydobyć niektóre z nich za pomocą struct.unpack (man tzfile daje pewne informacje na temat formatu), ale nigdy nie dostałem go działa całkowicie. Czy są jakieś standardowe pakiety, które mogą uzyskać dostęp do tych informacji? Wiem o pytz, który wydaje się uzyskać standardowe informacje DST z tej samej bazy danych, ale nie daje dostępu do sekund przestępnych. Znalazłem także tai64n, ale patrząc na jego kod źródłowy, zawiera on po prostu zakodowaną tabelę.

EDIT

Zainspirowany odpowiedź steveha i pewnego kodu w pytz/tzfile.py, ja wreszcie roztwór roboczy (testowane na py2.5 i 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) 

z wynikiem

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 

Podczas gdy to rozwiąże moje pytanie, prawdopodobnie nie pójdę po to rozwiązanie. Zamiast tego dołączę do mojego kodu kod leap-seconds.list, jak sugeruje Matt Johnson. Wydaje się, że jest to autorytatywna lista używana jako źródło tzdata i prawdopodobnie jest aktualizowana przez NIST dwa razy w roku. Oznacza to, że będę musiał ręcznie przeprowadzić aktualizację, ale ten plik jest łatwy do przeanalizowania i zawiera datę wygaśnięcia (której prawdopodobnie brakuje w tzdacie).

+2

wiem, są one również opublikowane [tutaj] (https://github.com/eggert/tz/blob/master/leap-seconds.list), i wiem też, że są one zestawiane z 'Zic ', więc powinny znajdować się w aktualizacjach tzdata. Jak zauważyłeś, plik [tzfile] (http://man7.org/linux/man-pages/man5/tzfile.5.html) pokazuje go w 'tzh_leapcnt', więc możesz go zdobyć w ten sposób. W tej chwili nie mam dla ciebie bardziej bezpośredniej odpowiedzi. Może ktoś inny to zrobi. –

+0

tzdaty przechowuje przesunięcia z UTC. Dlaczego miałby zawierać leapseconds? – mattexx

+1

@mattexx Nie pytaj mnie, dlaczego, ale pliki binarne tzdata zawierają informacje typu "leap-second", może właśnie w ten sposób, w jaki sposób konwersje czasowe mnie interesują. Ludzie utrzymujący to [baza danych] (http: // en .wikipedia.org/wiki/Olson_database) są bardzo skrupulatne, jeśli chodzi o zapisywanie historycznych zmian w definicjach czasu, czasami dostarczając aktualizacje 10 razy w roku, ponieważ jakiś szalony dyktator przesunął czas letni o jeden dzień. Śledzenie upływu czasu jest o wiele łatwiejsze, ponieważ IERS publikuje regularnie biuletyny i zwykle są one ogłaszane pół roku wcześniej. –

Odpowiedz

10

ja po prostu nie man 5 tzfile i obliczana offsetu, że znalezienie informacji sekundy przestępne, a następnie odczytać informacje sekundy przestępne.

Można odkomentować instrukcje drukowania "DEBUG:", aby zobaczyć więcej informacji o tym, co znajduje się w pliku.

EDYCJA: zaktualizowano program do teraz. Teraz używa pliku /usr/share/zoneinfo/right/UTC i teraz znajduje sekundy do drukowania.

Oryginalny program nie pomijał znaków skrótów timezeone, które są udokumentowane na stronie podręcznika, ale są ukryte ("... i tt_abbrind służy jako indeks do tablicy znaków strefy czasowej, które są zgodne ze strukturą ttinfo (s) w pliku. ").

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

Można mieć plik z liczbą sekund przestępnych od 1980 r., A następnie za każdym razem, gdy nastąpi sekunda przestępna w oparciu o funkcję 'leap_second', można zwiększyć liczbę. W Ubuntu na stronie startowej pakietu tzdata można pobrać stare wersje pakietu. –

+0

Dzięki Steveha, to było tego rodzaju rozwiązanie, którego szukałem. Nie mam teraz czasu, będę miał lepsze spojrzenie na twoje rozwiązanie w ciągu najbliższych dni. –

+0

Plik '/ usr/share/zoneinfo/right/UTC' zawiera sekundy przestępne. Uruchamianie twojego kodu na tym pliku daje błąd potwierdzający, tak myślę, ponieważ twoje 'f.seek' jest wyłączone o kilka bajtów. Zmiana tego na 'f.seek (offset + 4, 1)' wydaje się rozwiązać ten problem. Dodałem działający kod do mojego pytania. Dzięki za wskazanie mi właściwego kierunku. –

3

PyEphem ma funkcję delta_t, która zwraca różnicę między czasem ziemskim a czasem uniwersalnym (sekundy). Możesz odjąć 32.184 od niego, aby uzyskać skok sekund (ref).

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

Dzięki za link, ale patrząc na kod źródłowy PyEphem, wydaje się, że jego informacje o sekundach-sekundach pochodzą z 'libastro'. Patrząc na [to źródło] (https://github.com/brandon-rhodes/pyephem/blob/master/libastro-3.7.5/deltat.c), znów pojawia się zakodowana tabela. Najnowsza wersja wydaje się pochodzić z 2011 roku, która jest nieaktualna, ponieważ ostatnia sekunda przestępna miała miejsce w lipcu 2012 roku! Właśnie dlatego chciałem użyć czegoś opartego bezpośrednio na tzdata, ponieważ jest ono aktywnie aktualizowane kilka razy w roku. –

+1

To obliczenie faktycznie daje różnicę TT - UT1, a nie TT - UTC. – JPaget