Skip to content

Commit

Permalink
Merge pull request #347 from thekid/feature/date-timezone-string
Browse files Browse the repository at this point in the history
Accept strings or util.TimeZone instances
  • Loading branch information
thekid authored Dec 31, 2024
2 parents 8dc763f + 12f4505 commit fdcb46d
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 38 deletions.
72 changes: 50 additions & 22 deletions src/main/php/util/Date.class.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php namespace util;

use DateTime;
use DateTime, DateTimeZone;
use lang\{IllegalArgumentException, IllegalStateException, Value};

/**
Expand Down Expand Up @@ -38,28 +38,36 @@ static function __static() {
* timezone is used.
*
* @param ?int|float|string|DateTime $in
* @param ?util.TimeZone $timezone default NULL string of timezone
* @param ?string|util.TimeZone $timezone
* @throws lang.IllegalArgumentException in case the date is unparseable
*/
public function __construct($in= null, ?TimeZone $timezone= null) {
public function __construct($in= null, $timezone= null) {
if (null === $timezone) {
$tz= null;
} else if ($timezone instanceof TimeZone) {
$tz= $timezone->getHandle();
} else {
$tz= new DateTimeZone($timezone);
}

if (null === $in) {
$this->handle= date_create('now', $timezone ? $timezone->getHandle() : null);
$this->handle= date_create('now', $tz);
} else if ($in instanceof DateTime) {
$this->handle= $in;
} else if (is_int($in) || (string)(int)$in === $in) {

// Specially mark timestamps for parsing (we assume here that strings
// containing only digits are timestamps)
$this->handle= date_create('@'.$in);
$timezone && date_timezone_set($this->handle, $timezone->getHandle());
$tz && date_timezone_set($this->handle, $tz);
} else if (is_float($in)) {

// Timestamps with microseconds are defined as `"@" "-"? [0-9]+ "." [0-9]{0,6}`,
// see https://www.php.net/manual/en/datetime.formats.php#datetime.formats.relative
$this->handle= date_create('@'.sprintf('%.6f', $in));
$timezone && date_timezone_set($this->handle, $timezone->getHandle());
$tz && date_timezone_set($this->handle, $tz);
} else {
if (false === ($this->handle= date_create($in ?? 'now', $timezone ? $timezone->getHandle() : null))) {
if (false === ($this->handle= date_create($in ?? 'now', $tz))) {
throw new IllegalArgumentException('Given argument is neither a timestamp nor a well-formed timestring: '.Objects::stringOf($in));
}
}
Expand Down Expand Up @@ -96,13 +104,16 @@ public function __unserialize($data) {
* @param int $hour
* @param int $minute
* @param int $second
* @param ?util.TimeZone $tz default NULL
* @param ?string|util.TimeZone $timezone
* @return self
*/
public static function create($year, $month, $day, $hour, $minute, $second, ?TimeZone $tz= null): self {
$date= date_create();
if ($tz) {
date_timezone_set($date, $tz->getHandle());
public static function create($year, $month, $day, $hour, $minute, $second, $timezone= null): self {
if (null === $timezone) {
$date= date_create();
} else if ($timezone instanceof TimeZone) {
$date= date_create('now', $timezone->getHandle());
} else {
$date= date_create('now', new DateTimeZone($timezone));
}

try {
Expand Down Expand Up @@ -133,9 +144,9 @@ public function equals($cmp): bool {
return $cmp instanceof self && $this->getTime() === $cmp->getTime();
}

/** Static method to get current date/time */
public static function now(?TimeZone $tz= null): self {
return new self(null, $tz);
/** @param ?string|util.TimeZone $timezone */
public static function now($timezone= null): self {
return new self(null, $timezone);
}

/** Compare this date to another date */
Expand Down Expand Up @@ -199,11 +210,20 @@ public function getTimeZone(): Timezone {
*
* @see php://date
* @param string $format default Date::DEFAULT_FORMAT format-string
* @param ?util.TimeZone $outtz default NULL
* @param ?string|util.TimeZone $timezone
* @return string the formatted date
*/
public function toString(string $format= self::DEFAULT_FORMAT, ?TimeZone $outtz= null): string {
return date_format(($outtz === null ? $this : $outtz->translate($this))->handle, $format);
public function toString(string $format= self::DEFAULT_FORMAT, $timezone= null): string {
if (null === $timezone) {
$handle= $this->handle;
} else if ($timezone instanceof TimeZone) {
$handle= clone $this->handle;
date_timezone_set($handle, $timezone->getHandle());
} else {
$handle= clone $this->handle;
date_timezone_set($handle, new DateTimeZone($timezone));
}
return date_format($handle, $format);
}

/**
Expand All @@ -214,11 +234,10 @@ public function toString(string $format= self::DEFAULT_FORMAT, ?TimeZone $outtz=
*
* @see php://strftime
* @param string $format
* @param ?util.TimeZone $outtz default NULL
* @param ?string|util.TimeZone $timezone
* @return string
* @throws lang.IllegalArgumentException if unsupported token has been given
*/
public function format(string $format, ?TimeZone $outtz= null): string {
public function format(string $format, $timezone= null): string {
static $replace= [
'%d' => 'd',
'%m' => 'm',
Expand Down Expand Up @@ -253,6 +272,15 @@ public function format(string $format, ?TimeZone $outtz= null): string {
'%%' => '%'
];

return date_format(($outtz === null ? $this : $outtz->translate($this))->handle, strtr($format, $replace));
if (null === $timezone) {
$handle= $this->handle;
} else if ($timezone instanceof TimeZone) {
$handle= clone $this->handle;
date_timezone_set($handle, $timezone->getHandle());
} else {
$handle= clone $this->handle;
date_timezone_set($handle, new DateTimeZone($timezone));
}
return date_format($handle, strtr($format, $replace));
}
}
48 changes: 32 additions & 16 deletions src/test/php/util/unittest/DateTest.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@
class DateTest {
private $nowTime, $nowDate, $refDate, $tz;

/** @return iterable */
private function timezones() {
yield 'Europe/Berlin';
yield new TimeZone('Europe/Berlin');
}

#[Before]
public function setUp() {

Expand Down Expand Up @@ -51,11 +57,11 @@ public function constructorUnixtimestampWithoutTz() {
$this->assertDateEquals('2007-08-23T12:35:47+00:00', new Date(1187872547));
}

#[Test]
public function constructorUnixtimestampWithTz() {
$this->assertDateEquals('2007-08-23T14:35:47+02:00', new Date(1187872547, new TimeZone('Europe/Berlin')));
#[Test, Values(from: 'timezones')]
public function constructorUnixtimestampWithTz($tz) {
$this->assertDateEquals('2007-08-23T14:35:47+02:00', new Date(1187872547, $tz));
}

#[Test]
public function constructorParseTz() {
$date= new Date('2007-01-01 01:00:00 Europe/Berlin');
Expand Down Expand Up @@ -157,7 +163,12 @@ public function dateCreate() {
// Test with a date before 1971
Assert::equals(-44668800, Date::create(1968, 8, 2, 0, 0, 0)->getTime());
}


#[Test, Values(from: 'timezones')]
public function create_date_with_timezone($tz) {
Assert::equals(-44672400, Date::create(1968, 8, 2, 0, 0, 0, $tz)->getTime());
}

#[Test]
public function pre1970() {
$this->assertDateEquals('1969-02-01T00:00:00+00:00', new Date('01.02.1969'));
Expand Down Expand Up @@ -268,21 +279,29 @@ public function emptyTimeZoneNameIfUnknown() {
}

#[Test]
public function toStringOutput() {
$date= new Date('2007-11-10 20:15+0100');
Assert::equals('2007-11-10 20:15:00+0100', $date->toString());
Assert::equals('2007-11-10 19:15:00+0000', $date->toString(Date::DEFAULT_FORMAT, new TimeZone('GMT')));
public function string_representation() {
Assert::equals(
'2007-11-10 20:15:00+0100',
(new Date('2007-11-10 20:15+0100'))->toString(Date::DEFAULT_FORMAT)
);
}

#[Test, Values(from: 'timezones')]
public function string_representation_with_timezone($timezone) {
Assert::equals(
'2007-11-10 20:15:00+0100',
(new Date('2007-11-10 19:15+0000'))->toString(Date::DEFAULT_FORMAT, $timezone)
);
}

#[Test]
public function toStringOutputPreserved() {
public function timezone_preserved_during_serialization() {
$date= unserialize(serialize(new Date('2007-11-10 20:15+0100')));
Assert::equals('2007-11-10 20:15:00+0100', $date->toString());
Assert::equals('2007-11-10 19:15:00+0000', $date->toString(Date::DEFAULT_FORMAT, new TimeZone('GMT')));
}

#[Test, Expect(IllegalArgumentException::class)]
public function malformedInputString() {
public function malformed_input_string() {
new Date('@@not-a-date@@');
}

Expand All @@ -309,10 +328,7 @@ public function unknownTimeZoneOffsetInString() {
#[Test]
public function constructorBrokenAfterException() {
Date::now();
try {
new Date('bogus');
$this->fail('No exception raised', null, IllegalArgumentException::class);
} catch (\lang\IllegalArgumentException $expected) { }
Assert::throws(IllegalArgumentException::class, fn() => new Date('bogus'));
Date::now();
}

Expand Down

0 comments on commit fdcb46d

Please sign in to comment.