Skip to content

Instantly share code, notes, and snippets.

@BobRay
Forked from bohwaz/php-8.1-strftime.php
Created January 11, 2022 21:07
Show Gist options
  • Select an option

  • Save BobRay/57c837ef28240ab9764643fb08fada16 to your computer and use it in GitHub Desktop.

Select an option

Save BobRay/57c837ef28240ab9764643fb08fada16 to your computer and use it in GitHub Desktop.

Revisions

  1. @bohwaz bohwaz revised this gist Dec 17, 2021. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion php-8.1-strftime.php
    Original file line number Diff line number Diff line change
    @@ -7,7 +7,7 @@
    * Note that output can be slightly different between libc sprintf and this function as it is using ICU.
    *
    * Usage:
    * use func \PHP81_BC\sprintf;
    * use func \PHP81_BC\strftime;
    * echo strftime('%A %e %B %Y %X', new \DateTime('2021-09-28 00:00:00'), 'fr_FR', 'Europe/Paris');
    *
    * Original use:
  2. @bohwaz bohwaz revised this gist Dec 17, 2021. 1 changed file with 20 additions and 45 deletions.
    65 changes: 20 additions & 45 deletions php-8.1-strftime.php
    Original file line number Diff line number Diff line change
    @@ -6,7 +6,7 @@
    * This provides a cross-platform alternative to strftime() for when it will be removed from PHP.
    * Note that output can be slightly different between libc sprintf and this function as it is using ICU.
    *
    * Usage:
    * Usage:
    * use func \PHP81_BC\sprintf;
    * echo strftime('%A %e %B %Y %X', new \DateTime('2021-09-28 00:00:00'), 'fr_FR', 'Europe/Paris');
    *
    @@ -18,8 +18,9 @@
    * @param integer|string|DateTime $timestamp Timestamp
    * @param string|\DateTimeZone $timezone Timezone
    * @return string
    * @author BohwaZ <https://bohwaz.net/>
    */
    function strftime(string $format, $timestamp = null, ?string $locale = null, $timezone = null): string
    function strftime(string $format, $timestamp = null, ?string $locale = null): string
    {
    if (null === $timestamp) {
    $timestamp = new \DateTime;
    @@ -31,31 +32,11 @@ function strftime(string $format, $timestamp = null, ?string $locale = null, $ti
    $timestamp = date_create('!' . $timestamp);
    }

    if (!is_object($timestamp) || !($timestamp instanceof \DateTimeInterface)) {
    if (!($timestamp instanceof \DateTimeInterface)) {
    throw new \InvalidArgumentException('$timestamp argument is neither a valid UNIX timestamp, a valid date-time string or a DateTime object.');
    }

    if (null === $timezone) {
    $timezone = date_default_timezone_get();
    }
    elseif (is_object($timezone) && $timezone instanceof \DateTimeZone) {
    // Valid
    }
    elseif (is_string($timezone)) {
    $tz = timezone_open($timezone);

    if (!$tz) {
    throw new \UnexpectedValueException('$timezone argument is invalid: ' . $timezone);
    }

    $timezone = $tz;
    unset($tz);
    }
    else {
    throw new \InvalidArgumentException('$timezone argument must be a string or an instance of DateTimeZone');
    }

    $locale = substr(strtolower($locale), 0, 5);
    $locale = substr($locale, 0, 5);

    $intl_formats = [
    '%a' => 'EEE', // An abbreviated textual representation of the day Sun through Sat
    @@ -67,40 +48,34 @@ function strftime(string $format, $timestamp = null, ?string $locale = null, $ti
    '%P' => 'aa', // lower-case 'am' or 'pm' based on the given time Example: am for 00:31, pm for 22:23
    ];

    $intl_formatter = function (\DateTimeInterface $timestamp, string $format) use ($intl_formats, $timezone, $locale) {
    $intl_formatter = function (\DateTimeInterface $timestamp, string $format) use ($intl_formats, $locale) {
    $tz = $timestamp->getTimezone();
    $date_type = IntlDateFormatter::FULL;
    $time_type = IntlDateFormatter::FULL;
    $pattern = '';

    // %c = Preferred date and time stamp based on locale
    // Example: Tue Feb 5 00:45:10 2009 for February 5, 2009 at 12:45:10 AM
    if ($format == '%c') {
    $dateFormat = new IntlDateFormatter($locale,
    IntlDateFormatter::LONG,
    IntlDateFormatter::SHORT,
    $timezone);
    $date_type = IntlDateFormatter::LONG;
    $time_type = IntlDateFormatter::SHORT;
    }
    // %x = Preferred date representation based on locale, without the time
    // Example: 02/05/09 for February 5, 2009
    elseif ($format == '%x') {
    $dateFormat = new IntlDateFormatter($locale,
    IntlDateFormatter::SHORT,
    IntlDateFormatter::NONE,
    $timezone);
    $date_type = IntlDateFormatter::SHORT;
    $time_type = IntlDateFormatter::NONE;
    }
    // Localized time format
    elseif ($format == '%X') {
    $dateFormat = new IntlDateFormatter($locale,
    IntlDateFormatter::NONE,
    IntlDateFormatter::MEDIUM,
    $timezone);
    $date_type = IntlDateFormatter::NONE;
    $time_type = IntlDateFormatter::MEDIUM;
    }
    else {
    $dateFormat = new IntlDateFormatter($locale,
    IntlDateFormatter::FULL,
    IntlDateFormatter::FULL,
    $timezone,
    IntlDateFormatter::GREGORIAN,
    $intl_formats[$format]);
    $pattern = $intl_formats[$format];
    }

    return $dateFormat->format($timestamp);
    return (new IntlDateFormatter($locale, $date_type, $time_type, $tz, null, $pattern))->format($timestamp);
    };

    // Same order as https://www.php.net/manual/en/function.strftime.php
    @@ -197,4 +172,4 @@ function strftime(string $format, $timestamp = null, ?string $locale = null, $ti

    $out = str_replace('%%', '%', $out);
    return $out;
    }
    }
  3. @bohwaz bohwaz revised this gist Dec 17, 2021. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion php-8.1-strftime.php
    Original file line number Diff line number Diff line change
    @@ -11,7 +11,7 @@
    * echo strftime('%A %e %B %Y %X', new \DateTime('2021-09-28 00:00:00'), 'fr_FR', 'Europe/Paris');
    *
    * Original use:
    * \setlocale('fr_FR.UTF-8');
    * \setlocale('fr_FR.UTF-8', LC_TIME);
    * echo \strftime('%A %e %B %Y %X', strtotime('2021-09-28 00:00:00'));
    *
    * @param string $format Date format
  4. @bohwaz bohwaz created this gist Dec 17, 2021.
    200 changes: 200 additions & 0 deletions php-8.1-strftime.php
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,200 @@
    <?php
    namespace PHP81_BC;

    /**
    * Locale-formatted strftime using IntlDateFormatter (PHP 8.1 compatible)
    * This provides a cross-platform alternative to strftime() for when it will be removed from PHP.
    * Note that output can be slightly different between libc sprintf and this function as it is using ICU.
    *
    * Usage:
    * use func \PHP81_BC\sprintf;
    * echo strftime('%A %e %B %Y %X', new \DateTime('2021-09-28 00:00:00'), 'fr_FR', 'Europe/Paris');
    *
    * Original use:
    * \setlocale('fr_FR.UTF-8');
    * echo \strftime('%A %e %B %Y %X', strtotime('2021-09-28 00:00:00'));
    *
    * @param string $format Date format
    * @param integer|string|DateTime $timestamp Timestamp
    * @param string|\DateTimeZone $timezone Timezone
    * @return string
    */
    function strftime(string $format, $timestamp = null, ?string $locale = null, $timezone = null): string
    {
    if (null === $timestamp) {
    $timestamp = new \DateTime;
    }
    elseif (is_numeric($timestamp)) {
    $timestamp = date_create('@' . $timestamp);
    }
    elseif (is_string($timestamp)) {
    $timestamp = date_create('!' . $timestamp);
    }

    if (!is_object($timestamp) || !($timestamp instanceof \DateTimeInterface)) {
    throw new \InvalidArgumentException('$timestamp argument is neither a valid UNIX timestamp, a valid date-time string or a DateTime object.');
    }

    if (null === $timezone) {
    $timezone = date_default_timezone_get();
    }
    elseif (is_object($timezone) && $timezone instanceof \DateTimeZone) {
    // Valid
    }
    elseif (is_string($timezone)) {
    $tz = timezone_open($timezone);

    if (!$tz) {
    throw new \UnexpectedValueException('$timezone argument is invalid: ' . $timezone);
    }

    $timezone = $tz;
    unset($tz);
    }
    else {
    throw new \InvalidArgumentException('$timezone argument must be a string or an instance of DateTimeZone');
    }

    $locale = substr(strtolower($locale), 0, 5);

    $intl_formats = [
    '%a' => 'EEE', // An abbreviated textual representation of the day Sun through Sat
    '%A' => 'EEEE', // A full textual representation of the day Sunday through Saturday
    '%b' => 'MMM', // Abbreviated month name, based on the locale Jan through Dec
    '%B' => 'MMMM', // Full month name, based on the locale January through December
    '%h' => 'MMM', // Abbreviated month name, based on the locale (an alias of %b) Jan through Dec
    '%p' => 'aa', // UPPER-CASE 'AM' or 'PM' based on the given time Example: AM for 00:31, PM for 22:23
    '%P' => 'aa', // lower-case 'am' or 'pm' based on the given time Example: am for 00:31, pm for 22:23
    ];

    $intl_formatter = function (\DateTimeInterface $timestamp, string $format) use ($intl_formats, $timezone, $locale) {
    // %c = Preferred date and time stamp based on locale
    // Example: Tue Feb 5 00:45:10 2009 for February 5, 2009 at 12:45:10 AM
    if ($format == '%c') {
    $dateFormat = new IntlDateFormatter($locale,
    IntlDateFormatter::LONG,
    IntlDateFormatter::SHORT,
    $timezone);
    }
    // %x = Preferred date representation based on locale, without the time
    // Example: 02/05/09 for February 5, 2009
    elseif ($format == '%x') {
    $dateFormat = new IntlDateFormatter($locale,
    IntlDateFormatter::SHORT,
    IntlDateFormatter::NONE,
    $timezone);
    }
    // Localized time format
    elseif ($format == '%X') {
    $dateFormat = new IntlDateFormatter($locale,
    IntlDateFormatter::NONE,
    IntlDateFormatter::MEDIUM,
    $timezone);
    }
    else {
    $dateFormat = new IntlDateFormatter($locale,
    IntlDateFormatter::FULL,
    IntlDateFormatter::FULL,
    $timezone,
    IntlDateFormatter::GREGORIAN,
    $intl_formats[$format]);
    }

    return $dateFormat->format($timestamp);
    };

    // Same order as https://www.php.net/manual/en/function.strftime.php
    $translation_table = [
    // Day
    '%a' => $intl_formatter,
    '%A' => $intl_formatter,
    '%d' => 'd',
    '%e' => 'j',
    '%j' => function ($timestamp) {
    // Day number in year, 001 to 366
    return sprintf('%03d', $timestamp->format('z')+1);
    },
    '%u' => 'N',
    '%w' => 'w',

    // Week
    '%U' => function ($timestamp) {
    // Number of weeks between date and first Sunday of year
    $day = new \DateTime(sprintf('%d-01 Sunday', $timestamp->format('Y')));
    return intval(($timestamp->format('z') - $day->format('z')) / 7);
    },
    '%W' => function ($timestamp) {
    // Number of weeks between date and first Monday of year
    $day = new \DateTime(sprintf('%d-01 Monday', $timestamp->format('Y')));
    return intval(($timestamp->format('z') - $day->format('z')) / 7);
    },
    '%V' => 'W',

    // Month
    '%b' => $intl_formatter,
    '%B' => $intl_formatter,
    '%h' => $intl_formatter,
    '%m' => 'm',

    // Year
    '%C' => function ($timestamp) {
    // Century (-1): 19 for 20th century
    return (int) $timestamp->format('Y') / 100;
    },
    '%g' => function ($timestamp) {
    return substr($timestamp->format('o'), -2);
    },
    '%G' => 'o',
    '%y' => 'y',
    '%Y' => 'Y',

    // Time
    '%H' => 'H',
    '%k' => 'G',
    '%I' => 'h',
    '%l' => 'g',
    '%M' => 'i',
    '%p' => $intl_formatter, // AM PM (this is reversed on purpose!)
    '%P' => $intl_formatter, // am pm
    '%r' => 'G:i:s A', // %I:%M:%S %p
    '%R' => 'H:i', // %H:%M
    '%S' => 's',
    '%X' => $intl_formatter,// Preferred time representation based on locale, without the date

    // Timezone
    '%z' => 'O',
    '%Z' => 'T',

    // Time and Date Stamps
    '%c' => $intl_formatter,
    '%D' => 'm/d/Y',
    '%F' => 'Y-m-d',
    '%s' => 'U',
    '%x' => $intl_formatter,
    ];

    $out = preg_replace_callback('/(?<!%)(%[a-zA-Z])/', function ($match) use ($translation_table, $timestamp) {
    if ($match[1] == '%n') {
    return "\n";
    }
    elseif ($match[1] == '%t') {
    return "\t";
    }

    if (!isset($translation_table[$match[1]])) {
    throw new \InvalidArgumentException(sprintf('Format "%s" is unknown in time format', $match[1]));
    }

    $replace = $translation_table[$match[1]];

    if (is_string($replace)) {
    return $timestamp->format($replace);
    }
    else {
    return $replace($timestamp, $match[1]);
    }
    }, $format);

    $out = str_replace('%%', '%', $out);
    return $out;
    }