Thread overview
[Issue 19808] SysTime gives different results at compile vs. run time execution
April 15
https://issues.dlang.org/show_bug.cgi?id=19808

Jonathan M Davis <issues.dlang@jmdavisProg.com> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
                 CC|                            |issues.dlang@jmdavisProg.co
                   |                            |m
          Component|phobos                      |dmd

--- Comment #1 from Jonathan M Davis <issues.dlang@jmdavisProg.com> ---
SysTime's opEquals doesn't care one whit about the time zone. It only cares about the time in UTC. So, even if drastically different time zones are used, if they end up being the same time in UTC, then the two SysTimes will be equal.

As for not getting Z in the string with a SysTime created at compile-time that uses UTC, I don't know how that could be fixed. It has to do with how CTFE and classes work (which wasn't even possible when SysTime was originally written). In order to distinguish between UTC and a time zone that happens to line up with UTC (and some time zones line up during part of the year but not all of the year), the to*String functions on SysTime compare the _timezone object in the SysTime with UTC() using the is operator - so it's a pointer comparison. UTC is a singleton, and only one instance of it should exist. Specifically, it has these declarations:

    static immutable UTC _utc = new immutable(UTC)();

    static immutable(UTC) opCall() @safe pure nothrow
    {
        return _utc;
    }


However, when CTFE does its thing, it's somehow allocating a separate UTC instance and then restoring it at runtime without that instance being the same one that ends up in the static _utc member of the UTC class. It really should be the same instance, but clearly, it's not. And I don't know enough about how classes and CTFE work to know whether that's fixable or not.

--
April 17
https://issues.dlang.org/show_bug.cgi?id=19808

--- Comment #2 from anonymous4 <dfj1esp02@sneakemail.com> ---
I believe ISO 8601 even provides rationale that time zones are too messy to address so it doesn't and only specifies UTC offset, which is not a time zone, and offset 0 is UTC.

--
April 17
https://issues.dlang.org/show_bug.cgi?id=19808

--- Comment #3 from David Eckardt <david.eckardt@frequenz.io> ---
Jonathan, I see the problem. I wonder how `is` at run-time for class objects created at compile-time is supposed to work, as it compares memory addresses, but IIUC CTFE doesn’t use a model of addressed memory.

Bummer.

--
April 19
https://issues.dlang.org/show_bug.cgi?id=19808

--- Comment #4 from Jonathan M Davis <issues.dlang@jmdavisProg.com> ---
(In reply to anonymous4 from comment #2)
> I believe ISO 8601 even provides rationale that time zones are too messy to address so it doesn't and only specifies UTC offset, which is not a time zone, and offset 0 is UTC.

The bit with Z for when the time zone is UTC comes from ISO 8601. Otherwise, it handles all time zones as offsets from UTC and does not get into America/Los_Angeles or Pacific Standard Time or any other form of defining a time zone, which means that an ISO 8601 timestamp really just gives you the offset from UTC at the point in time that the timestamp is for, not the time zone that it's really associated with, with UTC itself being the only exception.

(In reply to David Eckardt from comment #3)
> Jonathan, I see the problem. I wonder how `is` at run-time for class objects created at compile-time is supposed to work, as it compares memory addresses, but IIUC CTFE doesn’t use a model of addressed memory.
> 
> Bummer.

I don't know. CTFE is one of those things which has grown organically over time, which makes some aspects of it kind of messy. However, in this case, your example isn't actually using is at compile-time, because the strings are being generated at runtime. The issue is that the UTC object that's created at compile-time is then not the one that ends up in the static variable to be used at runtime when calling UTC(). I don't know what would happen if you called toISOExtString at compile-time. I'd have to test it. It might not even work due to using a pointer comparison.

--
April 20
https://issues.dlang.org/show_bug.cgi?id=19808

--- Comment #5 from David Eckardt <david.eckardt@frequenz.io> ---
> I don't know what would happen if you called toISOExtString at compile-time. I'd have to test it. It might not even work due to using a pointer comparison.

I tried it:

import std.datetime.systime: SysTime;
import std.datetime.timezone: UTC;
pragma(msg, SysTime.fromUnixTime(0, UTC()).toISOExtString());

fails with “typecons.d(2145): Error: reinterpretation through overlapped field `stripped` is not allowed in CTFE”.

Consider SysTime.fromUnixTime(0, LocalTime()) at compile-time: The `is`
comparison` could really cause a bug here, using UTC instead. However,

static immutable ctfe = SysTime.fromUnixTime(0, LocalTime());

fails with “concurrency.d(2488): Error: static variable `lock` cannot be read at compile time”. So we are safe.

--
April 20
https://issues.dlang.org/show_bug.cgi?id=19808

--- Comment #6 from David Eckardt <david.eckardt@frequenz.io> ---
> your example isn't actually using is at compile-time, because the strings are being generated at runtime.

That is right, and it made the cause even harder to find. Without thinking a lot about it I changed SysTime.fromUnixTime to run at compile-time. Suddenly toISOExtString(), called at run-time somewhere else in the code, returned a different value.

Of course every programmer has one of these moments every once in a while. I
hope the following doesn’t sound too much like trolling, as there isn’t much
that can be done, I just can’t help finding it unfortunate that
• a new language feature, as useful as it might be, can cause an already
existing part of the standard library to suddenly return a different result,
• a naïve user can trigger it with a simple, subtle, seemingly innocent code
change (just add `static` before `immutable`),
• it seems to be unfixable in the affected standard library code and
• a more serious bug (incorrectly using UTC instead of local time) seems to be
prevented more by chance than by design.

--