Skip to content

Instantly share code, notes, and snippets.

@ve3
Last active June 19, 2023 12:33
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ve3/a3b7924a85c3286b554f9e636b919883 to your computer and use it in GitHub Desktop.
Save ve3/a3b7924a85c3286b554f9e636b919883 to your computer and use it in GitHub Desktop.
Convert strftime format to IntlDateFormatter pattern.
<?php
/**
* PHP `strftime()` format to `IntlDateFormatter()` pattern converter.
*
* PHP `strftime()` is deprecated since v 8.1. They recommended to use `IntlDateFormatter()` instead.
* However `IntlDateFormatter()` pattern does not fully supported all format that `strftime()` does.
*
* Run this file to get the result of most close pattern to `strftime()` based on Thai locale.
*
* @license MIT
*/
$locale = ($_POST['locale'] ?? 'en_US');
setlocale(LC_ALL, $locale);
// replace from `strftime` format to `IntlDateFormatter` pattern.
// based on Thai locale.
$replaces = [
'%a' => 'E',
'%A' => 'EEEE',
'%d' => 'dd',
'%e' => 'd',
'%j' => 'D',
'%u' => 'e',// not 100% correct
'%w' => 'c',// not 100% correct
'%U' => 'w',
'%V' => 'ww',// not 100% correct
'%W' => 'w',// not 100% correct
'%b' => 'MMM',
'%B' => 'MMMM',
'%h' => 'MMM',// alias of %b
'%m' => 'MM',
'%C' => 'yy',// no replace for this
'%g' => 'yy',// no replace for this
'%G' => 'Y',// not 100% correct
'%y' => 'yy',
'%Y' => 'yyyy',
'%H' => 'HH',
'%k' => 'H',
'%I' => 'hh',
'%l' => 'h',
'%M' => 'mm',
'%p' => 'a',
'%P' => 'a',// no replace for this
'%r' => 'hh:mm:ss a',
'%R' => 'HH:mm',
'%S' => 'ss',
'%T' => 'HH:mm:ss',
'%X' => 'HH:mm:ss',// no replace for this
'%z' => 'ZZ',
'%Z' => 'v',// no replace for this
'%c' => 'd/M/YYYY HH:mm:ss',// Buddhist era not converted.
'%D' => 'MM/dd/yy',
'%F' => 'yyyy-MM-dd',
'%s' => '',// no replace for this
'%x' => 'd/MM/yyyy',// Buddhist era not converted.
'%n' => "\n",
'%t' => "\t",
'%%' => '%',
];
$format = ($_POST['format'] ?? '');
$calendar = null;
if (!empty($format)) {
$pattern = $format;
// replace 1 single quote that is not following visible character or single quote and not follow by single quote or word or number.
// example: '
// replace with 2 single quotes. example: ''
$pattern = preg_replace('/(?<![\'\S])(\')(?![\'\w])/u', "'$1", $pattern);
// replace 1 single quote that is not following visible character or single quote and follow by word.
// example: 'xx
// replace with 2 single quotes. example: ''xx
$pattern = preg_replace('/(?<![\'\S])(\')(\w+)/u', "'$1$2", $pattern);
// replace 1 single quote that is following word (a-z 0-9) and not follow by single quote.
// example: xx'
// replace with 2 single quotes. example: xx''
$pattern = preg_replace('/([\w]+)(\')(?!\')/u', "$1'$2", $pattern);
// replace a-z (include upper case) that is not following %. example xxx.
// replace with wrap single quote. example: 'xxx'.
$pattern = preg_replace('/(?<![%a-zA-Z])([a-zA-Z]+)/u', "'$1$2'", $pattern);
// escape %%x with '%%x'.
$pattern = preg_replace('/(%%[a-zA-Z]+)/u', "'$1'", $pattern);
foreach ($replaces as $strftime => $intl) {
$pattern = preg_replace('/(?<!%)(' . $strftime . ')/u', $intl, $pattern);
}// endforeach;
unset($intl, $strftime);
}
if (class_exists('IntlDateFormatter')) {
$tz = null;
$calendar = ($_POST['calendar'] ?? null);
if ($calendar === 'default') {
$calendar = null;
} elseif ($calendar === 'traditional') {
$calendar = \IntlDateFormatter::TRADITIONAL;
}
$IntlDateFormatter = new \IntlDateFormatter($locale, \IntlDateFormatter::FULL, \IntlDateFormatter::FULL, $tz, $calendar);
$defaultPattern = $IntlDateFormatter->getPattern();
unset($tz);
}
// set locales for select box.
$locales = [
'en_US.utf-8',
'en_US.utf8',
'en_us.utf-8',
'en_us.utf8',
'en-US.utf-8',
'en-US.utf8',
'en-us.utf-8',
'en-us.utf8',
'en.utf-8',
'en.utf8',
'en_US',
'en_us',
'en-US',
'en-us',
'en',
'th_TH.utf-8',
'th_TH.utf8',
'th_th.utf-8',
'th_th.utf8',
'th-TH.utf-8',
'th-TH.utf8',
'th-th.utf-8',
'th-th.utf8',
'th.utf-8',
'th.utf8',
'th_TH',
'th_th',
'th-TH',
'th-th',
'th',
];
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>strftime to IntlDateFormatter format converter</title>
<style type="text/css">
code {
background-color: #eee;
padding: 2px 5px;
}
form {
margin: 0 0 20px;
}
form > div {
margin: 0 0 10px;
}
pre {
background-color: #eee;
padding: 10px;
}
table {
border: 1px solid #ccc;
border-collapse: collapse;
width: 100%;
}
table th,
table td {
border: 1px solid #ccc;
text-align: left;
padding: 5px;
}
table tr.different-result {
background-color: rgba(255, 218, 148, 0.4);
}
</style>
</head>
<bodY>
<form method="post">
<div>
Locale:
<select name="locale">
<?php
if (isset($locales) && is_array($locales)) {
foreach ($locales as $eachLocale) {
echo '<option value="' . $eachLocale . '"';
if ($locale === $eachLocale) {
echo ' selected';
}
echo '>' . $eachLocale . '</option>' . PHP_EOL;
}// endforeach;
unset($eachLocale);
}
?>
</select>
</div>
<div>
Calendar:
<select name="calendar">
<option value="default"<?php if ($calendar === 'default') {echo ' selected';} ?>>Default</option>
<option value="traditional"<?php if ($calendar === 'traditional') {echo ' selected';} ?>>Traditional</option>
</select>
</div>
<div>
<code>strftime()</code> format:
<input type="text" name="format" value="<?php echo htmlspecialchars($format, ENT_QUOTES); ?>" style="width: 300px;">
</div>
<button type="submit">Submit</button>
</form>
<?php
if (isset($pattern)) {
?>
<h3>Converted result</h3>
<pre><?php
echo htmlspecialchars($pattern, ENT_QUOTES);
?></pre>
<h3>Execute</h3>
<?php if (function_exists('strftime')) { ?>
<p><code>strftime('<?php echo $format; ?>')</code>: <strong><?php echo htmlspecialchars(@strftime($format), ENT_QUOTES); ?></strong></p>
<?php } ?>
<?php
if (isset($IntlDateFormatter)) {
$IntlDateFormatter->setPattern($pattern);
?>
<p><code>IntlDateFormatter::setPattern('<?php echo $pattern; ?>')</code>: <strong><?php echo htmlspecialchars($IntlDateFormatter->format(time()), ENT_QUOTES); ?></strong></p>
<?php
}
?>
<?php
}// endif;
?>
<p>Current locale: <code><?php echo $locale; ?></code></p>
<?php
if (isset($IntlDateFormatter)) {
$IntlDateFormatter->setPattern($defaultPattern);
?>
<p>
Default <code>IntlDateFormatter()</code> pattern (<code><?php echo $defaultPattern; ?></code>): <strong><?php echo $IntlDateFormatter->format(time()); ?></strong>
</p>
<?php
}
?>
<table>
<thead>
<tr>
<th><code>strftime()</code> format</th>
<th><code>strftime()</code> result</th>
<th><code>IntlDateFormatter()</code> pattern</th>
<th><code>IntlDateFormatter()</code> result</th>
</tr>
</thead>
<tbody>
<?php
foreach ($replaces as $strftime => $intl) {
if (function_exists('strftime')) {
$strftimeResult = @strftime($strftime);
}
if (isset($IntlDateFormatter)) {
$IntlDateFormatter->setPattern($intl);
$intlDResult = $IntlDateFormatter->format(time());
}
if (isset($strftimeResult) && isset($intlDResult)) {
if ($strftimeResult != $intlDResult) {
$rowDif = true;
}
}
?>
<tr<?php if (isset($rowDif)) { echo ' class="different-result"'; } ?>>
<td><code><?php echo htmlspecialchars($strftime, ENT_QUOTES); ?></code></td>
<td><?php
if (isset($strftimeResult)) {
echo htmlspecialchars($strftimeResult, ENT_QUOTES);
}
?></td>
<td><code><?php echo htmlspecialchars($intl, ENT_QUOTES); ?></code></td>
<td><?php
if (isset($intlDResult)) {
echo htmlspecialchars($intlDResult, ENT_QUOTES);
}
?></td>
</tr>
<?php
unset($intlDResult, $rowDif, $strftimeResult);
}// endforeach;
unset($intl, $strftime);
?>
</tbody>
</table>
<p>Reference:
<a href="https://www.php.net/manual/en/function.strftime.php" target="strftome">strftime format.</a>
<a href="https://unicode-org.github.io/icu/userguide/format_parse/datetime/" target="ICU">ICU format</a>
</p>
</bodY>
</html>
<?php
unset($IntlDateFormatter, $replaces);
@boindil
Copy link

