diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..73f69e0
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
+# Editor-based HTTP Client requests
+/httpRequests/
diff --git a/.idea/date-time.iml b/.idea/date-time.iml
new file mode 100644
index 0000000..58c557c
--- /dev/null
+++ b/.idea/date-time.iml
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..8310ca9
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..28a804d
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..f8654f4
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/php.xml b/.idea/php.xml
new file mode 100644
index 0000000..343ce1f
--- /dev/null
+++ b/.idea/php.xml
@@ -0,0 +1,103 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/phpunit.xml b/.idea/phpunit.xml
new file mode 100644
index 0000000..4f8104c
--- /dev/null
+++ b/.idea/phpunit.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/LocalDateRange.php b/src/LocalDateRange.php
index 58ca387..a7ed588 100644
--- a/src/LocalDateRange.php
+++ b/src/LocalDateRange.php
@@ -244,6 +244,25 @@ public function jsonSerialize(): string
return (string) $this;
}
+ /**
+ * Converts this LocalDateRange to Interval instance.
+ *
+ * The result is Interval from 00:00 start date and 00:00 end date + one day (because end in Interval is exclude)
+ * in the given time-zone.
+ */
+ public function toInterval(TimeZone $timeZone): Interval
+ {
+ $startZonedDateTime = $this->getStart()
+ ->atTime(LocalTime::min())
+ ->atTimeZone($timeZone);
+ $endZonedDateTime = $this->getEnd()
+ ->plusDays(1)
+ ->atTime(LocalTime::min())
+ ->atTimeZone($timeZone);
+
+ return $startZonedDateTime->getIntervalTo($endZonedDateTime);
+ }
+
/**
* Converts this LocalDateRange to a native DatePeriod object.
*
diff --git a/src/UtcDateTime.php b/src/UtcDateTime.php
new file mode 100644
index 0000000..16a7e99
--- /dev/null
+++ b/src/UtcDateTime.php
@@ -0,0 +1,173 @@
+isEqualTo(TimeZone::utc())) {
+ throw new InvalidArgumentException('Create UtcDateTime with not UTC timezone is not supported');
+ }
+
+ /** @var UtcDateTime $result */
+ $result = parent::of($dateTime, $timeZone);
+
+ return $result;
+ }
+
+ public static function ofInstant(Instant $instant, TimeZone $timeZone = null): UtcDateTime
+ {
+ if ($timeZone === null) {
+ $timeZone = TimeZone::utc();
+ }
+ if (! $timeZone->isEqualTo(TimeZone::utc())) {
+ throw new InvalidArgumentException('Create UtcDateTime with not UTC timezone is not supported');
+ }
+
+ /** @var UtcDateTime $result */
+ $result = parent::ofInstant($instant, $timeZone);
+
+ return $result;
+ }
+
+ public static function now(TimeZone $timeZone = null, ?Clock $clock = null): UtcDateTime
+ {
+ if ($timeZone === null) {
+ $timeZone = TimeZone::utc();
+ }
+ if (! $timeZone->isEqualTo(TimeZone::utc())) {
+ throw new InvalidArgumentException('Create UtcDateTime with not UTC timezone is not supported');
+ }
+
+ /** @var UtcDateTime $result */
+ $result = parent::now($timeZone, $clock);
+
+ return $result;
+ }
+
+ public static function from(DateTimeParseResult $result): UtcDateTime
+ {
+ $methodResult = parent::from($result);
+ if (! $methodResult->getTimeZone()->isEqualTo(TimeZone::utc())) {
+ $methodResult = $methodResult->withTimeZoneSameInstant(TimeZone::utc());
+ }
+
+ /** @var UtcDateTime $methodResult */
+ return $methodResult;
+ }
+
+ public static function parse(string $text, ?DateTimeParser $parser = null): UtcDateTime
+ {
+ $result = parent::parse($text, $parser);
+ if (! $result->getTimeZone()->isEqualTo(TimeZone::utc())) {
+ $result = $result->withTimeZoneSameInstant(TimeZone::utc());
+ }
+
+ /** @var UtcDateTime $result */
+ return $result;
+ }
+
+ /**
+ * @deprecated please use fromNativeDateTime instead
+ */
+ public static function fromDateTime(DateTimeInterface $dateTime): UtcDateTime
+ {
+ return self::fromNativeDateTime($dateTime);
+ }
+
+ public static function fromNativeDateTime(DateTimeInterface $dateTime): UtcDateTime
+ {
+ $result = parent::fromNativeDateTime($dateTime);
+ if (! $result->getTimeZone()->isEqualTo(TimeZone::utc())) {
+ $result = $result->withTimeZoneSameInstant(TimeZone::utc());
+ }
+
+ /** @var UtcDateTime $result */
+ return $result;
+ }
+
+ /**
+ * @param string $input Format "Y-m-d H:i:s.u" or "Y-m-d H:i:s".
+ */
+ public static function fromSqlFormat(string $input, TimeZone $timeZone = null): UtcDateTime
+ {
+ if ($timeZone === null) {
+ $timeZone = TimeZone::utc();
+ }
+ if (! $timeZone->isEqualTo(TimeZone::utc())) {
+ throw new InvalidArgumentException('Create UtcDateTime with not UTC timezone is not supported');
+ }
+
+ /** @var UtcDateTime $result */
+ $result = parent::fromSqlFormat($input, $timeZone);
+
+ return $result;
+ }
+
+ /**
+ * Convert to RFC 3339 compatible format (2022-03-30T21:00:00.000000Z).
+ */
+ public function toCanonicalFormat(int $precision = 6): string
+ {
+ if ($precision < 0 || $precision > 9) {
+ throw new InvalidArgumentException(
+ 'Incorrect precision. Expected value between 0 and 9, got: ' . $precision
+ );
+ }
+ $result = $this->toNativeFormat('Y-m-d\TH:i:s');
+
+ if ($precision > 0) {
+ $nano = str_pad((string) $this->getNano(), 9, '0', STR_PAD_LEFT);
+ $result .= '.' . substr($nano, 0, $precision);
+ }
+ $result .= 'Z';
+
+ return $result;
+ }
+}
diff --git a/tests/LocalDateRangeTest.php b/tests/LocalDateRangeTest.php
index 0bb6421..a6f1839 100644
--- a/tests/LocalDateRangeTest.php
+++ b/tests/LocalDateRangeTest.php
@@ -8,6 +8,7 @@
use Brick\DateTime\LocalDate;
use Brick\DateTime\LocalDateRange;
use Brick\DateTime\Parser\DateTimeParseException;
+use Brick\DateTime\TimeZone;
use function array_map;
use function iterator_count;
@@ -240,6 +241,28 @@ public function providerToNativeDatePeriod(): array
];
}
+ /**
+ * @dataProvider providerToInterval
+ */
+ public function testToInterval(string $range, string $timeZone, string $expectedInterval): void
+ {
+ $actualResult = LocalDateRange::parse($range)->toInterval(TimeZone::parse($timeZone));
+ self::assertSame($expectedInterval, (string) $actualResult);
+ }
+
+ public function providerToInterval(): array
+ {
+ return [
+ ['2010-01-01/2010-01-01', 'UTC', '2010-01-01T00:00Z/2010-01-02T00:00Z'],
+ ['2010-01-01/2020-12-31', 'UTC', '2010-01-01T00:00Z/2021-01-01T00:00Z'],
+ ['2022-03-20/2022-03-26', 'Europe/London', '2022-03-20T00:00Z/2022-03-27T00:00Z'],
+ ['2022-03-20/2022-03-27', 'Europe/London', '2022-03-20T00:00Z/2022-03-27T23:00Z'],
+ ['2022-03-20/2022-03-26', 'Europe/Berlin', '2022-03-19T23:00Z/2022-03-26T23:00Z'],
+ ['2022-03-20/2022-03-27', 'Europe/Berlin', '2022-03-19T23:00Z/2022-03-27T22:00Z'],
+ ['2022-01-01/2022-12-31', 'Europe/Berlin', '2021-12-31T23:00Z/2022-12-31T23:00Z'],
+ ];
+ }
+
/**
* @dataProvider providerIntersectsWith
*/