I'm working on some time related features and I opt to always use UTC times and store time stamps as integers for consistency.
However, I noticed that when I use mktime it seems that the currently set time zone has an influence of the return value of mktime. From the documentation I understand that mktime is supposed to return the number of seconds since epoch:
Returns the Unix timestamp corresponding to the arguments given. This
timestamp is a long integer containing the number of seconds between
the Unix Epoch (January 1 1970 00:00:00 GMT) and the time specified.
http://php.net/manual/en/function.mktime.php
However, it seems that mktime is including the time zone that is currently set. When using the following code:
date_default_timezone_set('UTC');
$time = mktime(0, 0, 0, 1, 1, 2016 );
echo "{$time}\n";
date_default_timezone_set('Australia/Sydney');
$time = mktime(0, 0, 0, 1, 1, 2016 );
echo "{$time}\n";
I would expect the two time vales to be same but apparently they are not:
1451606400
1451566800
Which seems to be exacly an 11 hour difference:
1451606400 - 1451566800 = 39600 / (60*60) = 11
What do I not understand correctly about mktime and/or why is the time zone taken into account when using mktime?
I can't tell you why it is the way it is (PHP has never made sense to me when it comes to date and time) but there is an alternative function gmmktime() which is
Identical to mktime() except the passed parameters represents a GMT date. gmmktime() internally uses mktime() so only times valid in derived local time can be used.
There is also a comment on the PHP documentation for this function which explains how mktime(), gmmktime() and time() work. Essentially, they assume that you always think in time zones even if a UNIX timestamp itself doesn't carry a timezone.
Resulting Unix timestamp are indeed encoded in a timezone agnostic way, but input arguments are interpreted relative to the timezone set for current process. And indeed, Sidneys 2016-01-01 00:00:00 (GMT+11) happened 11 hours before UTC 2016-01-01 00:00:00.
When some foreigner tells you a time, you have to know its time zone to correctly interpret it, and so does mktime().
If dates you want to pass to mktime() are UTC dates, then use gmmktime() which exists for that purpose.
I'm retrieving a unix timestamp from a DB and I want to check if this datetime has passed already.
I tried using an if statement to compare to time() but it always says the time has passed already.
What am I doing wrong?
EDIT: Just some more info..to determine am/pm I'm adding 12 to the hour if its PM before running it through mktime(). (Is this right?)
It's stored in the DB as int not as any datetime types.
Your PHP time could be affected by PHP's timezone. Use date_default_timezone_get() to find out what time zone you're in.
Make sure the timezones in the DB and PHP are the same, use NOW() function to fill the DB column with current timestamp (the column should be of datetime type), then you can get the timestamp using UNIX_TIMESTAMP() MySQL function which compares against PHP's time() just nice.
Alternatively, you can fill the DB column with something like
mysql_query("INSERT INTO your_table (your_date) VALUES (FROM_UNIXTIME(" . time() . "))")
That should work even with timezone discrepancies.
If you are using mktime to create a UNIX timestamp, PHP is using the timezone settings to interpret what you mean by the given parameters. It's possible that you should be using gmmktime. It depends on how the timestamps in the database are being created; I cannot say for sure without seeing more code and having a more detailed explanation.
I generally prefer to simply store all dates as DATETIME types in the UTC (GMT) timezone. It tends to be less confusing.
Just some more info..to determine am/pm I'm adding 12 to the hour if its PM before running it through mktime(). (Is this right?)
12 PM is hour 12.
1 PM is hour 13.
So you don't always add 12. (i.e., 12 Noon is the exception).
echo strtotime('2010-09-11 12:15:01');
Returns: 1284207301
SELECT UNIX_TIMESTAMP('2010-09-11 12:15:01')
Returns: 1284200101
Why the difference?
The answer is in the php.net article about strtotime:
This function will use the TZ environment variable (if available) to calculate the timestamp. Since PHP 5.1.0 there are easier ways to define the timezone that is used across all date/time functions. That process is explained in the date_default_timezone_get() function page.
Short version: strtotime uses your timezone by default, UNIX_TIMESTAMP does not.
This is commonly caused by setting PHP and MySQL to use different timezones, as well as discrepancies in the system time if they happen to be located in different servers.
UNIX timestamps are the number of seconds that have passed since the UNIX epoch on midnight January 1, 1970 UTC so timezone differences will give different timestamps.
1284207301-1284200101 = 7200 -> 2 hours. Such a perfectly rounded difference isn't due to a bug, it's a timezone diference. Most likely one's in UTC/GMT, and the other's UTC +/- 2
My guess is that Unix time is GMT and the other is your local timezone, there is exactly 2 hours difference (7200/60/60) between the 2 outputs
I have a date string, say '2008-09-11'. I want to get a timestamp out of this, but I need to specify a timezone dynamically (rather then PHP default).
So to recap, I have two strings:
$dateStr = '2008-09-11';
$timezone = 'Americas/New_York';
How do I get the timestamp for this?
EDIT: The time of day will be the midnight of that day.... $dateStr = '2008-09-11 00:00:00';
$date = new DateTime($dateStr, new DateTimeZone($timezone));
$timestamp = $date->format('U');
The accepted answer is great if you're running PHP > 5.2 (I think that's the version they added the DateTime class). If you want to support an older version, you don't want to type as much, or if you just prefer the functional approach there is another way which also does not modify global settings:
$dateStr = '2008-09-11 00:00:00';
$timezone = 'America/New_York';
$dtUtcDate = strtotime($dateStr. ' '. $timezone);
This will work if for some reason you're using <5.2 (Heaven forbid).
$reset = date_default_timezone_get();
date_default_timezone_set('America/New_York');
$stamp = strtotime($dateStr);
date_default_timezone_set($reset);
But anything 5.2 and above, I'd strongly recommend you opt for #salathe's answer.
If you're going to use Timezones, I propose you use the DateTime class, and in this case the DateTime::createFromFormat() function which will allow you to do something like this:
$start = "2015-01-14 11:59:43";
$timezone = "America/Montreal";
$tz = new DateTimeZone($timezone);
$dt = DateTime::createFromFormat('Y-m-d H:i:s', $start, $tz);
When you put $tz in the DateTime::createFromFormat function, you tell it what time zone the date you gave is in, so that when you need to convert it to another timezone, all you have to do is something like this:
$start = $dt->setTimeZone(new DateTimeZone('UTC'));
Whenever you are referring to an exact moment in time, persist the time according to a unified standard that is not affected by daylight savings. (GMT and UTC are equivalent with this regard, but it is preferred to use the term UTC. Notice that UTC is also known as Zulu or Z time.)
If instead you choose to persist a time using a local time value, include the local time offset from UTC, such that the timestamp can later be interpreted unambiguously.
In some cases, you may need to store both the UTC time and the equivalent local time. Often this is done with two separate fields, but some platforms support a datetimeoffset type that can store both in a single field.
When storing timestamps as a numeric value, use Unix time - which is the number of whole seconds since 1970-01-01T00:00:00Z (excluding leap seconds). If you require higher precision, use milliseconds instead. This value should always be based on UTC, without any time zone adjustment.
If you might later need to modify the timestamp, include the original time zone ID so you can determine if the offset may have changed from the original value recorded.
When scheduling future events, usually local time is preferred instead of UTC, as it is common for the offset to change. See answer, and blog post.
Remember that time zone offsets are not always an integer number of hours (for example, Indian Standard Time is UTC+05:30, and Nepal uses UTC+05:45).
If using Java, use java.time for Java 8, or use Joda Time for Java 7 or lower.
If using .NET, consider using Noda Time.
If using .NET without Noda Time, consider that DateTimeOffset is often a better choice than DateTime.
If using Perl, use DateTime.
If using Python, use pytz or dateutil.
If using JavaScript, use moment.js with the moment-timezone extension.
If using PHP > 5.2, use the native time zones conversions provided by DateTime, and DateTimeZone classes. Be careful when using.
DateTimeZone::listAbbreviations() - see answer. To keep PHP with up to date Olson data, install periodically the timezonedb PECL package; see answer.
If using C++, be sure to use a library that uses the properly implements the IANA timezone database. These include cctz, ICU, and Howard Hinnant's "tz" library.
Do not use Boost for time zone conversions. While its API claims to support standard IANA (aka "zoneinfo") identifiers, it crudely maps them to fixed offsets without considering the rich history of changes each zone may have had.
(Also, the file has fallen out of maintenance.)
Most business rules use civil time, rather than UTC or GMT. Therefore, plan to convert UTC timestamps to a local time zone before applying application logic.
Remember that time zones and offsets are not fixed and may change. For instance, historically US and UK used the same dates to 'spring forward' and 'fall back'.
However, in 2007 the US changed the dates that the clocks get changed on. This now means that for 48 weeks of the year the difference between London time and New York time is 5 hours and for 4 weeks (3 in the spring, 1 in the autumn) it is 4 hours. Be aware of items like this in any calculations that involve multiple zones.
Consider the type of time (actual event time, broadcast time, relative time, historical time, recurring time) what elements (timestamp, time zone offset and time zone name) you need to store for correct retrieval - see "Types of Time" in answer.
Keep your OS, database and application tzdata files in sync, between themselves and the rest of the world.
On servers, set hardware clocks and OS clocks to UTC rather than a local time zone.
Regardless of the previous bullet point, server-side code, including web sites, should never expect the local time zone of the server to be anything in particular. see answer.
Use NTP services on all servers.
If using FAT32, remember that timestamps are stored in local time, not UTC.
When dealing with recurring events (weekly TV show, for example), remember that the time changes with DST and will be different across time zones.
Always query date-time values as lower-bound inclusive, upper-bound exclusive (>=, <).
Laconic Answer (no need to change default timezone)
$dateStr = '2008-09-11';
$timezone = 'America/New_York';
$time = strtotime(
$dateStr,
// convert timezone to offset seconds
(new \DateTimeZone($timezone))->getOffset(new \DateTime) - (new \DateTimeZone(date_default_timezone_get()))->getOffset(new \DateTime) . ' seconds'
);
Loquacious Answer
Use strtotime's second option which changes the frame of reference of the function. By the way I prefer not to update the default time zone of the script:
http://php.net/manual/en/function.strtotime.php
int strtotime ( string $time [, int $now = time() ] )
The function
expects to be given a string containing an English date format and
will try to parse that format into a Unix timestamp (the number of
seconds since January 1 1970 00:00:00 UTC), relative to the timestamp
given in now, or the current time if now is not supplied.
And a Helper
/**
* Returns the timestamp of the provided time string using a specific timezone as the reference
*
* #param string $str
* #param string $timezone
* #return int number of the seconds
*/
function strtotimetz($str, $timezone)
{
return strtotime(
$str, strtotime(
// convert timezone to offset seconds
(new \DateTimeZone($timezone))->getOffset(new \DateTime) - (new \DateTimeZone(date_default_timezone_get()))->getOffset(new \DateTime) . ' seconds'
)
);
}
var_export(
date(
'Y-m-d',
strtotimetz('this monday', 'America/New_York')
)
);
Maybe not the most performant approach, but works well when you know the default timezone and the offset. For example if the default timezone is UTC and the offset is -8 hours:
var_dump(
date(
'Y-m-d',
strtotime('this tuesday', strtotime(-8 . ' hours'))
)
);
I need to format a UNIX timestamp in GMT format to a local date/time. I'm using gmstrftime to do this and I can get the correct result if I use an offset. I just so happen to know what my offset is for the pacific timezone but I don't want to have to get the correct time like this. I've used date_default_timezone_set so gmstrftime is reporting the correct timezone but the time is off by like a day.
I don't get it. If gmstrftime knows what timezone I'm in, why is the time off?
If you have the correct timezone set (such as with date_default_timezone_set) then you only need to use date() for the formatting, no extra coding. UNIX timestamps are in GMT by definition of a UNIX timestamp -- number of seconds since January 1, 1970 00:00:00 GMT.
gmstrftime will always return the time as UTC, seems like you want strftime.