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.

Read More

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

Related posts

1 comment

  1. There are two ways to do this, but both depend on how exactly the input is generated …

    1. 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.

    1. 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:

    1. You don’t have a time value in your input, so IntlDateFormatter::parse() assumes “00:00:00”.
    2. 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.

Comments are closed.