boindil commented Jul 29, 2022

First: Thank you, this saved a ton of work!

%C seems completely wrong since there is no century in ICU date formats.

I've done this for german and have come up with the following (whilst not 100% the same, it should make sense). Weirdly enough I noticed some strange behaviour with %s - strftime is exactly 1 hour behind. That might have something to do with winter-/summertime here, but not sure. Getting the actual correct timestamp is better anyways, so I'll just leave it like this.

<?php
/**
 * PHP `strftime()` format to `IntlDateFormatter()` pattern converter.
 * 
 * PHP `strftime()` is deprecated since v 8.1. They recommended to use `IntlDateFormatter()` instead.
 * However `IntlDateFormatter()` pattern does not fully supported all format that `strftime()` does.
 * 
 * Run this file to get the result of most close pattern to `strftime()` based on Thai locale.
 * 
 * @license MIT
 */


$locale = ($_POST['locale'] ?? 'de_DE.UTF-8');
setlocale(LC_ALL, $locale);

// replace from `strftime` format to `IntlDateFormatter` pattern.
// based on German locale.
$replaces = [
    '%a' => 'ccc',
    '%A' => 'cccc',
    '%d' => 'dd',
    '%e' => 'd',
    '%j' => 'D',
    '%u' => 'e',// not 100% correct
    '%w' => 'c',// not 100% correct
    '%U' => 'w',
    '%V' => 'ww',// not 100% correct
    '%W' => 'w',// not 100% correct
    '%b' => 'LLL',
    '%B' => 'LLLL',
    '%h' => 'LLL',// alias of %b
    '%m' => 'LL',
    '%C' => '\'{{century}}\'',// no replace for this
    '%g' => 'yy',// no replace for this
    '%G' => 'Y',// not 100% correct
    '%y' => 'yy',
    '%Y' => 'yyyy',
    '%H' => 'HH',
    '%k' => 'H',
    '%I' => 'hh',
    '%l' => 'h',
    '%M' => 'mm',
    '%p' => 'a',
    '%P' => 'a',// no replace for this
    '%r' => 'hh:mm:ss a',
    '%R' => 'HH:mm',
    '%S' => 'ss',
    '%T' => 'HH:mm:ss',
    '%X' => 'HH:mm:ss',// no replace for this
    '%z' => 'ZZ',
    '%Z' => 'zzz',// no replace for this
    '%c' => 'ccc d LLL YYYY HH:mm:ss zzz',// Buddhist era not converted.
    '%D' => 'MM/dd/yy',
    '%F' => 'yyyy-MM-dd',
    '%s' => '\'{{timestamp}}\'',// no replace for this
    '%x' => 'd.MM.yyyy',// Buddhist era not converted.
    '%n' => "\n",
    '%t' => "\t",
    '%%' => '%',
];
$format = ($_POST['format'] ?? '');
$calendar = null;

