Getting UTC time from Posix time - leap seconds

70 views (last 30 days)
Posix time is almost based on UTC, but doesn't include leap seconds that get thrown in at unpredictable (at least to me) intervals every few years.
I can convert posix time to calendar time in the date time format using datetime(xxx,'ConvertFrom','posixtime') but I need time in UTC. Does the
conversion add back in the missing leap seconds? There have been 26 leap seconds since 1970, and I suspect we're due for another soon. I
don't want to have to modify my code everytime there's a leap second.
  1 Comment
Yair Altman
Yair Altman on 14 Nov 2020
For the benefit of potential users who just need plain POSIX-to-UTC datetime conversion and don't care about the leap seconds, an easy and super-fast method is to use the vectorized input format of the datenum function. i.e.
datenum([1970 1 1 0 0 posixValue])
For example:
>> datestr(datenum([1970 1 1 0 0 129450789]))
ans =
'07-Feb-1974 06:33:09'
I am aware that this does not directly answer the poster's question (where leap secs are indeed needed), but in most cases for readers coming across this post, these leap seconds are not very important and then they could use datenum, on all Matlab releases (even R2019b or earlier).

Sign in to comment.

Answers (2)

Swatantra Mahato
Swatantra Mahato on 23 Oct 2020
Hi,
I am assuming you want to convert posixtime to UTC and take into account the leap seconds.
This can be done using the "datetime" function by also passing the 'TimeZone' Property as 'UTCLeapSeconds'. As an example to show the difference, consider the script:
d1=datetime(129450789,'ConvertFrom','posixtime','TimeZone','UTC'); %07-Feb-1974 06:33:09
d2=datetime(129450789,'ConvertFrom','posixtime','TimeZone','UTCLeapSeconds');
d3=datetime(14383421,'ConvertFrom','posixtime','TimeZone','UTC'); %16-Jun-1970 11:23:41
d4=datetime(14383421,'ConvertFrom','posixtime','TimeZone','UTCLeapSeconds');
a=d1-d3;
b=d2-d4;
On executing "a-b" on the MATLAB command line we get:
>> a-b
ans =
duration
-00:00:03
which is in line with there being 3 leap seconds in the duration considered.
You can refer to the documentation for the "datetime" function for more information:
Hope this helps
  4 Comments
Bruce McGuffin
Bruce McGuffin on 27 Oct 2020
So just to be clear, converting posix time to datetime format using the datetime command with arguement pair 'TimeZone', 'UTCleapseconds' does not add missing leap seconds to the result, but the missing leap seconds may be hidden away in some undocumented part of the datetime structure.
It looks like I could find the number of leap seconds since June 30, 1972 using the 'leapseconds' command, but that doesn't appear to be available in the version of matlab I am currently using. When was it added?
Steven Lord
Steven Lord on 27 Oct 2020
According to the entry at the end of the leapseconds documentation page it was Introduced in R2020a.

Sign in to comment.


Peter Perkins
Peter Perkins on 18 Nov 2020
Bruce, you are getting tripped up by the difference between clockface time, and the elapsed time since 1970. Here's the deal:
As you already know, the "real actual" UTC time line includes leap seconds that are inserted at somewhat unpredictable intervals. So in the "real actual" UTC time line, if you count the number of seconds since 1970, you get something that's 27s longer than what you might have expected. In MATLAB, you can demonstrate that using datetime, and MATLAB calls that timeline 'UTCLeapSeconds':
>> t = datetime(2020,11,18,'TimeZone','UTCLeapSeconds')
t =
datetime
2020-11-18T00:00:00.000Z
>> t - datetime(1970,1,1,'TimeZone','UTCLeapSeconds')
ans =
duration
446016:00:27
But here's the thing: almost noone wants to hear that "truth". So the POSIX time line, and datetime's default "unzoned" behavior, and datetime's behavior for 'UTC' tell a white lie
>> t = datetime(2020,11,18,'TimeZone','UTC','Format','dd-MMM-uuuu HH:mm:ss.SSS')
t =
datetime
18-Nov-2020 00:00:00.000
>> t - datetime(1970,1,1,'TimeZone','UTC')
ans =
duration
446016:00:00
because most people don't want their calculations involving elapsed times to "randomly" be off by several seconds. I can't tell which group of people you are in, but if all you are ultimately doing is converting to a text clockface timestamp, it won't matter unless one of your timestamps falls on a leapsecond. Generally speaking, people who need to care about leaps seconds are often doing things with satellites or similar, and they can opt in.
More details:
When you say, "converting posix time to datetime format using the datetime command with arguement pair 'TimeZone', 'UTCleapseconds' does not add missing leap seconds to the result", that's not actually true. You have to consider that POSIX times are not on the "leap seconds" time line, so converting to the latter has to account for the leap seconds. For a POSIX time around now, that means adding in 27s, but what's tripping you up is that it does not change the clockface time. In other words, the POSIX time for midnight 18-Nov-2020 may claim to be 1605657600s since 1970, but midnight 18-Nov-2020 in the "real actual" UTC timeline that 'UTCLeapSeconds' represents is 1605657627s since 1970. These two things
>> t1 = datetime(2020,11,18,'TimeZone','UTCLeapSeconds')
t1 =
datetime
2020-11-18T00:00:00.000Z
>> t1 = datetime(2020,11,18,'TimeZone','UTC','Format','dd-MMM-uuuu HH:mm:ss.SSS')
t1 =
datetime
18-Nov-2020 00:00:00.000
look the same, and refer to the same instant in time, but calculating elapsed times with them is not the same.
In short: converting POSIX to/from (MATLAB's) 'UTC' is straight-forward because they both tell the same white lie. Converting to/from 'UTCLeapSeconds' needs, internally, to add in leap seconds in one direction, and remove them in the other. And of course, what is the POSIX time for 31-Dec-2016 23:59:60Z? Uhhhh ... it doesn't exist. It's an ill-defined question. So datetime maps that back to 31-Dec-2016 23:59:59Z.
  1 Comment
James Tursa
James Tursa on 7 Aug 2023
Edited: James Tursa on 14 Aug 2023
"... And of course, what is the POSIX time for 31-Dec-2016 23:59:60Z? Uhhhh ... it doesn't exist. It's an ill-defined question ..."
To my understanding, posix times during +1 leap seconds certainly do exist, it is just that the resulting posix times are ambiguous (for -1 leap seconds there would be undefined UTC counterparts). E.g., for this +1 leap second:
format longg
dt = datetime(2016,12,31,23,59,59,'TimeZone','UTCLeapSeconds') + seconds(0:0.5:3)';
posixtime_dt = posixtime(dt);
table(dt,posixtime_dt)
ans = 7×2 table
dt posixtime_dt ________________________ ____________ 2016-12-31T23:59:59.000Z 1483228799 2016-12-31T23:59:59.500Z 1483228799.5 2016-12-31T23:59:60.000Z 1483228800 2016-12-31T23:59:60.500Z 1483228800.5 2017-01-01T00:00:00.000Z 1483228800 2017-01-01T00:00:00.500Z 1483228800.5 2017-01-01T00:00:01.000Z 1483228801
Note that there are two datetimes associated with the posix time 1483228800 when using the 'UTCLeapSeconds' time zone, and this is totally correct. In fact, all the posix times in the interval [1483228800,1483228801) certainly exist but they are ambiguous because of the 1 second jump back in time. It is when you try to convert this to MATLAB's 'UTC' time zone that you run into problems because that time zone doesn't have this leap second. But MATLAB has to do something, so this is what it does:
dt_utc = datetime(dt,'TimeZone','UTC');
posixtime_dt_utc = posixtime(dt_utc);
table(dt,posixtime_dt,dt_utc,posixtime_dt_utc)
ans = 7×4 table
dt posixtime_dt dt_utc posixtime_dt_utc ________________________ ____________ ________________________ ________________ 2016-12-31T23:59:59.000Z 1483228799 2016-12-31T23:59:59.000Z 1483228799 2016-12-31T23:59:59.500Z 1483228799.5 2016-12-31T23:59:59.500Z 1483228799.5 2016-12-31T23:59:60.000Z 1483228800 2016-12-31T23:59:59.000Z 1483228799 2016-12-31T23:59:60.500Z 1483228800.5 2016-12-31T23:59:59.500Z 1483228799.5 2017-01-01T00:00:00.000Z 1483228800 2017-01-01T00:00:00.000Z 1483228800 2017-01-01T00:00:00.500Z 1483228800.5 2017-01-01T00:00:00.500Z 1483228800.5 2017-01-01T00:00:01.000Z 1483228801 2017-01-01T00:00:01.000Z 1483228801
The posixtime_dt column is correct and the jump back in time happens at the proper instant. Converting dt to 'UTC' results in the jump occurring back to the 59 second mark. OK, that part is fine if that is how MATLAB wants to do it (I would have done it differently to make it consistent with posixtime), but then calling posixtime on that 'UTC' datetime results in an incorrect posix time of the original dt ... the jump back in time does not occur at the proper instant. Bottom line is you need to be careful how you use the MATLAB functions when dealing with leap seconds. Converting 'UTCLeapSeconds' to 'UTC' and then calling posixtime( ) on that will not get you the correct result during the leap second.
Also, on older versions of MATLAB the posixtime( ) and convertTo(__,'posix') functions cannot handle non-scalar variables with 'UTCLeapSeconds' time zone inputs. So here you are forced to write different code, like do the calculations individually in a loop or convert to 'UTC' first and then do a correction. E.g.,
unix_time = posixtime_dt_utc + (second(dt)>=60); % add 1 to seconds in 60+ range
table(dt,posixtime_dt,unix_time)
ans = 7×3 table
dt posixtime_dt unix_time ________________________ ____________ ____________ 2016-12-31T23:59:59.000Z 1483228799 1483228799 2016-12-31T23:59:59.500Z 1483228799.5 1483228799.5 2016-12-31T23:59:60.000Z 1483228800 1483228800 2016-12-31T23:59:60.500Z 1483228800.5 1483228800.5 2017-01-01T00:00:00.000Z 1483228800 1483228800 2017-01-01T00:00:00.500Z 1483228800.5 1483228800.5 2017-01-01T00:00:01.000Z 1483228801 1483228801
Finally, I would add that 'UTCLeapSeconds' also affects how the juliandate( ) function behaves near leap seconds. This function will smear the leap second across the +/-12 hour vicinity of the leap second, with the Julian Date day boundary occurring at the 60.5 second mark. Something to be aware of if you are working with Julian Dates. E.g.,
juliandate_dt = juliandate(dt);
juliandate_dt_utc = juliandate(dt_utc);
table(dt,juliandate_dt,dt_utc,juliandate_dt_utc)
ans = 7×4 table
dt juliandate_dt dt_utc juliandate_dt_utc ________________________ ________________ ________________________ _________________ 2016-12-31T23:59:59.000Z 2457754.49998264 2016-12-31T23:59:59.000Z 2457754.49998843 2016-12-31T23:59:59.500Z 2457754.49998843 2016-12-31T23:59:59.500Z 2457754.49999421 2016-12-31T23:59:60.000Z 2457754.49999421 2016-12-31T23:59:59.000Z 2457754.49998843 2016-12-31T23:59:60.500Z 2457754.5 2016-12-31T23:59:59.500Z 2457754.49999421 2017-01-01T00:00:00.000Z 2457754.50000579 2017-01-01T00:00:00.000Z 2457754.5 2017-01-01T00:00:00.500Z 2457754.50001157 2017-01-01T00:00:00.500Z 2457754.50000579 2017-01-01T00:00:01.000Z 2457754.50001736 2017-01-01T00:00:01.000Z 2457754.50001157
And the smearing effect:
dt = datetime(2016,12,31,23,59,59,'TimeZone','UTCLeapSeconds') + seconds(-43200:43200)';
jd_dt = juliandate(dt);
1-mean(diff(jd_dt)*86400)
ans =
1.15740112960339e-05
1/86400
ans =
1.15740740740741e-05
The average value of a second during the +/-12 hours surrounding the leap second time is slightly less than 1 second, and the amount of this difference is pretty darn close to 1/86400. That is, the 1 leap second has been smeared across the +/- 12 hour time interval. So Julian Dates calculated from the 'UTCLeapSeconds' dt variable will be slightly different from the Julian Dates calculated from their 'UTC' counterparts during this 24 hour interval. If you don't want this smearing, then you will need to convert to 'UTC' first and then write some code for how you want to handle the leap second itself.
*** Update 8/14/2023 ***
I have been informed by Mathworks that they are looking into this and may change the behavior of datetime when converting 'UTCLeapSeconds' to 'UTC' during the leap second in future MATLAB releases.

Sign in to comment.

Categories

Find more on Dates and Time in Help Center and File Exchange

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!