Jason Axelrod
Jason Axelrod

Reputation: 7805

Quirk with PHP DateTime ISO weeks

I have 3 values:

$timezone = new \DateTimeZone('America/New_York');
$year = 2019;
$week = 52;

I then take these 3 values and run it through a script:

$nowTime = new \DateTime('now', $timezone);
$currTime = clone $nowTime;
$currTime->setISODate($year, $week, 1);
$currTime->setTime(0,0,0);

As you can see, I am setting the current time to be the beginning of Week 52 in 2019.

I am then trying to get information about the next week.

$nextTime = clone $currTime;
$nextTime->modify('+1 week');

$nextWeek = [
    'year' => $nextTime->format('Y'),
    'week' => $nextTime->format('W'),
];

This script has worked in almost every instance I have found...

Hopever, in Week 52 in 2019, instead of returning the next week as Week 1 in 2020, it returns the next week as Week 1 in 2019... which sends me backwards in time.

How do I fix this? This seems to happen in every year where there are 53 weeks in the year.

Upvotes: 1

Views: 67

Answers (2)

Axalix
Axalix

Reputation: 2871

There are 2 different ways how weeks can be handled, depending on the task.

1. ISO format:

Weeks start with Monday. Each week's year is the Gregorian year in which the Thursday falls. The first week of the year, hence, always contains 4 January. ISO week year numbering therefore slightly deviates from the Gregorian for some days close to 1 January.

These rules may be hard to handle manually, that's why it's a part of standard PHP, so you can just use 'week' => $nextTime->format('W')

It is indeed possible that a day in 2019 will belong to a week from 2020. And it's easy to prove: when you open a calendar for 2019 / December, you may notice that last week "may share" days from next year. To prevent a problem with overlapping, when the same week belongs to different years but its number is different, e.g. week 52 in a year 2019 is the same as week 1 in a year 2020, they decided to make a set of rules: ISO. These rules are especially useful in accounting to prevent "ambiguous weeks".

2. You only need a "mathematical" week number as a simple counter:

$nextWeek = [
    'year' => $nextTime->format('Y'),
    'week' => (int)(($nextTime->format('z')) / 7) + 1
];

z formatter is The day of the year (starting from 0) and that's why I am adding "+1" at the end.

Even though it may look as a good approach, it has a drawback: the week #52 in 2019 is the same as week #1 in 2020.

Upvotes: 0

iainn
iainn

Reputation: 17426

You're combining two date formats (Y and W) that don't make sense together. W is the ISO week number, but Y is the calendar year.

The first ISO week of 2020 starts on December 30, 2019, so for that date, W returns 1, but Y still refers to the calendar year 2019.

PHP offers the o date modifier that can be used in place of Y in your code, defined in the manual as:

ISO-8601 week-numbering year. This has the same value as Y, except that if the ISO week number (W) belongs to the previous or next year, that year is used instead. (added in PHP 5.1.0)

So you can change your code to

$nextWeek = [
    'year' => $nextTime->format('o'),
    'week' => $nextTime->format('W'),
];

and it should work as intended.

Upvotes: 1

Related Questions