PHP DateTime::createFromFormat DATE_ATOM ISO 8601 - php

I am using PHP 7.2, and trying to create a date from a string as follows:
$dateString = '2018-12-31T01:01:01+00:00';
$converted = DateTime::createFromFormat(DATE_ATOM), $dateString);
The snippet above works fine and returns the expected result.
The problem happens when I swap the day and month in the date string provided above, as follows:
$dateString = '2018-31-12T01:01:01+00:00';
$converted = DateTime::createFromFormat(DATE_ATOM), $dateString);
I was expecting this second example to return false, but instead I get an actual date time, 2020-07-12 01:01:01.000000.
So, I have no way of telling whether the second date was a proper date or not, because the system accepted it, and I will be saving in my database something which I shouldn't.
Could this be considered a bug in PHP 7.2?

Could this be considered a bug in PHP 7.2?
No, this is how the method is intentionally implemented. You can argue if it's the right way to implement it, but that's how it is. Notably even if we all agree that it's not right here, it will still keep working as it's implemented.
The practical answer here is to write a validator (for entirety of format and all parts) and run any untrusted input through it.

Related

PHP DateTime::createFromFormat and multi languages

I'm using DateTime::createFromFormat() on a date that use some text like "April 15, 2016"
It's perfectly working as long as I'm using English culture.
April 15, 2016 -> ok
My code is set as WordPress plugin. Please understand that I have no control over the component that give me the date (as text) and the WordPress settings. If user set the WordPress installation on another language, the date will change from "April 15, 2016" to let's say (if French) "Avril 15, 2016".
It looks like that DateTime::createFromFormat() don't support other language than English so "April 15, 2016" will end up with:
"Fatal error: Call to a member function format() on boolean"
Did somebody have an idea how we can handle month date as text in several language? Using DateTime::createFromFormat() or another method in php?
Thanks
There are two ways to do this, but both depend on how exactly the input is generated ...
Maintain an array of month name translations
This is the simple/naive and most obvious way, I don't think the way to do this needs explaining.
However, depending on how WordPress (and/or the plugin that you're using) works, it may also be your only option.
The IntlDateFormatter class
This is the purely programmatic way and therefore what would be considered the "proper" one, but unfortunately this class comes as part of a PECL extension - intl - and isn't bundled with PHP.
It also requires that you know the language being used before parsing the date, but that shouldn't be a problem as that is how all localization solutions should work in the first place, so I assume this information is available to you in WordPress.
pickdate.js also seems to work with standard locales by default.
That being said, here's how it works:
// See http://userguide.icu-project.org/formatparse/datetime
$inputFormat = 'MMMM dd, yyyy';
$inputDate = 'Avril 15, 2016';
$locale = 'fr_FR';
// Here comes the magic ...
$dateFormatter = new IntlDateFormatter(
$locale,
IntlDateFormatter::LONG, // Not really important, may even be NONE
IntlDateFormatter::NONE, // Time ... we're not using it
NULL, // Will use date_default_timezone_get()
NULL, // Calendar; we don't need it
$inputFormat
);
// Will return bool(false) on failure, use getErrorMessage() for debugging
$unixTimestamp = $dateFormatter->parse($inputDate);
It's worth noting that IntlDateFormatter is designed mainly for creating localised outputs (and rightly so - you shouldn't be parsing a translated month name in the first place, or a name at all for that matter; numbers FTW), so we are indeed writing a lot of irrelevant stuff here.
For your use case, only the $locale and $inputFormat parameters matter.
There's one more caveat though - we need a timezone!
You can get it via (preferrably) $dateFormatter->getTimeZone() or in this case just date_default_timezone_get(), but you do need the timezone for two reasons:
You don't have a time value in your input, so IntlDateFormatter::parse() assumes "00:00:00".
UNIX timestamps are always in UTC, so IntlDateFormatter::parse() will take that into account and DateTime::createFromFormat('U', $unixTimestamp) will set the object's timezone to UTC!
If you're in another timezone, and more specifically in one on the east side of the world, this will happen:
$dateTime = DateTime::createFromFormat('U', $unixTimestamp);
var_dump($dateTime->format('Y-m-d')); // string(10) "2016-04-14" !!!
$dateTime->setTimeZone(new DateTimeZone($timezone));
var_dump($dateTime->format('Y-m-d')); // string(10) "2016-04-15"
You could tell IntlDateFormatter that you're in UTC in the first place, but that's technically cheating (wink) and depending on what you're using $dateTime for later, it may cause side-effects.

strtotime returns different results on different PHP versions

I came across a weird problem with strtotime function returning different (and I think incorrect) results.
This is my test code and results:
$fromTime = strtotime('2013-10-22');
$toTime = strtotime('2013-10-28');
$days = ($toTime - $fromTime) / (3600 * 24);
var_dump($days, $fromTime, $toTime);
// PHP 5.2.5 on codepad.org - correct
// int(6)
// int(1382400000)
// int(1382918400)
// PHP 5.3.15, PHP 5.4.6 - incorrect I guess
// float(6.0416666666667)
// int(1382392800)
// int(1382914800)
You can see it live on http://codepad.org/gdTqFLqG.
Do you have any idea why is this happening?
I agree with #N.B.'s suggestion to use the DateTime class instead -- You shouldn't really be using strtotime() for this kind of thing today.
However, as for why it's happening, look at the dates you're comparing.... what often happens between those dates? Yep, that's right -- daylight savings.
So my guess is that it's got nothing to do with the PHP version, and more to do with the timezone setup on the different platforms you're testing. One is set to use the UCT and the other is set to use a local timezone that has DST.
You should use date_default_timezone_set(), cause in second test you got correct time with some UTC+- correction.

PHP date time to Unix time Stamp

How can I convert string "M-d-Y::H:i:s" to unix timestamp in php. I just realized manipulating date and time must be the most time consuming entity in any programming language.
Trivial Question: Why do you think there isn't any universal date time format. Why are there so many variations of same data? AArrhh.
The OOP way (requires PHP >= 5.3.0):
$dt = DateTime::createFromFormat("M-d-Y::H:i:s", $input);
$ts = $dt->getTimestamp();
You should of course check the return value of createFromFormat (it's false if an error occurs) but you should also definitely check DateTime::getLastErrors(); otherwise you might be surprised if e.g. your input has a day of "Jan 32". See my answer here for more info.
i have this issue service unavailable (with message) ResponseText: Error: Call to a member function getTimestamp() on boolean How to fixed this issue.

strftime() suddenly stopped working. Any insights?

I have a simple script which takes an input date, formats it, and stores it in the database. I was using the strftime function in the following way:
$pdate = strftime('Y-m-d', strtotime($_POST['post_date']));
For some reason, this suddenly started returning 'Y-m-d'. Yes, it was returning the format string I was passing it as the first argument! No date information at all. I also tried doing this by passing it a straight-up unicode timestamp as the second argument, but it still just returned the format string. It was working fine until a few days ago.
Now I've switched it to use the date() function instead:
$pdate = date('Y-m-d', strtotime($_POST['post_date']));
Everything works fine now! I'm just wondering if anyone has any ideas why the strftime() function suddenly stopped working. It seems really strange and it's going to bug me all day.
Actually the question might be opposite :) I don't know how did it return correctly formated date, because generally it should be like this:
$pdate = strftime('%Y-%m-%d', strtotime($_POST['post_date']));
As it's described here :)
You are using wrong Date format in strftime parameter. Check strftime function. It should be like this:
$pdate = strftime('%Y-%m-%d', strtotime($_POST['post_date']));
and there are many other formats as well.

PHP : Better date parser than strtotime

I'm trying to parse a string in a specific format and I'm really surprised to discover that I can't find a good function to do that.
The only one I found is strtotime and it doesn't fit as it guesses the date format. I really don't trust the "guess" part.
Moreover my string input is in a french format (dd/mm/aaaa) which it seems that it's not well understood (it parses american formats like mm/dd/aaaa).
What I'm looking for is a function that take in input a date string and a format to parse.
I could do it myself with a regexp but I can't believe that it doesn't already exist.
I found :
DateTime::createFromFormat(). But it only work with PHP 5.3 and I don't have the power to upgrade the PHP version (5.2)
strptime(). This method does what I want but is not implemented on windows platform (by the way: WTF ??)
Any suggestion ?
Unfortunately, it seems that such parsing is better done manually, by exploding the string at slashes and then switching day and month.
Check out Zend_Date, which lets you specify the format when you set a date. As well as including constants for many common formats, you can specify your own too.
$date = new Zend_Date();
$date->set('27/08/2009','DD/MM/YYYY');
The following comment from php.net on strtotime may help:
Fails for non-US dates where the
ordering is uncertain, such as
01/02/2003 - parses this as Feb 1st,
rather than Jan 2nd.
If you are parsing dates for a non-US
locale, you can flip these elements of
your date:
<?php
$y = $_POST['date'];
if (preg_match('/^\s*(\d\d?)[^\w](\d\d?)[^\w](\d{1,4}\s*$)/', $y, $match)) {
$y = $match[2] . '/' . $match[1] . '/' . $match[3];
}
echo date('d # m # Y', strtotime($y));
?>
WARNING: Above only works for dates,
and breaks for times: 12:30:01 will be
converted to 30/12/01.
I've written a class myself, I think you'll find an ok version in gadmdatecommand.php in http://sourceforge.net/projects/phpdbedittk
Regarding the comments here to just explode by '/' and swap the number, its not quite that simple. If you offer to enter dates into an input box, you may get - depending on the locality of the user and the application
1/7/2010
1.7.2010
1-7-2010
15 Jul
1 Jul 2010
1/6/8
and many more variations. I've solved this problem (at least for me successfully) by creating dateformats, each of which have
a) a regex that matches the format
b) an array mapper that matches regex brackets into date pieces (day, month, minute, am/pm)
c) an output format for date()
HTH
If you know your date format input will be English-formatted, then you can process it into a more standard date format. A simple parsing of 24/7/2007 to 2007-07-24 is trivial. Explode with the forward slash and put the parts in the right spot. I know for a fact that strtotime will parse 2007-07-24 correctly.
strptime():
Internally, this function calls the strptime() function provided by the system's C library. This function can exhibit (!) noticeably different behaviour across different operating systems. The use of date_parse_from_format(), which does not suffer from these issues, is recommended on PHP 5.3.0 and later.
http://www.php.net/manual/en/function.strptime.php

Categories