PHP strtotime is missing a day during leap year - php

I'm trying to generate a list of consecutive date periods by using strtotime to add 13 days to the first date like this:
$start = strtotime(date('2020-06-01'));
for($x = 1; $x<=10; $x++) // increment $x until 10
{
$period = $start - ($x * 1209600); // 1209600 is equivalent to 14 days. Multiply by $x
$period_end = date('Y-m-d', strtotime(date('Y-m-d', $period). ' + 13 days'));
echo date('Y-m-d', $period) . " - " . $period_end ."<br>";
}
The output looks like this:
2020-05-18 - 20-05-31
2020-05-04 - 20-05-17
2020-04-20 - 20-05-03
2020-04-06 - 20-04-19
2020-03-23 - 20-04-05
2020-03-09 - 20-03-22
2020-02-23 - 20-03-07
2020-02-09 - 20-02-22
2020-01-26 - 20-02-08
2020-01-12 - 20-01-25
Everything works as expected until it hits the '2020-02-23 - 20-03-07' range. It should report '2020-02-24 - 2020-03-08'
Why the shift by 1 day? Is this a bug in PHP strtotime in relation to a leap year?
Edit: This was not a leap year issue. It turned out to be a daylight savings time issue in my timezone. When DST occurred on 3/8 the time in seconds from epoch changed by an hour. This shifted my date to 1 hour earlier which ends up being a previous day.

If we add H:i to your date()'s it all becomes clear.
$start = strtotime(date('2020-06-01'));
for($x = 1; $x<=10; $x++) // increment $x until 10
{
$period = $start - ($x * 1209600); // 1209600 is equivalent to 14 days. Multiply by $x
$period_end = date('Y-m-d H:i', strtotime(date('Y-m-d H:i', $period). ' + 13 days'));
echo date('Y-m-d H:i', $period) . " - " . $period_end ."<br>\n";
}
Output:
2020-05-18 00:00 - 2020-05-31 00:00<br>
2020-05-04 00:00 - 2020-05-17 00:00<br>
2020-04-20 00:00 - 2020-05-03 00:00<br>
2020-04-06 00:00 - 2020-04-19 00:00<br>
2020-03-22 23:00 - 2020-04-04 23:00<br>
2020-03-08 23:00 - 2020-03-21 23:00<br>
2020-02-23 23:00 - 2020-03-07 23:00<br>
2020-02-09 23:00 - 2020-02-22 23:00<br>
2020-01-26 23:00 - 2020-02-08 23:00<br>
2020-01-12 23:00 - 2020-01-25 23:00<br>
r3mainer comment is correct. Add 12:00 to the start and the problem will go away.
Because of the daylight savings you remove one hour too much, it's not the leap year.
$start = strtotime(date('2020-06-01 12:00'));
https://3v4l.org/Uj2CA

Related

PHP DateDiff time range blocks

I have time range which is in blocks of 30 minutes,
13:00 - 13:30
13:30 - 14:00
14:00 - 14:30
15:00 - 15:30
etc...
I want select the correct 30 minute block by the current time.
e.g.
Current Time 13:42
13:00 - 13:30
13:30 - 14:00 <- This block is selected
14:00 - 14:30
15:00 - 15:30
The purpose is retrieve assign a value to that block.
Really what I'm asking if what method and approach would I use?
I'll be using PHP
Thanks
$hour = date('H');
$minutes = date('i');
if ($minutes >= 30)
{
$hour++;
}
echo $hour;
/////////////////////
echo "<BR>";
$hour = date('H');
$minute = (date('n')>30)?'30':'00';
echo "$hour:$minute";
A simple string comparison after splitting with explode does it here:
$blocks = [
'12:30 - 13:00',
'13:00 - 13:30',
'13:30 - 14:00',
'14:00 - 14:30'
];
$curTime = '13:42';
$found = false;
foreach($blocks as $key => $block){
$time = explode(' - ',$block);
if($curTime >= $time[0] AND $curTime < $time[1]){
$found = $key;
break;
}
}
if($found !== false) echo $blocks[$found];
else echo 'not found';

How to check timespan PHP function as less than 1 hour, today and yesterday?

