9
\$\begingroup\$

(Note: although related, this challenge is not a duplicate of this one because it requires determining leap seconds automatically rather than hardcoding their times, and is not a duplicate of this one because most of the difficulty comes from determining the time without leap second skewing, something that most time APIs don't do by default. As such, a solution is likely to look different from a solution to either of those challenges.)

We're coming to the end of 2016, but it's going to take slightly longer than most people expect. So here's a challenge celebrating our extra second this year.

Output the current time in UTC, as hours, minutes, seconds. (For example, legitimate output formats for midday would include 12:00:00 and [12,0,0]; the formatting isn't massively important here.)

However, there's a twist: your program must handle leap seconds appropriately, both past and future. This means that your program will have to obtain a list of leap seconds from some online or automatically updated/updatable source. You may connect to the Internet to obtain this if you wish. However, you may only connect to a URL that predates this challenge (i.e. no downloading parts of your program from elsewhere), and you may not use the connection to determine the current time (specifically: your program must function even if any attempt to access the Internet returns a page that's up to 24 hours stale).

Most operating systems' default APIs for the current time will skew time around leap seconds in order to hide them from programs that might otherwise be confused. As such, the main difficulty of this challenge is to find a method or an API to undo that, and work out the true unmodified current time in UTC.

In theory, your program should be perfectly accurate if it ran on an infinitely fast computer, and must not intentionally take more than zero time to run. (Of course, in practice, your program will run on an imperfect computer, and so will probably not run instantaneously. You don't have to worry about this invalidating the results, but must not depend on it for the correctness of your program.)

Your program must function regardless of which timezone the system clock is set to. (However, it may request information from the operating system or environment as to what timezone is being used, and may assume that the reply is accurate.)

As a , the shortest program wins. Good luck!

\$\endgroup\$
2
\$\begingroup\$

PowerShell, 161 bytes

(('{0:H:m:s}','23:59:60')[(($d=[datetime]::UtcNow).Ticks-6114960*98e9)/1e8-in((irm ietf.org/timezones/data/leap-seconds.list)-split'[^@]	|
'-match'^\d{9}')])-f$d

Try it online! (doesn't work here, it seems that TIO doesn't recognize irm and iwr, maybe it's a security feature?)

Logic Test (Hardcoded Date):

(('{0:H:m:s}','23:59:60')[(($d=[datetime]'1/1/2017 00:00:00').Ticks-6114960*98e9)/1e8-in((irm ietf.org/timezones/data/leap-seconds.list)-split'[^@]	|
'-match'^\d{9}')])-f$d

Notes

There is a literal TAB and a literal linebreak (0xA) in the regex string, so that I didn't have to escape them (saved 1 byte each).

Explanation

The times given (of the leap seconds) in the ietf file are in seconds since NTP epoch which is 1/1/1900 00:00:00. On Windows, a "tick" is simply one ten-millionth of a second (10,000,000 ticks/sec).

If you add an integer to a [datetime] it counts the integer value as ticks, so I'm using the hardcoded tick value of NTP epoch, then subtracting it from the tick value of the current UTC time (the original value of which is simultaneously being assigned to $d).

To make that hardcoded tick value smaller, I took away some zeros (9 of them) and divided by 98, then multiply by 98e9 (98*109).

The result of that subtraction is the value in ticks since NTP epoch. That gets divided by 1e8 (not 1e9, for reasons that will be clear in a moment) to get the value in seconds (sort of) since NTP epoch. It will be smaller by a factor of 10, actually.

Retrieving the document from the IETF, rather than split it into lines first, then process the lines with the timestamps, I decided to split on both line breaks and on TAB characters, since that's what comes after the timestamp. Because of a single errant line in the file, that alone would include an additional timestamp we don't want. The line looks like this:

#@	3707596800

So I changed the regex to split on [^@]\t (any character that's not @ followed by a TAB) which works to exclude that line, but also ends up consuming the last 0 in each of the timestamps.

That's why I divide by 1e8 and not 1e9, to account for the missing 0.

The current timestamp is checked to see it exists inside the list of neutered leap second timestamps. This whole process I described is inside the array accessor [], so the resulting [bool] value gets coalesced to a 0 ($false) or 1 ($true). The array we're indexing into contains two elements: a format string for displaying the time, and a hardcoded 23:59:60. The truthiness of the aforementioned comparison determines which one will be chosen, and that is fed into the format operator -f with the previously assigned current date $d as its parameter.

\$\endgroup\$
  • \$\begingroup\$ What does this print the second after a leap second? I can follow the logic for leap seconds, but not sure I can follow the logic for times either side. (Does Windows naturally display both a leap second and the following second as 00:00:00?) \$\endgroup\$ – user62131 Jan 2 '17 at 8:50
  • \$\begingroup\$ @ais523 it will display the second after the leap second as whatever the system time is. Windows doesn't know about leap seconds, so the clock will be 1 second fast until corrected by a sync or manual setting. Given the requirements I can't really adjust for that, since it's not possible to determine whether the system time is correct or not without using an external time source, which is banned. About the best I could do is add code to this that forces NTP sync after it has printed the time, which of course could fail (network error, no time source set, etc.), if that's even allowed. \$\endgroup\$ – briantist Jan 2 '17 at 20:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy