Download Reference Manual
The Developer's Library for D
About Wiki Forums Source Search Contact

Ticket #671 (closed enhancement: fixed)

Opened 1 year ago

Last modified 9 months ago

Reduce all Time types to 2 structures

Reported by: schveiguy Assigned to: schveiguy
Priority: blocker Milestone: 0.99.4
Component: Core Functionality Version: trunk
Keywords: Cc:

Description

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);

Attachments

DateTime.d (23.4 kB) - added by schveiguy on 10/09/07 11:29:24.
new DateTime file
TimeSpan.d (8.3 kB) - added by schveiguy on 10/09/07 11:30:21.
TimeSpan file

Change History

10/09/07 11:29:24 changed by schveiguy

  • attachment DateTime.d added.

new DateTime file

10/09/07 11:30:21 changed by schveiguy

  • attachment TimeSpan.d added.

TimeSpan file

(in reply to: ↑ description ) 10/09/07 12:53:49 changed by schveiguy

Replying to schveiguy:

DateTime t2 = t1.date.addDays(1);

Sorry, I removed this function, this should read:

DateTime t2 = t1.date + TimeSpan.fromDays(1);

11/13/07 18:13:13 changed by sean

  • owner changed from sean to kris.

After a brief review, I think these proposed changes are a good idea. They seem to simplify things and decrease the chance of misuse at the same time. Also, this may actually provide a small performance gain over the current approach because it won't involve any floating point math. I'll admit that one thing which originally appealed to me about floating point intervals was the gee-whiz factor of being able to represent huge time intervals, but I suspect any science-type apps which actually care about this would use their own time representations anyway.

I'm going to pass this ticket on to Kris for approval, and assuming it gets the green light we'll likely work together on the changes.

11/13/07 18:14:47 changed by sean

  • milestone set to 0.99.4.

(follow-up: ↓ 6 ) 11/13/07 21:09:26 changed by kris

the reason for an FP interval had nothing to do with "gee whiz", and everything to do with avoiding a fixed timing interval (such as milliseconds)

:p

11/13/07 22:46:06 changed by kris

Something else to consider: An interface is a contract, and is deliberately void of implementation detail. Tango has a number of interfaces, and I've tried hard to keep them as pure contracts.

Thus I'm hesitant, for example, to declare an interface parameter to be a TimeSpan? (thus importing a concrete implementation into an interface). The contract and implementation would obviously not be distinct anymore :)

(in reply to: ↑ 4 ) 11/14/07 09:57:16 changed by schveiguy

Replying to kris:

the reason for an FP interval had nothing to do with "gee whiz", and everything to do with avoiding a fixed timing interval (such as milliseconds)

And the point of having a fixed timing interval is:

  1. you want to be very accurate for very small intervals, presumably less than 100ns since that is the resolution of TimeSpan?
  2. you want to allow gigantic timing intervals, such as 15000 years, which could not be represented by TimeSpan?.

First, a floating point cannot always accurately represent those values anyways.

Think about this: If I use SelectSelector?, and want to select for 1 microsecond (i.e. pass in .000001), it does not work with the current implementation due to floating point error. Instead it passes a 0 timeval to the underlying select function. This in itself violates the contract of select, because it is required to sleep AT LEAST the time specified.

Second, I believe that representing values less than 100ns or greater than 10000 years is not useful in general applications. I don't believe Tango is usable in a special-purpose real-time or scientific application. Such applications would use timing mechanisms that either had specific OS support or use more accurate representations than floating point variables.

Replying to kris:

Thus I'm hesitant, for example, to declare an interface parameter to be a TimeSpan?? (thus importing a concrete implementation into an interface). The contract and implementation would obviously not be distinct anymore :)

TimeSpan? would be a basic value type. It would be no different than Interval or the Time enumeration. There is no implementation being passed around with it, because it is a struct. The implementation exists outside the instance. This is no different than passing a Time value in, and using Time.TicksPerSecond? to convert to seconds.

11/14/07 12:21:46 changed by kris

so, use a double or real instead. No big deal. In truth, a double can exactly represent every whole number covered by an int, and 6 orders of magnitude more. Real obviously supports a greater range. So the point about 15000 years is a bit moot?

Also, a struct is implementation, which is quite different that using an int for example. I have some discomfort with that, as I said, regardless of your viewpoint :p

But these are perhaps trifling issues, with distinction based upon the perception of the viewer. A real problem is this: it's a breaking change. That doesn't mean Tango can't be changed, but it does mean there needs to be a really good reason for it.

11/14/07 13:36:41 changed by schveiguy