I tryed to display added date as 25 minutes ago, Today 5.30 PM etc.
$added_time = strtotime('1 Jan 2016 6:00 AM');
$currentTime = strtotime('1 Jan 2016 7:15 AM'); // probably uses time()
$diff = timespan($time1, $time2);
if($diff < 1 hour){ // how to check 1 hour
//display minutes ago
}
else {
//display added time
}
Conditions
if the time gap is less than 60 minute -> 25 minutes ago
If the time gap is over 60 minute But Today -> Today 6.00AM
If the time gap is over 60 minute But Yesterday -> Yesterday 6.00AM
Else exactly $added_time
How to check the condition for less than 1 hour, today and yesterday?
This is not specifically a CodeIgniter question. I am not sure exactly what you are doing, but this code will get you close.
$Added = new \DateTime(date('Y-m-d H:i:s', strtotime('1 Jan 2016 6:00 AM')));
$Current = new \DateTime(date('Y-m-d H:i:s', strtotime('1 Jan 2016 7:15 AM')));
$Diff = $Added->diff( $Current , FALSE);
$hours = $Diff->format('%H');
$mins = $Diff->format('%I');
if( $Diff->invert == true ){
echo "Some hours $hours and minutes $mins ago ";
}
else if( $Diff->invert == false ){
echo "Some hours $hours and minutes $mins into the future ";
}
References:
http://php.net/manual/en/class.datetime.php
http://php.net/manual/en/datetime.diff.php

loop with time math is incorrectly showing AM/PM

In this case hours_start will be 08:00:00 and hours_end will be 14:00:00
here's the code i'm using to generate a list of 30 minute time slots between start and end
while($row = $q->fetch()){
$hours = $row['hours_end'] - $row['hours_start']; //amount of hours working in day
for($i = 0; $i < $hours * 2; $i++){ // double hours for 30 minute increments
$minutes_to_add = 1800 * $i; // add 30 - 60 - 90 etc.
$timeslot = date('h:i:s', strtotime($row['hours_start'])+$minutes_to_add);
echo "
<tr>
<td>" . date('h:i A', strtotime($timeslot)) . "</td>
</tr>";
}
}
this is producing:
08:00 AM
08:30 AM
09:00 AM
09:30 AM
10:00 AM
10:30 AM
11:00 AM
11:30 AM
12:00 PM
12:30 PM
01:00 AM
01:30 AM
as you can see, it is functioning as (i) expected until it gets to (what should be) 1 PM then it switches back to AM. not sure whats going on here.
Its because you are setting $timeslot using h which is only a 12 hour format without appending am or pm. Then taking that 12 hour format and running it through strtotime which expects 24 hour format if am or pm is not present. Hence anything after 12 becomes am again.
You need to use:
$timeslot = date('H:i:s', strtotime($row['hours_start'])+$minutes_to_add);
OR
$timeslot = date('h:i:s a', strtotime($row['hours_start'])+$minutes_to_add);
Your date format is using h instead of H. The lowercase h is 12 hour format
Use a capital H instead for 24 hours.
date('H:i A', strtotime($timeslot))

PHP strtotime is wrong (some times)?

I have this issue with strtotime in my PHP code that it is wrong sometimes (only for some timezones) and it is correct for others!!
I cannot get my head around it.
I have set the <?php date_default_timezone_set('GMT'); ?> at the top of my page as well but that doesn't help!
basically what it does is that it will add or subtract the offset/3600 value to the set time in $time1 = strtotime('00:00'); depending on the if and else if conditions.
the offset/3660 value is the time difference between two timezones!
the code bellow works for some locations and it doesn't for others! basically it will add an extra 1-2 hours or takes off/subtract an extra 1-2 hours some times (not all the time).
i.e. the time difference between Abidjan and london is -1.
the time (value) that should be shown is 23:00 as 00:00 - 01:00 = 23:00. but the value is shown is 00:00.
However as i mentioned it works for some timezones.
i.e. for the time difference between New York and London which is -5 the code works and it shows 19:00 as 00:00 - 05:00 = 19:00
could someone please shed a light on this?
here is the code in question:
<?php
$time1 = strtotime('00:00');
if (0 > $offset)
{
// For negative offset (hours behind)
$hour_dif = date('H:i', strtotime($time1 -$offset/3600));
$time1 = "{$hour_dif}";
}
elseif (0 < $offset)
{
// For positive offset (hours ahead)
$hour_dif = date('H:i', strtotime($time1 +$offset/3600));
$time1 = "{$hour_dif}";
}
else
{
// For offsets in the same timezone.
$time1 = "in the same timezone";
}
echo "{$time1}";
?>
Well, since strtotime() already returns a timestamp and date() expects one you could just do
$hour_dif = date('H:i', ($time1 - ($offset*3600)));
or
$hour_dif = date('H:i', ($time1 + ($offset*3600)));
respectively, to remove or add the correct amount of seconds from/to the timestamp.
I am also assuming that $offset is the offset in hours, so you would have to multiply by 3600 to get the amount of seconds, not divide.
Alright, after testing your code and thinking through it some more, it became obvious.
With a negative offset like -1 you're going to calculate $time1 - (-1) * 3600 and we all know that a double negation is positive...
So in fact, your code can be condensed to:
$time1 = strtotime('00:00');
if ($offset == 0)
$time1 = "in the same timezone";
else
{
// For positive offset (hours ahead)
$hour_dif = date('H:i', ($time1 + ($offset*3600)));
$time1 = "{$hour_dif}";
}
echo "{$time1}\n";
and should work as expected:
cobra#box ~ $ for i in {-24..24}; do php test.php $i; done;
00:00
01:00
02:00
03:00
04:00
05:00
06:00
07:00
08:00
09:00
10:00
11:00
12:00
13:00
14:00
15:00
16:00
17:00
18:00
19:00
20:00
21:00
22:00
23:00
in the same timezone
01:00
02:00
03:00
04:00
05:00
06:00
07:00
08:00
09:00
10:00
11:00
12:00
13:00
14:00
15:00
16:00
17:00
18:00
19:00
20:00
21:00
22:00
23:00
00:00

Find weekly periods (starting on a Monday) for a month

I'm trying to find the weekly periods for a given month and year. Dates should start on a Monday and end on a Sunday. If the 1st of the month is a Sunday (Ex May 2011), it should be the first element.
May 2011
May 1 (Sunday)
May 2 - May 8 (Monday - Sunday)
May 9 - May 15 (Monday - Sunday)
May 17 - Ma6y 22 (Monday - Sunday)
May 23 - May 29 (Monday - Sunday)
May 30 - May 31 (Monday - Tuesday)
September 2012
September 1 - September 2
September 3 - September 9
September 10 - September 16
September 17 - September 23
September 24 - September 30
I am using this function to calculate the week numbers for two dates - I.e. the 1st day of the month and last day of the month.
public function getWeekNumbers($startDate, $endDate)
{
$p = new DatePeriod(
new DateTime($startDate),
new DateInterval('P1W'),
new DateTime($endDate)
);
$weekNumberList = array();
foreach ($p as $w)
{
$weekNumber = $w->format('W');
$weekNumberList[] = ltrim($weekNumber, '0');
}
return $weekNumberList;
}
Strangely, for the month of January, it returns week numbers of [52, 1, 2, 3, 4] when I'm expecting [1, 2, 3, 4, 5].
Once I have the week numbers, I'm using them like so:
//The following loop will populate the dataset with the entire month's durations - regardless if hours were worked or not.
$firstDayOfMonth = date('Y-m-d', strtotime("first day of {$this->year}-{$monthName}"));
$lastDayOfMonth = date('Y-m-d', strtotime("last day of {$this->year}-{$monthName}"));
foreach ($this->getWeekNumbers($firstDayOfMonth, $lastDayOfMonth) as $key => $weekId)
{
// find first mоnday of the year
$firstMon = strtotime("mon jan {$this->year}");
// calculate how many weeks to add
$weeksOffset = $weekId - date('W', $firstMon);
$beginDays = $weeksOffset * 7;
$endDays = ($weeksOffset * 7) + 6;
$searchedMon = strtotime(date('Y-m-d', $firstMon) . " +{$beginDays} days");
$searchedSun = strtotime(date('Y-m-d', $firstMon) . " +{$endDays} days");
echo date("M d", $searchedMon) . " - " . date("M d", $searchedSun);
}
Since, the getWeekNumbers function isn't returning the week numbers I'm expecting, it's not surprising that the output of the above function is
Dec 24 - Dec 30 (2012)
Jan 02 - Jan 08 (2012)
Jan 09 - Jan 15 (2012)
Jan 16 - Jan 22 (2012)
Jan 23 - Jan 29 (2012)
Note that the 1st line (Dec 24 - Dec 30) is the end of the current year (2012) and not the end of last year (2011).
Ideally, I want it to look like
Any ideas? Thanks!!
If you need all weeks for selected month, and all dates for selected week, then this is all you need:
function getWeekDays($month, $year)
{
$p = new DatePeriod(
DateTime::createFromFormat('!Y-n-d', "$year-$month-01"),
new DateInterval('P1D'),
DateTime::createFromFormat('!Y-n-d', "$year-$month-01")->add(new DateInterval('P1M'))
);
$datesByWeek = array();
foreach ($p as $d) {
$dateByWeek[ $d->format('W') ][] = $d;
}
return $dateByWeek;
}
getWeekDays() function returns multi dimension array. first key is week number. 2 level is array, that has dates saved as DateTime object.
Fetch example:
print_r( getWeekDays(5, 2011) ); # May 2011
print_r( getWeekDays(9, 2012) ); # Sep 2012
I had a little time extra, so I written an example ;-)
$datesByWeek = getWeekDays(8, 2012);
$o = '<table border="1">';
$o.= '<tr><th>Week</th><th>Monday</th><th>Tuesday</th><th>Wednesday</th><th>Thursday</th><th>Friday</th><th>Saturday</th><th>Sunday</th></tr>';
foreach ($datesByWeek as $week => $dates) {
$firstD = $dates[0];
$lastD = $dates[count($dates)-1];
$o.= "<tr>";
$o.= "<td>" . $firstD->format('M d') . ' - ' . $lastD->format('M d') . "</td>";
$N = $firstD->format('N');
for ($i = 1; $i < $N; $i++) {
$o.= "<td>-</td>";
}
foreach ($dates as $d) {
$o.= "<td>" . $d->format('d.') . " / 0.00</td>";
# for selected date do you magic
}
$N = $lastD->format('N');
for ($i = $N; $i < 7; $i++) {
$o.= "<td>-</td>";
}
$o.= "</tr>";
}
$o.= '</table>';
echo $o;
Output looks like:
The following assumes that the user can pick the month and year for which they wan to run the report (the value posted being 1-12 for month and YYYY for year). There may be a more elegant way to do it, but this seems to work for me. Also, at the top of your post, you say that you want the weeks to be Monday - Sunday. However, your example/screenshot at the bottom shows weeks being Sunday to Saturday. The below is for the originally-stated goal of Monday - Sunday.
$month = $_POST["month"];
$year = $_POST["year"];
$endDate = date("t", strtotime($year."-".$month."-01"));
$dayOfWeekOfFirstOfMonth = date("w", strtotime($year."-".$month."-01"));
$lastDayOfFirstWeek = 8 - $dayOfWeekOfFirstOfMonth;
$weeksArray = array(array("firstDay"=>1, "lastDay"=>$lastDayOfFirstWeek));
$loopDate = $lastDayOfFirstWeek + 1;
while($loopDate < $endDate)
{
$weeksArray[] = array("firstDay"=>$loopDate, "lastDay"=>($loopDate+6 > $endDate ? $endDate : $loopDate+6));
$loopDate+=7;
}
foreach($weeksArray as $week)
{
echo date("M d", strtotime($year."-".$month."-".$week["firstDay"])) . " - " . date("M d", strtotime($year."-".$month."-".$week["lastDay"])) . "\n";
}
this works perfect!!! phpfiddle here
<?php
// start and end must be timestamps !!!!
$start = 1346976000; // Thu 2012-09-06
$end = 1348704000; // Tue 2012-09-26
// generate the weeks
$weeks = generateweeks($start, $end);
// diaplay the weeks
echo 'From: '.fDate($start).'<br>';
foreach ($weeks as $week){
echo fDate($week['start']).' '.fDate($week['end']).'<br>';
}
echo 'To: '.fDate($end).'<br>';
/* outputs this:
From: Thu 2012-09-06
Thu 2012-09-06 Sun 2012-09-09
Mon 2012-09-10 Sun 2012-09-16
Mon 2012-09-17 Sun 2012-09-23
Mon 2012-09-24 Wed 2012-09-26
To: Wed 2012-09-26
*/
// $start and $end must be unix timestamps (any range)
// returns an array of arrays with 'start' and 'end' elements set
// for each week (or part of week) for the given interval
// return values are also in timestamps
function generateweeks($start,$end){
$ret = array();
$start = E2D($start);
$end = E2D($end);
$ns = nextSunday($start);
while(true){
if($ns>=$end) {insert($ret,$start,$end);return $ret;}
insert($ret,$start,$ns);
$start = $ns +1;
$ns+=7;
}
}
// helper function to append the array and convert back to unix timestamp
function insert(&$arr, $start, $end){$arr[] = array('start'=>D2E($start), 'end'=>D2E($end));}
// recives any date on CD format returns next Sunday on CD format
function nextSunday($Cdate){return $Cdate + 6 - $Cdate % 7;}
// recives any date on CD format returns previous Monday on CD format // finaly not used here
function prevMonday($Cdate){return $Cdate - $Cdate % 7;}
// recives timestamp returns CD
function E2D($what){return floor($what/86400)+2;} // floor may be optional in some circunstances
// recives CD returns timestamp
function D2E($what){return ($what-2)*86400;} // 24*60*60
// just format the timestamp for output, you can adapt it to your needs
function fDate($what){return date('D Y-m-d',$what);}

Categories