if (!empty($format)) {
    $pattern = $format;
    // replace 1 single quote that is not following visible character or single quote and not follow by single quote or word or number.
    // example: '
    // replace with 2 single quotes. example: ''
    $pattern = preg_replace('/(?<![\'\S])(\')(?![\'\w])/u', "'$1", $pattern);
    // replace 1 single quote that is not following visible character or single quote and follow by word.
    // example: 'xx
    // replace with 2 single quotes. example: ''xx
    $pattern = preg_replace('/(?<![\'\S])(\')(\w+)/u', "'$1$2", $pattern);
    // replace 1 single quote that is following word (a-z 0-9) and not follow by single quote.
    // example: xx'
    // replace with 2 single quotes. example: xx''
    $pattern = preg_replace('/([\w]+)(\')(?!\')/u', "$1'$2", $pattern);
    // replace a-z (include upper case) that is not following %. example xxx.
    // replace with wrap single quote. example: 'xxx'.
    $pattern = preg_replace('/(?<![%a-zA-Z])([a-zA-Z]+)/u', "'$1$2'", $pattern);

    // escape %%x with '%%x'.
    $pattern = preg_replace('/(%%[a-zA-Z]+)/u', "'$1'", $pattern);

    foreach ($replaces as $strftime => $intl) {
        $pattern = preg_replace('/(?<!%)(' . $strftime . ')/u', $intl, $pattern);
    }// endforeach;
    unset($intl, $strftime);
}

