Quirk with PHP DateTime ISO weeks - php

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.

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.

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.

Related

How do I find date ranges given a certain week number and year? [duplicate]

I'm trying to group together dates into a week number and year, and then I want to convert that week number back into a unix timestamp. How can I go about doing this?
I assume you are using ISO 8601 week numbers, and want the first day of a ISO 8601 week so that e.g. Week 1 of 2011 returns January 3 2011.
strtotime can do this out of the box using the {YYYY}W{WW} format:
echo date("Y-m-d", strtotime("2011W01")); // 2011-01-03
Note that the week number needs to be two digits.
Shamefully, DateTime::createFromFormat, the fancy new PHP 5 way of dealing with dates, seems unable to parse this kind of information - it doesn't have a "week" placeholder.
$week: The week number
$year: The year number
Then:
$timestamp = gmmktime (0, 0 , 0 , 1, , 4 + 7*($week - 1), $year);
The 4 + 7*($week - 1) comes from the fact that according to ISO 8601, the first week of the year is the one that contains January 4th.
strtotime('1/1/2011 + 4 weeks') (1/1 ist always in week number one; this would bring me to week number five). if you want any timestamp in the week then that's all you need, else you would have to go to the monday in this week:
$t = strtotime('1/1/2011 + 4 weeks');
$t -= 24 * 60 * 60 * date('w', $t);
Update: Instead of 1/1/2011 use the first monday in 2011. The 2nd calculation is not needed anymore.

How to get the starting weekday of a month in a future year using date() in php

I am using a date() format to return the starting weekday of a month. The code I have below is how I am attempting to achieve this. For the current year (2018) this works as normal. For example This month is august and the starting weekday is a Wednesday so it will return a 3 for Wednesday. (It works so far)
As we advance the year to 2019 it starts to get the starting weekday wrong.
For example January 2019 starts on a Tuesday so it should return 2 but returns 1. (one day out)
This error seems to be cumulative so if we go to 2020 then it is 2 days out etc.
I have tried so hard to format this Date() correctly but to no avail. Is this even the correct way to do this?
Code:
$future_month = 5 /*for January 2019*/
$starting_weekday = date('N',mktime(0, 0, 0, date('m', strtotime('+'.$future_month.' months', strtotime(date('Y-m-01')))), 1));
Many Thanks
Cameron
Your code makes this much more complicated than it needs to be.
$dt = new DateTime('first day of +5 months')
$dt->format('N'); // "2"

Logical Query to calculate weeks

I ask user to enter start and end date in a certain application. Then I calculate the no. of weeks in between these two dates.
I want to fetch documents created per week in the given period of time.
I am unable to implement a logic which will help me to fetch first week, second week, third week, and so on.
I will use this as input to database and then create graph. I am using php as the server side language. Anyone could suggest me the Algorithm.
ex:
Start date: 20/07/2012
End Date: 19/02/2013.
Total no. of days: 214.
Total no. of weeks: 30 (30 weeks and 4 days)
I want to fetch period of 20/07/2012 to 27/07/2012 and no. of Docs during this period. Till 15/02/2013.
Without seeing your database structure here is a sample of what you require
SELECT YEAR(doc_created) AS report_year, WEEKOFYEAR(doc_created) AS report_week, COUNT(*) AS doc_count
FROM documents
WHERE doc_created BETWEEN '2012-07-20' AND '2013-07-19'
GROUP BY YEAR(doc_created), WEEKOFYEAR(doc_created)
grouping by weekofyear is not enough, as period may overlap the next year, so we should also include the year.
possible results:
year week docs
2012 1 4
2012 3 1
2013 1 65
2013 2 4
as you can see 2012 week 1 would overlap 2013 week 1, if we just used week number.
for your grid/graph use the reference (year wk) and amount (docs)
e.g. 2012wk1 is 4, 2012wk3 is 1
Try this code:
$start_date = "20-07-2012";
$end_date = "20-08-2012 ";
while($start_date){
if(strtotime($start_date) > strtotime($end_date)){
exit;
}
$start_date = strtotime("+7 day", strtotime($start_date));
$start_date = date("d-m-Y", $start_date);
echo $start_date;
echo "<br/>";
}

php datetime bug?

