diff --git a/typo3/sysext/core/Classes/Localization/DateFormatter.php b/typo3/sysext/core/Classes/Localization/DateFormatter.php new file mode 100644 index 0000000000000000000000000000000000000000..a48268e6b6cf92c7f2051dcc6712028911d507a5 --- /dev/null +++ b/typo3/sysext/core/Classes/Localization/DateFormatter.php @@ -0,0 +1,61 @@ +<?php + +declare(strict_types=1); + +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ + +namespace TYPO3\CMS\Core\Localization; + +use TYPO3\CMS\Core\Utility\MathUtility; + +/** + * Wrapper for dealing with ICU-based (php-intl) date formatting + * see https://unicode-org.github.io/icu/userguide/format_parse/datetime/#datetime-format-syntax + */ +class DateFormatter +{ + /** + * Formats any given input ($date) into a localized, formatted result + * + * @param mixed $date could be a DateTime object, a string or a number (Unix Timestamp) + * @param string|int $format the pattern, as defined by the ICU - see https://unicode-org.github.io/icu/userguide/format_parse/datetime/#datetime-format-syntax + * @param string|Locale $locale the locale to be used, e.g. "nl-NL" + * @return string the formatted output, such as "Tuesday at 12:40:20" + */ + public function format(mixed $date, string|int $format, string|Locale $locale): string + { + $locale = (string)$locale; + if (is_int($format) || MathUtility::canBeInterpretedAsInteger($format)) { + $dateFormatter = new \IntlDateFormatter($locale, (int)$format, (int)$format); + } else { + $dateFormatter = match (strtoupper($format)) { + 'FULL' => new \IntlDateFormatter($locale, \IntlDateFormatter::FULL, \IntlDateFormatter::FULL), + 'FULLDATE' => new \IntlDateFormatter($locale, \IntlDateFormatter::FULL, \IntlDateFormatter::NONE), + 'FULLTIME' => new \IntlDateFormatter($locale, \IntlDateFormatter::NONE, \IntlDateFormatter::FULL), + 'LONG' => new \IntlDateFormatter($locale, \IntlDateFormatter::LONG, \IntlDateFormatter::LONG), + 'LONGDATE' => new \IntlDateFormatter($locale, \IntlDateFormatter::LONG, \IntlDateFormatter::NONE), + 'LONGTIME' => new \IntlDateFormatter($locale, \IntlDateFormatter::NONE, \IntlDateFormatter::LONG), + 'MEDIUM' => new \IntlDateFormatter($locale, \IntlDateFormatter::MEDIUM, \IntlDateFormatter::MEDIUM), + 'MEDIUMDATE' => new \IntlDateFormatter($locale, \IntlDateFormatter::MEDIUM, \IntlDateFormatter::NONE), + 'MEDIUMTIME' => new \IntlDateFormatter($locale, \IntlDateFormatter::NONE, \IntlDateFormatter::MEDIUM), + 'SHORT' => new \IntlDateFormatter($locale, \IntlDateFormatter::SHORT, \IntlDateFormatter::SHORT), + 'SHORTDATE' => new \IntlDateFormatter($locale, \IntlDateFormatter::SHORT, \IntlDateFormatter::NONE), + 'SHORTTIME' => new \IntlDateFormatter($locale, \IntlDateFormatter::NONE, \IntlDateFormatter::SHORT), + // Use a custom pattern + default => new \IntlDateFormatter($locale, \IntlDateFormatter::FULL, \IntlDateFormatter::FULL, null, null, $format), + }; + } + return $dateFormatter->format($date) ?: ''; + } +} diff --git a/typo3/sysext/core/Documentation/Changelog/12.3/Feature-100187-ICU-basedDateAndTimeFormatting.rst b/typo3/sysext/core/Documentation/Changelog/12.3/Feature-100187-ICU-basedDateAndTimeFormatting.rst new file mode 100644 index 0000000000000000000000000000000000000000..96ecdaaa32be6384e01c654d2a1a3cacc9a45bd8 --- /dev/null +++ b/typo3/sysext/core/Documentation/Changelog/12.3/Feature-100187-ICU-basedDateAndTimeFormatting.rst @@ -0,0 +1,97 @@ +.. include:: /Includes.rst.txt + +.. _feature-100187-1679001588: + +===================================================== +Feature: #100187 - ICU-based date and time formatting +===================================================== + +See :issue:`100187` + +Description +=========== + +TYPO3 now supports rendering date and time based on formats/patterns defined by +the International Components for Unicode standard (ICU). + +TYPO3 previously only supported rendering of dates based on the PHP-native +functions :php:`date()` and :php:`strftime()`. + +However, :php:`date()` can only format dates with english texts, such as +"December" as non-localized values, the C-based :php:`strftime()` function works +only with the locale defined in PHP and availability in the underlying operating +system. + +In addition, ICU-based date and time formatting is much more flexible in +rendering, as it ships with default patterns for date and time (namely +`FULL`, `LONG`, `MEDIUM` and `SHORT`) which are based on the given locale. + +This means, that when the locale `en-US` is given, the short date is rendered +as `mm/dd/yyyy` whereas `de-AT` uses the `dd.mm.yyyy` syntax automatically, +without having to define a custom pattern just by using the SHORT default +pattern. + +In addition, the patterns can be adjusted more fine-grained, and can easily +deal with TimeZones for output when DateTime objects are handed in. + +TYPO3 also adds prepared custom patterns: + +* `FULLDATE` (like `FULL`, but only the date information) +* `FULLTIME` (like `FULL`, but only the time information) +* `LONGDATE` (like `LONG`, but only the date information) +* `LONGTIME` (like `LONG`, but only the time information) +* `MEDIUMDATE` (like `MEDIUM`, but only the date information) +* `MEDIUMTIME` (like `MEDIUM`, but only the time information) +* `SHORTDATE` (like `SHORT`, but only the date information) +* `SHORTTIME` (like `SHORT`, but only the time information) + +See https://unicode-org.github.io/icu/userguide/format_parse/datetime/#datetime-format-syntax +for more information on the patterns. + + +Impact +====== + +A new stdWrap feature called `formattedDate` is added, and the new formatting +can also be used in Fluid's :html:`<f:format.date>` ViewHelper. + +The locale is typically fetched from the Site languages' locale (stdWrap or +ViewHelper), or the backend users' language (in Backend context) for the +ViewHelper usages. + +Examples for stdWrap: + +.. code-block:: typoscript + + page.10 = TEXT + page.10.value = 1998-02-20 3:00:00 + # see all available options https://unicode-org.github.io/icu/userguide/format_parse/datetime/#datetime-format-syntax + page.10.formattedDate = FULL + # optional, if a different locale is wanted other than the Site Language's locale + page.10.formattedDate.locale = de-DE + +will result in "Freitag, 20. Februar 1998 um 03:00:00 Koordinierte Weltzeit". + +.. code-block:: typoscript + + page.10 = TEXT + page.10.value = -5 days + page.10.formattedDate = FULL + page.10.formattedDate.locale = fr-FR + +will result in "jeudi 9 mars 2023 à 21:40:49 temps universel coordonné". + +Examples for Fluid `<f:format.date>` ViewHelper: + +.. code-block:: html + + <f:format.date pattern="dd. MMMM yyyy" locale="de-DE">{date}</f:format.date> + +will result in "20. Februar 1998". + +As soon as the :html:`pattern` attribute is used, the :html:`format` attribute +is disregarded. + +Both new ViewHelper arguments are optional. + +.. index:: Fluid, PHP-API, TypoScript, ext:core diff --git a/typo3/sysext/core/Tests/Unit/Localization/DateFormatterTest.php b/typo3/sysext/core/Tests/Unit/Localization/DateFormatterTest.php new file mode 100644 index 0000000000000000000000000000000000000000..a682f9e3efe98ff87d83e263bc89bdbcea53706b --- /dev/null +++ b/typo3/sysext/core/Tests/Unit/Localization/DateFormatterTest.php @@ -0,0 +1,132 @@ +<?php + +declare(strict_types=1); + +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ + +namespace TYPO3\CMS\Core\Tests\Unit\Localization; + +use TYPO3\CMS\Core\Localization\DateFormatter; +use TYPO3\CMS\Core\Localization\Locale; +use TYPO3\TestingFramework\Core\Unit\UnitTestCase; + +class DateFormatterTest extends UnitTestCase +{ + protected function formatDateProvider(): \Generator + { + yield 'regular formatting - no locale' => [ + '2023.02.02 AD at 13:05:00 UTC', + "yyyy.MM.dd G 'at' HH:mm:ss zzz", + ]; + yield 'full - no locale' => [ + 'Thursday, February 2, 2023 at 13:05:00 Coordinated Universal Time', + 'FULL', + ]; + yield 'long - no locale' => [ + 'February 2, 2023 at 13:05:00 UTC', + 'LONG', + ]; + yield 'medium - no locale' => [ + 'Feb 2, 2023, 13:05:00', + 'MEDIUM', + ]; + yield 'medium with int - no locale' => [ + 'Feb 2, 2023, 13:05:00', + \IntlDateFormatter::MEDIUM, + ]; + yield 'medium with int as string - no locale' => [ + 'Feb 2, 2023, 13:05:00', + (string)\IntlDateFormatter::MEDIUM, + ]; + yield 'short - no locale' => [ + '2/2/23, 13:05', + 'SHORT', + ]; + yield 'short in lowercase - no locale' => [ + '2/2/23, 13:05', + 'short', + ]; + yield 'regular formatting - en-US locale' => [ + '2023.02.02 AD at 13:05:00 UTC', + "yyyy.MM.dd G 'at' HH:mm:ss zzz", + 'en-US', + ]; + yield 'full - en-US locale' => [ + 'Thursday, February 2, 2023 at 1:05:00 PM Coordinated Universal Time', + 'FULL', + 'en-US', + ]; + yield 'long - en-US locale' => [ + 'February 2, 2023 at 1:05:00 PM UTC', + 'LONG', + 'en-US', + ]; + yield 'medium - en-US locale' => [ + 'Feb 2, 2023, 1:05:00 PM', + 'MEDIUM', + 'en-US', + ]; + yield 'short - en-US locale' => [ + '2/2/23, 1:05 PM', + 'SHORT', + 'en-US', + ]; + yield 'regular formatting - german locale' => [ + '2023.02.02 n. Chr. um 13:05:00 UTC', + "yyyy.MM.dd G 'um' HH:mm:ss zzz", + 'de-DE', + ]; + yield 'full - german locale' => [ + 'Donnerstag, 2. Februar 2023 um 13:05:00 Koordinierte Weltzeit', + 'FULL', + 'de-DE', + ]; + yield 'long - german locale' => [ + '2. Februar 2023 um 13:05:00 UTC', + 'LONG', + 'de-DE', + ]; + yield 'medium - german locale' => [ + '02.02.2023, 13:05:00', + 'MEDIUM', + 'de-DE', + ]; + yield 'short - german locale' => [ + '02.02.23, 13:05', + 'SHORT', + 'de-DE', + ]; + yield 'custom date only - german locale' => [ + '02. Februar 2023', + 'dd. MMMM yyyy', + 'de-DE', + ]; + yield 'custom time only - german locale' => [ + '13:05:00', + 'HH:mm:ss', + new Locale('de'), + ]; + } + + /** + * @test + * @dataProvider formatDateProvider + */ + public function formatFormatsCorrectly(string $expected, mixed $format, string|Locale|null $locale = 'C'): void + { + $input = new \DateTimeImmutable('2023-02-02 13:05:00'); + $subject = new DateFormatter(); + self::assertEquals($expected, $subject->format($input, $format, $locale)); + } +} diff --git a/typo3/sysext/fluid/Classes/ViewHelpers/Format/DateViewHelper.php b/typo3/sysext/fluid/Classes/ViewHelpers/Format/DateViewHelper.php index 0cce323a97ed777aca58c084aefc03813b2bd6c7..462baa0bbb3ce4e58ad47f661118b5495f5850b3 100644 --- a/typo3/sysext/fluid/Classes/ViewHelpers/Format/DateViewHelper.php +++ b/typo3/sysext/fluid/Classes/ViewHelpers/Format/DateViewHelper.php @@ -17,9 +17,15 @@ declare(strict_types=1); namespace TYPO3\CMS\Fluid\ViewHelpers\Format; +use Psr\Http\Message\ServerRequestInterface; +use TYPO3\CMS\Core\Authentication\BackendUserAuthentication; use TYPO3\CMS\Core\Context\Context; +use TYPO3\CMS\Core\Http\ApplicationType; +use TYPO3\CMS\Core\Localization\DateFormatter; +use TYPO3\CMS\Core\Localization\Locale; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\MathUtility; +use TYPO3\CMS\Fluid\Core\Rendering\RenderingContext; use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface; use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper; use TYPO3Fluid\Fluid\Core\ViewHelper\Exception; @@ -84,6 +90,26 @@ use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithContentArgumentAndRenderS * ``13. Dezember 1980`` * Depending on the current date and defined locale. In the example you see the 1980-12-13 in a german locale. * + * Localized dates using ICU-based date and time formatting + * -------------------------------------------------------- + * + * :: + * + * <f:format.date pattern="dd. MMMM yyyy" locale="de-DE">{dateObject}</f:format.date> + * + * ``13. Dezember 1980`` + * Depending on the current date. In the example you see the 1980-12-13 in a german locale. + * + * Localized dates using default formatting patterns + * ------------------------------------------------- + * + * :: + * + * <f:format.date pattern="FULL" locale="fr-FR">{dateObject}</f:format.date> + * + * ``jeudi 9 mars 2023 à 21:40:49 temps universel coordonné`` + * Depending on the current date and operating system setting. In the example you see the 2023-03-09 in a french locale. + * * Inline notation * --------------- * @@ -119,6 +145,8 @@ final class DateViewHelper extends AbstractViewHelper { $this->registerArgument('date', 'mixed', 'Either an object implementing DateTimeInterface or a string that is accepted by DateTime constructor'); $this->registerArgument('format', 'string', 'Format String which is taken to format the Date/Time', false, ''); + $this->registerArgument('pattern', 'string', 'Format date based on unicode ICO format pattern given see https://unicode-org.github.io/icu/userguide/format_parse/datetime/#datetime-format-syntax. If both "pattern" and "format" arguments are given, pattern will be used.'); + $this->registerArgument('locale', 'string', 'A locale format such as "nl-NL" to format the date in a specific locale, if none given, uses the current locale of the current request. Only works when pattern argument is given'); $this->registerArgument('base', 'mixed', 'A base time (an object implementing DateTimeInterface or a string) used if $date is a relative date specification. Defaults to current time.'); } @@ -128,6 +156,7 @@ final class DateViewHelper extends AbstractViewHelper public static function renderStatic(array $arguments, \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext): string { $format = $arguments['format'] ?? ''; + $pattern = $arguments['pattern'] ?? null; $base = $arguments['base'] ?? GeneralUtility::makeInstance(Context::class)->getPropertyFromAspect('date', 'timestamp'); if (is_string($base)) { $base = trim($base); @@ -161,6 +190,10 @@ final class DateViewHelper extends AbstractViewHelper } } + if ($pattern !== null) { + $locale = $arguments['locale'] ?? self::resolveLocale($renderingContext); + return (new DateFormatter())->format($date, $pattern, $locale); + } if (str_contains($format, '%')) { // @todo Replace deprecated strftime in php 8.1. Suppress warning in v11. return @strftime($format, (int)$date->format('U')); @@ -175,4 +208,27 @@ final class DateViewHelper extends AbstractViewHelper { return 'date'; } + + private static function resolveLocale(RenderingContextInterface $renderingContext): Locale + { + $request = null; + if ($renderingContext instanceof RenderingContext) { + $request = $renderingContext->getRequest(); + } elseif (($GLOBALS['TYPO3_REQUEST'] ?? null) instanceof ServerRequestInterface) { + $request = $GLOBALS['TYPO3_REQUEST']; + } + if ($request && ApplicationType::fromRequest($request)->isFrontend()) { + // Frontend application + $siteLanguage = $request->getAttribute('language'); + + // Get values from site language + if ($siteLanguage !== null) { + return $siteLanguage->getLocale(); + } + } elseif (($GLOBALS['BE_USER'] ?? null) instanceof BackendUserAuthentication + && !empty($GLOBALS['BE_USER']->user['lang'])) { + return new Locale($GLOBALS['BE_USER']->user['lang']); + } + return new Locale(); + } } diff --git a/typo3/sysext/fluid/Tests/Functional/ViewHelpers/Format/DateViewHelperTest.php b/typo3/sysext/fluid/Tests/Functional/ViewHelpers/Format/DateViewHelperTest.php index 1ee00b0151485360304a61f2bc16ec4a3e7dda7d..b991553f35f1ff8c237659643f1fd7854e9251a0 100644 --- a/typo3/sysext/fluid/Tests/Functional/ViewHelpers/Format/DateViewHelperTest.php +++ b/typo3/sysext/fluid/Tests/Functional/ViewHelpers/Format/DateViewHelperTest.php @@ -244,4 +244,34 @@ class DateViewHelperTest extends FunctionalTestCase $context->getTemplatePaths()->setTemplateSource('<f:format.date date="' . $date . '" format="Y-m-d H:i"/>'); self::assertEquals($expected, (new TemplateView($context))->render()); } + + public function viewHelperUsesIcuBasedPatternDataProvider(): \Generator + { + yield 'default value in english' => [ + '10:55:36 on a Tuesday', + 'HH:mm:ss \'on a\' cccc', + 'en-US', + ]; + yield 'quarter of the year in french' => [ + '4e trimestre', + 'QQQQ', + 'fr', + ]; + yield 'quarter of the year - no locale' => [ + '4th quarter of 2000', + 'QQQQ \'of\' yyyy', + ]; + } + + /** + * @test + * @dataProvider viewHelperUsesIcuBasedPatternDataProvider + */ + public function viewHelperUsesIcuBasedPattern(string $expected, string|int $pattern, ?string $locale = null): void + { + $date = '03/Oct/2000:14:55:36 +0400'; + $context = $this->get(RenderingContextFactory::class)->create(); + $context->getTemplatePaths()->setTemplateSource('<f:format.date date="' . $date . '" pattern="' . $pattern . '" locale="' . $locale . '"/>'); + self::assertEquals($expected, (new TemplateView($context))->render()); + } } diff --git a/typo3/sysext/frontend/Classes/ContentObject/ContentObjectRenderer.php b/typo3/sysext/frontend/Classes/ContentObject/ContentObjectRenderer.php index bcbd9fca153570d385f68e851f9f49b9f706d456..8d64ec2f4decd814a3e1638a7606cca137267a60 100644 --- a/typo3/sysext/frontend/Classes/ContentObject/ContentObjectRenderer.php +++ b/typo3/sysext/frontend/Classes/ContentObject/ContentObjectRenderer.php @@ -39,6 +39,7 @@ use TYPO3\CMS\Core\Html\SanitizerBuilderFactory; use TYPO3\CMS\Core\Html\SanitizerInitiator; use TYPO3\CMS\Core\Imaging\ImageManipulation\Area; use TYPO3\CMS\Core\Imaging\ImageManipulation\CropVariantCollection; +use TYPO3\CMS\Core\Localization\DateFormatter; use TYPO3\CMS\Core\Localization\LanguageServiceFactory; use TYPO3\CMS\Core\Localization\Locales; use TYPO3\CMS\Core\Log\LogManager; @@ -102,7 +103,7 @@ class ContentObjectRenderer implements LoggerAwareInterface * @see stdWrap() * @var string[] */ - public $stdWrapOrder = [ + public array $stdWrapOrder = [ 'stdWrapPreProcess' => 'hook', // this is a placeholder for the first Hook 'cacheRead' => 'hook', @@ -177,6 +178,8 @@ class ContentObjectRenderer implements LoggerAwareInterface 'strtotime.' => 'array', 'strftime' => 'strftimeconf', 'strftime.' => 'array', + 'formattedDate' => 'formattedDateconf', + 'formattedDate.' => 'array', 'age' => 'boolean', 'age.' => 'array', 'case' => 'case', @@ -1900,6 +1903,32 @@ class ContentObjectRenderer implements LoggerAwareInterface return strtotime($content, $GLOBALS['EXEC_TIME']); } + /** + * php-intl dateformatted + * Will return a timestamp based on configuration given according to PHP-intl DateFormatter->format() + * see https://unicode-org.github.io/icu/userguide/format_parse/datetime/#datetime-format-syntax + * + * @param string $content Input value undergoing processing in this function. + * @param array $conf stdWrap properties for formattedDate. + * @return string The processed input value + */ + public function stdWrap_formattedDate(string $content, array $conf): string + { + $pattern = $conf['formattedDate'] ?? 'LONG'; + $locale = $conf['formattedDate.']['locale'] ?? $this->getTypoScriptFrontendController()->getLanguage()->getLocale(); + + if ($content === '' || $content === '0') { + $content = $this->getTypoScriptFrontendController()->getContext()->getAspect('date')->getDateTime(); + } else { + // format this to a timestamp now + $content = strtotime((MathUtility::canBeInterpretedAsInteger($content) ? '@' : '') . $content); + if ($content === false) { + $content = $this->getTypoScriptFrontendController()->getContext()->getAspect('date')->getDateTime(); + } + } + return (new DateFormatter())->format($content, $pattern, $locale); + } + /** * age * Will return the age of a given timestamp based on configuration given by stdWrap properties diff --git a/typo3/sysext/frontend/Tests/Unit/ContentObject/ContentObjectRendererTest.php b/typo3/sysext/frontend/Tests/Unit/ContentObject/ContentObjectRendererTest.php index 68315697e6d1e4ae756c2455d16931cec2ea5186..ad6d4e42635b0a3ddffdcc38233a9b958a90a375 100644 --- a/typo3/sysext/frontend/Tests/Unit/ContentObject/ContentObjectRendererTest.php +++ b/typo3/sysext/frontend/Tests/Unit/ContentObject/ContentObjectRendererTest.php @@ -30,6 +30,7 @@ use TYPO3\CMS\Core\Cache\Frontend\NullFrontend; use TYPO3\CMS\Core\Configuration\Features; use TYPO3\CMS\Core\Configuration\SiteConfiguration; use TYPO3\CMS\Core\Context\Context; +use TYPO3\CMS\Core\Context\DateTimeAspect; use TYPO3\CMS\Core\Context\UserAspect; use TYPO3\CMS\Core\Context\WorkspaceAspect; use TYPO3\CMS\Core\Core\ApplicationContext; @@ -3699,7 +3700,7 @@ class ContentObjectRendererTest extends UnitTestCase } } self::assertSame(1, $notCallable); - self::assertSame(81, $callable); + self::assertSame(82, $callable); } /** @@ -3750,7 +3751,7 @@ class ContentObjectRendererTest extends UnitTestCase } } self::assertSame($expectExceptions, $exceptions); - self::assertSame(81, $count); + self::assertSame(82, $count); } /*************************************************************************** @@ -4631,6 +4632,90 @@ class ContentObjectRendererTest extends UnitTestCase ); } + protected function stdWrap_formattedDateProvider(): \Generator + { + yield 'regular formatting - no locale' => [ + '2023.02.02 AD at 13:05:00 UTC', + "yyyy.MM.dd G 'at' HH:mm:ss zzz", + ]; + yield 'full - no locale' => [ + 'Thursday, February 2, 2023 at 13:05:00 Coordinated Universal Time', + 'FULL', + ]; + yield 'long - no locale' => [ + 'February 2, 2023 at 13:05:00 UTC', + 'LONG', + ]; + yield 'medium - no locale' => [ + 'Feb 2, 2023, 13:05:00', + 'MEDIUM', + ]; + yield 'medium with int - no locale' => [ + 'Feb 2, 2023, 13:05:00', + \IntlDateFormatter::MEDIUM, + ]; + yield 'short - no locale' => [ + '2/2/23, 13:05', + 'SHORT', + ]; + yield 'regular formatting - german locale' => [ + '2023.02.02 n. Chr. um 13:05:00 UTC', + "yyyy.MM.dd G 'um' HH:mm:ss zzz", + 'de-DE', + ]; + yield 'full - german locale' => [ + 'Donnerstag, 2. Februar 2023 um 13:05:00 Koordinierte Weltzeit', + 'FULL', + 'de-DE', + ]; + yield 'long - german locale' => [ + '2. Februar 2023 um 13:05:00 UTC', + 'LONG', + 'de-DE', + ]; + yield 'medium - german locale' => [ + '02.02.2023, 13:05:00', + 'MEDIUM', + 'de-DE', + ]; + yield 'short - german locale' => [ + '02.02.23, 13:05', + 'SHORT', + 'de-DE', + ]; + yield 'custom date only - german locale' => [ + '02. Februar 2023', + 'dd. MMMM yyyy', + 'de-DE', + ]; + yield 'custom time only - german locale' => [ + '13:05:00', + 'HH:mm:ss', + 'de-DE', + ]; + yield 'given date and time - german locale' => [ + 'Freitag, 20. Februar 1998 um 03:00:00 Koordinierte Weltzeit', + 'FULL', + 'de-DE', + '1998-02-20 3:00:00', + ]; + } + + /** + * @test + * @dataProvider stdWrap_formattedDateProvider + */ + public function stdWrap_formattedDate(string $expected, mixed $pattern, string $locale = null, string $givenDate = null): void + { + $this->frontendControllerMock->getContext()->setAspect('date', new DateTimeAspect(new \DateTimeImmutable('2023-02-02 13:05:00'))); + $subject = new ContentObjectRenderer($this->frontendControllerMock); + $conf = ['formattedDate' => $pattern]; + if ($locale !== null) { + $conf['formattedDate.']['locale'] = $locale; + } + self::assertEquals($expected, $subject->stdWrap_formattedDate((string)$givenDate, $conf)); + } + /** * Data provider for stdWrap_csConv *