Skip to content

Commit 13c46d8

Browse files
Merge pull request #125 from Stanford-NavLab/ashwin/time-conversions
Ashwin/time conversions
2 parents aaa6fc0 + 14148bc commit 13c46d8

3 files changed

Lines changed: 127 additions & 74 deletions

File tree

docs/source/reference/reference.rst

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,35 @@ own methods using a combination of numpy methods and :code:`NavData`
113113
methods.
114114

115115

116+
Timing Conventions
117+
------------------
118+
119+
We use four different time formats in :code:`gnss_lib_py`:
120+
* :code:`gps_millis` : The number of milliseconds that have elapsed since
121+
the start of the GPS epoch on January 6th, 1980. This time format is
122+
continuous and is not adjusted with leap seconds. This time is stored
123+
as a single number that is a :code:`double int` or :code:`float`,
124+
depending on the context.
125+
* :code:`unix_millis` : The number of milliseconds that have elapsed since
126+
the start of the Unix epoch on January 1st, 1970. This time format is
127+
not continuous and is adjusted with leap seconds. This time is also
128+
stored as a single number that is a :code:`double int` or :code:`float`.
129+
* :code:`utc_timestamp` : UTC time, which is stored as a timestamp using
130+
the :code:`datetime` library. This time format is not continuous and is
131+
adjusted with leap seconds.
132+
* :code:`gps_week` and :code:`gps_tow` : The GPS week since the start of
133+
the GPS epoch on January 6th, 1980 and the time of that week in seconds.
134+
135+
Of these four time formats, we use :code:`gps_millis` as the default
136+
time that measurements and state estimates correspond to. Conversions
137+
between all these time formats are provided in the
138+
:code:`utils/time_conversion.py` file.
139+
140+
Between these four time formats, all major applications of GNSS-based
141+
state estimation should be covered and any of these time formats can be
142+
used interchangably.
143+
144+
116145
Standard Naming Conventions
117146
---------------------------
118147

gnss_lib_py/utils/time_conversions.py

Lines changed: 79 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
"""Timing conversions between reference frames.
1+
"""Timing conversions between reference frames for times.
22
33
Frame options are described in detail at on the documentation website:
4-
https://gnss-lib-py.readthedocs.io/en/latest/reference/reference.html#standard-naming-conventions
4+
https://gnss-lib-py.readthedocs.io/en/latest/reference/reference.html#timing-conventions
55
66
Frame options include:
77
- gps_millis : GPS milliseconds
@@ -78,24 +78,19 @@ def get_leap_seconds(gps_time):
7878
return out_leapsecs
7979

8080