I am having a problem with php's DateTime functions.
Today is monday 3 december.
Assuming the following code:
$dte = new DateTime(date('Y-m-d H:i:s'));
var_dump($dte->format('Y-W'));
$dte->modify('+4 weeks');
var_dump($dte->format('Y-m-d H:i:s -- Y_W'));
$dte->modify('+1 days');
var_dump($dte->format('Y-m-d H:i:s -- Y_W'));
After four weeks it would be 31st of december. I would suspect to get the last week of the year (52?). But what I get is week 1 of 2012 as you can see in the following output.
string '2012-49' (length=7)
string '2012-12-31 14:48:00 -- 2012_01' (length=30)
string '2013-01-01 14:48:00 -- 2013_01' (length=30)
So my problem is that after the first modification I think I should get:
2012-12-31 14:48:00 -- 2012_52
but instead I get
2012-12-31 14:48:00 -- 2012_01
So why does the week go back to 01 without incrementing the year, and than why does the other line gives me 2013_01 ?
EDIT::
I now see that the week before is week 52, anything to do with leap year?
But then again, how can the week go back to 01 without incrementing the year?
So why does the week go back to 01 without incrementing the year, and than why does the other line gives me 2013_01 ?
I think you're displaying the "year" instead of the "week-year". When you're using week numbers, it's the week-year that's the relevant part; simple "year" is only relevant with respect to month and day.
EDIT: I think you want the o format specifier instead, so try:
var_dump($dte->format('Y-m-d H:i:s -- o_W'));
That should show you 2013_01 for December 31st 2012, as it's in week 1 of week-year 2013.
So basically, I don't think this is a bug in DateTime - it's just a misunderstanding of how "week of year" is meant to be used.
This seems to be no bug. According to the documentation W will return ISO-8601 week number of year, weeks starting on Monday (added in PHP 4.1.0) and because Mon, 31 Dec 2012 15:04:46 +0100 is Monday it will be 1 instead of 52.
Further information on Wikipedia and this nice site.

PHP date format issue using strtotime()

I am using
$jsdate = date("Y, m, d", strtotime('-1 month', (strtotime($date))));
to convert my dates from
2011-03-28
to
2011, 02, 28
Problem is this is producing unpredictable results. For example today I got
2011-03-28
converted to
2011, 02, 28 // OK
AND
2011-03-29
to
2011, 03, 01 // not OK!
Does anyone know what's wrong here? I wonder if the calculation is inaccurate because of the -1 month.
Is there a way of simply subtracting 1 from m in ...date("Y, m, d", ...?
MORE INFO:
My data needs to be formatted as JavaScript Date Object in which January is 0, Feb is 1, etc. Therefore there is not a need to specifically subtract 1 month but actually subtract 1 from the month integer. At the end, the resulting string is not supposed to be 1 month earlier, but actually the same date, represented using JS Date Object style. I believe #vprimachenko's answer below is a good solution. I apologize if this wasn't clear in my OP.
Thanks!
you might use
$datee = explode('-',$date);
if($datee[1]-- < 0) {
$datee[1]=12;
$datee[0]--;
}
$jsdate = implode(', ',$datee);
The calculation isn't inaccurate, per se. There is no 2/29/2011. If you change your input to 3/29/2012, you'll see that it returns 2/29/2012, because 2012 is a leap year. The same would happen with using something like 7/31/2011. June only has 30 days, so July 31 minus one month would be July 1 (because June 31 doesn't exist).
You could just extract the month, subtract 1, and remake the date, but that will result in attempting to make dates that don't exist.
If you really need the corresponding day of the prior month, you'll probably need to do an if statement of something along the lines of the following to make the day roll back to the last day of February:
$jsdate = date("Y, m, d", strtotime('-1 month', (strtotime($date))));
if($month == '3') {
$jsdate = date("Y, m, d", strtotime('-1 day', (strtotime($jsdate))));
}
You'll also have to account for the rest of the days in March that February doesn't have, as well as leap years, and do something similar for 31-day months that follow 30-day months.
strtotime might work in an unexpected way but it is logical
strtotime('-1 months',strtotime('2011-03-29') // is 2011-02-29
date('Y-m-d','2011-02-29'); //gets converted to the next real date
Here is one kind of fix
http://www.phpreferencebook.com/tips/fixing-strtotime-1-month/

Categories