PHP DateTime::createFromFormat and multi languages - php

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.

Related

PHP DateTime::createFromFormat DATE_ATOM ISO 8601

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.

PHP: How to get timezone value (ex: Eastern Standard Time) from timezone name (ex: America/New_York)?

Is there a PHP function anywhere which converts between the timezone name (such as those found here: http://php.net/manual/en/timezones.america.php) and the "value" such as Eastern Standard Time, or Pacific Daylight Time?
Not looking to convert between zones, just get the EST, PDT, etc. names given the America/New_York (or other) name. The only similar question I found is for a different language.
If you you install the PHP Internationalization Package, you can do the following:
IntlTimeZone::createTimeZone('America/New_York')->getDisplayName()
This will return the CLDR English standard-long form by default, which is "Eastern Standard Time" in this case. You can find the other options available here. For example:
IntlTimeZone::createTimeZone('Europe/Paris')->getDisplayName(true, IntlTimeZone::DISPLAY_LONG, 'fr_FR')
The above will return "heure avancée d’Europe centrale" which is French for Central European Summer Time.
Be careful to pass the first parameter as true if DST is in effect for the date and time in question, or false otherwise. This is illustrated by the following technique:
$tz = 'America/New_York';
$dt = new DateTime('2016-01-01 00:00:00', new DateTimeZone($tz));
$dst = $dt->format('I');
$text = IntlTimeZone::createTimeZone($tz)->getDisplayName($dst);
echo($text); // "Eastern Standard Time"
Working PHP Fiddle Here
Please note that these strings are intended for display to an end user. If your intent is to use them for some programmatically purpose, such as calling into another API, then they are not appropriate - even if the English versions of some of the strings happen to align. For example, if you are sending the time zone to a Windows or .NET API, or to a Ruby on Rails API, these strings will not work.
If you know the value from your list at (http://php.net/manual/en/timezones.america.php) you can do something like.
<?php
$dateTime = new DateTime();
$dateTime->setTimeZone(new DateTimeZone('America/New_York'));
echo $dateTime->format('T');
?>

Generate appropriate pattern for IntlDateFormatter()->format() based on locale in PHP

I'm trying to rely on IntlDateFormatter to return the current date in a locale-based format. This means DD/MM/YYYY for it_IT, de_DE, fr_FR... or MM/DD/YYYY for en_US and so on for all the possible locales.
I'm trying to follow the same solution I used to retrieve the current month's name:
$formatter = new IntlDateFormatter("it_IT", IntlDateFormatter::FULL, IntlDateFormatter::FULL);
$formatter->setPattern("MMMM");
return $formatter->format($this);
This code correctly returns "January", "gennaio" etc.
The problem is, I cannot figure out the right pattern to get the date in the current locale format. The ICU User Guide mentions the DateTimePatternGenerator class, and this looks like it! But I cannot find it in PHP.
I would like to avoid a custom, huge switch-case and rely on built-in function instead.
It must be pure PHP.
Any help?
I'm not sure if you want to get pattern from ICU itself, or just format your DateTime. Here you have both solutions.
Basics:
You're doing well with you actual code for retrieving month name. IntlDateFormatter is pretty customizable, and it all depend on options you gave it.
Getting date with year, month and day depending on locale:
$locale = "en_GB";
$dateType = IntlDateFormatter::SHORT;//type of date formatting
$timeType = IntlDateFormatter::NONE;//type of time formatting setting to none, will give you date itself
$formatter =new IntlDateFormatter($locale, $dateType, $timeType);
$dateTime = new DateTime("2015-02-28");
echo $formatter->format($dateTime);
will give you
28/02/2015
You can play with other options of IntlDateFormatter. There's SHORT, MEDIUM, LONG, FULL and NONE - you can read about them and check example ouput here.
Getting pattern from formatter
$formatter->->getPattern();
will give you something like
dd/MM/yyyy
I hope this will help you. I've been struggling with it for a long time to do it properly :)

PHP date format with intl-aware day suffix?

Sorry if this is a dupe - lots of similar questions but obviously if I could find an exact answer I wouldn't be asking :)
Note I'm coming from .Net and am a PHP newbie, so there may be noob-scale errors.
I would like to be able to output e.g. new DateTime('2014-01-01 13:15:00') as:
'Wednesday the 1st of January 2014 at 1:15PM' (possible - non-localized) or 'Mercredi 1er Janvier 2014 à 13h15' (not possible?).
Basically, there seems to be no ISO formatting equivalent to PHP's 'S' date format specifier, nor is there one for strftime?
The IntlDateFormatter::FULL comes close - but 'Wednesday, 1 January' or 'mercredi 1 janvier' is not good English (or French) - but it seems to be the closest that I can get? I could live without the 'on', 'the' and 'at' if I had to, but ordinal suffixes would be nice. ('Wednesday one January' - what's that, the beginning to a poem?)
I did see one example on the strftime section comments on PHP.net addressing this issue (which seems to suggest that it is an issue) - however it only seemed to add the English suffixes, which didn't seem much use? I'd like a simple method that takes a UTC datetime, a locale and a timezone and outputs a localized string - preferably in 'proper' human-readable format (as above) as is possible in English. I'd like to achieve this without writing a format string for every language in the world. It would also be nice if it worked on my Windows dev box as well as the *nix production box.
<?php
$utcdate = new DateTime('2014-01-01 13:15:00', new DateTimeZone('UTC'));
echo $utcdate->format('l \t\h\e jS \o\f F Y \a\t g:ia') . "<br>";
function dumpDates($date, $locale, $tz){
$date->setTimeZone(new DateTimeZone($tz));
$fmt = new IntlDateFormatter( $locale, IntlDateFormatter::FULL, IntlDateFormatter::FULL,
$tz, IntlDateFormatter::GREGORIAN );
echo $fmt->format($date) . "<br>";
// doesn't work under windows?
setLocale(LC_TIME, $locale);
echo strftime('%A, %#d %B %Y %I:%M:%S %p', $date->getTimeStamp()) . "<br>";
}
dumpDates($utcdate, 'en_GB', 'Europe/London');
dumpDates($utcdate, 'de_DE', 'Europe/Berlin');
dumpDates($utcdate, 'fr_FR', 'Europe/Paris');
?>
The full part of this question - including full grammatical legibility - would be very difficult to do without either, as you say, writing a format string for every language in the world, or finding a library that contains such strings. MomentJs seems to provide great intl support, but after a cursory search, I haven't been able to find a PHP equivalent, other than the intl extension.
You could get to the stage of providing an internationalised form including ordinal-based number by using a combination of IntlDateFormatter and NumberFormatter, by first using NumberFormatter to get the pattern for the date's ordinal suffix/prefix:
$numFmt = new NumberFormatter('fr_FR', NumberFormatter::ORDINAL);
$ordinalDay = $numFmt->format($date->format('j'));
You could then create a IntlDateFormatter that allows you to retrieve the pattern for the Full date format of the target language:
$dateFormat = new IntlDateFormatter('fr_FR', IntlDateFormatter::FULL, IntlDateFormatter::FULL, $tz, IntlDateFormatter::GREGORIAN);
$datePattern = $dateFormat->getPattern();
Finally, you would need to replace the section in the $datePattern representing the day with the escaped ordinal day pattern:
$datePattern = preg_replace('/d+', "'"+$ordinalDay+"'", $datePattern);
$dateFormat->setPattern($datePattern);
$outputDate = $dateFormat->format($date);
Note that the pattern used by IntlDateFormatter is different from the usual PHP date formatting format codes, here is the documentation for the codes recognised.
A warning; in internationalised formats that are fairly rigidly standardized, an ordinal number would look out of place. For example in chinese, the format is:
y年M月d日EEEE
and inserting the ordinal prefix that exists for written Chinese before the day value may look odd to a Chinese reader.

PHP: strtotime formatting

I am trying to put a readable time and date as part of a file name (in php). I am having all kinds of trouble with this and was hoping someone could help. I have tried several different recommendations that I have read around the internet (plus I read the manual) but I really haven't gotten anything to work right. Right now I have this:
$Time=strtotime("now");
$date=DateTime::createFromFormat('m/d/Y H:i:s', '7/24/2012 14:40:30');
$date_readable=$date->$Timestamp();
At that point I then add $date_readable to a file name. It compiles and runs but it doesn't format the date at all. It still gives it as a timestamp.
Any suggestions on how to make this work?
you can do it with simple date function for example
$time = strtotime("now");
$formatDate = date('F jS, Y h:i:s A', $time);
echo $formatDate;
this will print something like
July 25th, 2012 1:02:29 am
DateTime class is more powerful then using simple date function, as DateTime class offers powerful API's plus it is object oriented. however for simple date conversions i would stick to php's date function. as that could do my purpose.
for more formatting option have a look at this link http://www.php.net/manual/en/function.date.php#refsect1-function.date-parameters

Categories