81-
def gps_millis_to_tow(millis, add_leap_secs=False, verbose=False):
81+
def gps_millis_to_tow(millis):
8282
"""Convert milliseconds since GPS epoch to GPS week number and time.
8383
8484
The initial GPS epoch is defined by the variable GPS_EPOCH_0 at
8585
which the week number is assumed to be 0.
8686
87+
Both of these times are in the GPS time frame of reference and as a
88+
result, leap seconds do not need to be accounted for here.
89+
8790
Parameters
8891
----------
8992
millis : float or array-like of floats
9093
Float object for Time of Clock [ms].
91-
add_leap_secs : bool
92-
Flag for whether input is in UTC seconds or GPS seconds.
93-
If True, we assume that input millis do not contain leap seconds
94-
and add them to the output time.
95-
If False, we assume that input millis contain leap seconds and
96-
do not add them.
97-
verbose : bool
98-
Flag for whether to print that leapseconds were added.
9994
10095
Returns
10196
-------
@@ -120,11 +115,6 @@ def gps_millis_to_tow(millis, add_leap_secs=False, verbose=False):
120115
for milli in millis:
121116
gps_week, tow = divmod(milli, 7*86400*1000)
122117
tow = tow / 1000.0
123-
if add_leap_secs:
124-
out_leapsecs = get_leap_seconds(milli)
125-
if verbose: #pragma: no cover
126-
print('Leap seconds added')
127-
tow = tow + out_leapsecs
128118

129119
gps_weeks.append(np.int64(gps_week))
130120
tows.append(tow)
@@ -134,19 +124,17 @@ def gps_millis_to_tow(millis, add_leap_secs=False, verbose=False):
134124
return gps_weeks, tows
135125

136126

137-
def datetime_to_tow(t_datetimes, add_leap_secs=True, verbose=False):
127+
def datetime_to_tow(t_datetimes):
138128
"""Convert Python datetime object to GPS Week and time of week.
139129
130+
For the `gnss_lib_py` convention, except for specific applications,
131+
we assume that the time recorded by the `datetime.datetime` object
132+
is UTC time. As a result, on converting to TOW, we add leap seconds.
133+
140134
Parameters
141135
----------
142136
t_datetimes : datetime.datetime or array-like of datetime.datetime
143-
Datetime object for Time of Clock.
144-
add_leap_secs : bool
145-
Flag for whether output is in UTC seconds or GPS seconds.
146-
If True, the output is in the GPS timing frame of reference and
147-
leap seconds are added.
148-
If False, the output is in the UTC timing frame of reference and
149-
leap seconds are not added.
137+
Datetime object for Time of Clock, assumed to be in UTC time frame.
150138
verbose : bool
151139
Flag for whether to print that leapseconds were added.
152140
@@ -173,11 +161,8 @@ def datetime_to_tow(t_datetimes, add_leap_secs=True, verbose=False):
173161
if t_datetime < GPS_EPOCH_0:
174162
raise RuntimeError("Input time must be after GPS epoch " \
175163
+ str(GPS_EPOCH_0))
176-
if add_leap_secs:
177-
out_leapsecs = get_leap_seconds(t_datetime)
178-
t_datetime = t_datetime + timedelta(seconds=out_leapsecs)
179-
if verbose: # pragma: no cover
180-
print("Leap seconds added")
164+
out_leapsecs = get_leap_seconds(t_datetime)
165+
t_datetime = t_datetime + timedelta(seconds=out_leapsecs)
181166
gps_week = (t_datetime - GPS_EPOCH_0).days // 7
182167

183168
tow = ((t_datetime - GPS_EPOCH_0) - timedelta(gps_week* 7.0)).total_seconds()
@@ -190,17 +175,18 @@ def datetime_to_tow(t_datetimes, add_leap_secs=True, verbose=False):
190175
return gps_weeks, tows
191176

192177

193-
def tow_to_datetime(gps_weeks, tows, rem_leap_secs=True):
178+
def tow_to_datetime(gps_weeks, tows):
194179
"""Convert GPS week and time of week (seconds) to datetime.
195180
181+
Because we assume that `datetime.datetime` objects are in UTC time,
182+
leap seconds are removed from the given TOW.
183+
196184
Parameters
197185
----------
198186
gps_weeks : int or array-like of ints
199187
GPS week.
200188
tows : float or array-like of floats
201189
GPS time of week [s].
202-
rem_leap_secs : bool
203-
Flag on whether to remove leap seconds from given tow.
204190
205191
Returns
206192
-------
@@ -231,9 +217,8 @@ def tow_to_datetime(gps_weeks, tows, rem_leap_secs=True):
231217

232218
seconds_since_epoch = WEEKSEC * gps_week + tow
233219
t_datetime = GPS_EPOCH_0 + timedelta(seconds=seconds_since_epoch)
234-
if rem_leap_secs:
235-
leap_secs = get_leap_seconds(t_datetime)
236-
t_datetime = t_datetime - timedelta(seconds=leap_secs)
220+
leap_secs = get_leap_seconds(t_datetime)
221+
t_datetime = t_datetime - timedelta(seconds=leap_secs)
237222

238223
t_datetimes.append(t_datetime)
239224

@@ -249,8 +234,8 @@ def tow_to_unix_millis(gps_weeks, tows):
249234
250235
Convert GPS week and time of week (seconds) to milliseconds since
251236
UNIX epoch.
252-
Leap seconds will always be removed from tow because of offset between
253-
UTC and GPS clocks.
237+
Leap seconds will always be removed from tow because GPS millis is a
238+
continuous time reference while unix millis adjust for leap seconds.
254239
255240
Parameters
256241
----------
@@ -287,7 +272,7 @@ def tow_to_unix_millis(gps_weeks, tows):
287272
for t_idx, gps_week in enumerate(gps_weeks):
288273
tow = tows[t_idx]
289274

290-
t_utc = tow_to_datetime(gps_week, tow, rem_leap_secs=True)
275+
t_utc = tow_to_datetime(gps_week, tow)
291276
t_utc = t_utc.replace(tzinfo=timezone.utc)
292277
unix_milli = datetime_to_unix_millis(t_utc)
293278
unix_millis.append(unix_milli)
@@ -329,7 +314,8 @@ def tow_to_gps_millis(gps_week, tow):
329314
def datetime_to_unix_millis(t_datetimes):
330315
"""Convert datetime to milliseconds since UNIX Epoch (1/1/1970 UTC).
331316
332-
If no timezone is specified, assumes UTC as timezone.
317+
If no timezone is specified, assumes UTC as timezone. This function
318+
does not add leapseconds to the datetime to get unix millis.
333319
334320
Parameters
335321
----------
@@ -363,20 +349,19 @@ def datetime_to_unix_millis(t_datetimes):
363349
return unix_millis_list
364350

