Skip to content

Instantly share code, notes, and snippets.

@ve3
Last active June 19, 2023 12:33
Show Gist options
  • 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);
@Daijobou
Copy link

Daijobou commented Mar 27, 2022

The script is a great helper! Thank you.
%b to M MM MMM MMMM is maybe not the best choice. In german MMM is "März" and not "Mär" or "Sept." (with dot) and not "Sep".
With L LL LLL LLLL its working like expected, LLL is only three letters for month.

And "E" should be "ccc"? In german "E" is not right. Its again with a dot.

See https://unicode-org.github.io/icu/userguide/format_parse/datetime/#date-field-symbol-table

@ve3
Copy link
Author

ve3 commented Mar 27, 2022

%b to M MM MMM MMMM is maybe not the best choice. In german MMM is "März" and not "Mär" or "Sept." (with dot) and not "Sep". With L LL LLL LLLL its working like expected, only three letters for month. See https://unicode-org.github.io/icu/userguide/format_parse/datetime/#date-field-symbol-table

Yes, you're welcome to recommended in your locale. 👍 But in my code it is based on Thai locale that's why it is just like that. (see code commented on line 8.)
Everyone can recommended anything based on your locale here. And thank you very much for this.

@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