GPS time vs. UTC

If you have ever worked with GPSes you probably know about the NMEA protocol. Many of the sentences there have got a time stamp that is in UTC (universal time coordinated – thank the French for the odd word order). Like me, you might have assumed that GPS works with UTC, but that is not the case.

Internally GPS works with GPS time which is kept as weeks and seconds since the start of the GPS system at 00:00:00 on 1980-01-06. This time is kept by atomic clocks. The other GNSSes (Global Satellite Navigation Systems) also have their own internal clock.

UTC (formerly known as GMT) is the local time at longitude 0 (where the London district Greenwich is located, hence Greenwich Mean Time), without any fiddling with daylight savings time. Since UTC is meant to conform closely to the rotation of the earth and the latter is not stable, it needs the occasional leap second. At the time of this writing, there have been 18 of these since 1980. So UTC is 18 seconds behind GPS time by now.

Why do I write about this? I have recently started integrating an OXTS INS into one of our measurement vehicles. These devices can be configured to send NMEA data over a serial port, just like a conventional GPS. They can also send NCOM data, which is their internal binary format. While the NMEA data adheres to the NMEA specification of providing the times in UTC, the NCOM data does provide the “native” GPS time. So they do not match, which took me by surprise when I encountered it.

Of course I first assumed a bug in my own code, but it wasn’t. The first hint was that the times were always 18 seconds off. I still didn’t get it and had to actually call support. (Afterwards I googled it and found lots of references, so I guess I should have been able to figure it out myself.)

Since that offset is not constant – there might be yet another leap second soon – the NCOM format also contains the current UTC time offset. Look in Table 26 on the NCOM format documentation.

It’s sent in the Status Information as channel 15 and is stored in byte 7 of the BatchS structure. Just to make it difficult to read it is encoded as a 8 bit signed integer with a twist: Bits 1–7: UTC time offset, valid when Bit 0 = 1.

Here is some Delphi code to decode that particular value:

function TSTable26Rec.TryGetUtcTimeOffsetSecs(out _Offset: Integer): Boolean;
  // only valid if Bit0 = 1
  Result := ((UtcTimeOffset and $01) <> 0);
  if Result then begin
    // the type cast to Smallint is necessary to get the sign correctly
    _Offset := Smallint(UtcTimeOffset shr 1);