Skip to content

Commit

Permalink
DateTimeRule: fix iso_compat format with second fractions with more t…
Browse files Browse the repository at this point in the history
…han 3 digits
  • Loading branch information
mabar committed Feb 17, 2025
1 parent 4ed2dc8 commit 5d4c111
Show file tree
Hide file tree
Showing 4 changed files with 26 additions and 8 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
### Fixed

- PHPStan generics
- `DateTimeRule`
- `format: 'iso_compat'` parses second fractions with 1 or more digits
- previously expected 3 or less
- fractions above 6 digits are ignored, they are not supported by native DateTimeInterface implementation

### Removed

Expand Down
4 changes: 2 additions & 2 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1505,8 +1505,8 @@ Parameters:
- default `DateTimeRule::FormatIsoCompat`
- expects standard ISO 8601 format as defined by
- `DateTimeInterface::ATOM`
- and JS ISO format `Y-m-d\TH:i:s.v\Z`
- e.g. `2013-04-12T16:40:00-04:00`, `2013-04-12T16:40:00.000Z`
- and JS/Go/others ISO format `Y-m-d\TH:i:s.u\Z` (second fractions above 6 digits are ignored)
- e.g. `2013-04-12T16:40:00-04:00`, `2013-04-12T16:40:00.000Z`, `2013-04-12T16:40:00.489932695Z`
- accepts any of the formats which are [supported by PHP](https://www.php.net/manual/en/datetime.formats.php)
- to try auto-parse date-time of unknown format, use format `any` (`DateTimeRule::FormatAny`)
- for timestamp use format `timestamp` (`DateTimeRule::FormatTimestamp`)
Expand Down
21 changes: 16 additions & 5 deletions src/Rules/DateTimeRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
use function is_a;
use function is_int;
use function is_string;
use function preg_replace;
use function sprintf;
use function strpos;
use function substr;
Expand All @@ -42,7 +43,7 @@ final class DateTimeRule implements Rule
FormatAny = 'any',
FormatIsoCompat = 'iso_compat';

private const JsIsoFormat = 'Y-m-d\TH:i:s.v\Z';
private const JsIsoFormat = 'Y-m-d\TH:i:s.u\Z';

public function resolveArgs(array $args, MetaFieldContext $context): DateTimeArgs
{
Expand Down Expand Up @@ -137,13 +138,23 @@ public function processValue(
throw ValueDoesNotMatch::create($type, Value::of($value));
}
} elseif ($format === self::FormatIsoCompat) {
$datetime = $stringValue !== ''
&& substr($stringValue, -1) === 'Z'
? $classType::createFromFormat(self::JsIsoFormat, $stringValue, new DateTimeZone('UTC'))
: $classType::createFromFormat(
if ($stringValue !== '' && substr($stringValue, -1) === 'Z') {
// Truncate fractional seconds beyond 6 digits if present.
// Example: 2023-07-14T13:52:32.489932695Z -> 2023-07-14T13:52:32.489932Z
$truncatedValue = preg_replace('/\.(\d{6})\d+Z$/', '.$1Z', $stringValue);
$datetime = $truncatedValue === null
? false
: $classType::createFromFormat(
self::JsIsoFormat,
$truncatedValue,
new DateTimeZone('UTC'),
);
} else {
$datetime = $classType::createFromFormat(
DateTimeInterface::ATOM,
$stringValue,
);
}
} else {
$datetime = $classType::createFromFormat($format, $stringValue);
}
Expand Down
5 changes: 4 additions & 1 deletion tests/Unit/Rules/DateTimeRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,10 @@ public function provideValidValues(): Generator
yield ['2013-04-12T16:40:00-04:00', DateTimeRule::FormatAny];
yield ['2013-04-12T16:40:00-04:00', DateTimeInterface::ATOM];
yield ['2013-04-12T16:40:00-04:00', DateTimeRule::FormatIsoCompat];
yield ['2013-04-12T16:40:00.0Z', DateTimeRule::FormatIsoCompat];
yield ['2013-04-12T16:40:00.000Z', DateTimeRule::FormatIsoCompat];
yield ['2023-07-14T13:52:32.489932Z', DateTimeRule::FormatIsoCompat];
yield ['2023-07-14T13:52:32.489932695Z', DateTimeRule::FormatIsoCompat];
yield ['1389312000', DateTimeRule::FormatTimestamp];
yield [1_389_312_000, DateTimeRule::FormatTimestamp];
yield ['1389312000', DateTimeRule::FormatAny];
Expand Down Expand Up @@ -213,7 +216,7 @@ public function testType(): void
self::assertSame('datetime', $type->getName());
self::assertCount(1, $type->getParameters());
self::assertTrue($type->hasParameter(DateTimeRule::Format));
self::assertSame('Y-m-d\TH:i:sP | Y-m-d\TH:i:s.v\Z', $type->getParameter(DateTimeRule::Format)->getValue());
self::assertSame('Y-m-d\TH:i:sP | Y-m-d\TH:i:s.u\Z', $type->getParameter(DateTimeRule::Format)->getValue());
}

public function testTypeWithTimestamp(): void
Expand Down

0 comments on commit 5d4c111

Please sign in to comment.