Skip to content

Commit

Permalink
Streamline comparisons in Moment (#40)
Browse files Browse the repository at this point in the history
Co-authored-by: Christian Kolb <[email protected]>
  • Loading branch information
christian-kolb and Christian Kolb authored Mar 27, 2024
1 parent ebc7da7 commit 653a356
Show file tree
Hide file tree
Showing 20 changed files with 1,677 additions and 58 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## 0.10.0

- Added `is*InTimeZone(Time | Date | Month | Year $equalTo, \DateTimeZone $timeZone): bool` methods to `Moment`.
- Added missing comparison methods to `Time` (like `isNotAfter(self $time): bool`).
- Deprecated `isDate*InTimeZone` methods from `Moment` (will be removed in next minor version).

## 0.9.0

- **[Breaking change](./UPGRADE.md#dropped-support-for-symfony-63)**: Dropped support for Symfony 6.3.
Expand Down
18 changes: 7 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,33 +36,29 @@ Install package through composer:
composer require digital-craftsman/date-time-precision
```

> ⚠️ This bundle can be used (and is being used) in production, but hasn't reached version 1.0 yet. Therefore, there will be breaking changes between minor versions. I'd recommend that you require the bundle only with the current minor version like `composer require digital-craftsman/date-time-precision:0.9.*`. Breaking changes are described in the releases and [the changelog](./CHANGELOG.md). Updates are described in the [upgrade guide](./UPGRADE.md).
> ⚠️ This bundle can be used (and is being used) in production, but hasn't reached version 1.0 yet. Therefore, there will be breaking changes between minor versions. I'd recommend that you require the bundle only with the current minor version like `composer require digital-craftsman/date-time-precision:0.10.*`. Breaking changes are described in the releases and [the changelog](./CHANGELOG.md). Updates are described in the [upgrade guide](./UPGRADE.md).
## When would I need that?

Basically whenever you use a `DateTime` object for something other than a single moment.

Storing more information in those cases just lead to more questions, like "When storing the month, do we store the first of month at midnight?" and therefore increases complexity. Additionally, you need mutate or reduce the point in time to be able to compare it. With the package it will be as easy as:
Storing more information in those cases just lead to more questions, like "When storing the month, do we store the first day of month at midnight, and if so, in which time zone?" and therefore increases complexity. Additionally, you need mutate or reduce the point in time to be able to compare it. With the package it will be as easy as:

```php
if ($now
->timeInTimeZone($facilityTimeZone)
->isBefore($facility->openFrom)
) {
if ($now->isBeforeInTimeZone($facility->openFrom, $facilityTimeZone)) {
throw new FacilityIsNotOpenYet();
}
```
`$now` is a `Moment` (in UTC) and `$facility->openFrom` is a `Time` (in the timezone of the facility).

The idea is that your system and all variables can still remain in the timezone `UTC` and you only call mutations on it when needed for a comparison.
The idea is that your system can run in `UTC` and all moments are in the timezone `UTC`. But all values that have an implicit time zone like a date or a time of day will be stored with just the data needed. This way we're getting rid of additional data that creates more surface for possible bugs. Through precise value objects and specific comparison functions, the code is more readable than before.

```php
if ($now
->dateInTimeZone($facilityTimeZone)
->isBefore($facility->earliestDayOfBooking)
) {
if ($now->isBeforeInTimeZone($facility->earliestDayOfBooking)) {
throw new BookingNotPossibleYet();
}
```
`$now` is a `Moment` (in UTC) and `$facility->earliestDayOfBooking` is a `Date` (in the timezone of the facility). The same method `isBeforeInTimeZone` that is used previously for the time comparison is the same that is used here. Depending on the type of the second parameter, the comparison is done on the relevant part of the moment.

Modifications work the same way.

Expand Down
4 changes: 4 additions & 0 deletions UPGRADE.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Upgrade guide

## From 0.9.* to 0.10.0

No breaking changes (just deprecations).

## From 0.8.* to 0.9.0

### Dropped support for Symfony 6.3
Expand Down
7 changes: 6 additions & 1 deletion infection.json5
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@
]
},
"mutators": {
"@default": true
"@default": true,
"InstanceOf_": {
"ignore": [
"DigitalCraftsman\\DateTimePrecision\\Moment",
]
},
},
"minMsi": 100,
"minCoveredMsi": 100
Expand Down
157 changes: 154 additions & 3 deletions src/Moment.php
Original file line number Diff line number Diff line change
Expand Up @@ -97,119 +97,270 @@ public function isEqualTo(self $moment): bool
return $this->dateTime == $moment->dateTime;
}

public function isEqualToInTimeZone(
Time | Date | Month | Year $comparator,
\DateTimeZone $timeZone,
): bool {
return match (true) {
$comparator instanceof Time => $this->timeInTimeZone($timeZone)->isEqualTo($comparator),
$comparator instanceof Date => $this->dateInTimeZone($timeZone)->isEqualTo($comparator),
$comparator instanceof Month => $this->monthInTimeZone($timeZone)->isEqualTo($comparator),
$comparator instanceof Year => $this->yearInTimeZone($timeZone)->isEqualTo($comparator),
};
}

public function isNotEqualTo(self $moment): bool
{
return $this->dateTime != $moment->dateTime;
}

public function isNotEqualToInTimeZone(
Time | Date | Month | Year $comparator,
\DateTimeZone $timeZone,
): bool {
return match (true) {
$comparator instanceof Time => $this->timeInTimeZone($timeZone)->isNotEqualTo($comparator),
$comparator instanceof Date => $this->dateInTimeZone($timeZone)->isNotEqualTo($comparator),
$comparator instanceof Month => $this->monthInTimeZone($timeZone)->isNotEqualTo($comparator),
$comparator instanceof Year => $this->yearInTimeZone($timeZone)->isNotEqualTo($comparator),
};
}

public function isAfter(self $moment): bool
{
return $this->dateTime > $moment->dateTime;
}

public function isAfterInTimeZone(
Time | Date | Month | Year $comparator,
\DateTimeZone $timeZone,
): bool {
return match (true) {
$comparator instanceof Time => $this->timeInTimeZone($timeZone)->isAfter($comparator),
$comparator instanceof Date => $this->dateInTimeZone($timeZone)->isAfter($comparator),
$comparator instanceof Month => $this->monthInTimeZone($timeZone)->isAfter($comparator),
$comparator instanceof Year => $this->yearInTimeZone($timeZone)->isAfter($comparator),
};
}

public function isNotAfter(self $moment): bool
{
return !($this->dateTime > $moment->dateTime);
}

public function isNotAfterInTimeZone(
Time | Date | Month | Year $comparator,
\DateTimeZone $timeZone,
): bool {
return match (true) {
$comparator instanceof Time => $this->timeInTimeZone($timeZone)->isNotAfter($comparator),
$comparator instanceof Date => $this->dateInTimeZone($timeZone)->isNotAfter($comparator),
$comparator instanceof Month => $this->monthInTimeZone($timeZone)->isNotAfter($comparator),
$comparator instanceof Year => $this->yearInTimeZone($timeZone)->isNotAfter($comparator),
};
}

public function isAfterOrEqualTo(self $moment): bool
{
return $this->dateTime >= $moment->dateTime;
}

public function isAfterOrEqualToInTimeZone(
Time | Date | Month | Year $comparator,
\DateTimeZone $timeZone,
): bool {
return match (true) {
$comparator instanceof Time => $this->timeInTimeZone($timeZone)->isAfterOrEqualTo($comparator),
$comparator instanceof Date => $this->dateInTimeZone($timeZone)->isAfterOrEqualTo($comparator),
$comparator instanceof Month => $this->monthInTimeZone($timeZone)->isAfterOrEqualTo($comparator),
$comparator instanceof Year => $this->yearInTimeZone($timeZone)->isAfterOrEqualTo($comparator),
};
}

public function isNotAfterOrEqualTo(self $moment): bool
{
return !($this->dateTime >= $moment->dateTime);
}

public function isNotAfterOrEqualToInTimeZone(
Time | Date | Month | Year $comparator,
\DateTimeZone $timeZone,
): bool {
return match (true) {
$comparator instanceof Time => $this->timeInTimeZone($timeZone)->isNotAfterOrEqualTo($comparator),
$comparator instanceof Date => $this->dateInTimeZone($timeZone)->isNotAfterOrEqualTo($comparator),
$comparator instanceof Month => $this->monthInTimeZone($timeZone)->isNotAfterOrEqualTo($comparator),
$comparator instanceof Year => $this->yearInTimeZone($timeZone)->isNotAfterOrEqualTo($comparator),
};
}

public function isBeforeOrEqualTo(self $moment): bool
{
return $this->dateTime <= $moment->dateTime;
}

public function isBeforeOrEqualToInTimeZone(
Time | Date | Month | Year $comparator,
\DateTimeZone $timeZone,
): bool {
return match (true) {
$comparator instanceof Time => $this->timeInTimeZone($timeZone)->isBeforeOrEqualTo($comparator),
$comparator instanceof Date => $this->dateInTimeZone($timeZone)->isBeforeOrEqualTo($comparator),
$comparator instanceof Month => $this->monthInTimeZone($timeZone)->isBeforeOrEqualTo($comparator),
$comparator instanceof Year => $this->yearInTimeZone($timeZone)->isBeforeOrEqualTo($comparator),
};
}

public function isNotBeforeOrEqualTo(self $moment): bool
{
return !($this->dateTime <= $moment->dateTime);
}

public function isBefore(self $moment): bool
{
return $this->dateTime < $moment->dateTime;
public function isNotBeforeOrEqualToInTimeZone(
Time | Date | Month | Year $comparator,
\DateTimeZone $timeZone,
): bool {
return match (true) {
$comparator instanceof Time => $this->timeInTimeZone($timeZone)->isNotBeforeOrEqualTo($comparator),
$comparator instanceof Date => $this->dateInTimeZone($timeZone)->isNotBeforeOrEqualTo($comparator),
$comparator instanceof Month => $this->monthInTimeZone($timeZone)->isNotBeforeOrEqualTo($comparator),
$comparator instanceof Year => $this->yearInTimeZone($timeZone)->isNotBeforeOrEqualTo($comparator),
};
}

public function isBefore(
self $before,
): bool {
return $this->dateTime < $before->dateTime;
}

public function isBeforeInTimeZone(
Time | Date | Month | Year $comparator,
\DateTimeZone $timeZone,
): bool {
return match (true) {
$comparator instanceof Time => $this->timeInTimeZone($timeZone)->isBefore($comparator),
$comparator instanceof Date => $this->dateInTimeZone($timeZone)->isBefore($comparator),
$comparator instanceof Month => $this->monthInTimeZone($timeZone)->isBefore($comparator),
$comparator instanceof Year => $this->yearInTimeZone($timeZone)->isBefore($comparator),
};
}

public function isNotBefore(self $moment): bool
{
return !($this->dateTime < $moment->dateTime);
}

public function isNotBeforeInTimeZone(
Time | Date | Month | Year $comparator,
\DateTimeZone $timeZone,
): bool {
return match (true) {
$comparator instanceof Time => $this->timeInTimeZone($timeZone)->isNotBefore($comparator),
$comparator instanceof Date => $this->dateInTimeZone($timeZone)->isNotBefore($comparator),
$comparator instanceof Month => $this->monthInTimeZone($timeZone)->isNotBefore($comparator),
$comparator instanceof Year => $this->yearInTimeZone($timeZone)->isNotBefore($comparator),
};
}

public function compareTo(self $moment): int
{
return $this->dateTime <=> $moment->dateTime;
}

/**
* @deprecated Will be removed in one of the next minor releases. Use @see Date->isAfterInTimeZone instead.
*/
public function isDateAfterInTimeZone(self $moment, \DateTimeZone $timeZone): bool
{
return $this->dateInTimeZone($timeZone)->isAfter(
$moment->dateInTimeZone($timeZone),
);
}

/**
* @deprecated Will be removed in one of the next minor releases. Use @see Date->isNotAfterInTimeZone instead.
*/
public function isDateNotAfterInTimeZone(self $moment, \DateTimeZone $timeZone): bool
{
return $this->dateInTimeZone($timeZone)->isNotAfter(
$moment->dateInTimeZone($timeZone),
);
}

/**
* @deprecated Will be removed in one of the next minor releases. Use @see Date->isAfterOrEqualToInTimeZone instead.
*/
public function isDateAfterOrEqualToInTimeZone(self $moment, \DateTimeZone $timeZone): bool
{
return $this->dateInTimeZone($timeZone)->isAfterOrEqualTo(
$moment->dateInTimeZone($timeZone),
);
}

/**
* @deprecated Will be removed in one of the next minor releases. Use @see Date->isNotAfterOrEqualToInTimeZone instead.
*/
public function isDateNotAfterOrEqualToInTimeZone(self $moment, \DateTimeZone $timeZone): bool
{
return $this->dateInTimeZone($timeZone)->isNotAfterOrEqualTo(
$moment->dateInTimeZone($timeZone),
);
}

/**
* @deprecated Will be removed in one of the next minor releases. Use @see Date->isEqualToInTimeZone instead.
*/
public function isDateEqualToInTimeZone(self $moment, \DateTimeZone $timeZone): bool
{
return $this->dateInTimeZone($timeZone)->isEqualTo(
$moment->dateInTimeZone($timeZone),
);
}

/**
* @deprecated Will be removed in one of the next minor releases. Use @see Date->isNotEqualToInTimeZone instead.
*/
public function isDateNotEqualToInTimeZone(self $moment, \DateTimeZone $timeZone): bool
{
return $this->dateInTimeZone($timeZone)->isNotEqualTo(
$moment->dateInTimeZone($timeZone),
);
}

/**
* @deprecated Will be removed in one of the next minor releases. Use @see Date->isBeforeInTimeZone instead.
*/
public function isDateBeforeInTimeZone(self $moment, \DateTimeZone $timeZone): bool
{
return $this->dateInTimeZone($timeZone)->isBefore(
$moment->dateInTimeZone($timeZone),
);
}

/**
* @deprecated Will be removed in one of the next minor releases. Use @see Date->isNotBeforeInTimeZone instead.
*/
public function isDateNotBeforeInTimeZone(self $moment, \DateTimeZone $timeZone): bool
{
return $this->dateInTimeZone($timeZone)->isNotBefore(
$moment->dateInTimeZone($timeZone),
);
}

/**
* @deprecated Will be removed in one of the next minor releases. Use @see Date->isBeforeOrEqualToInTimeZone instead.
*/
public function isDateBeforeOrEqualToInTimeZone(self $moment, \DateTimeZone $timeZone): bool
{
return $this->dateInTimeZone($timeZone)->isBeforeOrEqualTo(
$moment->dateInTimeZone($timeZone),
);
}

/**
* @deprecated Will be removed in one of the next minor releases. Use @see Date->isNotBeforeOrEqualToInTimeZone instead.
*/
public function isDateNotBeforeOrEqualToInTimeZone(self $moment, \DateTimeZone $timeZone): bool
{
return $this->dateInTimeZone($timeZone)->isNotBeforeOrEqualTo(
Expand Down
Loading

0 comments on commit 653a356

Please sign in to comment.