Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why doe `later` method lead to infinite range in `Date` range?

In Julia, you may generate a date range by month like this:

julia> dr = Date(2014,1,29):Dates.Month(1):Date(2014,07,29)
Date("2014-01-29"):Month(1):Date("2014-07-29")

julia> collect(dr)
7-element Array{Date,1}:
 2014-01-29
 2014-02-28
 2014-03-29
 2014-04-29
 2014-05-29
 2014-06-29
 2014-07-29

Date(2014,1,29) is the start date, Dates.Month(1) is the step, Date(2014,07,29) is the end date.

Raku has a method later, but when used in custom generator, it lead to infinite range:

lazy my @dates = Date.new('2014-01-29'), Date.new('2014-02-28'), { $^a.later(:1month) } ... Date.new('2014-07-29')

If I use * >= Date.new('2014-07-29') instead of Date.new('2014-07-29') on the right of ... operator, it works:

lazy my @a = Date.new('2014-01-29'), Date.new('2014-02-28'), { $^a.later(:1month) } ... * >= Date.new('2014-07-29')


2014-01-29
2014-02-28
2014-03-28
2014-04-28
2014-05-28
2014-06-28
2014-07-28
2014-08-28

why the custom generator { $^a.later(:1month) } in the { $^a.later(:1month) } ... Date.new('2014-07-29') doesn't stop at 2014-07-29 and lead to infinite range?

like image 306
chenyf Avatar asked Mar 02 '23 05:03

chenyf


2 Answers

As JJMerelo indicates in the comments, the way that the ... operator works is to continue generating elements based on the left-hand-arguments until the right-hand-argument is (per smartmatching) exactly reached.

For example, if we made a sequence of multiples of 10s,

my @tens = 0, 10, 20 ... 95;
say @tens[10]; # 100
say @tens[11]; # 110

This is because not element of @tens will actually be 95. To determine if an element is the final one, the smartmatch operator (~~) is used. Smartmatching a DateTime with another Datetime returns true if the two represent the same time (which may be different nominal times because of timezones, etc).

For sequences, DateTime is further complicated by the fact that .later and .earlier are not communative, so doing $date.later(:1month).later(:1month) is not guaranteed to give the same result as $date.later(:2month).

The reason that * ≥ DateTime.new(…) is different is that smartmatching for Callable objects (which that technically is, it's whatever code equivalent to anon sub $dt { $dt ≥ DateTime.new(…) } passes the left hand argument to the callable. If you're not 100% sure that a sequence will terminate by reaching an exact value, it's best to use the whatever code approach to ensure a value eventually matches.

like image 157
user0721090601 Avatar answered Mar 05 '23 16:03

user0721090601


Hmmm - for any kids out there trying this, there are some pitfalls to do with "what I mean"... for example when I try this

lazy my @b = Date.new('2014-01-31'), Date.new('2014-02-28'), { $^a.later(:1month) } ... * >= Date.new('2014-07-29')

I get this...

#(2014-01-31 2014-02-28 2014-03-28 ...)

But maybe I wanted the last day of each month, what if 2014 is a leap year, blah, blah

So if you want the last day (or last day -1), then there is also this handy method...

say Date.new('2015-11-24').last-date-in-month;
like image 35
p6steve Avatar answered Mar 05 '23 14:03

p6steve