How to subtract two dates, ignoring daylight savings time in PHP? - 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.

Related

Need help about UNIX epoch

I have been googling last 2 hours and I figured out that the problem is related to Unix epoch.
What I want to do is a count down in php. As you can see below, (Gave them letters to refer them easily).
B minus C is equal to A. But it gives me this: "31/12/69 19:00:08" but I would like to have "00/00/00 00:00:08".
(A) 31/12/69 19:00:08 = (B) 25/03/15 00:13:26 - (C) 25/03/15 00:13:18
And this problem cause a problem in my count down. When I wanna count down from 10:00 (Min, Sec), it starts counting down from 60:00 and when it reaches 50:00 count down stops. So it works! after 10 min it stops but the problem is, it counts down from 60. So, it is caused from unix epoch I guess. If I can initialize the unnecessary part, in the date, to zero or null I can fix the problem.
$pastTime = $currentTime - $row['startTime'];
if($pastTime >= $row['durationTime']){
$processStatus = "Completed";
$remainingTime = 0;
}else{
$processStatus = "in Process";
$remainingTime = $durationTime - $pastTime;
}
echo "<td>" . date('i:s', $remainingTime) . "</td>";
echo "<td>$processStatus</td>";
echo "</tr>";
This is some part of my code. So, I am having problem when I do this; date('i:s', $remainingTime). All the variables you see, contains seconds which is generated by time() function.
You're not sharing any code, but I take it that you subtract two timestamps to figure out the difference in seconds, and then format the result as a unix timestamp.
The problem is in that last step. When you subtract the two numbers, you do get a (mostly) correct number, but then you decide to use (presumably) date() for display.
Instead, you may want to try to use two DateTime objects, use diff to get the difference (as a DateInterval object) and then use DateInterval::format to get the format you actually want.

how to convert PHP timezone string(e.g Europe/Berlin) to codeigniter timezone(e.g. UP1)

