commit 760a3e72b107f03c80ca0870c3b8f735a38930ea Author: upstream Date: Fri Sep 4 09:47:39 2026 +0200 Upstream cakephp/chronos state of the touched files before cdfce65279 diff --git a/src/ChronosTime.php b/src/ChronosTime.php new file mode 100644 index 0000000..f066978 --- /dev/null +++ b/src/ChronosTime.php @@ -0,0 +1,582 @@ +setTimezone($timezone); + } + $this->ticks = static::parseString($time->format('H:i:s.u')); + } elseif (is_string($time)) { + $this->ticks = static::parseString($time); + } elseif ($time instanceof ChronosTime) { + $this->ticks = $time->ticks; + } else { + $this->ticks = static::parseString($time->format('H:i:s.u')); + } + } + + /** + * Copies time from onther instance or from string in the format HH[:.]mm or HH[:.]mm[:.]ss.u + * + * Defaults to server time. + * + * @param \Cake\Chronos\ChronosTime|\DateTimeInterface|string $time Time + * @param \DateTimeZone|string|null $timezone The timezone to use for now + * @return static + */ + public static function parse( + ChronosTime|DateTimeInterface|string|null $time = null, + DateTimeZone|string|null $timezone = null, + ): static { + return new static($time, $timezone); + } + + /** + * @param string $time Time string in the format HH[:.]mm or HH[:.]mm[:.]ss.u + * @return int + */ + protected static function parseString(string $time): int + { + if (!preg_match('/^\s*(\d{1,2})[:.](\d{1,2})(?|[:.](\d{1,2})[.](\d+)|[:.](\d{1,2}))?\s*$/', $time, $matches)) { + throw new InvalidArgumentException( + sprintf('Time string `%s` is not in expected format `HH[:.]mm` or `HH[:.]mm[:.]ss.u`.', $time), + ); + } + + $hours = (int)$matches[1]; + $minutes = (int)$matches[2]; + $seconds = (int)($matches[3] ?? 0); + $microseconds = (int)substr($matches[4] ?? '', 0, 6); + + if ($hours > 24 || $minutes > 59 || $seconds > 59 || $microseconds > 999_999) { + throw new InvalidArgumentException(sprintf('Time string `%s` contains invalid values.', $time)); + } + + $ticks = $hours * self::TICKS_PER_HOUR; + $ticks += $minutes * self::TICKS_PER_MINUTE; + $ticks += $seconds * self::TICKS_PER_SECOND; + $ticks += $microseconds * self::TICKS_PER_MICROSECOND; + + return $ticks % self::TICKS_PER_DAY; + } + + /** + * Returns instance set to server time. + * + * @param \DateTimeZone|string|null $timezone The timezone to use for now + * @return static + */ + public static function now(DateTimeZone|string|null $timezone = null): static + { + return new static(null, $timezone); + } + + /** + * Returns instance set to midnight. + * + * @return static + */ + public static function midnight(): static + { + return new static('00:00:00'); + } + + /** + * Returns instance set to noon. + * + * @return static + */ + public static function noon(): static + { + return new static('12:00:00'); + } + + /** + * Returns instance set to end of day - either + * 23:59:59 or 23:59:59.999999 if `$microseconds` is true + * + * @param bool $microseconds Whether to set microseconds or not + * @return static + */ + public static function endOfDay(bool $microseconds = false): static + { + if ($microseconds) { + return new static('23:59:59.999999'); + } + + return new static('23:59:59'); + } + + /** + * Returns clock microseconds. + * + * @return int + */ + public function getMicroseconds(): int + { + return intdiv($this->ticks % self::TICKS_PER_SECOND, self::TICKS_PER_MICROSECOND); + } + + /** + * Sets clock microseconds. + * + * @param int $microseconds Clock microseconds + * @return static + */ + public function setMicroseconds(int $microseconds): static + { + $baseTicks = $this->ticks - $this->ticks % self::TICKS_PER_SECOND; + $newTicks = static::mod($baseTicks + $microseconds * self::TICKS_PER_MICROSECOND, self::TICKS_PER_DAY); + + $clone = clone $this; + $clone->ticks = $newTicks; + + return $clone; + } + + /** + * Return clock seconds. + * + * @return int + */ + public function getSeconds(): int + { + $secondsTicks = $this->ticks % self::TICKS_PER_MINUTE - $this->ticks % self::TICKS_PER_SECOND; + + return intdiv($secondsTicks, self::TICKS_PER_SECOND); + } + + /** + * Set clock seconds. + * + * @param int $seconds Clock seconds + * @return static + */ + public function setSeconds(int $seconds): static + { + $baseTicks = $this->ticks - ($this->ticks % self::TICKS_PER_MINUTE - $this->ticks % self::TICKS_PER_SECOND); + $newTicks = static::mod($baseTicks + $seconds * self::TICKS_PER_SECOND, self::TICKS_PER_DAY); + + $clone = clone $this; + $clone->ticks = $newTicks; + + return $clone; + } + + /** + * Returns clock minutes. + * + * @return int + */ + public function getMinutes(): int + { + $minutesTicks = $this->ticks % self::TICKS_PER_HOUR - $this->ticks % self::TICKS_PER_MINUTE; + + return intdiv($minutesTicks, self::TICKS_PER_MINUTE); + } + + /** + * Set clock minutes. + * + * @param int $minutes Clock minutes + * @return static + */ + public function setMinutes(int $minutes): static + { + $baseTicks = $this->ticks - ($this->ticks % self::TICKS_PER_HOUR - $this->ticks % self::TICKS_PER_MINUTE); + $newTicks = static::mod($baseTicks + $minutes * self::TICKS_PER_MINUTE, self::TICKS_PER_DAY); + + $clone = clone $this; + $clone->ticks = $newTicks; + + return $clone; + } + + /** + * Returns clock hours. + * + * @return int + */ + public function getHours(): int + { + $hoursInTicks = $this->ticks - $this->ticks % self::TICKS_PER_HOUR; + + return intdiv($hoursInTicks, self::TICKS_PER_HOUR); + } + + /** + * Set clock hours. + * + * @param int $hours Clock hours + * @return static + */ + public function setHours(int $hours): static + { + $baseTicks = $this->ticks - ($this->ticks - $this->ticks % self::TICKS_PER_HOUR); + $newTicks = static::mod($baseTicks + $hours * self::TICKS_PER_HOUR, self::TICKS_PER_DAY); + + $clone = clone $this; + $clone->ticks = $newTicks; + + return $clone; + } + + /** + * Sets clock time. + * + * @param int $hours Clock hours + * @param int $minutes Clock minutes + * @param int $seconds Clock seconds + * @param int $microseconds Clock microseconds + * @return static + */ + public function setTime(int $hours = 0, int $minutes = 0, int $seconds = 0, int $microseconds = 0): static + { + $ticks = $hours * self::TICKS_PER_HOUR + + $minutes * self::TICKS_PER_MINUTE + + $seconds * self::TICKS_PER_SECOND + + $microseconds * self::TICKS_PER_MICROSECOND; + $ticks = static::mod($ticks, self::TICKS_PER_DAY); + + $clone = clone $this; + $clone->ticks = $ticks; + + return $clone; + } + + /** + * Resets time to the start of the current hour. + * + * @return static + */ + public function startOfHour(): static + { + $hourTicks = $this->ticks - $this->ticks % self::TICKS_PER_HOUR; + + $clone = clone $this; + $clone->ticks = $hourTicks; + + return $clone; + } + + /** + * Sets time to the end of the current hour. + * + * @return static + */ + public function endOfHour(): static + { + $hourTicks = $this->ticks - $this->ticks % self::TICKS_PER_HOUR; + $endTicks = $hourTicks + self::TICKS_PER_HOUR - 1; + + $clone = clone $this; + $clone->ticks = $endTicks; + + return $clone; + } + + /** + * @param int $a Left side + * @param int $a Right side + * @return int + */ + protected static function mod(int $a, int $b): int + { + if ($a < 0) { + return $a % $b + $b; + } + + return $a % $b; + } + + /** + * Formats string using the same syntax as `DateTimeImmutable::format()`. + * + * As this uses DateTimeImmutable::format() to format the string, non-time formatters + * will still be interpreted. Be sure to escape those characters first. + * + * @param string $format Format string + * @return string + */ + public function format(string $format): string + { + return $this->toDateTimeImmutable()->format($format); + } + + /** + * Reset the format used to the default when converting to a string + * + * @return void + */ + public static function resetToStringFormat(): void + { + static::setToStringFormat(static::DEFAULT_TO_STRING_FORMAT); + } + + /** + * Set the default format used when converting to a string + * + * @param string $format The format to use in future __toString() calls. + * @return void + */ + public static function setToStringFormat(string $format): void + { + static::$toStringFormat = $format; + } + + /** + * Format the instance as a string using the set format + * + * @return string + */ + public function __toString(): string + { + return $this->format(static::$toStringFormat); + } + + /** + * Returns whether time is equal to target time. + * + * @param \Cake\Chronos\ChronosTime $target Target time + * @return bool + */ + public function equals(ChronosTime $target): bool + { + return $this->ticks === $target->ticks; + } + + /** + * Returns whether time is greater than target time. + * + * @param \Cake\Chronos\ChronosTime $target Target time + * @return bool + */ + public function greaterThan(ChronosTime $target): bool + { + return $this->ticks > $target->ticks; + } + + /** + * Returns whether time is greater than or equal to target time. + * + * @param \Cake\Chronos\ChronosTime $target Target time + * @return bool + */ + public function greaterThanOrEquals(ChronosTime $target): bool + { + return $this->ticks >= $target->ticks; + } + + /** + * Returns whether time is less than target time. + * + * @param \Cake\Chronos\ChronosTime $target Target time + * @return bool + */ + public function lessThan(ChronosTime $target): bool + { + return $this->ticks < $target->ticks; + } + + /** + * Returns whether time is less than or equal to target time. + * + * @param \Cake\Chronos\ChronosTime $target Target time + * @return bool + */ + public function lessThanOrEquals(ChronosTime $target): bool + { + return $this->ticks <= $target->ticks; + } + + /** + * Returns whether time is between time range. + * + * @param \Cake\Chronos\ChronosTime $start Start of target range + * @param \Cake\Chronos\ChronosTime $end End of target range + * @param bool $equals Whether to include the beginning and end of range + * @return bool + */ + public function between(ChronosTime $start, ChronosTime $end, bool $equals = true): bool + { + if ($start->greaterThan($end)) { + [$start, $end] = [$end, $start]; + } + + if ($equals) { + return $this->greaterThanOrEquals($start) && $this->lessThanOrEquals($end); + } + + return $this->greaterThan($start) && $this->lessThan($end); + } + + /** + * Returns whether time is start of day. + * + * @return bool + */ + public function isStartOfDay(): bool + { + return $this->ticks === 0; + } + + /** + * Returns whether time is end of day. + * + * Compares against 23:59:59, ignoring microseconds. + * + * @return bool + */ + public function isEndOfDay(): bool + { + $endOfDayTicks = 23 * self::TICKS_PER_HOUR + + 59 * self::TICKS_PER_MINUTE + + 59 * self::TICKS_PER_SECOND; + + $ticksWithoutMicroseconds = $this->ticks - $this->ticks % self::TICKS_PER_SECOND; + + return $ticksWithoutMicroseconds === $endOfDayTicks; + } + + /** + * Returns whether time is midnight. + * + * @return bool + */ + public function isMidnight(): bool + { + return $this->isStartOfDay(); + } + + /** + * Returns whether time is midday. + * + * @return bool + */ + public function isMidday(): bool + { + return $this->ticks === 12 * self::TICKS_PER_HOUR; + } + + /** + * Returns an `DateTimeImmutable` instance set to this clock time. + * + * @param \DateTimeZone|string|null $timezone Time zone the DateTimeImmutable instance will be in + * @return \DateTimeImmutable + */ + public function toDateTimeImmutable(DateTimeZone|string|null $timezone = null): DateTimeImmutable + { + $timezone = is_string($timezone) ? new DateTimeZone($timezone) : $timezone; + + return (new DateTimeImmutable(timezone: $timezone))->setTime( + $this->getHours(), + $this->getMinutes(), + $this->getSeconds(), + $this->getMicroseconds(), + ); + } + + /** + * Returns an `DateTimeImmutable` instance set to this clock time. + * + * Alias of `toDateTimeImmutable()`. + * + * @param \DateTimeZone|string|null $timezone Time zone the DateTimeImmutable instance will be in + * @return \DateTimeImmutable + */ + public function toNative(DateTimeZone|string|null $timezone = null): DateTimeImmutable + { + return $this->toDateTimeImmutable($timezone); + } + + /** + * Returns the time as an associative array. + * + * @return array{hour: int, minute: int, second: int, microsecond: int} + */ + public function toArray(): array + { + return [ + 'hour' => $this->getHours(), + 'minute' => $this->getMinutes(), + 'second' => $this->getSeconds(), + 'microsecond' => $this->getMicroseconds(), + ]; + } +} diff --git a/tests/TestCase/ChronosTimeTest.php b/tests/TestCase/ChronosTimeTest.php new file mode 100644 index 0000000..1be8a37 --- /dev/null +++ b/tests/TestCase/ChronosTimeTest.php @@ -0,0 +1,379 @@ +assertSame('12:13:14.123456', $t->format('H:i:s.u')); + + $t = new ChronosTime(); + $this->assertSame('12:13:14.123456', $t->format('H:i:s.u')); + } + + public function testConstructFromString(): void + { + $t = new ChronosTime('12:13'); + $this->assertSame('12:13:00.000000', $t->format('H:i:s.u')); + + $t = new ChronosTime('0.0.0.0'); + $this->assertSame('00:00:00.000000', $t->format('H:i:s.u')); + + $t = new ChronosTime('1:01:1.000001'); + $this->assertSame('01:01:01.000001', $t->format('H:i:s.u')); + + $t = new ChronosTime('23:59.59.999999'); + $this->assertSame('23:59:59.999999', $t->format('H:i:s.u')); + + $t = new ChronosTime('23:59.59.9999991'); + $this->assertSame('23:59:59.999999', $t->format('H:i:s.u')); + + $t = new ChronosTime('24:59.59.9999991'); + $this->assertSame('00:59:59.999999', $t->format('H:i:s.u')); + } + + public function testConstructFromInstance(): void + { + $t = new ChronosTime(new DateTimeImmutable('23:59:59.999999')); + $this->assertSame('23:59:59.999999', $t->format('H:i:s.u')); + + $t = new ChronosTime(new Chronos('23:59:59.999999')); + $this->assertSame('23:59:59.999999', $t->format('H:i:s.u')); + + $t = new ChronosTime(new ChronosTime(new Chronos('23:59:59.999999'))); + $this->assertSame('23:59:59.999999', $t->format('H:i:s.u')); + } + + public function testConstructInvalid(): void + { + $this->expectException(InvalidArgumentException::class); + new ChronosTime('tomorrow'); + } + + public function testConstructIncomplete(): void + { + $this->expectException(InvalidArgumentException::class); + new ChronosTime('23'); + } + + public function testConstructInvalidHours(): void + { + $this->expectException(InvalidArgumentException::class); + new ChronosTime('25:00:00'); + } + + public function testConstructInvalidMinutes(): void + { + $this->expectException(InvalidArgumentException::class); + new ChronosTime('23:60:00'); + } + + public function testConstructInvalidSeconds(): void + { + $this->expectException(InvalidArgumentException::class); + new ChronosTime('23:59:60'); + } + + public function testParse(): void + { + $t = ChronosTime::parse('23:59:59.999999'); + $this->assertSame('23:59:59.999999', $t->format('H:i:s.u')); + + Chronos::setTestNow(new Chronos('2001-01-01 12:13:14.123456', 'America/Chicago')); + $t = ChronosTime::parse(null, 'America/New_York'); + $this->assertSame('13:13:14.123456', $t->format('H:i:s.u')); + } + + public function testNow(): void + { + Chronos::setTestNow(new Chronos('2001-01-01 12:13:14.123456', 'America/Chicago')); + + $t = ChronosTime::now(); + $this->assertSame('12:13:14.123456', $t->format('H:i:s.u')); + + $t = ChronosTime::now('America/New_York'); + $this->assertSame('13:13:14.123456', $t->format('H:i:s.u')); + } + + public function testMidnight(): void + { + $t = ChronosTime::midnight(); + $this->assertSame('00:00:00.000000', $t->format('H:i:s.u')); + } + + public function testNoon(): void + { + $t = ChronosTime::noon(); + $this->assertSame('12:00:00.000000', $t->format('H:i:s.u')); + } + + public function testEndOfDay(): void + { + $t = ChronosTime::endOfDay(); + $this->assertSame('23:59:59.000000', $t->format('H:i:s.u')); + + $t = ChronosTime::endOfDay(microseconds: true); + $this->assertSame('23:59:59.999999', $t->format('H:i:s.u')); + } + + public function testSetters(): void + { + $t = ChronosTime::midnight()->setHours(24); + $this->assertSame('00:00:00.000000', $t->format('H:i:s.u')); + + $t = ChronosTime::midnight()->setHours(-1); + $this->assertSame('23:00:00.000000', $t->format('H:i:s.u')); + + $t = ChronosTime::midnight()->setMinutes(60); + $this->assertSame('01:00:00.000000', $t->format('H:i:s.u')); + + $t = ChronosTime::midnight()->setMinutes(-1); + $this->assertSame('23:59:00.000000', $t->format('H:i:s.u')); + + $t = ChronosTime::midnight()->setSeconds(60); + $this->assertSame('00:01:00.000000', $t->format('H:i:s.u')); + + $t = ChronosTime::midnight()->setSeconds(-1); + $this->assertSame('23:59:59.000000', $t->format('H:i:s.u')); + + $t = ChronosTime::midnight()->setMicroseconds(1_000_000); + $this->assertSame('00:00:01.000000', $t->format('H:i:s.u')); + + $t = ChronosTime::midnight()->setMicroseconds(-1); + $this->assertSame('23:59:59.999999', $t->format('H:i:s.u')); + + $t = ChronosTime::midnight()->setHours(25)->setMinutes(120)->setSeconds(120)->setMicroseconds(2_000_001); + $this->assertSame('03:02:02.000001', $t->format('H:i:s.u')); + } + + public function testGetters(): void + { + $t = ChronosTime::midnight()->setTime(-1, -1, -1, -1); + $this->assertSame(22, $t->getHours()); + $this->assertSame(58, $t->getMinutes()); + $this->assertSame(58, $t->getSeconds()); + $this->assertSame(999_999, $t->getMicroseconds()); + } + + public function testSetTime(): void + { + $t = new ChronosTime(); + $new = $t->setTime(); + $this->assertNotSame($new, $t); + $this->assertSame('00:00:00.000000', $new->format('H:i:s.u')); + + $t = ChronosTime::midnight()->setTime(24, 120, 120, 1_000_000); + $this->assertSame('02:02:01.000000', $t->format('H:i:s.u')); + + $t = ChronosTime::midnight()->setTime(-1, -1, -1, -1); + $this->assertSame('22:58:58.999999', $t->format('H:i:s.u')); + + $t = ChronosTime::midnight()->setTime(-1, 120, -1, 1_000_001); + $this->assertSame('01:00:00.000001', $t->format('H:i:s.u')); + } + + public function testStartOfHour(): void + { + $t = ChronosTime::parse('12:30:45.123456'); + $start = $t->startOfHour(); + + $this->assertNotSame($t, $start); + $this->assertSame('12:00:00.000000', $start->format('H:i:s.u')); + + $t = ChronosTime::parse('00:59:59.999999'); + $this->assertSame('00:00:00.000000', $t->startOfHour()->format('H:i:s.u')); + + $t = ChronosTime::parse('23:01:01'); + $this->assertSame('23:00:00.000000', $t->startOfHour()->format('H:i:s.u')); + } + + public function testEndOfHour(): void + { + $t = ChronosTime::parse('12:30:45.123456'); + $end = $t->endOfHour(); + + $this->assertNotSame($t, $end); + $this->assertSame('12:59:59.999999', $end->format('H:i:s.u')); + + $t = ChronosTime::parse('00:00:00'); + $this->assertSame('00:59:59.999999', $t->endOfHour()->format('H:i:s.u')); + + $t = ChronosTime::parse('23:30:00'); + $this->assertSame('23:59:59.999999', $t->endOfHour()->format('H:i:s.u')); + } + + public function testFormat(): void + { + $t = new ChronosTime('23:59:59.999999'); + $this->assertSame('23:59:59.999999', $t->format('H:i:s.u')); + } + + public function testComparisons(): void + { + $t1 = ChronosTime::parse('00:00:00'); + $t2 = ChronosTime::parse('00:00:00'); + $this->assertTrue($t1->equals($t2)); + $this->assertTrue($t1->greaterThanOrEquals($t2)); + $this->assertTrue($t1->lessThanOrEquals($t2)); + $this->assertFalse($t1->greaterThan($t2)); + $this->assertFalse($t1->lessThan($t2)); + + $t1 = ChronosTime::parse('00:00:00'); + $t2 = ChronosTime::parse('00:00:00')->setHours(24); + $this->assertTrue($t1->equals($t2)); + $this->assertTrue($t1->greaterThanOrEquals($t2)); + $this->assertTrue($t1->lessThanOrEquals($t2)); + $this->assertFalse($t1->greaterThan($t2)); + $this->assertFalse($t1->lessThan($t2)); + + $t1 = ChronosTime::parse('00:00:00'); + $t2 = ChronosTime::parse('00:00:00.000001'); + $this->assertTrue($t1->lessThan($t2)); + $this->assertTrue($t1->lessThanOrEquals($t2)); + $this->assertFalse($t1->equals($t2)); + $this->assertFalse($t1->greaterThan($t2)); + $this->assertFalse($t1->greaterThanOrEquals($t2)); + + $t1 = ChronosTime::parse('00:00:00.000001'); + $t2 = ChronosTime::parse('00:00:00'); + $this->assertTrue($t1->greaterThan($t2)); + $this->assertTrue($t1->greaterThanOrEquals($t2)); + $this->assertFalse($t1->equals($t2)); + $this->assertFalse($t1->lessThan($t2)); + $this->assertFalse($t1->lessThanOrEquals($t2)); + + $t1 = ChronosTime::parse('00:00:00.000001'); + $t2 = ChronosTime::parse('00:00:00.000002'); + $t3 = ChronosTime::parse('00:00:00.000003'); + $this->assertTrue($t2->between($t1, $t3)); + + $t1 = ChronosTime::parse('00:00:00.000001'); + $t2 = ChronosTime::parse('00:00:00.000002'); + $t3 = ChronosTime::parse('00:00:00.000003'); + $this->assertTrue($t2->between($t1, $t3, false)); + + $t1 = ChronosTime::parse('00:00:00.000001'); + $t2 = ChronosTime::parse('00:00:00.000002'); + $t3 = ChronosTime::parse('00:00:00.000003'); + $this->assertTrue($t1->between($t1, $t3, true)); + + $t1 = ChronosTime::parse('00:00:00.000001'); + $t2 = ChronosTime::parse('00:00:00.000002'); + $t3 = ChronosTime::parse('00:00:00.000003'); + $this->assertFalse($t1->between($t1, $t3, false)); + + $t1 = ChronosTime::parse('00:00:00.000001'); + $t2 = ChronosTime::parse('00:00:00.000002'); + $t3 = ChronosTime::parse('00:00:00.000003'); + $this->assertTrue($t2->between($t3, $t1, false)); + + $t1 = ChronosTime::parse('00:00:00.000001'); + $t2 = ChronosTime::parse('00:00:00.000002'); + $t3 = ChronosTime::parse('00:00:00.000003'); + $this->assertFalse($t1->between($t2, $t3)); + + $t1 = ChronosTime::parse('00:00:00.000001'); + $t2 = ChronosTime::parse('00:00:00.000002'); + $t3 = ChronosTime::parse('00:00:00.000003'); + $this->assertFalse($t3->between($t1, $t2)); + } + + public function testIsStartOfDay(): void + { + $this->assertTrue(ChronosTime::parse('00:00:00')->isStartOfDay()); + $this->assertTrue(ChronosTime::midnight()->isStartOfDay()); + $this->assertFalse(ChronosTime::parse('00:00:00.000001')->isStartOfDay()); + $this->assertFalse(ChronosTime::parse('00:00:01')->isStartOfDay()); + $this->assertFalse(ChronosTime::noon()->isStartOfDay()); + } + + public function testIsEndOfDay(): void + { + $this->assertTrue(ChronosTime::parse('23:59:59')->isEndOfDay()); + $this->assertTrue(ChronosTime::endOfDay()->isEndOfDay()); + $this->assertTrue(ChronosTime::parse('23:59:59.999999')->isEndOfDay()); + $this->assertFalse(ChronosTime::parse('23:59:58')->isEndOfDay()); + $this->assertFalse(ChronosTime::midnight()->isEndOfDay()); + $this->assertFalse(ChronosTime::noon()->isEndOfDay()); + } + + public function testIsMidnight(): void + { + $this->assertTrue(ChronosTime::midnight()->isMidnight()); + $this->assertTrue(ChronosTime::parse('00:00:00')->isMidnight()); + $this->assertFalse(ChronosTime::parse('00:00:00.000001')->isMidnight()); + $this->assertFalse(ChronosTime::noon()->isMidnight()); + } + + public function testIsMidday(): void + { + $this->assertTrue(ChronosTime::noon()->isMidday()); + $this->assertTrue(ChronosTime::parse('12:00:00')->isMidday()); + $this->assertFalse(ChronosTime::parse('12:00:00.000001')->isMidday()); + $this->assertFalse(ChronosTime::parse('12:00:01')->isMidday()); + $this->assertFalse(ChronosTime::midnight()->isMidday()); + } + + public function testToDateTimeImmutable(): void + { + $time = ChronosTime::parse('23:59:59.999999'); + $native = $time->toDateTimeImmutable(); + $this->assertSame('23:59:59.999999', $native->format('H:i:s.u')); + + $native = $time->toNative(); + $this->assertSame('23:59:59.999999', $native->format('H:i:s.u')); + + $native = $time->toDateTimeImmutable('Asia/Tokyo'); + $this->assertSame('23:59:59.999999', $native->format('H:i:s.u')); + $this->assertSame('Asia/Tokyo', $native->getTimezone()->getName()); + + $native = $time->toNative('Asia/Tokyo'); + $this->assertSame('23:59:59.999999', $native->format('H:i:s.u')); + $this->assertSame('Asia/Tokyo', $native->getTimezone()->getName()); + } + + public function testToString(): void + { + $t = new ChronosTime('12:13:14.123456'); + $this->assertSame('12:13:14', (string)$t); + + ChronosTime::setToStringFormat('H:i:s.u'); + $this->assertSame('12:13:14.123456', (string)$t); + + ChronosTime::resetToStringFormat(); + $this->assertSame('12:13:14', (string)$t); + } + + public function testToArray(): void + { + $t = new ChronosTime('12:30:45.123456'); + $array = $t->toArray(); + + $this->assertSame(12, $array['hour']); + $this->assertSame(30, $array['minute']); + $this->assertSame(45, $array['second']); + $this->assertSame(123456, $array['microsecond']); + $this->assertCount(4, $array); + } +}