configure am/pm time-format as user preference (#2789)

This commit is contained in:
Kevin Papst
2021-11-14 18:18:26 +01:00
committed by GitHub
parent dce0578a2b
commit 8b0962e192
32 changed files with 216 additions and 260 deletions

View File

@@ -1,30 +1,16 @@
### DATABASE CONFIGURATION # Configure your database connection and set the correct server version:
# Replace "user", "password" and "database" with your database connection. # for MySQL "serverVersion=5.7" and for MariaDB "serverVersion=mariadb-10.5.8"
# Configure the server version, MariaDB requires the "mariadb-" prefix, eg:
# for MySQL "serverVersion=5.7" and for MariaDB "serverVersion=mariadb-10.5.8"
DATABASE_URL=mysql://user:password@127.0.0.1:3306/database?charset=utf8&serverVersion=5.7 DATABASE_URL=mysql://user:password@127.0.0.1:3306/database?charset=utf8&serverVersion=5.7
# Email will be sent with this address as sender
### EMAIL CONFIGURATION
# Emails will be sent "from":
MAILER_FROM=kimai@example.com MAILER_FROM=kimai@example.com
# Email connection (disabled by default) more info at https://www.kimai.org/documentation/emails.html
# Email connection (disabled by default with MAILER_URL=null://null)
# SMTP: smtp://localhost:25?encryption=&auth_mode=
# Google: gmail://username:password@default
# Amazon: ses://ACCESS_KEY:SECRET_KEY@default?region=eu-west-1
# Mailchimp: mandrill://KEY@default
# Mailgun: mailgun://KEY:DOMAIN@default
# Postmark: postmark://ID@default
# Sendgrid: sendgrid://KEY@default
# Disable emails: null://null
MAILER_URL=null://null MAILER_URL=null://null
# do not change, unless you are developing for Kimai
### APPLICATION CONFIGURATION
APP_ENV=prod APP_ENV=prod
# should be changed to a unique character sequence
APP_SECRET=change_this_to_something_unique APP_SECRET=change_this_to_something_unique
# unlikely, that you need to change this one
# Running in a "special" environment, eg. behind reverse proxies?
# Check those:
# TRUSTED_PROXIES=127.0.0.1,127.0.0.2
# TRUSTED_HOSTS=localhost,example.com
CORS_ALLOW_ORIGIN=^https?://localhost(:[0-9]+)?$ CORS_ALLOW_ORIGIN=^https?://localhost(:[0-9]+)?$
# Running behind reverse proxies? Use those:
# TRUSTED_PROXIES=127.0.0.1,127.0.0.2
# TRUSTED_HOSTS=localhost,example.com

View File

@@ -8,6 +8,13 @@ you can upgrade your Kimai installation to the latest stable release.
Check below if there are more version specific steps required, which need to be executed after the normal update process. Check below if there are more version specific steps required, which need to be executed after the normal update process.
Perform EACH version specific task between your version and the new one, otherwise you risk data inconsistency or a broken installation. Perform EACH version specific task between your version and the new one, otherwise you risk data inconsistency or a broken installation.
## [1.16](https://github.com/kevinpapst/kimai2/releases/tag/1.16)
**DEVELOPER**
- Removed `formDateTime` field from API model `I18nConfig`
## [1.15](https://github.com/kevinpapst/kimai2/releases/tag/1.15) ## [1.15](https://github.com/kevinpapst/kimai2/releases/tag/1.15)
**Many database changes: don't forget to [run the updater](https://www.kimai.org/documentation/updates.html).** **Many database changes: don't forget to [run the updater](https://www.kimai.org/documentation/updates.html).**

View File

@@ -165,7 +165,8 @@
"bin/console lint:xliff translations", "bin/console lint:xliff translations",
"bin/console doctrine:schema:validate --skip-sync -vvv --no-interaction" "bin/console doctrine:schema:validate --skip-sync -vvv --no-interaction"
], ],
"kimai:tests": "vendor/bin/phpunit tests/", "tests": "vendor/bin/phpunit tests/",
"kimai:tests": "@tests",
"kimai:tests-unit": "vendor/bin/phpunit --exclude-group integration tests/", "kimai:tests-unit": "vendor/bin/phpunit --exclude-group integration tests/",
"kimai:tests-integration": "vendor/bin/phpunit --group integration tests/", "kimai:tests-integration": "vendor/bin/phpunit --group integration tests/",
"kimai:phpstan": "@phpstan", "kimai:phpstan": "@phpstan",

View File

