Is this correct?
Is there some built-in PHP way of doing this or a library I should be using instead of this custom function?
/**
* Supports the 4 Generalized Time Zones shown here: https://www.timeanddate.com/time/zone/usa of the contiguous U.S. (https://en.wikipedia.org/w/index.php?title=Time_in_the_United_States&oldid=885295732#Zones_used_in_the_contiguous_U.S.)
* #see https://en.wikipedia.org/wiki/Tz_database
*
* #param string $ianatz
* #return string
* #throws \Exception
*/
public static function getUsaGeneralizedTimeZoneAbbrev($ianatz) {
if ($ianatz == 'America/New_York') {
return 'ET';
} else if ($ianatz == 'America/Chicago') {
return 'CT';
} else if ($ianatz == 'America/Phoenix') {
return 'MT';
} else if ($ianatz == 'America/Los_Angeles') {
return 'PT';
} else {
throw new \Exception('Please use one of the 4 supported time zones of the contiguous USA.');
}
}
Note: I am NOT interested in EDT vs EST. I want the generalized version (ET) so that the change in season doesn't make the label incorrect (e.g. for a form input that could accept a date of any season).
You can get Timezone Abbreviation.
$dateTime = new DateTime();
$dateTime->setTimeZone(new DateTimeZone('America/Chicago'));
$dateTime->format('T'); // CST
But I would suggest using UTC in forms which do not have DST.
Wikipedia UTC:
UTC does not change with a change of seasons, but local time or civil time may change if a time zone jurisdiction observes daylight saving time or summer time. For example, UTC is 5 hours ahead of local time on the east coast of the United States during winter, but 4 hours ahead during summer.
Also, I would suggest reading a great article by Zach Holman UTC is enough for everyone right?
Related
I'm trying to do a comparison between date & times in Carbon PHP 2. For context, my server is in Europe/London timezone, and a user has the functionality to set their own timezone, thus my $timezone variable. My Laravel 8 project default timezone config is Europe/London too.
When a user provides a start time, I store the date & time as a date field in my DB, but obviously the day, month and year would always be wrong at the point my code runs, thus why we override these with the current day.
Still though, you can see from my output that their time is greater than the start time, but my if statement never runs, why?
$timezone = 'Asia/Tokyo';
$startTime = Carbon::parse('2022-08-01 05:00:00');
$theirTime = Carbon::parse(Carbon::now())->setTimezone($timezone);
$ourTime = Carbon::parse($theirTime)->setTimezone('Europe/London');
$startTime = $startTime->day($theirTime->day);
$startTime = $startTime->month($theirTime->month);
$startTime = $startTime->year($theirTime->year);
echo "their time: $theirTime ----- start: $startTime";
if ($theirTime >= $startTime) {
echo 'run now';
} else {
echo 'do not run';
}
output is:
their time: 2022-08-05 05:16:27 ----- start: 2022-08-05 05:00:00do not run
05:16:27 is greater than 05:00:00 so should output run now, what am I missing?
You can use carbon gte() method
if ($theirTime->gte($startTime)) {
echo 'run now';
} else {
echo 'do not run';
}
as for why it says "do not run", it is because "2022-08-05 05:16:27 GMT+1" comes before "2022-08-05 05:00:00 GMT" and carbon carbon converts itself to integer (unix timestamp) in the comparisation.
I cant give you a complete example because you did not define what $timezone is in your question.
If you want to compare the times as strings directly (and have total faith in your control of timezones you can
if ($theirTime->format('H:i:s') >= $startTime->format('H:i:s')) {
echo 'run now';
} else {
echo 'do not run';
}
Ideally you would run the server in UTC. When the user enters Wake me up at 6am you need to know 6am in what timezone, so need to store the timezone with the 6am or with that user's profile - whatever makes more sense in your application. But it would be a problem to search the database for each user's wakeup time in their own timezone so for activities like this, convert the time to UTC before storing it.
Then if the user wanted waking at 6am, this might be 18:00 utc but that would not matter. When the 'wakeup' time is the same as the server's current time, wake the user up and tell them "this is your $wakeup)->tz($user->timezone) wakeup".
Regarding your specific situation, you want to know if now() in Tokyo is greater than the time on the DB record, however you can only look at the H:m in the stored value;
$theirTime = Carbon\Carbon::parse('2022-03-01 05:00'); // get this from DB
// our reference point
$current = now()->tz('Asia/Tokyo');
$target = now()->tz('Asia/Tokyo');
$target->hour = $theirTime->hour;
$target->minute = $theirTime->minute;
$target->second = 0;
if($current->gte($target)) {
echo 'overdue';
} else {
echo 'Not due';
}
I have a given time and i need to create another time base on another given time. Let suppose i have given 4:00:00 AM, and another time is 2:00:00 , my result should be 6:00:00 AM, and 2:00:00 AM(based on condition).
this is what i am using but its not giving correect result.
if($data['turn_on_before_or_after'] == 'before'){
$time = strtotime($data['sunset']) - strtotime($data['variation_turn_on']);
$dataNew['final_turn_on'] = date('h:m:s',$time);
}
if($data['turn_on_before_or_after'] == 'after'){
$time = strtotime($data['sunset']) + strtotime($data['variation_turn_on']);
$dataNew['final_turn_on'] = date('h:m:s',$time);
}
Recommendation: use strtotime(). It will takes the date/time/datetime string and convert it to an integer; starting at the unix epoch. So 2AM would be 7200 and 4AM would be 14400; add those integers together and use date('H', $result) would convert the integer back into a time string. Boosh, win!
Opinion: many people will say unix timestamp is hard to use 'cause it is not human readable;I'd rather my logic be easy to read than the output. As the output only happens at the end of processing.
I recreated your scenario, but instead of using strtotime I used the DateTime object.
Your main problem is that your first date ($data['sunset']) must be considered as a real date, but your second date ($data['variation_turn_on']) must be considered as an interval. Because of this, and after looking at the DateInterval object constructor, you notice that you can create an interval using sscanf from your initial string. After creating that interval, all you have to do is to use the methods from the DateTime class to simply add or substract intervals from a specific date.
Here is the code I wrote to obtain the results you expect (6:00:00 AM and 2:00:00 AM) :
<?php
/* Initial parameters */
$data['turn_on_before_or_after'] = "before";
$data['sunset'] = "4:00:00 AM";
$data['variation_turn_on'] = "2:00:00";
/* Creating a list with your variation values */
list($hours, $minutes, $seconds) = sscanf($data['variation_turn_on'], '%d:%d:%d');
/* Creating the interval (here is the magic) */
$intervale = new DateInterval(sprintf('PT%dH%dM%dS', $hours, $minutes, $seconds));
/* Creating a DateTime object from your sunset time */
$date = new DateTime($data['sunset']);
/* Ternary for simplification, substract if before, add if everything else, you may use an if statement here */
$data['turn_on_before_or_after'] == 'before' ? $date->sub($intervale) : $date->add($intervale);
/* Printing the result */
echo $date->format('h:i:s A');
Alright so basically, I am a little confused about how the timestamp works in DateTime in PHP. I wish to make two methods that convert from a local time to UTC and vice versa.
I currently have this:
/**
* #param \IPS\DateTime $utcDateTime The UTC datetime.
* #param \DateTimeZone $timezone The timezone to convert the UTC time to.
* #return \IPS\DateTime New datetime object in local datetime.
* #throws \Exception when the UTC date is not in UTC format. (debugging purposes)
*/
public static function utcToLocal($utcDateTime, $timezone)
{
if ($utcDateTime->getTimezone()->getName() !== "UTC") {
throw new \Exception("Date time is not UTC!");
}
$time = new DateTime($utcDateTime, new \DateTimeZone("UTC"));
$time->setTimezone($timezone);
return $time;
}
/**
* #param \IPS\DateTime $localDateTime A datetime configured with the the user's timezone
* #return DateTime New datetime object in UTC format
* #throws \Exception When given datetime is already in UTC (for debugging purposes)
*/
public static function localToUtc($localDateTime)
{
if ($localDateTime->getTimezone()->getName() === "UTC") {
throw new \Exception("Value is already UTC");
}
$time = new DateTime($localDateTime, $localDateTime->getTimezone());
$time->setTimezone(new \DateTimeZone("UTC"));
return $time;
}
When I debug this code, at the last line return $time in localToUtc(...) my debugger shows the correct conversions:
However, when I evaluate the expression
$localDateTime->getTimestamp() === $time->getTimestamp()
it will return true.
So I am a little confused, I just want the timestamps to change when I change the timezone. I am thinking maybe I need to work with getOffset() but I want to make sure I do it in the correct way. I'd also prefer not to use any string format tricks because I feel like that is not the correct way.
I want to list for the user, all timezones with their native UTC/GMT offset, regardless of DST
How can I do it?
I 've come up with this function to do the job:
function standard_tz_offset($timezone) {
$now = new DateTime('now', $timezone);
$year = $now->format('Y');
$startOfYear = new DateTime('1/1/'.$year, $timezone);
$startOfNext = new DateTime('1/1/'.($year + 1), $timezone);
$transitions = $timezone->getTransitions($startOfYear->getTimestamp(),
$startOfNext->getTimestamp());
foreach($transitions as $transition) {
if(!$transition['isdst']) {
return $transition['offset'];
}
}
return false;
}
How it works
The function accepts a timezone and creates two DateTime objects: January 1st 00:00 of the current year and January 1st 00:00 of the next year, both specified in that timezone.
It then calculates the DST transitions during this year, and returns the offset for the first transition it finds where DST is not active.
PHP 5.3 is required because of the call to DateTimeZone::getTransitions with three parameters. If you want this to work in earlier versions you will have to accept a performance hit, because a whole lot of transitions will be generated by PHP (in this case, you don't need to bother with creating the $startOfYear and $startOfNext dates).
I have also tested this with timezones that do not observe DST (e.g. Asia/Calcutta) and it works for those as well.
To test it:
$timezone = new DateTimeZone("Europe/Athens");
echo standard_tz_offset($timezone);
In PHP, you can tell if a given date is during the Daylight Savings Time period by using something like this:
$isDST = date("I", $myDate); // 1 or 0
The problem is that this only tells you whether that one point in time is in DST. Is there a reliable way to check whether DST is in effect at any time in that timezone?
Edit to clarify:
Brisbane, Australia does not observe daylight savings at any time of the year. All year around, it is GMT+10.
Sydney, Australia does, from October to March when it changes from GMT+10 to GMT+11.
I'm wondering if there would be some existing method, or a way to implement a method which works as such:
timezoneDoesDST('Australia/Brisbane'); // false
timezoneDoesDST('Australia/Sydney'); // true
I've found a method which works using PHP's DateTimezone class (PHP 5.2+)
function timezoneDoesDST($tzId) {
$tz = new DateTimeZone($tzId);
$trans = $tz->getTransitions();
return ((count($trans) && $trans[count($trans) - 1]['ts'] > time()));
}
or, if you're running PHP 5.3+
function timezoneDoesDST($tzId) {
$tz = new DateTimeZone($tzId);
return count($tz->getTransitions(time())) > 0;
}
The getTransitions() function gives you information about each time the offset changes for a timezone. This includes historical data (Brisbane had daylight savings in 1916.. who knew?), so this function checks if there's an offset change in the future or not.
Actually nickf method didn't works for me so I reworked it a little ...
/**
* Finds wherever a TZ is experimenting dst or not
* #author hertzel Armengol <emudojo # gmail.com>
* #params string TimeZone -> US/Pacific for example
*
*/
function timezoneExhibitsDST($tzId) {
$tz = new DateTimeZone($tzId);
$date = new DateTime("now",$tz);
$trans = $tz->getTransitions();
foreach ($trans as $k => $t)
if ($t["ts"] > $date->format('U')) {
return $trans[$k-1]['isdst'];
}
}
// Usage
var_dump(timezoneExhibitsDST("US/Pacific")); --> prints false
var_dump(timezoneExhibitsDST("Europe/London")); --> prints false
var_dump(timezoneExhibitsDST("America/Chicago")); --> prints false
same function call will return true in 1 month (March) hope it helps
DateTimeZone::getTransitions might help.
You could probably wing it:
$hasDst = date("I", strtotime('June 1')) !== date("I", strtotime('Jan 1'));
Otherwise you'd need to parse the text-based zoneinfo data files.
I don't think so, but since almost every country that observes DST changes its time for an entire season or two, you could try to test 4 points during any given year.
For example, test date("I", $date) for 2009/01/01, 2009/04/01, 2009/07/01 and 2009/10/01. If that timezone falls into DST, then at least one of those dates will return 1.
date has to be on the user/server timezone for it to work, and you can't use a range with date as you do with getTransitions