365351

366-
def datetime_to_gps_millis(t_datetimes, add_leap_secs=True):
352+
def datetime_to_gps_millis(t_datetimes):
367353
"""Convert datetime to milliseconds since GPS Epoch.
368354
369355
GPS Epoch starts at the 6th January 1980.
370356
If no timezone is specified, assumes UTC as timezone and returns
371357
milliseconds in GPS time frame of reference by adding leap seconds.
372-
Milliseconds are not added when the flag add_leap_secs is False.
358+
Leap seconds are always added because UTC time is adjusted for leap
359+
seconds while GPS milliseconds are not.
373360
374361
Parameters
375362
----------
376363
t_datetime : datetime.datetime or array-like of datetime.datetime
377364
UTC time as a datetime object.
378-
add_leap_secs : bool
379-
Flag for whether output is in UTC seconds or GPS seconds.
380365
381366
Returns
382367
-------
@@ -386,7 +371,7 @@ def datetime_to_gps_millis(t_datetimes, add_leap_secs=True):
386371
387372
388373
"""
389-
gps_weeks, tows = datetime_to_tow(t_datetimes, add_leap_secs=add_leap_secs)
374+
gps_weeks, tows = datetime_to_tow(t_datetimes)
390375
gps_millis = tow_to_gps_millis(gps_weeks, tows)
391376
return gps_millis
392377

@@ -448,11 +433,11 @@ def unix_millis_to_tow(unix_millis):
448433
"""
449434

450435
t_utc = unix_millis_to_datetime(unix_millis)
451-
gps_week, tow = datetime_to_tow(t_utc, add_leap_secs=True)
436+
gps_week, tow = datetime_to_tow(t_utc)
452437
return np.int64(gps_week), tow
453438

454439

455-
def unix_to_gps_millis(unix_millis, add_leap_secs=True):
440+
def unix_to_gps_millis(unix_millis):
456441
"""Convert milliseconds since UNIX epoch (1/1/1970) to GPS millis.
457442
458443
Adds leap seconds by default but time can be kept in UTC frame by
@@ -477,21 +462,24 @@ def unix_to_gps_millis(unix_millis, add_leap_secs=True):
477462
gps_millis = np.zeros_like(unix_millis)
478463
for t_idx, unix in enumerate(unix_millis):
479464
t_utc = unix_millis_to_datetime(unix)
480-
gps_millis[t_idx] = datetime_to_gps_millis(t_utc, add_leap_secs=add_leap_secs)
465+
gps_millis[t_idx] = datetime_to_gps_millis(t_utc)
481466
gps_millis = gps_millis.astype(np.float64)
482467
else:
483468
t_utc = unix_millis_to_datetime(unix_millis)
484-
gps_millis = np.float64(datetime_to_gps_millis(t_utc, add_leap_secs=add_leap_secs))
469+
gps_millis = np.float64(datetime_to_gps_millis(t_utc))
485470
return gps_millis
486471

487472

