The formatting of date and time varies region to region. As a classic example, consider the year 2016, month April, day 15 and a time in the evening. The format preferred by denizens of the United States would be 7:23 PM, 4/15/2016, whereas in China you would most likely see 2016-04-15 19:23. As mentioned with number and currency formatting, it would also be important to display (and parse) dates in a format acceptable to your web visitors.
Application\I18n\Locale, adding statements to use date formatting classes:use IntlCalendar; use IntlDateFormatter;
IntlDateFormatter instance, as well as a series of predefined constants:const DATE_TYPE_FULL = IntlDateFormatter::FULL; const DATE_TYPE_LONG = IntlDateFormatter::LONG; const DATE_TYPE_MEDIUM = IntlDateFormatter::MEDIUM; const DATE_TYPE_SHORT = IntlDateFormatter::SHORT; const ERROR_UNABLE_TO_PARSE = 'ERROR: Unable to parse'; const ERROR_UNABLE_TO_FORMAT = 'ERROR: Unable to format date'; const ERROR_ARGS_STRING_ARRAY = 'ERROR: Date must be string YYYY-mm-dd HH:ii:ss or array(y,m,d,h,i,s)'; const ERROR_CREATE_INTL_DATE_FMT = 'ERROR: Unable to create international date formatter'; protected $dateFormatter;
getDateFormatter(), which returns an IntlDateFormatter instance. The value of $type matches one of the DATE_TYPE_* constants defined previously:public function getDateFormatter($type)
{
switch ($type) {
case self::DATE_TYPE_SHORT :
$formatter = new IntlDateFormatter($this->getLocaleCode(),
IntlDateFormatter::SHORT, IntlDateFormatter::SHORT);
break;
case self::DATE_TYPE_MEDIUM :
$formatter = new IntlDateFormatter($this->getLocaleCode(), IntlDateFormatter::MEDIUM, IntlDateFormatter::MEDIUM);
break;
case self::DATE_TYPE_LONG :
$formatter = new IntlDateFormatter($this->getLocaleCode(), IntlDateFormatter::LONG, IntlDateFormatter::LONG);
break;
case self::DATE_TYPE_FULL :
$formatter = new IntlDateFormatter($this->getLocaleCode(), IntlDateFormatter::FULL, IntlDateFormatter::FULL);
break;
default :
throw new InvalidArgumentException(self::ERROR_CREATE_INTL_DATE_FMT);
}
$this->dateFormatter = $formatter;
return $this->dateFormatter;
}$date is a bit tricky. It cannot be locale-specific, otherwise we will need to parse it according to locale rules, with unpredictable results. A better strategy would be to accept an array of values that represent year, month, day, and so on as integers. As a fallback, we will accept a string but only in this format: YYYY-mm-dd HH:ii:ss. Time zone is optional, and can be set separately. First we initialize variables:public function formatDate($date, $type, $timeZone = NULL)
{
$result = NULL;
$year = date('Y');
$month = date('m');
$day = date('d');
$hour = 0;
$minutes = 0;
$seconds = 0;if (is_string($date)) {
list($dateParts, $timeParts) = explode(' ', $date);
list($year,$month,$day) = explode('-',$dateParts);
list($hour,$minutes,$seconds) = explode(':',$timeParts);
} elseif (is_array($date)) {
list($year,$month,$day,$hour,$minutes,$seconds) = $date;
} else {
throw new InvalidArgumentException(self::ERROR_ARGS_STRING_ARRAY);
}IntlCalendar instance, which will serve as an argument when running format(). We set the date using the discreet integer values:$intlDate = IntlCalendar::createInstance($timeZone, $this->getLocaleCode()); $intlDate->set($year,$month,$day,$hour,$minutes,$seconds);
$formatter = $this->getDateFormatter($type);
if ($timeZone) {
$formatter->setTimeZone($timeZone);
}
$result = $formatter->format($intlDate);
return $result ?? self::ERROR_UNABLE_TO_FORMAT;
}parseDate() method is actually simpler than formatting. The only complication is what to do if the type is not specified (which will be the most likely case). All we need to do is to loop through all possible types (of which there are only four) until a result is produced:public function parseDate($string, $type = NULL)
{
if ($type) {
$result = $this->getDateFormatter($type)->parse($string);
} else {
$tryThese = [self::DATE_TYPE_FULL,
self::DATE_TYPE_LONG,
self::DATE_TYPE_MEDIUM,
self::DATE_TYPE_SHORT];
foreach ($tryThese as $type) {
$result = $this->getDateFormatter($type)->parse($string);
if ($result) {
break;
}
}
}
return ($result) ? $result : self::ERROR_UNABLE_TO_PARSE;
}Code the changes to Application\I18n\Locale, discussed previously. You can then create a test file, chap_08_formatting_date.php, which sets up autoloading, and creates two instances of the Locale class, one for the USA, the other for France:
<?php
require __DIR__ . '/../Application/Autoload/Loader.php';
Application\Autoload\Loader::init(__DIR__ . '/..');
use Application\I18n\Locale;
$localeFr = new Locale('fr-FR');
$localeUs = new Locale('en_US');
$date = '2016-02-29 17:23:58';
?>Next, with suitable styling, run a test of formatDate() and parseDate():
echo $localeFr->formatDate($date, Locale::DATE_TYPE_FULL); echo $localeUs->formatDate($date, Locale::DATE_TYPE_MEDIUM); $localeUs->parseDate($localeFr->formatDate($date, Locale::DATE_TYPE_MEDIUM)); // etc.
An example of the output is shown here:
