datetime review part 2 [Update 4]

Jonathan M Davis jmdavisProg at gmx.com
Sun Nov 14 03:21:23 PST 2010


On Sunday 14 November 2010 02:31:00 Dmitry Olshansky wrote:
> On 14.11.2010 7:47, Jonathan M Davis wrote:
> So bi-directionality is one of places where generator-on-previous-date
> approach is failing.
> I see that with current API the user need to be aware which delegate use
> when:
> auto func = IRange.everyDayOfWeek!(Date,
> Direction.bwd)(DayOfWeek.friday);//note the Direction.bwd bit
> auto range = interval.bwdRange(func);//also note the bwd thingy
> I get it would just throw exception if  bwdRange got replaced by
> fwdRange? It's acceptable, but still not clean.

It'll throw if you use Direction.bwd with interval.fwdRange() or Direction.fwd 
with interval.bwdRange(). And using the filter approach wouldn't really fix the 
bi-directional problem anyway, since you'd have to iterate over the entire range 
in order to determine what the time points at the end of the range are. 
Backwards ranges only work because they calculate from the end point rather than 
the begin point. They aren't going to necessarily result in the same time points 
as iterating forwards would. Even if you figured out how to deal with the day 
overflow issues (covered below) and state while using filter, you'd have the exact 
same problem where you'd have to iterate over _all_ of the time points to be 
able to know what the end was.

> >>> Are you trying to say that the range should automatically provide _all_
> >>> possible time points in an interval and then you specifically filter
> >>> them? That's nowhere near as flexible, and in the case of SysTime in
> >>> particular, think about how many time points that is. It has
> >>> hecto-nanosecond (100 ns) precision.  That's 10 million time points a
> >>> second. It could get really inefficient to try and filter all of those.
> >>> Also, I'm not sure that filter would work with an infinite range (I'd
> >>> have to check), which would be a huge setback. I really don't get the
> >>> benefit of your suggestion, though I can undkerstand it if it's not
> >>> entirely clear how ranges in std.datetime are supposed to work.
> >> 
> >> Not at all, you provide the precision that the user asks for. E.g. if
> >> users want tot iterate by days - no problem iterate by days.
> >> If by hnseconds, well he(or she) knows better ... Right?
> >> The point is we don't need some predefined delegates, just the natural
> >> ones e.g. by secs, hours and so on as with the roll/add methods.
> > 
> > That's doable, but it seems far less flexible.
> 
> OK, got it.
> Skimming again through docs I'd say we just see the API at different
> sides, i.e.
> I was mostly referring to IRange block, it's slightly cumbersome.
> 
> For now, I as user see it more as interval functions:
> Interval.byDur(Duration) + Interval.by!"days"(x)....
> Interval.by!"months"(x) + Interval.by"years"(x)
> 
> where only byDur, by"months" and by"years" are necessary the other are
> just a convenience.
> They could be implemented as one-liners in terms of yours IRange (I
> still don't get why such a name?).
> Then it would be just an implementation detail with IRange being private.
> 
> But then it would cost in some extra lines, plus as you told there
> should be fwd/back param somewhere.
> Then it goes like Interval.by!("days",Direction.Back) with default being
> Fwd of course...

I'm not sure that I get what you're trying to do here. You have an interval, and 
if you want to iterate it, you need a range over that interval. I take it that 
you're trying to have specific range functions on interval instead of using the 
more generic fwdRange() and bwdRange()? That's considerably less generic, and 
what happens if the interval that you're trying to iterate over doesn't have 
months in it (e.g. TimeOfDay)? It's certainly doable, but it's less generic. The 
interval types are intended to be generic and work over any type of time point. 
Also, they have no concept of precision across an interval, so whether you're 
dealing with time points months apart or hnsecs apart has nothing to do with 
interval as it stands. That's entirely up to the range. You could put that kind 
of information in the interval, but other than iterating over it (which is the 
range's job), that information is completely irrelevant. What I have is generic 
over any type of time point. You use fwdRange() or bwdRange() and give it the 
delegate to generate the range. IRange is there to provide you with delegates 
that you're likely to need without figuring out having to code them yourself.

IRange stands for IntervalRange, and it would be _useless_ if it were not a 
public API. The entire point of it is to save users from having to declare the 
delegates themselves. It gives basic delegates for some of the common situations 
(though it would probably be good to add a few more). It's not quite as clean as 
I'd like because it doesn't know whether you intend to use a delegate with 
fwdRange() or bwdRange(), so in the case of bwdRange(), you have to pass 
Direction.bwd to them, but it works, and I'd expect people to want to iterate 
forwards most of the time anyway.

The entire purpose of IRange is to save the user from having to figure out how to 
write some of the simple range delegates.

What you're suggesting could work, but it's less generic and far less flexible. 
What I have clearly separates the concept of an interval of time (which covers 
from one point of time to another without caring one whit about what's in 
between) and a range over that interval which you iterate over (which then has 
to care about which time points within the interval get iterated over).

> > For instance, how would you
> > calculate each successive Easter? Or a holiday which always occurred on
> > the Xth day of the week of a particular month? A number of calculations
> > could easily be based on the dates that came before rather than
> > examining each date on its own and determining whether it was one of the
> > ones that you were looking to iterate over.
> 
> Yeah, with that simplified approach using filtering by e.g. days could
> suck in term of speed, need checking.
> It's however possible, see my later point. If speed is of concern IMO I
> would just create a special
> range by hand, it's a standard library right?
> About speed:  because of flexibility yours bwdRange and fwdRange are
> checking on both bounds(if any)
>   at each iteration, for trivial tasks that's also costs.
> Maybe we need some realistic date-time benchmarks (if there is such a
> thing)?

It's the difference between checking the predicate against every time point in 
the range until you find one which matches it or calculating a new time point 
from the current one and verifying that the new one is greater than the old one 
(or less than if iterating backwards). My guess would be that my solution would 
be faster, since you would have far fewer comparisons, but it would likely 
depend on what set of time points you're trying to iterate over.

> As you said it's not a simple case, and how would you handle it with
> current API anyway? Custom generator-delegate for grunted.
> So custom in any way (range or delegate), and not everything we generate
> needs previous timepoint.

The add functions which deal with years and months take an enum value - 
AllowDayOverflow - which indicates whether days should be allowed to overflow into 
the next month or not. So, for instance, if adding one month to 2010-08-31 and 
AllowDayOverflow.yes is used, then the resulting date would be 2010-10-01, 
whereas if AllowDayOverflow.no is used, then the resulting date would be 
2010-09-30.

- Jonathan M Davis


More information about the Digitalmars-d mailing list