Skip to content

Commit 9b5efb0

Browse files
authored
feat: add formatters for dates and times (#36)
1 parent 6479942 commit 9b5efb0

15 files changed

Lines changed: 1571 additions & 25 deletions

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
1111

1212
- Add `Intl\NumberFormatOptions` to allow users to configure number string formatting.
1313
- Add `Intl\DateTimeFormatOptions` to allow users to configure date and time string formatting.
14+
- Provide functionality for formatting dates and times through `Intl\DateTimeFormat`, as well as `FormatPHP::formatDate()` and `FormatPHP::formatTime()` convenience methods.
1415
- Add `UnableToFormatStringException` from which other formatting exceptions will descend.
16+
- Add `UnableToFormatDateTimeException` thrown when we're unable to format a date or time string.
1517

1618
### Changed
1719

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
/**
4+
* This file is part of skillshare/formatphp
5+
*
6+
* skillshare/formatphp is open source software: you can distribute
7+
* it and/or modify it under the terms of the MIT License
8+
* (the "License"). You may not use this file except in
9+
* compliance with the License.
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
14+
* implied. See the License for the specific language governing
15+
* permissions and limitations under the License.
16+
*
17+
* @copyright Copyright (c) Skillshare, Inc. <https://www.skillshare.com>
18+
* @license https://opensource.org/licenses/MIT MIT License
19+
*/
20+
21+
declare(strict_types=1);
22+
23+
namespace FormatPHP\Exception;
24+
25+
/**
26+
* Thrown when we are unable to format a date/time
27+
*/
28+
class UnableToFormatDateTimeException extends UnableToFormatStringException
29+
{
30+
}

src/FormatPHP.php

Lines changed: 74 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,25 @@
2222

2323
namespace FormatPHP;
2424

25-
use FormatPHP\Exception\InvalidArgumentException;
26-
use FormatPHP\Exception\UnableToGenerateMessageIdException;
25+
use DateTimeImmutable as PhpDateTimeImmutable;
26+
use DateTimeInterface as PhpDateTimeInterface;
27+
use Exception as PhpException;
28+
use FormatPHP\Intl\DateTimeFormat;
29+
use FormatPHP\Intl\DateTimeFormatOptions;
2730
use FormatPHP\Intl\MessageFormat;
2831
use FormatPHP\Util\MessageCleaner;
2932
use FormatPHP\Util\MessageRetriever;
3033

3134
use function array_merge;
35+
use function gettype;
3236
use function is_int;
37+
use function is_string;
38+
use function sprintf;
3339

3440
/**
3541
* FormatPHP internationalization and localization
42+
*
43+
* @psalm-import-type DateTimeType from FormatterInterface
3644
*/
3745
class FormatPHP implements FormatterInterface
3846
{
@@ -76,19 +84,80 @@ public function formatMessage(array $descriptor, array $values = []): string
7684
$descriptor['description'] ?? null,
7785
),
7886
);
79-
} catch (UnableToGenerateMessageIdException $exception) {
80-
throw new InvalidArgumentException(
87+
} catch (Exception\UnableToGenerateMessageIdException $exception) {
88+
throw new Exception\InvalidArgumentException(
8189
'The message descriptor must have an ID or default message',
82-
is_int($exception->getCode()) ? $exception->getCode() : 0, // @phpstan-ignore-line
90+
(int) $exception->getCode(),
8391
$exception,
8492
);
8593
}
8694

8795
return $this->messageFormat->format($this->cleanMessage($messagePattern), $values);
8896
}
8997

98+
/**
99+
* @throws Exception\InvalidArgumentException
100+
* @throws Exception\UnableToFormatDateTimeException
101+
*
102+
* @inheritdoc
103+
*/
104+
public function formatDate($date = null, ?DateTimeFormatOptions $options = null): string
105+
{
106+
$formatter = new DateTimeFormat($this->config->getLocale(), $options);
107+
108+
return $formatter->format($this->convertToDateTime($date));
109+
}
110+
111+
/**
112+
* @throws Exception\InvalidArgumentException
113+
* @throws Exception\UnableToFormatDateTimeException
114+
*
115+
* @inheritdoc
116+
*/
117+
public function formatTime($date = null, ?DateTimeFormatOptions $options = null): string
118+
{
119+
$options = $options ? clone $options : new DateTimeFormatOptions();
120+
121+
if ($options->dateStyle === null && $options->timeStyle === null) {
122+
$options->hour = $options->hour ?? 'numeric';
123+
$options->minute = $options->minute ?? 'numeric';
124+
}
125+
126+
return $this->formatDate($date, $options);
127+
}
128+
90129
protected function getConfig(): ConfigInterface
91130
{
92131
return $this->config;
93132
}
133+
134+
/**
135+
* @param DateTimeType | mixed $date
136+
*
137+
* @throws Exception\InvalidArgumentException
138+
* @throws PhpException
139+
*/
140+
private function convertToDateTime($date): PhpDateTimeInterface
141+
{
142+
if ($date === null) {
143+
return new PhpDateTimeImmutable();
144+
}
145+
146+
if ($date instanceof PhpDateTimeInterface) {
147+
return $date;
148+
}
149+
150+
if (is_string($date)) {
151+
return new PhpDateTimeImmutable($date);
152+
}
153+
154+
if (is_int($date)) {
155+
return new PhpDateTimeImmutable('@' . $date);
156+
}
157+
158+
throw new Exception\InvalidArgumentException(sprintf(
159+
'Value must be a string, integer, or instance of DateTimeInterface; received \'%s\'',
160+
gettype($date),
161+
));
162+
}
94163
}

src/FormatterInterface.php

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,15 @@
2222

2323
namespace FormatPHP;
2424

25+
use DateTimeInterface as PhpDateTimeInterface;
26+
use FormatPHP\Intl\DateTimeFormatOptions;
27+
2528
/**
2629
* FormatPHP formatter methods
30+
*
31+
* @psalm-type MessageDescriptorType = array{id?: string, defaultMessage?: string, description?: string}
32+
* @psalm-type MessageValuesType = array<array-key, float | int | string | callable(string):string>
33+
* @psalm-type DateTimeType = PhpDateTimeInterface | string | int
2734
*/
2835
interface FormatterInterface
2936
{
@@ -36,8 +43,39 @@ interface FormatterInterface
3643
* If we cannot find the given ID in the configured messages, we will use
3744
* the descriptor's defaultMessage, if provided.
3845
*
39-
* @param array{id?: string, defaultMessage?: string, description?: string} $descriptor
40-
* @param array<array-key, int | float | string> $values
46+
* @throws Exception\InvalidArgumentException
47+
* @throws Exception\UnableToFormatMessageException
48+
*
49+
* @psalm-param MessageDescriptorType $descriptor
50+
* @psalm-param MessageValuesType $values
4151
*/
4252
public function formatMessage(array $descriptor, array $values = []): string;
53+
54+
/**
55+
* Returns a date string formatted according to the locale of this formatter
56+
*
57+
* Additional options may be provided to configure how the date should be
58+
* formatted.
59+
*
60+
* @param DateTimeType | null $date
61+
*
62+
* @throws Exception\InvalidArgumentException
63+
* @throws Exception\UnableToFormatDateTimeException
64+
*/
65+
public function formatDate($date = null, ?DateTimeFormatOptions $options = null): string;
66+
67+
/**
68+
* Returns a date string formatted according to the locale of this formatter,
69+
* but it differs from `formatDate()` by using "numeric" as the default value
70+
* for the `hour` and `minute` options
71+
*
72+
* Additional options may be provided to configure how the date should be
73+
* formatted.
74+
*
75+
* @param DateTimeType | null $date
76+
*
77+
* @throws Exception\InvalidArgumentException
78+
* @throws Exception\UnableToFormatDateTimeException
79+
*/
80+
public function formatTime($date = null, ?DateTimeFormatOptions $options = null): string;
4381
}

0 commit comments

Comments
 (0)