php date/week problem - php

I'm kind of at a loss here. It seems as though somehow my code is missing a whole week at the end of 2009 and I've tried a couple different things.
My base function to get the start and end date for a week is below. Given a Year, Week and Day of the Week it gives you a date.
function datefromweeknr($aYear, $aWeek, $aDay)
{
$Days=array('xx','ma','di','wo','do','vr','za','zo');
//xx = Current Sun, ma = Mon ..... zo = Sun of the next Week
$DayOfWeek=array_search($aDay,$Days); //get day of week (1=Monday)
$DayOfWeekRef = date("w", mktime (0,0,0,1,4,$aYear)); //get day of week of January 4 (always week 1)
if ($DayOfWeekRef==0){
$DayOfWeekRef=7;
}
$ResultDate=mktime(0,0,0,1,4,$aYear)+((($aWeek-1)*7+($DayOfWeek-$DayOfWeekRef))*86400);
return $ResultDate;
}
Seemed to work completely fine until I realized that I was missing the week of December 27th 2009 to January 2nd 2010.
echo '<table border="1">';
for($i = 1; $i < 53; $i++){
if($i < 10){
$w = '0'.$i.'1';
}
else{
$w = $i.'1';
}
echo '<tr><td>Week#'.$i.' </td><td> '.date("Y-m-d",datefromweeknr(2009,$i,"xx")).' </td><td> '.date("Y-m-d",datefromweeknr(2009, $i,"za")).'</td><td> Week = '.date("W: Y-m-d",strtotime("2009W$w")).' </td></tr>';
}
echo '</table>';
It seems the 52nd week of the year ends on 2009-12-26 and the 1st week of the new year starts on 2010-01-03. I'm losing a whole week, No Bueno!
Anyone know what I'm doing wrong or can point me to a fool proof way of supplying a week number and a year to get me the start and end date of that week without losing any days in the process?

Check here:
http://www.onlineconversion.com/day_week_number.htm
If you enter 29 december 2009, so see that US and ISO/Europe give different week numbers (resp. 52 and 53).
Could this be related to your problem? Which standard do you dates conform too?
Edit:
From http://www.epochconverter.com/epoch/weeknumbers.php :
Week number according to the ISO-8601 standard, weeks starting on Monday. The first week of the year is the week that contains that year's first Thursday. The highest week number in a year is either 52 or 53.

Your question remainded me of a bug comment I read today on php.net:
In PHP 5 prior to 5.2.7, requesting a
given occurrence of a given weekday in
a month where that weekday was the
first day of the month would
incorrectly add one week to the
returned timestamp. This has been
corrected in 5.2.7 and later versions.
Which is irrevelent for now but I would suggest you to replace your calculations in datefromweeknr with strtotime calls. I'm pretty sure strtotime will fix your calculation bug.
So you could use something like:
strtotime('last Monday', $timestamp);

Related

Carbon get month by number returns march for number 2

