Skip to content

Commit

Permalink
fix precision of elapsed periods
Browse files Browse the repository at this point in the history
  • Loading branch information
Baptouuuu committed Nov 28, 2024
1 parent 04f9bbb commit ec45ec7
Show file tree
Hide file tree
Showing 16 changed files with 318 additions and 118 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
- `Innmind\TimeContinuum\Clock::ofFormat()`
- `Innmind\TimeContinuum\Offset::plus()`
- `Innmind\TimeContinuum\Offset::minus()`
- `Innmind\TimeContinuum\ElapsedPeriod::asPeriod()`
- `Innmind\TimeContinuum\Period\Value::seconds()`

### Changed

Expand Down Expand Up @@ -72,6 +74,9 @@
- `Innmind\TimeContinuum\ElapsedPeriod::ofPeriod()`
- `Innmind\TimeContinuum\PointInTime::milliseconds()`
- `Innmind\TimeContinuum\Offset::of()`
- `Innmind\TimeContinuum\ElapsedPeriod::milliseconds()`
- `Innmind\TimeContinuum\Period\Value::second`
- `Innmind\TimeContinuum\Period\Value::milliseconds()`

## 3.4.1 - 2023-09-17

Expand Down
109 changes: 109 additions & 0 deletions proofs/elapsedPeriod.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<?php
declare(strict_types = 1);

use Innmind\TimeContinuum\{
PointInTime\HighResolution,
Period,
};
use Fixtures\Innmind\TimeContinuum as Fixtures;
use Innmind\BlackBox\Set;

return static function() {
yield proof(
'High resolution elapsed period',
given(
Set\Integers::above(0),
Set\Integers::above(0),
Set\Integers::between(0, 999_999_999),
Set\Integers::between(0, 999_999_999),
)->filter(static fn($start, $end) => $end > $start),
static function(
$assert,
$startSeconds,
$endSeconds,
$startNanoseconds,
$endNanoseconds,
) {
$start = HighResolution::of($startSeconds, $startNanoseconds);
$end = HighResolution::of($endSeconds, $endNanoseconds);

$assert->true(
$start
->elapsedSince($start)
->equals(
Period::microsecond(0)->asElapsedPeriod(),
),
);
$assert->true(
$end
->elapsedSince($start)
->longerThan(
Period::microsecond(1)->asElapsedPeriod(),
),
);
},
);
yield proof(
'High resolution elapsed period within same second',
given(
Set\Integers::between(0, 999_999_999),
Set\Integers::between(0, 999_999_999),
)->filter(static fn($start, $end) => $end > $start),
static function(
$assert,
$startNanoseconds,
$endNanoseconds,
) {
$start = HighResolution::of(0, $startNanoseconds);
$end = HighResolution::of(0, $endNanoseconds);

$assert->true(
$start
->elapsedSince($start)
->equals(
Period::microsecond(0)->asElapsedPeriod(),
),
);
$assert->true(
$end
->elapsedSince($start)
->longerThan(
Period::microsecond(1)->asElapsedPeriod(),
),
);
},
);

yield proof(
'Elapsed period',
given(
Fixtures\PointInTime::any(),
Set\Integers::above(1),
),
static function($assert, $start, $microsecond) {
$assert->true(
$start
->elapsedSince($start)
->equals(
Period::microsecond(0)->asElapsedPeriod(),
),
);
$assert->true(
$start
->goForward(Period::microsecond($microsecond))
->elapsedSince($start)
->equals(
Period::microsecond($microsecond)->asElapsedPeriod(),
),
);
$assert->true(
$start
->goForward(Period::microsecond($microsecond))
->elapsedSince($start)
->longerThan(
Period::microsecond(0)->asElapsedPeriod(),
),
);
},
);
};
69 changes: 42 additions & 27 deletions src/ElapsedPeriod.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,52 +9,67 @@
final class ElapsedPeriod
{
/** @var int<0, max> */
private int $seconds;
/** @var int<0, 999> */
private int $milliseconds;
/** @var int<0, 999> */
private int $microseconds;

private function __construct(int $microseconds)
{
if ($microseconds < 0) {
throw new \RuntimeException((string) $microseconds);
}

/**
* @param int<0, max> $seconds
* @param int<0, 999> $milliseconds
* @param int<0, 999> $microseconds
*/
private function __construct(
int $seconds,
int $milliseconds,
int $microseconds,
) {
$this->seconds = $seconds;
$this->milliseconds = $milliseconds;
$this->microseconds = $microseconds;
}

/**
* @psalm-pure
* @internal
*
* @throws \RuntimeException
* @param int<0, max> $seconds
* @param int<0, 999> $milliseconds
* @param int<0, 999> $microseconds
*/
public static function of(int $microseconds): self
{
return new self($microseconds);
public static function of(
int $seconds,
int $milliseconds,
int $microseconds,
): self {
return new self($seconds, $milliseconds, $microseconds);
}

/**
* @return int<0, max>
*/
public function milliseconds(): int
public function longerThan(self $period): bool
{
/** @var int<0, max> */
return (int) ($this->microseconds / 1_000);
}
if ($this->seconds > $period->seconds) {
return true;
}

/**
* @return int<0, max>
*/
public function microseconds(): int
{
return $this->microseconds;
}
if ($this->milliseconds > $period->milliseconds) {
return true;
}

public function longerThan(self $period): bool
{
return $this->microseconds > $period->microseconds;
}

public function equals(self $period): bool
{
return $this->microseconds === $period->microseconds;
return $this->seconds === $period->seconds &&
$this->milliseconds === $period->milliseconds &&
$this->microseconds === $period->microseconds;
}

public function asPeriod(): Period
{
return Period::second($this->seconds)
->add(Period::millisecond($this->milliseconds))
->add(Period::microsecond($this->microseconds));
}
}
18 changes: 10 additions & 8 deletions src/Period.php
Original file line number Diff line number Diff line change
Expand Up @@ -458,13 +458,15 @@ public function asElapsedPeriod(): ElapsedPeriod
throw new \LogicException('Months and years can not be converted to microseconds');
}

$milliseconds = Period\Value::day->milliseconds($this->days()) +
Period\Value::hour->milliseconds($this->hours()) +
Period\Value::minute->milliseconds($this->minutes()) +
Period\Value::second->milliseconds($this->seconds()) +
$this->milliseconds();
$milliseconds *= 1_000;

return ElapsedPeriod::of($milliseconds + $this->microseconds());
$seconds = Period\Value::day->seconds($this->days()) +
Period\Value::hour->seconds($this->hours()) +
Period\Value::minute->seconds($this->minutes()) +
$this->second;

return ElapsedPeriod::of(
$seconds,
$this->millisecond,
$this->microsecond,
);
}
}
18 changes: 10 additions & 8 deletions src/Period/Value.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,24 @@
*/
enum Value
{
case second;
case minute;
case hour;
case day;

/**
* Returns the number of milliseconds contained in the number of seconds,
* minutes, hours and days
* Returns the number of seconds contained in the number of minutes, hours
* and days
*
* @param int<0, max> $number
*
* @return int<0, max>
*/
public function milliseconds(int $number): int
public function seconds(int $number): int
{
return match ($this) {
self::second => $number * 1000,
self::minute => $number * self::second->milliseconds(60),
self::hour => $number * self::minute->milliseconds(60),
self::day => $number * self::hour->milliseconds(24),
self::minute => $number * 60,
self::hour => $number * self::minute->seconds(60),
self::day => $number * self::hour->seconds(24),
};
}
}
35 changes: 27 additions & 8 deletions src/PointInTime.php
Original file line number Diff line number Diff line change
Expand Up @@ -143,14 +143,33 @@ public function elapsedSince(self $point): ElapsedPeriod
}

$seconds = ((int) $this->date->format('U')) - ((int) $point->date->format('U'));
$milliseconds = $seconds * 1_000;
$milliseconds += $this->millisecond()->toInt();
$milliseconds -= $point->millisecond()->toInt();
$microseconds = $milliseconds * 1_000;
$microseconds += $this->microsecond()->toInt();
$microseconds -= $point->microsecond()->toInt();

return ElapsedPeriod::of($microseconds);
$milliseconds = $this->millisecond()->toInt() - $point->millisecond()->toInt();
$microseconds = $this->microsecond()->toInt() - $point->microsecond()->toInt();

if ($milliseconds < 0) {
$seconds -= 1;
$milliseconds += 1_000;
}

if ($microseconds < 0) {
$milliseconds -= 1;
$microseconds += 1_000;
}

if ($seconds < 0 || $milliseconds < 0) {
throw new \RuntimeException(\sprintf(
'Negative period : %ss, %smillis, %smicros',
$seconds,
$milliseconds,
$microseconds,
));
}

return ElapsedPeriod::of(
$seconds,
$milliseconds,
$microseconds,
);
}

public function goBack(Period $period): self
Expand Down
37 changes: 29 additions & 8 deletions src/PointInTime/HighResolution.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ final class HighResolution
{
/**
* @param int<0, max> $seconds
* @param int<0, max> $nanoseconds
* @param int<0, 999_999_999> $nanoseconds
*/
private function __construct(
private int $seconds,
Expand All @@ -28,7 +28,7 @@ public static function now(): self
{
/**
* @var int<0, max> $seconds
* @var int<0, max> $nanoseconds
* @var int<0, 999_999_999> $nanoseconds
*/
[$seconds, $nanoseconds] = \hrtime();

Expand All @@ -39,20 +39,41 @@ public static function now(): self
* @internal
*
* @param int<0, max> $seconds
* @param int<0, max> $nanoseconds
* @param int<0, 999_999_999> $nanoseconds
*/
public static function of(int $seconds, int $nanoseconds): self
{
return new self($seconds, $nanoseconds);
}

public function elapsedSince(self $time): ElapsedPeriod
public function elapsedSince(self $other): ElapsedPeriod
{
$seconds = $this->seconds - $time->seconds;
$nanoseconds = $this->nanoseconds - $time->nanoseconds;
$seconds = $this->seconds - $other->seconds;
$nanoseconds = $this->nanoseconds - $other->nanoseconds;

$microseconds = ($seconds * 1_000_000) + (int) ($nanoseconds / 1_000);
if ($nanoseconds < 0) {
$seconds -= 1;
$nanoseconds += 1_000_000_000;
}

return ElapsedPeriod::of($microseconds);
/** @var int<0, 999> */
$microseconds = ((int) ($nanoseconds / 1_000)) % 1_000;
/** @var int<0, 999> */
$milliseconds = ((int) ($nanoseconds / 1_000_000)) % 1_000;

if ($seconds < 0) {
throw new \RuntimeException(\sprintf(
'Negative period : %ss, %smillis, %smicros',
$seconds,
$milliseconds,
$microseconds,
));
}

return ElapsedPeriod::of(
$seconds,
$milliseconds,
$microseconds,
);
}
}
Loading

0 comments on commit ec45ec7

Please sign in to comment.