python datetime astronomy tzinfo leap-second

python - Extraer segundos de salto históricos de tzdata



datetime astronomy (2)

¿Hay una manera de extraer el momento histórico de los segundos de salto de la base de datos de zona horaria que se distribuye en la mayoría de las distribuciones de Linux? Estoy buscando una solución en Python, pero cualquier cosa que funcione en la línea de comandos también estaría bien.

Mi caso de uso es convertir entre gps-time (que es básicamente el número de segundos desde que se encendió el primer GPS-satélite en 1980) y UTC o la hora local. La UTC se ajusta en intervalos de segundos de vez en cuando, mientras que el tiempo de gps aumenta linealmente. Esto es equivalente a convertir entre UTC y TAI . TAI también ignora los segundos de salto, por lo que TAI y gps-time siempre deben evolucionar con el mismo desplazamiento. En el trabajo, usamos gps-time como el estándar de tiempo para sincronizar observaciones astronómicas en todo el mundo.

Tengo funciones de trabajo que convierten entre gps-time y UTC, pero tuve que codificar una tabla de segundos de salto, que obtengo here (el archivo tzdata2013xx.tar.gz contiene un archivo llamado leapseconds ). Tengo que actualizar este archivo a mano cada pocos años cuando se anuncie un nuevo salto de segundo. Preferiría obtener esta información del tzdata estándar, que se actualiza automáticamente a través de las actualizaciones del sistema varias veces al año.

Estoy bastante seguro de que la información está oculta en algunos archivos binarios en /usr/share/zoneinfo/ . He podido extraer algo de él utilizando struct.unpack ( man tzfile da algo de información sobre el formato), pero nunca lo conseguí funcionando completamente. ¿Hay algún paquete estándar que pueda acceder a esta información? Sé de pytz , que parece obtener la información estándar de horario de verano de la misma base de datos, pero no da acceso a segundos de salto. También encontré tai64n , pero mirando su código fuente, solo contiene una tabla codificada.

EDITAR

Inspirado por la respuesta de steveha y algunos códigos en pytz/tzfile.py , finalmente obtuve una solución funcional (probada en py2.5 y 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)

con resultado

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

Si bien esto resuelve mi pregunta, probablemente no vaya a buscar esta solución. En lugar de eso, leap-seconds.list con mi código, como lo sugirió Matt Johnson. Esta parece ser la lista autorizada utilizada como fuente para tzdata, y probablemente es actualizada por NIST dos veces al año. Esto significa que tendré que hacer la actualización a mano, pero este archivo es fácil de analizar e incluye una fecha de caducidad (que tzdata parece faltar).


Acabo de hacer man 5 tzfile y man 5 tzfile un desplazamiento que encontraría la información de segundos de salto, y luego leería la información de segundos de salto.

Puede descomentar las declaraciones de impresión "DEBUG:" para ver más de lo que encuentra en el archivo.

EDITAR: programa actualizado para ser ahora correcto. Ahora usa el archivo /usr/share/zoneinfo/right/UTC y ahora encuentra unos segundos para imprimir.

El programa original no estaba omitiendo los caracteres abreviados de Timezeone, que están documentados en la página del manual pero están ocultos ("... y tt_abbrind sirve como un índice en la matriz de caracteres abreviados de zonas horarias que siguen la estructura (s) ttinfo en el archivo.").

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)


PyEphem tiene una función delta_t que devuelve la diferencia entre el Tiempo Terrestre y el Tiempo Universal (segundos). Puedes restar 32.184 para obtener Leap Seconds ( ref ).

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