Skip to content

Instantly share code, notes, and snippets.

@hopeseekr
Created September 24, 2025 13:39
Show Gist options
  • Select an option

  • Save hopeseekr/3ac57243ad8ed282ad0075cb2fe4fa5b to your computer and use it in GitHub Desktop.

Select an option

Save hopeseekr/3ac57243ad8ed282ad0075cb2fe4fa5b to your computer and use it in GitHub Desktop.
nth Day PHP + benchmarks
<?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