Is this a PHP bug on date? - php

Doing this on PHP 5.6.14:
$a = strtotime('2016-10-01');
$b = $a+29*24*3600;
$c = $a+30*24*3600;
die(date("d/m/Y",$b).' - '.date("d/m/Y",$c));
will return 30/10/2016 - 30/10/2016
How strange since I expect $c to be 31/10/2016 instead of 30/10/2016. Is it a PHP bug?
Notes: I know there are several ways to do date operations, but I'm asking specifically on this. If I change the $a month to other numbers than 10 (e.g. 8,9,11,12) it gave me expected results.

The way to fix this issue is by adding midday like so:
$a = strtotime('2016-10-01 12:00');
$b = $a+29*24*3600;
$c = $a+30*24*3600;
die(date("d/m/Y",$b).' - '.date("d/m/Y",$c));
The reason why is because you did not include a time so PHP assumed "now" for the time part of strtotime. You then have day light savings in October in your country (well, the country of the server) and this pulled back the closing time value of $c.
Adding midday (12:00) gives you a buffer. It does not need to be 12:00 as 1:00 would have worked as well but personally I like to work with 12:00.

Related

Find next occurance of a given time after given timestamp

Given an arbitrary timestamp (e.g. 2019-02-26 10:30:00) I would like to find the next occurrence of an arbitrary time.
For example, the next occurrence of 12:00:00 will be 2019-02-26 12:00:00 but the next occurrence of 09:00:00 will be the next day at 2019-02-27 09:00:00. The results could be Carbon or Datetime objects. The test time will just be a string as shown.
Is there a way to calculate this in native PHP or PHP Carbon without conditionally boxing in time periods. An obvious way would be to see if the time being tested is past the check time for today, and if it is, taking the result as the check time plus 24 hours (the next day). That feels to me like too much chopping and joining of dates and times, so is there a way to calculate it by considering time to be a simple linear line?
All times will be in a single timezone, with DST. Note: the arbitrary datetimes and check times will stay clear of DST changeovers i.e. 01:00 to 02:00 so hopefully they will not be an issue to take into account.
Short answer is no for PHP (partial answer, I'm no specialist of Carbon but from quick look it's also no, but you can create a macro from following code).
However, with a ternary condition the one-liner is simple enough IMHO (replace the second DateTime($str) with DateTime() if you want to compare with current date and time, and change the >= by > if you want next day when time compared is exactly the same):
$str = '2019-02-26 10:30:00';
$date1 = ( ($a = (new DateTime($str))->setTime(12,00)) >= (new DateTime($str)) ) ? $a : $a->modify('+1 day');
$date2 = ( ($a = (new DateTime($str))->setTime(9,00)) >= (new DateTime($str)) ) ? $a : $a->modify('+1 day');
echo $date1->format('Y-m-d H:i:s'); //2019-02-26 12:00:00
echo $date2->format('Y-m-d H:i:s'); //2019-02-27 09:00:00
quick note: what you gave us is not a timestamp, but a formatted date.
Here is what I am using now through Carbon, which appears to give me the correct results:
$dateTime = Carbon::parse('2019-03-30 17:34:50', 'Europe/London');
$testTime = '16:00:00';
list ($hour, $minute, $second) = explode(':', $testTime);
$nextTimeOccurrence = $dateTime
->copy() // Carbon 1 only
->hour($hour)->minute($minute)->second($second);
if ($dateTime->gt($nextTimeOccurrence)) {
$nextTimeOccurrence = $nextTimeOccurrence->addDay();
}
// $nextTimeOccurrence is the next occurrence of $testTime after $dateTime
The splitting of the time seems clumsy, but might be the best way? The approach is:
Create a timestamp with the test time on the same day as the timestamp I'm checking. This will be the timestamp I am looking for.
If the timestamp I'm checking is after the timestamp created in the previous step, then add a day to it.
I've tested this around DST, and happily Carbon/Datetime keeps the same time when adding a day over a DST period, where a day there would be 25 hours or 23 hours, depending on which way it goes.
I still think there is a more "linear time" way to do this, but this seems simple and robust. Thanks go to #michael-stokoe here at the office for my lead on this.

Cannot figure out php date() timestamps for two timestamps

With PHP, I am trying to convert a bunch of numbers into a a readable format, the thing is, I have no idea how/what format these are in or can be parsed in using the date() or time() functions in php. there are two of these as well.
(they're built from a total time spent online and time since last log-on)
onlinetime : 1544946 = 2w 3d 21h 9m
lastonline : 1397087222 = 1h 32m
does anyone know the way to get the two different times from the two different timestamps?
If you have a Unix timestamp, take a look at Convert timestamp to readable date/time PHP. The PHP documentation is here: http://php.net/manual/en/function.date.php.
For the online time, you could do modulo arithmetic to figure out the values for each, and then just make a string out of the result. Someone may have a nicer suggestion for this though.
I think John is right, the first is the number of seconds in the timespan listed. And the second certainly looks like a unix timestamp to me. So here's how you can get what you want from these sets of numbers:
1) For the first number, simply divide the number by the seconds in a given time span and use floor():
$timeElapsed = 154496; // in this case
$weeksElapsed = floor($timeElapsed / 604800);
$remainder = $timeElapsed % 604800;
$daysElapsed = floor($remainder / 86400);
etc...
2) For the second number, you can do the same thing by first getting the current timestamp and then subtracting the given timestamp from it:
$lastOnline = 1397087222; // again, in this case
$currentTimestamp = time();
$elapsedSinceLastLogin = $currentTimestamp - $lastonline;
$weeksSinceLastLogin = floor($elapsedSinceLastLogin / 604800);
etc...

Split a time range into pieces by other time ranges

I have a complicated task that I have been beating my head against the wall now for a few days. I've tried about 4 different approaches, however each seems to stall and it is becoming extremely frustrating.
I have a time range. For example, 14:30:00 until 18:30:00. Consider this time range somebody's work shift. During this time range, they state they cannot work from 15:30:00 until 16:30:00 and from 17:30:00 until 18:30:00. I need to modify the original shift's start and end times to remove the conflicting shifts.
The original shift array looks like this:
$original_shift[0]['start'] = '14:30:00';
$original_shift[0]['end'] = '18:30:00';
And the time ranges to be removed from the original shift look like this:
$subshift[0]['start'] = '15:30:00';
$subshift[0]['end'] = '16:30:00';
$subshift[1]['start'] = '17:30:00';
$subshift[1]['end'] = '18:30:00';
Here is a visualization:
So, I basically need my original shift to look like this when I'm done:
$original_shift[0]['start'] = '14:30:00';
$original_shift[0]['end'] = '15:30:00';
$original_shift[1]['start'] = '16:30:00';
$original_shift[1]['end'] = '17:30:00';
Some complications that I also need to consider are:
These time ranges may be any times (not constrained to the half hour as I have used in my example), however I will know with 100% certainty the the unavailable time ranges will always start and end on or in between the original shift's start and end times.
Unavailable times may butt up and/or take the entire original shift's time up.
I'm not looking for someone to "write my code" as much as I am looking for someone who has dealt with something like this in the past and may have some insight on how they accomplished it.
As you specifically asked for "some insight" rather than a full working answer, I'd personally go with arrays populated with "minutes".
$shift = array(
'start' => '15:30:00',
'end' => '18:30:00',
'original' => array(),
'unavailable' => array(),
'modified' => array()
);
You'd then do some jiggery pokery to convert 15:30:00 into 930 and 18:30:00 into 1110 (number of minutes) which will give you the difference between start and end times.
Use range() to quickly fill up the original array, load in your unavailable in a similar format and then use things like array_intersect() and array_diff() to work out which minutes from the original shift are unavailable.
From that, build up the modified array, and read directly from there to your output.
You need to do calculations of time-ranges. As the image shows this seems like a simple subtraction. It would be nice to just have objects that do these.
I had no code for this ready, so the following concept is a bit rough although probably not that bad.
A Range type that represents a time from-to. Those are as DateTime so that the benefits of these existing types can be used. I didn't use much of the benefits so far, however for the rest of the application this can make sense.
The Range type already contains some basic comparison methods I thought were useful to do parts of the calculations.
However as an object can not divide itself into two I also created a Ranges type which can represent one or more Ranges. This was necessary to have something that can be "divided".
I cheated a little here because I implemented the difference calculation as a member of Range, returning an array with one or multiple Range objects. The final calculation then is just having a shift and substract the unavailable ranges from it:
$shift = new Ranges(new DateTime('14:30:00'), new DateTime('18:30:00'));
$unavailables = new Ranges([
new Range(new DateTime('15:30:00'), new DateTime('16:30:00')),
new Range(new DateTime('17:30:00'), new DateTime('18:30:00')),
]);
$shift->subtract($unavailables);
The shift then spans:
14:30:00 - 15:30:00
16:30:00 - 17:30:00
Demo; Gist
I can not say if it is worth the abstraction, what is nice with DateTime objects is that you can compare them with >, < and =. The real benefit from these classes might shed into light when you need more calculations between Range and Ranges. Maybe the interface is not sweet yet, however the basic calculations behind are outlined in the code already.
One caveat: The difference between 14:00-15:00 and 14:00-15:00 in my code will result to 14:00-14:00. I keep the start time to not run empty, but you can run empty, too. The Ranges object should handle it nicely.
The code should speak for itself:
$original_shift[0]['start'] = '14:30:00';
$original_shift[0]['end'] = '18:30:00';
$breaks[0]['start'] = '14:30:00';
$breaks[0]['end'] = '15:30:00';
$breaks[1]['start'] = '16:30:00';
$breaks[1]['end'] = '17:30:00';
$modified_shift = array(
array('start' => $original_shift[0]['start'])
);
for($x = 0, $y = count($breaks), $z = 0; $x < $y; $x++){
$modified_shift[$z]['end'] = $breaks[$x]['start'];
if($modified_shift[$z]['end'] != $modified_shift[$z]['start']){
$z++;
}
$modified_shift[$z]['start'] = $breaks[$x]['end'];
}
$modified_shift[$z]['end'] = $original_shift[0]['end'];
if($modified_shift[$z]['end'] == $modified_shift[$z]['start']){
unset($modified_shift[$z]);
}