Has anyone ever had this bug before, or know a way to fix it?
I want to get a list of all months in the current year (start date and end date) in an array, so i'm doing like this (open to suggestions for cleaner easier ways)
//Create a months array
$months = [];
//Get start and end of all months
for($i = 1; $i <= 12; $i++){
$array = [];
$array['start'] = Carbon::create()->month($i)->startOfMonth()->format('d/m/y');
$array['end'] = Carbon::create()->month($i)->endOfMonth()->format('d/m/y');
array_push($months, $array);
}
Which produces this result
As you can see, its looped and retrieved the months, but notice that it skips February completely and adds March twice.
If I manually run and return this code
return Carbon::create()->month(2)->startOfMonth()->format('d/m/y');
It returns 01/03/2018.
Why does carbon print out March for month 2? Has anyone ever had this issue before or know of a way to fix it?
Carbon::create()->month(2) will first create today's date, and then set the month to 2, but keep the other values. Because today's date is the 29th of August, the date ends up referring to the 29th of February, which (this year at least) doesn't exist. PHP rolls these "fake" dates over into the next month, so February 29th becomes March 1st.
If you explicitly set the day of the month first, this should work as expected:
Carbon::create()->day(1)->month($i);
(Also, if you'd tried this yesterday it would have worked fine, and you might never have noticed the bug. If you tried it tomorrow, you would have ended up with March 2nd and probably noticed it a lot faster. Dates are great fun.)
Figured this out whilst commenting on a suggested answer.
Its because todays date is 29th August 2018, and there are not 29 days in February.
Basically Carbon is creating an instance from todays date with Carbon::create() but then when using ->month(2) will try to get the 29th Feb and error. It needs to work from the first of the month so change it to
Carbon::create()->startOfMonth()->month($i)->startOfMonth()->format('d/m/y');
So it references from the 1st of the month and it will work as expected.
Pure PHP is enough for this:
$datePeriod = new \DatePeriod(
new \DateTimeImmutable(date('01-01-Y')),
new \DateInterval('P1M'),
new \DateTimeImmutable(date('31-12-Y'))
);
$dates = [];
foreach ($datePeriod as $date) {
$dates[] = [
'start' => $date->modify('first day of this month')->format('d/m/y'),
'end' => $date->modify('last day of this month')->format('d/m/y'),
];
}
It's 0 based by the looks. so month(0) would be January, 1 = February, etc.
Change your loop to
for($i = 0; $i < 12; $i++){
$array = [];
$array['start'] = Carbon::create()->month($i)->startOfMonth()->format('d/m/y');
$array['end'] = Carbon::create()->month($i)->endOfMonth()->format('d/m/y');
array_push($months, $array);
}

Calculate end date pushing date forward if there is a holiday

I have written some code where I have a start date and number of days duration and then get an end date ie start date 18th December, duration 21 days, end date 8th January. However I want to push the end date forward to avoid certain holidays (25th December through to 6th January) so that the end date becomes 15th January. All the answers I have seen include how to calculate business days and take out weekends, which I don't need. I just want to be able to define specific holidays in an array, get it to see if the holiday is within the start date and end date and if it is move the end date forward by that number of days.
Oh, then the date needs to be inserted into a database. Thanks.
Iterate through the $holidays array. If a holiday is in the given range, then move end date one day in the future:
$startDate = new DateTime('2015-12-18');
$endDate = new DateTime('2016-01-08');
// $holidays array must be sorted. if not, then sort it first
$holidays = array("2015-12-25","2015-12-26","2016-01-01");
$newEndDate = $endDate;
foreach ($holidays as $holiday) {
$holidayDate = new DateTime($holiday);
if ($startDate <= $holidayDate && $holidayDate <= $newEndDate) {
// there is a holiday in the range
$newEndDate->add(new DateInterval('P1D'));
}
}
echo($newEndDate->format('Y-m-d'));
Thank you for all those that posted possible solutions. I solved the problem by adding a selector to the php page which adds 7, 14 or 21 days to the end date (the user sees this as 1 week, 2 weeks etc) and this solved the problem.

PHP DateTime() class, change first day of the week to Monday

The DateTime class in PHP (5.3+) works just great as long as the first day of the week in your country is Sunday. In the Netherlands the first day of the week is Monday and that just makes the class useless for building a calendar with week view and calculations.
I can't seem to find an answer on Stackoverflow or the rest of the Internet on how to have DateTime act as if the first day of the week is Monday.
I found this piece on Stackoverflow, but it doesn't fix all the ways you can get into trouble and it's not an elegant solution.
$dateTime = new DateTime('2012-05-14');
$monday = clone $dateTime->modify(('Sunday' == $dateTime->format('l')) ? 'Monday last week' : 'Monday this week');
Is there a way to change this or extent DateTime? Can't imagine it's not a setting as most of Europe starts their weeks on monday.
Added:
Posting the full calendar and functions code will not make things clearer. But here is one one line for example.
I often have to check what the first day of the week is or calculate from the first day of the week to a different date and time in that week. My code is getting full of these:
$startOfWeek = $date->modify(('Sunday' == $date->format('l')) ? 'Monday last week' : 'Monday this week')->modify('+3 hours')->format(DATETIME);
I also get an unwanted result trying to get the first full week of the month or year. As my $date object doesn't always contain the same date I have to keep checking it this way, making the code difficult to read. Having a lot more programming to do on this calendar I can't forsee where it's going to bug again.
EDIT There are some inconsistencies though. For some strange reason DateTime does get this next one right:
$test = new DateTime('2012-10-29'); // Monday
echo $test->modify('Sunday this week')->format('Y-m-d'); // 2012-11-04
// But...
$test = new DateTime('2012-11-04'); // Sunday
echo $test->modify('Monday this week')->format('Y-m-d'); // 2012-11-05 instead of 2012-10-29
But I think I can make the question clearer:
Can the DateTime() class be used with monday as the first day of the week. If not, can the class be extended to use monday as the first day of the week.
UPDATE:
Ok, I think I'm getting somewhere... I'm not a pro at coding classes..but this seems to work for the weeks. But it still needs rules for first day, second day... and also for the day name Sunday itself. I don't think this is foolproof. I would appreciate any help to fix it.
class EuroDateTime extends DateTime {
// Fields
private $weekModifiers = array (
'this week',
'next week',
'previous week',
'last week'
);
// Override "modify()"
public function modify($string) {
// Search pattern
$pattern = '/'.implode('|', $this->weekModifiers).'/';
// Change the modifier string if needed
if ( $this->format('N') == 7 ) { // It's Sunday
$matches = array();
if ( preg_match( $pattern, $string, $matches )) {
$string = str_replace($matches[0], '-7 days '.$matches[0], $string);
}
}
return parent::modify($string);
}
}
// This works
$test = new EuroDateTime('2012-11-04');
echo $test->modify('Monday this week')->format('Y-m-d');
// And I can still concatenate calls like the DateTime class was intended
echo $test->modify('Monday this week')->modify('+3 days')->format('Y-m-d');
I found this to work, yet there are some inconsistencies in PHP's DateTime class.
If the departing date is a sunday the previous monday is not considered the same week (fixed by this class). But departing from a monday, the next sunday is considered as the same week. If they fix that in the future this class will need some additions.
class EuroDateTime extends DateTime {
// Override "modify()"
public function modify($string) {
// Change the modifier string if needed
if ( $this->format('N') == 7 ) { // It's Sunday and we're calculating a day using relative weeks
$matches = array();
$pattern = '/this week|next week|previous week|last week/i';
if ( preg_match( $pattern, $string, $matches )) {
$string = str_replace($matches[0], '-7 days '.$matches[0], $string);
}
}
return parent::modify($string);
}
}
There's nothing to stop you manually modifying a date to get the "first" day of the week, depending on your definition of "first". For instance:
$firstDayOfWeek = 1; // Monday
$dateTime = new DateTime('2012-05-16'); // Wednesday
// calculate how many days to remove to get back to the "first" day
$difference = ($firstDayOfWeek - $dateTime->format('N'));
if ($difference > 0) { $difference -= 7; }
$dateTime->modify("$difference days");
var_dump($dateTime->format('r')); // "Mon, 14 May 2012 00:00:00 +0000"
Notice how the output changes as you vary $firstDayOfWeek; if it was changed to 4 above (Thursday), it would then consider Thu, 10 May 2012 as the "first" day.
Edit: this is a rather basic example. The correct way to do this is by using the user/system's locale to give you the "first" day, and compute from there. See this question for more information.
You can also get the start and the end of the week with setISODate, the last parameter is the ISO-daynumber in the week, monday is 1 and sunday 7
$firstday = new \DateTime();
$lastday = clone($firstday);
$firstday->setISODate($firstday->format("Y"),$firstday->format("W"),1);
$lastday->setISODate($firstday->format("Y"),$firstday->format("W"),7);
I too am confused about what the problem is, because DateTime does have a notion of the week starting from Monday: The N format gives you the days of the week counting from Monday to Sunday, with Monday being 1 and Sunday being 7. Moreover, the W format, the only way to get a week number, also starts the week on Monday. (I seem to remember that Dutch week numbers aren't exactly like ISO week numbers, but that's a different story). So what feature are you missing exactly?
Edit So the problem is (only?) that the relative datetime formats, such as "Monday this week" give the wrong result when the reference date is a Sunday. From the docs it sounds like DateTime just defers to strtotime, which is supposed to be locale-dependent. So if you have your locale set properly and this is not working, it sounds like a bug (for what that's worth).
The proper answer:
$week_start_day; // 0 - Sunday, 1 - Monday
$date_time_from = new DateTime('now');
$date_time_to = clone $date_time_from;
$this_week_first_day = $date_time_from->setISODate($date_time_from->format("Y"), $date_time_from->format("W"), $week_start_day);
$this_week_last_day = $date_time_to->setISODate($date_time_to->format("Y"), $date_time_to->format("W"), (6 + $week_start_day));

Can't get previous month from DateTime in PHP- Is this a (pretty big) bug?

I need to create functions in PHP that let me step up/down given datetime units. Specifically, I need to be able to move to the next/previous month from the current one.
I thought I could do this using DateTime::add/sub(P1M). However, when trying to get the previous month, it messes up if the date value = 31- looks like it's actually trying to count back 30 days instead of decrementing the month value!:
$prevMonth = new DateTime('2010-12-31');
Try to decrement the month:
$prevMonth->sub(new DateInterval('P1M')); // = '2010-12-01'
$prevMonth->add(DateInterval::createFromDateString('-1 month')); // = '2010-12-01'
$prevMonth->sub(DateInterval::createFromDateString('+1 month')); // = '2010-12-01'
$prevMonth->add(DateInterval::createFromDateString('previous month')); // = '2010-12-01'
This certainly seems like the wrong behavior. Anyone have any insight?
Thanks-
NOTE: PHP version 5.3.3
(Credit actually belongs to Alex for pointing this out in the comments)
The problem is not a PHP one but a GNU one, as outlined here:
Relative items in date strings
The key here is differentiating between the concept of 'this date last month', which, because months are 'fuzzy units' with different numbers of dates, is impossible to define for a date like Dec 31 (because Nov 31 doesn't exist), and the concept of 'last month, irrespective of date'.
If all we're interested in is the previous month, the only way to gaurantee a proper DateInterval calculation is to reset the date value to the 1st, or some other number that every month will have.
What really strikes me is how undocumented this issue is, in PHP and elsewhere- considering how much date-dependent software it's probably affecting.
Here's a safe way to handle it:
/*
Handles month/year increment calculations in a safe way,
avoiding the pitfall of 'fuzzy' month units.
Returns a DateTime object with incremented month/year values, and a date value == 1.
*/
function incrementDate($startDate, $monthIncrement = 0, $yearIncrement = 0) {
$startingTimeStamp = $startDate->getTimestamp();
// Get the month value of the given date:
$monthString = date('Y-m', $startingTimeStamp);
// Create a date string corresponding to the 1st of the give month,
// making it safe for monthly/yearly calculations:
$safeDateString = "first day of $monthString";
// Increment date by given month/year increments:
$incrementedDateString = "$safeDateString $monthIncrement month $yearIncrement year";
$newTimeStamp = strtotime($incrementedDateString);
$newDate = DateTime::createFromFormat('U', $newTimeStamp);
return $newDate;
}
Easiest way to achieve this in my opinion is using mktime.
Like this:
$date = mktime(0,0,0,date('m')-1,date('d'),date('Y'));
echo date('d-m-Y', $date);
Greetz Michael
p.s mktime documentation can be found here: http://nl2.php.net/mktime
You could go old school on it and just use the date and strtotime functions.
$date = '2010-12-31';
$monthOnly = date('Y-m', strtotime($date));
$previousMonth = date('Y-m-d', strtotime($monthOnly . ' -1 month'));
(This maybe should be a comment but it's to long for one)
Here is how it works on windows 7 Apache 2.2.15 with PHP 5.3.3:
<?php $dt = new DateTime('2010-12-31');
$dt->sub(new DateInterval('P1M'));
print $dt->format('Y-m-d').'<br>';
$dt->add(DateInterval::createFromDateString('-1 month'));
print $dt->format('Y-m-d').'<br>';
$dt->sub(DateInterval::createFromDateString('+1 month'));
print $dt->format('Y-m-d').'<br>';
$dt->add(DateInterval::createFromDateString('previous month'));
print $dt->format('Y-m-d').'<br>'; ?>
2010-12-01
2010-11-01
2010-10-01
2010-09-01
So this does seem to confirm it's related to the GNU above.
Note: IMO the code below works as expected.
$dt->sub(new DateInterval('P1M'));
Current month: 12
Last month: 11
Number of Days in 12th month: 31
Number of Days in 11th month: 30
Dec 31st - 31 days = Nov 31st
Nov 31st = Nov 1 + 31 Days = 1st of Dec (30+1)

Find out date of nth week's monday in PHP?

I have a simple situation where I have a user supplied week number X, and I need to find out that week's monday's date (e.g. 12 December). How would I achieve this? I know year and week.
Some code based mainly on previous proposals:
$predefinedYear = 2009;
$predefinedWeeks = 47;
// find first mоnday of the year
$firstMon = strtotime("mon jan {$predefinedYear}");
// calculate how much weeks to add
$weeksOffset = $predefinedWeeks - date('W', $firstMon);
// calculate searched monday
$searchedMon = strtotime("+{$weeksOffset} week " . date('Y-m-d', $firstMon));
An idea to get you started:
take first day of year
add 7 * X days
use strtodate, passing in "last Monday" and the date calculated above.
May need to add one day to the above.
Depending on the way you are calculating week numbers and the start of the week this may sometimes be out. (i.e. if the monday in the first week of the year was actually in the previous year!)
TEST THIS THOROUGHLY - but I've used a similar approach for similar calcualtions in the past.
This will solve the problem for you. It mainly derives from Mihail Dimitrov's answer, but simplifies and condenses this somewhat. It can be a one-line solution if you really want it to be.
function getMondaysDate($year, $week) {
if (!is_numeric($year) || !is_numeric($week)) {
return null;
// or throw Exception, etc.
}
$timestamp = strtotime("+$week weeks Monday January $year");
$prettyDate = date('d M Y');
return $prettyDate;
}
A couple of notes:
As above, strtotime("Monday January $year") will give you the timestamp of the first Monday of the year.
As above +X weeks will increment a specified date by that many weeks.
You can validate this by trying:
date('c',strtotime('Sunday Jan 2018'));
// "2018-01-07T00:00:00+11:00" (or whatever your timezone is)
date('c',strtotime('+1 weeks Sunday Jan 2018'));
// "2018-01-14T00:00:00+11:00" (or whatever your timezone is)
date('c',strtotime('+52 weeks Sunday Jan 2018'));
// "2019-01-06T00:00:00+11:00"
Due to reputation restriction i can't post multiple links
for details check
http://php.net/manual/en/function.date.php and http://php.net/manual/en/function.mktime.php
you can use something like this :
use mktime to get a timestamp of the week : $stamp = mktime(0,0,0,0,<7*x>,) {used something similar a few years back, so i'm not sure it works like this}
and then use $wDay = date('N',$stamp). You now have the day of the week, the timestamp of the monday should be
mktime(0,0,0,0,<7*x>-$wDay+1,) {the 'N' parameter returns 1 for monday, 6 for sunday}
hope this helps
//To calculate 12 th Monday from this Monday(2014-04-07)
$n_monday=12;
$cur_mon=strtotime("next Monday");
for($i=1;$i<=$n_monday;$i++){
echo date('Y-m-d', $cur_mon);
$cur_mon=strtotime(date('Y-m-d', strtotime("next Monday",$cur_mon)));
}
Out Put
2014-04-07
2014-04-14
2014-04-21
2014-04-28
2014-05-05
2014-05-12
2014-05-19
2014-05-26
2014-06-02
2014-06-09
2014-06-16
2014-06-23

Categories