aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorArthurHoaro <arthur@hoa.ro>2017-03-06 21:13:48 +0100
committerGitHub <noreply@github.com>2017-03-06 21:13:48 +0100
commit9971f7c82cc47446b457464cec5b4fefcae470e1 (patch)
tree87b01fc1dd9d0ca1f0c874aad847029eab74d0d8
parent236239be752a7bb24547237b5751ac4fcbc0e549 (diff)
parent36c8fb1ef869c29e783f0dd5ebef2fb5566e2611 (diff)
downloadShaarli-9971f7c82cc47446b457464cec5b4fefcae470e1.tar.gz
Shaarli-9971f7c82cc47446b457464cec5b4fefcae470e1.tar.zst
Shaarli-9971f7c82cc47446b457464cec5b4fefcae470e1.zip
Merge pull request #750 from ArthurHoaro/feature/intl-dates
Improve datetime display
-rw-r--r--.travis.yml8
-rw-r--r--Makefile16
-rw-r--r--application/Utils.php85
-rw-r--r--composer.json3
-rw-r--r--phpunit.xml20
-rw-r--r--tests/UtilsTest.php46
-rw-r--r--tests/languages/bootstrap.php7
-rw-r--r--tests/languages/de/UtilsDeTest.php25
-rw-r--r--tests/languages/en/UtilsEnTest.php25
-rw-r--r--tests/languages/fr/UtilsFrTest.php25
-rw-r--r--tpl/default/linklist.html5
-rw-r--r--tpl/vintage/linklist.html4
12 files changed, 246 insertions, 23 deletions
diff --git a/.travis.yml b/.travis.yml
index 03071a47..59b86c08 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,5 +1,11 @@
1sudo: false 1sudo: false
2language: php 2language: php
3addons:
4 apt:
5 packages:
6 - locales
7 - language-pack-de
8 - language-pack-fr
3cache: 9cache:
4 directories: 10 directories:
5 - $HOME/.composer/cache 11 - $HOME/.composer/cache
@@ -14,4 +20,4 @@ install:
14script: 20script:
15 - make clean 21 - make clean
16 - make check_permissions 22 - make check_permissions
17 - make test 23 - make all_tests
diff --git a/Makefile b/Makefile
index f3065b77..1d8a73a2 100644
--- a/Makefile
+++ b/Makefile
@@ -124,8 +124,20 @@ test:
124 @echo "-------" 124 @echo "-------"
125 @echo "PHPUNIT" 125 @echo "PHPUNIT"
126 @echo "-------" 126 @echo "-------"
127 @mkdir -p sandbox 127 @mkdir -p sandbox coverage
128 @$(BIN)/phpunit tests 128 @$(BIN)/phpunit --coverage-php coverage/main.cov --testsuite unit-tests
129
130locale_test_%:
131 @UT_LOCALE=$*.utf8 \
132 $(BIN)/phpunit \
133 --coverage-php coverage/$(firstword $(subst _, ,$*)).cov \
134 --bootstrap tests/languages/bootstrap.php \
135 --testsuite language-$(firstword $(subst _, ,$*))
136
137all_tests: test locale_test_de_DE locale_test_en_US locale_test_fr_FR
138 @$(BIN)/phpcov merge --html coverage coverage
139 @# --text doesn't work with phpunit 4.* (v5 requires PHP 5.6)
140 @#$(BIN)/phpcov merge --text coverage/txt coverage
129 141
130## 142##
131# Custom release archive generation 143# Custom release archive generation
diff --git a/application/Utils.php b/application/Utils.php
index 35d65224..a936b09f 100644
--- a/application/Utils.php
+++ b/application/Utils.php
@@ -216,20 +216,55 @@ function is_session_id_valid($sessionId)
216function autoLocale($headerLocale) 216function autoLocale($headerLocale)
217{ 217{
218 // Default if browser does not send HTTP_ACCEPT_LANGUAGE 218 // Default if browser does not send HTTP_ACCEPT_LANGUAGE
219 $attempts = array('en_US'); 219 $attempts = array('en_US', 'en_US.utf8', 'en_US.UTF-8');
220 if (isset($headerLocale)) { 220 if (isset($headerLocale)) {
221 // (It's a bit crude, but it works very well. Preferred language is always presented first.) 221 // (It's a bit crude, but it works very well. Preferred language is always presented first.)
222 if (preg_match('/([a-z]{2})-?([a-z]{2})?/i', $headerLocale, $matches)) { 222 if (preg_match('/([a-z]{2,3})[-_]?([a-z]{2})?/i', $headerLocale, $matches)) {
223 $loc = $matches[1] . (!empty($matches[2]) ? '_' . strtoupper($matches[2]) : ''); 223 $first = [strtolower($matches[1]), strtoupper($matches[1])];
224 $attempts = array( 224 $separators = ['_', '-'];
225 $loc.'.UTF-8', $loc, str_replace('_', '-', $loc).'.UTF-8', str_replace('_', '-', $loc), 225 $encodings = ['utf8', 'UTF-8'];
226 $loc . '_' . strtoupper($loc).'.UTF-8', $loc . '_' . strtoupper($loc), 226 if (!empty($matches[2])) {
227 $loc . '_' . $loc.'.UTF-8', $loc . '_' . $loc, $loc . '-' . strtoupper($loc).'.UTF-8', 227 $second = [strtoupper($matches[2]), strtolower($matches[2])];
228 $loc . '-' . strtoupper($loc), $loc . '-' . $loc.'.UTF-8', $loc . '-' . $loc 228 $attempts = cartesian_product_generator([$first, $separators, $second, ['.'], $encodings]);
229 ); 229 } else {
230 $attempts = cartesian_product_generator([$first, $separators, $first, ['.'], $encodings]);
231 }
232 }
233 }
234 setlocale(LC_ALL, implode('implode', iterator_to_array($attempts)));
235}
236
237/**
238 * Build a Generator object representing the cartesian product from given $items.
239 *
240 * Example:
241 * [['a'], ['b', 'c']]
242 * will generate:
243 * [
244 * ['a', 'b'],
245 * ['a', 'c'],
246 * ]
247 *
248 * @param array $items array of array of string
249 *
250 * @return Generator representing the cartesian product of given array.
251 *
252 * @see https://en.wikipedia.org/wiki/Cartesian_product
253 */
254function cartesian_product_generator($items)
255{
256 if (empty($items)) {
257 yield [];
258 }
259 $subArray = array_pop($items);
260 if (empty($subArray)) {
261 return;
262 }
263 foreach (cartesian_product_generator($items) as $item) {
264 foreach ($subArray as $value) {
265 yield $item + [count($item) => $value];
230 } 266 }
231 } 267 }
232 setlocale(LC_ALL, $attempts);
233} 268}
234 269
235/** 270/**
@@ -270,3 +305,33 @@ function normalize_spaces($string)
270{ 305{
271 return preg_replace('/\s{2,}/', ' ', trim($string)); 306 return preg_replace('/\s{2,}/', ' ', trim($string));
272} 307}
308
309/**
310 * Format the date according to the locale.
311 *
312 * Requires php-intl to display international datetimes,
313 * otherwise default format '%c' will be returned.
314 *
315 * @param DateTime $date to format.
316 * @param bool $intl Use international format if true.
317 *
318 * @return bool|string Formatted date, or false if the input is invalid.
319 */
320function format_date($date, $intl = true)
321{
322 if (! $date instanceof DateTime) {
323 return false;
324 }
325
326 if (! $intl || ! class_exists('IntlDateFormatter')) {
327 return strftime('%c', $date->getTimestamp());
328 }
329
330 $formatter = new IntlDateFormatter(
331 setlocale(LC_TIME, 0),
332 IntlDateFormatter::LONG,
333 IntlDateFormatter::LONG
334 );
335
336 return $formatter->format($date);
337}
diff --git a/composer.json b/composer.json
index b82aceef..70b87bb9 100644
--- a/composer.json
+++ b/composer.json
@@ -20,7 +20,8 @@
20 "phpmd/phpmd" : "@stable", 20 "phpmd/phpmd" : "@stable",
21 "phpunit/phpunit": "4.8.*", 21 "phpunit/phpunit": "4.8.*",
22 "sebastian/phpcpd": "*", 22 "sebastian/phpcpd": "*",
23 "squizlabs/php_codesniffer": "2.*" 23 "squizlabs/php_codesniffer": "2.*",
24 "phpunit/phpcov": "*"
24 }, 25 },
25 "autoload": { 26 "autoload": {
26 "psr-4": { 27 "psr-4": {
diff --git a/phpunit.xml b/phpunit.xml
index d6e01c35..8b66e6c5 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -3,13 +3,25 @@
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4 xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.5/phpunit.xsd" 4 xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.5/phpunit.xsd"
5 colors="true"> 5 colors="true">
6 <testsuites>
7 <testsuite name="unit-tests">
8 <directory>tests</directory>
9 <exclude>tests/languages</exclude>
10 </testsuite>
11 <testsuite name="language-de">
12 <directory>tests/languages/de</directory>
13 </testsuite>
14 <testsuite name="language-en">
15 <directory>tests/languages/en</directory>
16 </testsuite>
17 <testsuite name="language-fr">
18 <directory>tests/languages/fr</directory>
19 </testsuite>
20 </testsuites>
21
6 <filter> 22 <filter>
7 <whitelist addUncoveredFilesFromWhitelist="true"> 23 <whitelist addUncoveredFilesFromWhitelist="true">
8 <directory suffix=".php">application</directory> 24 <directory suffix=".php">application</directory>
9 </whitelist> 25 </whitelist>
10 </filter> 26 </filter>
11 <logging>
12 <log type="coverage-html" target="coverage" lowUpperBound="30" highLowerBound="80"/>
13 <log type="coverage-text" target="php://stdout" showUncoveredFiles="true"/>
14 </logging>
15</phpunit> 27</phpunit>
diff --git a/tests/UtilsTest.php b/tests/UtilsTest.php
index c885f552..e70cc1ae 100644
--- a/tests/UtilsTest.php
+++ b/tests/UtilsTest.php
@@ -23,7 +23,12 @@ class UtilsTest extends PHPUnit_Framework_TestCase
23 23
24 // Expected log date format 24 // Expected log date format
25 protected static $dateFormat = 'Y/m/d H:i:s'; 25 protected static $dateFormat = 'Y/m/d H:i:s';
26 26
27 /**
28 * @var string Save the current timezone.
29 */
30 protected static $defaultTimeZone;
31
27 32
28 /** 33 /**
29 * Assign reference data 34 * Assign reference data
@@ -31,6 +36,17 @@ class UtilsTest extends PHPUnit_Framework_TestCase
31 public static function setUpBeforeClass() 36 public static function setUpBeforeClass()
32 { 37 {
33 self::$sidHashes = ReferenceSessionIdHashes::getHashes(); 38 self::$sidHashes = ReferenceSessionIdHashes::getHashes();
39 self::$defaultTimeZone = date_default_timezone_get();
40 // Timezone without DST for test consistency
41 date_default_timezone_set('Africa/Nairobi');
42 }
43
44 /**
45 * Reset the timezone
46 */
47 public static function tearDownAfterClass()
48 {
49 date_default_timezone_set(self::$defaultTimeZone);
34 } 50 }
35 51
36 /** 52 /**
@@ -282,4 +298,32 @@ class UtilsTest extends PHPUnit_Framework_TestCase
282 $this->assertEquals('', normalize_spaces('')); 298 $this->assertEquals('', normalize_spaces(''));
283 $this->assertEquals(null, normalize_spaces(null)); 299 $this->assertEquals(null, normalize_spaces(null));
284 } 300 }
301
302 /**
303 * Test arrays_combine
304 */
305 public function testCartesianProductGenerator()
306 {
307 $arr = [['ab', 'cd'], ['ef', 'gh'], ['ij', 'kl'], ['m']];
308 $expected = [
309 ['ab', 'ef', 'ij', 'm'],
310 ['ab', 'ef', 'kl', 'm'],
311 ['ab', 'gh', 'ij', 'm'],
312 ['ab', 'gh', 'kl', 'm'],
313 ['cd', 'ef', 'ij', 'm'],
314 ['cd', 'ef', 'kl', 'm'],
315 ['cd', 'gh', 'ij', 'm'],
316 ['cd', 'gh', 'kl', 'm'],
317 ];
318 $this->assertEquals($expected, iterator_to_array(cartesian_product_generator($arr)));
319 }
320
321 /**
322 * Test date_format() with invalid parameter.
323 */
324 public function testDateFormatInvalid()
325 {
326 $this->assertFalse(format_date([]));
327 $this->assertFalse(format_date(null));
328 }
285} 329}
diff --git a/tests/languages/bootstrap.php b/tests/languages/bootstrap.php
new file mode 100644
index 00000000..95609210
--- /dev/null
+++ b/tests/languages/bootstrap.php
@@ -0,0 +1,7 @@
1<?php
2if (! empty('UT_LOCALE')) {
3 setlocale(LC_ALL, getenv('UT_LOCALE'));
4}
5
6require_once 'vendor/autoload.php';
7
diff --git a/tests/languages/de/UtilsDeTest.php b/tests/languages/de/UtilsDeTest.php
new file mode 100644
index 00000000..8a740389
--- /dev/null
+++ b/tests/languages/de/UtilsDeTest.php
@@ -0,0 +1,25 @@
1<?php
2
3require_once 'tests/UtilsTest.php';
4
5
6class UtilsDeTest extends UtilsTest
7{
8 /**
9 * Test date_format().
10 */
11 public function testDateFormat()
12 {
13 $date = DateTime::createFromFormat('Ymd_His', '20170101_101112');
14 $this->assertRegExp('/1. Januar 2017 (um )?10:11:12 GMT\+0?3(:00)?/', format_date($date, true));
15 }
16
17 /**
18 * Test date_format() using builtin PHP function strftime.
19 */
20 public function testDateFormatDefault()
21 {
22 $date = DateTime::createFromFormat('Ymd_His', '20170101_101112');
23 $this->assertEquals('So 01 Jan 2017 10:11:12 EAT', format_date($date, false));
24 }
25}
diff --git a/tests/languages/en/UtilsEnTest.php b/tests/languages/en/UtilsEnTest.php
new file mode 100644
index 00000000..60bcb653
--- /dev/null
+++ b/tests/languages/en/UtilsEnTest.php
@@ -0,0 +1,25 @@
1<?php
2
3require_once 'tests/UtilsTest.php';
4
5
6class UtilsEnTest extends UtilsTest
7{
8 /**
9 * Test date_format().
10 */
11 public function testDateFormat()
12 {
13 $date = DateTime::createFromFormat('Ymd_His', '20170101_101112');
14 $this->assertRegExp('/January 1, 2017 (at )?10:11:12 AM GMT\+0?3(:00)?/', format_date($date, true));
15 }
16
17 /**
18 * Test date_format() using builtin PHP function strftime.
19 */
20 public function testDateFormatDefault()
21 {
22 $date = DateTime::createFromFormat('Ymd_His', '20170101_101112');
23 $this->assertEquals('Sun 01 Jan 2017 10:11:12 AM EAT', format_date($date, false));
24 }
25}
diff --git a/tests/languages/fr/UtilsFrTest.php b/tests/languages/fr/UtilsFrTest.php
new file mode 100644
index 00000000..890308d3
--- /dev/null
+++ b/tests/languages/fr/UtilsFrTest.php
@@ -0,0 +1,25 @@
1<?php
2
3require_once 'tests/UtilsTest.php';
4
5
6class UtilsFrTest extends UtilsTest
7{
8 /**
9 * Test date_format().
10 */
11 public function testDateFormat()
12 {
13 $date = DateTime::createFromFormat('Ymd_His', '20170101_101112');
14 $this->assertRegExp('/1 janvier 2017 (à )?10:11:12 UTC\+0?3(:00)?/', format_date($date));
15 }
16
17 /**
18 * Test date_format() using builtin PHP function strftime.
19 */
20 public function testDateFormatDefault()
21 {
22 $date = DateTime::createFromFormat('Ymd_His', '20170101_101112');
23 $this->assertEquals('dim. 01 janv. 2017 10:11:12 EAT', format_date($date, false));
24 }
25}
diff --git a/tpl/default/linklist.html b/tpl/default/linklist.html
index a9712704..9bc3ba1a 100644
--- a/tpl/default/linklist.html
+++ b/tpl/default/linklist.html
@@ -171,10 +171,11 @@
171 <div class="linklist-item-infos-dateblock pure-u-lg-3-8 pure-u-1"> 171 <div class="linklist-item-infos-dateblock pure-u-lg-3-8 pure-u-1">
172 <a href="?{$value.shorturl}" title="{'Permalink'|t}"> 172 <a href="?{$value.shorturl}" title="{'Permalink'|t}">
173 {if="!$hide_timestamps || isLoggedIn()"} 173 {if="!$hide_timestamps || isLoggedIn()"}
174 {$updated=$value.updated_timestamp ? 'Edited: '. strftime('%c', $value.updated_timestamp) : 'Permalink'} 174 {$updated=$value.updated_timestamp ? 'Edited: '. format_date($value.updated) : 'Permalink'}
175 <span class="linkdate" title="{$updated}"> 175 <span class="linkdate" title="{$updated}">
176 <i class="fa fa-clock-o"></i> 176 <i class="fa fa-clock-o"></i>
177 {function="strftime('%c', $value.timestamp)"}{if="$value.updated_timestamp"}*{/if} 177 {$value.created|format_date}
178 {if="$value.updated_timestamp"}*{/if}
178 &middot; 179 &middot;
179 </span> 180 </span>
180 {/if} 181 {/if}
diff --git a/tpl/vintage/linklist.html b/tpl/vintage/linklist.html
index 5accc92f..fc116667 100644
--- a/tpl/vintage/linklist.html
+++ b/tpl/vintage/linklist.html
@@ -99,11 +99,11 @@
99 <br> 99 <br>
100 {if="$value.description"}<div class="linkdescription">{$value.description}</div>{/if} 100 {if="$value.description"}<div class="linkdescription">{$value.description}</div>{/if}
101 {if="!$hide_timestamps || isLoggedIn()"} 101 {if="!$hide_timestamps || isLoggedIn()"}
102 {$updated=$value.updated_timestamp ? 'Edited: '. strftime('%c', $value.updated_timestamp) : 'Permalink'} 102 {$updated=$value.updated_timestamp ? 'Edited: '. format_date($value.updated) : 'Permalink'}
103 <span class="linkdate" title="Permalink"> 103 <span class="linkdate" title="Permalink">
104 <a href="?{$value.shorturl}"> 104 <a href="?{$value.shorturl}">
105 <span title="{$updated}"> 105 <span title="{$updated}">
106 {function="strftime('%c', $value.timestamp)"} 106 {$value.created|format_date}
107 {if="$value.updated_timestamp"}*{/if} 107 {if="$value.updated_timestamp"}*{/if}
108 </span> 108 </span>
109 - permalink 109 - permalink