Merge pull request #750 from ArthurHoaro/feature/intl-dates
Improve datetime display
This commit is contained in:
commit
9971f7c82c
12 changed files with 246 additions and 23 deletions
|
@ -1,5 +1,11 @@
|
||||||
sudo: false
|
sudo: false
|
||||||
language: php
|
language: php
|
||||||
|
addons:
|
||||||
|
apt:
|
||||||
|
packages:
|
||||||
|
- locales
|
||||||
|
- language-pack-de
|
||||||
|
- language-pack-fr
|
||||||
cache:
|
cache:
|
||||||
directories:
|
directories:
|
||||||
- $HOME/.composer/cache
|
- $HOME/.composer/cache
|
||||||
|
@ -14,4 +20,4 @@ install:
|
||||||
script:
|
script:
|
||||||
- make clean
|
- make clean
|
||||||
- make check_permissions
|
- make check_permissions
|
||||||
- make test
|
- make all_tests
|
||||||
|
|
16
Makefile
16
Makefile
|
@ -124,8 +124,20 @@ test:
|
||||||
@echo "-------"
|
@echo "-------"
|
||||||
@echo "PHPUNIT"
|
@echo "PHPUNIT"
|
||||||
@echo "-------"
|
@echo "-------"
|
||||||
@mkdir -p sandbox
|
@mkdir -p sandbox coverage
|
||||||
@$(BIN)/phpunit tests
|
@$(BIN)/phpunit --coverage-php coverage/main.cov --testsuite unit-tests
|
||||||
|
|
||||||
|
locale_test_%:
|
||||||
|
@UT_LOCALE=$*.utf8 \
|
||||||
|
$(BIN)/phpunit \
|
||||||
|
--coverage-php coverage/$(firstword $(subst _, ,$*)).cov \
|
||||||
|
--bootstrap tests/languages/bootstrap.php \
|
||||||
|
--testsuite language-$(firstword $(subst _, ,$*))
|
||||||
|
|
||||||
|
all_tests: test locale_test_de_DE locale_test_en_US locale_test_fr_FR
|
||||||
|
@$(BIN)/phpcov merge --html coverage coverage
|
||||||
|
@# --text doesn't work with phpunit 4.* (v5 requires PHP 5.6)
|
||||||
|
@#$(BIN)/phpcov merge --text coverage/txt coverage
|
||||||
|
|
||||||
##
|
##
|
||||||
# Custom release archive generation
|
# Custom release archive generation
|
||||||
|
|
|
@ -216,20 +216,55 @@ function is_session_id_valid($sessionId)
|
||||||
function autoLocale($headerLocale)
|
function autoLocale($headerLocale)
|
||||||
{
|
{
|
||||||
// Default if browser does not send HTTP_ACCEPT_LANGUAGE
|
// Default if browser does not send HTTP_ACCEPT_LANGUAGE
|
||||||
$attempts = array('en_US');
|
$attempts = array('en_US', 'en_US.utf8', 'en_US.UTF-8');
|
||||||
if (isset($headerLocale)) {
|
if (isset($headerLocale)) {
|
||||||
// (It's a bit crude, but it works very well. Preferred language is always presented first.)
|
// (It's a bit crude, but it works very well. Preferred language is always presented first.)
|
||||||
if (preg_match('/([a-z]{2})-?([a-z]{2})?/i', $headerLocale, $matches)) {
|
if (preg_match('/([a-z]{2,3})[-_]?([a-z]{2})?/i', $headerLocale, $matches)) {
|
||||||
$loc = $matches[1] . (!empty($matches[2]) ? '_' . strtoupper($matches[2]) : '');
|
$first = [strtolower($matches[1]), strtoupper($matches[1])];
|
||||||
$attempts = array(
|
$separators = ['_', '-'];
|
||||||
$loc.'.UTF-8', $loc, str_replace('_', '-', $loc).'.UTF-8', str_replace('_', '-', $loc),
|
$encodings = ['utf8', 'UTF-8'];
|
||||||
$loc . '_' . strtoupper($loc).'.UTF-8', $loc . '_' . strtoupper($loc),
|
if (!empty($matches[2])) {
|
||||||
$loc . '_' . $loc.'.UTF-8', $loc . '_' . $loc, $loc . '-' . strtoupper($loc).'.UTF-8',
|
$second = [strtoupper($matches[2]), strtolower($matches[2])];
|
||||||
$loc . '-' . strtoupper($loc), $loc . '-' . $loc.'.UTF-8', $loc . '-' . $loc
|
$attempts = cartesian_product_generator([$first, $separators, $second, ['.'], $encodings]);
|
||||||
);
|
} else {
|
||||||
|
$attempts = cartesian_product_generator([$first, $separators, $first, ['.'], $encodings]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setlocale(LC_ALL, implode('implode', iterator_to_array($attempts)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a Generator object representing the cartesian product from given $items.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* [['a'], ['b', 'c']]
|
||||||
|
* will generate:
|
||||||
|
* [
|
||||||
|
* ['a', 'b'],
|
||||||
|
* ['a', 'c'],
|
||||||
|
* ]
|
||||||
|
*
|
||||||
|
* @param array $items array of array of string
|
||||||
|
*
|
||||||
|
* @return Generator representing the cartesian product of given array.
|
||||||
|
*
|
||||||
|
* @see https://en.wikipedia.org/wiki/Cartesian_product
|
||||||
|
*/
|
||||||
|
function cartesian_product_generator($items)
|
||||||
|
{
|
||||||
|
if (empty($items)) {
|
||||||
|
yield [];
|
||||||
|
}
|
||||||
|
$subArray = array_pop($items);
|
||||||
|
if (empty($subArray)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
foreach (cartesian_product_generator($items) as $item) {
|
||||||
|
foreach ($subArray as $value) {
|
||||||
|
yield $item + [count($item) => $value];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setlocale(LC_ALL, $attempts);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -270,3 +305,33 @@ function normalize_spaces($string)
|
||||||
{
|
{
|
||||||
return preg_replace('/\s{2,}/', ' ', trim($string));
|
return preg_replace('/\s{2,}/', ' ', trim($string));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format the date according to the locale.
|
||||||
|
*
|
||||||
|
* Requires php-intl to display international datetimes,
|
||||||
|
* otherwise default format '%c' will be returned.
|
||||||
|
*
|
||||||
|
* @param DateTime $date to format.
|
||||||
|
* @param bool $intl Use international format if true.
|
||||||
|
*
|
||||||
|
* @return bool|string Formatted date, or false if the input is invalid.
|
||||||
|
*/
|
||||||
|
function format_date($date, $intl = true)
|
||||||
|
{
|
||||||
|
if (! $date instanceof DateTime) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $intl || ! class_exists('IntlDateFormatter')) {
|
||||||
|
return strftime('%c', $date->getTimestamp());
|
||||||
|
}
|
||||||
|
|
||||||
|
$formatter = new IntlDateFormatter(
|
||||||
|
setlocale(LC_TIME, 0),
|
||||||
|
IntlDateFormatter::LONG,
|
||||||
|
IntlDateFormatter::LONG
|
||||||
|
);
|
||||||
|
|
||||||
|
return $formatter->format($date);
|
||||||
|
}
|
||||||
|
|
|
@ -20,7 +20,8 @@
|
||||||
"phpmd/phpmd" : "@stable",
|
"phpmd/phpmd" : "@stable",
|
||||||
"phpunit/phpunit": "4.8.*",
|
"phpunit/phpunit": "4.8.*",
|
||||||
"sebastian/phpcpd": "*",
|
"sebastian/phpcpd": "*",
|
||||||
"squizlabs/php_codesniffer": "2.*"
|
"squizlabs/php_codesniffer": "2.*",
|
||||||
|
"phpunit/phpcov": "*"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
|
|
20
phpunit.xml
20
phpunit.xml
|
@ -3,13 +3,25 @@
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.5/phpunit.xsd"
|
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.5/phpunit.xsd"
|
||||||
colors="true">
|
colors="true">
|
||||||
|
<testsuites>
|
||||||
|
<testsuite name="unit-tests">
|
||||||
|
<directory>tests</directory>
|
||||||
|
<exclude>tests/languages</exclude>
|
||||||
|
</testsuite>
|
||||||
|
<testsuite name="language-de">
|
||||||
|
<directory>tests/languages/de</directory>
|
||||||
|
</testsuite>
|
||||||
|
<testsuite name="language-en">
|
||||||
|
<directory>tests/languages/en</directory>
|
||||||
|
</testsuite>
|
||||||
|
<testsuite name="language-fr">
|
||||||
|
<directory>tests/languages/fr</directory>
|
||||||
|
</testsuite>
|
||||||
|
</testsuites>
|
||||||
|
|
||||||
<filter>
|
<filter>
|
||||||
<whitelist addUncoveredFilesFromWhitelist="true">
|
<whitelist addUncoveredFilesFromWhitelist="true">
|
||||||
<directory suffix=".php">application</directory>
|
<directory suffix=".php">application</directory>
|
||||||
</whitelist>
|
</whitelist>
|
||||||
</filter>
|
</filter>
|
||||||
<logging>
|
|
||||||
<log type="coverage-html" target="coverage" lowUpperBound="30" highLowerBound="80"/>
|
|
||||||
<log type="coverage-text" target="php://stdout" showUncoveredFiles="true"/>
|
|
||||||
</logging>
|
|
||||||
</phpunit>
|
</phpunit>
|
||||||
|
|
|
@ -23,7 +23,12 @@ class UtilsTest extends PHPUnit_Framework_TestCase
|
||||||
|
|
||||||
// Expected log date format
|
// Expected log date format
|
||||||
protected static $dateFormat = 'Y/m/d H:i:s';
|
protected static $dateFormat = 'Y/m/d H:i:s';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string Save the current timezone.
|
||||||
|
*/
|
||||||
|
protected static $defaultTimeZone;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assign reference data
|
* Assign reference data
|
||||||
|
@ -31,6 +36,17 @@ class UtilsTest extends PHPUnit_Framework_TestCase
|
||||||
public static function setUpBeforeClass()
|
public static function setUpBeforeClass()
|
||||||
{
|
{
|
||||||
self::$sidHashes = ReferenceSessionIdHashes::getHashes();
|
self::$sidHashes = ReferenceSessionIdHashes::getHashes();
|
||||||
|
self::$defaultTimeZone = date_default_timezone_get();
|
||||||
|
// Timezone without DST for test consistency
|
||||||
|
date_default_timezone_set('Africa/Nairobi');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the timezone
|
||||||
|
*/
|
||||||
|
public static function tearDownAfterClass()
|
||||||
|
{
|
||||||
|
date_default_timezone_set(self::$defaultTimeZone);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -282,4 +298,32 @@ public function testNormalizeSpace()
|
||||||
$this->assertEquals('', normalize_spaces(''));
|
$this->assertEquals('', normalize_spaces(''));
|
||||||
$this->assertEquals(null, normalize_spaces(null));
|
$this->assertEquals(null, normalize_spaces(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test arrays_combine
|
||||||
|
*/
|
||||||
|
public function testCartesianProductGenerator()
|
||||||
|
{
|
||||||
|
$arr = [['ab', 'cd'], ['ef', 'gh'], ['ij', 'kl'], ['m']];
|
||||||
|
$expected = [
|
||||||
|
['ab', 'ef', 'ij', 'm'],
|
||||||
|
['ab', 'ef', 'kl', 'm'],
|
||||||
|
['ab', 'gh', 'ij', 'm'],
|
||||||
|
['ab', 'gh', 'kl', 'm'],
|
||||||
|
['cd', 'ef', 'ij', 'm'],
|
||||||
|
['cd', 'ef', 'kl', 'm'],
|
||||||
|
['cd', 'gh', 'ij', 'm'],
|
||||||
|
['cd', 'gh', 'kl', 'm'],
|
||||||
|
];
|
||||||
|
$this->assertEquals($expected, iterator_to_array(cartesian_product_generator($arr)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test date_format() with invalid parameter.
|
||||||
|
*/
|
||||||
|
public function testDateFormatInvalid()
|
||||||
|
{
|
||||||
|
$this->assertFalse(format_date([]));
|
||||||
|
$this->assertFalse(format_date(null));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
7
tests/languages/bootstrap.php
Normal file
7
tests/languages/bootstrap.php
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<?php
|
||||||
|
if (! empty('UT_LOCALE')) {
|
||||||
|
setlocale(LC_ALL, getenv('UT_LOCALE'));
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once 'vendor/autoload.php';
|
||||||
|
|
25
tests/languages/de/UtilsDeTest.php
Normal file
25
tests/languages/de/UtilsDeTest.php
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
require_once 'tests/UtilsTest.php';
|
||||||
|
|
||||||
|
|
||||||
|
class UtilsDeTest extends UtilsTest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Test date_format().
|
||||||
|
*/
|
||||||
|
public function testDateFormat()
|
||||||
|
{
|
||||||
|
$date = DateTime::createFromFormat('Ymd_His', '20170101_101112');
|
||||||
|
$this->assertRegExp('/1. Januar 2017 (um )?10:11:12 GMT\+0?3(:00)?/', format_date($date, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test date_format() using builtin PHP function strftime.
|
||||||
|
*/
|
||||||
|
public function testDateFormatDefault()
|
||||||
|
{
|
||||||
|
$date = DateTime::createFromFormat('Ymd_His', '20170101_101112');
|
||||||
|
$this->assertEquals('So 01 Jan 2017 10:11:12 EAT', format_date($date, false));
|
||||||
|
}
|
||||||
|
}
|
25
tests/languages/en/UtilsEnTest.php
Normal file
25
tests/languages/en/UtilsEnTest.php
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
require_once 'tests/UtilsTest.php';
|
||||||
|
|
||||||
|
|
||||||
|
class UtilsEnTest extends UtilsTest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Test date_format().
|
||||||
|
*/
|
||||||
|
public function testDateFormat()
|
||||||
|
{
|
||||||
|
$date = DateTime::createFromFormat('Ymd_His', '20170101_101112');
|
||||||
|
$this->assertRegExp('/January 1, 2017 (at )?10:11:12 AM GMT\+0?3(:00)?/', format_date($date, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test date_format() using builtin PHP function strftime.
|
||||||
|
*/
|
||||||
|
public function testDateFormatDefault()
|
||||||
|
{
|
||||||
|
$date = DateTime::createFromFormat('Ymd_His', '20170101_101112');
|
||||||
|
$this->assertEquals('Sun 01 Jan 2017 10:11:12 AM EAT', format_date($date, false));
|
||||||
|
}
|
||||||
|
}
|
25
tests/languages/fr/UtilsFrTest.php
Normal file
25
tests/languages/fr/UtilsFrTest.php
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
require_once 'tests/UtilsTest.php';
|
||||||
|
|
||||||
|
|
||||||
|
class UtilsFrTest extends UtilsTest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Test date_format().
|
||||||
|
*/
|
||||||
|
public function testDateFormat()
|
||||||
|
{
|
||||||
|
$date = DateTime::createFromFormat('Ymd_His', '20170101_101112');
|
||||||
|
$this->assertRegExp('/1 janvier 2017 (à )?10:11:12 UTC\+0?3(:00)?/', format_date($date));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test date_format() using builtin PHP function strftime.
|
||||||
|
*/
|
||||||
|
public function testDateFormatDefault()
|
||||||
|
{
|
||||||
|
$date = DateTime::createFromFormat('Ymd_His', '20170101_101112');
|
||||||
|
$this->assertEquals('dim. 01 janv. 2017 10:11:12 EAT', format_date($date, false));
|
||||||
|
}
|
||||||
|
}
|
|
@ -171,10 +171,11 @@ <h2>
|
||||||
<div class="linklist-item-infos-dateblock pure-u-lg-3-8 pure-u-1">
|
<div class="linklist-item-infos-dateblock pure-u-lg-3-8 pure-u-1">
|
||||||
<a href="?{$value.shorturl}" title="{'Permalink'|t}">
|
<a href="?{$value.shorturl}" title="{'Permalink'|t}">
|
||||||
{if="!$hide_timestamps || isLoggedIn()"}
|
{if="!$hide_timestamps || isLoggedIn()"}
|
||||||
{$updated=$value.updated_timestamp ? 'Edited: '. strftime('%c', $value.updated_timestamp) : 'Permalink'}
|
{$updated=$value.updated_timestamp ? 'Edited: '. format_date($value.updated) : 'Permalink'}
|
||||||
<span class="linkdate" title="{$updated}">
|
<span class="linkdate" title="{$updated}">
|
||||||
<i class="fa fa-clock-o"></i>
|
<i class="fa fa-clock-o"></i>
|
||||||
{function="strftime('%c', $value.timestamp)"}{if="$value.updated_timestamp"}*{/if}
|
{$value.created|format_date}
|
||||||
|
{if="$value.updated_timestamp"}*{/if}
|
||||||
·
|
·
|
||||||
</span>
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -99,11 +99,11 @@
|
||||||
<br>
|
<br>
|
||||||
{if="$value.description"}<div class="linkdescription">{$value.description}</div>{/if}
|
{if="$value.description"}<div class="linkdescription">{$value.description}</div>{/if}
|
||||||
{if="!$hide_timestamps || isLoggedIn()"}
|
{if="!$hide_timestamps || isLoggedIn()"}
|
||||||
{$updated=$value.updated_timestamp ? 'Edited: '. strftime('%c', $value.updated_timestamp) : 'Permalink'}
|
{$updated=$value.updated_timestamp ? 'Edited: '. format_date($value.updated) : 'Permalink'}
|
||||||
<span class="linkdate" title="Permalink">
|
<span class="linkdate" title="Permalink">
|
||||||
<a href="?{$value.shorturl}">
|
<a href="?{$value.shorturl}">
|
||||||
<span title="{$updated}">
|
<span title="{$updated}">
|
||||||
{function="strftime('%c', $value.timestamp)"}
|
{$value.created|format_date}
|
||||||
{if="$value.updated_timestamp"}*{/if}
|
{if="$value.updated_timestamp"}*{/if}
|
||||||
</span>
|
</span>
|
||||||
- permalink
|
- permalink
|
||||||
|
|
Loading…
Reference in a new issue