From 3802129a1b5e8aa719f21c9ba57e0ed105a6cf35 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sat, 13 Dec 2025 11:55:15 +0100 Subject: [PATCH 1/5] Work with `DateInterval` instances in util.TimeSpan --- src/main/php/util/TimeSpan.class.php | 47 +++++++++++++++---- .../php/util/unittest/TimeSpanTest.class.php | 27 +++++++---- 2 files changed, 56 insertions(+), 18 deletions(-) diff --git a/src/main/php/util/TimeSpan.class.php b/src/main/php/util/TimeSpan.class.php index 98b1fe72b..572e9d0a8 100755 --- a/src/main/php/util/TimeSpan.class.php +++ b/src/main/php/util/TimeSpan.class.php @@ -1,27 +1,54 @@ getName()); + public function __construct($arg) { + $this->_seconds= self::secondsOf($arg); + } + + /** + * Converts given argument to seconds + * + * @param int|string|DateInterval $arg + * @return int + * @throws lang.IllegalArgumentException + */ + public static function secondsOf($arg) { + if (is_numeric($arg)) return (int)abs($arg); + + try { + if ($arg instanceof DateInterval) { + $i= $arg; + } else if ('P' === $arg[0] ?? null) { + $i= new DateInterval($arg); + } else { + $i= DateInterval::createFromDateString($arg); + } + } catch (Exception $e) { + throw new IllegalArgumentException('Cannot convert '.$arg, $e); } - $this->_seconds= (int)abs($secs); + + // Years and months do not have a constant amount of seconds. Technically, days + // don't either (think: leap seconds), but we'll handle them as 86400 seconds + if ($i->y || $i->m) throw new IllegalArgumentException('Cannot create from interval with years / months'); + + return $i->d * 86400 + $i->h * 3600 + $i->i * 60 + $i->s; } /** diff --git a/src/test/php/util/unittest/TimeSpanTest.class.php b/src/test/php/util/unittest/TimeSpanTest.class.php index ae8212ba5..76e2b98e0 100755 --- a/src/test/php/util/unittest/TimeSpanTest.class.php +++ b/src/test/php/util/unittest/TimeSpanTest.class.php @@ -1,24 +1,35 @@ toString()); + #[Test, Values([7265, 7265.0, 'PT2H1M5S', '2 hours 1 minute 5 seconds'])] + public function span_from($arg) { + Assert::equals('0d, 2h, 1m, 5s', (new TimeSpan($arg))->toString()); } #[Test] - public function newNegativeTimeSpan() { + public function negative_span() { Assert::equals('0d, 0h, 0m, 1s', (new TimeSpan(-1))->toString()); } - #[Test, Expect(IllegalArgumentException::class)] - public function wrongArguments() { - new TimeSpan('2 days'); + #[Test, Expect(IllegalArgumentException::class), Values([null, '', 'not a time span'])] + public function invalid_string_argument($arg) { + new TimeSpan($arg); + } + + #[Test, Expect(IllegalArgumentException::class), Values(['P1Y', 'P1M'])] + public function unsupported_dateinterval($arg) { + new TimeSpan(new DateInterval($arg)); + } + + #[Test] + public function span_from_dateinterval() { + Assert::equals('1d, 2h, 0m, 0s', (new TimeSpan(new DateInterval('P1DT2H')))->toString()); } #[Test] From 00e52d661ba4775d488fb6b942bc995c0c729c72 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sat, 13 Dec 2025 12:02:00 +0100 Subject: [PATCH 2/5] Handle PHP < 8.3 returning false instead of raising an exception --- src/main/php/util/TimeSpan.class.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/php/util/TimeSpan.class.php b/src/main/php/util/TimeSpan.class.php index 572e9d0a8..029b7826c 100755 --- a/src/main/php/util/TimeSpan.class.php +++ b/src/main/php/util/TimeSpan.class.php @@ -41,9 +41,12 @@ public static function secondsOf($arg) { $i= DateInterval::createFromDateString($arg); } } catch (Exception $e) { - throw new IllegalArgumentException('Cannot convert '.$arg, $e); + throw new IllegalArgumentException('Invalid time span '.$arg, $e); } + // PHP < 8.3 returns false for invalid time spans + if (false === $i) throw new IllegalArgumentException('Invalid time span '.$arg); + // Years and months do not have a constant amount of seconds. Technically, days // don't either (think: leap seconds), but we'll handle them as 86400 seconds if ($i->y || $i->m) throw new IllegalArgumentException('Cannot create from interval with years / months'); From c23973b7c2dff877d513f3c0bdd5131938b290b6 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sat, 13 Dec 2025 12:04:50 +0100 Subject: [PATCH 3/5] Inline secondsOf() --- src/main/php/util/TimeSpan.class.php | 49 ++++++++++++---------------- 1 file changed, 20 insertions(+), 29 deletions(-) diff --git a/src/main/php/util/TimeSpan.class.php b/src/main/php/util/TimeSpan.class.php index 029b7826c..16008171a 100755 --- a/src/main/php/util/TimeSpan.class.php +++ b/src/main/php/util/TimeSpan.class.php @@ -19,39 +19,30 @@ class TimeSpan implements Value { * @throws lang.IllegalArgumentException */ public function __construct($arg) { - $this->_seconds= self::secondsOf($arg); - } - - /** - * Converts given argument to seconds - * - * @param int|string|DateInterval $arg - * @return int - * @throws lang.IllegalArgumentException - */ - public static function secondsOf($arg) { - if (is_numeric($arg)) return (int)abs($arg); - - try { - if ($arg instanceof DateInterval) { - $i= $arg; - } else if ('P' === $arg[0] ?? null) { - $i= new DateInterval($arg); - } else { - $i= DateInterval::createFromDateString($arg); + if (is_numeric($arg)) { + $this->_seconds= (int)abs($arg); + } else { + try { + if ($arg instanceof DateInterval) { + $i= $arg; + } else if ('P' === $arg[0] ?? null) { + $i= new DateInterval($arg); + } else { + $i= DateInterval::createFromDateString($arg); + } + } catch (Exception $e) { + throw new IllegalArgumentException('Invalid time span '.$arg, $e); } - } catch (Exception $e) { - throw new IllegalArgumentException('Invalid time span '.$arg, $e); - } - // PHP < 8.3 returns false for invalid time spans - if (false === $i) throw new IllegalArgumentException('Invalid time span '.$arg); + // PHP < 8.3 returns false for invalid time spans + if (false === $i) throw new IllegalArgumentException('Invalid time span '.$arg); - // Years and months do not have a constant amount of seconds. Technically, days - // don't either (think: leap seconds), but we'll handle them as 86400 seconds - if ($i->y || $i->m) throw new IllegalArgumentException('Cannot create from interval with years / months'); + // Years and months do not have a constant amount of seconds. Technically, days + // don't either (think: leap seconds), but we'll handle them as 86400 seconds + if ($i->y || $i->m) throw new IllegalArgumentException('Cannot create from interval with years / months'); - return $i->d * 86400 + $i->h * 3600 + $i->i * 60 + $i->s; + $this->_seconds= $i->d * 86400 + $i->h * 3600 + $i->i * 60 + $i->s; + } } /** From 1572033790ce5e64682daf37f0705bd04b90b443 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sat, 13 Dec 2025 12:09:23 +0100 Subject: [PATCH 4/5] Ensure argument is a string in exception messages --- src/main/php/util/TimeSpan.class.php | 4 ++-- src/test/php/util/unittest/TimeSpanTest.class.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/php/util/TimeSpan.class.php b/src/main/php/util/TimeSpan.class.php index 16008171a..0ba514666 100755 --- a/src/main/php/util/TimeSpan.class.php +++ b/src/main/php/util/TimeSpan.class.php @@ -31,11 +31,11 @@ public function __construct($arg) { $i= DateInterval::createFromDateString($arg); } } catch (Exception $e) { - throw new IllegalArgumentException('Invalid time span '.$arg, $e); + throw new IllegalArgumentException('Invalid time span '.Objects::stringOf($arg), $e); } // PHP < 8.3 returns false for invalid time spans - if (false === $i) throw new IllegalArgumentException('Invalid time span '.$arg); + if (false === $i) throw new IllegalArgumentException('Invalid time span '.Objects::stringOf($arg)); // Years and months do not have a constant amount of seconds. Technically, days // don't either (think: leap seconds), but we'll handle them as 86400 seconds diff --git a/src/test/php/util/unittest/TimeSpanTest.class.php b/src/test/php/util/unittest/TimeSpanTest.class.php index 76e2b98e0..b96ebb2fa 100755 --- a/src/test/php/util/unittest/TimeSpanTest.class.php +++ b/src/test/php/util/unittest/TimeSpanTest.class.php @@ -17,7 +17,7 @@ public function negative_span() { Assert::equals('0d, 0h, 0m, 1s', (new TimeSpan(-1))->toString()); } - #[Test, Expect(IllegalArgumentException::class), Values([null, '', 'not a time span'])] + #[Test, Expect(IllegalArgumentException::class), Values([null, '', 'not a time span', false, true])] public function invalid_string_argument($arg) { new TimeSpan($arg); } From 88fedec1e8dd9c35d29e978751a1c5f399067b9f Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sat, 13 Dec 2025 12:12:37 +0100 Subject: [PATCH 5/5] QA: CS for test method names --- .../php/util/unittest/TimeSpanTest.class.php | 56 ++++++++++--------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/src/test/php/util/unittest/TimeSpanTest.class.php b/src/test/php/util/unittest/TimeSpanTest.class.php index b96ebb2fa..828fff923 100755 --- a/src/test/php/util/unittest/TimeSpanTest.class.php +++ b/src/test/php/util/unittest/TimeSpanTest.class.php @@ -44,12 +44,13 @@ public function add() { public function subtract() { Assert::equals('0d, 22h, 58m, 55s', (new TimeSpan(86400)) ->substract(new TimeSpan(3600), new TimeSpan(60)) - ->substract(new TimeSpan(5))->toString() + ->substract(new TimeSpan(5)) + ->toString() ); } #[Test] - public function subtractToZero() { + public function subtract_resulting_in_zero() { Assert::equals( '0d, 0h, 0m, 0s', (new TimeSpan(6100))->substract(new TimeSpan(6100))->toString() @@ -57,50 +58,51 @@ public function subtractToZero() { } #[Test, Expect(IllegalStateException::class)] - public function subtractToNegative() { + public function subtract_may_not_result_in_negative_values() { (new TimeSpan(0))->substract(new TimeSpan(1)); } #[Test] - public function addAndSubstract() { + public function add_and_subtract() { Assert::equals('1d, 1h, 0m, 55s', (new TimeSpan(86400)) ->add(new TimeSpan(3600), new TimeSpan(60)) - ->substract(new TimeSpan(5))->toString() + ->substract(new TimeSpan(5)) + ->toString() ); } #[Test, Expect(IllegalArgumentException::class)] - public function addWrongArguments() { + public function incorrect_argument_to_add() { (new TimeSpan(0))->add('2 days'); } #[Test] - public function fromSeconds() { + public function from_seconds() { Assert::equals('0d, 1h, 0m, 0s', TimeSpan::seconds(3600)->toString()); } #[Test] - public function fromMinutes() { + public function from_minutes() { Assert::equals('0d, 2h, 7m, 0s', TimeSpan::minutes(127)->toString()); } #[Test] - public function fromHours() { + public function from_hours() { Assert::equals('1d, 3h, 0m, 0s', TimeSpan::hours(27)->toString()); } #[Test] - public function fromDays() { + public function from_days() { Assert::equals('40d, 0h, 0m, 0s', TimeSpan::days(40)->toString()); } #[Test] - public function fromWeeks() { + public function from_weeks() { Assert::equals('7d, 0h, 0m, 0s', TimeSpan::weeks(1)->toString()); } #[Test] - public function wholeValues() { + public function whole_values() { $t= new TimeSpan(91865); Assert::equals(5, $t->getWholeSeconds(), 'wholeSeconds'); Assert::equals(31, $t->getWholeMinutes(), 'wholeMinutes'); @@ -109,77 +111,77 @@ public function wholeValues() { } #[Test] - public function formatSeconds() { + public function format_seconds() { Assert::equals('91865', (new TimeSpan(91865))->format('%s')); } #[Test] - public function formatWholeSeconds() { + public function format_whole_seconds() { Assert::equals('5', (new TimeSpan(91865))->format('%w')); } #[Test] - public function formatMinutes() { + public function format_minutes() { Assert::equals('1531', (new TimeSpan(91865))->format('%m')); } #[Test] - public function formatFloatMinutes() { + public function format_float_minutes() { Assert::equals('1531.08', (new TimeSpan(91865))->format('%M')); } #[Test] - public function formatWholeMinutes() { + public function format_whole_minutes() { Assert::equals('31', (new TimeSpan(91865))->format('%j')); } #[Test] - public function formatHours() { + public function format_hours() { Assert::equals('25', (new TimeSpan(91865))->format('%h')); } #[Test] - public function formatFloatHours() { + public function format_float_hours() { Assert::equals('25.52', (new TimeSpan(91865))->format('%H')); } #[Test] - public function formatWholeHours() { + public function format_whole_hours() { Assert::equals('1', (new TimeSpan(91865))->format('%y')); } #[Test] - public function formatDays() { + public function format_days() { Assert::equals('1', (new TimeSpan(91865))->format('%d')); } #[Test] - public function formatFloatDays() { + public function format_float_days() { Assert::equals('1.06', (new TimeSpan(91865))->format('%D')); } #[Test] - public function formatWholeDays() { + public function format_whole_days() { Assert::equals('1', (new TimeSpan(91865))->format('%e')); } #[Test] - public function format() { + public function format_() { Assert::equals('1d1h', (new TimeSpan(91865))->format('%ed%yh')); } #[Test] - public function formatPercent() { + public function format_percent() { Assert::equals('%1d%1h%', (new TimeSpan(91865))->format('%%%ed%%%yh%%')); } #[Test] - public function compareTwoEqualInstances() { + public function compare_two_equal_instances() { Assert::equals(new TimeSpan(3600), new TimeSpan(3600)); } #[Test] - public function compareTwoUnequalInstances() { + public function compare_two_differing_instances() { Assert::notEquals(new TimeSpan(3600), new TimeSpan(0)); } } \ No newline at end of file