From 2af7a43837db5c4ccbd191882bbaae49e7ce817c Mon Sep 17 00:00:00 2001 From: Dirk Nederveen Date: Sat, 27 Feb 2021 11:42:01 +0100 Subject: [PATCH 1/3] Add Psalm immutability annotations This helps to ensure that classes marked as immutable will actually be immutable[^1]. Downstream projects using both `brick/date-time` and Psalm will have the benefit of allowing for more thorough analysis. [^1]: https://psalm.dev/docs/annotating_code/supported_annotations/#psalm-immutable --- src/DateTimeException.php | 5 +++++ src/DayOfWeek.php | 6 ++++++ src/DefaultClock.php | 2 ++ src/Duration.php | 6 ++++++ src/Field/DayOfMonth.php | 2 ++ src/Field/DayOfWeek.php | 2 ++ src/Field/DayOfYear.php | 2 ++ src/Field/HourOfDay.php | 2 ++ src/Field/MinuteOfHour.php | 2 ++ src/Field/MonthOfYear.php | 6 ++++++ src/Field/NanoOfSecond.php | 2 ++ src/Field/SecondOfMinute.php | 2 ++ src/Field/TimeZoneOffsetTotalSeconds.php | 2 ++ src/Field/WeekOfYear.php | 6 ++++++ src/Field/Year.php | 4 ++++ src/Instant.php | 4 ++++ src/LocalDate.php | 12 ++++++++++++ src/LocalDateTime.php | 8 ++++++++ src/LocalTime.php | 20 ++++++++++++++++++++ src/Month.php | 10 ++++++++++ src/MonthDay.php | 2 ++ src/Parser/DateTimeParseResult.php | 2 ++ src/Parser/PatternParser.php | 2 ++ src/Period.php | 6 ++++++ src/TimeZone.php | 5 +++++ src/TimeZoneOffset.php | 7 +++++++ src/TimeZoneRegion.php | 2 ++ src/Utility/Math.php | 10 ++++++++++ src/Year.php | 4 ++++ src/YearMonth.php | 4 ++++ src/YearWeek.php | 4 ++++ src/ZonedDateTime.php | 14 +++++++++++--- 32 files changed, 164 insertions(+), 3 deletions(-) diff --git a/src/DateTimeException.php b/src/DateTimeException.php index 82e1f7c..b834fbe 100644 --- a/src/DateTimeException.php +++ b/src/DateTimeException.php @@ -14,12 +14,17 @@ class DateTimeException extends \RuntimeException * @param int $value The actual value. * @param int $min The minimum allowed value. * @param int $max The maximum allowed value. + * + * @psalm-pure */ public static function fieldNotInRange(string $field, int $value, int $min, int $max) : self { return new DateTimeException("Invalid $field: $value is not in the range $min to $max."); } + /** + * @psalm-pure + */ public static function unknownTimeZoneRegion(string $region) : self { return new self(\sprintf('Unknown time zone region "%s".', $region)); diff --git a/src/DayOfWeek.php b/src/DayOfWeek.php index d0309fb..4b31e20 100644 --- a/src/DayOfWeek.php +++ b/src/DayOfWeek.php @@ -8,6 +8,8 @@ * A day-of-week, such as Tuesday. * * This class is immutable. + * + * @psalm-immutable */ final class DayOfWeek implements \JsonSerializable { @@ -40,6 +42,8 @@ private function __construct(int $value) * Returns a cached DayOfWeek instance. * * @param int $value The day-of-week value, validated from 1 to 7. + * + * @psalm-pure */ private static function get(int $value) : DayOfWeek { @@ -61,6 +65,8 @@ private static function get(int $value) : DayOfWeek * @return DayOfWeek The DayOfWeek instance. * * @throws DateTimeException If the day-of-week is not valid. + * + * @psalm-pure */ public static function of(int $dayOfWeek) : DayOfWeek { diff --git a/src/DefaultClock.php b/src/DefaultClock.php index ff4f23b..5c771ca 100644 --- a/src/DefaultClock.php +++ b/src/DefaultClock.php @@ -30,6 +30,8 @@ private function __construct() /** * Gets the default clock. + * + * @psalm-external-mutation-free */ public static function get() : Clock { diff --git a/src/Duration.php b/src/Duration.php index 64db747..12b5fdc 100644 --- a/src/Duration.php +++ b/src/Duration.php @@ -12,6 +12,8 @@ * Represents a duration of time measured in seconds. * * This class is immutable. + * + * @psalm-immutable */ final class Duration implements \JsonSerializable { @@ -145,6 +147,8 @@ public static function parse(string $text) : Duration * * @param int $seconds The number of seconds of the duration. * @param int $nanoAdjustment The adjustment to the duration in nanoseconds. + * + * @psalm-pure */ public static function ofSeconds(int $seconds, int $nanoAdjustment = 0) : Duration { @@ -211,6 +215,8 @@ public static function ofDays(int $days) : Duration * * @param Instant $startInclusive The start instant, inclusive. * @param Instant $endExclusive The end instant, exclusive. + * + * @psalm-pure */ public static function between(Instant $startInclusive, Instant $endExclusive) : Duration { diff --git a/src/Field/DayOfMonth.php b/src/Field/DayOfMonth.php index fee51fe..f46828e 100644 --- a/src/Field/DayOfMonth.php +++ b/src/Field/DayOfMonth.php @@ -27,6 +27,8 @@ final class DayOfMonth * @param int|null $year An optional year to check against, validated. * * @throws DateTimeException If the day-of-month is not valid. + * + * @psalm-pure */ public static function check(int $dayOfMonth, ?int $monthOfYear = null, ?int $year = null) : void { diff --git a/src/Field/DayOfWeek.php b/src/Field/DayOfWeek.php index 7aae3a6..98455d5 100644 --- a/src/Field/DayOfWeek.php +++ b/src/Field/DayOfWeek.php @@ -20,6 +20,8 @@ final class DayOfWeek * @param int $dayOfWeek The day-of-week to check. * * @throws DateTimeException If the day-of-week is not valid. + * + * @psalm-pure */ public static function check(int $dayOfWeek) : void { diff --git a/src/Field/DayOfYear.php b/src/Field/DayOfYear.php index ea0804b..277cddb 100644 --- a/src/Field/DayOfYear.php +++ b/src/Field/DayOfYear.php @@ -21,6 +21,8 @@ final class DayOfYear * @param int|null $year An optional year to check against, validated. * * @throws DateTimeException If the day-of-year is not valid. + * + * @psalm-pure */ public static function check(int $dayOfYear, ?int $year = null) : void { diff --git a/src/Field/HourOfDay.php b/src/Field/HourOfDay.php index ab5e21a..11bdd08 100644 --- a/src/Field/HourOfDay.php +++ b/src/Field/HourOfDay.php @@ -25,6 +25,8 @@ final class HourOfDay * @param int $hourOfDay The hour-of-day to check. * * @throws DateTimeException If the hour-of-day is not valid. + * + * @psalm-pure */ public static function check(int $hourOfDay) : void { diff --git a/src/Field/MinuteOfHour.php b/src/Field/MinuteOfHour.php index 06d60e7..7cf6a2b 100644 --- a/src/Field/MinuteOfHour.php +++ b/src/Field/MinuteOfHour.php @@ -25,6 +25,8 @@ final class MinuteOfHour * @param int $minuteOfHour The minute-of-hour to check. * * @throws DateTimeException If the minute-of-hour is not valid. + * + * @psalm-pure */ public static function check(int $minuteOfHour) : void { diff --git a/src/Field/MonthOfYear.php b/src/Field/MonthOfYear.php index b405e4d..369fd7f 100644 --- a/src/Field/MonthOfYear.php +++ b/src/Field/MonthOfYear.php @@ -25,6 +25,8 @@ final class MonthOfYear * @param int $monthOfYear The month-of-year to check. * * @throws DateTimeException If the month-of-year is not valid. + * + * @psalm-pure */ public static function check(int $monthOfYear) : void { @@ -40,6 +42,8 @@ public static function check(int $monthOfYear) : void * * @param int $monthOfYear The month-of-year, validated. * @param int|null $year An optional year the month-of-year belongs to, validated. + * + * @psalm-pure */ public static function getLength(int $monthOfYear, ?int $year = null) : int { @@ -62,6 +66,8 @@ public static function getLength(int $monthOfYear, ?int $year = null) : int * Returns the camel-cased English name of the given month-of-year. * * @param int $monthOfYear The month-of-year, validated. + * + * @psalm-pure */ public static function getName(int $monthOfYear) : string { diff --git a/src/Field/NanoOfSecond.php b/src/Field/NanoOfSecond.php index 3abcc14..29f51d5 100644 --- a/src/Field/NanoOfSecond.php +++ b/src/Field/NanoOfSecond.php @@ -20,6 +20,8 @@ final class NanoOfSecond * @param int $nanoOfSecond The nano-of-second to check. * * @throws DateTimeException If the nano-of-second is not valid. + * + * @psalm-pure */ public static function check(int $nanoOfSecond) : void { diff --git a/src/Field/SecondOfMinute.php b/src/Field/SecondOfMinute.php index 37ae968..55bf74e 100644 --- a/src/Field/SecondOfMinute.php +++ b/src/Field/SecondOfMinute.php @@ -25,6 +25,8 @@ final class SecondOfMinute * @param int $secondOfMinute The second-of-minute to check. * * @throws DateTimeException If the second-of-minute is not valid. + * + * @psalm-pure */ public static function check(int $secondOfMinute) : void { diff --git a/src/Field/TimeZoneOffsetTotalSeconds.php b/src/Field/TimeZoneOffsetTotalSeconds.php index 8deef39..3d465e0 100644 --- a/src/Field/TimeZoneOffsetTotalSeconds.php +++ b/src/Field/TimeZoneOffsetTotalSeconds.php @@ -27,6 +27,8 @@ final class TimeZoneOffsetTotalSeconds * @param int $offsetSeconds The offset-seconds to check. * * @throws DateTimeException If the offset-seconds is not valid. + * + * @psalm-pure */ public static function check(int $offsetSeconds) : void { diff --git a/src/Field/WeekOfYear.php b/src/Field/WeekOfYear.php index 195f9fa..e18107e 100644 --- a/src/Field/WeekOfYear.php +++ b/src/Field/WeekOfYear.php @@ -23,6 +23,8 @@ final class WeekOfYear * @param int|null $year An optional year to check against, validated. * * @throws DateTimeException If the week-of-year is not valid. + * + * @psalm-pure */ public static function check(int $weekOfYear, ?int $year = null) : void { @@ -43,6 +45,8 @@ public static function check(int $weekOfYear, ?int $year = null) : void * @param int $year The year, validated. * * @return bool True if 53 weeks, false if 52 weeks. + * + * @psalm-pure */ public static function is53WeekYear(int $year) : bool { @@ -59,6 +63,8 @@ public static function is53WeekYear(int $year) : bool * @param int $year The year, validated. * * @return int The number of weeks in the year. + * + * @psalm-pure */ public static function getWeeksInYear(int $year) : int { diff --git a/src/Field/Year.php b/src/Field/Year.php index 466997d..a930870 100644 --- a/src/Field/Year.php +++ b/src/Field/Year.php @@ -35,6 +35,8 @@ final class Year * @param int $year The year to check. * * @throws DateTimeException If the year is not valid. + * + * @psalm-pure */ public static function check(int $year) : void { @@ -45,6 +47,8 @@ public static function check(int $year) : void /** * @param int $year The year, validated. + * + * @psalm-pure */ public static function isLeap(int $year) : bool { diff --git a/src/Instant.php b/src/Instant.php index 2799cfb..86e60f1 100644 --- a/src/Instant.php +++ b/src/Instant.php @@ -12,6 +12,8 @@ * Instant represents the computer view of the timeline. It unambiguously represents a point in time, * without any calendar concept of date, time or time zone. It is not very meaningful to humans, * but can be converted to a `ZonedDateTime` by providing a time zone. + * + * @psalm-immutable */ final class Instant implements \JsonSerializable { @@ -55,6 +57,8 @@ private function __construct(int $epochSecond, int $nano) * * @param int $epochSecond The number of seconds since the UNIX epoch of 1970-01-01T00:00:00Z. * @param int $nanoAdjustment The adjustment to the epoch second in nanoseconds. + * + * @psalm-pure */ public static function of(int $epochSecond, int $nanoAdjustment = 0) : Instant { diff --git a/src/LocalDate.php b/src/LocalDate.php index d22f277..423ecc1 100644 --- a/src/LocalDate.php +++ b/src/LocalDate.php @@ -14,6 +14,8 @@ * A date without a time-zone in the ISO-8601 calendar system, such as `2007-12-03`. * * This class is immutable. + * + * @psalm-immutable */ final class LocalDate implements \JsonSerializable { @@ -80,6 +82,8 @@ private function __construct(int $year, int $month, int $day) * @param int $day The day-of-month, from 1 to 31. * * @throws DateTimeException If the date is not valid. + * + * @psalm-pure */ public static function of(int $year, int $month, int $day) : LocalDate { @@ -97,6 +101,8 @@ public static function of(int $year, int $month, int $day) : LocalDate * @param int $dayOfYear The day-of-year, from 1 to 366. * * @throws DateTimeException If either value is not valid. + * + * @psalm-pure */ public static function ofYearDay(int $year, int $dayOfYear) : LocalDate { @@ -120,6 +126,8 @@ public static function ofYearDay(int $year, int $dayOfYear) : LocalDate /** * @throws DateTimeException If the date is not valid. * @throws DateTimeParseException If required fields are missing from the result. + * + * @psalm-pure */ public static function from(DateTimeParseResult $result) : LocalDate { @@ -167,6 +175,8 @@ public static function fromDateTime(\DateTimeInterface $dateTime) : LocalDate * where day 0 is 1970-01-01. Negative numbers represent earlier days. * * @throws DateTimeException If the resulting date has a year out of range. + * + * @psalm-pure */ public static function ofEpochDay(int $epochDay) : LocalDate { @@ -206,6 +216,8 @@ public static function ofEpochDay(int $epochDay) : LocalDate * Returns the current date in the given time-zone, according to the given clock. * * If no clock is provided, the system clock is used. + * + * @psalm-mutation-free */ public static function now(TimeZone $timeZone, ?Clock $clock = null) : LocalDate { diff --git a/src/LocalDateTime.php b/src/LocalDateTime.php index 2cc475b..52de56f 100644 --- a/src/LocalDateTime.php +++ b/src/LocalDateTime.php @@ -14,6 +14,8 @@ * A date-time without a time-zone in the ISO-8601 calendar system, such as 2007-12-03T10:15:30. * * This class is immutable. + * + * @psalm-immutable */ final class LocalDateTime implements \JsonSerializable { @@ -65,6 +67,8 @@ public static function now(TimeZone $timeZone, ?Clock $clock = null) : LocalDate /** * @throws DateTimeException If the date-time is not valid. * @throws DateTimeParseException If required fields are missing from the result. + * + * @psalm-pure */ public static function from(DateTimeParseResult $result) : LocalDateTime { @@ -374,6 +378,8 @@ public function withNano(int $nano) : LocalDateTime * @param TimeZone $zone The zime-zone to use. * * @return ZonedDateTime The zoned date-time formed from this date-time. + * + * @psalm-mutation-free */ public function atTimeZone(TimeZone $zone) : ZonedDateTime { @@ -726,6 +732,8 @@ public function isPast(TimeZone $timeZone, ?Clock $clock = null) : bool * * Note that the native DateTime object supports a precision up to the microsecond, * so the nanoseconds are rounded down to the nearest microsecond. + * + * @psalm-mutation-free */ public function toDateTime() : \DateTime { diff --git a/src/LocalTime.php b/src/LocalTime.php index 30b8420..5beccf3 100644 --- a/src/LocalTime.php +++ b/src/LocalTime.php @@ -17,6 +17,8 @@ * A time without a time-zone in the ISO-8601 calendar system, such as 10:15:30. * * This class is immutable. + * + * @psalm-immutable */ final class LocalTime implements \JsonSerializable { @@ -83,6 +85,8 @@ private function __construct(int $hour, int $minute, int $second, int $nano) * @param int $nano The nano-of-second, from 0 to 999,999,999. * * @throws DateTimeException + * + * @psalm-pure */ public static function of(int $hour, int $minute, int $second = 0, int $nano = 0) : LocalTime { @@ -118,6 +122,8 @@ public static function ofSecondOfDay(int $secondOfDay, int $nanoOfSecond = 0) : /** * @throws DateTimeException If the time is not valid. * @throws DateTimeParseException If required fields are missing from the result. + * + * @psalm-pure */ public static function from(DateTimeParseResult $result) : LocalTime { @@ -172,11 +178,17 @@ public static function now(TimeZone $timeZone, ?Clock $clock = null) : LocalTime return ZonedDateTime::now($timeZone, $clock)->getTime(); } + /** + * @psalm-pure + */ public static function midnight() : LocalTime { return new LocalTime(0, 0, 0, 0); } + /** + * @psalm-pure + */ public static function noon() : LocalTime { return new LocalTime(12, 0, 0, 0); @@ -184,6 +196,8 @@ public static function noon() : LocalTime /** * Returns the smallest possible value for LocalTime. + * + * @psalm-pure */ public static function min() : LocalTime { @@ -192,6 +206,8 @@ public static function min() : LocalTime /** * Returns the highest possible value for LocalTime. + * + * @psalm-pure */ public static function max() : LocalTime { @@ -206,6 +222,8 @@ public static function max() : LocalTime * @return LocalTime The earliest LocalTime object. * * @throws DateTimeException If the array is empty. + * + * @psalm-pure */ public static function minOf(LocalTime ...$times) : LocalTime { @@ -232,6 +250,8 @@ public static function minOf(LocalTime ...$times) : LocalTime * @return LocalTime The latest LocalTime object. * * @throws DateTimeException If the array is empty. + * + * @psalm-pure */ public static function maxOf(LocalTime ...$times) : LocalTime { diff --git a/src/Month.php b/src/Month.php index b9d2ea0..ed30f7e 100644 --- a/src/Month.php +++ b/src/Month.php @@ -6,6 +6,8 @@ /** * Represents a month-of-year such as January. + * + * @psalm-immutable */ final class Month implements \JsonSerializable { @@ -45,6 +47,8 @@ private function __construct(int $month) * @param int $value The month value, validated from 1 to 12. * * @return Month The cached Month instance. + * + * @psalm-pure */ private static function get(int $value) : Month { @@ -66,6 +70,8 @@ private static function get(int $value) : Month * @return Month The Month instance. * * @throws DateTimeException + * + * @psalm-pure */ public static function of(int $value) : Month { @@ -78,6 +84,8 @@ public static function of(int $value) : Month * Returns the twelve months of the year in an array. * * @return Month[] + * + * @psalm-pure */ public static function getAll() : array { @@ -165,6 +173,8 @@ public function getMaxLength() : int * * This returns the day-of-year that this month begins on, using the leap * year flag to determine the length of February. + * + * @psalm-mutation-free */ public function getFirstDayOfYear(bool $leapYear) : int { diff --git a/src/MonthDay.php b/src/MonthDay.php index 45a634a..266878a 100644 --- a/src/MonthDay.php +++ b/src/MonthDay.php @@ -11,6 +11,8 @@ /** * A month-day in the ISO-8601 calendar system, such as `--12-03`. + * + * @psalm-immutable */ final class MonthDay implements \JsonSerializable { diff --git a/src/Parser/DateTimeParseResult.php b/src/Parser/DateTimeParseResult.php index 25a18ac..2896f58 100644 --- a/src/Parser/DateTimeParseResult.php +++ b/src/Parser/DateTimeParseResult.php @@ -35,6 +35,8 @@ public function hasField(string $name) : bool * @return string The value for this field. * * @throws DateTimeParseException If the field is not present in this set. + * + * @psalm-mutation-free */ public function getField(string $name) : string { diff --git a/src/Parser/PatternParser.php b/src/Parser/PatternParser.php index a183517..5742461 100644 --- a/src/Parser/PatternParser.php +++ b/src/Parser/PatternParser.php @@ -6,6 +6,8 @@ /** * Matches a regular expression pattern to a set of date-time fields. + * + * @psalm-immutable */ final class PatternParser implements DateTimeParser { diff --git a/src/Period.php b/src/Period.php index 4ac9a12..63302ac 100644 --- a/src/Period.php +++ b/src/Period.php @@ -8,6 +8,8 @@ * A date-based amount of time in the ISO-8601 calendar system, such as '2 years, 3 months and 4 days'. * * This class is immutable. + * + * @psalm-immutable */ final class Period implements \JsonSerializable { @@ -46,6 +48,8 @@ private function __construct(int $years, int $months, int $days) * @param int $years The number of years. * @param int $months The number of months. * @param int $days The number of days. + * + * @psalm-pure */ public static function of(int $years, int $months, int $days) : Period { @@ -267,6 +271,8 @@ public function multipliedBy(int $scalar) : Period /** * Returns a new instance with each amount in this Period negated. + * + * @psalm-mutation-free */ public function negated() : Period { diff --git a/src/TimeZone.php b/src/TimeZone.php index eea0c0f..956e6a3 100644 --- a/src/TimeZone.php +++ b/src/TimeZone.php @@ -11,6 +11,8 @@ * * * `TimeZoneOffset` represents a fixed offset from UTC such as `+02:00`. * * `TimeZoneRegion` represents a geographical region such as `Europe/London`. + * + * @psalm-immutable */ abstract class TimeZone { @@ -36,6 +38,9 @@ public static function parse(string $text) : TimeZone return TimeZoneRegion::parse($text); } + /** + * @psalm-pure + */ public static function utc() : TimeZoneOffset { return TimeZoneOffset::utc(); diff --git a/src/TimeZoneOffset.php b/src/TimeZoneOffset.php index 79d954e..ffb04bf 100644 --- a/src/TimeZoneOffset.php +++ b/src/TimeZoneOffset.php @@ -11,6 +11,8 @@ /** * A time-zone offset from Greenwich/UTC, such as `+02:00`. + * + * @psalm-immutable */ final class TimeZoneOffset extends TimeZone { @@ -81,6 +83,8 @@ public static function of(int $hours, int $minutes = 0, int $seconds = 0) : Time * @param int $totalSeconds The total offset in seconds. * * @throws DateTimeException + * + * @psalm-pure */ public static function ofTotalSeconds(int $totalSeconds) : TimeZoneOffset { @@ -89,6 +93,9 @@ public static function ofTotalSeconds(int $totalSeconds) : TimeZoneOffset return new TimeZoneOffset($totalSeconds); } + /** + * @psalm-pure + */ public static function utc() : TimeZoneOffset { return new TimeZoneOffset(0); diff --git a/src/TimeZoneRegion.php b/src/TimeZoneRegion.php index 2c383c8..339dacc 100644 --- a/src/TimeZoneRegion.php +++ b/src/TimeZoneRegion.php @@ -11,6 +11,8 @@ /** * A geographical region where the same time-zone rules apply, such as `Europe/London`. + * + * @psalm-immutable */ final class TimeZoneRegion extends TimeZone { diff --git a/src/Utility/Math.php b/src/Utility/Math.php index 419e767..45ef59f 100644 --- a/src/Utility/Math.php +++ b/src/Utility/Math.php @@ -10,11 +10,15 @@ * Internal utility class for calculations on integers. * * @internal + * + * @psalm-immutable */ final class Math { /** * @throws ArithmeticError + * + * @psalm-pure */ public static function addExact(int $a, int $b) : int { @@ -29,6 +33,8 @@ public static function addExact(int $a, int $b) : int /** * @throws ArithmeticError + * + * @psalm-pure */ public static function multiplyExact(int $a, int $b) : int { @@ -46,6 +52,8 @@ public static function multiplyExact(int $a, int $b) : int * * @param int $a The first argument. * @param int $b The second argument, non-zero. + * + * @psalm-pure */ public static function floorDiv(int $a, int $b) : int { @@ -67,6 +75,8 @@ public static function floorDiv(int $a, int $b) : int * * @param int $a The first argument. * @param int $b The second argument, non-zero. + * + * @psalm-pure */ public static function floorMod(int $a, int $b) : int { diff --git a/src/Year.php b/src/Year.php index 4006151..303e3d2 100644 --- a/src/Year.php +++ b/src/Year.php @@ -8,6 +8,8 @@ /** * Represents a year in the proleptic calendar. + * + * @psalm-immutable */ final class Year implements \JsonSerializable { @@ -31,6 +33,8 @@ private function __construct(int $year) /** * @throws DateTimeException If the year is out of range. + * + * @psalm-pure */ public static function of(int $year) : Year { diff --git a/src/YearMonth.php b/src/YearMonth.php index 9583a30..4cd6836 100644 --- a/src/YearMonth.php +++ b/src/YearMonth.php @@ -12,6 +12,8 @@ /** * Represents the combination of a year and a month. + * + * @psalm-immutable */ final class YearMonth implements \JsonSerializable { @@ -46,6 +48,8 @@ private function __construct(int $year, int $month) * @param int $month The month-of-year, from 1 (January) to 12 (December). * * @throws DateTimeException + * + * @psalm-pure */ public static function of(int $year, int $month) : YearMonth { diff --git a/src/YearWeek.php b/src/YearWeek.php index f72b47d..84bf96d 100644 --- a/src/YearWeek.php +++ b/src/YearWeek.php @@ -6,6 +6,8 @@ /** * Represents the combination of a year and a week. + * + * @psalm-immutable */ final class YearWeek implements \JsonSerializable { @@ -40,6 +42,8 @@ private function __construct(int $year, int $week) * @param int $week The week number, from 1 to 53. * * @throws DateTimeException + * + * @psalm-pure */ public static function of(int $year, int $week) : YearWeek { diff --git a/src/ZonedDateTime.php b/src/ZonedDateTime.php index 9eb66d3..c972b90 100644 --- a/src/ZonedDateTime.php +++ b/src/ZonedDateTime.php @@ -14,6 +14,8 @@ * * A ZonedDateTime can be viewed as a LocalDateTime along with a time zone * and targets a specific point in time. + * + * @psalm-immutable */ class ZonedDateTime implements \JsonSerializable { @@ -89,11 +91,13 @@ private function __construct(LocalDateTime $localDateTime, TimeZoneOffset $offse * - If the local date-time falls in the middle of a gap, then the resulting date-time will be shifted forward * by the length of the gap, and the later offset, typically "summer" time, will be used. * - If the local date-time falls in the middle of an overlap, then the offset closest to UTC will be used. + * + * @psalm-pure */ public static function of(LocalDateTime $dateTime, TimeZone $timeZone) : ZonedDateTime { $dtz = $timeZone->toDateTimeZone(); - $dt = new \DateTime((string) $dateTime->withNano(0), $dtz); + $dt = new \DateTimeImmutable((string) $dateTime->withNano(0), $dtz); $instant = Instant::of($dt->getTimestamp(), $dateTime->getNano()); @@ -115,16 +119,18 @@ public static function of(LocalDateTime $dateTime, TimeZone $timeZone) : ZonedDa * Creates a ZonedDateTime from an instant and a time zone. * * This resolves the instant to a date and time without ambiguity. + * + * @psalm-pure */ public static function ofInstant(Instant $instant, TimeZone $timeZone) : ZonedDateTime { $dateTimeZone = $timeZone->toDateTimeZone(); // We need to pass a DateTimeZone to avoid a PHP warning... - $dateTime = new \DateTime('@' . $instant->getEpochSecond(), $dateTimeZone); + $dateTime = new \DateTimeImmutable('@' . $instant->getEpochSecond(), $dateTimeZone); // ... but this DateTimeZone is ignored because of the timestamp, so we set it again. - $dateTime->setTimezone($dateTimeZone); + $dateTime = $dateTime->setTimezone($dateTimeZone); $localDateTime = LocalDateTime::parse($dateTime->format('Y-m-d\TH:i:s')); $localDateTime = $localDateTime->withNano($instant->getNano()); @@ -667,6 +673,8 @@ public function isPast(?Clock $clock = null) : bool * * Note that the native DateTime object supports a precision up to the microsecond, * so the nanoseconds are rounded down to the nearest microsecond. + * + * @psalm-mutation-free */ public function toDateTime() : \DateTime { From 24778661d62ca304a9383ae102f43980da5c0c6b Mon Sep 17 00:00:00 2001 From: Dirk Nederveen Date: Sat, 27 Feb 2021 12:11:55 +0100 Subject: [PATCH 2/3] Psalm-immutable: eagerly calculate TimeZoneOffset::$id --- src/Field/SecondOfDay.php | 2 ++ src/LocalTime.php | 2 ++ src/TimeZoneOffset.php | 22 +++++++++------------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Field/SecondOfDay.php b/src/Field/SecondOfDay.php index 6c8b322..6ba6fbf 100644 --- a/src/Field/SecondOfDay.php +++ b/src/Field/SecondOfDay.php @@ -20,6 +20,8 @@ final class SecondOfDay * @param int $secondOfDay The second-of-day to check. * * @throws DateTimeException If the second-of-day is not valid. + * + * @psalm-pure */ public static function check(int $secondOfDay) : void { diff --git a/src/LocalTime.php b/src/LocalTime.php index 5beccf3..c730754 100644 --- a/src/LocalTime.php +++ b/src/LocalTime.php @@ -105,6 +105,8 @@ public static function of(int $hour, int $minute, int $second = 0, int $nano = 0 * @param int $nanoOfSecond The nano-of-second, from 0 to 999,999,999. * * @throws DateTimeException + * + * @psalm-pure */ public static function ofSecondOfDay(int $secondOfDay, int $nanoOfSecond = 0) : LocalTime { diff --git a/src/TimeZoneOffset.php b/src/TimeZoneOffset.php index ffb04bf..d635a28 100644 --- a/src/TimeZoneOffset.php +++ b/src/TimeZoneOffset.php @@ -24,9 +24,7 @@ final class TimeZoneOffset extends TimeZone /** * The string representation of this time-zone offset. * - * This is generated on-the-fly, and will be null before the first call to getId(). - * - * @var string|null + * @var string */ private $id; @@ -38,6 +36,14 @@ final class TimeZoneOffset extends TimeZone private function __construct(int $totalSeconds) { $this->totalSeconds = $totalSeconds; + + if ($this->totalSeconds < 0) { + $this->id = '-' . LocalTime::ofSecondOfDay(- $this->totalSeconds); + } elseif ($this->totalSeconds > 0) { + $this->id = '+' . LocalTime::ofSecondOfDay($this->totalSeconds); + } else { + $this->id = 'Z'; + } } /** @@ -168,16 +174,6 @@ public function getTotalSeconds() : int public function getId() : string { - if ($this->id === null) { - if ($this->totalSeconds < 0) { - $this->id = '-' . LocalTime::ofSecondOfDay(- $this->totalSeconds); - } elseif ($this->totalSeconds > 0) { - $this->id = '+' . LocalTime::ofSecondOfDay($this->totalSeconds); - } else { - $this->id = 'Z'; - } - } - return $this->id; } From 341a7bcf7f35444404ffe808afef0532eb7ce9da Mon Sep 17 00:00:00 2001 From: Dirk Nederveen Date: Wed, 21 Apr 2021 09:15:05 +0200 Subject: [PATCH 3/3] Round 2 of psalm-mutation annotations --- src/Clock.php | 2 + src/Clock/OffsetClock.php | 3 + src/Clock/ScaleClock.php | 3 + src/Clock/SystemClock.php | 6 ++ src/DayOfWeek.php | 5 +- src/DefaultClock.php | 3 +- src/Field/TimeZoneOffsetHour.php | 2 + src/Field/TimeZoneOffsetMinute.php | 2 + src/Field/TimeZoneOffsetSecond.php | 2 + src/Instant.php | 89 +++++++++++++++++++++++++++++- src/LocalDateRange.php | 4 ++ src/LocalDateTime.php | 4 +- src/LocalTime.php | 2 +- src/Month.php | 5 +- src/MonthDay.php | 4 ++ src/Parser/DateTimeParseResult.php | 4 +- src/TimeZoneOffset.php | 25 ++++++--- src/TimeZoneRegion.php | 4 ++ src/ZonedDateTime.php | 4 ++ 19 files changed, 155 insertions(+), 18 deletions(-) diff --git a/src/Clock.php b/src/Clock.php index bf8ccb4..844f179 100644 --- a/src/Clock.php +++ b/src/Clock.php @@ -8,6 +8,8 @@ interface Clock { /** * Returns the current time. + * + * @psalm-mutation-free */ public function getTime() : Instant; } diff --git a/src/Clock/OffsetClock.php b/src/Clock/OffsetClock.php index e74a6e3..f2970ee 100644 --- a/src/Clock/OffsetClock.php +++ b/src/Clock/OffsetClock.php @@ -37,6 +37,9 @@ public function __construct(Clock $referenceClock, Duration $offset) $this->offset = $offset; } + /** + * @psalm-mutation-free + */ public function getTime() : Instant { return $this->referenceClock->getTime()->plus($this->offset); diff --git a/src/Clock/ScaleClock.php b/src/Clock/ScaleClock.php index a47cd56..28c89ac 100644 --- a/src/Clock/ScaleClock.php +++ b/src/Clock/ScaleClock.php @@ -50,6 +50,9 @@ public function __construct(Clock $referenceClock, int $timeScale) $this->timeScale = $timeScale; } + /** + * @psalm-mutation-free + */ public function getTime() : Instant { $duration = Duration::between($this->startTime, $this->referenceClock->getTime()); diff --git a/src/Clock/SystemClock.php b/src/Clock/SystemClock.php index 2f769bf..23e145c 100644 --- a/src/Clock/SystemClock.php +++ b/src/Clock/SystemClock.php @@ -14,8 +14,14 @@ */ final class SystemClock implements Clock { + /** + * @psalm-mutation-free + */ public function getTime() : Instant { + /** + * @psalm-suppress ImpureFunctionCall This is how time works + */ [$fraction, $epochSecond] = \explode(' ', microtime()); $epochSecond = (int) $epochSecond; diff --git a/src/DayOfWeek.php b/src/DayOfWeek.php index 4b31e20..b849586 100644 --- a/src/DayOfWeek.php +++ b/src/DayOfWeek.php @@ -47,7 +47,10 @@ private function __construct(int $value) */ private static function get(int $value) : DayOfWeek { - /** @var array $values */ + /** + * @var array $values + * @psalm-suppress ImpureStaticVariable only used to cache results + */ static $values = []; if (! isset($values[$value])) { diff --git a/src/DefaultClock.php b/src/DefaultClock.php index 5c771ca..f903f67 100644 --- a/src/DefaultClock.php +++ b/src/DefaultClock.php @@ -31,7 +31,8 @@ private function __construct() /** * Gets the default clock. * - * @psalm-external-mutation-free + * @psalm-mutation-free + * @psalm-suppress ImpureStaticProperty */ public static function get() : Clock { diff --git a/src/Field/TimeZoneOffsetHour.php b/src/Field/TimeZoneOffsetHour.php index f84e29b..44842b5 100644 --- a/src/Field/TimeZoneOffsetHour.php +++ b/src/Field/TimeZoneOffsetHour.php @@ -25,6 +25,8 @@ final class TimeZoneOffsetHour * @param int $offsetHour The offset-hour to check. * * @throws DateTimeException If the offset-hour is not valid. + * + * @psalm-pure */ public static function check(int $offsetHour) : void { diff --git a/src/Field/TimeZoneOffsetMinute.php b/src/Field/TimeZoneOffsetMinute.php index a3b8a39..c8e80fb 100644 --- a/src/Field/TimeZoneOffsetMinute.php +++ b/src/Field/TimeZoneOffsetMinute.php @@ -25,6 +25,8 @@ final class TimeZoneOffsetMinute * @param int $offsetMinute The offset-minute to check. * * @throws DateTimeException If the offset-minute is not valid. + * + * @psalm-pure */ public static function check(int $offsetMinute) : void { diff --git a/src/Field/TimeZoneOffsetSecond.php b/src/Field/TimeZoneOffsetSecond.php index 265a9b6..8b1e4ad 100644 --- a/src/Field/TimeZoneOffsetSecond.php +++ b/src/Field/TimeZoneOffsetSecond.php @@ -25,6 +25,8 @@ final class TimeZoneOffsetSecond * @param int $offsetSecond The offset-second to check. * * @throws DateTimeException If the offset-second is not valid. + * + * @psalm-pure */ public static function check(int $offsetSecond) : void { diff --git a/src/Instant.php b/src/Instant.php index 86e60f1..a73da41 100644 --- a/src/Instant.php +++ b/src/Instant.php @@ -12,8 +12,6 @@ * Instant represents the computer view of the timeline. It unambiguously represents a point in time, * without any calendar concept of date, time or time zone. It is not very meaningful to humans, * but can be converted to a `ZonedDateTime` by providing a time zone. - * - * @psalm-immutable */ final class Instant implements \JsonSerializable { @@ -21,6 +19,8 @@ final class Instant implements \JsonSerializable * The number of seconds since the epoch of 1970-01-01T00:00:00Z. * * @var int + * + * @psalm-readonly */ private $epochSecond; @@ -28,6 +28,8 @@ final class Instant implements \JsonSerializable * The nanoseconds adjustment to the epoch second, in the range 0 to 999,999,999. * * @var int + * + * @psalm-readonly */ private $nano; @@ -36,6 +38,8 @@ final class Instant implements \JsonSerializable * * @param int $epochSecond The epoch second. * @param int $nano The nanosecond adjustment, validated in the range 0 to 999,999,999. + * + * @psalm-mutation-free */ private function __construct(int $epochSecond, int $nano) { @@ -74,11 +78,17 @@ public static function of(int $epochSecond, int $nanoAdjustment = 0) : Instant return new Instant($epochSecond, $nanos); } + /** + * @psalm-pure + */ public static function epoch() : Instant { return new Instant(0, 0); } + /** + * @psalm-mutation-free + */ public static function now(?Clock $clock = null) : Instant { if ($clock === null) { @@ -92,6 +102,8 @@ public static function now(?Clock $clock = null) : Instant * Returns the minimum supported instant. * * This could be used by an application as a "far past" instant. + * + * @psalm-pure */ public static function min() : Instant { @@ -102,12 +114,17 @@ public static function min() : Instant * Returns the maximum supported instant. * * This could be used by an application as a "far future" instant. + * + * @psalm-pure */ public static function max() : Instant { return new Instant(\PHP_INT_MAX, 999999999); } + /** + * @psalm-mutation-free + */ public function plus(Duration $duration) : Instant { if ($duration->isZero()) { @@ -120,6 +137,9 @@ public function plus(Duration $duration) : Instant return Instant::of($seconds, $nanos); } + /** + * @psalm-mutation-free + */ public function minus(Duration $duration) : Instant { if ($duration->isZero()) { @@ -129,6 +149,9 @@ public function minus(Duration $duration) : Instant return $this->plus($duration->negated()); } + /** + * @psalm-mutation-free + */ public function plusSeconds(int $seconds) : Instant { if ($seconds === 0) { @@ -138,31 +161,49 @@ public function plusSeconds(int $seconds) : Instant return new Instant($this->epochSecond + $seconds, $this->nano); } + /** + * @psalm-mutation-free + */ public function minusSeconds(int $seconds) : Instant { return $this->plusSeconds(-$seconds); } + /** + * @psalm-mutation-free + */ public function plusMinutes(int $minutes) : Instant { return $this->plusSeconds($minutes * LocalTime::SECONDS_PER_MINUTE); } + /** + * @psalm-mutation-free + */ public function minusMinutes(int $minutes) : Instant { return $this->plusMinutes(-$minutes); } + /** + * @psalm-mutation-free + */ public function plusHours(int $hours) : Instant { return $this->plusSeconds($hours * LocalTime::SECONDS_PER_HOUR); } + /** + * @psalm-mutation-free + */ public function minusHours(int $hours) : Instant { return $this->plusHours(-$hours); } + /** + * @psalm-mutation-free + */ public function plusDays(int $days) : Instant { return $this->plusSeconds($days * LocalTime::SECONDS_PER_DAY); @@ -170,6 +211,8 @@ public function plusDays(int $days) : Instant /** * Returns a copy of this Instant with the epoch second altered. + * + * @psalm-mutation-free */ public function withEpochSecond(int $epochSecond) : Instant { @@ -184,6 +227,8 @@ public function withEpochSecond(int $epochSecond) : Instant * Returns a copy of this Instant with the nano-of-second altered. * * @throws DateTimeException If the nano-of-second if not valid. + * + * @psalm-mutation-free */ public function withNano(int $nano) : Instant { @@ -196,16 +241,25 @@ public function withNano(int $nano) : Instant return new Instant($this->epochSecond, $nano); } + /** + * @psalm-mutation-free + */ public function minusDays(int $days) : Instant { return $this->plusDays(-$days); } + /** + * @psalm-mutation-free + */ public function getEpochSecond() : int { return $this->epochSecond; } + /** + * @psalm-mutation-free + */ public function getNano() : int { return $this->nano; @@ -215,6 +269,8 @@ public function getNano() : int * Compares this instant with another. * * @return int [-1,0,1] If this instant is before, on, or after the given instant. + * + * @psalm-mutation-free */ public function compareTo(Instant $that) : int { @@ -235,6 +291,8 @@ public function compareTo(Instant $that) : int /** * Returns whether this instant equals another. + * + * @psalm-mutation-free */ public function isEqualTo(Instant $that) : bool { @@ -243,6 +301,8 @@ public function isEqualTo(Instant $that) : bool /** * Returns whether this instant is after another. + * + * @psalm-mutation-free */ public function isAfter(Instant $that) : bool { @@ -251,6 +311,8 @@ public function isAfter(Instant $that) : bool /** * Returns whether this instant is after or equal to another. + * + * @psalm-mutation-free */ public function isAfterOrEqualTo(Instant $that) : bool { @@ -259,6 +321,8 @@ public function isAfterOrEqualTo(Instant $that) : bool /** * Returns whether this instant is before another. + * + * @psalm-mutation-free */ public function isBefore(Instant $that) : bool { @@ -267,17 +331,25 @@ public function isBefore(Instant $that) : bool /** * Returns whether this instant is before or equal to another. + * + * @psalm-mutation-free */ public function isBeforeOrEqualTo(Instant $that) : bool { return $this->compareTo($that) <= 0; } + /** + * @psalm-mutation-free + */ public function isBetweenInclusive(Instant $from, Instant $to) : bool { return $this->isAfterOrEqualTo($from) && $this->isBeforeOrEqualTo($to); } + /** + * @psalm-mutation-free + */ public function isBetweenExclusive(Instant $from, Instant $to) : bool { return $this->isAfter($from) && $this->isBefore($to); @@ -287,6 +359,8 @@ public function isBetweenExclusive(Instant $from, Instant $to) : bool * Returns whether this instant is in the future, according to the given clock. * * If no clock is provided, the system clock is used. + * + * @psalm-mutation-free */ public function isFuture(?Clock $clock = null) : bool { @@ -297,6 +371,8 @@ public function isFuture(?Clock $clock = null) : bool * Returns whether this instant is in the past, according to the given clock. * * If no clock is provided, the system clock is used. + * + * @psalm-mutation-free */ public function isPast(?Clock $clock = null) : bool { @@ -305,6 +381,8 @@ public function isPast(?Clock $clock = null) : bool /** * Returns a ZonedDateTime formed from this instant and the specified time-zone. + * + * @psalm-mutation-free */ public function atTimeZone(TimeZone $timeZone) : ZonedDateTime { @@ -317,6 +395,8 @@ public function atTimeZone(TimeZone $timeZone) : ZonedDateTime * The output does not have trailing decimal zeros. * * Examples: `123456789`, `123456789.5`, `123456789.000000001`. + * + * @psalm-mutation-free */ public function toDecimal() : string { @@ -335,12 +415,17 @@ public function toDecimal() : string /** * Serializes as a string using {@see Instant::__toString()}. + * + * @psalm-mutation-free */ public function jsonSerialize() : string { return (string) $this; } + /** + * @psalm-mutation-free + */ public function __toString() : string { return (string) ZonedDateTime::ofInstant($this, TimeZone::utc()); diff --git a/src/LocalDateRange.php b/src/LocalDateRange.php index 83c2de2..9fe3c86 100644 --- a/src/LocalDateRange.php +++ b/src/LocalDateRange.php @@ -49,6 +49,8 @@ private function __construct(LocalDate $start, LocalDate $end) * @param LocalDate $end The end date, inclusive. * * @throws DateTimeException If the end date is before the start date. + * + * @psalm-mutation-free */ public static function of(LocalDate $start, LocalDate $end) : LocalDateRange { @@ -66,6 +68,8 @@ public static function of(LocalDate $start, LocalDate $end) : LocalDateRange * * @throws DateTimeException If the date range is not valid. * @throws DateTimeParseException If required fields are missing from the result. + * + * @psalm-mutation-free */ public static function from(DateTimeParseResult $result) : LocalDateRange { diff --git a/src/LocalDateTime.php b/src/LocalDateTime.php index 52de56f..60bccab 100644 --- a/src/LocalDateTime.php +++ b/src/LocalDateTime.php @@ -58,6 +58,8 @@ public static function of(int $year, int $month, int $day, int $hour = 0, int $m * Returns the current local date-time in the given time-zone, according to the given clock. * * If no clock is provided, the system clock is used. + * + * @psalm-mutation-free */ public static function now(TimeZone $timeZone, ?Clock $clock = null) : LocalDateTime { @@ -68,7 +70,7 @@ public static function now(TimeZone $timeZone, ?Clock $clock = null) : LocalDate * @throws DateTimeException If the date-time is not valid. * @throws DateTimeParseException If required fields are missing from the result. * - * @psalm-pure + * @psalm-mutation-free */ public static function from(DateTimeParseResult $result) : LocalDateTime { diff --git a/src/LocalTime.php b/src/LocalTime.php index c730754..44472e9 100644 --- a/src/LocalTime.php +++ b/src/LocalTime.php @@ -125,7 +125,7 @@ public static function ofSecondOfDay(int $secondOfDay, int $nanoOfSecond = 0) : * @throws DateTimeException If the time is not valid. * @throws DateTimeParseException If required fields are missing from the result. * - * @psalm-pure + * @psalm-mutation-free */ public static function from(DateTimeParseResult $result) : LocalTime { diff --git a/src/Month.php b/src/Month.php index ed30f7e..d06c0cc 100644 --- a/src/Month.php +++ b/src/Month.php @@ -52,7 +52,10 @@ private function __construct(int $month) */ private static function get(int $value) : Month { - /** @var array $values */ + /** + * @var array $values + * @psalm-suppress ImpureStaticVariable Caching only + */ static $values = []; if (! isset($values[$value])) { diff --git a/src/MonthDay.php b/src/MonthDay.php index 266878a..6cbd973 100644 --- a/src/MonthDay.php +++ b/src/MonthDay.php @@ -49,6 +49,8 @@ private function __construct(int $month, int $day) * @param int $day The day-of-month, from 1 to 31. * * @throws DateTimeException If the month-day is not valid. + * + * @psalm-pure */ public static function of(int $month, int $day) : MonthDay { @@ -61,6 +63,8 @@ public static function of(int $month, int $day) : MonthDay /** * @throws DateTimeException If the month-day is not valid. * @throws DateTimeParseException If required fields are missing from the result. + * + * @psalm-pure */ public static function from(DateTimeParseResult $result) : MonthDay { diff --git a/src/Parser/DateTimeParseResult.php b/src/Parser/DateTimeParseResult.php index 2896f58..c2fb1f6 100644 --- a/src/Parser/DateTimeParseResult.php +++ b/src/Parser/DateTimeParseResult.php @@ -21,6 +21,8 @@ public function addField(string $name, string $value) : void /** * Returns whether this result has at least one value for the given field. + * + * @psalm-mutation-free */ public function hasField(string $name) : bool { @@ -35,8 +37,6 @@ public function hasField(string $name) : bool * @return string The value for this field. * * @throws DateTimeParseException If the field is not present in this set. - * - * @psalm-mutation-free */ public function getField(string $name) : string { diff --git a/src/TimeZoneOffset.php b/src/TimeZoneOffset.php index d635a28..e8a1f47 100644 --- a/src/TimeZoneOffset.php +++ b/src/TimeZoneOffset.php @@ -24,7 +24,9 @@ final class TimeZoneOffset extends TimeZone /** * The string representation of this time-zone offset. * - * @var string + * @var string|null + * + * @psalm-allow-private-mutation */ private $id; @@ -36,14 +38,6 @@ final class TimeZoneOffset extends TimeZone private function __construct(int $totalSeconds) { $this->totalSeconds = $totalSeconds; - - if ($this->totalSeconds < 0) { - $this->id = '-' . LocalTime::ofSecondOfDay(- $this->totalSeconds); - } elseif ($this->totalSeconds > 0) { - $this->id = '+' . LocalTime::ofSecondOfDay($this->totalSeconds); - } else { - $this->id = 'Z'; - } } /** @@ -56,6 +50,8 @@ private function __construct(int $totalSeconds) * @param int $seconds The time-zone offset in seconds, from 0 to 59, sign matching hours and minute. * * @throws DateTimeException If the values are not in range or the signs don't match. + * + * @psalm-pure */ public static function of(int $hours, int $minutes = 0, int $seconds = 0) : TimeZoneOffset { @@ -110,6 +106,8 @@ public static function utc() : TimeZoneOffset /** * @throws DateTimeException If the offset is not valid. * @throws DateTimeParseException If required fields are missing from the result. + * + * @psalm-mutation-free */ public static function from(DateTimeParseResult $result) : TimeZoneOffset { @@ -174,6 +172,15 @@ public function getTotalSeconds() : int public function getId() : string { + if ($this->id === null) { + if ($this->totalSeconds < 0) { + $this->id = '-' . LocalTime::ofSecondOfDay(- $this->totalSeconds); + } elseif ($this->totalSeconds > 0) { + $this->id = '+' . LocalTime::ofSecondOfDay($this->totalSeconds); + } else { + $this->id = 'Z'; + } + } return $this->id; } diff --git a/src/TimeZoneRegion.php b/src/TimeZoneRegion.php index 339dacc..62d6aaf 100644 --- a/src/TimeZoneRegion.php +++ b/src/TimeZoneRegion.php @@ -35,6 +35,8 @@ private function __construct(\DateTimeZone $zone) * @param string $id The region id. * * @throws DateTimeException If the region id is invalid. + * + * @psalm-pure */ public static function of(string $id) : TimeZoneRegion { @@ -53,6 +55,8 @@ public static function of(string $id) : TimeZoneRegion /** * @throws DateTimeException If the region is not valid. * @throws DateTimeParseException If required fields are missing from the result. + * + * @psalm-mutation-free */ public static function from(DateTimeParseResult $result) : TimeZoneRegion { diff --git a/src/ZonedDateTime.php b/src/ZonedDateTime.php index c972b90..9ca2f17 100644 --- a/src/ZonedDateTime.php +++ b/src/ZonedDateTime.php @@ -148,6 +148,8 @@ public static function ofInstant(Instant $instant, TimeZone $timeZone) : ZonedDa * Returns the current date-time in the given time-zone, according to the given clock. * * If no clock is provided, the system clock is used. + * + * @psalm-mutation-free */ public static function now(TimeZone $timeZone, ?Clock $clock = null) : ZonedDateTime { @@ -161,6 +163,8 @@ public static function now(TimeZone $timeZone, ?Clock $clock = null) : ZonedDate * * @throws DateTimeException If the zoned date-time is not valid. * @throws DateTimeParseException If required fields are missing from the result. + * + * @psalm-mutation-free */ public static function from(DateTimeParseResult $result) : ZonedDateTime {