Skip to content

Commit

Permalink
Fix microsecond rounding error on 32-bit systems
Browse files Browse the repository at this point in the history
  • Loading branch information
ramsey committed Feb 21, 2020
1 parent 1396eaf commit a7cf07a
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 17 deletions.
10 changes: 9 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
### Security


## [4.0.0-alpha3] - 2020-02-21

### Fixed

* Fix microsecond rounding error on 32-bit systems.


## [4.0.0-alpha2] - 2020-02-21

### Added
Expand Down Expand Up @@ -823,7 +830,8 @@ versions leading up to this release.*
[ramsey/uuid-doctrine]: https://github.com/ramsey/uuid-doctrine
[ramsey/uuid-console]: https://github.com/ramsey/uuid-console

[unreleased]: https://github.com/ramsey/uuid/compare/4.0.0-alpha2...HEAD
[unreleased]: https://github.com/ramsey/uuid/compare/4.0.0-alpha3...HEAD
[4.0.0-alpha3]: https://github.com/ramsey/uuid/compare/4.0.0-alpha2...4.0.0-alpha3
[4.0.0-alpha2]: https://github.com/ramsey/uuid/compare/4.0.0-alpha1...4.0.0-alpha2
[4.0.0-alpha1]: https://github.com/ramsey/uuid/compare/3.9.3...4.0.0-alpha1
[3.9.3]: https://github.com/ramsey/uuid/compare/3.9.2...3.9.3
Expand Down
4 changes: 1 addition & 3 deletions src/Converter/Time/GenericTimeConverter.php
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,8 @@ public function convertTime(Hexadecimal $uuidTimestamp): Time
new IntegerValue('122192928000000000')
);

// Round down so that the microseconds do not shift the timestamp
// into the next second, giving us the wrong Unix timestamp.
$unixTimestamp = $this->calculator->divide(
RoundingMode::DOWN,
RoundingMode::HALF_UP,
6,
$epochNanoseconds,
new IntegerValue('10000000')
Expand Down
15 changes: 14 additions & 1 deletion src/Converter/Time/PhpTimeConverter.php
Original file line number Diff line number Diff line change
Expand Up @@ -154,9 +154,22 @@ private function splitTime($time): array
return [];
}

$microseconds = $split[1];

// Ensure the microseconds are no longer than 6 digits. If they are,
// truncate the number to the first 6 digits and round up, if needed.
if (strlen($microseconds) > 6) {
$roundingDigit = (int) substr($microseconds, 6, 1);
$microseconds = (int) substr($microseconds, 0, 6);

if ($roundingDigit >= 5) {
$microseconds++;
}
}

return [
'sec' => $split[0],
'usec' => str_pad($split[1], 6, '0', STR_PAD_RIGHT),
'usec' => str_pad((string) $microseconds, 6, '0', STR_PAD_RIGHT),
];
}
}
13 changes: 1 addition & 12 deletions src/Rfc4122/UuidV1.php
Original file line number Diff line number Diff line change
Expand Up @@ -78,23 +78,12 @@ public function getDateTime(): DateTimeInterface
{
$time = $this->timeConverter->convertTime($this->fields->getTimestamp());

$microseconds = $time->getMicroSeconds()->toString();

if (strlen($microseconds) > 6) {
$roundingDigit = (int) substr($microseconds, 6, 1);
$microseconds = (int) substr($microseconds, 0, 6);

if ($roundingDigit >= 5) {
$microseconds++;
}
}

try {
return new DateTimeImmutable(
'@'
. $time->getSeconds()->toString()
. '.'
. str_pad((string) $microseconds, 6, '0', STR_PAD_LEFT)
. str_pad($time->getMicroSeconds()->toString(), 6, '0', STR_PAD_LEFT)
);
} catch (Throwable $e) {
throw new DateTimeException($e->getMessage(), (int) $e->getCode(), $e);
Expand Down
33 changes: 33 additions & 0 deletions tests/UuidTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,15 @@
use Brick\Math\BigDecimal;
use Brick\Math\RoundingMode;
use DateTimeInterface;
use Mockery;
use PHPUnit\Framework\MockObject\MockObject;
use Ramsey\Uuid\Builder\DefaultUuidBuilder;
use Ramsey\Uuid\Codec\StringCodec;
use Ramsey\Uuid\Codec\TimestampFirstCombCodec;
use Ramsey\Uuid\Codec\TimestampLastCombCodec;
use Ramsey\Uuid\Converter\Number\BigNumberConverter;
use Ramsey\Uuid\Converter\TimeConverterInterface;
use Ramsey\Uuid\Exception\DateTimeException;
use Ramsey\Uuid\Exception\InvalidArgumentException;
use Ramsey\Uuid\Exception\InvalidUuidStringException;
use Ramsey\Uuid\Exception\UnsupportedOperationException;
Expand Down Expand Up @@ -1464,6 +1470,33 @@ public function testUuidVersionConstantForVersion5(): void
$this->assertEquals($uuid->getVersion(), Uuid::UUID_TYPE_HASH_SHA1);
}

public function testGetDateTimeThrowsExceptionWhenDateTimeCannotParseDate(): void
{
$numberConverter = new BigNumberConverter();
$timeConverter = Mockery::mock(TimeConverterInterface::class);

$timeConverter
->shouldReceive('convertTime')
->once()
->andReturn(new Time(1234567890, '1234567'));

$builder = new DefaultUuidBuilder($numberConverter, $timeConverter);
$codec = new StringCodec($builder);

$factory = new UuidFactory();
$factory->setCodec($codec);

$uuid = $factory->fromString('b1484596-25dc-11ea-978f-2e728ce88125');

$this->expectException(DateTimeException::class);
$this->expectExceptionMessage(
'DateTimeImmutable::__construct(): Failed to parse time string '
. '(@1234567890.1234567) at position 18 (7): Unexpected character'
);

$uuid->getDateTime();
}

/**
* @param class-string $expectedClass
* @param mixed[] $args
Expand Down

0 comments on commit a7cf07a

Please sign in to comment.