Comparing two date / times to find out if 5 mins has lapsed between the two times php

I need to compare two dates to show an edit link if it is within 5 mins after the post was made, in PHP. If more than 5 minutes have passed, don't show anything.
$answer_post_date = get_the_time("Y-m-d");
$current_date = date("Y-m-d");
$formated_current_date = strtotime($answer_post_date);
$formated_answer_post_date = strtotime($current_date);
At this point I have two values:
1274414400 ($formated_current_date)
1276056000 ($formated_answer_post_date)
I am not sure what to do next to check if the current date/time is > 5 mins from the answer post date.
Any suggestions would be great.
All I really need the answer to be is a Boolean (yes/no) and if yes, display the minuets left to show the link to edit.
You're only handling dates, how are you supposed to know if the difference is 5 minutes?
Anyway, I'd say the majority of the PHP code that uses the default PHP functions is at least somewhat broken. The problem is you, despite a unix timestamp storing the correct point in time something happens, it does not store timezone information. See here.
So, forget using only date and strtotime. Use the datetime extension.
Store in the database the Unix timestamp and the timezone (by timezone I mean e.g. Europe/Lisbon). Then:
$tz = new DateTimeZone($timezone);
$answer_post_date = new DateTime("$timestamp");
$answer_post_date->setTimeZone($tz);
$current_date = new DateTime("now", $tz);
$diff = $current_date->diff($answer_post_date);
if ($diff->format("a") > 0 ||
$diff->format("h") > 0 ||
$diff->format("m") >= 5) {
//more than 5 minutes have passed
}
Of course, for comparing dates, you can always compare the timestamps.
My understanding of what you need to do:
$delta = ($formated_current_date - $formated_answer_post_date) / 60; // in minutes
if ($delta < 5) {
// show $delta
}
EDIT: Like others pointed out, this alone will not fix all of the issues at hand. As I see it, the smallest change to your current code would be to use a date format with higher granularity - such as "Y-m-d H:i:s". This being enough, like others pointed out, is contingent on the post's date being in the same timezone as your system.
I don't see the need to do a round-trip to a string format and back, regardless of how efficient or reliable it is.
date() will default to calling time() which you can call directly and get the current time in seconds as a Unix epoch timestamp (which is what you're trying to end up with in $formated_answer_post_date). You need to look in the WordPress docs to find the equivalent based on the post's value.
Then you can do a simple comparison of seconds. 5 minutes is 300 seconds.
You will still need to check that the code can assume the timezones of both values will be the same.

How to subtract two dates, ignoring daylight savings time in PHP?

I'm trying to calculate the number of days between two days, but I'm running into issues with Daylight Savings Time. Here's my code:
function date_diff($old_date, $new_date) {
$offset = strtotime($new_date) - strtotime($old_date);
return $offset/60/60/24;
}
Works fine as long as the days are both within the same DST period:
echo date_diff('3/15/09', '3/18/09'); // 3
But not if they're further apart:
echo date_diff('11/15/08', '3/18/09'); // 122.95833333333
I want an even number of days, and don't care about DST. I suppose I could round the result, but that feels kludgy. Is there a better (easy) way? I don't want to have to write a whole day-parsing-and-counting-avoiding-leap-years thing if I can avoid it.
(Note: this has to run under php 5.1.6, so some of the date features in 5.3 may not be available.)
A bit more info: I'm going to take the offset and add it to other datetimes that are in a db, and I want only the day part to change, not the time part. Turns out rounding won't work, anyway, because when I do the adding it gets off by one hour in the other direction. Maybe there's a better approach to the whole problem....
Force the dates to live into a timezone without Daylight Savings Time, GMT/UTC:
function date_diff($old_date, $new_date) {
$offset = strtotime($new_date . " UTC") - strtotime($old_date . " UTC");
return $offset/60/60/24;
}
echo date_diff('3/15/09', '3/18/09'); // 3
echo date_diff('11/15/08', '3/18/09'); // 123
you could use http://ca3.php.net/date_default_timezone_set to set the timezone to GMT so there will be no offset.
Alternately, you can manually add an offset using the date('I',$timetamp)
if ( date("I") == 1 ) { // "WE ARE MDT";
$timeZone = "MDT";
} else {
$timeZone = "MST";
}
You can force rounding in a specific direction by using floor() or ceil().
I tried the 'UTC' code above. Didnt work for me. I stll got decimal values.
When there is daylight saving date within the date range the difference serial decimal portion will be either under .5 or over. when date range has day light saving going on 3/15 the decimal value is > .5 when daylight is going off date decimal < .5. so I just put the difference serial in a round() function and I get the whole numbers i need for number of days.

Categories