In a short if i have to ask this question is just the vice verse of this link question .
I am using a javscript library which automcatically detects the timezone and returns the time zone name in PHP format i.e 'Europe/Berlin' format.
But I make use of Codeigniters timezone_menu which gives a drop down box of time timezones and I am wondering how can i convert this php time zone (i.e 'Europe/Berlin' ) to codeigniters timezone format i.e UP1.?
It appears from the CodeIgniter documentation that they are treating time zones as fixed offsets. Their convention is fairly straight forward:
UTC to represent exactly UTC.
or
U to represent UTC
M or P for Minus or Plus
A number to describe the offset
A single digit for whole hour offsets
Two digits when there are half-hour offsets, but these are shifted funky (you would think +05:30 would be represented by UP55, but its actually UP45
So why did they do this? Who knows. It's not a standard thing, it's a CodeIgnitor special format. Normally an offset is just represented like +05:30 or +0530.
Using an offset to represent a whole time zone is out of sync with reality. If it was that easy, then we wouldn't need the IANA time zones like Europe/Berlin in the first place. You can see in the time zone list here that Europe/Berlin alternates between +01:00 and +02:00. Code Ignitor might say that it is UP1, but then that wouldn't ever take into account the daylight time offset. (Daylight saving time is different all over the world, so you can't just augment this with a checkbox and expect to be reliable.)
So, if you must have CodeIgnitor's strange form of time zone representation, then take the base offset of the IANA zone and apply their funky formula (as dev-null-dweller showed in his answer). Just don't expect it to be accurate.
And if you're in zone with a :45 offset, then you're out of luck. That would be Pacific/Chatham or Asia/Kathmandu.
You can read more in the timezone tag wiki under "Time Zone != Offset"
Just to add something actually constructive to to this answer, I recommend not using CodeIgnitor's time zones. Just stick with the IANA zones as provided for you by PHP. They are even kept up to date via timezondb in the PECL.
I don't know CI that much, but from quick look at the date helper documentation, it looks like it can be created from offset:
$now = new DateTime('now', new DateTimeZone('UTC'));
$tz = new DateTimeZone('America/Argentina/Buenos_Aires');
$offset = $tz->getOffset($now) / 3600;
$ci_tz = 'U';
if($offset) {
if($offset < 0) {
$ci_tz .= 'M';
$offset = abs($offset);
} else {
$ci_tz .= 'P';
}
if(is_float($offset)) {
$offset = $offset * 10 - 10;
}
$ci_tz .= (string)$offset;
} else {
$ci_tz .= 'TC';
}
var_dump($ci_tz); // UM3 = UTC Minus 3 hours

assistance with a timestamp php function

I need to convert a timestamp to UTC-5
The $offset is the user's timezone the values are from -12 to 12. $ds is daylight savings, if it's on, it will add an extra hour. I made this function, but I think it converts a UTC-5 timestamp to a new timestamp based on the user's timezone...I need the function to be inverted so that it returns a timestamp in UTC-5 instead. Of course the problem is much larger than this, but here's where I'm stuck. Any way to go about it?
function input_date($timestamp)
{
global $vbulletin;
$timestamp = (int)$timestamp;
if (strlen((string)$timestamp) == 10)
{
$hour = 3600.00;
$offset = $vbulletin->userinfo['timezoneoffset'];//sample -8
$ds = (int)$vbulletin->userinfo['dstonoff'];//DST values are 1 or 0
$fluff = $hour*($offset+5.00);
$timestamp = $timestamp+$fluff+($ds*$hour);
return $timestamp;//return timestamp in UTC-5 format..
}
else
{
return 0;
}
}
Whoa... talk about reinventing the wheel! And in an area notoriously hard to get right (date/time manipulation) no less.
Have you seen the DateTime and DateTimeZone classes? In addition to doing basic math, they will help you with the particular insanities of this realm of programming (per-county DST! Leap years!).
I have to ask, though, why you are doing this? The UNIX timestamp is, by definition, independent of timezones, DST, etc. It is defined precisely as the number of seconds that have elapsed since a given reference date, and the flow of time (relativistic effects notwithstanding ;-) is invariant with respect to location or the particular idiosyncrasies of lawmakers.
Maybe if you can describe in more detail what your actual goal is then we might be able to suggest a more coherent approach.

PHP strtotime without leap-years

With regards to this thread, I've developed a partial solution:
function strtosecs($time,$now=null){
static $LEAPDIFF=86400;
$time=strtotime($time,$now);
return $time-(round((date('Y',$time)-1968)/4)*$LEAPDIFF);
}
The function is supposed to get the number of seconds given a string without checking leap-years.
It does this calculating the number of leap-years 1970 [(year-1986)/4], multiplying it by the difference in seconds between a leap-year and a normal year (which in the end, it's just the number of seconds in a day).
Finally, I simply remove all those excess leap-year seconds from the calculated time. Here's some examples of the inputs/outputs:
// test code
echo strtosecs('+20 years',0).'=>'.(strtosecs('+20 years',0)/31536000);
echo strtosecs('+1 years',0).'=>'.(strtosecs('+1 years',0)/31536000);
// test output
630676800 => 19.998630136986
31471200 => 0.99794520547945
You will probably ask why am I doing a division on the output? It's to test it out; 31536000 is the number of seconds in a year, so that 19.99... should be 20 and 0.99... should be a 1.
Sure, I could round it all and get "correct" answer, but I'm worried about the inaccuracies.
Edit1: Since it doesn't seem obvious, my problem is with inveteracies; you just don't ask PHP for 20 years and it gives you 19.99..., right?
Edit2: It all seems to boil down to the part about 1968;
1970; found it accurate in all tests I've tried.
1969; Found it used here (...ex: (2008-1969)/4 = 9.75...) as well as mentioned here. Accurate after the 2nd year (+3 years) onwards.
1968; as detailed below, this is "year zero" of leap years from unix time (1970). It sounds "right" (to me) but it isn't accurate, at all.
Could this be related to the inherent inaccuracy experienced when using PHP to manage floating point numbers?
http://php.net/manual/en/language.types.float.php
You should replace 1968 in your calculation (where does it come from ?) by the origine of unix time : 1970 and you will get more accurate results.
Edit
You have to do an intval to count the number of leapyears which must be an integer :
return $time - (intval( (date('Y', $time) - 1969) / 4) * $LEAPDIFF);
This will give you correct results within the range +0 -> +68 , end of unix time on 32bit machine

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.

Categories