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.
Related
When dealing with local DateTime values provided by a user, it's quite possible to have a time that is either invalid or ambiguous, due to Daylight Saving Time transitions.
In other languages and frameworks, there are often methods such as isAmbiguous and isValid, on some representation of the time zone. For example in .NET, there is TimeZoneInfo.IsAmbiguousTime and TimeZoneInfo.IsInvalidTime.
Plenty of other time zone implementations have similar methods, or functionality to address this concern. For example, in Python, the pytz library will throw an AmbiguousTimeError or InvalidTimeError exception that you can trap.
PHP has excellent time zone support, but I can't seem to find anything to address this. The closest thing I can find is DateTimeZone::getTransitions. That provides the raw data, so I can see that some methods could be written on top of this. But do they exist already somewhere? If not, can anyone provide a good implementation? I would expect them to work something like this:
$tz = new DateTimeZone('America/New_York');
echo $tz->isValidTime(new DateTime('2013-03-10 02:00:00')); # false
echo $tz->isAmbiguousTime(new DateTime('2013-11-03 01:00:00')); # true
I 'm not aware of any existing implementations and I haven't had cause to use advanced date/time features such as these as yet, so here is a clean room implementation.
To enable the syntax illustrated in the question we are going to extend DateTimeZone as follows:
class DateTimeZoneEx extends DateTimeZone
{
const MAX_DST_SHIFT = 7200; // let's be generous
// DateTime instead of DateTimeInterface for PHP < 5.5
public function isValidTime(DateTimeInterface $date);
public function isAmbiguousTime(DateTimeInterface $date);
}
To keep distracting details from cluttering the implementation, I am going to assume that the $date arguments have been created with the proper time zone; this is in contrast to the example code given in the question.
That is to say, the correct result will not be produced by this:
$tz = new DateTimeZoneEx('America/New_York');
echo $tz->isValidTime(new DateTime('2013-03-10 02:00:00'));
but instead by this:
$tz = new DateTimeZoneEx('America/New_York');
echo $tz->isValidTime(new DateTime('2013-03-10 02:00:00', $tz));
Of course since $tz is already known to the object as $this, it should be easy to extend the methods so that this requirement is removed. In any case, making the interface super user friendly is out of the scope of this answer; going forward I will focus on the technical details.
isValidTime
The idea here is to use getTransitions to see if there are any transitions around the date/time we are interested in. getTransitions will return an array with either one or two elements; the timezone situation for the "begin" timestamp will always be there, and another element will exist if a transition occurs shortly after it. The value of MAX_DST_SHIFT is small enough that there is no chance of getting a second transition/third element.
Let's see the code:
public function isValidTime(DateTime $date)
{
$ts = $date->getTimestamp();
$transitions = $this->getTransitions(
$ts - self::MAX_DST_SHIFT,
$ts + self::MAX_DST_SHIFT
);
if (count($transitions) == 1) {
// No DST changes around here, so obviously $date is valid
return true;
}
$shift = $transitions[1]['offset'] - $transitions[0]['offset'];
if ($shift < 0) {
// The clock moved backward, so obviously $date is valid
// (although it might be ambiguous)
return true;
}
$compare = new DateTime($date->format('Y-m-d H:i:s'), $this);
return $compare->modify("$shift seconds")->getTimestamp() != $ts;
}
The final point of the code depends on the fact that PHP's date functions calculate timestamps for invalid date/times as if wall clock time had not shifted. That is, the timestamps calculated for 2013-03-10 02:30:00 and 2013-03-10 03:30:00 will be identical on the New York timezone.
It's not difficult to see how to take advantage of this fact: create a new DateTime instance equal to the input $date, then shift it forward in wall clock time terms an amount equal to the DST shift in seconds (it is imperative that DST not be taken into account to make this adjustment). If the timestamp of the result (here the DST rules come into play) is equal to the timestamp of the input, then the input is an invalid date/time.
isAmbiguousTime
The implementation is quite similar to isValidTime, only a few details change:
public function isAmbiguousTime(DateTime $date)
{
$ts = $date->getTimestamp();
$transitions = $this->getTransitions(
$ts - self::MAX_DST_SHIFT,
$ts + self::MAX_DST_SHIFT);
if (count($transitions) == 1) {
return false;
}
$shift = $transitions[1]['offset'] - $transitions[0]['offset'];
if ($shift > 0) {
// The clock moved forward, so obviously $date is not ambiguous
// (although it might be invalid)
return false;
}
$shift = -$shift;
$compare = new DateTime($date->format('Y-m-d H:i:s'), $this);
return $compare->modify("$shift seconds")->getTimestamp() - $ts > $shift;
}
The final point depends on another implementation detail of PHP's date functions: when asked to produce the timestamp for an ambiguous date/time, PHP produces the timestamp of the first (in absolute time terms) occurrence. This means that the timestamps of the latest ambiguous time and the earliest non-ambiguous time for a given DST change will differ by an amount larger than the DST offset (specifically, the difference will be in the range [offset + 1, 2 * offset], where offset is an absolute value).
The implementation takes advantage of this by again doing a "wall clock shift" forward and checking the timestamp difference between the result and the input $date.
See the code in action.
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
When dealing with local DateTime values provided by a user, it's quite possible to have a time that is either invalid or ambiguous, due to Daylight Saving Time transitions.
In other languages and frameworks, there are often methods such as isAmbiguous and isValid, on some representation of the time zone. For example in .NET, there is TimeZoneInfo.IsAmbiguousTime and TimeZoneInfo.IsInvalidTime.
Plenty of other time zone implementations have similar methods, or functionality to address this concern. For example, in Python, the pytz library will throw an AmbiguousTimeError or InvalidTimeError exception that you can trap.
PHP has excellent time zone support, but I can't seem to find anything to address this. The closest thing I can find is DateTimeZone::getTransitions. That provides the raw data, so I can see that some methods could be written on top of this. But do they exist already somewhere? If not, can anyone provide a good implementation? I would expect them to work something like this:
$tz = new DateTimeZone('America/New_York');
echo $tz->isValidTime(new DateTime('2013-03-10 02:00:00')); # false
echo $tz->isAmbiguousTime(new DateTime('2013-11-03 01:00:00')); # true
I 'm not aware of any existing implementations and I haven't had cause to use advanced date/time features such as these as yet, so here is a clean room implementation.
To enable the syntax illustrated in the question we are going to extend DateTimeZone as follows:
class DateTimeZoneEx extends DateTimeZone
{
const MAX_DST_SHIFT = 7200; // let's be generous
// DateTime instead of DateTimeInterface for PHP < 5.5
public function isValidTime(DateTimeInterface $date);
public function isAmbiguousTime(DateTimeInterface $date);
}
To keep distracting details from cluttering the implementation, I am going to assume that the $date arguments have been created with the proper time zone; this is in contrast to the example code given in the question.
That is to say, the correct result will not be produced by this:
$tz = new DateTimeZoneEx('America/New_York');
echo $tz->isValidTime(new DateTime('2013-03-10 02:00:00'));
but instead by this:
$tz = new DateTimeZoneEx('America/New_York');
echo $tz->isValidTime(new DateTime('2013-03-10 02:00:00', $tz));
Of course since $tz is already known to the object as $this, it should be easy to extend the methods so that this requirement is removed. In any case, making the interface super user friendly is out of the scope of this answer; going forward I will focus on the technical details.
isValidTime
The idea here is to use getTransitions to see if there are any transitions around the date/time we are interested in. getTransitions will return an array with either one or two elements; the timezone situation for the "begin" timestamp will always be there, and another element will exist if a transition occurs shortly after it. The value of MAX_DST_SHIFT is small enough that there is no chance of getting a second transition/third element.
Let's see the code:
public function isValidTime(DateTime $date)
{
$ts = $date->getTimestamp();
$transitions = $this->getTransitions(
$ts - self::MAX_DST_SHIFT,
$ts + self::MAX_DST_SHIFT
);
if (count($transitions) == 1) {
// No DST changes around here, so obviously $date is valid
return true;
}
$shift = $transitions[1]['offset'] - $transitions[0]['offset'];
if ($shift < 0) {
// The clock moved backward, so obviously $date is valid
// (although it might be ambiguous)
return true;
}
$compare = new DateTime($date->format('Y-m-d H:i:s'), $this);
return $compare->modify("$shift seconds")->getTimestamp() != $ts;
}
The final point of the code depends on the fact that PHP's date functions calculate timestamps for invalid date/times as if wall clock time had not shifted. That is, the timestamps calculated for 2013-03-10 02:30:00 and 2013-03-10 03:30:00 will be identical on the New York timezone.
It's not difficult to see how to take advantage of this fact: create a new DateTime instance equal to the input $date, then shift it forward in wall clock time terms an amount equal to the DST shift in seconds (it is imperative that DST not be taken into account to make this adjustment). If the timestamp of the result (here the DST rules come into play) is equal to the timestamp of the input, then the input is an invalid date/time.
isAmbiguousTime
The implementation is quite similar to isValidTime, only a few details change:
public function isAmbiguousTime(DateTime $date)
{
$ts = $date->getTimestamp();
$transitions = $this->getTransitions(
$ts - self::MAX_DST_SHIFT,
$ts + self::MAX_DST_SHIFT);
if (count($transitions) == 1) {
return false;
}
$shift = $transitions[1]['offset'] - $transitions[0]['offset'];
if ($shift > 0) {
// The clock moved forward, so obviously $date is not ambiguous
// (although it might be invalid)
return false;
}
$shift = -$shift;
$compare = new DateTime($date->format('Y-m-d H:i:s'), $this);
return $compare->modify("$shift seconds")->getTimestamp() - $ts > $shift;
}
The final point depends on another implementation detail of PHP's date functions: when asked to produce the timestamp for an ambiguous date/time, PHP produces the timestamp of the first (in absolute time terms) occurrence. This means that the timestamps of the latest ambiguous time and the earliest non-ambiguous time for a given DST change will differ by an amount larger than the DST offset (specifically, the difference will be in the range [offset + 1, 2 * offset], where offset is an absolute value).
The implementation takes advantage of this by again doing a "wall clock shift" forward and checking the timestamp difference between the result and the input $date.
See the code in action.
I am working on project in which i am converting the time according to user location. i.e if user belongs to ALASKA then then all the information will be shown according to his countries timezone.I did some conversion but i am getting wrong time in case of user has a Daylight timezone (DST) which changes frequently.
Here is my code snippet.
date_default_timezone_set('UTC');
$offset=$_SESSION['gmtoffset'];
echo date('Y-m-d H:i:s',strtotime(date('Y-m-d H:i:s'))-(-$offset));
So in this case how do i maintain this timezone? what is the exact way for this time related conversions?
Any guidance and idea will be appreciated.
Thanks
Here's a simple solution
Javascript:
var timeZone = new Date().getTimezoneOffset();
document.cookie="tz=" + (timeZone/(-60));
Then PHP:
if(!empty($_COOKIE['tz']) && $_COOKIE['tz']>=-12 && $_COOKIE['tz']<=13){
$offset = $_COOKIE['tz'];
$tz = timezone_name_from_abbr(null, $offset * 3600, true);
if($tz === false) $tz = timezone_name_from_abbr(null, $offset * 3600, false);
date_default_timezone_set($tz);
}else{
date_default_timezone_set('UTC');
}
I am a bit uncertain to what in particular you are asking, but hopefully this will help.
If your question is regarding detecting that the user is in the Alaska time zone, then read:
How to detect user's timezone?
If your question is about picking a time zone by country, then read:
In PHP, how can I get Timezone from offset and country?
If you are wanting to understand Alaskan time zones, recognize that there are three different legal zones, and seven different IANA/Olson/TZ time zones (that PHP can use):
http://en.wikipedia.org/wiki/Time_in_Alaska
Don't try to treat a time zone as a numeric value. See "TimeZone != Offset" in the timezone tag wiki.
If you are wanting to manipulate dates with time zones in PHP, see the example in the PHP documentation for date_default_timezone_set.
If you just want to work with UTC, then there's no reason to do anything with time zones at all. The browser can always convert UTC to and from the user's local time zone, whatever that might be. Just create your JavaScript Date objects with a UTC timestamp integer, or parse from a UTC format, such as YYYY-MM-DDTHH:MM:SSZ.
To eliminate browser inconsistencies, you might want to use a library instead, such as moment.js:
// here's one way
var m = moment('2013-05-31T12:34:56Z');
// here's another
var m = moment.utc('2013-05-31 12:34:56','YYYY-MM-DD HH:mm:ss');
// pretty much anything else you might want to do...
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.