From 5a2f99f3fa079e45a998c390390ae92c0c60998e Mon Sep 17 00:00:00 2001 From: Dorian Villet Date: Sat, 30 Sep 2023 11:46:48 +0200 Subject: [PATCH 1/2] Add YearWeek::parse() and YearWeek::from(). --- src/Field/WeekOfYear.php | 5 +++++ src/Parser/IsoParsers.php | 20 ++++++++++++++++++ src/YearWeek.php | 34 +++++++++++++++++++++++++++++++ tests/YearWeekTest.php | 43 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 102 insertions(+) diff --git a/src/Field/WeekOfYear.php b/src/Field/WeekOfYear.php index c6a4383..604e39c 100644 --- a/src/Field/WeekOfYear.php +++ b/src/Field/WeekOfYear.php @@ -18,6 +18,11 @@ final class WeekOfYear */ public const NAME = 'week-of-year'; + /** + * The regular expression pattern of the ISO 8601 representation. + */ + public const PATTERN = '[0-9]{2}'; + /** * @param int $weekOfYear The week-of-year to check. * @param int|null $year An optional year to check against, validated. diff --git a/src/Parser/IsoParsers.php b/src/Parser/IsoParsers.php index c341405..4d45bc4 100644 --- a/src/Parser/IsoParsers.php +++ b/src/Parser/IsoParsers.php @@ -15,6 +15,7 @@ use Brick\DateTime\Field\TimeZoneOffsetSecond; use Brick\DateTime\Field\TimeZoneOffsetSign; use Brick\DateTime\Field\TimeZoneRegion; +use Brick\DateTime\Field\WeekOfYear; use Brick\DateTime\Field\Year; /** @@ -184,6 +185,25 @@ public static function yearMonth(): PatternParser ->toParser(); } + /** + * Returns a parser for a year-week such as `2014-W15`. + */ + public static function yearWeek(): PatternParser + { + /** @var PatternParser|null $parser */ + static $parser; + + if ($parser) { + return $parser; + } + + return $parser = (new PatternParserBuilder()) + ->appendCapturePattern(Year::PATTERN, Year::NAME) + ->appendLiteral('-W') + ->appendCapturePattern(WeekOfYear::PATTERN, WeekOfYear::NAME) + ->toParser(); + } + /** * Returns a parser for a month-day such as `12-31`. */ diff --git a/src/YearWeek.php b/src/YearWeek.php index 8a410cc..e261f1e 100644 --- a/src/YearWeek.php +++ b/src/YearWeek.php @@ -4,6 +4,10 @@ namespace Brick\DateTime; +use Brick\DateTime\Parser\DateTimeParseException; +use Brick\DateTime\Parser\DateTimeParser; +use Brick\DateTime\Parser\DateTimeParseResult; +use Brick\DateTime\Parser\IsoParsers; use JsonSerializable; use function sprintf; @@ -49,6 +53,36 @@ public static function of(int $year, int $week): YearWeek return new YearWeek($year, $week); } + /** + * @throws DateTimeException If the year-week is not valid. + * @throws DateTimeParseException If required fields are missing from the result. + */ + public static function from(DateTimeParseResult $result): YearWeek + { + return YearWeek::of( + (int) $result->getField(Field\Year::NAME), + (int) $result->getField(Field\WeekOfYear::NAME) + ); + } + + /** + * Obtains an instance of `YearWeek` from a text string. + * + * @param string $text The text to parse, such as `2007-W48`. + * @param DateTimeParser|null $parser The parser to use, defaults to the ISO 8601 parser. + * + * @throws DateTimeException If the year-week is not valid. + * @throws DateTimeParseException If the text string does not follow the expected format. + */ + public static function parse(string $text, ?DateTimeParser $parser = null): YearWeek + { + if (! $parser) { + $parser = IsoParsers::yearWeek(); + } + + return YearWeek::from($parser->parse($text)); + } + public static function now(TimeZone $timeZone, ?Clock $clock = null): YearWeek { return LocalDate::now($timeZone, $clock)->getYearWeek(); diff --git a/tests/YearWeekTest.php b/tests/YearWeekTest.php index 682cea2..f60e201 100644 --- a/tests/YearWeekTest.php +++ b/tests/YearWeekTest.php @@ -336,6 +336,49 @@ public function testToString(int $year, int $week, string $expected): void self::assertSame($expected, (string) $yearWeek); } + /** + * @dataProvider providerParse + */ + public function testParse(string $string, int $expectedYear, int $expectedWeek): void + { + $yearWeek = YearWeek::parse($string); + self::assertYearWeekIs($expectedYear, $expectedWeek, $yearWeek); + } + + public function providerParse(): array + { + return [ + ['-2000-W12', -2000, 12], + ['-0100-W01', -100, 1], + ['2015-W01', 2015, 1], + ['2015-W48', 2015, 48], + ['2026-W53', 2026, 53], + ['120195-W23', 120195, 23], + ]; + } + + /** + * @dataProvider providerParseInvalidYearWeekThrowsException + */ + public function testParseInvalidYearWeekThrowsException(string $invalidValue, ?string $error = null): void + { + $this->expectException(DateTimeException::class); + $this->expectExceptionMessage($error ?? 'Failed to parse "' . $invalidValue . '"'); + + YearWeek::parse($invalidValue); + } + + public function providerParseInvalidYearWeekThrowsException(): array + { + return [ + [''], + ['+2000-W01'], + ['2000W01'], + ['2000-W54', 'Invalid week-of-year: 54 is not in the range 1 to 53.'], + ['2025-W53', 'Year 2025 does not have 53 weeks'], + ]; + } + public function testNow(): void { $now = new FixedClock(Instant::of(2000000000)); From 89381c2dd4e2d4e80d5c2174a559efd5e48b87d3 Mon Sep 17 00:00:00 2001 From: Dorian Villet Date: Sat, 30 Sep 2023 11:47:00 +0200 Subject: [PATCH 2/2] Add Year::parse() and Year::from(). --- src/Parser/IsoParsers.php | 17 +++++++++++++++++ src/Year.php | 33 ++++++++++++++++++++++++++++++++ tests/YearTest.php | 40 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 90 insertions(+) diff --git a/src/Parser/IsoParsers.php b/src/Parser/IsoParsers.php index 4d45bc4..590a11a 100644 --- a/src/Parser/IsoParsers.php +++ b/src/Parser/IsoParsers.php @@ -166,6 +166,23 @@ public static function yearMonthRange(): PatternParser ->toParser(); } + /** + * Returns a parser for a year such as `2014`. + */ + public static function year(): PatternParser + { + /** @var PatternParser|null $parser */ + static $parser; + + if ($parser) { + return $parser; + } + + return $parser = (new PatternParserBuilder()) + ->appendCapturePattern(Year::PATTERN, Year::NAME) + ->toParser(); + } + /** * Returns a parser for a year-month such as `2014-12`. */ diff --git a/src/Year.php b/src/Year.php index 6caa41d..10a56f3 100644 --- a/src/Year.php +++ b/src/Year.php @@ -4,6 +4,10 @@ namespace Brick\DateTime; +use Brick\DateTime\Parser\DateTimeParseException; +use Brick\DateTime\Parser\DateTimeParser; +use Brick\DateTime\Parser\DateTimeParseResult; +use Brick\DateTime\Parser\IsoParsers; use JsonSerializable; /** @@ -37,6 +41,35 @@ public static function of(int $year): Year return new Year($year); } + /** + * @throws DateTimeException If the year is not valid. + * @throws DateTimeParseException If required fields are missing from the result. + */ + public static function from(DateTimeParseResult $result): Year + { + $year = (int) $result->getField(Field\Year::NAME); + + return Year::of($year); + } + + /** + * Obtains an instance of `Year` from a text string. + * + * @param string $text The text to parse, such as `2007`. + * @param DateTimeParser|null $parser The parser to use, defaults to the ISO 8601 parser. + * + * @throws DateTimeException If the year is not valid. + * @throws DateTimeParseException If the text string does not follow the expected format. + */ + public static function parse(string $text, ?DateTimeParser $parser = null): Year + { + if (! $parser) { + $parser = IsoParsers::year(); + } + + return Year::from($parser->parse($text)); + } + /** * Returns the current year in the given time-zone, according to the given clock. * diff --git a/tests/YearTest.php b/tests/YearTest.php index b5dbb01..3b7a318 100644 --- a/tests/YearTest.php +++ b/tests/YearTest.php @@ -35,6 +35,46 @@ public function testOfInvalidYearThrowsException(int $invalidYear): void Year::of($invalidYear); } + /** + * @dataProvider providerParse + */ + public function testParse(string $string, int $expectedYear): void + { + self::assertYearIs($expectedYear, Year::parse($string)); + } + + public function providerParse(): array + { + return [ + ['-2023', -2023], + ['-0100', -100], + ['1987', 1987], + ['121241', 121241], + ]; + } + + /** + * @dataProvider providerParseInvalidYearThrowsException + */ + public function testParseInvalidYearThrowsException(string $invalidValue): void + { + $this->expectException(DateTimeException::class); + $this->expectExceptionMessage('Failed to parse "' . $invalidValue . '"'); + + Year::parse($invalidValue); + } + + public function providerParseInvalidYearThrowsException(): array + { + return [ + [''], + ['+2000'], + ['-100'], + ['ABC'], + ['9999999999'], + ]; + } + public function providerOfInvalidYearThrowsException(): array { return [