if (class_exists('IntlDateFormatter')) {
    $tz = null;
    $calendar = ($_POST['calendar'] ?? null);
    if ($calendar === 'default') {
        $calendar = null;
    } elseif ($calendar === 'traditional') {
        $calendar = \IntlDateFormatter::TRADITIONAL;
    }
    $IntlDateFormatter = new \IntlDateFormatter($locale, \IntlDateFormatter::FULL, \IntlDateFormatter::FULL, $tz, $calendar);
    $defaultPattern = $IntlDateFormatter->getPattern();
    unset($tz);
}

// set locales for select box.
$locales = [
	'de_DE.UTF-8'
];
?>
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>strftime to IntlDateFormatter format converter</title>
        <style type="text/css">
            code {
                background-color: #eee;
                padding: 2px 5px;
            }
            form {
                margin: 0 0 20px;
            }
            form > div {
                margin: 0 0 10px;
            }
            pre {
                background-color: #eee;
                padding: 10px;
            }
            table {
                border: 1px solid #ccc;
                border-collapse: collapse;
                width: 100%;
            }
            table th,
            table td {
                border: 1px solid #ccc;
                text-align: left;
                padding: 5px;
            }
            table tr.different-result {
                background-color: rgba(255, 218, 148, 0.4);
            }
        </style>
    </head>
    <bodY>
        <form method="post">
            <div>
                Locale:
                <select name="locale">
                    <?php
                    if (isset($locales) && is_array($locales)) {
                        foreach ($locales as $eachLocale) {
                            echo '<option value="' . $eachLocale . '"';
                            if ($locale === $eachLocale) {
                                echo ' selected';
                            }
                            echo '>' . $eachLocale . '</option>' . PHP_EOL;
                        }// endforeach;
                        unset($eachLocale);
                    }
                    ?>
                </select>
            </div>
            <div>
                Calendar:
                <select name="calendar">
                    <option value="default"<?php if ($calendar === 'default') {echo ' selected';} ?>>Default</option>
                    <option value="traditional"<?php if ($calendar === 'traditional') {echo ' selected';} ?>>Traditional</option>
                </select>
            </div>
            <div>
                <code>strftime()</code> format: 
                <input type="text" name="format" value="<?php echo htmlspecialchars($format, ENT_QUOTES); ?>" style="width: 300px;">
            </div>
            <button type="submit">Submit</button>
        </form>

        <?php 
        if (isset($pattern)) {
        ?>
        <h3>Converted result</h3>
        <pre><?php 
        echo htmlspecialchars($pattern, ENT_QUOTES); 
        ?></pre>
        <h3>Execute</h3>
        <?php if (function_exists('strftime')) { ?>
        <p><code>strftime('<?php echo $format; ?>')</code>: <strong><?php echo htmlspecialchars(@strftime($format), ENT_QUOTES); ?></strong></p>
        <?php } ?>
        <?php 
        if (isset($IntlDateFormatter)) { 
            $IntlDateFormatter->setPattern($pattern);
        ?>
        <p><code>IntlDateFormatter::setPattern('<?php echo $pattern; ?>')</code>: <strong><?php echo htmlspecialchars($IntlDateFormatter->format(time()), ENT_QUOTES); ?></strong></p>
        <?php 
        } 
        ?>
        <?php
        }// endif;
        ?>

        <p>Current locale: <code><?php echo $locale; ?></code></p>
        <?php 
        if (isset($IntlDateFormatter)) {
            $IntlDateFormatter->setPattern($defaultPattern);
        ?>
        <p>
            Default <code>IntlDateFormatter()</code> pattern (<code><?php echo $defaultPattern; ?></code>): <strong><?php echo $IntlDateFormatter->format(time()); ?></strong>
        </p>
        <?php
        }
        ?>
        <table>
            <thead>
                <tr>
                    <th><code>strftime()</code> format</th>
                    <th><code>strftime()</code> result</th>
                    <th><code>IntlDateFormatter()</code> pattern</th>
                    <th><code>IntlDateFormatter()</code> result</th>
                </tr>
            </thead>
            <tbody>
                <?php
                foreach ($replaces as $strftime => $intl) {
                    $currentTime = time();
                    
                    
                    
                    if (function_exists('strftime')) {
                        $strftimeResult = @strftime($strftime, $currentTime);
                    }
                    if (isset($IntlDateFormatter)) {
                        $IntlDateFormatter->setPattern($intl);
                        $intlDResult = $IntlDateFormatter->format($currentTime);
                        
                        $matchcount = preg_match_all("|\{\{(.*)\}\}|U", $intlDResult, $matches, PREG_PATTERN_ORDER);
                        if((int)$matchcount > 0) {
                            foreach($matches[1] as $match) {
                                switch($match) {
                                    case 'timestamp':
                                        $intlDResult = str_replace('{{timestamp}}', $currentTime, $intlDResult);
                                        break;
                                    
                                    case 'century':
                                        $IntlDateFormatter->setPattern('YYYY');
                                        $intlDResult = str_replace('{{century}}', substr($IntlDateFormatter->format($currentTime), 0, 2), $intlDResult);
                                        break;
                                }
                            }
                        }
                    }
                    if (isset($strftimeResult) && isset($intlDResult)) {
                        if ($strftimeResult != $intlDResult) {
                            $rowDif = true;
                        }
                    }
                ?>
                <tr<?php if (isset($rowDif)) { echo ' class="different-result"'; } ?>>
                    <td><code><?php echo htmlspecialchars($strftime, ENT_QUOTES); ?></code></td>
                    <td><?php
                    if (isset($strftimeResult)) {
                        echo htmlspecialchars($strftimeResult, ENT_QUOTES);
                    }
                    ?></td>
                    <td><code><?php echo htmlspecialchars($intl, ENT_QUOTES); ?></code></td>
                    <td><?php 
                    if (isset($intlDResult)) {
                        echo htmlspecialchars($intlDResult, ENT_QUOTES);
                    }
                    ?></td>
                </tr>
                <?php
                    unset($intlDResult, $rowDif, $strftimeResult);
                }// endforeach;
                unset($intl, $strftime);
                ?>
            </tbody>
        </table>

        <p>Reference:
            <a href="https://www.php.net/manual/en/function.strftime.php" target="strftome">strftime format.</a>
            <a href="https://unicode-org.github.io/icu/userguide/format_parse/datetime/" target="ICU">ICU format</a>
        </p>
    </bodY>
</html>
<?php

unset($IntlDateFormatter, $replaces);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment