Thread overview | ||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
March 29, 2013 Rant: Date and Time fall short of simplicity in D | ||||
---|---|---|---|---|
| ||||
import core.thread; import core.time; import std.datetime; import std.stdio; void main() { StopWatch sw; sw.start(); Thread.sleep(dur!"seconds"(1)); Thread.sleep(dur!"msecs"(200)); Thread.sleep(dur!"usecs"(800)); sw.stop(); TickDuration time = sw.peek(); long secs = time.seconds; long msecs = time.msecs - (1000 * secs); long usecs = time.usecs - (1000 * msecs) - (1000 * 1000 * secs); writefln("secs: %s, msecs: %s, usecs: %s", secs, msecs, usecs); } The above works, but it's really messy having to do this all by hand. So the other workaround is: TickDuration time = sw.peek(); long secs = time.seconds; time -= cast(TickDuration)dur!"seconds"(secs); long msecs = time.msecs; time -= cast(TickDuration)dur!"msecs"(msecs); long usecs = time.usecs; Note that the casts are necessary since there is no "tickdur!()" function and a TickDuration cannot be subtracted with a Duration (I don't understand why it's implemented like this). The whole date and time API is extremely non orthogonal and ugly in D. For example, Duration has seconds, msecs, usecs as property functions but they are all calculated as the duration minus the larger units, so 1.200 seconds means that time.seconds == 1 && time.msecs == 200. But TickDuration uses a different API where time.seconds == 1 && time.msecs == 1200. Why was this inconsistency introduced? -- The documentation for various property functions was clearly copy-pasted, take a look for example at these: /++ The value of this $(D FracSec) as milliseconds. +/ @property int msecs() @safe const pure nothrow; /++ The value of this $(D FracSec) as milliseconds. Params: milliseconds = The number of milliseconds passed the second. Throws: $(D TimeException) if the given value is not less than $(D 1) second and greater than a $(D -1) seconds. +/ @property void msecs(int milliseconds) @safe pure; The second property function is a setter, it should be documented as: "Sets the value of this $(D FracSec) to $(B milliseconds) number of milliseconds." I've seen this type of property documentation copied everywhere in date/time-related structures and classes, it's not clear from the documentation that these are setters, they should be documented as such. --- Another example, I once had to convert a long type which represented Unix time into DateTime. Here's the code to do it: return cast(DateTime)SysTime(unixTimeToStdTime(cast(int)d.when.time)); It's unbelievable how ugly this is, and it took me way over half an hour searching the docs and trying various stuff out to figure this out. --- Anyway, maybe time and datetime are power-houses in Druntime and Phobos, but they trade their features for simplicity. Perhaps the real problem is the documentation, or the actual layout of the API itself. The API seems to contain a ton of functionality, and maybe the more specialized functions should be moved into separate modules (and make datetime be part of its own package). The docs for std.datetime for example are huge. |
March 29, 2013 Re: Rant: Date and Time fall short of simplicity in D | ||||
---|---|---|---|---|
| ||||
Posted in reply to Andrej Mitrovic | There are many many time formats. Do you want them all readily supported? |
March 29, 2013 Re: Rant: Date and Time fall short of simplicity in D | ||||
---|---|---|---|---|
| ||||
Posted in reply to Andrej Mitrovic | On Fri, 29 Mar 2013 01:42:41 -0400, Andrej Mitrovic <andrej.mitrovich@gmail.com> wrote: > import core.thread; > import core.time; > import std.datetime; > import std.stdio; > > void main() > { > StopWatch sw; > sw.start(); > Thread.sleep(dur!"seconds"(1)); > Thread.sleep(dur!"msecs"(200)); > Thread.sleep(dur!"usecs"(800)); Thread.sleep(1.seconds + 200.msecs + 800.usecs); > sw.stop(); > > TickDuration time = sw.peek(); > > long secs = time.seconds; > long msecs = time.msecs - (1000 * secs); > long usecs = time.usecs - (1000 * msecs) - (1000 * 1000 * secs); > > writefln("secs: %s, msecs: %s, usecs: %s", secs, msecs, usecs); > } This one is VERY annoying, Duration has some of the necessary properties. The sub-second values you must obtain with FracSecs, and once you get there, it does NOT give you properties which remove the higher units. So I can't really do this any better than you did. core.time needs to be fixed. Duration is really what you should use when doing duration math. I don't know why TickDuration still exists actually. > Why was this inconsistency introduced? Well, Duration was the basic type for general date time stuff that Jonathan came up with. TickDuration was created for StopWatch, which was developed by Kato Shoichi. IMO, they should be combined. > Another example, I once had to convert a long type which represented > Unix time into DateTime. Here's the code to do it: > > return cast(DateTime)SysTime(unixTimeToStdTime(cast(int)d.when.time)); I have three comments here: 1. unixTimeToStdTime should take ulong. 2. There should be a shortcut for this. Note on Windows, given a SYSTEMTIME we can do: return cast(DateTime)SYSTEMTIMEToSysTime(t); We need an equivalent unixTimeToSysTime, and in fact, I think we can get rid of unixTimeToStdTime, what is the point of that? 3. I HATE "safe" cast conversions. If you want to make a conversion, use a method/property. I don't even know why D allows overloading casting. Casts are way too blunt for this. The code should be: return unixTimeToSysTime(d.when.time).asDateTime; > Anyway, maybe time and datetime are power-houses in Druntime and > Phobos, but they trade their features for simplicity. > > Perhaps the real problem is the documentation, or the actual layout of > the API itself. The API seems to contain a ton of functionality, and > maybe the more specialized functions should be moved into separate > modules (and make datetime be part of its own package). > > The docs for std.datetime for example are huge. This is an issue with the doc generator. Date and time are surprisingly complex features, you need a lot of power to do them properly. The doc generator should split up the docs into docs for each type. For a better experience, see here: http://vibed.org/temp/d-programming-language.org/phobos/std/datetime.html -Steve |
March 29, 2013 Re: Rant: Date and Time fall short of simplicity in D | ||||
---|---|---|---|---|
| ||||
Posted in reply to Steven Schveighoffer | On 03/29/2013 03:03 PM, Steven Schveighoffer wrote:
> ...
>
> 3. I HATE "safe" cast conversions. If you want to make a conversion,
> use a method/property. I don't even know why D allows overloading
> casting. Casts are way too blunt for this.
> ...
The only conceivable reason is opCast!bool, eg. for use in if conditions.
|
March 29, 2013 Re: Rant: Date and Time fall short of simplicity in D | ||||
---|---|---|---|---|
| ||||
Posted in reply to Timon Gehr | On 03/29/2013 03:20 PM, Timon Gehr wrote:
> On 03/29/2013 03:03 PM, Steven Schveighoffer wrote:
>> ...
>>
>> 3. I HATE "safe" cast conversions. If you want to make a conversion,
>> use a method/property. I don't even know why D allows overloading
>> casting. Casts are way too blunt for this.
>> ...
>
> The only conceivable reason is opCast!bool, eg. for use in if conditions.
(In case this is not clear, a conversion method cannot really do the job satisfactorily because of the if(auto x = foo()) construct.)
|
March 29, 2013 Re: Rant: Date and Time fall short of simplicity in D | ||||
---|---|---|---|---|
| ||||
Posted in reply to Timon Gehr | On Fri, 29 Mar 2013 10:22:43 -0400, Timon Gehr <timon.gehr@gmx.ch> wrote:
> On 03/29/2013 03:20 PM, Timon Gehr wrote:
>> On 03/29/2013 03:03 PM, Steven Schveighoffer wrote:
>>> ...
>>>
>>> 3. I HATE "safe" cast conversions. If you want to make a conversion,
>>> use a method/property. I don't even know why D allows overloading
>>> casting. Casts are way too blunt for this.
>>> ...
>>
>> The only conceivable reason is opCast!bool, eg. for use in if conditions.
>
> (In case this is not clear, a conversion method cannot really do the job satisfactorily because of the if(auto x = foo()) construct.)
That one I don't mind, because it is an implicit cast. I think that feature actually would be better served by opBool or opTest or something like that.
It's the explicit casts which are horrible. Cast makes type-checking go away to some degree, it's like requiring a sledge hammer to put in finish nails. Plus the syntax is awkward.
-Steve
|
March 29, 2013 Re: Rant: Date and Time fall short of simplicity in D | ||||
---|---|---|---|---|
| ||||
Posted in reply to Steven Schveighoffer | On Friday, March 29, 2013 10:03:31 Steven Schveighoffer wrote: > On Fri, 29 Mar 2013 01:42:41 -0400, Andrej Mitrovic > > sw.stop(); > > > > TickDuration time = sw.peek(); > > > > long secs = time.seconds; > > long msecs = time.msecs - (1000 * secs); > > long usecs = time.usecs - (1000 * msecs) - (1000 * 1000 * secs); > > > > writefln("secs: %s, msecs: %s, usecs: %s", secs, msecs, usecs); > > > > } > > This one is VERY annoying, Duration has some of the necessary properties. The sub-second values you must obtain with FracSecs, and once you get there, it does NOT give you properties which remove the higher units. > > So I can't really do this any better than you did. Clearly, I need to take another look at FracSec then. > core.time needs to be fixed. Duration is really what you should use when doing duration math. I don't know why TickDuration still exists actually. I tend to agree. We _do_ need a separate time type which is in system ticks for the monotonic clock, but the stopwatch stuff doesn't need to use it. I'd fix it, but I don't know if we can do so without breaking code. I'll have to think about it. > 3. I HATE "safe" cast conversions. If you want to make a conversion, use a method/property. I don't even know why D allows overloading casting. Casts are way too blunt for this. It's how you define coversions to work with std.conv.to, so you could do to!DateTime(sysTime), which is shorter and avoids the explicit cast in your code (though the cast still occurs within std.conv.to). - Jonathan M Davis |
March 29, 2013 Re: Rant: Date and Time fall short of simplicity in D | ||||
---|---|---|---|---|
| ||||
Posted in reply to Jonathan M Davis | On Fri, 29 Mar 2013 15:22:19 -0400, Jonathan M Davis <jmdavisProg@gmx.com> wrote: > On Friday, March 29, 2013 10:03:31 Steven Schveighoffer wrote: >> 3. I HATE "safe" cast conversions. If you want to make a conversion, use >> a method/property. I don't even know why D allows overloading casting. >> Casts are way too blunt for this. > > It's how you define coversions to work with std.conv.to, so you could do > to!DateTime(sysTime), which is shorter and avoids the explicit cast in your > code (though the cast still occurs within std.conv.to). This seems like a horrible round-about way of creating a method: 1. Define opCast!OtherType 2. import std.conv 3. instead of using obj.toOtherType, use to!OtherType(obj) Do we really need to go through this mess? I understand the point is for 'to' to be usable in generic code, but can't to be made to call the correct method instead of using cast? I don't really like to using cast in this case anyway. What about something like (omitting necessary constraints in the interest of brevity): T to(T, U)(U u) { return mixin!("u.to" ~ T.stringof); } Or at least just alias the opCast to the method. -Steve |
March 29, 2013 Re: Rant: Date and Time fall short of simplicity in D | ||||
---|---|---|---|---|
| ||||
Posted in reply to Steven Schveighoffer | On Friday, March 29, 2013 15:33:35 Steven Schveighoffer wrote:
> On Fri, 29 Mar 2013 15:22:19 -0400, Jonathan M Davis <jmdavisProg@gmx.com>
>
> wrote:
> > On Friday, March 29, 2013 10:03:31 Steven Schveighoffer wrote:
> >> 3. I HATE "safe" cast conversions. If you want to make a conversion,
> >> use
> >> a method/property. I don't even know why D allows overloading casting.
> >> Casts are way too blunt for this.
> >
> > It's how you define coversions to work with std.conv.to, so you could do
> > to!DateTime(sysTime), which is shorter and avoids the explicit cast in
> > your
> > code (though the cast still occurs within std.conv.to).
>
> This seems like a horrible round-about way of creating a method:
>
> 1. Define opCast!OtherType
> 2. import std.conv
> 3. instead of using obj.toOtherType, use to!OtherType(obj)
>
> Do we really need to go through this mess? I understand the point is for 'to' to be usable in generic code, but can't to be made to call the correct method instead of using cast? I don't really like to using cast in this case anyway.
>
> What about something like (omitting necessary constraints in the interest
> of brevity):
>
> T to(T, U)(U u)
> {
> return mixin!("u.to" ~ T.stringof);
> }
>
> Or at least just alias the opCast to the method.
It used to be that std.conv.to used to on the type, but it was decided to get rid of that in favor of using opCast. I don't remember all of the reasons for it though.
But std.conv.to is the standard way to convert things, and I don't see how changing how std.conv.to determines how to do the conversion would help us any. Whether there was a to function on the type or opCast really makes no difference if you're using std.conv.to, and if you're not, then the way that the language provides to covert types - casting - works.
Unless you're arguing for using something other than std.conv.to to convert types, I really don't see the problem, and arguably, because std.conv.to is really the standard way to convert stuff, it's what should be used. So, I could see a definite argument for using std.conv.to in code rather than opCast, but I don't see much point in avoiding defining opCast on types, especially if code is then generally using std.conv.to rather than casting directly.
- Jonathan M Davis
|
March 29, 2013 Re: Rant: Date and Time fall short of simplicity in D | ||||
---|---|---|---|---|
| ||||
Posted in reply to Jonathan M Davis | On Fri, 29 Mar 2013 17:17:58 -0400, Jonathan M Davis <jmdavisProg@gmx.com> wrote:
> But std.conv.to is the standard way to convert things, and I don't see how
> changing how std.conv.to determines how to do the conversion would help us
> any. Whether there was a to function on the type or opCast really makes no
> difference if you're using std.conv.to, and if you're not, then the way that
> the language provides to covert types - casting - works.
>
> Unless you're arguing for using something other than std.conv.to to convert
> types, I really don't see the problem, and arguably, because std.conv.to is
> really the standard way to convert stuff, it's what should be used. So, I could
> see a definite argument for using std.conv.to in code rather than opCast, but I
> don't see much point in avoiding defining opCast on types, especially if code
> is then generally using std.conv.to rather than casting directly.
When I say "cast(Duration)time is ugly and dangerous" you say, "use std.conv.to instead." Why?
It seems you are using std.conv.to as part of the API of core.time types. I can't really understand the point of this. There exists a safe and necessary conversion (since both provide different features) from a TickDuration to a Duration. Why would that be an obscure part of the API? Why would the preferable interface be to use a cast? Why does std.conv.to have to be involved to get something readable that doesn't contain the red-flag cast operator? Both TickDuration and Duration know about each other, there is no reason to make this a dangerous operation (and yes, casts are dangerous and should be avoided).
It looks to me like the only reason a cast was chosen over a property/method is *so* it will work with std.conv.to. I contend that it would be better of std.conv.to was not able to convert these types than to have to use cast on it to get this behavior.
If std.conv.to cannot work on type-defined conversions without opCast, then it is poorly implemented. There needs to be a better mechanism.
-Steve
|
Copyright © 1999-2021 by the D Language Foundation