Skip to content

Commit

Permalink
Changes
Browse files Browse the repository at this point in the history
  • Loading branch information
Christian Kolb committed May 6, 2024
1 parent 6cc09ab commit a28e56c
Show file tree
Hide file tree
Showing 4 changed files with 513 additions and 0 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ To solve this issue, this package introduces specific classes to work with more
- `CalendarMonth` (`JANUARY` to `DECEMBER` like `MAY`)
- `Year` (like `2024`)

TODO: OVERALL: Split into parts that are valuable right now and which can come later. Dependent on a current project.

TODO: Add `Weekday` as `MONDAY` to `SUNDAY`, `CalendarWeek` as 1 to 53 and `CalendarMonth` as `JANUARY` to `DECEMBER`.
TODO: Remove milliseconds from `Time`.
TODO: Manipulation / Calculation of one component into another one is simpler
Expand All @@ -25,6 +27,7 @@ TODO: Examples for use cases
- Is after from moment to date
- Move event from one day of the week to another (independent of whether it's forward or backwards)
TODO: Classes for `Hour`, `Minute`, `Second`, `Millisecond`?
TODO: TimeFrames? For all elements.

The only class in the PHP SPL to work with dates and times is `DateTime` (and it's immutable counterpart). It represents a specific moment in time in a specific timezone (whether that timezone/offset is explicitly defined or not). Unfortunately dates are complex and there is more than just a moment in time. For example there are

Expand Down
81 changes: 81 additions & 0 deletions src/CalendarMonth.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?php

declare(strict_types=1);

namespace DigitalCraftsman\DateTimePrecision;

enum CalendarMonth: string
{
case JANUARY = 'JANUARY';
case FEBRUARY = 'FEBRUARY';
case MARCH = 'MARCH';
case APRIL = 'APRIL';
case MAY = 'MAY';
case JUNE = 'JUNE';
case JULY = 'JULY';
case AUGUST = 'AUGUST';
case SEPTEMBER = 'SEPTEMBER';
case OCTOBER = 'OCTOBER';
case NOVEMBER = 'NOVEMBER';
case DECEMBER = 'DECEMBER';

// -- Construction

public static function fromDateTime(\DateTimeImmutable $dateTime): self
{
return self::fromMonthNumber((int) $dateTime->format('n'));
}

public static function fromDate(Date $date): self
{
return self::fromMonthNumber((int) $date->format('n'));
}

public static function fromMonthNumber(int $dayOfIsoWeek): self
{
return match ($dayOfIsoWeek) {
1 => self::JANUARY,
2 => self::FEBRUARY,
3 => self::MARCH,
4 => self::APRIL,
5 => self::MAY,
6 => self::JUNE,
7 => self::JULY,
8 => self::AUGUST,
9 => self::SEPTEMBER,
10 => self::OCTOBER,
11 => self::NOVEMBER,
12 => self::DECEMBER,
};
}

// -- Accessors

public function numberOfMonth(): int
{
return match ($this) {
self::JANUARY => 1,
self::FEBRUARY => 2,
self::MARCH => 3,
self::APRIL => 4,
self::MAY => 5,
self::JUNE => 6,
self::JULY => 7,
self::AUGUST => 8,
self::SEPTEMBER => 9,
self::OCTOBER => 10,
self::NOVEMBER => 11,
self::DECEMBER => 12,
};
}

public function isEqualTo(self $calendarMonth): bool
{
return $this === $calendarMonth;
}

public function isNotEqualTo(self $calendarMonth): bool
{
return $this !== $calendarMonth;
}
}
199 changes: 199 additions & 0 deletions src/CalendarWeek.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
<?php

declare(strict_types=1);

namespace DigitalCraftsman\DateTimePrecision;

final readonly class CalendarWeek
{
// -- Construction

public function __construct(
public int $week,
) {
if ($week < 1
|| $week > 53
) {
// TODO: Better exception
throw new \InvalidArgumentException(sprintf('Value "%d" is not a valid week.', $week));
}
}

public static function fromDateTime(\DateTimeImmutable $dateTime): self
{
return new self(
(int) $dateTime->format('W'),
);
}

// -- Accessors

public function isEqualTo(self $month): bool
{
return $this->toDateTimeImmutable() == $month->toDateTimeImmutable();
}

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

public function isBefore(self $month): bool
{
return $this->toDateTimeImmutable() < $month->toDateTimeImmutable();
}

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

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

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

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

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

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

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

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

/**
* Returns all months until the given month. If the given month is before this month, the result will be an empty array.
*
* @return array<int, Month>
*/
public function monthsUntil(
self $month,
PeriodLimit $periodLimit = PeriodLimit::INCLUDING_START_AND_END,
): array {
$startDateTime = $periodLimit === PeriodLimit::INCLUDING_START_AND_END
|| $periodLimit === PeriodLimit::INCLUDING_START
? $this
->modify('- 1 month')
->toDateTimeImmutable()
: $this->toDateTimeImmutable();

$endDateTime = $periodLimit === PeriodLimit::INCLUDING_START_AND_END
|| $periodLimit === PeriodLimit::INCLUDING_END
? $month
->modify('+ 1 month')
->toDateTimeImmutable()
: $month->toDateTimeImmutable();

$interval = new \DateInterval('P1M');
/**
* The options here seem counter-intuitive, but are set in a way that this logic is only handled in one place (above) instead of
* two place with part of it above and part below.
*/
$period = new \DatePeriod($startDateTime, $interval, $endDateTime, \DatePeriod::EXCLUDE_START_DATE);

$months = [];
foreach ($period as $dateTime) {
$months[] = self::fromDateTime($dateTime);
}

return $months;
}

// -- Mutations

public function firstDay(): Date
{
$firstDayOfMonth = new \DateTimeImmutable(sprintf(
'first day of %d-%d',
$this->year->year,
$this->week,
));

return Date::fromDateTime($firstDayOfMonth);
}

public function lastDay(): Date
{
$lastDayOfMonth = new \DateTimeImmutable(sprintf(
'last day of %d-%d',
$this->year->year,
$this->week,
));

return Date::fromDateTime($lastDayOfMonth);
}

public function format(string $format): string
{
return $this
->toDateTimeImmutable()
->format($format);
}

public function modify(string $modifier): self
{
$modifiedDateTime = $this->toDateTimeImmutable()
->modify($modifier);

/** @psalm-suppress PossiblyFalseArgument */
return self::fromDateTime($modifiedDateTime);
}

public function toMomentInTimeZone(\DateTimeZone $timeZone): Moment
{
return Moment::fromStringInTimeZone(
sprintf(
'%d-%d-01 00:00:00',
$this->year->year,
$this->week,
),
$timeZone,
);
}

public function modifyInTimeZone(string $modify, \DateTimeZone $timeZone): self
{
$dateTimeImmutable = new \DateTimeImmutable(
sprintf(
'%d-%d-01 00:00:00',
$this->year->year,
$this->week,
),
$timeZone,
);

/** @psalm-suppress PossiblyFalseArgument */
return self::fromDateTime($dateTimeImmutable->modify($modify));
}

private function toDateTimeImmutable(): \DateTimeImmutable
{
/** @psalm-suppress FalsableReturnStatement */
return \DateTimeImmutable::createFromFormat(
'W',
(string) $this->week,
);
}
}
Loading

0 comments on commit a28e56c

Please sign in to comment.