Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support ranges #2

Closed
GoogleCodeExporter opened this issue Mar 15, 2015 · 23 comments
Closed

Support ranges #2

GoogleCodeExporter opened this issue Mar 15, 2015 · 23 comments
Milestone

Comments

@GoogleCodeExporter
Copy link

Several applications need the idea of a range - I suspect the most common 
case is local dates.

It would be nice if we could use:

    LocalDateRange range = new LocalDateRange(start, end);
    foreach (LocalDate date in range)
    {
    }

We can use the code in MiscUtil (http://pobox.com/~skeet/csharp/miscutil) as 
a starting point for this. We may want to consider whether to "go generic" or 
just include the ranges for the most common cases.

Original issue reported on code.google.com by jonathan.skeet on 4 Dec 2009 at 8:09

@GoogleCodeExporter
Copy link
Author

We would also need to consider whether LocalDateRange(start, end) is inclusive 
or
exclusive of end. normally ranges are better if they are exclusive but with 
dates I'm
not so sure.

Also do we want to be able to have different increments (by month, by year, 
etc.).

Original comment by james.keesey on 17 Feb 2010 at 6:26

@GoogleCodeExporter
Copy link
Author

Original comment by jonathan.skeet on 17 Mar 2012 at 9:21

  • Added labels: V1-OutOfScope

@GoogleCodeExporter
Copy link
Author

Original comment by malcolm.rowe on 13 Jul 2012 at 8:33

  • Added labels: Type-Enhancement

@GoogleCodeExporter
Copy link
Author

Original comment by malcolm.rowe on 30 Jul 2012 at 7:45

  • Added labels: Milestone-unscheduled
  • Removed labels: V1-OutOfScope

@GoogleCodeExporter
Copy link
Author

James raised two good issues: having different increments and 
inclusive/exclusive ending  points.  

We already have the Period type which could handle by month, year, etc.  If we 
added an Enumerate (don't like the name) method to it, we could have the 
required functionality and let the API user determine what is the ending point.

Assuming `IEnumerable<LocalDate> Enumerate(LocalDate start)`, then we could do 
things like:

- foreach (var date in period.Enumerate(start).TakeWhile(d => d < end)) { ... }
- foreach (var date in period.Enumerate(start).TakeWhile(d => d <= end)) { ... }
- foreach (var date in period.Enumerate(start).Take(n)) { ... }


Original comment by makaretu on 11 Nov 2012 at 4:26

@GoogleCodeExporter
Copy link
Author

Period is good for local types (LocalDate, LocalDateTime, LocalTime).

Duration would be good for Instants. I'm not sure about either OffsetDateTime 
or ZonedDateTime (in the same way as we don't have arithmetic for them).

I think I'd probably *prefer* to put the methods on the start, e.g.

    foreach (var date in start.StepBy(period).TakeWhile(d => d < end))

Oh, and we might want to consider a way of iterating over time zone transitions 
in the same way. I've often used that - but it may be more useful for Noda Time 
developers than line-of-business app developers...

Original comment by jonathan.skeet on 11 Nov 2012 at 4:44

@GoogleCodeExporter
Copy link
Author

StepBy generates a sequence of date/times by continually incrementing a start 
date/time with a step.
Stepping by zero raises a precondition exception. Underflow/Overflow terminates 
the sequence.

Added StepBy(Period) to LocalDate, LocalTime and LocalDateTime.
Added StepBy(Duration) to Instant.

OffsetDateTime and ZonedDateTime was not changed because we have not 
implemented arithmetic for the types.

First time I ever used the 'checked' keyword!

Original comment by makaretu on 12 Nov 2012 at 11:25

Attachments:

@GoogleCodeExporter
Copy link
Author

Is anyone considering pulling this change?

Original comment by makaretu on 2 Dec 2012 at 10:33

@GoogleCodeExporter
Copy link
Author

I don't know about anyone else, but I don't have the bandwidth right now. I'm 
hoping to get back to Noda Time in the new year... although I really want to 
start off by focusing on the PCL and CLDR work. I'll see if I can take a quick 
look at this anyway though...

Original comment by jonsk...@google.com on 3 Dec 2012 at 8:20

@GoogleCodeExporter
Copy link
Author

Some discussion here: 
http://stackoverflow.com/questions/20599006/how-do-i-accurately-represent-a-date
-range-in-nodatime

Original comment by malcolm.rowe on 16 Dec 2013 at 11:48

@GoogleCodeExporter
Copy link
Author

We probably want to do some sort of DateRange for 2.0 - amongst other benefits, 
it would cover issue 297.

Original comment by jonathan.skeet on 26 Jun 2014 at 5:33

  • Added labels: Milestone-2.0-consider
  • Removed labels: Milestone-unscheduled

@jskeet
Copy link
Member

jskeet commented Nov 24, 2016

This is implemented as DateInterval. Leaving this open to review the API.

@lundmikkel
Copy link

@jskeet Date intervals are normally closed intervals, i.e. both endpoints should be included. @mj1856 should be able to confirm.

@jskeet
Copy link
Member

jskeet commented Dec 21, 2016

@lundmikkel: I think it can be useful to have the ability to treat them as half-closed, e.g. to make it clear that a sequence of intervals is contiguous. (e.g. "1st of January (inclusive) to 1st February (exclusive), then 1st February (inclusive) to 1st March (exclusive)") The current DateInterval allows both... do you think that causes any problems? It does mean users need to be aware that it can be used both ways, admittedly.

@lundmikkel
Copy link

I find that people often get confused about interval endpoint inclusion. For certain interval types, only/mainly one type of endpoint inclusion makes sense. E.g. time intervals are half-open (as with Interval), date intervals are closed. That doesn't necessarily mean that that is the only way to consider them, but keeping to a "standard" would be nice.

I have previously done an extensive interval collection library, and we found that doing endpoint inclusion explicit was the best help for users. It also makes algorithms, e.g. for overlap or containment, easier since they can be considered more generally.

To get back to DateInterval, I think calling it Inclusive is asking for trouble. It isn't clear what kind of inclusion it is, unless you read the documentation, which states that it only is the last endpoint. My suggestion is to name it explicitly, e.g. 'EndInclusive', or only allow one type of interval, e.g. closed.

Another possible approach is the one we took, where the interval is generic and allows for all types of endpoint inclusion, but I haven't looked enough at DateInterval to say if that makes sense.

@jskeet
Copy link
Member

jskeet commented Jan 4, 2017

I would definitely be happy with EndInclusive instead of Inclusive. But I think there are sufficient situations where you do want an exclusive end point for a date range that it's worth keeping that available. (I don't think it would make sense for a regular Interval to be customizable like that though.)

@mattjohnsonpint
Copy link
Contributor

Well, perhaps I'm opinionated on this, but I really think 2017-01-01 - 2017-01-04 can only be interpreted as "four days", while 2017-01-01T00:00:00Z - 2017-01-04T00:00:00Z can only be interpreted as "72 hours" (or "3 standard days").

I can't really think of any exceptions to this, other than human confusion around whether or not the time is a part of the data in question - but we've eliminated that in NodaTime by having separate types for LocalDate and Instant (and others).

@jskeet
Copy link
Member

jskeet commented Jan 5, 2017

Well, I'd always express a half-open interval as [2017-01-01, 2017-01-04) to be clearer :)

The use case I find compelling is when you're thinking in terms of dates, but you want to have an abutting sequence of date intervals, e.g.

  • [2017-01-01, 2017-02-01)
  • [2017-02-01, 2017-03-01)
  • [2017-03-01, 2017-04-01)
  • [2017-04-01, 2017-05-01)

I find it easier to check that intervals are abutting by knowing that the end of one should be the same as the start of the other than to have to add a day. (It would be particularly easy to get leap years wrong here, for example.)

jskeet added a commit to jskeet/nodatime that referenced this issue Mar 5, 2017
This improves clarity - the property *could* still be removed
if nodatime#2 is resolved to make all date intervals inclusive.
jskeet added a commit to jskeet/nodatime that referenced this issue Mar 5, 2017
This improves clarity - the property *could* still be removed
if nodatime#2 is resolved to make all date intervals inclusive.
jskeet added a commit that referenced this issue Mar 5, 2017
This improves clarity - the property *could* still be removed
if #2 is resolved to make all date intervals inclusive.
@jskeet
Copy link
Member

jskeet commented Mar 5, 2017

I can't remember the discussion I had with @malcolmr about using different types for this, but possible compromise solution - 3 types:

  • IDateInterval interface, with the same public members as the current DateInterval
  • DateInterval class which is always end-inclusive
  • EndExclusiveDateInterval which is always end-exclusive

The current ContainedDatesDateIntervalEqualityComparer could implement IEqualityComparer<IDateInterval>; NormalizingDateIntervalEqualityComparer would only be relevant to EndExclusiveDateInterval as DateInterval could never be empty.

Basically, those who never need to deal with an end-exclusive date interval could just use DateInterval (with the bias towards that being shown in the name) but for the uses as in my earlier comment, we could use EndExclusiveDateInterval.

Thoughts?

@lundmikkel
Copy link

I would seriously only have a DateInterval with both endpoints included. I don't see any real benefits from allowing both endpoint inclusion types. Quite the contrary actually: I think it only confuses the user even more.

I not sure about your example, @jskeet. If you wanted to create a half-open interval, then a factory method could be the solution. If it was for readability, then maybe the ToString/DebuggerDisplay could print month names instead of the specific dates. For instance (for some locale):

  • [2017-01-01;2017-01-04] could print something like "January 1st, 2017 - January 4th, 2017"
  • [2017-01-01;2017-01-31] could print something like "January, 2017"
  • [2017-01-01;2017-12-31] could print something like "2017"

@jskeet
Copy link
Member

jskeet commented Mar 7, 2017

@lundmikkel: That would assume that every use case for an interval that happens to cover every day of a month is the same. Maybe I've got a set of intervals that is meant to be February 1st to February 28th each year, exactly 28 days... it would be odd for that to show differently in leap years to non-leap-years.

However, the fact that everyone else seems to think the use case I'm concerned with is irrelevant does make me want to give in. @malcolmr - apologies for the fact that we'll be wasting a lot of the comparer work you did, if we do indeed make these always-inclusive.

@malcolmr
Copy link
Contributor

malcolmr commented Mar 7, 2017

(as noted in #671, I'd be happier if the inclusivity of the range was a property of the type rather than the instance anyway: it seems less prone to mistakes.)

@jskeet
Copy link
Member

jskeet commented Mar 7, 2017

@malcolmr: Yup, hence my alternative proposal a few comments ago - but it sounds like Mikkel and Matt are pretty sure that the speculated EndInclusiveDateInterval type just shouldn't exist. I suppose we can always add that (and the interval) later...

jskeet added a commit to jskeet/nodatime that referenced this issue Mar 7, 2017
jskeet added a commit to jskeet/nodatime that referenced this issue Mar 7, 2017
jskeet added a commit that referenced this issue Mar 7, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants