Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## 2.6.1 under development

- no changes in this release.
- Enh #709: In date rules use `format` property for formatting dates in error messages when message format properties are not set (@WarLikeLaux)

## 2.6.0 June 02, 2026

Expand Down
24 changes: 20 additions & 4 deletions src/Rule/Date/BaseDateHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@
private readonly string $incorrectInputMessage,
private readonly string $tooEarlyMessage,
private readonly string $tooLateMessage,
private readonly ?int $defaultMessageDateType = null,
private readonly ?int $defaultMessageTimeType = null,
) {}

public function validate(mixed $value, RuleInterface $rule, ValidationContext $context): Result
Expand All @@ -67,7 +69,7 @@
'Property' => $context->getCapitalizedTranslatedProperty(),
],
);
return $result;

Check warning on line 72 in src/Rule/Date/BaseDateHandler.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.5-ubuntu-latest

Escaped Mutant for Mutator "ReturnRemoval": @@ @@ $date = $this->prepareValue($value, $rule, $timeZone, false); if ($date === null) { $result->addError($rule->getIncorrectInputMessage() ?? $this->incorrectInputMessage, ['property' => $context->getTranslatedProperty(), 'Property' => $context->getCapitalizedTranslatedProperty()]); - return $result; + } $min = $this->prepareValue($rule->getMin(), $rule, $timeZone, true); if ($min !== null && $date < $min) {
}

$min = $this->prepareValue($rule->getMin(), $rule, $timeZone, true);
Expand All @@ -81,7 +83,7 @@
'limit' => $this->formatDate($min, $rule, $timeZone),
],
);
return $result;

Check warning on line 86 in src/Rule/Date/BaseDateHandler.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.5-ubuntu-latest

Escaped Mutant for Mutator "ReturnRemoval": @@ @@ $min = $this->prepareValue($rule->getMin(), $rule, $timeZone, true); if ($min !== null && $date < $min) { $result->addError($rule->getTooEarlyMessage() ?? $this->tooEarlyMessage, ['property' => $context->getTranslatedProperty(), 'Property' => $context->getCapitalizedTranslatedProperty(), 'value' => $this->formatDate($date, $rule, $timeZone), 'limit' => $this->formatDate($min, $rule, $timeZone)]); - return $result; + } $max = $this->prepareValue($rule->getMax(), $rule, $timeZone, true); if ($max !== null && $date > $max) {
}

$max = $this->prepareValue($rule->getMax(), $rule, $timeZone, true);
Expand All @@ -95,7 +97,7 @@
'limit' => $this->formatDate($max, $rule, $timeZone),
],
);
return $result;

Check warning on line 100 in src/Rule/Date/BaseDateHandler.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.5-ubuntu-latest

Escaped Mutant for Mutator "ReturnRemoval": @@ @@ $max = $this->prepareValue($rule->getMax(), $rule, $timeZone, true); if ($max !== null && $date > $max) { $result->addError($rule->getTooLateMessage() ?? $this->tooLateMessage, ['property' => $context->getTranslatedProperty(), 'Property' => $context->getCapitalizedTranslatedProperty(), 'value' => $this->formatDate($date, $rule, $timeZone), 'limit' => $this->formatDate($max, $rule, $timeZone)]); - return $result; + } return $result; }
}

return $result;
Expand Down Expand Up @@ -160,7 +162,7 @@
}

$errors = DateTimeImmutable::getLastErrors();
if ($errors !== false && !empty($errors['warning_count'])) {

Check warning on line 165 in src/Rule/Date/BaseDateHandler.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.5-ubuntu-latest

Escaped Mutant for Mutator "LogicalAnd": @@ @@ return null; } $errors = DateTimeImmutable::getLastErrors(); - if ($errors !== false && !empty($errors['warning_count'])) { + if ($errors !== false || !empty($errors['warning_count'])) { return null; } return $date;
return null;
}

Expand All @@ -187,14 +189,28 @@

private function formatDate(DateTimeInterface $date, Date|DateTime|Time $rule, ?DateTimeZone $timeZone): string
{
$formatterDateType = $this->getMessageDateTypeFromRule($rule)
$ruleMessageDateType = $this->getMessageDateTypeFromRule($rule);
$ruleMessageTimeType = $this->getMessageTimeTypeFromRule($rule);

$formatterDateType = $ruleMessageDateType
?? $this->messageDateType
?? $this->getDateTypeFromRule($rule);
$formatterTimeType = $this->getMessageTimeTypeFromRule($rule)
?? $this->defaultMessageDateType
?? ($rule instanceof Time ? IntlDateFormatter::NONE : IntlDateFormatter::SHORT);
$formatterTimeType = $ruleMessageTimeType
?? $this->messageTimeType
?? $this->getTimeTypeFromRule($rule);
?? $this->defaultMessageTimeType

Check warning on line 201 in src/Rule/Date/BaseDateHandler.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.5-ubuntu-latest

Escaped Mutant for Mutator "Coalesce": @@ @@ $ruleMessageDateType = $this->getMessageDateTypeFromRule($rule); $ruleMessageTimeType = $this->getMessageTimeTypeFromRule($rule); $formatterDateType = $ruleMessageDateType ?? $this->messageDateType ?? $this->defaultMessageDateType ?? ($rule instanceof Time ? IntlDateFormatter::NONE : IntlDateFormatter::SHORT); - $formatterTimeType = $ruleMessageTimeType ?? $this->messageTimeType ?? $this->defaultMessageTimeType ?? ($rule instanceof Date ? IntlDateFormatter::NONE : IntlDateFormatter::SHORT); + $formatterTimeType = $ruleMessageTimeType ?? $this->messageTimeType ?? ($rule instanceof Date ? IntlDateFormatter::NONE : IntlDateFormatter::SHORT) ?? $this->defaultMessageTimeType; $format = $rule->getMessageFormat() ?? $this->messageFormat; if ($format === null && $this->messageDateType === null && $this->messageTimeType === null && $ruleMessageDateType === null && $ruleMessageTimeType === null) { $format = $rule->getFormat();
?? ($rule instanceof Date ? IntlDateFormatter::NONE : IntlDateFormatter::SHORT);

$format = $rule->getMessageFormat() ?? $this->messageFormat;
if (
$format === null
&& $this->messageDateType === null
&& $this->messageTimeType === null
&& $ruleMessageDateType === null
&& $ruleMessageTimeType === null
) {
$format = $rule->getFormat();
}
Comment thread
WarLikeLaux marked this conversation as resolved.
if (is_string($format) && str_starts_with($format, 'php:')) {
return $date->format(substr($format, 4));
}
Expand Down
8 changes: 6 additions & 2 deletions src/Rule/Date/DateHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,18 @@ final class DateHandler extends BaseDateHandler
/**
* @psalm-param IntlDateFormatterFormat $dateType
* @psalm-param non-empty-string|null $timeZone
* @psalm-param IntlDateFormatterFormat $defaultMessageDateType
*/
public function __construct(
int $dateType = IntlDateFormatter::SHORT,
?string $timeZone = null,
?string $locale = null,
?string $messageFormat = null,
?int $messageDateType = IntlDateFormatter::SHORT,
?int $messageDateType = null,
string $incorrectInputMessage = '{Property} must be a date.',
string $tooEarlyMessage = '{Property} must be no earlier than {limit}.',
string $tooLateMessage = '{Property} must be no later than {limit}.',
int $defaultMessageDateType = IntlDateFormatter::SHORT,
) {
parent::__construct(
$dateType,
Expand All @@ -32,10 +34,12 @@ public function __construct(
$locale,
$messageFormat,
$messageDateType,
IntlDateFormatter::NONE,
null,
$incorrectInputMessage,
$tooEarlyMessage,
$tooLateMessage,
$defaultMessageDateType,
null,
);
}
}
10 changes: 8 additions & 2 deletions src/Rule/Date/DateTimeHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,22 @@ final class DateTimeHandler extends BaseDateHandler
* @psalm-param IntlDateFormatterFormat $dateType
* @psalm-param IntlDateFormatterFormat $timeType
* @psalm-param non-empty-string|null $timeZone
* @psalm-param IntlDateFormatterFormat $defaultMessageDateType
* @psalm-param IntlDateFormatterFormat $defaultMessageTimeType
*/
public function __construct(
int $dateType = IntlDateFormatter::SHORT,
int $timeType = IntlDateFormatter::SHORT,
?string $timeZone = null,
?string $locale = null,
?string $messageFormat = null,
?int $messageDateType = IntlDateFormatter::SHORT,
?int $messageTimeType = IntlDateFormatter::SHORT,
?int $messageDateType = null,
?int $messageTimeType = null,
string $incorrectInputMessage = '{Property} must be a date.',
string $tooEarlyMessage = '{Property} must be no earlier than {limit}.',
string $tooLateMessage = '{Property} must be no later than {limit}.',
int $defaultMessageDateType = IntlDateFormatter::SHORT,
int $defaultMessageTimeType = IntlDateFormatter::SHORT,
) {
parent::__construct(
$dateType,
Expand All @@ -39,6 +43,8 @@ public function __construct(
$incorrectInputMessage,
$tooEarlyMessage,
$tooLateMessage,
$defaultMessageDateType,
$defaultMessageTimeType,
);
}
}
8 changes: 6 additions & 2 deletions src/Rule/Date/TimeHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,28 +14,32 @@ final class TimeHandler extends BaseDateHandler
/**
* @psalm-param IntlDateFormatterFormat $timeType
* @psalm-param non-empty-string|null $timeZone
* @psalm-param IntlDateFormatterFormat $defaultMessageTimeType
*/
public function __construct(
int $timeType = IntlDateFormatter::SHORT,
?string $timeZone = null,
?string $locale = null,
?string $messageFormat = null,
?int $messageTimeType = IntlDateFormatter::SHORT,
?int $messageTimeType = null,
string $incorrectInputMessage = '{Property} must be a time.',
string $tooEarlyMessage = '{Property} must be no earlier than {limit}.',
string $tooLateMessage = '{Property} must be no later than {limit}.',
int $defaultMessageTimeType = IntlDateFormatter::SHORT,
) {
parent::__construct(
IntlDateFormatter::NONE,
$timeType,
$timeZone,
$locale,
$messageFormat,
IntlDateFormatter::NONE,
null,
$messageTimeType,
$incorrectInputMessage,
$tooEarlyMessage,
$tooLateMessage,
null,
$defaultMessageTimeType,
);
}
}
70 changes: 62 additions & 8 deletions tests/Rule/Date/DateTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ public static function dataValidationFailed(): array
'min' => [
'2024-03-29',
new Date(format: 'yyyy-MM-dd', min: '2025-01-01'),
['' => ['Value must be no earlier than 1/1/25.']],
['' => ['Value must be no earlier than 2025-01-01.']],
],
'min-custom-message' => [
['a' => '2024-03-29'],
Expand All @@ -88,12 +88,12 @@ public static function dataValidationFailed(): array
tooEarlyMessage: 'Prop — {property}. Date — {value}. Min — {limit}.',
),
],
['a' => ['Prop — a. Date — 3/29/24. Min — 1/1/25.']],
['a' => ['Prop — a. Date — 2024-03-29. Min — 2025-01-01.']],
],
'max' => [
'2024-03-29',
new Date(format: 'php:Y-m-d', max: '2024-01-01'),
['' => ['Value must be no later than 1/1/24.']],
['' => ['Value must be no later than 2024-01-01.']],
],
'max-custom-message' => [
['a' => '2024-03-29'],
Expand All @@ -104,29 +104,35 @@ public static function dataValidationFailed(): array
tooLateMessage: 'Prop — {property}. Date — {value}. Max — {limit}.',
),
],
['a' => ['Prop — a. Date — 3/29/24. Max — 1/1/24.']],
['a' => ['Prop — a. Date — 2024-03-29. Max — 2024-01-01.']],
],
'rule-and-handler-locales' => [
'2024-03-29',
new Date(format: 'php:Y-m-d', locale: 'ru', max: '2024-01-01'),
['' => ['Value must be no later than 01.01.2024.']],
['' => ['Value must be no later than 2024-01-01.']],
[DateHandler::class => new DateHandler(locale: 'en')],
],
'handler-locale' => [
'2024-03-29',
new Date(format: 'php:Y-m-d', max: '2024-01-01'),
['' => ['Value must be no later than 01.01.2024.']],
['' => ['Value must be no later than 2024-01-01.']],
[DateHandler::class => new DateHandler(locale: 'ru')],
],
'handler-custom-message' => [
'2024-03-29',
new Date(format: 'php:Y-m-d', max: '2024-01-01'),
['' => ['Max: 2024-01-01.']],
[DateHandler::class => new DateHandler(tooLateMessage: 'Max: {limit}.')],
],
'timestamp' => [
1711705158,
new Date(min: 1711705200),
['' => ['Value must be no earlier than 3/29/24.']],
],
'without-message-date-type' => [
'29*03*2024',
new Date(format: 'php:d*m*Y', max: '11*11*2023', ),
['' => ['Value must be no later than 11/11/23.']],
new Date(format: 'php:d*m*Y', max: '11*11*2023'),
['' => ['Value must be no later than 11*11*2023.']],
[DateHandler::class => new DateHandler(messageDateType: null)],
],
'rule-message-format' => [
Expand All @@ -153,6 +159,54 @@ public static function dataValidationFailed(): array
['' => ['Value must be no later than 10.11.2002.']],
[DateHandler::class => new DateHandler(locale: 'en')],
],
'handler-message-type-overrides-format' => [
'29*03*2024',
new Date(format: 'php:d*m*Y', max: '11*11*2023'),
['' => ['Value must be no later than Saturday, November 11, 2023.']],
[DateHandler::class => new DateHandler(messageDateType: IntlDateFormatter::FULL)],
],
'handler-date-type-does-not-affect-message' => [
'March 29, 2024',
new Date(max: 'January 1, 2024'),
['' => ['Value must be no later than 1/1/24.']],
[DateHandler::class => new DateHandler(dateType: IntlDateFormatter::LONG)],
],
'handler-message-date-type-short-overrides-format' => [
'2024-03-29',
new Date(format: 'php:Y-m-d', max: '2024-01-01'),
['' => ['Value must be no later than 1/1/24.']],
[DateHandler::class => new DateHandler(messageDateType: IntlDateFormatter::SHORT)],
],
'default-message-date-type-used-when-unset' => [
'3/29/24',
new Date(max: '1/1/24'),
['' => ['Value must be no later than Monday, January 1, 2024.']],
[DateHandler::class => new DateHandler(defaultMessageDateType: IntlDateFormatter::FULL)],
],
'default-message-date-type-does-not-override-format' => [
'29*03*2024',
new Date(format: 'php:d*m*Y', max: '11*11*2023'),
['' => ['Value must be no later than 11*11*2023.']],
[DateHandler::class => new DateHandler(defaultMessageDateType: IntlDateFormatter::FULL)],
],
'format-used-for-message' => [
'01.01.2100',
new Date(
format: 'php:d.m.Y',
min: '19.11.2013',
max: '31.12.2099',
),
['' => ['Value must be no later than 31.12.2099.']],
],
'format-overridden-by-message-format' => [
'01.01.2100',
new Date(
format: 'php:d.m.Y',
max: '31.12.2099',
messageFormat: 'php:Y/m/d',
),
['' => ['Value must be no later than 2099/12/31.']],
],
];
}

Expand Down
75 changes: 71 additions & 4 deletions tests/Rule/Date/DateTimeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use DateTimeImmutable;
use DateTimeZone;
use IntlDateFormatter;
use Yiisoft\Validator\Rule\Date\DateTime;
use Yiisoft\Validator\Rule\Date\Date;
use Yiisoft\Validator\Rule\Date\DateTimeHandler;
Expand Down Expand Up @@ -74,24 +75,90 @@ public static function dataValidationFailed(): array
'min' => [
'2024-03-29, 12:35',
new DateTime(format: 'yyyy-MM-dd, HH:mm', min: '2025-01-01, 11:00'),
['' => ['Value must be no earlier than 1/1/25, 11:00 AM.']],
['' => ['Value must be no earlier than 2025-01-01, 11:00.']],
],
'max' => [
'2024-03-29, 12:50',
new DateTime(format: 'php:Y-m-d, H:i', max: '2024-01-01, 00:00'),
['' => ['Value must be no later than 1/1/24, 12:00 AM.']],
['' => ['Value must be no later than 2024-01-01, 00:00.']],
],
'handler-custom-message' => [
'2024-03-29, 12:50',
new DateTime(format: 'php:Y-m-d, H:i', max: '2024-01-01, 00:00'),
['' => ['Max: 2024-01-01, 00:00.']],
[DateTimeHandler::class => new DateTimeHandler(tooLateMessage: 'Max: {limit}.')],
],
'timestamp' => [
1711705158,
new DateTime(format: 'php:d.m.Y, H:i:s', min: 1711705200),
['' => ['Value must be no earlier than 3/29/24, 9:40 AM.']],
['' => ['Value must be no earlier than 29.03.2024, 09:40:00.']],
],
'without-message-date-and-time-type' => [
'29*03*2024*12*35',
new DateTime(format: 'php:d*m*Y*H*i', max: '11*11*2023*12*35'),
['' => ['Value must be no later than 11/11/23, 12:35 PM.']],
['' => ['Value must be no later than 11*11*2023*12*35.']],
[DateTimeHandler::class => new DateTimeHandler(messageDateType: null, messageTimeType: null)],
],
'handler-date-and-time-types-do-not-affect-message' => [
1711719000,
new DateTime(
dateType: IntlDateFormatter::LONG,
timeType: IntlDateFormatter::SHORT,
max: 1711711800,
timeZone: 'UTC',
locale: 'en_US',
),
['' => ['Value must be no later than 3/29/24, 11:30 AM.']],
[
DateTimeHandler::class => new DateTimeHandler(
dateType: IntlDateFormatter::LONG,
timeType: IntlDateFormatter::FULL,
),
],
],
'handler-message-date-and-time-types-short-override-format' => [
'2024*03*29*13*30',
new DateTime(format: 'php:Y*m*d*H*i', max: '2024*01*01*00*00', locale: 'en_US'),
['' => ['Value must be no later than 1/1/24, 12:00 AM.']],
[
DateTimeHandler::class => new DateTimeHandler(
messageDateType: IntlDateFormatter::SHORT,
messageTimeType: IntlDateFormatter::SHORT,
),
],
],
'handler-message-date-and-time-types-full-override-format' => [
'2024*03*29*13*30',
new DateTime(format: 'php:Y*m*d*H*i', max: '2024*01*01*00*00', locale: 'en_US'),
[
'' => [
'Value must be no later than Monday, January 1, 2024 at 12:00:00 AM Coordinated Universal Time.',
],
],
[
DateTimeHandler::class => new DateTimeHandler(
messageDateType: IntlDateFormatter::FULL,
messageTimeType: IntlDateFormatter::FULL,
),
],
],
'handler-message-date-type-only-short-overrides-format' => [
'29*03*2024, 12:50',
new DateTime(format: 'php:d*m*Y, H:i', max: '11*11*2023, 12:35'),
['' => ['Value must be no later than 11/11/23, 12:35 PM.']],
[DateTimeHandler::class => new DateTimeHandler(messageDateType: IntlDateFormatter::SHORT)],
],
'handler-unset-message-time-type-stays-short' => [
1704114000,
new DateTime(max: 1704106800),
['' => ['Value must be no later than Monday, January 1, 2024 at 11:00 AM.']],
[
DateTimeHandler::class => new DateTimeHandler(
timeType: IntlDateFormatter::LONG,
messageDateType: IntlDateFormatter::FULL,
),
],
],
];
}

Expand Down
Loading
Loading