Search code examples
pythondatetimetimedstleap-second

Safest and most reliable way to measure short intervals in Python? (cross-platform, cross-hardware, resistant to DST and leap seconds)


The more I read about datetime arithmetic, the bigger a headache I get.

There's lots of different kinds of time:

  • Civil time
  • UTC
  • TAI
  • UNIX time
  • system time
  • thread time
  • CPU time

And then the clocks can run faster or slower or jump backwards or forwards because of

  • daylight savings
  • moving across timezones
  • leap seconds
  • NTP synchronization
  • general relativity

And how these are dealt with depends in turn on:

  • Operating system
  • Hardware
  • Programming language

So please can somebody tell me, for my specific use case, the safest and most reliable way to measure a short interval? Here is what I am doing:

I'm making a game in Python (3.7.x) and I need to keep track of how long it has been since certain events. For example, how long the player has been holding a button, or how long since an enemy has spotted the player, or how long since a level was loaded. Timescales should be accurate to the millisecond (nanoseconds are overkill).

Here are scenarios I want to be sure are averted:

  • You play the game late at night. In your timezone, on that night, the clocks go forward an hour at 2am for DST, so the minutes go: 1:58, 1:59, 2:00, 3:01, 3:02. Every time-related variable in the game suddenly has an extra hour added to it -- it thinks you'd been holding down that button for an hour and 2 seconds instead of just 2 seconds. Catastrophe ensues.

  • The same, but the IERS decides to insert or subtract a leap second sometime that day. You play through the transition, and all time variables get an extra second added or subtracted. Catastrophe ensues.

  • You play the game on a train or plane and catastrophe ensues when you cross a timezone boundary and/or the International Date Line.

  • The game works correctly in the above scenarios on some hardware and operating systems, but not others. I.e. it breaks on Linux but not Window, or vice versa.

And I can't really write tests for these since the problematic events come around so rarely. I need to get it right the first time. So, what time-related function do I need to use? I know there's plain old time.time(), but also a bewildering array of other options like

  • time.clock()
  • time.perf_counter()
  • time.process_time()
  • time.monotonic()
  • and then nanosecond variants of all of the above.

From reading the documentation it seems like time.monotonic() is the one I want. But if reading about all the details of timekeeping has taught me anything, it's that these things are never quite what they seem. Once upon a time, I thought I knew what a "second" was. Now I'm not so sure.

So, how do I make sure my game clocks work properly?


Solution

  • The specification of time module is the best place to look for details about each of those.

    There, you can easily see that:

    • time.clock() is deprecated and should be replaced with other functions
    • time.process_time() counts only CPU time spent by your process, so it is not suitable for measuring wall clock time (which is what you need)
    • time.perf_counter() has the same problem as time.process_time()
    • time.time() is just about right, but it will give bad timings if user modifies the current time
    • time.monotonic() - this seems to be the safest bet for measuring time intervals - note that this does not give you current time at all, but it gives you a correct difference between two time points

    As for the nanoseconds versions, you should use those only if you need nanoseconds.