488-
def gps_millis_to_datetime(gps_millis, rem_leap_secs=True):
473+
def gps_millis_to_datetime(gps_millis):
489474
"""Convert milliseconds since GPS epoch to datetime.
490475
491476
GPS millis is from the start of the GPS in GPS reference.
492477
The initial GPS epoch is defined by the variable GPS_EPOCH_0 at
493478
which the week number is assumed to be 0.
494479
480+
The :code:`datetime` instances are assumed to be in UTC time and leap
481+
seconds are removed from the gps_millis as a result.
482+
495483
Parameters
496484
----------
497485
gps_millis : float or array-like of float
@@ -505,18 +493,21 @@ def gps_millis_to_datetime(gps_millis, rem_leap_secs=True):
505493
UTC time as a datetime object. Either `datetime.datetime` or
506494
`np.ndarray` with `dtype = datetime.datetime`.
507495
"""
508-
gps_week, tow = gps_millis_to_tow(gps_millis, add_leap_secs=False)
509-
t_utc = tow_to_datetime(gps_week, tow, rem_leap_secs=rem_leap_secs)
496+
gps_week, tow = gps_millis_to_tow(gps_millis)
497+
t_utc = tow_to_datetime(gps_week, tow)
510498
return t_utc
511499

512500

513-
def gps_to_unix_millis(gps_millis, rem_leap_secs=True):
501+
def gps_to_unix_millis(gps_millis):
514502
"""Convert milliseconds since GPS epoch to UNIX millis.
515503
516504
GPS millis is from the start of the GPS in GPS reference.
517505
The initial GPS epoch is defined by the variable GPS_EPOCH_0 at
518506
which the week number is assumed to be 0.
519507
508+
Leap seconds are removed from gps_millis because of the difference
509+
between how GPS and Unix time handle milliseconds.
510+
520511
Parameters
521512
----------
522513
gps_millis : float or array-like of float
@@ -537,15 +528,49 @@ def gps_to_unix_millis(gps_millis, rem_leap_secs=True):
537528
and len(np.atleast_1d(gps_millis)) > 1:
538529
unix_millis = np.zeros_like(gps_millis)
539530
for t_idx, gps in enumerate(gps_millis):
540-
t_utc = gps_millis_to_datetime(gps, rem_leap_secs=rem_leap_secs)
531+
t_utc = gps_millis_to_datetime(gps)
541532
unix_millis[t_idx] = datetime_to_unix_millis(t_utc)
542533
gps_millis = gps_millis.astype(np.float64)
543534
else:
544-
t_utc = gps_millis_to_datetime(gps_millis, rem_leap_secs=rem_leap_secs)
535+
t_utc = gps_millis_to_datetime(gps_millis)
545536
unix_millis = np.float64(datetime_to_unix_millis(t_utc))
546537
return unix_millis
547538

548539

540+
def gps_datetime_to_gps_millis(t_gps):
541+
"""Convert datetime in GPS time of reference to milliseconds since GPS Epoch.
542+
543+
GPS Epoch starts at the 6th January 1980.
544+
This function assumes that the input datetime is in the GPS time
545+
frame of reference and converts that to GPS milliseconds.
546+
547+
Parameters
548+
----------
549+
t_datetime : datetime.datetime or array-like of datetime.datetime
550+
GPS time as a datetime object.
551+
552+
Returns
553+
-------
554+
gps_millis : float or np.ndarray
555+
Milliseconds since GPS Epoch (6th January 1980 GPS). Either
556+
`float` or `np.ndarray` with `dtype = float`.
557+
558+
559+
"""
560+
if isinstance(t_gps,datetime):
561+
t_gps = [t_gps]
562+
if isinstance(t_gps,np.ndarray) \
563+
and len(np.atleast_1d(t_gps)) == 1:
564+
t_gps = [t_gps.item()]
565+
gps_millis = []
566+
for t_datetime in t_gps:
567+
gps_milli = (t_datetime - GPS_EPOCH_0).total_seconds()*1000
568+
gps_millis.append(gps_milli)
569+
gps_millis = np.squeeze(np.asarray(gps_millis))
570+
return gps_millis
571+
572+
573+
549574
def tzinfo_to_utc(t_datetime):
550575
"""Raises warning if time doesn't have timezone and converts to UTC.
551576

0 commit comments

Comments
 (0)