configure am/pm time-format as user preference (#2789)
This commit is contained in:
@@ -64,13 +64,12 @@ final class ConfigurationController extends BaseApiController
|
||||
|
||||
$model = new I18nConfig();
|
||||
$model
|
||||
->setFormDateTime($formats->getDateTimeTypeFormat($locale))
|
||||
->setFormDate($formats->getDateTypeFormat($locale))
|
||||
->setDateTime($formats->getDateTimeFormat($locale))
|
||||
->setDate($formats->getDateFormat($locale))
|
||||
->setDuration($formats->getDurationFormat($locale))
|
||||
->setTime($formats->getTimeFormat($locale))
|
||||
->setIs24hours($formats->isTwentyFourHours($locale))
|
||||
->setIs24hours($user->is24Hour())
|
||||
->setNow($this->getDateTimeFactory()->createDateTime())
|
||||
;
|
||||
|
||||
|
||||
@@ -18,17 +18,6 @@ use JMS\Serializer\Annotation as Serializer;
|
||||
*/
|
||||
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
|
||||
*
|
||||
@@ -114,13 +103,6 @@ final class I18nConfig
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setFormDateTime(string $formDateTime): I18nConfig
|
||||
{
|
||||
$this->formDateTime = $formDateTime;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setFormDate(string $formDate): I18nConfig
|
||||
{
|
||||
$this->formDate = $formDate;
|
||||
|
||||
@@ -60,28 +60,6 @@ final class LanguageFormattings
|
||||
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".
|
||||
*
|
||||
@@ -126,17 +104,6 @@ final class LanguageFormattings
|
||||
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 $locale
|
||||
|
||||
@@ -329,13 +329,19 @@ class Configuration implements ConfigurationInterface
|
||||
->useAttributeAsKey('name', false) // see https://github.com/symfony/symfony/issues/18988
|
||||
->arrayPrototype()
|
||||
->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')->defaultValue('Y-m-d')->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('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()
|
||||
;
|
||||
|
||||
@@ -430,6 +430,20 @@ class User implements UserInterface, EquatableInterface, \Serializable
|
||||
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
|
||||
{
|
||||
return $this->getPreferenceValue(UserPreference::LOCALE, User::DEFAULT_LANGUAGE);
|
||||
|
||||
@@ -30,6 +30,7 @@ class UserPreference
|
||||
public const INTERNAL_RATE = 'internal_rate';
|
||||
public const SKIN = 'skin';
|
||||
public const LOCALE = 'language';
|
||||
public const HOUR_24 = 'hours_24';
|
||||
public const TIMEZONE = 'timezone';
|
||||
public const FIRST_WEEKDAY = 'first_weekday';
|
||||
|
||||
|
||||
@@ -112,6 +112,13 @@ final class UserPreferenceSubscriber implements EventSubscriberInterface
|
||||
->setSection('locale')
|
||||
->setType(FirstWeekDayType::class),
|
||||
|
||||
(new UserPreference())
|
||||
->setName(UserPreference::HOUR_24)
|
||||
->setValue(true)
|
||||
->setOrder(305)
|
||||
->setSection('locale')
|
||||
->setType(CheckboxType::class),
|
||||
|
||||
(new UserPreference())
|
||||
->setName(UserPreference::SKIN)
|
||||
->setValue($this->configuration->getUserDefaultTheme())
|
||||
|
||||
@@ -10,11 +10,15 @@
|
||||
namespace App\Form\Type;
|
||||
|
||||
use App\API\BaseApiController;
|
||||
use App\Entity\User;
|
||||
use App\Utils\DateFormatConverter;
|
||||
use App\Utils\LocaleSettings;
|
||||
use App\Utils\MomentFormatConverter;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\Form\FormView;
|
||||
use Symfony\Component\OptionsResolver\Options;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
@@ -34,9 +38,6 @@ class DateTimePickerType extends AbstractType
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$dateTimePicker = $this->localeSettings->getDateTimePickerFormat();
|
||||
$dateTimeFormat = $this->localeSettings->getDateTimeTypeFormat();
|
||||
|
||||
$resolver->setDefaults([
|
||||
'documentation' => [
|
||||
'type' => 'string',
|
||||
@@ -46,8 +47,18 @@ class DateTimePickerType extends AbstractType
|
||||
'label' => 'label.begin',
|
||||
'widget' => 'single_text',
|
||||
'html5' => false,
|
||||
'format' => $dateTimeFormat,
|
||||
'format_picker' => $dateTimePicker,
|
||||
'format' => function (Options $options) {
|
||||
/** @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,
|
||||
'time_increment' => 1,
|
||||
]);
|
||||
|
||||
@@ -15,6 +15,7 @@ use App\Entity\User;
|
||||
use App\Utils\LocaleFormats;
|
||||
use App\Utils\LocaleFormatter;
|
||||
use DateTime;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
use Twig\Extension\AbstractExtension;
|
||||
use Twig\TwigFilter;
|
||||
use Twig\TwigFunction;
|
||||
@@ -22,10 +23,9 @@ use Twig\TwigTest;
|
||||
|
||||
final class LocaleFormatExtensions extends AbstractExtension
|
||||
{
|
||||
/**
|
||||
* @var LanguageFormattings|null
|
||||
*/
|
||||
private $formats;
|
||||
private $security;
|
||||
|
||||
/**
|
||||
* @var LocaleFormats|null
|
||||
*/
|
||||
@@ -38,10 +38,12 @@ final class LocaleFormatExtensions extends AbstractExtension
|
||||
* @var string
|
||||
*/
|
||||
private $locale;
|
||||
private $userFormat;
|
||||
|
||||
public function __construct(LanguageFormattings $formats)
|
||||
public function __construct(LanguageFormattings $formats, Security $security)
|
||||
{
|
||||
$this->formats = $formats;
|
||||
$this->security = $security;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -169,7 +171,18 @@ final class LocaleFormatExtensions extends AbstractExtension
|
||||
*/
|
||||
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
|
||||
@@ -201,7 +214,7 @@ final class LocaleFormatExtensions extends AbstractExtension
|
||||
*/
|
||||
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)
|
||||
{
|
||||
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
|
||||
|
||||
33
src/Utils/DateFormatConverter.php
Normal file
33
src/Utils/DateFormatConverter.php
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -75,21 +75,12 @@ class LocaleFormats
|
||||
/**
|
||||
* Returns the format which is used by the form component to handle datetime values.
|
||||
*
|
||||
* @deprecated since 1.16
|
||||
* @return string
|
||||
*/
|
||||
public function getDateTimeTypeFormat(): string
|
||||
{
|
||||
return $this->formats->getDateTimeTypeFormat($this->getLocale());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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());
|
||||
return $this->formats->getDateTypeFormat($this->getLocale()) . ' HH:mm';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -131,14 +122,4 @@ class LocaleFormats
|
||||
{
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,10 @@ final class LocaleFormatter
|
||||
* @var string
|
||||
*/
|
||||
private $dateTimeFormat = null;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $dateTypeFormat = null;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
@@ -54,10 +58,6 @@ final class LocaleFormatter
|
||||
* @var string
|
||||
*/
|
||||
private $timeFormat = null;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $isTwentyFourHour = null;
|
||||
|
||||
public function __construct(LanguageFormattings $formats, string $locale)
|
||||
{
|
||||
@@ -216,6 +216,15 @@ final class LocaleFormatter
|
||||
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
|
||||
* @return string
|
||||
@@ -239,13 +248,15 @@ final class LocaleFormatter
|
||||
|
||||
/**
|
||||
* @param DateTime|string $date
|
||||
* @param string $timeFormat
|
||||
* @param bool $stripMidnight
|
||||
* @return bool|false|string
|
||||
*/
|
||||
public function dateTimeFull($date, bool $stripMidnight = false)
|
||||
public function dateTimeFull($date, string $timeFormat, bool $stripMidnight = false)
|
||||
{
|
||||
if (null === $this->dateTimeTypeFormat) {
|
||||
$this->dateTimeTypeFormat = $this->localeFormats->getDateTimeTypeFormat();
|
||||
$converter = new DateFormatConverter();
|
||||
$this->dateTimeTypeFormat = $this->getDateTypeFormat() . ' ' . $converter->convert($timeFormat);
|
||||
}
|
||||
|
||||
if (!$date instanceof DateTime) {
|
||||
@@ -298,7 +309,7 @@ final class LocaleFormatter
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
public function time($date)
|
||||
public function time($date, string $format = null)
|
||||
{
|
||||
if (null === $this->timeFormat) {
|
||||
$this->timeFormat = $this->localeFormats->getTimeFormat();
|
||||
@@ -308,7 +319,7 @@ final class LocaleFormatter
|
||||
$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'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,8 +18,8 @@ class MomentFormatConverter
|
||||
{
|
||||
/**
|
||||
* 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 Moment formats see http://momentjs.com/docs/#/displaying/format/.
|
||||
* 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/
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
@@ -34,6 +34,8 @@ class MomentFormatConverter
|
||||
'ZZZZZ' => 'Z', 'ZZZ' => 'ZZ',
|
||||
// letter 'T'
|
||||
'\'T\'' => 'T',
|
||||
// am/pm to AM/PM
|
||||
'a' => 'A',
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user