Created
February 22, 2026 19:07
-
-
Save bland-industries/32eee85fef2316b322763ca025e7bc6a to your computer and use it in GitHub Desktop.
Fixed Calendar Converter for Laravel with tests
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| // These are the routes I added to get the data | |
| Route::get('fixed-calendar/today', [FixedCalendarController::class, 'today']) | |
| ->name('fixed-calendar.today'); | |
| Route::get('fixed-calendar/{year}/{month}/{day}', [FixedCalendarController::class, 'date']) | |
| ->name('fixed-calendar.date'); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| namespace App\Http\Controllers; | |
| use Illuminate\Http\JsonResponse; | |
| use Illuminate\Support\Carbon; | |
| use Illuminate\Support\Number; | |
| /** | |
| * Class to handle determining the Fixed Calendar date. | |
| * https://en.wikipedia.org/wiki/International_Fixed_Calendar | |
| * | |
| * I have also added some fun information about the origins/meanings of our month and day names | |
| */ | |
| class FixedCalendarController extends Controller | |
| { | |
| /** | |
| * Standard names for the months | |
| */ | |
| protected array $months = [ | |
| 'January', | |
| 'February', | |
| 'March', | |
| 'April', | |
| 'May', | |
| 'June', | |
| 'Sol', | |
| 'July', | |
| 'August', | |
| 'September', | |
| 'October', | |
| 'November', | |
| 'December', | |
| ]; | |
| /** | |
| * Origin names for the months | |
| */ | |
| protected array $originMonths = [ | |
| 'Janus', | |
| 'Purification', | |
| 'Mars', | |
| 'Opening', | |
| 'Maia', | |
| 'Juno', | |
| 'Sun', | |
| 'Julius Ceaser', | |
| 'Augustus Ceaser', | |
| 'Seventh', | |
| 'Eighth', | |
| 'Ninth', | |
| 'Tenth', | |
| ]; | |
| /** | |
| * Standard names for the days | |
| */ | |
| protected array $days = [ | |
| 'Sunday', | |
| 'Monday', | |
| 'Tuesday', | |
| 'Wednesday', | |
| 'Thursday', | |
| 'Friday', | |
| 'Saturday', | |
| ]; | |
| /** | |
| * Origin names for the days | |
| */ | |
| protected array $originDays = [ | |
| 'Sun', | |
| 'Moon', | |
| 'Mars', | |
| 'Mercury', | |
| 'Jupiter', | |
| 'Venus', | |
| 'Saturn', | |
| ]; | |
| /** | |
| * Default return value for Leap Day | |
| */ | |
| protected array $leapDay = [ | |
| 'day' => [ | |
| 'index' => -1, | |
| 'number' => 0, | |
| 'name' => 'Leap Day', | |
| 'origin' => 'Leap Day', | |
| ], | |
| 'week' => [ | |
| 'index' => -1, | |
| 'number' => 0, | |
| ], | |
| 'month' => [ | |
| 'index' => -1, | |
| 'number' => 0, | |
| 'name' => 'Leap Day', | |
| 'origin' => 'Leap Day', | |
| ], | |
| 'year' => [ | |
| 'number' => null, | |
| 'he' => null, | |
| ], | |
| ]; | |
| /** | |
| * Default return value for Year Day | |
| */ | |
| protected array $yearDay = [ | |
| 'day' => [ | |
| 'index' => -1, | |
| 'number' => 0, | |
| 'name' => 'Year Day', | |
| 'origin' => 'Year Day', | |
| ], | |
| 'week' => [ | |
| 'index' => -1, | |
| 'number' => 0, | |
| ], | |
| 'month' => [ | |
| 'index' => -1, | |
| 'number' => 0, | |
| 'name' => 'Year Day', | |
| 'origin' => 'Year Day', | |
| ], | |
| 'year' => [ | |
| 'number' => null, | |
| 'he' => null, | |
| ], | |
| ]; | |
| /** | |
| * Function to determine the Fixed Calendar date data from the given Gregorian Date | |
| */ | |
| public function determineDate(Carbon $date): array | |
| { | |
| $dayOfYear = $date->dayOfYear; | |
| // check for leap year | |
| if ($date->isLeapYear()) { | |
| // check if Leap Day | |
| if ($dayOfYear == 169) { | |
| return $this->formatSpecialDay($this->leapDay, $date); | |
| } | |
| // Any day after the Gregorian Leap Day needs to be shifted by 1 | |
| if ($dayOfYear > 60) { | |
| $dayOfYear -= 1; | |
| } | |
| } | |
| // check if Year Day | |
| if ($dayOfYear == 365) { | |
| return $this->formatSpecialDay($this->yearDay, $date); | |
| } | |
| // calculate values | |
| $monthIndex = intdiv($date->dayOfYear - 1, 28); | |
| $dayOfMonth = ($dayOfYear % 28); | |
| $dayOfMonth = $dayOfMonth == 0 ? 28 : $dayOfMonth; // for the 28th day | |
| $weekOfMonthIndex = intdiv($dayOfMonth - 1, 7); | |
| $dayOfWeek = ($dayOfMonth % 7); | |
| $dayOfWeek = $dayOfWeek == 0 ? 7 : $dayOfWeek; // for the 7th day | |
| $dayOfWeekIndex = $dayOfWeek - 1; | |
| $data = [ | |
| 'day' => [ | |
| 'index' => $dayOfMonth - 1, | |
| 'number' => $dayOfMonth, | |
| 'name' => $this->days[$dayOfWeekIndex], | |
| 'origin' => $this->originDays[$dayOfWeekIndex], | |
| ], | |
| 'week' => [ | |
| 'index' => $weekOfMonthIndex, | |
| 'number' => $weekOfMonthIndex + 1, | |
| ], | |
| 'month' => [ | |
| 'index' => $monthIndex, | |
| 'number' => $monthIndex + 1, | |
| 'name' => $this->months[$monthIndex], | |
| 'origin' => $this->originMonths[$monthIndex], | |
| ], | |
| 'year' => [ | |
| 'number' => $date->year, | |
| 'he' => $date->year + 10000, | |
| ], | |
| ]; | |
| $data['funText'] = $this->formatFunText($data); | |
| $data['fixedText'] = $this->formatFixedText($data); | |
| return $data; | |
| } | |
| /** | |
| * Format the fixed data into a human readable string | |
| * Examples: | |
| * - Sunday, August 1st, 2026 | |
| * - Year Day, 2026 | |
| */ | |
| public function formatFixedText($data) | |
| { | |
| return sprintf('%s, %s %s, %u', | |
| $data['day']['name'], | |
| $data['month']['name'], | |
| Number::ordinal($data['day']['number']), | |
| $data['year']['number'] | |
| ); | |
| } | |
| /** | |
| * Format the fixed data into a human readable string with origin names and Human Era years | |
| * Examples: | |
| * - Sun Day of the 1st week, 1st day of Augustus Ceaser Month, 12026 HE | |
| * - Year Day, 12026 HE | |
| */ | |
| public function formatFunText($data) | |
| { | |
| return sprintf('%s Day of the %s week, %s day of %s Month, %u HE', | |
| $data['day']['origin'], | |
| Number::ordinal($data['week']['number']), | |
| Number::ordinal($data['day']['number']), | |
| $data['month']['origin'], | |
| $data['year']['he'] | |
| ); | |
| } | |
| /** | |
| * Format the output of the special days | |
| */ | |
| protected function formatSpecialDay(array $dateObject, Carbon $date): array | |
| { | |
| $he = $date->year + 10000; | |
| $dateObject['year'] = [ | |
| 'number' => $date->year, | |
| 'he' => $he, | |
| ]; | |
| $dateObject['funText'] = "{$dateObject['day']['name']}, {$he} HE"; | |
| $dateObject['fixedText'] = "{$dateObject['day']['name']}, {$date->year}"; | |
| return $dateObject; | |
| } | |
| /** | |
| * Route endpoint to get today's Fixed Calendar data | |
| */ | |
| public function today(): JsonResponse | |
| { | |
| $now = Carbon::now(); | |
| return response()->json($this->determineDate($now)); | |
| } | |
| /** | |
| * Route endpoint to get a specific dates Fixed Calendar data | |
| */ | |
| public function date($year, $month, $day): JsonResponse | |
| { | |
| $date = Carbon::createFromDate($year, $month, $day); | |
| return response()->json($this->determineDate($date)); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| use App\Http\Controllers\FixedCalendarController; | |
| use Illuminate\Support\Carbon; | |
| test('2026-08-13', function () { | |
| $fixedCal = new FixedCalendarController; | |
| $date = Carbon::createFromDate(2026, 8, 13); | |
| $fixedCalDate = $fixedCal->determineDate($date); | |
| expect($fixedCalDate)->toBe([ | |
| 'day' => [ | |
| 'index' => 0, | |
| 'number' => 1, | |
| 'name' => 'Sunday', | |
| 'origin' => 'Sun', | |
| ], | |
| 'week' => [ | |
| 'index' => 0, | |
| 'number' => 1, | |
| ], | |
| 'month' => [ | |
| 'index' => 8, | |
| 'number' => 9, | |
| 'name' => 'August', | |
| 'origin' => 'Augustus Ceaser', | |
| ], | |
| 'year' => [ | |
| 'number' => 2026, | |
| 'he' => 12026, | |
| ], | |
| 'funText' => 'Sun Day of the 1st week, 1st day of Augustus Ceaser Month, 12026 HE', | |
| 'fixedText' => 'Sunday, August 1st, 2026', | |
| ]); | |
| }); | |
| test('2026-12-19', function () { | |
| $fixedCal = new FixedCalendarController; | |
| $date = Carbon::createFromDate(2026, 12, 19); | |
| $fixedCalDate = $fixedCal->determineDate($date); | |
| expect($fixedCalDate)->toBe([ | |
| 'day' => [ | |
| 'index' => 16, | |
| 'number' => 17, | |
| 'name' => 'Tuesday', | |
| 'origin' => 'Mars', | |
| ], | |
| 'week' => [ | |
| 'index' => 2, | |
| 'number' => 3, | |
| ], | |
| 'month' => [ | |
| 'index' => 12, | |
| 'number' => 13, | |
| 'name' => 'December', | |
| 'origin' => 'Tenth', | |
| ], | |
| 'year' => [ | |
| 'number' => 2026, | |
| 'he' => 12026, | |
| ], | |
| 'funText' => 'Mars Day of the 3rd week, 17th day of Tenth Month, 12026 HE', | |
| 'fixedText' => 'Tuesday, December 17th, 2026', | |
| ]); | |
| }); | |
| test('2026-04-28', function () { | |
| $fixedCal = new FixedCalendarController; | |
| $date = Carbon::createFromDate(2026, 4, 28); | |
| $fixedCalDate = $fixedCal->determineDate($date); | |
| expect($fixedCalDate)->toBe([ | |
| 'day' => [ | |
| 'index' => 5, | |
| 'number' => 6, | |
| 'name' => 'Friday', | |
| 'origin' => 'Venus', | |
| ], | |
| 'week' => [ | |
| 'index' => 0, | |
| 'number' => 1, | |
| ], | |
| 'month' => [ | |
| 'index' => 4, | |
| 'number' => 5, | |
| 'name' => 'May', | |
| 'origin' => 'Maia', | |
| ], | |
| 'year' => [ | |
| 'number' => 2026, | |
| 'he' => 12026, | |
| ], | |
| 'funText' => 'Venus Day of the 1st week, 6th day of Maia Month, 12026 HE', | |
| 'fixedText' => 'Friday, May 6th, 2026', | |
| ]); | |
| }); | |
| test('2026-04-19', function () { | |
| $fixedCal = new FixedCalendarController; | |
| $date = Carbon::createFromDate(2026, 4, 19); | |
| $fixedCalDate = $fixedCal->determineDate($date); | |
| expect($fixedCalDate)->toBe([ | |
| 'day' => [ | |
| 'index' => 24, | |
| 'number' => 25, | |
| 'name' => 'Wednesday', | |
| 'origin' => 'Mercury', | |
| ], | |
| 'week' => [ | |
| 'index' => 3, | |
| 'number' => 4, | |
| ], | |
| 'month' => [ | |
| 'index' => 3, | |
| 'number' => 4, | |
| 'name' => 'April', | |
| 'origin' => 'Opening', | |
| ], | |
| 'year' => [ | |
| 'number' => 2026, | |
| 'he' => 12026, | |
| ], | |
| 'funText' => 'Mercury Day of the 4th week, 25th day of Opening Month, 12026 HE', | |
| 'fixedText' => 'Wednesday, April 25th, 2026', | |
| ]); | |
| }); | |
| test('2026-06-17 - Not Leap Day', function () { | |
| $fixedCal = new FixedCalendarController; | |
| $date = Carbon::createFromDate(2026, 6, 17); | |
| $fixedCalDate = $fixedCal->determineDate($date); | |
| expect($fixedCalDate)->toBe([ | |
| 'day' => [ | |
| 'index' => 27, | |
| 'number' => 28, | |
| 'name' => 'Saturday', | |
| 'origin' => 'Saturn', | |
| ], | |
| 'week' => [ | |
| 'index' => 3, | |
| 'number' => 4, | |
| ], | |
| 'month' => [ | |
| 'index' => 5, | |
| 'number' => 6, | |
| 'name' => 'June', | |
| 'origin' => 'Juno', | |
| ], | |
| 'year' => [ | |
| 'number' => 2026, | |
| 'he' => 12026, | |
| ], | |
| 'funText' => 'Saturn Day of the 4th week, 28th day of Juno Month, 12026 HE', | |
| 'fixedText' => 'Saturday, June 28th, 2026', | |
| ]); | |
| }); | |
| test('2026-06-18', function () { | |
| $fixedCal = new FixedCalendarController; | |
| $date = Carbon::createFromDate(2026, 6, 18); | |
| $fixedCalDate = $fixedCal->determineDate($date); | |
| expect($fixedCalDate)->toBe([ | |
| 'day' => [ | |
| 'index' => 0, | |
| 'number' => 1, | |
| 'name' => 'Sunday', | |
| 'origin' => 'Sun', | |
| ], | |
| 'week' => [ | |
| 'index' => 0, | |
| 'number' => 1, | |
| ], | |
| 'month' => [ | |
| 'index' => 6, | |
| 'number' => 7, | |
| 'name' => 'Sol', | |
| 'origin' => 'Sun', | |
| ], | |
| 'year' => [ | |
| 'number' => 2026, | |
| 'he' => 12026, | |
| ], | |
| 'funText' => 'Sun Day of the 1st week, 1st day of Sun Month, 12026 HE', | |
| 'fixedText' => 'Sunday, Sol 1st, 2026', | |
| ]); | |
| }); | |
| test('2026-12-31 - Year Day', function () { | |
| $fixedCal = new FixedCalendarController; | |
| $date = Carbon::createFromDate(2026, 12, 31); | |
| $fixedCalDate = $fixedCal->determineDate($date); | |
| expect($fixedCalDate)->toBe([ | |
| 'day' => [ | |
| 'index' => -1, | |
| 'number' => 0, | |
| 'name' => 'Year Day', | |
| 'origin' => 'Year Day', | |
| ], | |
| 'week' => [ | |
| 'index' => -1, | |
| 'number' => 0, | |
| ], | |
| 'month' => [ | |
| 'index' => -1, | |
| 'number' => 0, | |
| 'name' => 'Year Day', | |
| 'origin' => 'Year Day', | |
| ], | |
| 'year' => [ | |
| 'number' => 2026, | |
| 'he' => 12026, | |
| ], | |
| 'funText' => 'Year Day, 12026 HE', | |
| 'fixedText' => 'Year Day, 2026', | |
| ]); | |
| }); | |
| test('2028-08-13', function () { | |
| $fixedCal = new FixedCalendarController; | |
| $date = Carbon::createFromDate(2028, 8, 13); | |
| $fixedCalDate = $fixedCal->determineDate($date); | |
| expect($fixedCalDate)->toBe([ | |
| 'day' => [ | |
| 'index' => 0, | |
| 'number' => 1, | |
| 'name' => 'Sunday', | |
| 'origin' => 'Sun', | |
| ], | |
| 'week' => [ | |
| 'index' => 0, | |
| 'number' => 1, | |
| ], | |
| 'month' => [ | |
| 'index' => 8, | |
| 'number' => 9, | |
| 'name' => 'August', | |
| 'origin' => 'Augustus Ceaser', | |
| ], | |
| 'year' => [ | |
| 'number' => 2028, | |
| 'he' => 12028, | |
| ], | |
| 'funText' => 'Sun Day of the 1st week, 1st day of Augustus Ceaser Month, 12028 HE', | |
| 'fixedText' => 'Sunday, August 1st, 2028', | |
| ]); | |
| }); | |
| test('2028-12-19', function () { | |
| $fixedCal = new FixedCalendarController; | |
| $date = Carbon::createFromDate(2028, 12, 19); | |
| $fixedCalDate = $fixedCal->determineDate($date); | |
| expect($fixedCalDate)->toBe([ | |
| 'day' => [ | |
| 'index' => 16, | |
| 'number' => 17, | |
| 'name' => 'Tuesday', | |
| 'origin' => 'Mars', | |
| ], | |
| 'week' => [ | |
| 'index' => 2, | |
| 'number' => 3, | |
| ], | |
| 'month' => [ | |
| 'index' => 12, | |
| 'number' => 13, | |
| 'name' => 'December', | |
| 'origin' => 'Tenth', | |
| ], | |
| 'year' => [ | |
| 'number' => 2028, | |
| 'he' => 12028, | |
| ], | |
| 'funText' => 'Mars Day of the 3rd week, 17th day of Tenth Month, 12028 HE', | |
| 'fixedText' => 'Tuesday, December 17th, 2028', | |
| ]); | |
| }); | |
| test('2028-04-28', function () { | |
| $fixedCal = new FixedCalendarController; | |
| $date = Carbon::createFromDate(2028, 4, 28); | |
| $fixedCalDate = $fixedCal->determineDate($date); | |
| expect($fixedCalDate)->toBe([ | |
| 'day' => [ | |
| 'index' => 5, | |
| 'number' => 6, | |
| 'name' => 'Friday', | |
| 'origin' => 'Venus', | |
| ], | |
| 'week' => [ | |
| 'index' => 0, | |
| 'number' => 1, | |
| ], | |
| 'month' => [ | |
| 'index' => 4, | |
| 'number' => 5, | |
| 'name' => 'May', | |
| 'origin' => 'Maia', | |
| ], | |
| 'year' => [ | |
| 'number' => 2028, | |
| 'he' => 12028, | |
| ], | |
| 'funText' => 'Venus Day of the 1st week, 6th day of Maia Month, 12028 HE', | |
| 'fixedText' => 'Friday, May 6th, 2028', | |
| ]); | |
| }); | |
| test('2028-04-19', function () { | |
| $fixedCal = new FixedCalendarController; | |
| $date = Carbon::createFromDate(2028, 4, 19); | |
| $fixedCalDate = $fixedCal->determineDate($date); | |
| expect($fixedCalDate)->toBe([ | |
| 'day' => [ | |
| 'index' => 24, | |
| 'number' => 25, | |
| 'name' => 'Wednesday', | |
| 'origin' => 'Mercury', | |
| ], | |
| 'week' => [ | |
| 'index' => 3, | |
| 'number' => 4, | |
| ], | |
| 'month' => [ | |
| 'index' => 3, | |
| 'number' => 4, | |
| 'name' => 'April', | |
| 'origin' => 'Opening', | |
| ], | |
| 'year' => [ | |
| 'number' => 2028, | |
| 'he' => 12028, | |
| ], | |
| 'funText' => 'Mercury Day of the 4th week, 25th day of Opening Month, 12028 HE', | |
| 'fixedText' => 'Wednesday, April 25th, 2028', | |
| ]); | |
| }); | |
| test('2028-12-31 - Year Day', function () { | |
| $fixedCal = new FixedCalendarController; | |
| $date = Carbon::createFromDate(2028, 12, 31); | |
| $fixedCalDate = $fixedCal->determineDate($date); | |
| expect($fixedCalDate)->toBe([ | |
| 'day' => [ | |
| 'index' => -1, | |
| 'number' => 0, | |
| 'name' => 'Year Day', | |
| 'origin' => 'Year Day', | |
| ], | |
| 'week' => [ | |
| 'index' => -1, | |
| 'number' => 0, | |
| ], | |
| 'month' => [ | |
| 'index' => -1, | |
| 'number' => 0, | |
| 'name' => 'Year Day', | |
| 'origin' => 'Year Day', | |
| ], | |
| 'year' => [ | |
| 'number' => 2028, | |
| 'he' => 12028, | |
| ], | |
| 'funText' => 'Year Day, 12028 HE', | |
| 'fixedText' => 'Year Day, 2028', | |
| ]); | |
| }); | |
| test('2028-02-29 - Gregorian Leap Day', function () { | |
| $fixedCal = new FixedCalendarController; | |
| $date = Carbon::createFromDate(2028, 2, 29); | |
| $fixedCalDate = $fixedCal->determineDate($date); | |
| expect($fixedCalDate)->toBe([ | |
| 'day' => [ | |
| 'index' => 3, | |
| 'number' => 4, | |
| 'name' => 'Wednesday', | |
| 'origin' => 'Mercury', | |
| ], | |
| 'week' => [ | |
| 'index' => 0, | |
| 'number' => 1, | |
| ], | |
| 'month' => [ | |
| 'index' => 2, | |
| 'number' => 3, | |
| 'name' => 'March', | |
| 'origin' => 'Mars', | |
| ], | |
| 'year' => [ | |
| 'number' => 2028, | |
| 'he' => 12028, | |
| ], | |
| 'funText' => 'Mercury Day of the 1st week, 4th day of Mars Month, 12028 HE', | |
| 'fixedText' => 'Wednesday, March 4th, 2028', | |
| ]); | |
| }); | |
| test('2028-06-17 - Leap Day', function () { | |
| $fixedCal = new FixedCalendarController; | |
| $date = Carbon::createFromDate(2028, 6, 17); | |
| $fixedCalDate = $fixedCal->determineDate($date); | |
| expect($fixedCalDate)->toBe([ | |
| 'day' => [ | |
| 'index' => -1, | |
| 'number' => 0, | |
| 'name' => 'Leap Day', | |
| 'origin' => 'Leap Day', | |
| ], | |
| 'week' => [ | |
| 'index' => -1, | |
| 'number' => 0, | |
| ], | |
| 'month' => [ | |
| 'index' => -1, | |
| 'number' => 0, | |
| 'name' => 'Leap Day', | |
| 'origin' => 'Leap Day', | |
| ], | |
| 'year' => [ | |
| 'number' => 2028, | |
| 'he' => 12028, | |
| ], | |
| 'funText' => 'Leap Day, 12028 HE', | |
| 'fixedText' => 'Leap Day, 2028', | |
| ]); | |
| }); | |
| test('2028-06-18', function () { | |
| $fixedCal = new FixedCalendarController; | |
| $date = Carbon::createFromDate(2028, 6, 18); | |
| $fixedCalDate = $fixedCal->determineDate($date); | |
| expect($fixedCalDate)->toBe([ | |
| 'day' => [ | |
| 'index' => 0, | |
| 'number' => 1, | |
| 'name' => 'Sunday', | |
| 'origin' => 'Sun', | |
| ], | |
| 'week' => [ | |
| 'index' => 0, | |
| 'number' => 1, | |
| ], | |
| 'month' => [ | |
| 'index' => 6, | |
| 'number' => 7, | |
| 'name' => 'Sol', | |
| 'origin' => 'Sun', | |
| ], | |
| 'year' => [ | |
| 'number' => 2028, | |
| 'he' => 12028, | |
| ], | |
| 'funText' => 'Sun Day of the 1st week, 1st day of Sun Month, 12028 HE', | |
| 'fixedText' => 'Sunday, Sol 1st, 2028', | |
| ]); | |
| }); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment