Skip to content

Commit 80a5ef7

Browse files
authored
Merge pull request #28 from assoconnect/parser
Localized string parser
2 parents 02db5e5 + fb7a345 commit 80a5ef7

5 files changed

Lines changed: 110 additions & 7 deletions

File tree

composer.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@
2525
"assoconnect/php-quality-config": "^1.1"
2626
},
2727
"require": {
28-
"php": "^7.4|^8.0"
28+
"php": "^7.4|^8.0",
29+
"ext-intl": "*",
30+
"thecodingmachine/safe": "^1.3|^2.0"
2931
},
3032
"config": {
3133
"allow-plugins": {

src/AbsoluteDate.php

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ class AbsoluteDate
1414

1515
/**
1616
* AbsoluteDate constructor from a date as string
17-
* Use createInTimezone method if you have a DateTime object or your format includes the hour part
17+
* Use createInTimezone method if you have a DateTime instance or your format includes the hour part
1818
*
1919
* @param string $date Date as string
2020
* @param string $format Format to parse the provided date
@@ -97,7 +97,7 @@ private function getDateTimeFromFormatAndTimezone(string $format, \DateTimeZone
9797
}
9898

9999
/**
100-
* Checks whether the value represented by this object equals to the other.
100+
* Checks whether the date represented by this instance equals to the other.
101101
*/
102102
public function equalsTo(self $other): bool
103103
{
@@ -138,15 +138,15 @@ public function isBeforeOrEqualTo(self $other): bool
138138
}
139139

140140
/**
141-
* Returns whether this instant is after another.
141+
* Returns whether this date is after another.
142142
*/
143143
public function isAfter(self $other): bool
144144
{
145145
return $this->__toString() > $other->__toString();
146146
}
147147

148148
/**
149-
* Returns whether this instant is after or equal to another.
149+
* Returns whether this date is after or equal to another.
150150
*/
151151
public function isAfterOrEqualTo(self $other): bool
152152
{
@@ -172,7 +172,7 @@ public function __toString(): string
172172
}
173173

174174
/**
175-
* Returns an AbsoluteDate object
175+
* Returns an AbsoluteDate instance
176176
*
177177
* @param \DateTimeZone $timezone Timezone to use to get the right date
178178
* @param \DateTimeInterface|null $datetime Point in time to find the date from
@@ -187,7 +187,7 @@ public static function createInTimezone(\DateTimeZone $timezone, \DateTimeInterf
187187
}
188188

189189
/**
190-
* Returns an AbsoluteDate object from a relative format in a given timezone
190+
* Returns an AbsoluteDate instance from a relative format in a given timezone
191191
*
192192
* @param string $relative Relative format to use
193193
* @param ?\DateTimeZone $timezone Timezone to use to get the right date
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace AssoConnect\PHPDate\Exception;
6+
7+
class UnknownPatternException extends \UnexpectedValueException
8+
{
9+
}

src/LocalizedStringParser.php

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace AssoConnect\PHPDate;
6+
7+
use AssoConnect\PHPDate\Exception\UnknownPatternException;
8+
use IntlDateFormatter;
9+
10+
class LocalizedStringParser
11+
{
12+
/**
13+
* Returns an AbsoluteDate instance from a formatted string like 9/1/23 for September, 1st 2023 in the USA
14+
* @param string $date Formatted date
15+
* @param string $locale Locale used to format the date (both en-US & en_US patterns are supported)
16+
*/
17+
public function create(string $date, string $locale): AbsoluteDate
18+
{
19+
// ⚠️ IntlDateFormatter & DateTimeInterface don't use the same patterns
20+
$parts = \Safe\array_combine(
21+
explode('/', $this->getPatternFromLocale($locale)), // IntlDateFormatter pattern
22+
explode('/', $date)
23+
);
24+
$orderedDateParts = ['', '', ''];
25+
$patternParts = ['', '', '']; // DateTimeInterface pattern
26+
foreach ($parts as $pattern => $value) {
27+
switch ($pattern) {
28+
case 'd': // Day without leading 0
29+
case 'dd': // Day with leading 0
30+
$orderedDateParts[2] = str_pad($value, 2, '0', STR_PAD_LEFT);
31+
$patternParts[2] = 'd';
32+
break;
33+
case 'M': // Month without leading 0
34+
case 'MM': // Month with leading 0
35+
$orderedDateParts[1] = str_pad($value, 2, '0', STR_PAD_LEFT);
36+
$patternParts[1] = 'm';
37+
break;
38+
case 'y': // Year on 4 digits
39+
case 'yy': // Year on 2 digits
40+
case 'yyyy': // Year on 4 digits
41+
$orderedDateParts[0] = $value;
42+
$patternParts[0] = (2 === strlen($value)) ? 'y' : 'Y';
43+
break;
44+
default:
45+
throw new UnknownPatternException($pattern);
46+
}
47+
}
48+
return new AbsoluteDate(
49+
implode('-', $orderedDateParts),
50+
implode('-', $patternParts)
51+
);
52+
}
53+
54+
public function getPatternFromLocale(string $locale): string
55+
{
56+
return (new IntlDateFormatter(
57+
$locale,
58+
IntlDateFormatter::SHORT,
59+
IntlDateFormatter::NONE,
60+
))->getPattern();
61+
}
62+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace AssoConnect\PHPDate\Tests;
6+
7+
use AssoConnect\PHPDate\LocalizedStringParser;
8+
use PHPUnit\Framework\TestCase;
9+
10+
class LocalizedStringParserTest extends TestCase
11+
{
12+
/** @dataProvider provideStringsAndLocales */
13+
public function testCreateFromLocaleWorks(string $formattedDate, string $locale, string $date): void
14+
{
15+
$parser = new LocalizedStringParser();
16+
self::assertSame($date, $parser->create($formattedDate, $locale)->format());
17+
}
18+
19+
/** @return array{string, string, string}[] */
20+
public function provideStringsAndLocales(): iterable
21+
{
22+
yield ['09/01/2023', 'en_US', '2023-09-01'];
23+
yield ['9/1/2023', 'en_US', '2023-09-01'];
24+
yield ['9/1/23', 'en_US', '2023-09-01'];
25+
26+
yield ['09/01/2023', 'fr_FR', '2023-01-09'];
27+
yield ['9/1/2023', 'fr_FR', '2023-01-09'];
28+
yield ['9/1/23', 'fr_FR', '2023-01-09'];
29+
}
30+
}

0 commit comments

Comments
 (0)