Skip to content

Commit

Permalink
Add Instant::truncatedTo(), roundedTo()
Browse files Browse the repository at this point in the history
  • Loading branch information
emonkak committed Dec 3, 2015
1 parent 28747e2 commit bc072d2
Show file tree
Hide file tree
Showing 3 changed files with 164 additions and 0 deletions.
53 changes: 53 additions & 0 deletions src/Instant.php
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,59 @@ public function minusDays($days)
return $this->plusDays(-$days);
}

/**
* Returns a copy of this with the time truncated.
*
* @param Duration $unit
*
* @return Instant
*
* @throws DateTimeException if the unit is not supported
*/
public function truncatedTo(Duration $unit)
{
$remainder = $this->modulo($unit);
return $this->minus($remainder);
}

/**
* Returns a copy of this with the time rounded.
*
* @param Duration $unit
*
* @return Instant
*
* @throws DateTimeException if the unit is not supported
*/
public function roundedTo(Duration $unit)
{
$remainder = $this->modulo($unit);
if ($remainder->plus($remainder)->isLessThan($unit)) {
return $this->minus($remainder);
}
return $this->plus($unit->minus($remainder));
}

/**
* @param Duration $unit
*
* @return Duration
*
* @throws DateTimeException if the unit is not supported
*/
private function modulo(Duration $unit)
{
if ($unit->getSeconds() > LocalTime::SECONDS_PER_DAY) {
throw new DateTimeException('Unit is too large to be used for modulo');
}
$unitNanos = $unit->getTotalNanos();
if ((LocalTime::NANOS_PER_DAY % $unitNanos) !== 0) {
throw new DateTimeException('Unit must divide into a standard day without remainder');
}
$nanoOfDay = ($this->epochSecond % LocalTime::SECONDS_PER_DAY) * LocalTime::NANOS_PER_SECOND + $this->nano;
return Duration::ofSeconds(0, $nanoOfDay % $unitNanos);
}

/**
* {@inheritdoc}
*/
Expand Down
3 changes: 3 additions & 0 deletions src/LocalTime.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ class LocalTime implements DateTimeAccessor
const SECONDS_PER_HOUR = 3600;
const SECONDS_PER_DAY = 86400;
const NANOS_PER_SECOND = 1000000000;
const NANOS_PER_MINUTE = 60000000000;
const NANOS_PER_HOUR = 3600000000000;
const NANOS_PER_DAY = 86400000000000;

/**
* The hour, in the range 0 to 23.
Expand Down
108 changes: 108 additions & 0 deletions tests/InstantTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,114 @@ public function providerPlusDays()
];
}

/**
* @dataProvider provideTruncatedTo
*
* @param integer $second The base second.
* @param integer $nano The base nano-of-second.
* @param integer $unitSecond The unit second.
* @param integer $unitNano The unit nano-of-second.
* @param integer $expectedSecond The expected second of the result.
* @param integer $expectedNano The expected nano of the result.
*/
public function testTruncatedTo($second, $nano, $unitSecond, $unitNano, $expectedSecond, $expectedNano)
{
$unit = Duration::ofSeconds($unitSecond, $unitNano);
$result = Instant::of($second, $nano)->truncatedTo($unit);
$this->assertReadableInstantIs($expectedSecond, $expectedNano, $result);
}

/**
* @return array
*/
public function provideTruncatedTo()
{
return [
[0, 0, 30, 0, 0, 0],
[0, 0, 1, 500000000, 0, 0],
[1, 0, 30, 0, 0, 0],
[1, 0, 1, 500000000, 0, 0],
[14, 987654321, 30, 0, 0, 0],
[14, 987654321, 1, 500000000, 13, 500000000],
[15, 987654321, 30, 0, 0, 0],
[15, 987654321, 1, 500000000, 15, 0],
[29, 0, 30, 0, 0, 0],
[29, 0, 1, 500000000, 28, 500000000],
[30, 0, 30, 0, 30, 0],
[30, 0, 1, 500000000, 30, 0],
];
}

/**
* @expectedException \Brick\DateTime\DateTimeException
*/
public function testTruncatedToLargeUnitThrowsException()
{
Instant::of(0)->truncatedTo(Duration::ofDays(2));
}

/**
* @expectedException \Brick\DateTime\DateTimeException
*/
public function testTruncatedToNotDivisibleUnitThrowsException()
{
Instant::of(0)->truncatedTo(Duration::ofSeconds(7));
}

/**
* @dataProvider provideRoundedTo
*
* @param integer $second The base second.
* @param integer $nano The base nano-of-second.
* @param integer $unitSecond The unit second.
* @param integer $unitNano The unit nano-of-second.
* @param integer $expectedSecond The expected second of the result.
* @param integer $expectedNano The expected nano of the result.
*/
public function testRoundedTo($second, $nano, $unitSecond, $unitNano, $expectedSecond, $expectedNano)
{
$unit = Duration::ofSeconds($unitSecond, $unitNano);
$result = Instant::of($second, $nano)->roundedTo($unit);
$this->assertReadableInstantIs($expectedSecond, $expectedNano, $result);
}

/**
* @return array
*/
public function provideRoundedTo()
{
return [
[0, 0, 30, 0, 0, 0],
[0, 0, 1, 500000000, 0, 0],
[1, 0, 30, 0, 0, 0],
[1, 0, 1, 500000000, 1, 500000000],
[14, 987654321, 30, 0, 0, 0],
[14, 987654321, 1, 500000000, 15, 0],
[15, 987654321, 30, 0, 30, 0],
[15, 987654321, 1, 500000000, 16, 500000000],
[29, 0, 30, 0, 30, 0],
[29, 0, 1, 500000000, 28, 500000000],
[30, 0, 30, 0, 30, 0],
[30, 0, 1, 500000000, 30, 0],
];
}

/**
* @expectedException \Brick\DateTime\DateTimeException
*/
public function testRoundedToLargeUnitThrowsException()
{
Instant::of(0)->roundedTo(Duration::ofDays(2));
}

/**
* @expectedException \Brick\DateTime\DateTimeException
*/
public function testRoundedToNotDivisibleUnitThrowsException()
{
Instant::of(0)->roundedTo(Duration::ofSeconds(7));
}

/**
* @dataProvider providerCompareTo
*
Expand Down

0 comments on commit bc072d2

Please sign in to comment.