@@ -214,91 +214,74 @@ kimai:
# -------------------------------------------------------------------------------- # --------------------------------------------------------------------------------
languages: languages:
cs: cs:
date_time_type: 'dd.MM.yyyy HH:mm'
date_type: 'dd.MM.yyyy' date_type: 'dd.MM.yyyy'
date: 'd.m.Y' date: 'd.m.Y'
date_time: 'd.m H:i' date_time: 'd.m H:i'
da: da:
date_time_type: 'dd.MM.yyyy HH:mm'
date_type: 'dd.MM.yyyy' date_type: 'dd.MM.yyyy'
date: 'd.m.Y' date: 'd.m.Y'
date_time: 'd.m. H:i' date_time: 'd.m. H:i'
de: de:
date_time_type: 'dd.MM.yyyy HH:mm'
date_type: 'dd.MM.yyyy' date_type: 'dd.MM.yyyy'
date: 'd.m.Y' date: 'd.m.Y'
date_time: 'd.m. H:i' date_time: 'd.m. H:i'
de_AT: de_AT:
date_time_type: 'dd.MM.yyyy HH:mm'
date_type: 'dd.MM.yyyy' date_type: 'dd.MM.yyyy'
date: 'd.m.Y' date: 'd.m.Y'
date_time: 'd.m. H:i' date_time: 'd.m. H:i'
de_CH: de_CH:
date_time_type: 'dd.MM.yyyy HH:mm'
date_type: 'dd.MM.yyyy' date_type: 'dd.MM.yyyy'
date: 'd.m.Y' date: 'd.m.Y'
date_time: 'd.m. H:i' date_time: 'd.m. H:i'
el: el:
date_time_type: 'dd.MM.yyyy HH:mm'
date_type: 'dd.MM.yyyy' date_type: 'dd.MM.yyyy'
date: 'd.m.Y' date: 'd.m.Y'
date_time: 'd.m. H:i' date_time: 'd.m. H:i'
en: en:
date_time_type: 'yyyy-MM-dd HH:mm'
date_type: 'yyyy-MM-dd' date_type: 'yyyy-MM-dd'
date: 'Y-m-d' date: 'Y-m-d'
date_time: 'm-d H:i' date_time: 'm-d H:i'
duration: '%%h:%%m h' duration: '%%h:%%m h'
es: es:
date_time_type: 'dd.MM.yyyy HH:mm'
date_type: 'dd.MM.yyyy' date_type: 'dd.MM.yyyy'
date: 'd.m.Y' date: 'd.m.Y'
date_time: 'd.m. H:i' date_time: 'd.m. H:i'
fi: fi:
date_time_type: 'dd.MM.yyyy HH:mm'
date_type: 'dd.MM.yyyy' date_type: 'dd.MM.yyyy'
date: 'd.m.Y' date: 'd.m.Y'
date_time: 'd.m. H:i' date_time: 'd.m. H:i'
fr: fr:
date_time_type: 'dd/MM/yyyy HH:mm'
date_type: 'dd/MM/yyyy' date_type: 'dd/MM/yyyy'
date: 'd/m/Y' date: 'd/m/Y'
date_time: 'd/m H:i' date_time: 'd/m H:i'
duration: '%%h h %%m' duration: '%%h h %%m'
he: he:
date_time_type: 'dd/MM/yyyy HH:mm'
date_type: 'dd/MM/yyyy' date_type: 'dd/MM/yyyy'
date: 'd/m/Y' date: 'd/m/Y'
date_time: 'd/m H:i' date_time: 'd/m H:i'
duration: '%%h:%%m' duration: '%%h:%%m'
hu: hu:
date_time_type: 'yyyy.MM.dd. HH:mm'
date_type: 'yyyy.MM.dd.' date_type: 'yyyy.MM.dd.'
date: 'Y.m.d.' date: 'Y.m.d.'
date_time: 'm.d. H:i' date_time: 'm.d. H:i'
it: it:
date_time_type: 'dd.MM.yyyy HH:mm'
date_type: 'dd.MM.yyyy' date_type: 'dd.MM.yyyy'
date: 'd.m.Y' date: 'd.m.Y'
date_time: 'd.m. H:i' date_time: 'd.m. H:i'
nl: nl:
date_time_type: 'dd.MM.yyyy HH:mm'
date_type: 'dd.MM.yyyy' date_type: 'dd.MM.yyyy'
date: 'd.m.Y' date: 'd.m.Y'
date_time: 'd.m. H:i' date_time: 'd.m. H:i'
duration: '%%hu%%m' duration: '%%hu%%m'
pt_BR: pt_BR:
date_time_type: 'dd-MM-yyyy HH:mm'
date_type: 'dd-MM-yyyy' date_type: 'dd-MM-yyyy'
date: 'd-m-Y' date: 'd-m-Y'
date_time: 'd-m H:i' date_time: 'd-m H:i'
ru: ru:
date_time_type: 'dd.MM.yyyy HH:mm'
date_type: 'dd.MM.yyyy' date_type: 'dd.MM.yyyy'
date: 'd.m.Y' date: 'd.m.Y'
date_time: 'd.m. H:i' date_time: 'd.m. H:i'
sk: sk:
date_time_type: 'dd. MM. yyyy HH:mm'
date_type: 'dd. MM. yyyy' date_type: 'dd. MM. yyyy'
date: 'd. m. Y' date: 'd. m. Y'
date_time: 'd. m. H:i' date_time: 'd. m. H:i'
@@ -306,7 +289,6 @@ kimai:
duration: '%%h:%%m tim' duration: '%%h:%%m tim'
date_time: 'd/m H:i' date_time: 'd/m H:i'
pl: pl:
date_time_type: 'dd. MM. yyyy HH:mm'
date_type: 'dd. MM. yyyy' date_type: 'dd. MM. yyyy'
date: 'd. m. Y' date: 'd. m. Y'
date_time: 'd. m. H:i' date_time: 'd. m. H:i'

View File

@@ -64,13 +64,12 @@ final class ConfigurationController extends BaseApiController
$model = new I18nConfig(); $model = new I18nConfig();
$model $model
->setFormDateTime($formats->getDateTimeTypeFormat($locale))
->setFormDate($formats->getDateTypeFormat($locale)) ->setFormDate($formats->getDateTypeFormat($locale))
->setDateTime($formats->getDateTimeFormat($locale)) ->setDateTime($formats->getDateTimeFormat($locale))
->setDate($formats->getDateFormat($locale)) ->setDate($formats->getDateFormat($locale))
->setDuration($formats->getDurationFormat($locale)) ->setDuration($formats->getDurationFormat($locale))
->setTime($formats->getTimeFormat($locale)) ->setTime($formats->getTimeFormat($locale))
->setIs24hours($formats->isTwentyFourHours($locale)) ->setIs24hours($user->is24Hour())
->setNow($this->getDateTimeFactory()->createDateTime()) ->setNow($this->getDateTimeFactory()->createDateTime())
; ;

View File

@@ -18,17 +18,6 @@ use JMS\Serializer\Annotation as Serializer;
*/ */
final class I18nConfig final class I18nConfig
{ {
/**
* Format used for 'begin' and 'end'
*
* @var string
*
* @Serializer\Expose()
* @Serializer\Groups({"Default"})
* @Serializer\Type(name="string")
* @phpstan-ignore-next-line
*/
private $formDateTime = '';
/** /**
* Format used for toolbar queries * Format used for toolbar queries
* *
@@ -114,13 +103,6 @@ final class I18nConfig
return $this; return $this;
} }
public function setFormDateTime(string $formDateTime): I18nConfig
{
$this->formDateTime = $formDateTime;
return $this;
}
public function setFormDate(string $formDate): I18nConfig public function setFormDate(string $formDate): I18nConfig
{ {
$this->formDate = $formDate; $this->formDate = $formDate;

View File

@@ -60,28 +60,6 @@ final class LanguageFormattings
return $this->momentFormatter->convert($this->getDateTypeFormat($locale)); return $this->momentFormatter->convert($this->getDateTypeFormat($locale));
} }
/**
* Returns the format which is used by the form component to handle datetime values.
*
* @param string $locale
* @return string
*/
public function getDateTimeTypeFormat(string $locale): string
{
return $this->getConfig('date_time_type', $locale);
}
/**
* Returns the format which is used by the Javascript component to handle datetime values.
*
* @param string $locale
* @return string
*/
public function getDateTimePickerFormat(string $locale): string
{
return $this->momentFormatter->convert($this->getDateTimeTypeFormat($locale));
}
/** /**
* Returns the locale specific date format, which should be used in combination with the twig filter "|date". * Returns the locale specific date format, which should be used in combination with the twig filter "|date".
* *
@@ -126,17 +104,6 @@ final class LanguageFormattings
return $this->getConfig('duration', $locale); return $this->getConfig('duration', $locale);
} }
/**
* Returns whether this locale uses the 24 hour format.
*
* @param string $locale
* @return bool
*/
public function isTwentyFourHours(string $locale): bool
{
return (bool) $this->getConfig('24_hours', $locale);
}
/** /**
* @param string $key * @param string $key
* @param string $locale * @param string $locale

View File

@@ -329,13 +329,19 @@ class Configuration implements ConfigurationInterface
->useAttributeAsKey('name', false) // see https://github.com/symfony/symfony/issues/18988 ->useAttributeAsKey('name', false) // see https://github.com/symfony/symfony/issues/18988
->arrayPrototype() ->arrayPrototype()
->children() ->children()
->scalarNode('date_time_type')->defaultValue('yyyy-MM-dd HH:mm')->end() // for DateTimeType ->scalarNode('date_time_type') // for DateTimeType
->defaultValue('yyyy-MM-dd HH:mm')
->setDeprecated('date_time_type is deprecated since 1.16 and was replaced by the 24 user configuration')
->end()
->scalarNode('date_type')->defaultValue('yyyy-MM-dd')->end() // for DateType ->scalarNode('date_type')->defaultValue('yyyy-MM-dd')->end() // for DateType
->scalarNode('date')->defaultValue('Y-m-d')->end() // for display via twig ->scalarNode('date')->defaultValue('Y-m-d')->end() // for display via twig
->scalarNode('date_time')->defaultValue('m-d H:i')->end() // for display via twig ->scalarNode('date_time')->defaultValue('m-d H:i')->end() // for display via twig
->scalarNode('duration')->defaultValue('%%h:%%m h')->end() // for display via twig ->scalarNode('duration')->defaultValue('%%h:%%m h')->end() // for display via twig
->scalarNode('time')->defaultValue('H:i')->end() // for display via twig ->scalarNode('time')->defaultValue('H:i')->end() // for display via twig
->booleanNode('24_hours')->defaultTrue()->end() // for DateTimeType JS component ->booleanNode('24_hours') // for DateTimeType JS component
->defaultTrue()
->setDeprecated('24_hours is deprecated since 1.16 and a user configuration now')
->end()
->end() ->end()
->end() ->end()
; ;

View File

@@ -430,6 +430,20 @@ class User implements UserInterface, EquatableInterface, \Serializable
return null; return null;
} }
public function getTimeFormat(): string
{
if ($this->is24Hour()) {
return 'H:i';
}
return 'h:i A';
}
public function is24Hour(): bool
{
return (bool) $this->getPreferenceValue(UserPreference::HOUR_24, true);
}
public function getLocale(): string public function getLocale(): string
{ {
return $this->getPreferenceValue(UserPreference::LOCALE, User::DEFAULT_LANGUAGE); return $this->getPreferenceValue(UserPreference::LOCALE, User::DEFAULT_LANGUAGE);

View File

@@ -30,6 +30,7 @@ class UserPreference
public const INTERNAL_RATE = 'internal_rate'; public const INTERNAL_RATE = 'internal_rate';
public const SKIN = 'skin'; public const SKIN = 'skin';
public const LOCALE = 'language'; public const LOCALE = 'language';
public const HOUR_24 = 'hours_24';
public const TIMEZONE = 'timezone'; public const TIMEZONE = 'timezone';
public const FIRST_WEEKDAY = 'first_weekday'; public const FIRST_WEEKDAY = 'first_weekday';

View File

@@ -112,6 +112,13 @@ final class UserPreferenceSubscriber implements EventSubscriberInterface
->setSection('locale') ->setSection('locale')
->setType(FirstWeekDayType::class), ->setType(FirstWeekDayType::class),
(new UserPreference())
->setName(UserPreference::HOUR_24)
->setValue(true)
->setOrder(305)
->setSection('locale')
->setType(CheckboxType::class),
(new UserPreference()) (new UserPreference())
->setName(UserPreference::SKIN) ->setName(UserPreference::SKIN)
->setValue($this->configuration->getUserDefaultTheme()) ->setValue($this->configuration->getUserDefaultTheme())

View File

@@ -10,11 +10,15 @@
namespace App\Form\Type; namespace App\Form\Type;
use App\API\BaseApiController; use App\API\BaseApiController;
use App\Entity\User;
use App\Utils\DateFormatConverter;
use App\Utils\LocaleSettings; use App\Utils\LocaleSettings;
use App\Utils\MomentFormatConverter;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\DateTimeType; use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView; use Symfony\Component\Form\FormView;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\OptionsResolver\OptionsResolver;
/** /**
@@ -34,9 +38,6 @@ class DateTimePickerType extends AbstractType
*/ */
public function configureOptions(OptionsResolver $resolver) public function configureOptions(OptionsResolver $resolver)
{ {
$dateTimePicker = $this->localeSettings->getDateTimePickerFormat();
$dateTimeFormat = $this->localeSettings->getDateTimeTypeFormat();
$resolver->setDefaults([ $resolver->setDefaults([
'documentation' => [ 'documentation' => [
'type' => 'string', 'type' => 'string',
@@ -46,8 +47,18 @@ class DateTimePickerType extends AbstractType
'label' => 'label.begin', 'label' => 'label.begin',
'widget' => 'single_text', 'widget' => 'single_text',
'html5' => false, 'html5' => false,
'format' => $dateTimeFormat, 'format' => function (Options $options) {
'format_picker' => $dateTimePicker, /** @var User $user */
$user = $options['user'];
$converter = new DateFormatConverter();
return $this->localeSettings->getDateTypeFormat() . ' ' . $converter->convert($user->getTimeFormat()); // PHP
},
'format_picker' => function (Options $options) {
$converter = new MomentFormatConverter();
return $converter->convert($options['format']); // JS
},
'with_seconds' => false, 'with_seconds' => false,
'time_increment' => 1, 'time_increment' => 1,
]); ]);

View File

@@ -15,6 +15,7 @@ use App\Entity\User;
use App\Utils\LocaleFormats; use App\Utils\LocaleFormats;
use App\Utils\LocaleFormatter; use App\Utils\LocaleFormatter;
use DateTime; use DateTime;
use Symfony\Component\Security\Core\Security;
use Twig\Extension\AbstractExtension; use Twig\Extension\AbstractExtension;
use Twig\TwigFilter; use Twig\TwigFilter;
use Twig\TwigFunction; use Twig\TwigFunction;
@@ -22,10 +23,9 @@ use Twig\TwigTest;
final class LocaleFormatExtensions extends AbstractExtension final class LocaleFormatExtensions extends AbstractExtension
{ {
/**
* @var LanguageFormattings|null
*/
private $formats; private $formats;
private $security;
/** /**
* @var LocaleFormats|null * @var LocaleFormats|null
*/ */
@@ -38,10 +38,12 @@ final class LocaleFormatExtensions extends AbstractExtension
* @var string * @var string
*/ */
private $locale; private $locale;
private $userFormat;
public function __construct(LanguageFormattings $formats) public function __construct(LanguageFormattings $formats, Security $security)
{ {
$this->formats = $formats; $this->formats = $formats;
$this->security = $security;
} }
/** /**
@@ -169,7 +171,18 @@ final class LocaleFormatExtensions extends AbstractExtension
*/ */
public function dateTimeFull($date, bool $stripMidnight = false) public function dateTimeFull($date, bool $stripMidnight = false)
{ {
return $this->getFormatter()->dateTimeFull($date, $stripMidnight); return $this->getFormatter()->dateTimeFull($date, $this->getUserTimeFormat(), $stripMidnight);
}
private function getUserTimeFormat(): string
{
if ($this->userFormat === null) {
/** @var User|null $user */
$user = $this->security->getUser();
$this->userFormat = $user !== null ? $user->getTimeFormat() : 'H:i';
}
return $this->userFormat;
} }
public function createDate(string $date, ?User $user = null): \DateTime public function createDate(string $date, ?User $user = null): \DateTime
@@ -201,7 +214,7 @@ final class LocaleFormatExtensions extends AbstractExtension
*/ */
public function time($date) public function time($date)
{ {
return $this->getFormatter()->time($date); return $this->getFormatter()->time($date, $this->getUserTimeFormat());
} }
/** /**
@@ -240,7 +253,16 @@ final class LocaleFormatExtensions extends AbstractExtension
*/ */
public function hour24($twentyFour, $twelveHour) public function hour24($twentyFour, $twelveHour)
{ {
return $this->getFormatter()->hour24($twentyFour, $twelveHour); @trigger_error('Twig filter "hour24" is deprecated, use app.user.is24Hour() instead', E_USER_DEPRECATED);
/** @var User|null $user */
$user = $this->security->getUser();
if (null === $user) {
return true;
}
return $user->is24Hour();
} }
public function getDurationFormat(): string public function getDurationFormat(): string

View File

@@ -0,0 +1,33 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Utils;
class DateFormatConverter
{
/**
* This defines the mapping between PHP date format (key) and ICU date format (value).
* https://unicode-org.github.io/icu/userguide/format_parse/datetime/#datetime-format-syntax
*
* @var array
*/
private static $formatConvertRules = [
// hours
'h' => 'hh', 'H' => 'HH',
// minutes
'i' => 'mm',
// am/pm to AM/PM
'A' => 'a'
];
public function convert(string $format): string
{
return strtr($format, self::$formatConvertRules);
}
}

View File

@@ -75,21 +75,12 @@ class LocaleFormats
/** /**
* Returns the format which is used by the form component to handle datetime values. * Returns the format which is used by the form component to handle datetime values.
* *
* @deprecated since 1.16
* @return string * @return string
*/ */
public function getDateTimeTypeFormat(): string public function getDateTimeTypeFormat(): string
{ {
return $this->formats->getDateTimeTypeFormat($this->getLocale()); return $this->formats->getDateTypeFormat($this->getLocale()) . ' HH:mm';
}
/**
* Returns the format which is used by the Javascript component to handle datetime values.
*
* @return string
*/
public function getDateTimePickerFormat(): string
{
return $this->formats->getDateTimePickerFormat($this->getLocale());
} }
/** /**
@@ -131,14 +122,4 @@ class LocaleFormats
{ {
return $this->formats->getDurationFormat($this->getLocale()); return $this->formats->getDurationFormat($this->getLocale());
} }
/**
* Returns whether this locale uses the 24 hour format.
*
* @return bool
*/
public function isTwentyFourHours(): bool
{
return $this->formats->isTwentyFourHours($this->getLocale());
}
} }

View File

@@ -46,6 +46,10 @@ final class LocaleFormatter
* @var string * @var string
*/ */
private $dateTimeFormat = null; private $dateTimeFormat = null;
/**
* @var string
*/
private $dateTypeFormat = null;
/** /**
* @var string * @var string
*/ */
@@ -54,10 +58,6 @@ final class LocaleFormatter
* @var string * @var string
*/ */
private $timeFormat = null; private $timeFormat = null;
/**
* @var bool
*/
private $isTwentyFourHour = null;
public function __construct(LanguageFormattings $formats, string $locale) public function __construct(LanguageFormattings $formats, string $locale)
{ {
@@ -216,6 +216,15 @@ final class LocaleFormatter
return $date->format($this->dateFormat); return $date->format($this->dateFormat);
} }
private function getDateTypeFormat(): string
{
if (null === $this->dateTypeFormat) {
$this->dateTypeFormat = $this->localeFormats->getDateTypeFormat();
}
return $this->dateTypeFormat;
}
/** /**
* @param DateTime|string $date * @param DateTime|string $date
* @return string * @return string
@@ -239,13 +248,15 @@ final class LocaleFormatter
/** /**
* @param DateTime|string $date * @param DateTime|string $date
* @param string $timeFormat
* @param bool $stripMidnight * @param bool $stripMidnight
* @return bool|false|string * @return bool|false|string
*/ */
public function dateTimeFull($date, bool $stripMidnight = false) public function dateTimeFull($date, string $timeFormat, bool $stripMidnight = false)
{ {
if (null === $this->dateTimeTypeFormat) { if (null === $this->dateTimeTypeFormat) {
$this->dateTimeTypeFormat = $this->localeFormats->getDateTimeTypeFormat(); $converter = new DateFormatConverter();
$this->dateTimeTypeFormat = $this->getDateTypeFormat() . ' ' . $converter->convert($timeFormat);
} }
if (!$date instanceof DateTime) { if (!$date instanceof DateTime) {
@@ -298,7 +309,7 @@ final class LocaleFormatter
* @return string * @return string
* @throws Exception * @throws Exception
*/ */
public function time($date) public function time($date, string $format = null)
{ {
if (null === $this->timeFormat) { if (null === $this->timeFormat) {
$this->timeFormat = $this->localeFormats->getTimeFormat(); $this->timeFormat = $this->localeFormats->getTimeFormat();
@@ -308,7 +319,7 @@ final class LocaleFormatter
$date = new DateTime($date); $date = new DateTime($date);
} }
return $date->format($this->timeFormat); return $date->format($format ?? $this->timeFormat);
} }
/** /**
@@ -342,22 +353,4 @@ final class LocaleFormatter
{ {
return $this->formatIntl($dateTime, ($short ? 'EE' : 'EEEE')); return $this->formatIntl($dateTime, ($short ? 'EE' : 'EEEE'));
} }
/**
* @param mixed $twentyFour
* @param mixed $twelveHour
* @return mixed
*/
public function hour24($twentyFour, $twelveHour)
{
if (null === $this->isTwentyFourHour) {
$this->isTwentyFourHour = $this->localeFormats->isTwentyFourHours();
}
if (true === $this->isTwentyFourHour) {
return $twentyFour;
}
return $twelveHour;
}
} }

View File

@@ -18,8 +18,8 @@ class MomentFormatConverter
{ {
/** /**
* This defines the mapping between PHP ICU date format (key) and moment.js date format (value) * This defines the mapping between PHP ICU date format (key) and moment.js date format (value)
* For ICU formats see http://userguide.icu-project.org/formatparse/datetime#TOC-Date-Time-Format-Syntax * For ICU formats see https://unicode-org.github.io/icu/userguide/format_parse/datetime/#datetime-format-syntax
* For Moment formats see http://momentjs.com/docs/#/displaying/format/. * For Moment formats see http://momentjs.com/docs/#/displaying/format/
* *
* @var array * @var array
*/ */
@@ -34,6 +34,8 @@ class MomentFormatConverter
'ZZZZZ' => 'Z', 'ZZZ' => 'ZZ', 'ZZZZZ' => 'Z', 'ZZZ' => 'ZZ',
// letter 'T' // letter 'T'
'\'T\'' => 'T', '\'T\'' => 'T',
// am/pm to AM/PM
'a' => 'A',
]; ];
/** /**

View File

@@ -296,7 +296,7 @@
login: '{{ path('fos_user_security_login') }}', login: '{{ path('fos_user_security_login') }}',
locale: '{{ app.request.locale }}', locale: '{{ app.request.locale }}',
first_dow_iso: {{ iso_day_by_name(app.user.firstDayOfWeek) }}, first_dow_iso: {{ iso_day_by_name(app.user.firstDayOfWeek) }},
twentyFourHours: {{ 'true'|hour24('false') }}, twentyFourHours: {{ app.user.is24Hour() ? 'true' : 'false' }},
autoComplete: {{ kimai_config.themeAutocompleteCharacters }}, autoComplete: {{ kimai_config.themeAutocompleteCharacters }},
defaultColor: '{{ constant('App\\Constants::DEFAULT_COLOR') }}', defaultColor: '{{ constant('App\\Constants::DEFAULT_COLOR') }}',
updateBrowserTitle: {% if app.user.preferenceValue('theme.update_browser_title') %}true{% else %}false{% endif %} updateBrowserTitle: {% if app.user.preferenceValue('theme.update_browser_title') %}true{% else %}false{% endif %}

View File

@@ -309,7 +309,7 @@
| |
<label class="control-label" for="begin-format"> <label class="control-label" for="begin-format">
{{ 'label.begin'|trans }}: {{ 'label.begin'|trans }}:
{% set demo_date = create_date('2020-01-01 13:00:00') %} {% set demo_date = create_date('2020-01-01 11:00:00') %}
<select id="begin-format" name="begin-format"> <select id="begin-format" name="begin-format">
<option value="plain">{{ demo_date|date_format('H:i') }}</option> <option value="plain">{{ demo_date|date_format('H:i') }}</option>
<option value="time">{{ demo_date|date_time }}</option> <option value="time">{{ demo_date|date_time }}</option>

View File

@@ -29,13 +29,13 @@ class ConfigurationControllerTest extends APIControllerBaseTest
$this->assertIsArray($result); $this->assertIsArray($result);
$this->assertNotEmpty($result); $this->assertNotEmpty($result);
$this->assertEquals(8, \count($result)); $this->assertEquals(7, \count($result));
$this->assertI18nStructure($result); $this->assertI18nStructure($result);
} }
protected function assertI18nStructure(array $result) protected function assertI18nStructure(array $result)
{ {
$expectedKeys = ['date', 'dateTime', 'duration', 'formDate', 'formDateTime', 'is24hours', 'time', 'now']; $expectedKeys = ['date', 'dateTime', 'duration', 'formDate', 'is24hours', 'time', 'now'];
$actual = array_keys($result); $actual = array_keys($result);
sort($actual); sort($actual);
sort($expectedKeys); sort($expectedKeys);

View File

@@ -26,7 +26,6 @@ class I18nConfigTest extends TestCase
$this->assertInstanceOf(I18nConfig::class, $sut->setDate('bar')); $this->assertInstanceOf(I18nConfig::class, $sut->setDate('bar'));
$this->assertInstanceOf(I18nConfig::class, $sut->setDateTime('hello')); $this->assertInstanceOf(I18nConfig::class, $sut->setDateTime('hello'));
$this->assertInstanceOf(I18nConfig::class, $sut->setFormDate('world')); $this->assertInstanceOf(I18nConfig::class, $sut->setFormDate('world'));
$this->assertInstanceOf(I18nConfig::class, $sut->setFormDateTime('testing'));
$this->assertInstanceOf(I18nConfig::class, $sut->setTime('fun')); $this->assertInstanceOf(I18nConfig::class, $sut->setTime('fun'));
} }
} }

View File

@@ -26,61 +26,50 @@ class LanguageFormattingsTest extends TestCase
{ {
return [ return [
'de' => [ 'de' => [
'date_time_type' => 'dd.MM.yyyy HH:mm',
'date_type' => 'dd.MM.yyyy', 'date_type' => 'dd.MM.yyyy',
'date' => 'd.m.Y', 'date' => 'd.m.Y',
'date_time' => 'd.m. H:i', 'date_time' => 'd.m. H:i',
'duration' => '%h:%m h', 'duration' => '%h:%m h',
'time' => 'H:i', 'time' => 'H:i',
'24_hours' => true,
], ],
'en' => [ 'en' => [
'date_time_type' => 'yyyy-MM-dd HH:mm',
'date_type' => 'yyyy-MM-dd', 'date_type' => 'yyyy-MM-dd',
'date' => 'Y-m-d', 'date' => 'Y-m-d',
'date_time' => 'm-d H:i', 'date_time' => 'm-d H:i',
'duration' => '%h:%m h', 'duration' => '%h:%m h',
'time' => 'H:i:s', 'time' => 'H:i:s',
'24_hours' => false,
], ],
'pt_BR' => [ 'pt_BR' => [
'date_time_type' => 'dd-MM-yyyy HH:mm',
'date_type' => 'dd-MM-yyyy', 'date_type' => 'dd-MM-yyyy',
'date' => 'd-m-Y', 'date' => 'd-m-Y',
'duration' => '%h:%m h', 'duration' => '%h:%m h',
], ],
'it' => [ 'it' => [
'date_time_type' => 'dd.MM.yyyy HH:mm',
'date_type' => 'dd.MM.yyyy', 'date_type' => 'dd.MM.yyyy',
'date' => 'd.m.Y', 'date' => 'd.m.Y',
'duration' => '%h:%m h', 'duration' => '%h:%m h',
], ],
'fr' => [ 'fr' => [
'date_time_type' => 'dd/MM/yyyy HH:mm',
'date_type' => 'dd/MM/yyyy', 'date_type' => 'dd/MM/yyyy',
'date' => 'd/m/Y', 'date' => 'd/m/Y',
'duration' => '%h h %m', 'duration' => '%h h %m',
], ],
'es' => [ 'es' => [
'date_time_type' => 'dd.MM.yyyy HH:mm',
'date_type' => 'dd.MM.yyyy', 'date_type' => 'dd.MM.yyyy',
'date' => 'd.m.Y', 'date' => 'd.m.Y',
'duration' => '%h:%m h', 'duration' => '%h:%m h',
], ],
'ru' => [ 'ru' => [
'date_time_type' => 'dd.MM.yyyy HH:mm',
'date_type' => 'dd.MM.yyyy', 'date_type' => 'dd.MM.yyyy',
'date' => 'd.m.Y', 'date' => 'd.m.Y',
'duration' => '%h:%m h', 'duration' => '%h:%m h',
], ],
'ar' => [ 'ar' => [
'date_time_type' => 'yyyy-MM-dd HH:mm',
'date_type' => 'yyyy-MM-dd', 'date_type' => 'yyyy-MM-dd',
'date' => 'Y-m-d', 'date' => 'Y-m-d',
'duration' => '%h:%m h', 'duration' => '%h:%m h',
], ],
'hu' => [ 'hu' => [
'date_time_type' => 'yyyy.MM.dd HH:mm',
'date_type' => 'yyyy.MM.dd', 'date_type' => 'yyyy.MM.dd',
'date' => 'Y.m.d.', 'date' => 'Y.m.d.',
'duration' => '%h:%m h', 'duration' => '%h:%m h',
@@ -136,40 +125,10 @@ class LanguageFormattingsTest extends TestCase
$this->assertEquals('DD.MM.YYYY', $sut->getDatePickerFormat('de')); $this->assertEquals('DD.MM.YYYY', $sut->getDatePickerFormat('de'));
} }
public function testGetDateTimeTypeFormat()
{
$sut = $this->getSut($this->getDefaultSettings());
$this->assertEquals('dd.MM.yyyy HH:mm', $sut->getDateTimeTypeFormat('de'));
}
public function testGetDateTimePickerFormat()
{
$sut = $this->getSut($this->getDefaultSettings());
$this->assertEquals('DD.MM.YYYY HH:mm', $sut->getDateTimePickerFormat('de'));
}
public function testIs24Hours()
{
$sut = $this->getSut($this->getDefaultSettings());
$this->assertTrue($sut->isTwentyFourHours('de'));
$this->assertFalse($sut->isTwentyFourHours('en'));
}
public function testGetTimeFormat() public function testGetTimeFormat()
{ {
$sut = $this->getSut($this->getDefaultSettings()); $sut = $this->getSut($this->getDefaultSettings());
$this->assertEquals('H:i', $sut->getTimeFormat('de')); $this->assertEquals('H:i', $sut->getTimeFormat('de'));
$this->assertEquals('H:i:s', $sut->getTimeFormat('en')); $this->assertEquals('H:i:s', $sut->getTimeFormat('en'));
} }
public function testUnknownSetting()
{
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('Unknown setting for locale en: date_time_type');
$sut = $this->getSut(['en' => [
'xxx' => 'dd.MM.yyyy HH:mm',
]]);
$sut->getDateTimePickerFormat('en');
}
} }

View File

@@ -430,7 +430,7 @@ class ProfileControllerTest extends ControllerBaseTest
2 => ['name' => UserPreference::TIMEZONE, 'value' => 'America/Creston'], 2 => ['name' => UserPreference::TIMEZONE, 'value' => 'America/Creston'],
3 => ['name' => UserPreference::LOCALE, 'value' => 'ar'], 3 => ['name' => UserPreference::LOCALE, 'value' => 'ar'],
4 => ['name' => UserPreference::FIRST_WEEKDAY, 'value' => 'sunday'], 4 => ['name' => UserPreference::FIRST_WEEKDAY, 'value' => 'sunday'],
5 => ['name' => UserPreference::SKIN, 'value' => 'blue'], 6 => ['name' => UserPreference::SKIN, 'value' => 'blue'],
] ]
] ]
]); ]);

View File

@@ -49,6 +49,7 @@ class UserTest extends TestCase
self::assertFalse($user->canSeeAllData()); self::assertFalse($user->canSeeAllData());
self::assertFalse($user->isSmallLayout()); self::assertFalse($user->isSmallLayout());
self::assertFalse($user->isExportDecimal()); self::assertFalse($user->isExportDecimal());
self::assertTrue($user->is24Hour());
$user->setAvatar('https://www.gravatar.com/avatar/00000000000000000000000000000000?d=retro&f=y'); $user->setAvatar('https://www.gravatar.com/avatar/00000000000000000000000000000000?d=retro&f=y');
self::assertEquals('https://www.gravatar.com/avatar/00000000000000000000000000000000?d=retro&f=y', $user->getAvatar()); self::assertEquals('https://www.gravatar.com/avatar/00000000000000000000000000000000?d=retro&f=y', $user->getAvatar());
@@ -481,4 +482,15 @@ class UserTest extends TestCase
$member->setUser(new User()); $member->setUser(new User());
$sut->addMembership($member); $sut->addMembership($member);
} }
public function test24Hour()
{
$user = new User();
self::assertTrue($user->is24Hour());
self::assertEquals('H:i', $user->getTimeFormat());
$user->setPreferenceValue(UserPreference::HOUR_24, false);
self::assertFalse($user->is24Hour());
self::assertEquals('h:i A', $user->getTimeFormat());
}
} }

View File

@@ -30,6 +30,7 @@ class UserPreferenceSubscriberTest extends TestCase
'language', 'language',
'first_weekday', 'first_weekday',
'skin', 'skin',
'hours_24',
'theme.layout', 'theme.layout',
'theme.collapsed_sidebar', 'theme.collapsed_sidebar',
'theme.update_browser_title', 'theme.update_browser_title',

View File

@@ -34,6 +34,7 @@ use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
use Symfony\Component\Security\Core\Security;
use Symfony\Contracts\Translation\TranslatorInterface; use Symfony\Contracts\Translation\TranslatorInterface;
abstract class AbstractRendererTest extends KernelTestCase abstract class AbstractRendererTest extends KernelTestCase
@@ -52,8 +53,11 @@ abstract class AbstractRendererTest extends KernelTestCase
] ]
]; ];
$security = $this->createMock(Security::class);
$security->expects($this->any())->method('getUser')->willReturn(new User());
$translator = $this->createMock(TranslatorInterface::class); $translator = $this->createMock(TranslatorInterface::class);
$dateExtension = new LocaleFormatExtensions(new LanguageFormattings($languages)); $dateExtension = new LocaleFormatExtensions(new LanguageFormattings($languages), $security);
$dispatcher = new EventDispatcher(); $dispatcher = new EventDispatcher();
$dispatcher->addSubscriber(new MetaFieldColumnSubscriber()); $dispatcher->addSubscriber(new MetaFieldColumnSubscriber());

View File

@@ -10,10 +10,14 @@
namespace App\Tests\Export\Renderer; namespace App\Tests\Export\Renderer;
use App\Activity\ActivityStatisticService; use App\Activity\ActivityStatisticService;
use App\Entity\User;
use App\Export\Renderer\HtmlRenderer; use App\Export\Renderer\HtmlRenderer;
use App\Project\ProjectStatisticService; use App\Project\ProjectStatisticService;
use Symfony\Bridge\Twig\AppVariable;
use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Twig\Environment; use Twig\Environment;
/** /**
@@ -43,6 +47,16 @@ class HtmlRendererTest extends AbstractRendererTest
$kernel = self::bootKernel(); $kernel = self::bootKernel();
/** @var Environment $twig */ /** @var Environment $twig */
$twig = $kernel->getContainer()->get('twig'); $twig = $kernel->getContainer()->get('twig');
$token = $this->createMock(TokenInterface::class);
$token->expects($this->any())->method('getUser')->willReturn(new User());
$tokenStorage = new TokenStorage();
$tokenStorage->setToken($token);
/** @var AppVariable $app */
$app = $twig->getGlobals()['app'];
$twig->addGlobal('app', $app);
$app->setTokenStorage($tokenStorage);
$stack = $kernel->getContainer()->get('request_stack'); $stack = $kernel->getContainer()->get('request_stack');
$request = new Request(); $request = new Request();
$request->setLocale('en'); $request->setLocale('en');

View File

@@ -33,6 +33,7 @@ use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
use Symfony\Component\Security\Core\Security;
use Symfony\Contracts\Translation\TranslatorInterface; use Symfony\Contracts\Translation\TranslatorInterface;
abstract class AbstractRendererTest extends KernelTestCase abstract class AbstractRendererTest extends KernelTestCase
@@ -51,8 +52,11 @@ abstract class AbstractRendererTest extends KernelTestCase
] ]
]; ];
$security = $this->createMock(Security::class);
$security->expects($this->any())->method('getUser')->willReturn(new User());
$translator = $this->getMockBuilder(TranslatorInterface::class)->getMock(); $translator = $this->getMockBuilder(TranslatorInterface::class)->getMock();
$dateExtension = new LocaleFormatExtensions(new LanguageFormattings($languages)); $dateExtension = new LocaleFormatExtensions(new LanguageFormattings($languages), $security);
$dispatcher = new EventDispatcher(); $dispatcher = new EventDispatcher();
$dispatcher->addSubscriber(new MetaFieldColumnSubscriber()); $dispatcher->addSubscriber(new MetaFieldColumnSubscriber());

View File

@@ -15,6 +15,7 @@ use App\Entity\User;
use App\Twig\LocaleFormatExtensions; use App\Twig\LocaleFormatExtensions;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Symfony\Component\Intl\Util\IntlTestHelper; use Symfony\Component\Intl\Util\IntlTestHelper;
use Symfony\Component\Security\Core\Security;
use Twig\TwigFilter; use Twig\TwigFilter;
use Twig\TwigFunction; use Twig\TwigFunction;
use Twig\TwigTest; use Twig\TwigTest;
@@ -43,7 +44,11 @@ class LocaleFormatExtensionsTest extends TestCase
$language = $dateSettings; $language = $dateSettings;
$dateSettings = $locale; $dateSettings = $locale;
} }
$sut = new LocaleFormatExtensions(new LanguageFormattings($dateSettings));
$security = $this->createMock(Security::class);
$security->expects($this->any())->method('getUser')->willReturn(new User());
$sut = new LocaleFormatExtensions(new LanguageFormattings($dateSettings), $security);
$sut->setLocale($language); $sut->setLocale($language);
return $sut; return $sut;
@@ -220,31 +225,21 @@ class LocaleFormatExtensionsTest extends TestCase
$this->assertEquals('17:53', $sut->time('2016-06-23 17:53')); $this->assertEquals('17:53', $sut->time('2016-06-23 17:53'));
} }
public function testHour24()
{
$sut = $this->getSut('en', [
'en' => ['24_hours' => false],
]);
$this->assertEquals('bar', $sut->hour24('foo', 'bar'));
$sut = $this->getSut('de', [
'de' => ['24_hours' => true],
]);
$this->assertEquals('foo', $sut->hour24('foo', 'bar'));
}
public function testDateTimeFull() public function testDateTimeFull()
{ {
$sut = $this->getSut('en', [ $sut = $this->getSut('en', [
'en' => ['date_time_type' => 'yyyy-MM-dd HH:mm:ss'], 'en' => ['date_type' => 'dd-yyyy-MM-'],
]); ]);
$dateTime = new \DateTime('2019-08-17 12:29:47', new \DateTimeZone(date_default_timezone_get())); $dateTime = new \DateTime('2019-08-17 12:29:47', new \DateTimeZone(date_default_timezone_get()));
$dateTime->setDate(2019, 8, 17); $dateTime->setDate(2019, 8, 17);
$dateTime->setTime(12, 29, 47); $dateTime->setTime(12, 29, 47);
$this->assertEquals('2019-08-17 12:29:47', $sut->dateTimeFull($dateTime)); $this->assertEquals('17-2019-08- 12:29', $sut->dateTimeFull($dateTime));
$this->assertEquals('2019-08-17 12:29:47', $sut->dateTimeFull('2019-08-17 12:29:47')); $this->assertEquals('17-2019-08- 12:29', $sut->dateTimeFull('2019-08-17 12:29:47'));
$dateTime = new \DateTime('2019-08-17 00:00:00');
$this->assertEquals('17-2019-08-', $sut->dateTimeFull($dateTime, true));
// next test checks the fallback for errors while converting the date // next test checks the fallback for errors while converting the date
/* @phpstan-ignore-next-line */ /* @phpstan-ignore-next-line */

View File

@@ -28,61 +28,50 @@ class LocaleFormatsTest extends TestCase
{ {
return [ return [
'de' => [ 'de' => [
'date_time_type' => 'dd.MM.yyyy HH:mm',
'date_type' => 'dd.MM.yyyy', 'date_type' => 'dd.MM.yyyy',
'date' => 'd.m.Y', 'date' => 'd.m.Y',
'date_time' => 'd.m. H:i', 'date_time' => 'd.m. H:i',
'duration' => '%h:%m h', 'duration' => '%h:%m h',
'time' => 'H:i', 'time' => 'H:i',
'24_hours' => true,
], ],
'en' => [ 'en' => [
'date_time_type' => 'yyyy-MM-dd HH:mm',
'date_type' => 'yyyy-MM-dd', 'date_type' => 'yyyy-MM-dd',
'date' => 'Y-m-d', 'date' => 'Y-m-d',
'date_time' => 'm-d H:i', 'date_time' => 'm-d H:i',
'duration' => '%h:%m h', 'duration' => '%h:%m h',
'time' => 'H:i:s', 'time' => 'H:i:s',
'24_hours' => false,
], ],
'pt_BR' => [ 'pt_BR' => [
'date_time_type' => 'dd-MM-yyyy HH:mm',
'date_type' => 'dd-MM-yyyy', 'date_type' => 'dd-MM-yyyy',
'date' => 'd-m-Y', 'date' => 'd-m-Y',
'duration' => '%h:%m h', 'duration' => '%h:%m h',
], ],
'it' => [ 'it' => [
'date_time_type' => 'dd.MM.yyyy HH:mm',
'date_type' => 'dd.MM.yyyy', 'date_type' => 'dd.MM.yyyy',
'date' => 'd.m.Y', 'date' => 'd.m.Y',
'duration' => '%h:%m h', 'duration' => '%h:%m h',
], ],
'fr' => [ 'fr' => [
'date_time_type' => 'dd/MM/yyyy HH:mm',
'date_type' => 'dd/MM/yyyy', 'date_type' => 'dd/MM/yyyy',
'date' => 'd/m/Y', 'date' => 'd/m/Y',
'duration' => '%h h %m', 'duration' => '%h h %m',
], ],
'es' => [ 'es' => [
'date_time_type' => 'dd.MM.yyyy HH:mm',
'date_type' => 'dd.MM.yyyy', 'date_type' => 'dd.MM.yyyy',
'date' => 'd.m.Y', 'date' => 'd.m.Y',
'duration' => '%h:%m h', 'duration' => '%h:%m h',
], ],
'ru' => [ 'ru' => [
'date_time_type' => 'dd.MM.yyyy HH:mm',
'date_type' => 'dd.MM.yyyy', 'date_type' => 'dd.MM.yyyy',
'date' => 'd.m.Y', 'date' => 'd.m.Y',
'duration' => '%h:%m h', 'duration' => '%h:%m h',
], ],
'ar' => [ 'ar' => [
'date_time_type' => 'yyyy-MM-dd HH:mm',
'date_type' => 'yyyy-MM-dd', 'date_type' => 'yyyy-MM-dd',
'date' => 'Y-m-d', 'date' => 'Y-m-d',
'duration' => '%h:%m h', 'duration' => '%h:%m h',
], ],
'hu' => [ 'hu' => [
'date_time_type' => 'yyyy.MM.dd HH:mm',
'date_type' => 'yyyy.MM.dd', 'date_type' => 'yyyy.MM.dd',
'date' => 'Y.m.d.', 'date' => 'Y.m.d.',
'duration' => '%h:%m h', 'duration' => '%h:%m h',
@@ -160,32 +149,9 @@ class LocaleFormatsTest extends TestCase
$this->assertEquals('yyyy-MM-dd HH:mm', $sut->getDateTimeTypeFormat()); $this->assertEquals('yyyy-MM-dd HH:mm', $sut->getDateTimeTypeFormat());
} }
public function testGetDateTimePickerFormat()
{
$sut = $this->getSut('en', $this->getDefaultSettings());
$this->assertEquals('YYYY-MM-DD HH:mm', $sut->getDateTimePickerFormat());
}
public function testIs24Hours()
{
$sut = $this->getSut('en', $this->getDefaultSettings());
$this->assertFalse($sut->isTwentyFourHours());
}
public function testGetTimeFormat() public function testGetTimeFormat()
{ {
$sut = $this->getSut('en', $this->getDefaultSettings()); $sut = $this->getSut('en', $this->getDefaultSettings());
$this->assertEquals('H:i:s', $sut->getTimeFormat()); $this->assertEquals('H:i:s', $sut->getTimeFormat());
} }
public function testUnknownSetting()
{
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('Unknown setting for locale en: date_time_type');
$sut = $this->getSut('en', ['en' => [
'xxx' => 'dd.MM.yyyy HH:mm',
]]);
$sut->getDateTimePickerFormat();
}
} }

View File

@@ -1089,7 +1089,7 @@
</trans-unit> </trans-unit>
<trans-unit id="crrlEUA" resname="preview.skipped_rows"> <trans-unit id="crrlEUA" resname="preview.skipped_rows">
<source>preview.skipped_rows</source> <source>preview.skipped_rows</source>
<target>Überspringe Vorschau von %rows% weiteren Zeilen …</target> <target>Überspringe Vorschau von %rows% weiteren Zeilen …</target>
</trans-unit> </trans-unit>
<!-- <!--
Export Export
@@ -1283,6 +1283,10 @@
<source>quick_entry.title</source> <source>quick_entry.title</source>
<target>Wochen-Arbeitsstunden</target> <target>Wochen-Arbeitsstunden</target>
</trans-unit> </trans-unit>
<trans-unit id="61p_ckr" resname="label.hours_24">
<source>label.hours_24</source>
<target>24 Stunden</target>
</trans-unit>
</body> </body>
</file> </file>
</xliff> </xliff>

View File

@@ -1283,6 +1283,10 @@
<source>quick_entry.title</source> <source>quick_entry.title</source>
<target>Weekly working hours</target> <target>Weekly working hours</target>
</trans-unit> </trans-unit>
<trans-unit id="61p_ckr" resname="label.hours_24">
<source>label.hours_24</source>
<target>24 hours</target>
</trans-unit>
</body> </body>
</file> </file>
</xliff> </xliff>