You are misunderstanding my point. double and real certainly can represent a larger range of floating point values than float. None of the floating point types can ACCURATELY represent non-integer values. This is the problem with the current Interval alias. If I want to sleep for a sub-second time, the translation between real/double/float and timeval or whatever OS structure LOSES the accuracy. float, double, and real cannot accurately represent .1, .01, .001, etc. This is a fact of floating point math. And the point about 15000 years was my assumption of YOUR argument to use floating point values, not mine :)

A struct is not an implementation. It is a POD value, which in the case of TimeSpan? contains a single value, which is equivalent to a long with a set of global functions to manipulate it. I don't quite understand your objection to having functions that are able to manipulate a type that is passed to an interface, be they global or part of a structure/class. Isn't that the point of a typed language? Isn't that the point of declaring that an argument to an interface is a type, and not just object?

You are correct on the breaking change problem. I believe that it is worth it for accuracy, performance, and reducing confusion. I believe that the damage can be minimized by leaving Time intact, and deprecating any usage of it. If a new user wants to use Tango, and wants to figure out how to get/manipulate time values, there are 3 choices, 1 (Interval) which is incompatible with the other 2, and 2 that are represented identically (Time and DateTime?) but have disjoint functionality. This is the delimma I had which caused me to propose this change. I believe there are no real drawbacks to making the change besides the slight headaches it will cause users of the current tango.

(follow-up: ↓ 10 ) 11/14/07 17:12:57 changed by kris

  • status changed from new to assigned.

That assumption was incorrect :)

I have a concern with pure interfaces importing a concrete implementation, and I have a concern over breaking changes. Yeah, I certainly agree it would be nice to potentially simplify some things in this arena. But the trade-offs are not that attractive. We have different perspectives on much of this, so we should just accept that and move on.

:p

(in reply to: ↑ 9 ) 11/14/07 17:47:26 changed by schveiguy

Replying to kris:

That assumption was incorrect :)

Oops, I think I misspoke in that very first rebuttal :) It should have read:

"And the point of NOT having a fixed timing interval is:"

I think this was the source of the confusion...

11/20/07 17:56:58 changed by schveiguy

  • status changed from assigned to closed.
  • resolution set to fixed.

(In [2900]) Updated to use new TimeSpan? struct and DateTime? for all date/time related functi ons. THIS IS A BREAKING CHANGE.

closes #671

11/23/07 20:47:34 changed by kris

  • status changed from closed to reopened.
  • resolution deleted.

The change in [2900] added DateTime to a number of interface modules. DateTime itself imports others. This means that what used to be purely interface decls only, now contains a slew of implementation.

This is really not acceptable for Tango, and needs to be rectified before the next release. TimeSpan does something similar, but at least does not import anything else. Clock and WallClock can be removed from DateTime, but that still leaves TimeSpan being imported. This concern is exactly why Time was used in the manner that it was before ... all it takes is one small mistake with toString, and half the formatting code will be dragging into these interfaces also.

Basically, interfaces are interfaces only in Tango. Not a mix of contract and imlementation. Let's get this sorted out as quickly as we can

11/23/07 20:47:54 changed by kris

  • owner changed from kris to schveiguy.
  • status changed from reopened to new.

11/25/07 16:14:36 changed by kris

after much wrangling, I moved TimeSpan? back to tango.util.time with the related Time modules. Thread.d was the only thing causing this break in "obviousness", so I decoupled it, and injected a yield(ulong nano) in there instead.

11/26/07 13:30:26 changed by schveiguy

  • status changed from new to assigned.

Current thinking is to eliminate all Calendar oriented processing from DateTime? as that functionality is already in the chrono package. DateTime? will simply be a linear value, and provide no interpretation based on a particular calendar.

This will result in very simple value types that do not imply any implementation except how DateTime? and TimeSpan? values relate to each other.

Since TimeSpan? no longer needs to be in tango/core, I will combine DateTime?.d and TimeSpan?.d into Time.d.

11/26/07 15:38:37 changed by larsivi

[21:22] <larsivi> schveiguy, Deewiant : I want no more haphazard changes to the time types until it is known what we want
[21:23] <larsivi> create a page in the wiki where requirements to support ISO, UTC, whatever is detailed
[21:23] <larsivi> explain why the current situation isn't good enough for it
[21:24] <larsivi> explain why other things should be done (like decoupling DateTime and Calendar)
[21:24] <larsivi> make careful note of how such changes will affect the locale package

11/27/07 17:13:24 changed by larsivi

Use TimePackageDesign to flesh out all ideas, thoughts, reasonings, technical merits, use cases, whatnots, before doing any further changes to the Time package.

12/02/07 17:37:42 changed by kris

  • priority changed from normal to blocker.

12/10/07 11:48:28 changed by schveiguy

  • status changed from assigned to closed.
  • resolution set to fixed.

After much deliberation, I think we have a workable solution! The result is checked in to tango.time.