Saltar al contenido

Cómo obtener la fecha y la hora actuales de la hora de un segmento de GPS en Python

Luego de de esta prolongada compilación de información dimos con la respuesta esta duda que tienen ciertos de nuestros lectores. Te regalamos la solución y esperamos servirte de mucha apoyo.

Solución:

La hora del GPS comenzó en sincronía con UTC: 1980-01-06 (UTC) == 1980-01-06 (GPS). Ambos marcan en segundos SI. La diferencia entre la hora del GPS y la hora UTC aumenta con cada segundo intercalar (intercalario).

Para encontrar la hora UTC correcta, necesita saber la cantidad de segundos intercalares ocurridos antes de la hora GPS dada:

#!/usr/bin/env python
from datetime import datetime, timedelta

# utc = 1980-01-06UTC + (gps - (leap_count(2014) - leap_count(1980)))
utc = datetime(1980, 1, 6) + timedelta(seconds=1092121243.0 - (35 - 19))
print(utc)

Producción

2014-08-15 07:00:27 # (UTC)

donde leap_count(date) es el número de segundos bisiestos introducidos antes de la fecha indicada. De la tabla TAI-UTC (nota: el sitio es la fuente autorizada en segundos bisiestos. Publica el Boletín C anunciando nuevos segundos bisiestos):

1980..: 19s 
2012..: 35s

y por lo tanto:

(leap_count(2014) - leap_count(1980)) == (35 - 19)

Si está en Unix, entonces podría usar "right" zona horaria para obtener la hora UTC de la hora TAI (y es fácil obtener la hora TAI de la hora GPS: TAI = GPS + 19 segundos (desplazamiento constante)):

#!/usr/bin/env python
import os
import time

os.environ['TZ'] = 'right/UTC' # TAI scale with 1970-01-01 00:00:10 (TAI) epoch
time.tzset() # Unix

from datetime import datetime, timedelta

gps_timestamp = 1092121243.0 # input
gps_epoch_as_gps = datetime(1980, 1, 6) 
# by definition
gps_time_as_gps = gps_epoch_as_gps + timedelta(seconds=gps_timestamp) 
gps_time_as_tai = gps_time_as_gps + timedelta(seconds=19) # constant offset
tai_epoch_as_tai = datetime(1970, 1, 1, 0, 0, 10)
# by definition
tai_timestamp = (gps_time_as_tai - tai_epoch_as_tai).total_seconds() 
print(datetime.utcfromtimestamp(tai_timestamp)) # "right" timezone is in effect!

Producción

2014-08-15 07:00:27 # (UTC)

Puede evitar cambiar la zona horaria si extrae la lista de segundos intercalares del correspondiente tzfile(5). Es una combinación de los dos primeros métodos donde el cálculo del recuento de saltos del primer método está automatizado y la actualización automática tzdata (paquete de sistema para la base de datos tz) del segundo método:

>>> from datetime import datetime, timedelta
>>> import leapseconds
>>> leapseconds.gps_to_utc(datetime(1980,1,6) + timedelta(seconds=1092121243.0))
datetime.datetime(2014, 8, 15, 7, 0, 27)

donde leapseconds.py puede extraer segundos intercalares de /usr/share/zoneinfo/right/UTC archivo (parte de tzdata paquete).

Los tres métodos producen el mismo resultado.

Puede usar el paquete astropy.time para hacer esto:

Tiempo GPS para TAI

from astropy.time import Time
mytime = 1092121243.0
t = Time(mytime, format='gps')
t = Time(t, format='iso') # same as scale='tai'
print(t)

que regresa 2014-08-15 07:01:02.000

Hora GPS a UTC

from astropy.time import Time
sec = 1092121243.0
t_in = Time(sec, format='gps')
t_out = Time(t_in, format='iso', scale='utc')
print(t_out)

que salidas 2014-08-15 07:00:27.000

Utilizo la siguiente función que cuenta los segundos intercalares:

import bisect
from datetime import datetime, timedelta

_LEAP_DATES = ((1981, 6, 30), (1982, 6, 30), (1983, 6, 30),
               (1985, 6, 30), (1987, 12, 31), (1989, 12, 31),
               (1990, 12, 31), (1992, 6, 30), (1993, 6, 30),
               (1994, 6, 30), (1995, 12, 31), (1997, 6, 30),
               (1998, 12, 31), (2005, 12, 31), (2008, 12, 31),
               (2012, 6, 30), (2015, 6, 30), (2016, 12, 31))

LEAP_DATES = tuple(datetime(i[0], i[1], i[2], 23, 59, 59) for i in _LEAP_DATES)

def leap(date):
    """
    Return the number of leap seconds since 1980-01-01

    :param date: datetime instance
    :return: leap seconds for the date (int)
    """
    # bisect.bisect returns the index `date` would have to be
    # inserted to keep `LEAP_DATES` sorted, so is the number of
    # values in `LEAP_DATES` that are less than `date`, or the
    # number of leap seconds.
    return bisect.bisect(LEAP_DATES, date)

Por supuesto, necesitas actualizar _LEAP_DATES ocasionalmente, pero estas actualizaciones son bastante raras.

En general, la hora del GPS consta de dos números: Semana GPS y el numero de segundos desde el comienzo de la semana GPS actual. Por lo tanto, puede utilizar lo siguiente:

def gps2utc(week, secs):
    """
    :param week: GPS week number, i.e. 1866
    :param secs: number of seconds since the beginning of `week`
    :return: datetime instance with UTC time
    """
    secs_in_week = 604800
    gps_epoch = datetime(1980, 1, 6, 0, 0, 0)
    date_before_leaps = gps_epoch + timedelta(seconds=week * secs_in_week + secs)
    return date_before_leaps - timedelta(seconds=leap(date_before_leaps))

En tu caso week = 0, entonces:

In [1]: gps2utc(0, 1092121243.0)
Out[1]: datetime.datetime(2014, 8, 15, 7, 0, 27)

Si crees que ha resultado de ayuda nuestro post, sería de mucha ayuda si lo compartieras con el resto entusiastas de la programación de este modo nos ayudas a extender este contenido.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *