chansen / p5-time-moment

Time::Moment represents an exact moment in time.
32 stars 8 forks source link

Weirdness in week calculations. #21

Open kentfredric opened 8 years ago

kentfredric commented 8 years ago

Seems 2016 and 2017 have some issues.

The first week of 2016 really shouldn't be in Jan 2015 ... The first week of 2017 really shouldn't be in Jan 2016 ... esp not around Jan 10 ...

perl -MTime::Moment -E' say Time::Moment->new( year => 2014 )->with_week(1) '
#2014-01-01T00:00:00Z
perl -MTime::Moment -E' say Time::Moment->new( year => 2015 )->with_week(1) '
#2015-01-01T00:00:00Z
perl -MTime::Moment -E' say Time::Moment->new( year => 2016 )->with_week(1) '
#2015-01-02T00:00:00Z
perl -MTime::Moment -E' say Time::Moment->new( year => 2017 )->with_week(1) '
#2016-01-10T00:00:00Z

Seems off by a year for the whole year

perl -MTime::Moment -E' say Time::Moment->new( year => 2016 )->with_week(25) '
#2015-06-19T00:00:00Z
perl -MTime::Moment -E' say Time::Moment->new( year => 2017 )->with_week(25) '
#2016-06-26T00:00:00Z
perl -MTime::Moment -E' say Time::Moment->new( year => 2018 )->with_week(25) '
#2018-06-18T00:00:00Z

Also disagrees with Date::ISO8601

perl -MDate::ISO8601 -E' say sprintf q[%s-%s-%s],  Date::ISO8601::cjdn_to_ymd( Date::ISO8601::ywd_to_cjdn( 2014, 1, 1 ) )'
#2013-12-30
perl -MDate::ISO8601 -E' say sprintf q[%s-%s-%s],  Date::ISO8601::cjdn_to_ymd( Date::ISO8601::ywd_to_cjdn( 2015, 1, 1 ) )'
#2014-12-29
perl -MDate::ISO8601 -E' say sprintf q[%s-%s-%s],  Date::ISO8601::cjdn_to_ymd( Date::ISO8601::ywd_to_cjdn( 2016, 1, 1 ) )'
#2016-1-4
perl -MDate::ISO8601 -E' say sprintf q[%s-%s-%s],  Date::ISO8601::cjdn_to_ymd( Date::ISO8601::ywd_to_cjdn( 2017, 1, 1 ) )'
#2017-1-2
perl -MDate::ISO8601 -E' say sprintf q[%s-%s-%s],  Date::ISO8601::cjdn_to_ymd( Date::ISO8601::ywd_to_cjdn( 2018, 1, 1 ) )'
#2018-1-1
chansen commented 8 years ago

The ->with_week() method alters the week of the week based week year, not the calendar year. The week based year can deviate from the calendar year on the first and last week of the week based year:

$ perl -MTime::Moment -wE 'do {
    say Time::Moment->new(year => $_)
                    ->strftime("%Y-%m-%d | %G-W%V-%u") 
} for (2014..2018)'
2014-01-01 | 2014-W01-3
2015-01-01 | 2015-W01-4
2016-01-01 | 2015-W53-5
2017-01-01 | 2016-W52-7
2018-01-01 | 2018-W01-1

Please note that January 4th is always in the first week and the week based year does not deviate from the calendar year:

$ perl -MTime::Moment -wE 'do {
    say Time::Moment->new(year => $_)
                    ->with_day_of_year(4)
                    ->strftime("%Y-%m-%d | %G-W%V-%u")
} for (2014..2018)'
2014-01-04 | 2014-W01-6
2015-01-04 | 2015-W01-7
2016-01-04 | 2016-W01-1
2017-01-04 | 2017-W01-3
2018-01-04 | 2018-W01-4

perl -MTime::Moment -wE 'do {
    say Time::Moment->new(year => $_)
                    ->with_day_of_year(4)
                    ->with_week(25)
                    ->strftime("%Y-%m-%d | %G-W%V-%u")
} for (2014..2018)'
2014-06-21 | 2014-W25-6
2015-06-21 | 2015-W25-7
2016-06-20 | 2016-W25-1
2017-06-21 | 2017-W25-3
2018-06-21 | 2018-W25-4

Time::Moment is in agreement with Date::ISO8601:

$ perl -MTime::Moment -wE 'do {
    say Time::Moment->from_string("${_}-W01-1T00Z")
                    ->strftime("%Y-%m-%d | %G-W%V-%u")
} for (2014..2018)'
2013-12-30 | 2014-W01-1
2014-12-29 | 2015-W01-1
2016-01-04 | 2016-W01-1
2017-01-02 | 2017-W01-1
2018-01-01 | 2018-W01-1

The Wikipedia article ISO week date goes into further details.

kentfredric commented 8 years ago

I am aware the first week of the year can occur in a different calendar year under ISO .

The confusion pertains to week turning up a whole 50+ weeks earlier than expected in 2016 and 2017

I'm guessing this is of course dependent on the fact ->new( year => y ) implying:

year => y, month => 1, day => 1 , and that by proxy having a week start in y - 1.

But I'm still not sure where its going wrong.

It would probably be more obvious to allow Time::Moment->new() to receive a week value, which in combination with only year and day ( maybe ), or some other simple syntax for defining a Time based on a week number.

chansen commented 8 years ago

The reason it's 50+ weeks earlier in 2016 and 2017 is because the calendar date is the last week of the week based year (of the previous calendar year):

2016-01-01 | 2015-W53-5 2017-01-01 | 2016-W52-7

To construct an instance of Time::Moment set to Monday of the first week of the given week based year:

perl -MTime::Moment -wE 'do {
    say Time::Moment->new(year => $_, day => 4)
                    ->with_day_of_week(1)
                    ->strftime("%Y-%m-%d | %G-W%V-%u")
} for (2014..2018)'

2013-12-30 | 2014-W01-1
2014-12-29 | 2015-W01-1
2016-01-04 | 2016-W01-1
2017-01-02 | 2017-W01-1
2018-01-01 | 2018-W01-1

To construct an instance of Time::Moment set to Monday of the 25th week of the given week based year:

perl -MTime::Moment -wE 'do {
    say Time::Moment->new(year => $_, day => 4)
                    ->with_day_of_week(1)
                    ->with_week(25)
                    ->strftime("%Y-%m-%d | %G-W%V-%u")
} for (2014..2018)'

2014-06-16 | 2014-W25-1
2015-06-15 | 2015-W25-1
2016-06-20 | 2016-W25-1
2017-06-19 | 2017-W25-1
2018-06-18 | 2018-W25-1