@@ -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 ;
}