Welcome, guest | Sign In | My Account | Store | Cart

A Mutton, Lettuce and Tomato sandwich is generally agreed to be the greatest thing in the world. But a different kind of MLT comes in at a close second: Monotonic Local Time. This is a time value in your local timezone that takes care of that annoying hour that happens once a year where time goes backwards. It works by extending the previous day by a couple of hours (24, 25 etc) until after the switch.

Python, 47 lines
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import time

def monotoniclocaltime(seconds=None):
    """monotoniclocaltime([seconds]) -> (tm_year,tm_mon,tm_mday,tm_hour,tm_min,
                                  tm_sec,tm_wday,tm_yday,tm_isdst)

Convert seconds since the Epoch to a time tuple expressing monotonic
local time. Monotonicity is achieved by extending the day before the
end of DST with some extra hours (24, 25 etc) until after the switch."""

    if seconds is None:
        seconds = time.time()

    res = time.localtime(seconds)

    dayseconds = res.tm_sec + res.tm_min*60 + res.tm_hour*3600
    nextmidnight = time.localtime(seconds - dayseconds + 86400)

    if res.tm_isdst and not nextmidnight.tm_isdst:
        tl = list(time.localtime(seconds - 86400))  # same time yesterday
        tl[3] += 24                                 # plus 24h
        res = time.struct_time(tl)

    return res

def test():

    def findswitch():
        start = (int(time.time()) // 86400) * 86400
        for t in range(start, start + 365*86400, 3600):
            if monotoniclocaltime(t) != time.localtime(t):
                return t

    def iso8601(tup):
        return "%04d-%02d-%02d %02d:%02d:%02d" % tuple(tup)[:6]

    switch = findswitch()
    if not switch:
        print("No time zone transitions found in your local timezone")
        return

    for t in range(switch-3600*5, switch+3600*5, 3600):
        print(iso8601(monotoniclocaltime(t-1)))
        print(iso8601(monotoniclocaltime(t)))

if __name__ == '__main__':
   test()

The correct way to store timestamps is obviously either in UTC or in local time with a timezone offset. Unfortunately, some systems still use unqualified local time and cannot be easily modified. This leads to ambiguous and non-monotonic time values on the day DST ends and an hour is duplicated. In some cases timestamps are in ISO8601 format and do have an associated timezone offset but rely on the fact that ISO8601 formatted timestamps sort correctly as strings in lexicographic order - except for that one hour per year.

There are some precedents for using hours greater than 23. The ISO8601 standard allows the use of the time 24:00:00 to indicate the end of the day (although, like many other options in ISO8601, this is not supported by the more commonly implemented subset specified in rfc3339). Times at or shortly after midnight (e.g. movie show times) sometimes use hours greater than 23 to keep them grouped with the date of the previous day.

I guess this is going to be a bit controversial. In many cases this trick is likely to cause more trouble than it saves, but I find it to be appropriate in certain circumstances.

$ TZ="America/New_York" python mlt.py
2013-11-02 18:59:59
2013-11-02 19:00:00
2013-11-02 19:59:59
2013-11-02 20:00:00
2013-11-02 20:59:59
2013-11-02 21:00:00
2013-11-02 21:59:59
2013-11-02 22:00:00
2013-11-02 22:59:59
2013-11-02 23:00:00
2013-11-02 23:59:59
2013-11-02 24:00:00
2013-11-02 24:59:59
2013-11-02 25:00:00
2013-11-02 25:59:59
2013-11-03 01:00:00
2013-11-03 01:59:59
2013-11-03 02:00:00
2013-11-03 02:59:59
2013-11-03 03:00:00