As I have stated in "Change Time to a struct/eliminate Interval," I believe the concept of Time should be unified into two types, a type to represent a point in time, and a type to represent a length of time. The current implementation has three definitions of time:
- DateTime is used to represent points in time AND lengths of time as a structure with some conversion/construction methods
- Time is used to represent points in time AND lengths of time as a long (acutally as an enum).
- Interval is used to represent lengths of time as a float.
I propose the following structures to be the only representations of time:
- DateTime: represents a point in time. Cannot represent a length of time.
- TimeSpan: represents a length of time.
I have two reasons for this modification:
First, Interval should not be a floating point value. As I have demonstrated in the above mentioned forum post, 80% of integer values representing microseconds from 0 to int.max cannot be represented as floating point values accurately.
Second, using structures instead of aliases would allow for implementation hiding. A structure which contains one member that represents the time takes up the same amount of space as that member alone, but allows for methods, constructors, conversion operators, etc.
Where should they go? TimeSpan should live in core, DateTime can live in core, or could be put into util/time.
How does this affect the current tree? Instances in the tango tree of Interval should be replaced with TimeSpan. Instances of Time should sometimes be replaced with DateTime, sometimes with TimeSpan, depending on the usage of the variable.
I have performed the update to the tree myself, and there were about 50-60 files that needed updating. The updates were mostly trivial, and actually improved the readability and comprehension of the code. For example, in net/http/HttpClient.d, the following was the original code:
private Interval timeout = 3;
After the update, this reads:
private TimeSpan timeout = TimeSpan.fromSeconds(3);
The second clearly states that the timeout is 3 seconds, without the necessity of a comment.
Because of the many changes, I think a lot of people will have to evaluate whether they want this change or not. Please read my forum post for the detailed arguments I have for this change.
I have attached the two files, TimeSpan.d and the updated DateTime.d.
I am not dead-set on the names DateTime and TimeSpan, I just used them because it's the same as C# (which gave me the idea), and seems logical.
In addition to the above example, here are comparisons of how some code would look with the current implementation to my implementation:
// confusing type conventions
Time t1 = Clock.now;
Time t2 -= Time.TicksTo1970; // 'Time' now represents ticks since 1970
//
// after some code execution
// ...
//
Time t3 = Clock.now - t1; // 'Time' now represents a length
Time t4 = t3 / Time.TicksPerMillisecond // 'Time' now represents length in ms
// calculating how long to sleep until the start of tomorrow.
DateTime t1 = DateTime.now;
DateTime t2 = t1.date.addDays(1);
DateTime timeToSleep = t2 - t1;
// sleep takes Interval, so now we must convert to Interval
Interval sleeptime = cast(Interval)timeToSleep.ticks / Time.TicksPerSecond;
Thread.sleep(sleeptime);
And the new way:
// no longer confusing type conventions
DateTime t1 = Clock.now;
TimeSpan t2 = t1 - DateTime.Epoch1970; // represents ticks since 1970
//
// after some code execution
// ...
//
TimeSpan t3 = Clock.now - t1; // represents a length of time
// DateTime t3 = Clock.now - t1; // doesn't compile, because
// t3 cannot be a point in time.
long t4 = t3.toMilliseconds; // no longer a TimeSpan because it's
// not in ticks.
// TimeSpan t4 = t3.toMilliseconds; // doesn't compile.
// calculating how long to sleep until the start of tomorrow.
DateTime t1 = DateTime.now;
DateTime t2 = t1.date.addDays(1);
TimeSpan timeToSleep = t2 - t1;
// sleep now takes TimeSpan, so conversion isn't needed.
Thread.sleep(timeToSleep);