added configurable formats for duration (#287)
This commit is contained in:
@@ -45,16 +45,28 @@ kimai:
|
|||||||
languages:
|
languages:
|
||||||
de:
|
de:
|
||||||
date_short: 'd.m.Y'
|
date_short: 'd.m.Y'
|
||||||
|
duration: '%%h:%%m Stunden'
|
||||||
|
duration_short: '%%h:%%m h'
|
||||||
en:
|
en:
|
||||||
date_short: 'Y-m-d'
|
date_short: 'Y-m-d'
|
||||||
|
duration: '%%h:%%m hours'
|
||||||
|
duration_short: '%%h:%%m h'
|
||||||
it:
|
it:
|
||||||
date_short: 'd.m.Y'
|
date_short: 'd.m.Y'
|
||||||
|
duration: '%%h:%%m h'
|
||||||
|
duration_short: '%%h:%%m h'
|
||||||
fr:
|
fr:
|
||||||
date_short: 'd/m/Y'
|
date_short: 'd/m/Y'
|
||||||
|
duration: '%%h:%%m h'
|
||||||
|
duration_short: '%%h:%%m h'
|
||||||
es:
|
es:
|
||||||
date_short: 'd.m.Y'
|
date_short: 'd.m.Y'
|
||||||
|
duration: '%%h:%%m h'
|
||||||
|
duration_short: '%%h:%%m h'
|
||||||
ru:
|
ru:
|
||||||
date_short: 'd.m.Y'
|
date_short: 'd.m.Y'
|
||||||
|
duration: '%%h:%%m h'
|
||||||
|
duration_short: '%%h:%%m h'
|
||||||
|
|
||||||
# timesheet calendar configuration
|
# timesheet calendar configuration
|
||||||
calendar:
|
calendar:
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ services:
|
|||||||
|
|
||||||
App\Twig\Extensions:
|
App\Twig\Extensions:
|
||||||
arguments:
|
arguments:
|
||||||
$locales: "%app_locales%"
|
$languages: "%kimai.languages%"
|
||||||
|
|
||||||
App\Twig\DateExtensions:
|
App\Twig\DateExtensions:
|
||||||
arguments:
|
arguments:
|
||||||
|
|||||||
@@ -156,6 +156,8 @@ class Configuration implements ConfigurationInterface
|
|||||||
->arrayPrototype()
|
->arrayPrototype()
|
||||||
->children()
|
->children()
|
||||||
->scalarNode('date_short')->end()
|
->scalarNode('date_short')->end()
|
||||||
|
->scalarNode('duration')->end()
|
||||||
|
->scalarNode('duration_short')->end()
|
||||||
->end()
|
->end()
|
||||||
->end()
|
->end()
|
||||||
;
|
;
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ class DurationType extends AbstractType
|
|||||||
$builder->addModelTransformer(new CallbackTransformer(
|
$builder->addModelTransformer(new CallbackTransformer(
|
||||||
function ($intToFormat) use ($formatter) {
|
function ($intToFormat) use ($formatter) {
|
||||||
try {
|
try {
|
||||||
return $formatter->format($intToFormat, true);
|
return $formatter->format($intToFormat);
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
throw new TransformationFailedException($e->getMessage());
|
throw new TransformationFailedException($e->getMessage());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,14 +85,13 @@ class Extensions extends \Twig_Extension
|
|||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extensions constructor.
|
* @param RequestStack $requestStack
|
||||||
* @param string $locales
|
* @param array $languages
|
||||||
* @param string $locale
|
|
||||||
*/
|
*/
|
||||||
public function __construct(RequestStack $requestStack, $locales)
|
public function __construct(RequestStack $requestStack, array $languages)
|
||||||
{
|
{
|
||||||
$this->requestStack = $requestStack;
|
$this->requestStack = $requestStack;
|
||||||
$this->locales = explode('|', $locales);
|
$this->locales = $languages;
|
||||||
$this->durationFormatter = new Duration();
|
$this->durationFormatter = new Duration();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,10 +156,10 @@ class Extensions extends \Twig_Extension
|
|||||||
* Transforms seconds into a duration string.
|
* Transforms seconds into a duration string.
|
||||||
*
|
*
|
||||||
* @param int|Timesheet $duration
|
* @param int|Timesheet $duration
|
||||||
* @param bool $includeSeconds
|
* @param string $format
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function duration($duration, $includeSeconds = false)
|
public function duration($duration, $format = null)
|
||||||
{
|
{
|
||||||
$seconds = $duration;
|
$seconds = $duration;
|
||||||
if ($duration instanceof Timesheet) {
|
if ($duration instanceof Timesheet) {
|
||||||
@@ -170,7 +169,22 @@ class Extensions extends \Twig_Extension
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->durationFormatter->format($seconds, $includeSeconds) . ' h';
|
$locale = $this->getLocale();
|
||||||
|
switch ($format) {
|
||||||
|
case 'full':
|
||||||
|
$format = isset($this->locales[$locale]) ? $this->locales[$locale]['duration'] : null;
|
||||||
|
break;
|
||||||
|
case null:
|
||||||
|
case 'short':
|
||||||
|
$format = isset($this->locales[$locale]) ? $this->locales[$locale]['duration_short'] : null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null === $format) {
|
||||||
|
$format = '%h:%m h';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->durationFormatter->format($seconds, $format);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -244,7 +258,7 @@ class Extensions extends \Twig_Extension
|
|||||||
public function getLocales()
|
public function getLocales()
|
||||||
{
|
{
|
||||||
$locales = [];
|
$locales = [];
|
||||||
foreach ($this->locales as $locale) {
|
foreach (array_keys($this->locales) as $locale) {
|
||||||
$locales[] = ['code' => $locale, 'name' => Intl::getLocaleBundle()->getLocaleName($locale, $locale)];
|
$locales[] = ['code' => $locale, 'name' => Intl::getLocaleBundle()->getLocaleName($locale, $locale)];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,14 +18,17 @@ class Duration
|
|||||||
public const FORMAT_NATURAL = 'natural';
|
public const FORMAT_NATURAL = 'natural';
|
||||||
public const FORMAT_SECONDS = 'seconds';
|
public const FORMAT_SECONDS = 'seconds';
|
||||||
|
|
||||||
|
public const FORMAT_WITH_SECONDS = '%h:%m:%s';
|
||||||
|
public const FORMAT_NO_SECONDS = '%h:%m';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transforms seconds into a duration string.
|
* Transforms seconds into a duration string.
|
||||||
*
|
*
|
||||||
* @param $seconds
|
* @param $seconds
|
||||||
* @param bool $includeSeconds
|
* @param string $format
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function format($seconds, $includeSeconds = false)
|
public function format($seconds, $format = self::FORMAT_NO_SECONDS)
|
||||||
{
|
{
|
||||||
$hour = floor($seconds / 3600);
|
$hour = floor($seconds / 3600);
|
||||||
$minute = floor(($seconds / 60) % 60);
|
$minute = floor(($seconds / 60) % 60);
|
||||||
@@ -33,14 +36,13 @@ class Duration
|
|||||||
$hour = $hour > 9 ? $hour : '0' . $hour;
|
$hour = $hour > 9 ? $hour : '0' . $hour;
|
||||||
$minute = $minute > 9 ? $minute : '0' . $minute;
|
$minute = $minute > 9 ? $minute : '0' . $minute;
|
||||||
|
|
||||||
if (!$includeSeconds) {
|
|
||||||
return $hour . ':' . $minute;
|
|
||||||
}
|
|
||||||
|
|
||||||
$second = $seconds % 60;
|
$second = $seconds % 60;
|
||||||
$second = $second > 9 ? $second : '0' . $second;
|
$second = $second > 9 ? $second : '0' . $second;
|
||||||
|
|
||||||
return $hour . ':' . $minute . ':' . $second;
|
$formatted = str_replace('%h', $hour, $format);
|
||||||
|
$formatted = str_replace('%m', $minute, $formatted);
|
||||||
|
|
||||||
|
return str_replace('%s', $second, $formatted);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -94,7 +94,7 @@
|
|||||||
{% for widget in section.widgets|slice(1, width) %}
|
{% for widget in section.widgets|slice(1, width) %}
|
||||||
{% set data = widget.data %}
|
{% set data = widget.data %}
|
||||||
{% if widget.dataType == constant('App\\Model\\Widget::DATA_TYPE_DURATION') %}
|
{% if widget.dataType == constant('App\\Model\\Widget::DATA_TYPE_DURATION') %}
|
||||||
{% set data = widget.data|duration %}
|
{% set data = widget.data|duration('full') %}
|
||||||
{% elseif widget.dataType == constant('App\\Model\\Widget::DATA_TYPE_MONEY') %}
|
{% elseif widget.dataType == constant('App\\Model\\Widget::DATA_TYPE_MONEY') %}
|
||||||
{% set data = widget.data|money %}
|
{% set data = widget.data|money %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
{% set data = widget.data %}
|
{% set data = widget.data %}
|
||||||
{% if widget.dataType == constant('App\\Model\\Widget::DATA_TYPE_DURATION') %}
|
{% if widget.dataType == constant('App\\Model\\Widget::DATA_TYPE_DURATION') %}
|
||||||
{% set data = widget.data|duration %}
|
{% set data = widget.data|duration('full') %}
|
||||||
{% elseif widget.dataType == constant('App\\Model\\Widget::DATA_TYPE_MONEY') %}
|
{% elseif widget.dataType == constant('App\\Model\\Widget::DATA_TYPE_MONEY') %}
|
||||||
{% set data = widget.data|money %}
|
{% set data = widget.data|money %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -21,8 +21,13 @@ use Twig\TwigFilter;
|
|||||||
*/
|
*/
|
||||||
class ExtensionsTest extends TestCase
|
class ExtensionsTest extends TestCase
|
||||||
{
|
{
|
||||||
|
private $localeEn = ['en' => ['date_short' => 'Y-m-d', 'duration' => '%h:%m:%s h', 'duration_short' => '%h:%m h']];
|
||||||
|
private $localeDe = ['de' => ['date_short' => 'd.m.Y', 'duration' => '%h:%m:%s Stunden', 'duration_short' => '%h:%m h']];
|
||||||
|
private $localeRu = ['ru' => ['date_short' => 'd.m.Y', 'duration' => '%h:%m:%s h', 'duration_short' => '%h:%m h']];
|
||||||
|
private $localeFake = ['XX' => ['date_short' => 'd.m.Y', 'duration' => '%h Stunden, %m Minuten und %s Sekunden', 'duration_short' => '%h - %m - %s Zeit']];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $locales
|
* @param array $locales
|
||||||
* @param string $locale
|
* @param string $locale
|
||||||
* @return Extensions
|
* @return Extensions
|
||||||
*/
|
*/
|
||||||
@@ -39,7 +44,7 @@ class ExtensionsTest extends TestCase
|
|||||||
public function testGetFilters()
|
public function testGetFilters()
|
||||||
{
|
{
|
||||||
$filters = ['duration', 'money', 'currency', 'country', 'icon'];
|
$filters = ['duration', 'money', 'currency', 'country', 'icon'];
|
||||||
$sut = $this->getSut('de');
|
$sut = $this->getSut($this->localeDe);
|
||||||
$twigFilters = $sut->getFilters();
|
$twigFilters = $sut->getFilters();
|
||||||
$this->assertCount(count($filters), $twigFilters);
|
$this->assertCount(count($filters), $twigFilters);
|
||||||
$i = 0;
|
$i = 0;
|
||||||
@@ -52,7 +57,7 @@ class ExtensionsTest extends TestCase
|
|||||||
public function testGetFunctions()
|
public function testGetFunctions()
|
||||||
{
|
{
|
||||||
$functions = ['locales', 'is_visible_column'];
|
$functions = ['locales', 'is_visible_column'];
|
||||||
$sut = $this->getSut('de');
|
$sut = $this->getSut($this->localeDe);
|
||||||
$twigFunctions = $sut->getFunctions();
|
$twigFunctions = $sut->getFunctions();
|
||||||
$this->assertCount(count($functions), $twigFunctions);
|
$this->assertCount(count($functions), $twigFunctions);
|
||||||
$i = 0;
|
$i = 0;
|
||||||
@@ -70,7 +75,8 @@ class ExtensionsTest extends TestCase
|
|||||||
['code' => 'ru', 'name' => 'русский'],
|
['code' => 'ru', 'name' => 'русский'],
|
||||||
];
|
];
|
||||||
|
|
||||||
$sut = $this->getSut('en|de|ru');
|
$appLocales = array_merge($this->localeEn, $this->localeDe, $this->localeRu);
|
||||||
|
$sut = $this->getSut($appLocales);
|
||||||
$this->assertEquals($locales, $sut->getLocales());
|
$this->assertEquals($locales, $sut->getLocales());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,7 +88,7 @@ class ExtensionsTest extends TestCase
|
|||||||
'RUB' => 'RUB',
|
'RUB' => 'RUB',
|
||||||
];
|
];
|
||||||
|
|
||||||
$sut = $this->getSut('en');
|
$sut = $this->getSut($this->localeEn);
|
||||||
foreach ($symbols as $name => $symbol) {
|
foreach ($symbols as $name => $symbol) {
|
||||||
$this->assertEquals($symbol, $sut->currency($name));
|
$this->assertEquals($symbol, $sut->currency($name));
|
||||||
}
|
}
|
||||||
@@ -96,7 +102,7 @@ class ExtensionsTest extends TestCase
|
|||||||
'ES' => 'Spain',
|
'ES' => 'Spain',
|
||||||
];
|
];
|
||||||
|
|
||||||
$sut = $this->getSut('en');
|
$sut = $this->getSut($this->localeEn);
|
||||||
foreach ($countries as $locale => $name) {
|
foreach ($countries as $locale => $name) {
|
||||||
$this->assertEquals($name, $sut->country($locale));
|
$this->assertEquals($name, $sut->country($locale));
|
||||||
}
|
}
|
||||||
@@ -111,7 +117,7 @@ class ExtensionsTest extends TestCase
|
|||||||
*/
|
*/
|
||||||
public function testMoney($result, $amount, $currency, $locale)
|
public function testMoney($result, $amount, $currency, $locale)
|
||||||
{
|
{
|
||||||
$sut = $this->getSut('en', $locale);
|
$sut = $this->getSut($this->localeEn, $locale);
|
||||||
$this->assertEquals($result, $sut->money($amount, $currency));
|
$this->assertEquals($result, $sut->money($amount, $currency));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,12 +143,22 @@ class ExtensionsTest extends TestCase
|
|||||||
{
|
{
|
||||||
$record = $this->getTimesheet(9437);
|
$record = $this->getTimesheet(9437);
|
||||||
|
|
||||||
$sut = $this->getSut('en');
|
$sut = $this->getSut($this->localeEn);
|
||||||
$this->assertEquals('02:37 h', $sut->duration($record->getDuration()));
|
$this->assertEquals('02:37 h', $sut->duration($record->getDuration()));
|
||||||
$this->assertEquals('02:37:17 h', $sut->duration($record->getDuration(), true));
|
$this->assertEquals('02:37:17 h', $sut->duration($record->getDuration(), '%h:%m:%s h'));
|
||||||
|
|
||||||
|
// test Timesheet object
|
||||||
$this->assertEquals('02:37 h', $sut->duration($record));
|
$this->assertEquals('02:37 h', $sut->duration($record));
|
||||||
$this->assertEquals('02:37:17 h', $sut->duration($record, true));
|
$this->assertEquals('02:37:17', $sut->duration($record, '%h:%m:%s'));
|
||||||
|
|
||||||
|
// test extended format
|
||||||
|
$sut = $this->getSut($this->localeFake, 'XX');
|
||||||
|
$this->assertEquals('02 - 37 - 17 Zeit', $sut->duration($record->getDuration(), 'short'));
|
||||||
|
$this->assertEquals('02 Stunden, 37 Minuten und 17 Sekunden', $sut->duration($record->getDuration(), 'full'));
|
||||||
|
|
||||||
|
// test fallback format
|
||||||
|
$sut = $this->getSut($this->localeEn, 'XX');
|
||||||
|
$this->assertEquals('02:37 h', $sut->duration($record->getDuration()));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getTimesheet($seconds)
|
protected function getTimesheet($seconds)
|
||||||
@@ -167,7 +183,7 @@ class ExtensionsTest extends TestCase
|
|||||||
];
|
];
|
||||||
|
|
||||||
// test pre-defined icons
|
// test pre-defined icons
|
||||||
$sut = $this->getSut('en');
|
$sut = $this->getSut($this->localeEn);
|
||||||
foreach ($icons as $icon) {
|
foreach ($icons as $icon) {
|
||||||
$result = $sut->icon($icon);
|
$result = $sut->icon($icon);
|
||||||
$this->assertNotEmpty($result, 'Problem with icon definition: ' . $icon);
|
$this->assertNotEmpty($result, 'Problem with icon definition: ' . $icon);
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class DurationTest extends TestCase
|
|||||||
{
|
{
|
||||||
$sut = new Duration();
|
$sut = new Duration();
|
||||||
$this->assertEquals('02:38', $sut->format(9494));
|
$this->assertEquals('02:38', $sut->format(9494));
|
||||||
$this->assertEquals('02:38:14', $sut->format(9494, true));
|
$this->assertEquals('02:38:14', $sut->format(9494, Duration::FORMAT_WITH_SECONDS));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user