php giving wrong answer when using time zone Australia/Sydney - php

I am developing an website to run in Australia.
so i have set the time zone as follows.
date_default_timezone_set('Australia/Sydney');
I need to calculate number of days between two dates.
I found a strange behavior in the month of October.
$now = strtotime('2013-10-06'); // or your date as well
$your_date = strtotime('2013-10-01');
$datediff = $now - $your_date;
echo floor($datediff/(60*60*24));//gives output 5, this is right
$now = strtotime('2013-10-07'); // or your date as well
$your_date = strtotime('2013-10-01');
$datediff = $now - $your_date;
echo floor($datediff/(60*60*24));//gives output 5, this is wrong, but it should be 6 here
after 2013-10-07 it always give one day less in answer.
Its fine with other timezones. May be its due to daylight saving. But whats the solution for this.
Please help.
Thanks

Why it says 5, and why this is technically correct
In Sydney, DST begins at 2013-10-06 02:00:00 - so you lose an hour in dates straddling that.
When you call strtime, it will interpret the time as a Sydney time, but return a Unix timestamp. If you converted the second set of timestamps to UTC, you'd get a range from 2013-09-30 14:00:00 to 2013-10-06 13:00:00, which isn't quite 6 days, so gets rounded down to 5.
How to get the time difference ignoring DST transitions
Try using DateTime objects instead, e.g.
$tz=new DateTimeZone('Australia/Sydney');
$start=new DateTime('2013-10-01', $tz);
$end=new DateTime('2013-10-07', $tz);
$diff=$end->diff($start);
//displays 6
echo "difference in days is ".$diff->d."\n";
Why does DateTime::diff work differently?
You might ask "why does that work?" - after all, there really isn't 6 days between those times, it's 5 days and 23 hours.
The reason is that DateTime::diff actually corrects for DST transitions. I had to read the source to figure that out - the correction happens inside the internal timelib_diff function. This correction happens if all the following are true
each DateTime uses the same timezone
the timezone must be geographic id and not an abbreviation like GMT
each DateTime must have different DST offsets (i.e. one in DST and one not)
To illustrate this point, here's what happens if we use two times just a few hours either side of the switch to DST
$tz=new DateTimeZone('Australia/Sydney');
$start=new DateTime('2013-10-06 00:00:00', $tz);
$end=new DateTime('2013-10-06 04:00:00', $tz);
//diff will correct for the DST transition
$diffApparent=$end->diff($start);
//but timestamps represent the reality
$diffActual=($end->getTimestamp() - $start->getTimestamp()) / 3600;
echo "Apparent difference is {$diffApparent->h} hours\n";
echo "Actual difference is {$diffActual} hours\n";
This outputs
Apparent difference is 4 hours
Actual difference is 3 hours

Related

# How to use the DateTime class (Dealing with Conversions, Formatting, Diffs, and Time zones) [duplicate]

This question already has answers here:
Convert one date format into another in PHP
(17 answers)
How to calculate the difference between two dates using PHP?
(34 answers)
Timezone conversion in php
(9 answers)
Adding three months to a date in PHP
(11 answers)
Closed 4 years ago.
Everyone has to deal with date/time formatting, time zones, weird time formats, etc. at some point in programming. PHP has few ways to deal with these problems, the most notable of which is PHP's built in DateTime class. This is because the DateTime class acts as an all-in-one solution to all theses problems.
However, the DateTime class has one flaw: it can be confusing to use if you aren't already familiar with it.
This post is to help those people who have want to know more about the DateTime class and/or found themselves asking one of the following questions:
How do I convert a string to a modifiable date/time?
How do I format a date/time string?
How do I get the difference between 2 times?
How do I account for time zones?
Please Note:
This post will not be addressing the usage of date(), time(), strtotime(), or any of their related functions. This post is purely to explain the correct usage of the DateTime and its related classes in PHP. While many of these answers can also be achieved via date() and its related functions, DateTime wraps all of that functionality into a few clean classes; which can make it easier to comprehend as a whole.
Most of the below information can be gotten from various parts of PHP's documentation of the DateTime class. However, this answer is formatted in a way that should answer most people's questions regarding the DateTime class, and should apply to most of its use cases.
If you are trying to do something more advanced with Dates/Times such as creating a DateTime wrapper, using immutable DateTime instances, or something else that's specific to your applications needs I highly suggest you checkout the full Date and Time documentation.
1. How do I convert a string to a modifiable date/time?
One of the hardest things to do in programming is to try and make end-user input usable. However, when it comes to dates and times, the DateTime class makes this practically child's play.
How
DateTime's constructor uses a powerful parser that accepts most widely known formats including relative formats.
$datetime = new DateTime($datetime_string);
From there you can use any of the following methods to modify the time:
$datetime->modify() - Alters the timestamp (works great with relative formats!)
$datetime->add() - Adds an amount of days, months, years, hours, minutes and seconds to a DateTime object
$datetime->sub() - Subtracts an amount of days, months, years, hours, minutes and seconds from a DateTime object
$datetime->setDate() - Sets the date
$datetime->setISODate() - Sets the ISO date
$datetime->setTime() - Sets the time
$datetime->setTimestamp() - Sets the date and time based on an Unix timestamp (great for dealing with absolute times across time zones!)
To see the full list of formats that DateTime::__construct() supports check out: Supported Date and Time Formats.
Example - Interpreting End-User Input
Lets say you have form that allows users to say what day they want to make an appointment, but this input is not a date picker with a forced format, but is instead a plain text input.
A typical end-user will put something like these in that input and a typical programmer will respond in the following ways when asked to support it:
12/31/2000 - "OK"
2000-12-31 - "Sure"
Today - "Um, I guess we could support that?"
Tomorrow - "I guess we should support that too."
wednesday next week - "No."
After a while you either force a specific format (which you should always do anyway) or weep at your poor form design. However, DateTime allows all of these as valid inputs and interprets them flawlessly.
// 2000-12-31 00:00:00.000000
new DateTime('12/31/2000');
// 2000-12-31 00:00:00.000000
new DateTime('2000-12-31');
// 2000-12-31 00:00:00.000000
new DateTime('Today');
// 2001-01-01 00:00:00.000000
new DateTime('Tomorrow');
// 2001-01-03 00:00:00.000000
new DateTime('wednesday next week');
However, like most things the DateTime class is not perfect, and doesn't support every format. Which is why you should always use try ... catch blocks with DateTime and confirm with your end-user that the date you interpreted is what the end-user desired. One great example is European date formats:
try {
new DateTime('31/12/2000');
} catch (Exception $e) {
echo $e->getMessage();
}
Output:
DateTime::__construct(): Failed to parse time string (31/12/2000) at position 0 (3): Unexpected character
Example - Modifying A Date/Time
You can adjust any date/time easily with the $datetime->modify() method:
$datetime = new DateTime('2001-01-01');
// 2001-01-04 00:00:00.000000
$datetime->modify('+3 days');
// 2001-02-04 00:00:00.000000
$datetime->modify('+1 month');
// 2001-02-03 23:59:00.000000
$datetime->modify('-60 seconds');
// 2001-02-02 00:00:00.000000
$datetime->modify('yesterday');
// 2001-02-02 18:00:00.000000
$datetime->modify('6pm');
The $datetime->modify() method is the easiest way to modify any instance of DateTime.
However, due to parsing it is somewhat inefficient. If you are modifying 1000's of dates/times and you need better performance, then use add(), sub(), setDate(), setISODate(), setTime(), and setTimestamp() instead of modify().
$datetime = new DateTime('2001-01-01');
// 2001-06-01 00:00:00.000000
$datetime->setDate(2001, 6, 1);
// 2001-06-01 06:30:00.000000
$datetime->setTime(6, 30, 0);
// No sane person should ever do the below when they could just add 10,000
// seconds, but it's a good way to test how fast your system will handle
// updating DateTime.
$timestamp = $datetime->getTimestamp();
foreach (range(1, 10000) as $i) {
$timestamp++;
$datetime->setTimestamp($timestamp);
}
// 2001-06-01 09:16:40.000000
2. How do I format a date/time string?
It's common that you need to take 1 date/time string and format it as another date/time string, or even just take and existing date/time and update it. The DateTime class makes this simple as well.
How
DateTime has the method format() which returns the date/time as a formatted string.
$datetime = new DateTime;
$format = 'Y-m-d H:i:s';
echo $datetime->format($format);
We are only going to use a small subset of the formatting options available in these examples, so I highly encourage you to checkout the documentation on formatting dates/times as well as the predefined DateTime constants.
Notice: Be aware that if you attempt to escape a character that could be PHP string escape character you may get unexpected results.
Incorrect Result
// output: Da e 2000-12-31
echo $datetime->format("\D\a\t\e\: Y-m-d").PHP_EOL;
Correct Result
// output: Date 2000-12-31
echo $datetime->format("\D\a\\t\e\: Y-m-d").PHP_EOL;
// output: Date 2000-12-31
echo $datetime->format('\D\a\t\e\: Y-m-d').PHP_EOL;
Examples
These are some common formats that you may need:
SQL Dates/Times
// output: 2000-12-31
echo $datetime->format('Y-m-d').PHP_EOL;
// output: 23:59:59
echo $datetime->format('H:i:s').PHP_EOL;
// output: 2000-12-31 23:59:59
echo $datetime->format('Y-m-d H:i:s').PHP_EOL;
End-User Readable Dates/Times
// output: 12/31/2000
echo $datetime->format('n/j/Y').PHP_EOL;
// output: 11:59pm
echo $datetime->format('g:ia').PHP_EOL;
// output: 12/31/2000 at 11:59pm
echo $datetime->format('n/j/Y \a\t g:ia').PHP_EOL;
// output: Sunday the 31st of December 2000 at 11:59:59 PM
echo $datetime->format('l \t\h\e jS \o\f F Y \a\t g:i:s A').PHP_EOL;
Dates/Times With Time zones
date_default_timezone_set('America/New_York');
$datetime = new DateTime('2000-12-31 23:59:59');
// output: 2000-12-31 23:59:59 America/New_York
echo $datetime->format('Y-m-d H:i:s e').PHP_EOL;
// output: 2000-12-31 23:59:59 EST
echo $datetime->format('Y-m-d H:i:s T').PHP_EOL;
// output: 2000-12-31 23:59:59 -0500
echo $datetime->format('Y-m-d H:i:s O').PHP_EOL;
3. How do I get the difference between 2 times?
It is common to need to know the difference in time between 2 dates/times. With DateTime there are actually 3 different ways to achieve this, and which one you will want to use will depend on your needs.
How (with Examples)
Scenario 1: You only need to know if $datetime1 is greater than, less than, or equal to $datetime2
In this case, you can simply directly compare the instances of DateTime.
$datetime1 = new DateTime;
sleep(1);
$datetime2 = new DateTime;
var_dump($datetime1 > $datetime2); // FALSE
var_dump($datetime1 < $datetime2); // TRUE
var_dump($datetime1 == $datetime2); // FALSE
Scenario 2: You need the difference between $datetime1 and $datetime2 expressed as broken-down years/months/days/etc.
This will work for most cases, however the DateInterval instance you get back from $datetime->diff() has its own "gotchas" and may not work for your specific use case.
$datetime1 = new DateTime('2000-01-01 00:00:00.000000');
$datetime2 = new DateTime('2001-02-03 04:05:06.789012');
$diff = $datetime1->diff($datetime2);
// output: 1 Years, 1 Months, 2 Days, 4 Hours, 5 Minutes, 6 Seconds
echo $diff->format('%y Years, %m Months, %d Days, %h Hours, %i Minutes, %s Seconds');
Scenario 3: You need the difference between $datetime1 and $datetime2 expressed in another way.
This will work in any context at the cost of a little extra code.
$interval = 60 * 60 * 24; // 1 day in seconds
$datetime1 = new DateTime('2000-01-01');
$datetime2 = new DateTime;
$diff = $datetime2->getTimestamp() - $datetime1->getTimestamp();
// output: It has been 6956 days since 2000-01-01!
printf('It has been %s days since %s!', floor($diff / $interval), $datetime1->format('Y-m-d'));
4. How do I account for time zones?
When it comes to dealing with time in programming, by far the worst part is dealing with time zones. Fortunately, this is something else that the DateTime class handles gracefully.
How
DateTime's constructor allows you to specify the source time zone in either the date/time string or as the 2nd Argument. After that, just set a new timezone with $datetime->setTimezone() and DateTime will take care of the rest.
// These 2 lines are functionally identical
$datetime = new DateTime('2001-01-01 00:00:00', new DateTimeZone('UTC')); // recommended, may be faster
$datetime = new DateTime('2001-01-01 00:00:00 UTC');
$datetime->setTimezone(new DateTimeZone('EST'));
I recommend checking out the full list of PHP's supported time zones as well as the docs on the DateTimeZone class.
Example
Lets say you want to show your end-users the time your customer support line opens in their time zone. With DateTime the code would look something like this:
$support_opens = new DateTime('08:00:00', new DateTimeZone('America/New_York'));
$customer_timezones = array('America/New_York', 'America/Chicago', 'America/Denver', 'America/Phoenix', 'America/Los_Angeles', 'America/Anchorage', 'America/Adak', 'Pacific/Honolulu');
echo "Today we open at the following times:".PHP_EOL;
foreach ($customer_timezones as $timezone) {
$support_opens->setTimezone(new DateTimeZone($timezone));
echo '* '.$support_opens->format('g:ia \f\o\r \t\h\e e').' time zone'.PHP_EOL;
}
Output:
Today we open at the following times:
* 8:00am for the America/New_York time zone
* 7:00am for the America/Chicago time zone
* 6:00am for the America/Denver time zone
* 6:00am for the America/Phoenix time zone
* 5:00am for the America/Los_Angeles time zone
* 4:00am for the America/Anchorage time zone
* 3:00am for the America/Adak time zone
* 3:00am for the Pacific/Honolulu time zone
Notice: If you supply a time zone in both the date/time string and as the second argument, the argument time zone will be ignored.
$datetime = new DateTime('2001-01-01 00:00:00 EST', new DateTimeZone('UTC'));
// output: 2001-01-01 00:00:00 EST
echo $datetime1->format('Y-m-d H:i:s');

strtotime() returns different Unix timestamp in different timezones for string "now"

php > echo strtotime("now America/New_York")."\n";
1376459035 // -> 2013-08-14 05:43:55 - Wrong
php > echo strtotime("now UTC")."\n";
1376444635 // -> 2013-08-14 01:43:55 - OK
php > echo time()."\n";
1376444635 // -> 2013-08-14 01:43:55 - OK
Can anyone explain?
Is this some PHP's invention – timezone-"corrected" unix timestamps?
// edit:
I realize it makes no sense to even specify the timezone with "now". It does, however, with other relative times, e.g. "tomorrow midnight". There, depending on the timezone, "tomorrow" could be a day further away, depending on whether the timezone is over midnight already. The behavior is equally weird, just a bit harder to explain.
As explained on Unix time wiki, Unix Epoch is always in UTC. Thats why outputs from
echo strtotime("now UTC");
echo time();
are the same. According this info New York time zone is UTC -5 hours. With current daylight saving time +1 now it equally UTC -4 hours. That's why you got result 2013-08-14 05:43:55 (4 hours diff).
you are using
strtotime("now America/New_York") thats why it is showing unix timestamp in different timezone for string.
use this:
date_default_timezone_set("America/New_York");

Why is getTimestamp() affected by setTimezone()

I've been struggling quite a while with PHP's DateTime classes. My understanding is that a UNIX-timstamp is always in UTC, regardless of the timezone.
That's why I am quite confused with this code sample.
$date1 = new DateTime("#1351382400"); // Sun Oct 28 2012 02:00:00 GMT+2 (DST)
var_dump($date1->getTimestamp()); //prints: 1351382400
$date1->setTimezone(new DateTimeZone("Europe/Stockholm"););
var_dump($date1->getTimestamp()); //prints: 1351386000
As you can see setTimezone() changes the result of getTimestamp().
Is it expected that setTimezone() affects getTimestamp()?
The amount that you're off is 3600 seconds, or 1 hour.
I think that what you're seeing is because the date you picked is the end of Daylight Savings Time in Stockholm. If you use a different date, you don't get that effect:
$now = time();
echo " now: $now\n";
$date1 = new DateTime("#{$now}");
echo " date1 here: {$date1->getTimestamp()}\n";
$date1->setTimezone(new DateTimeZone("Europe/Stockholm"));
echo "date1 Stockholm: {$date1->getTimestamp()}\n";
Output:
now: 1352321491
date1 here: 1352321491
date1 Stockholm: 1352321491
I'm not sure if this is a bug or not, but it doesn't happen if you don't pick a date on which DST is changing.
Yes, unix timestamp is the current time as per the date object or your current machine time from Epoch.

PHP calculating time difference, result 1 hour wrong

I want to calculate difference of two times and print it in pretty way. I know the difference will never exceed 24 hours, so I tried the following
$time = 7200; //time difference in seconds
date("H:i:s", $time); // should print 02:00
The result is unexpected, it prints 03:00 instead.
What am I doing wrong?
Why not the DateTime::diff??
Here, check PHP.net for DateTime class
Grabbed from php.net datetime diff page:
$datetime1 = new DateTime('2009-10-11');
$datetime2 = new DateTime('2009-10-13');
$interval = $datetime1->diff($datetime2);
echo $interval->format('%R%a days');
//Outputs +2 days
The PHP date() function is meant for formatting absolute time stamps (as in "Friday, 20 July 2012, 15:02 UTC"), not for time differences (as in "3 hours and 5 minutes ago").
In some cases, it is possible to trick date() into producing something that looks like a correctly formatted time difference by setting your time zone to UTC and relying on the fact that the Unix epoch happens to fall on midnight in UTC. However, even then, this will only work for positive time differences of less than 24 hours.
Instead, the correct solution is to use a function designed for formatting time differences, or write one yourself. Here's a simple function to do so:
function format_time_difference ( $delta ) {
$sign = ( $delta < 0 ? "-" : "" );
$delta = abs( $delta );
return sprintf( "%s%02d:%02d:%02d", $sign, $delta / 3600,
($delta / 60) % 60, $delta % 60 );
}
Of course, you can extend this function to e.g. include days in the output if you like.
You should never use date to compute time difference. Firstly it is ugly hack. It was not intended for that purpose. And secondly, it works reliably only when timezone set to UTC.
Now, why it does not work:
PHP function date takes two arguments, format and timestamp, the latter is defined as number of seconds from 1st January 1970 00:00 UTC called unix epoch. So if you call date("H:i", 3600) and your timezone is set to UTC, it will return "01:00", cause it represents time one hour after unix epoch and the epoch was at the midnight.
The problem is, unix epoch was at the midnight only in UTC, not in the other timezones. And this is the source of the incorrect result.

Converting Date Format for Calculations in PHP

I'm displaying a due date for tasks in an m/d/y format. I'm displaying the day the task was posted in a "Posted X $name(s) ago" (eg. "Posted 6 day(s) ago").
I'm working on giving the timestamps (posted and due date) different CSS classes, depending on how many days there are from TODAY until the due date. (So the "Posted X" timestamp is less relevant, I just wanted to give a better picture.)
So far, I have the following down:
$cdate = $this->data['due'];
$today = time();
$dovi = date('m/d/Y', $today);
$difference = $cdate - $dovi;
$upcoming= floor($difference/60/60/24);
$cdate is pulling the due date from the DB, in m/d/y format. $today is telling us what today is (using the UNIX timestamp). $dovi is converting time() in to the m/d/y format. $difference is telling us the difference between today and the due date. $upcoming, in theory, should take that difference and dumb it down into a simple number.
I think that for the calculation to work, I would need to convert $cdate in to a UNIX timestamp or somehow convert both $today and $cdate into some other matching format other than m/d/y.
Does anyone have suggestions on the best way to make this work? I already have some code to run the CSS changes, the only thing I'm stuck on is this conversion/calculation issue to determine how many days from NOW (time()) the due date is. Thanks!!
Maybe something like this? Assuming $this->data['due'] is in m/d/Y format.
$this->data['due'] = '7/28/2012';
$diff = strtotime($this->data['due']) - strtotime(date('m/d/Y'));
var_dump(date('d',$diff)); // 3
You can pass any valid parameter to php's date function to have it formatted however you would like.
Its long one, but works...
$datetime = new DateTime("2012-07-22 02:03:50"); // your date in datetime type
$curr_stamp = time();
$act_stamp = mktime($datetime->format('H'), $datetime->format('i'), $datetime->format('s'), $datetime->format('n'), $datetime->format('j'), $datetime->format('Y'));
$diff=$curr_stamp-$act_stamp;
$day_diff = floor($diff / 86400);
if($day_diff < 7)
echo $day_diff." days ago";
Rather elegant and right solution (and also viable after the end of UNIX epoch).
$today = new DateTime(); // creating `today` DateTime object
$expiry = DateTime::createFromFormat('m/d/Y', $this->data['due']) // creating DateTime object from already formatted date
$difference = $today->diff($expiry); // 1st variant to calculate difference between dates
$difference = date_diff($today, $expiry); // and 2nd variant
echo $difference->format('Interval (difference) is %R% days');
Remember that UNIX epoch (timestamp) will "end" "soon" and code based on timestamps possibly will face some problems (maybe we will find the solution in future to avoid this, but ...), it is better to use DateTime class, bec. even to calculate number of years for those who are born before 1970 year can become kinda problem if you don't remember the date 1970.01.01 and trying to do it using timestamps (it is widespread database practice BTW :) ).
Never do it (timestamps) for very old dates and look to the future and DateTime will SaveOurSouls.

Categories