Created
September 24, 2025 13:39
-
-
Save hopeseekr/3ac57243ad8ed282ad0075cb2fe4fa5b to your computer and use it in GitHub Desktop.
nth Day PHP + benchmarks
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 | |
| // Day | dateSuffixOriginal (s) | date_suffix_v5 (s) | Difference (date_suffix_v5 - dateSuffixOriginal) | % Difference | |
| // ----|------------|------------|------------|------------ | |
| // Avg | 0.024745 | 0.012849 | -0.011896 | -48.06% | |
| const DAY_SUFFIXES = ['th', 'st', 'nd', 'rd', 'th', 'th', 'th', 'th', 'th', 'th']; | |
| function date_suffix_v5(int $day) | |
| { | |
| // Single comparison for 11th, 12th, 13th | |
| if ($day - 11 <= 2 && $day >= 11) { | |
| return 'th'; | |
| } | |
| return DAY_SUFFIXES[$day % 10]; | |
| } | |
| // Original function | |
| function dateSuffixOriginal($x) { | |
| $s = [0, "st", "nd", "rd"]; | |
| return (in_array($x, [1, 2, 3, 21, 22, 23, 31])) ? $s[$x % 10] : "th"; | |
| } | |
| // Rewritten, more readable function | |
| function dateSuffixReadable($day) { | |
| // Validate input: ensure it's a positive integer | |
| if (!is_numeric($day) || $day < 1 || $day != (int)$day) { | |
| return "th"; // Default to "th" for invalid inputs | |
| } | |
| // Array mapping last digit to suffix (1 => "st", 2 => "nd", 3 => "rd") | |
| $suffixes = [ | |
| 1 => "st", | |
| 2 => "nd", | |
| 3 => "rd" | |
| ]; | |
| // Special case: numbers ending in 11, 12, 13 use "th" | |
| $lastTwoDigits = $day % 100; | |
| if ($lastTwoDigits >= 11 && $lastTwoDigits <= 13) { | |
| return "th"; | |
| } | |
| // Get the last digit of the day | |
| $lastDigit = $day % 10; | |
| // Return the appropriate suffix or "th" if not 1, 2, or 3 | |
| if (isset($suffixes[$lastDigit])) { | |
| return $suffixes[$lastDigit]; | |
| } else { | |
| return "th"; | |
| } | |
| } | |
| function dateSuffixReadable_v2(int $day) | |
| { | |
| if ($day < 1) { | |
| throw new \InvalidArgumentException("Days can never be negative."); | |
| } | |
| // Use a switch statement instead of multiple if/else statements | |
| switch ($lastDigit = $day % 10) { | |
| case 1: | |
| $suffix = "st"; | |
| break; | |
| case 2: | |
| $suffix = "nd"; | |
| break; | |
| case 3: | |
| $suffix = "rd"; | |
| break; | |
| default: | |
| $suffix = "th"; | |
| } | |
| if ($day === 11 || $day === 12) { | |
| return "th"; | |
| } | |
| return $suffix; | |
| } | |
| function test_day_suffix(callable $tested_function) | |
| { | |
| // Get the function name using reflection | |
| $reflection = new ReflectionFunction($tested_function); | |
| $implementationName = $reflection->getName(); | |
| // Test cases: array mapping days to their expected suffixes | |
| $testCases = [ | |
| 1 => 'st', | |
| 2 => 'nd', | |
| 3 => 'rd', | |
| 4 => 'th', | |
| 5 => 'th', | |
| 6 => 'th', | |
| 7 => 'th', | |
| 8 => 'th', | |
| 9 => 'th', | |
| 10 => 'th', | |
| 11 => 'th', | |
| 12 => 'th', | |
| 13 => 'th', | |
| 14 => 'th', | |
| 15 => 'th', | |
| 16 => 'th', | |
| 17 => 'th', | |
| 18 => 'th', | |
| 19 => 'th', | |
| 20 => 'th', | |
| 21 => 'st', | |
| 22 => 'nd', | |
| 23 => 'rd', | |
| 24 => 'th', | |
| 25 => 'th', | |
| 26 => 'th', | |
| 27 => 'th', | |
| 28 => 'th', | |
| 29 => 'th', | |
| 30 => 'th', | |
| 31 => 'st' | |
| ]; | |
| // Test each day from 1 to 31 | |
| for ($a = 1; $a <= 31; ++$a) { | |
| $suffix = $tested_function($a); | |
| $expectedSuffix = $testCases[$a]; | |
| $status = ($suffix === $expectedSuffix) ? "PASSED" : "FAILED"; | |
| echo "[$implementationName] $a: $a{$suffix} - $status (Expected: $a{$expectedSuffix})\n"; | |
| } | |
| } | |
| // Example function to test | |
| function date_suffix($day) { | |
| // Handle special cases for 11th, 12th, 13th | |
| if ($day >= 11 && $day <= 13) { | |
| return 'th'; | |
| } | |
| // Get the last digit of the day | |
| $lastDigit = $day % 10; | |
| switch ($lastDigit) { | |
| case 1: | |
| return 'st'; | |
| case 2: | |
| return 'nd'; | |
| case 3: | |
| return 'rd'; | |
| default: | |
| return 'th'; | |
| } | |
| } | |
| function date_suffix_v2(int $day) | |
| { | |
| // Handle special cases for 11th, 12th, 13th | |
| if ($day >= 11 && $day <= 13) { | |
| return 'th'; | |
| } | |
| // Get the last digit of the day | |
| $lastDigit = $day % 10; | |
| return match ($lastDigit) { | |
| 1 => 'st', | |
| 2 => 'nd', | |
| 3 => 'rd', | |
| default => 'th' | |
| }; | |
| } | |
| function date_suffix_v3(int $day): string | |
| { | |
| // Handle special cases for 11th, 12th, 13th | |
| if ($day >= 11 && $day <= 13) { | |
| return 'th'; | |
| } | |
| // Direct array lookup (most efficient) | |
| static $suffixes = [1 => 'st', 2 => 'nd', 3 => 'rd']; | |
| $lastDigit = $day % 10; | |
| return $suffixes[$lastDigit] ?? 'th'; | |
| } | |
| function date_suffix_v4(int $day): string | |
| { | |
| return match (true) { | |
| $day % 100 >= 11 && $day % 100 <= 13 => 'th', | |
| $day % 10 === 1 => 'st', | |
| $day % 10 === 2 => 'nd', | |
| $day % 10 === 3 => 'rd', | |
| default => 'th' | |
| }; | |
| } | |
| // Run the tests | |
| $implementations = [ | |
| // 'dateSuffixOriginal', | |
| // 'dateSuffixReadable', | |
| 'dateSuffixReadable_v2', | |
| // 'date_suffix', | |
| ]; | |
| foreach ($implementations as $implementation) { | |
| test_day_suffix($implementation); | |
| } | |
| function benchmark_functions(callable $firstImplementation, callable $secondImplementation, bool $shortTest = true) | |
| { | |
| // Get function names using reflection | |
| $reflection1 = new ReflectionFunction($firstImplementation); | |
| $funcName1 = $reflection1->getName(); | |
| $reflection2 = new ReflectionFunction($secondImplementation); | |
| $funcName2 = $reflection2->getName(); | |
| echo "Benchmarking functions: $funcName1 and $funcName2\n"; | |
| echo "------------------------------------------------------------\n"; | |
| // Benchmark settings | |
| $iterations = 1000000; // Number of iterations for each day | |
| if ($shortTest === true) { | |
| $days = [1, 2, 3, 4, 5, 11, 12, 21, 22, 23]; | |
| } else { | |
| $days = range(1, 31); | |
| } | |
| $timeData = []; | |
| foreach ($days as $day) { | |
| // Benchmark first implementation | |
| $start1 = microtime(true); | |
| for ($i = 0; $i < $iterations; $i++) { | |
| $firstImplementation($day); | |
| } | |
| $end1 = microtime(true); | |
| $timeData[$day]['func1'] = $end1 - $start1; | |
| // Benchmark second implementation | |
| $start2 = microtime(true); | |
| for ($i = 0; $i < $iterations; $i++) { | |
| $secondImplementation($day); | |
| } | |
| $end2 = microtime(true); | |
| $timeData[$day]['func2'] = $end2 - $start2; | |
| } | |
| // Calculate averages | |
| $avg1 = array_sum(array_column($timeData, 'func1')) / count($days); | |
| $avg2 = array_sum(array_column($timeData, 'func2')) / count($days); | |
| // Output results | |
| echo "Benchmark Results (over $iterations iterations per day, days 1-31)\n"; | |
| echo "------------------------------------------------------------\n"; | |
| echo "$funcName1 Average Time: " . number_format($avg1, 6) . " seconds\n"; | |
| echo "$funcName2 Average Time: " . number_format($avg2, 6) . " seconds\n"; | |
| echo "------------------------------------------------------------\n"; | |
| // Detailed results per day | |
| // Detailed results per day | |
| echo "\nDetailed Results Per Day:\n"; | |
| echo "Day | $funcName1 (s) | $funcName2 (s) | Difference ($funcName2 - $funcName1) | % Difference\n"; | |
| echo "----|---------------|---------------|--------------------------|------------\n"; | |
| $sumFunc1 = $sumFunc2 = $sumDiff = $sumPercent = 0.0; | |
| $cnt = count($days); // number of days | |
| foreach ($days as $day) { | |
| $func1 = $timeData[$day]['func1']; | |
| $func2 = $timeData[$day]['func2']; | |
| $diff = $func2 - $func1; | |
| $percent = $func1 != 0 ? ($diff / $func1) * 100 : 0.0; | |
| // keep running totals | |
| $sumFunc1 += $func1; | |
| $sumFunc2 += $func2; | |
| $sumDiff += $diff; | |
| $sumPercent += $percent; | |
| printf( | |
| " %2d | %10.6f | %10.6f | %10.6f | %8.2f%%\n", | |
| $day, | |
| $func1, | |
| $func2, | |
| $diff, | |
| $percent | |
| ); | |
| } | |
| echo "----|------------|------------|------------|------------\n"; | |
| // --- final average row ---------------------------------------------------- | |
| $avgFunc1 = $cnt ? $sumFunc1 / $cnt : 0.0; | |
| $avgFunc2 = $cnt ? $sumFunc2 / $cnt : 0.0; | |
| $avgDiff = $cnt ? $sumDiff / $cnt : 0.0; | |
| $avgPercent = $cnt ? $sumPercent / $cnt : 0.0; | |
| echo "Avg | "; | |
| printf( | |
| "%10.6f | %10.6f | %10.6f | %8.2f%%\n", | |
| $avgFunc1, | |
| $avgFunc2, | |
| $avgDiff, | |
| $avgPercent | |
| ); | |
| } | |
| benchmark_functions('dateSuffixOriginal', 'dateSuffixReadable'); | |
| benchmark_functions('dateSuffixReadable', 'dateSuffixReadable_v2'); | |
| benchmark_functions('dateSuffixReadable', 'date_suffix'); | |
| benchmark_functions('date_suffix', 'date_suffix_v2'); | |
| benchmark_functions('date_suffix_v2', 'date_suffix_v3'); | |
| benchmark_functions('date_suffix_v3', 'date_suffix_v4'); | |
| benchmark_functions('date_suffix_v3', 'date_suffix_v5'); | |
| benchmark_functions('dateSuffixOriginal', 'date_suffix_v5', shortTest: false); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment