]> git.immae.eu Git - github/shaarli/Shaarli.git/commitdiff
Merge pull request #750 from ArthurHoaro/feature/intl-dates
authorArthurHoaro <arthur@hoa.ro>
Mon, 6 Mar 2017 20:13:48 +0000 (21:13 +0100)
committerGitHub <noreply@github.com>
Mon, 6 Mar 2017 20:13:48 +0000 (21:13 +0100)
Improve datetime display

12 files changed:
.travis.yml
Makefile
application/Utils.php
composer.json
phpunit.xml
tests/UtilsTest.php
tests/languages/bootstrap.php [new file with mode: 0644]
tests/languages/de/UtilsDeTest.php [new file with mode: 0644]
tests/languages/en/UtilsEnTest.php [new file with mode: 0644]
tests/languages/fr/UtilsFrTest.php [new file with mode: 0644]
tpl/default/linklist.html
tpl/vintage/linklist.html

index 03071a4734535485b8f9cc0f0106a3f8668de34c..59b86c08e45e5ba21b5ff6fb821e426b50eb7c51 100644 (file)
@@ -1,5 +1,11 @@
 sudo: false
 language: php
+addons:
+  apt:
+    packages:
+      - locales
+      - language-pack-de
+      - language-pack-fr
 cache:
   directories:
     - $HOME/.composer/cache
@@ -14,4 +20,4 @@ install:
 script:
   - make clean
   - make check_permissions
-  - make test
+  - make all_tests
index f3065b77ed2658856018372444844dc00e4575da..1d8a73a2e6f8b27a05e36d29225db1db567c1e22 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -124,8 +124,20 @@ test:
        @echo "-------"
        @echo "PHPUNIT"
        @echo "-------"
-       @mkdir -p sandbox
-       @$(BIN)/phpunit tests
+       @mkdir -p sandbox coverage
+       @$(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
index 35d652241bb6a5a4c42c7ded7b7381be48dc7f15..a936b09fa87710ef86df5ffd9f28a8584b9ac09c 100644 (file)
@@ -216,20 +216,55 @@ function is_session_id_valid($sessionId)
 function autoLocale($headerLocale)
 {
     // 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)) {
         // (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)) {
-            $loc = $matches[1] . (!empty($matches[2]) ? '_' . strtoupper($matches[2]) : '');
-            $attempts = array(
-                $loc.'.UTF-8', $loc, str_replace('_', '-', $loc).'.UTF-8', str_replace('_', '-', $loc),
-                $loc . '_' . strtoupper($loc).'.UTF-8', $loc . '_' . strtoupper($loc),
-                $loc . '_' . $loc.'.UTF-8', $loc . '_' . $loc, $loc . '-' . strtoupper($loc).'.UTF-8',
-                $loc . '-' . strtoupper($loc), $loc . '-' . $loc.'.UTF-8', $loc . '-' . $loc
-            );
+        if (preg_match('/([a-z]{2,3})[-_]?([a-z]{2})?/i', $headerLocale, $matches)) {
+            $first = [strtolower($matches[1]), strtoupper($matches[1])];
+            $separators = ['_', '-'];
+            $encodings = ['utf8', 'UTF-8'];
+            if (!empty($matches[2])) {
+                $second = [strtoupper($matches[2]), strtolower($matches[2])];
+                $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));
 }
+
+/**
+ * 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);
+}
index b82aceefe43402f64e38120a934e5a456d3469d1..70b87bb95b33cba9e4e8a1de71eb2dd9f9f5ed7b 100644 (file)
@@ -20,7 +20,8 @@
         "phpmd/phpmd" : "@stable",
         "phpunit/phpunit": "4.8.*",
         "sebastian/phpcpd": "*",
-        "squizlabs/php_codesniffer": "2.*"
+        "squizlabs/php_codesniffer": "2.*",
+        "phpunit/phpcov": "*"
     },
     "autoload": {
         "psr-4": {
index d6e01c35795b3837d97c2aa4366e4d383509f569..8b66e6c5b3ce0d0ccd351766f6678c353eef3a86 100644 (file)
@@ -3,13 +3,25 @@
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.5/phpunit.xsd"
     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>
     <whitelist addUncoveredFilesFromWhitelist="true">
       <directory suffix=".php">application</directory>
     </whitelist>
   </filter>
-  <logging>
-    <log type="coverage-html" target="coverage" lowUpperBound="30" highLowerBound="80"/>
-    <log type="coverage-text" target="php://stdout" showUncoveredFiles="true"/>
-  </logging>
 </phpunit>
index c885f552350b62cf003921c5b36673b3224eea14..e70cc1aef10197d66718149a0ded03f85cbec072 100644 (file)
@@ -23,7 +23,12 @@ class UtilsTest extends PHPUnit_Framework_TestCase
 
     // Expected log date format
     protected static $dateFormat = 'Y/m/d H:i:s';
-    
+
+    /**
+     * @var string Save the current timezone.
+     */
+    protected static $defaultTimeZone;
+
 
     /**
      * Assign reference data
@@ -31,6 +36,17 @@ class UtilsTest extends PHPUnit_Framework_TestCase
     public static function setUpBeforeClass()
     {
         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 @@ class UtilsTest extends PHPUnit_Framework_TestCase
         $this->assertEquals('', normalize_spaces(''));
         $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));
+    }
 }
diff --git a/tests/languages/bootstrap.php b/tests/languages/bootstrap.php
new file mode 100644 (file)
index 0000000..9560921
--- /dev/null
@@ -0,0 +1,7 @@
+<?php
+if (! empty('UT_LOCALE')) {
+    setlocale(LC_ALL, getenv('UT_LOCALE'));
+}
+
+require_once 'vendor/autoload.php';
+
diff --git a/tests/languages/de/UtilsDeTest.php b/tests/languages/de/UtilsDeTest.php
new file mode 100644 (file)
index 0000000..8a74038
--- /dev/null
@@ -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));
+    }
+}
diff --git a/tests/languages/en/UtilsEnTest.php b/tests/languages/en/UtilsEnTest.php
new file mode 100644 (file)
index 0000000..60bcb65
--- /dev/null
@@ -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));
+    }
+}
diff --git a/tests/languages/fr/UtilsFrTest.php b/tests/languages/fr/UtilsFrTest.php
new file mode 100644 (file)
index 0000000..890308d
--- /dev/null
@@ -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));
+    }
+}
index a9712704b387ba762f6850cb71e2395bb113d506..9bc3ba1a3c51e28d55d6ec74f8447f3fa40e8a70 100644 (file)
               <div class="linklist-item-infos-dateblock pure-u-lg-3-8 pure-u-1">
                 <a href="?{$value.shorturl}" title="{'Permalink'|t}">
                   {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}">
                       <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}
                       &middot;
                     </span>
                   {/if}
index 5accc92fa4eed0367a187c3509d27e943ffd50fd..fc116667aac83dd12bb4d45bbfad8d5744cbb560 100644 (file)
                 <br>
                 {if="$value.description"}<div class="linkdescription">{$value.description}</div>{/if}
                 {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">
                         <a href="?{$value.shorturl}">
                             <span title="{$updated}">
-                                {function="strftime('%c', $value.timestamp)"}
+                                {$value.created|format_date}
                                 {if="$value.updated_timestamp"}*{/if}
                             </span>
                             - permalink