diff --git a/.devcontainer.json b/.devcontainer.json
new file mode 100644
index 0000000..c1b51b7
--- /dev/null
+++ b/.devcontainer.json
@@ -0,0 +1,16 @@
+// For format details, see https://aka.ms/devcontainer.json. For config options, see the
+// README at: https://github.com/devcontainers/templates/tree/main/src/php
+{
+ "name": "PHP",
+ "image": "webdevops/php-dev:8.2",
+ "customizations": {
+ "vscode": {
+ "extensions": [
+ "bmewburn.vscode-intelephense-client"
+ ]
+ }
+ },
+ "containerEnv": {
+ "XDEBUG_MODE": "coverage"
+ }
+}
\ No newline at end of file
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
new file mode 100644
index 0000000..8aa8647
--- /dev/null
+++ b/.github/workflows/lint.yml
@@ -0,0 +1,27 @@
+name: Lint
+
+on:
+ push:
+ branches: [ master ]
+ pull_request:
+ branches: [ master ]
+
+jobs:
+ lint:
+ name: Lint
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: ☁️ checkout repository
+ uses: actions/checkout@v2
+
+ - name: 🐘 setup PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: 8.2
+
+ - name: 📦 install PHP dependencies
+ uses: ramsey/composer-install@v2
+
+ - name: Run linter
+ run: vendor/bin/pint --test
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 60dc061..574f37b 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -5,33 +5,30 @@ on:
branches: [ master ]
pull_request:
branches: [ master ]
- schedule:
- - cron: "0 12 1 * *"
jobs:
- build:
-
+ test:
name: CI on PHP ${{matrix.php_versions}}
runs-on: ubuntu-latest
strategy:
matrix:
- php_versions: ['7.2', '7.3', '7.4', '8.0']
+ php_versions: ['8.1', '8.2']
steps:
- - uses: actions/checkout@v2
+ - name: '☁️ checkout repository'
+ uses: actions/checkout@v2
- - name: Install PHP
+ - name: 🐘 setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php_versions }}
coverage: xdebug
-
- - name: Validate composer.json and composer.lock
+ - name: ✅ validate composer.json and composer.lock
run: composer validate
- - name: Install dependencies
- run: composer install --prefer-dist --no-progress --no-suggest
+ - name: 📦 install PHP dependencies
+ uses: ramsey/composer-install@v2
- - name: Run PHPUNIT
+ - name: 👩💻 run phpunit
run: vendor/bin/phpunit --coverage-text
diff --git a/README.md b/README.md
index 49232c5..6f1fc6d 100644
--- a/README.md
+++ b/README.md
@@ -1,134 +1,89 @@
-# AIRAC Calculator
+# AIRAC Calculator for PHP
[](https://github.com/atoff/airac-calculator/actions/workflows/test.yml)
-## About
-#### Description
-airac-calculator is a lightweight PHP library that provides information on AIRAC cycle effective dates. AIRAC stands for Aeronautical Information Regulation And Control, and the cycles associated with them are used to almost version aviation data. A cycle is valid for 28 days, and so there are usually 13 cycles per year.
+`airac-calculator` is a zero dependency PHP library for parsing and computing AIRAC (Aeronautical Information Regulation And Control) cycles.
-Typically, AIRAC cycles are used to update various aviation procedures, including aeronautical charts, flight procedures (such as SIDs and STARS) and other operational data.
+AIRAC cycles are used in the aviation industry to standardise significant revisions to operational information, such as aeronautical charts, frequencies, procedures and more. Each cycle lasts of 28 days, with 13 cycles per year (or exceptionally 14 in some cases). Cycles have two key, unique, properties; the date it becomes effective and a 4 digit cycle code.
-The cycle code is created using the two number representation of the year (i.e. 2019 = 19) along with the cycle number (i.e. 01 if the first cycle). Thus, a typical cycle code may look like 1901.
+This package has been validated against the EUROCONTROL AIRAC dates database.
-#### Requirements
-* PHP 7.2 and above
+## Requirements
+* PHP 8.1 and above (tested up to PHP 8.2)
-#### Author
-This package was created by [Alex Toff](https://alextoff.uk)
-
-#### License
+## License
airac-calculator is licensed under the MIT license, which can be found in the root of the package in the `LICENSE` file.
## Installation
-The easiest way to install is through the use of composer:
+The easiest way to install is via composer:
```
$ composer require cobaltgrid/airac-calculator
```
## Usage
If you are using Composer:
-```
+```php
format('d/m/Y');
-// ... 03/01/2019
-```
+// Getting the AIRAC cycle from a cycle code
+AIRACCycle::fromCycleCode('2308');
-### nextAiracCycle(Carbon $date = null)
-Computes and returns the upcoming AIRAC cycle
-* Arguments:
- * (opt) `$date` `Carbon\Carbon` A Carbon date instance. If supplied, the resulting cycle will be the next effective cycle from this date, rather than the current date
-* Returns:
- * `string` AIRAC Cycle Code
-```
-// Today is 30/06/2019
-echo AIRACCalculator::nextAiracCycle();
-// ... 1908
-```
+// Getting from a serial (a serial is the number of cycles since the AIRAC epoch)
+AIRACCycle::fromSerial(1619);
-### nextAiracDate(Carbon $date = null)
-Computes and returns the effective date of the upcoming AIRAC cycle
-* Arguments:
- * (opt) `$date` `Carbon\Carbon` A Carbon date instance. If supplied, the resulting date will be the next effective cycle date from this date, rather than from the current date
-* Returns:
- * `Carbon\Carbon` Next effective cycle date
-```
-// Today is 30/06/2019
-echo AIRACCalculator::nextAiracDate()->format('d/m/Y');
-// ... 18/07/2019
-```
+// Getting the current effective AIRAC
+AIRACCycle::current();
-### currentAiracCycle(Carbon $date = null)
-Computes and returns the current AIRAC cycle code
-* Arguments:
- * (opt) `$date` `Carbon\Carbon` A Carbon date instance. If supplied, the resulting cycle will be the effective cycle at this date.
-* Returns:
- * `string` AIRAC Cycle Code
-```
-// Today is 30/06/2019
-echo AIRACCalculator::currentAiracCycle();
-// ... 1907
-```
+// Getting the next AIRAC to become effective
+AIRACCycle::next();
-### isNewAiracDay(Carbon $date = null)
-Returns whether an AIRAC effective date lies on this day
-* Arguments:
- * (opt) `$date` `Carbon\Carbon` A Carbon date instance. If supplied, instead of using the current day, it will check if the date supplied lies on an update day.
-* Returns:
- * `boolean`
+// Getting the previous AIRAC
+AIRACCycle::previous();
```
-// Today is 30/06/2019
-echo AIRACCalculator::isNewAiracDay();
-// ... 0
-// Today is 20/06/2019
-echo AIRACCalculator::isNewAiracDay();
-// ... 1
-```
+### Getting cycle information
+```php
+$cycle = AIRACCycle::current();
+
+// Get the 4-digit AIRAC cycle code (string)
+$cycle->getCycleCode(); // 2308
+
+// Gets the number of the cycle in it's year, starting at 1 (i.e. the first cycle is ordinal 1, second is 2, etc.) (int)
+$cycle->getOrdinal(); // 1
-### cyclesForYear($year = null)
-Computes and returns a 2D array of the cycles for a year
-* Arguments:
- * (opt) `$year` `string|int` A 4 digit year, either in int or string format. If not supplied, will use current year.
-* Returns:
- * `array` An array of cycles. Each "cycle" item is an array following a `[$cycleCode, $effectiveDate]` format. The `$effectiveDate` is given as a `Carbon\Carbon` object.
+// Returns the date the cycle became/becomes effective (DateTime)
+$cycle->getEffectiveDate(); // DateTime Instance
+
+// Returns the serial (number of cycles since AIRAC epoch) (int)
+$cycle->getSerial(); // 1619
+
+// Returns the next cycle from this one (AIRACCycle)
+$cycle->nextCycle(); // AIRACCycle Instance
+
+// Returns the previous cycle from this one (AIRACCycle)
+$cycle->previousCycle(); // AIRACCycle Instance
```
-foreach (AIRACCalculator::cyclesForYear() as $cycle){
- echo $cycle[0] . ' at ' . $cycle[1]->format('d/m/Y');
-}
-
-// 1901 at 03/01/2019
-// 1902 at 31/01/2019
-// 1903 at 28/02/2019
-...
+
+## Testing
+The code base is tested with PHP unit:
```
+$ ./vendor/bin/phpunit
+```
\ No newline at end of file
diff --git a/composer.json b/composer.json
index ca8e107..cd635c8 100644
--- a/composer.json
+++ b/composer.json
@@ -3,25 +3,28 @@
"description": "PHP library for AIRAC date information",
"type": "library",
"license": "MIT",
- "authors" : [
- {
- "name" : "Alex Toff",
- "email" : "alex@cobaltgrid.com",
- "homepage" : "https://www.cobaltgrid.com"
- }
+ "authors": [
+ {
+ "name": "Alex Toff",
+ "homepage": "https://cobaltgrid.com"
+ }
],
"require": {
- "php": "^7.2|^8.0",
- "nesbot/carbon": "^2.20"
+ "php": "^7.2|^8.0"
},
"autoload": {
"psr-4": {
- "CobaltGrid\\Aviation\\": "src/"
+ "CobaltGrid\\AIRACCalculator\\": "src/"
}
},
"require-dev": {
"phpunit/phpunit": "^8.2|^9.0",
"symfony/dom-crawler": "^4.3",
- "symfony/css-selector": "^4.3"
+ "symfony/css-selector": "^4.3",
+ "larapack/dd": "1.*",
+ "laravel/pint": "^1.10"
+ },
+ "suggest": {
+ "laravel/pint": "Required to lint the project"
}
}
diff --git a/examples/airacfordate.php b/examples/airacfordate.php
deleted file mode 100644
index 2544f00..0000000
--- a/examples/airacfordate.php
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
-
-
-format('d M Y H:i') . "";
-echo "The current AIRAC is " . AIRACCalculator::currentAiracCycle($date) . ", which became effective at " . AIRACCalculator::dateForCycle(AIRACCalculator::currentAiracCycle($date))->format('d/m/Y') . '';
-echo "The next effective AIRAC is " . AIRACCalculator::nextAiracCycle($date) . ", which becomes effective at " . AIRACCalculator::nextAiracDate($date)->format('d/m/Y');
\ No newline at end of file
diff --git a/examples/airacyear.php b/examples/airacyear.php
deleted file mode 100644
index 09d0be0..0000000
--- a/examples/airacyear.php
+++ /dev/null
@@ -1,43 +0,0 @@
-
-
-
-
-format('Y'));
-
-?>
-
-
-
- Cycle |
- Effective Date |
-
-
-
-
-
- = $cycle[0] ?> |
- = $cycle[1] ?> |
-
-
-
-
diff --git a/examples/currentairac.php b/examples/currentairac.php
deleted file mode 100644
index 20e8e60..0000000
--- a/examples/currentairac.php
+++ /dev/null
@@ -1,6 +0,0 @@
-format('d/m/Y');
\ No newline at end of file
diff --git a/phpunit b/phpunit
deleted file mode 100644
index 35e3df5..0000000
--- a/phpunit
+++ /dev/null
@@ -1 +0,0 @@
-./vendor/bin/phpunit
\ No newline at end of file
diff --git a/src/AIRACCalculator.php b/src/AIRACCalculator.php
deleted file mode 100644
index 53a5b94..0000000
--- a/src/AIRACCalculator.php
+++ /dev/null
@@ -1,314 +0,0 @@
-= $possibleCycle) {
- $numCycleDiff++;
- }
- }
-
- return self::datumDate()->addDays($numCycleDiff * 28);
- }
-
- /**
- * Computes and returns the upcoming AIRAC cycle
- *
- * @param Carbon|null $date Optional date to compute from. If supplied, the resulting cycle will be the next effective cycle from this date.
- * @return string The AIRAC cycle code
- * @throws \Exception
- */
- public static function nextAiracCycle(Carbon $date = null)
- {
- if (!$date) {
- $date = Carbon::now();
- }
-
- $twoDigitYear = $date->format('y');
- $month = $date->format('m');
-
- $attempts = 0;
-
- while (true) {
- $attempts++;
-
-
- $guess = $twoDigitYear . $month;
- $guessDate = self::dateForCycle($guess);
-
- if ($attempts > 10) {
- throw new \Exception('Unable to find next AIRAC!');
- }
-
- if ($guessDate->greaterThan($date) && $guessDate->diffInDays($date) <= 28) {
- // The guessed cycle is in the future and within 28 days. Yes!
- return $guess;
- }
- // The guessed cycle is before the current date
- if ($month < 13) {
- $month = str_pad($month + 1, 2, '0', STR_PAD_LEFT);
- continue;
- }
-
- if ($month == 13) {
- // Check to see if is "exceptional year"
- $years = self::extraordinaryCycles();
- if (in_array("20" . $twoDigitYear, $years)) {
- $month = str_pad($month + 1, 2, '0', STR_PAD_LEFT);
- continue;
- }
- }
-
- // Else, roll over year
- $twoDigitYear = $date->copy()->addYear()->format('y');
- $month = "01";
- continue;
- }
- }
-
- /**
- * Computes and returns the effective date of the upcoming AIRAC cycle
- *
- * @param Carbon|null $date Optional date to compute from. If supplied, the resulting date will be the next effective AIRAC date from this date.
- * @return Carbon
- */
- public static function nextAiracDate(Carbon $date = null)
- {
- if (!$date) {
- $date = Carbon::now();
- }
-
- return self::dateForCycle(self::nextAiracCycle($date));
- }
-
- /**
- * Computes and returns the current AIRAC cycle code
- *
- * @param Carbon|null $date Optional date to compute from. If supplied, the resulting cycle will be the effective cycle at this date.
- * @return string
- * @throws \Exception
- */
- public static function currentAiracCycle(Carbon $date = null)
- {
- if (!$date) {
- $date = Carbon::now();
- }
- $twoDigitYear = $date->format('y');
- $month = $date->format('m');
-
- $attempts = 0;
-
- while (true) {
- $attempts++;
-
- $guess = $twoDigitYear . $month;
- $guessDate = self::dateForCycle($guess);
- if ($attempts > 10) {
- throw new \Exception('Unable to find current AIRAC!');
- }
-
- if ($guessDate == $date) {
- return $guess;
- }
-
- if ($guessDate->greaterThan($date)) {
- if ($month > 1) {
- $month = str_pad($month - 1, 2, '0', STR_PAD_LEFT);
- continue;
- }
- // Else, roll over the year
- $twoDigitYear = (clone $date)->subYear()->format('y');
-
- // Check to see if is "exceptional year"
- $years = self::extraordinaryCycles();
- if (in_array("20" . $twoDigitYear, $years)) {
- $month = "14";
- continue;
- }
-
- $month = "13";
- continue;
- }
-
- if ($guessDate->lessThan($date) && $guessDate->diffInDays($date) < 28) {
- return $guess;
- } else {
- // Try increasing by month
- if ($month < 13) {
- $month = str_pad($month + 1, 2, '0', STR_PAD_LEFT);
- continue;
- }
-
- if ($month == 13) {
- // Check to see if is "exceptional year"
- $years = self::extraordinaryCycles();
- if (in_array("20" . $twoDigitYear, $years)) {
- $month = str_pad($month + 1, 2, '0', STR_PAD_LEFT);
- continue;
- }
- }
-
- // Else, roll over year
- $twoDigitYear = (clone $date)->addYear()->format('y');
- $month = "01";
- continue;
- }
- }
- }
-
- /**
- * Returns whether an AIRAC effective date lies on this day
- *
- * @param Carbon|null $date Optional date to compute from. If supplied, instead of using the current day, it will check if the date supplied lies on an update day.
- * @return bool
- */
- public static function isNewAiracDay(Carbon $date = null)
- {
- if (!$date) {
- $date = Carbon::now();
- }
-
- return $date->diffInDays(self::dateForCycle(self::currentAiracCycle($date))) < 1 ? true : false;
- }
-
- /**
- * Computes and returns a 2D array of the cycles in the year. Items follow a schema of [$cycleCode, $effectiveDate]
- *
- * @param string|int|null $year Optional 4-digit year. If supplied, will return cycles for the given year instead of current year.
- * @return array
- */
- public static function cyclesForYear($year = null)
- {
- if (!$year) {
- $year = Carbon::now()->format('y');
- } else {
- $year = (new Carbon($year . '-01-01'))->format('y');
- }
-
- // Check if we have an exceptional cycle
- $extra = self::extraordinaryCycles(new Carbon((int)$year + 1), true);
-
- $numCycles = 13;
- if (in_array($year . '14', $extra)) {
- $numCycles++;
- }
-
- $cycles = [];
-
- for ($i = 1; $i <= $numCycles; $i++) {
- $cycleId = $year . str_pad($i, 2, '0', STR_PAD_LEFT);
- $cycles[] = [$cycleId, self::dateForCycle($cycleId)];
- }
-
- return $cycles;
- }
-
- /*
- Support functions
- */
-
- /**
- * Turns two digit year into four digit year
- *
- * @param $cycle Two digit year
- * @return string
- */
- private static function cycleYear($cycle)
- {
- return "20" . substr($cycle, 0, 2);
- }
-
- /**
- * Computes list of years with 14 cycles instead of 13
- *
- * @param Carbon|null $upToDate Optional date. If supplied, will compute list of years up to this date. If null, 5 'exceptional' years from the current year will be found.
- * @param bool $asCycleNumbers Determines if the returned values should be years (e.g. 2020) or cycles (e.g. 2014)
- * @return array
- */
- private static function extraordinaryCycles(Carbon $upToDate = null, $asCycleNumbers = false)
- {
- // Frequency of 14-cycle years, in years
- $occurrence = "29";
-
- if ($upToDate) {
- $yearsFromDatum = $upToDate->diffInYears(self::datumDate());
- $nubOccurrences = $yearsFromDatum / $occurrence;
- } else {
- // Take 5 cycles from datum
- $nubOccurrences = 5;
- }
-
- $list = [];
-
- for ($i = 0; $i < $nubOccurrences; $i++) {
- if ($asCycleNumbers) {
- $list[] = self::datumDate()->addYears($i * $occurrence)->format('y') . "14";
- } else {
- $list[] = self::datumDate()->addYears($i * $occurrence)->format('Y');
- }
- }
-
- return $list;
- }
-
- /**
- * Computes how many extra cycles lie between datum and the requested cycle
- *
- * @param $requestedCycle The 4 digit cycle code to work up to
- * @return int
- */
- private static function numberOfExtraordinaryCycles($requestedCycle)
- {
- $possibleCycles = self::extraordinaryCycles();
-
- $count = 0;
- foreach ($possibleCycles as $cycle) {
- if ($cycle < $requestedCycle) {
- $count++;
- }
- }
- return $count;
- }
-
- /**
- * Gets the datum date as a Carbon object
- *
- * @return Carbon
- */
- private static function datumDate()
- {
- return new Carbon(self::$datumDate);
- }
-}
diff --git a/src/AIRACCycle.php b/src/AIRACCycle.php
new file mode 100644
index 0000000..0f999b1
--- /dev/null
+++ b/src/AIRACCycle.php
@@ -0,0 +1,174 @@
+format(DateTime::ATOM)} is not a valid effective date");
+ }
+
+ return static::fromSerial($serial);
+ }
+
+ /**
+ * Returns the AIRAC cycle identified by the given cycle code.
+ *
+ * Cycle codes take the format YYOO. For example 2301 identifies the first cycle of 2023.
+ * Identifiers starting 64 to 99 are cycles between years 1964 and 1999 inclusive
+ * Identifiers starting 00 to 63 are cycles between years 2000 and 2063 inclsuive
+ *
+ * @throws InvalidAIRACCycleCodeException
+ */
+ public static function fromCycleCode(string $cycleCode): static
+ {
+ preg_match("/^(\d\d)(\d\d)$/", $cycleCode, $matches);
+
+ if (count($matches) !== 3) {
+ throw new InvalidAIRACCycleCodeException("{$cycleCode} was not of format YYOO");
+ }
+
+ $yearPart = (int) $matches[1];
+ $ordinalPart = (int) $matches[2];
+
+ $year = $yearPart > 63 ? 1900 + $yearPart : 2000 + $yearPart;
+ $previousYear = $year - 1;
+
+ $previousYearFinalAirac = static::forDate(new DateTime("{$previousYear}-12-31", new DateTimeZone('UTC')));
+ $airac = static::fromSerial($previousYearFinalAirac->getSerial() + $ordinalPart);
+
+ if ((int) $airac->getEffectiveDate()->format('Y') !== $year) {
+ throw new InvalidAIRACCycleCodeException("Year {$year} does not have {$ordinalPart} cycles");
+ }
+
+ return $airac;
+ }
+
+ /** Creates an AIRAC cycle from an AIRAC serial */
+ public static function fromSerial(int $serial): static
+ {
+ return new AIRACCycle($serial);
+ }
+
+ /** Returns the current AIRAC cycle */
+ public static function current(): static
+ {
+ return static::forDate(new DateTime());
+ }
+
+ /** Returns the next AIRAC cycle */
+ public static function next(): static
+ {
+ return static::fromSerial(static::current()->getSerial() + 1);
+ }
+
+ /** Returns the previous AIRAC cycle */
+ public static function previous(): static
+ {
+ return static::fromSerial(static::current()->getSerial() - 1);
+ }
+
+ /** Returns the serial of the AIRAC cycle at the given date. Whilt a float is always returned, note that this is an integer if exactly at an effective date, or a float otherwise*/
+ public static function getSerialForDate(DateTime $dateTime): float
+ {
+ $diffSeconds = $dateTime->getTimestamp() - static::getEpochDate()->getTimestamp();
+
+ return $diffSeconds / (static::$cycleDurationDays * 24 * 60 * 60);
+ }
+
+ /**
+ * Create an AIRAC Cycle instance
+ */
+ public function __construct(protected int $serial)
+ {
+ if ($serial < 0) {
+ throw new InvalidAIRACSerialException("{$serial} is not a valid AIRAC serial");
+ }
+ }
+
+ /**
+ * Returns the AIRAC Cycle's identifier code
+ *
+ * Identifiers starting 64 to 99 are cycles between years 1964 and 1999 inclusive
+ * Identifiers starting 00 to 63 are cycles between years 2000 and 2063 inclsuive
+ *
+ * @return string Cycle identifier code, of format YYMM (e.g. 2308 is the 8th cycle for the year 2023)
+ */
+ public function getCycleCode(): string
+ {
+ $year = $this->getEffectiveDate()->format('Y');
+ $yearPart = (string) ($year < 2000 ? 64 + (int) ($this->getEffectiveDate()->format('y') - 1) : $this->getEffectiveDate()->format('y'));
+ $ordinalPart = str_pad($this->getOrdinal(), 2, '0', STR_PAD_LEFT);
+
+ return $yearPart.$ordinalPart;
+ }
+
+ /** Returns the ordinal (cycle number of the year) for this cycle. Between 1 and 14 */
+ public function getOrdinal(): int
+ {
+ return floor(($this->getEffectiveDate()->format('z') - 1) / 28) + 1;
+ }
+
+ /**
+ * Returns the date the AIRAC Cycle becomes effective
+ */
+ public function getEffectiveDate(): DateTime
+ {
+ $days = $this->serial * static::$cycleDurationDays;
+
+ return $this->getEpochDate()->add(new DateInterval("P{$days}D"));
+ }
+
+ /** Returns the cycle's serial */
+ public function getSerial(): int
+ {
+ return $this->serial;
+ }
+
+ /** Returns the next cycle from the cycle */
+ public function nextCycle(): static
+ {
+ return static::fromSerial($this->serial + 1);
+ }
+
+ /** Returns the previous cycle from the cycle */
+ public function previousCycle(): static
+ {
+ return static::fromSerial($this->serial - 1);
+ }
+
+ /** Returns a DateTime instance that represents the AIRAC epoch */
+ protected static function getEpochDate(): DateTime
+ {
+ return new DateTime(static::$epochDate, new DateTimeZone('UTC'));
+ }
+}
diff --git a/src/Exceptions/InvalidAIRACCycleCodeException.php b/src/Exceptions/InvalidAIRACCycleCodeException.php
new file mode 100644
index 0000000..cb4f941
--- /dev/null
+++ b/src/Exceptions/InvalidAIRACCycleCodeException.php
@@ -0,0 +1,9 @@
+cycle = new AIRACCycle(1619);
+ }
+
+ public function testGettingCycleCode(): void
+ {
+ $this->assertEquals('2502', $this->cycle->getCycleCode());
+ }
+
+ public function testGettingOrdinal(): void
+ {
+ $this->assertEquals(2, $this->cycle->getOrdinal());
+ }
+
+ public function testGettingEffectiveDate(): void
+ {
+ $this->assertEquals(new DateTime('2025-02-20', new DateTimeZone('UTC')), $this->cycle->getEffectiveDate());
+ }
+
+ public function testGettingSerial(): void
+ {
+ $this->assertEquals(1619, $this->cycle->getSerial());
+ }
+
+ public function testGettingNextCycle(): void
+ {
+ $this->assertEquals(1620, $this->cycle->nextCycle()->getSerial());
+ }
+
+ public function testGettingPreviousCycle(): void
+ {
+ $this->assertEquals(1618, $this->cycle->previousCycle()->getSerial());
+ }
+}
diff --git a/tests/AIRACCreationTest.php b/tests/AIRACCreationTest.php
new file mode 100644
index 0000000..cf3e36b
--- /dev/null
+++ b/tests/AIRACCreationTest.php
@@ -0,0 +1,94 @@
+assertEquals(1619, $cycle->getSerial());
+
+ // Within the cycle
+ $cycle = AIRACCycle::forDate(new DateTime('2025-03-19 17:00:00', new DateTimeZone('UTC')));
+ $this->assertEquals(1619, $cycle->getSerial());
+
+ // Different timezone
+ $cycle = AIRACCycle::forDate(new DateTime('2025-03-19 15:59:00', new DateTimeZone('PST')));
+ $this->assertEquals(1619, $cycle->getSerial());
+ }
+
+ public function testItCanGetCycleFromValidEffectiveDate(): void
+ {
+ $cycle = AIRACCycle::fromEffectiveDate(new DateTime('2025-02-20', new DateTimeZone('UTC')));
+ $this->assertEquals(1619, $cycle->getSerial());
+
+ $cycle = AIRACCycle::fromEffectiveDate(new DateTime('2025-02-19 16:00:00', new DateTimeZone('PST')));
+ $this->assertEquals(1619, $cycle->getSerial());
+ }
+
+ public function testItThrowsIfNotValidEffectiveDate(): void
+ {
+ $this->expectException(InvalidAIRACEffectiveDateException::class);
+ $this->expectExceptionMessage('2025-02-20T00:00:01+00:00 is not a valid effective date');
+ AIRACCycle::fromEffectiveDate(new DateTime('2025-02-20 00:00:01', new DateTimeZone('UTC')));
+ }
+
+ public function testItCanGetForValidCycleCode(): void
+ {
+ $cycle = AIRACCycle::fromCycleCode('2502');
+ $this->assertEquals(1619, $cycle->getSerial());
+
+ $cycle = AIRACCycle::fromCycleCode('2513');
+ $this->assertEquals(1630, $cycle->getSerial());
+ }
+
+ public function testItThrowsForInvalidCycleCodeFormat(): void
+ {
+ $this->expectException(InvalidAIRACCycleCodeException::class);
+ $this->expectExceptionMessage('25021 was not of format YYOO');
+ AIRACCycle::fromCycleCode('25021');
+ }
+
+ public function testItThrowsForInvalidCycleCodeWithTooManyCycles(): void
+ {
+ $this->expectException(InvalidAIRACCycleCodeException::class);
+ $this->expectExceptionMessage('Year 2025 does not have 14 cycles');
+ AIRACCycle::fromCycleCode('2514');
+ }
+
+ public function testItCanCreateFromValidSerial(): void
+ {
+ $cycle = AIRACCycle::fromSerial('1619');
+ $this->assertEquals(1619, $cycle->getSerial());
+ }
+
+ public function testItThrowsForInvalidSerial(): void
+ {
+ $this->expectException(InvalidAIRACSerialException::class);
+ $this->expectExceptionMessage('-1 is not a valid AIRAC serial');
+ AIRACCycle::fromSerial(-1);
+ }
+
+ public function testItCanGetCurrentAirac(): void
+ {
+ $this->assertEquals(AIRACCycle::forDate(new DateTime())->getSerial(), AIRACCycle::current()->getSerial());
+ }
+
+ public function testItCanGetNextAirac(): void
+ {
+ $this->assertEquals(AIRACCycle::forDate(new DateTime())->getSerial() + 1, AIRACCycle::next()->getSerial());
+ }
+
+ public function testItCanGetPreviousAirac(): void
+ {
+ $this->assertEquals(AIRACCycle::forDate(new DateTime())->getSerial() - 1, AIRACCycle::previous()->getSerial());
+ }
+}
diff --git a/tests/AccuracyTest.php b/tests/AccuracyTest.php
index 82e76a0..3acc0c8 100644
--- a/tests/AccuracyTest.php
+++ b/tests/AccuracyTest.php
@@ -1,17 +1,11 @@
assertEquals(new Carbon(AIRACCalculator::$datumDate), AIRACCalculator::dateForCycle(AIRACCalculator::$datumCycle));
- }
-
public function testItIsTheSameAsOfficalSource(): void
{
$crawler = new Crawler(file_get_contents('https://www.nm.eurocontrol.int/RAD/common/airac_dates.html'));
@@ -30,9 +24,8 @@ public function testItIsTheSameAsOfficalSource(): void
$cycle = $cycles->eq($i);
$cycleNumber = $cycle->filter('td:nth-of-type(2)')->text();
- $cycleDate = new Carbon($cycle->filter('td:nth-of-type(5)')->text());
-
- $this->assertEquals($cycleDate, AIRACCalculator::dateForCycle($cycleNumber));
+ $cycleDate = new DateTime($cycle->filter('td:nth-of-type(5)')->text(), new DateTimeZone('UTC'));
+ $this->assertEquals($cycleDate, AIRACCycle::fromCycleCode($cycleNumber)->getEffectiveDate());
}
}
}
diff --git a/tests/CurrentAiracTest.php b/tests/CurrentAiracTest.php
deleted file mode 100644
index e896ade..0000000
--- a/tests/CurrentAiracTest.php
+++ /dev/null
@@ -1,69 +0,0 @@
-assertEquals('1906', AIRACCalculator::currentAiracCycle());
-
- Carbon::setTestNow(new Carbon('19 JUN 19 23:59:59'));
- $this->assertEquals('1906', AIRACCalculator::currentAiracCycle());
-
-
- Carbon::setTestNow(new Carbon('20 JUN 19'));
- $this->assertEquals('1907', AIRACCalculator::currentAiracCycle());
-
- Carbon::setTestNow();
- }
-
- public function testItReportsCorrectlyOverAYear(): void
- {
- // 2014 cycle effective at 31 DEC 20
- // 2101 cycle effective at 28 JAN 21
-
- Carbon::setTestNow(new Carbon('10 JAN 21'));
- $this->assertEquals('2014', AIRACCalculator::currentAiracCycle());
-
- Carbon::setTestNow(new Carbon('10 FEB 21'));
- $this->assertEquals('2101', AIRACCalculator::currentAiracCycle());
-
- Carbon::setTestNow();
- }
-
- public function testItCorrectlyIndicatesIfTodayIsNewAirac(): void
- {
- // 1906 cycle effective at 23 MAY 19
- // 1907 cycle effective at 20 JUN 19
-
- Carbon::setTestNow(new Carbon('20 JUN 19'));
- $this->assertTrue(AIRACCalculator::isNewAiracDay());
-
- Carbon::setTestNow(new Carbon('20 JUN 19 14:35:59'));
- $this->assertTrue(AIRACCalculator::isNewAiracDay());
-
- Carbon::setTestNow(new Carbon('19 JUN 19 23:59:59'));
- $this->assertFalse(AIRACCalculator::isNewAiracDay());
-
- Carbon::setTestNow();
- }
-
- public function testItCorrectlyIndicatesIfADateIsNewAirac(): void
- {
- // 1906 cycle effective at 23 MAY 19
- // 1907 cycle effective at 20 JUN 19
-
- $this->assertTrue(AIRACCalculator::isNewAiracDay(new Carbon('20 JUN 19')));
-
- $this->assertTrue(AIRACCalculator::isNewAiracDay(new Carbon('20 JUN 19 14:35:59')));
-
- $this->assertFalse(AIRACCalculator::isNewAiracDay(new Carbon('19 JUN 19 23:59:59')));
- }
-}
diff --git a/tests/CycleListTest.php b/tests/CycleListTest.php
deleted file mode 100644
index 785205e..0000000
--- a/tests/CycleListTest.php
+++ /dev/null
@@ -1,44 +0,0 @@
-assertEquals(13, count(AIRACCalculator::cyclesForYear('2019')));
- $this->assertEquals(14, count(AIRACCalculator::cyclesForYear('2020')));
-
- // Assert 2D array
- $this->assertEquals(2, count(AIRACCalculator::cyclesForYear('2020')[0]));
- }
-
- public function testItReturnsCyclesCurrentYear(): void
- {
- // 2020 has 14 cycles
- Carbon::setTestNow(new Carbon('01 JAN 2020'));
- $this->assertEquals(14, count(AIRACCalculator::cyclesForYear()));
- }
-
- public function testItReturnsCorrectArray(): void
- {
- // 2019 has 13 cycles, first cycle 1901 effective 2019-01-03
- $cycles = AIRACCalculator::cyclesForYear('2019');
-
- $this->assertEquals("1901", $cycles[0][0]);
- $this->assertEquals(new Carbon('2019-01-03'), $cycles[0][1]);
-
-
- // 2020 has 14 cycles, cycle 2014 effective 2020-12-31
-
- $cycles = AIRACCalculator::cyclesForYear('2020');
-
- $this->assertEquals("2014", $cycles[13][0]);
- $this->assertEquals(new Carbon('2020-12-31'), $cycles[13][1]);
- }
-}
diff --git a/tests/DateForCycleTest.php b/tests/DateForCycleTest.php
deleted file mode 100644
index e671d21..0000000
--- a/tests/DateForCycleTest.php
+++ /dev/null
@@ -1,17 +0,0 @@
-assertEquals(AIRACCalculator::dateForCycle('1906'), new Carbon('23 MAY 19'));
- $this->assertEquals(AIRACCalculator::dateForCycle('1907'), new Carbon('20 JUN 19'));
- }
-}
diff --git a/tests/NextAiracCycleTest.php b/tests/NextAiracCycleTest.php
deleted file mode 100644
index 6f79e4e..0000000
--- a/tests/NextAiracCycleTest.php
+++ /dev/null
@@ -1,29 +0,0 @@
-assertEquals(AIRACCalculator::nextAiracCycle(), '1907');
- $this->assertEquals(AIRACCalculator::nextAiracCycle(new Carbon('20 JUN 19')), '1908');
- $this->assertEquals(AIRACCalculator::nextAiracCycle(new Carbon('21 JUN 19')), '1908');
- }
-
- public function testItCanFindNextAiracCycleForExceptionalYear(): void
- {
- // 2013 cycle effective at 03 DEC 20
- // 2014 cycle effective at 31 DEC 20
- // 2101 cycle effective at 28 JAN 21
- $this->assertEquals(AIRACCalculator::nextAiracCycle(new Carbon('02 DEC 20')), '2013');
- $this->assertEquals(AIRACCalculator::nextAiracCycle(new Carbon('03 DEC 20')), '2014');
- $this->assertEquals(AIRACCalculator::nextAiracCycle(new Carbon('31 DEC 20')), '2101');
- }
-}
diff --git a/tests/NextAiracDateTest.php b/tests/NextAiracDateTest.php
deleted file mode 100644
index 8eda069..0000000
--- a/tests/NextAiracDateTest.php
+++ /dev/null
@@ -1,29 +0,0 @@
-assertEquals(AIRACCalculator::nextAiracDate(), new Carbon('20 JUN 19'));
- $this->assertEquals(AIRACCalculator::nextAiracDate(new Carbon('20 JUN 19')), new Carbon('18 JUL 19'));
- $this->assertEquals(AIRACCalculator::nextAiracDate(new Carbon('21 JUN 19')), new Carbon('18 JUL 19'));
- }
-
- public function testItCanFindNextAiracDateForExceptionalYear(): void
- {
- // 2013 cycle effective at 03 DEC 20
- // 2014 cycle effective at 31 DEC 20
- // 2101 cycle effective at 28 JAN 21
- $this->assertEquals(AIRACCalculator::nextAiracDate(new Carbon('02 DEC 20')), new Carbon('03 DEC 20'));
- $this->assertEquals(AIRACCalculator::nextAiracDate(new Carbon('03 DEC 20')), new Carbon('31 DEC 20'));
- $this->assertEquals(AIRACCalculator::nextAiracDate(new Carbon('31 DEC 20')), new Carbon('28 JAN 21'));
- }
-}