I thought I came up with the best solution for a workaround for PHP strftime() on Windows, and then a user reported trouble.
Windows has some limitations to strftime() and furthermore it does not support UTF-8. So I wrote a workaround for both of these issues but stumble upon charset problems.
This is what my code looks like:
function strftimefixed($format, $timestamp=null) {
if ($timestamp === null) $timestamp = time();
if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN') {
$format = preg_replace('#(?<!%)((?:%%)*)%e#', '\1%#d', $format); // Don't mind this line
}
return mb_convert_encoding(strftime($format, $timestamp), 'UTF-8', 'auto'); // Charset is the problem
}
Error message
Warning: mb_convert_encoding(): Unable to detect character encoding
As you can see 'auto' fails to identify encoding. The user is on a Czech windows installation, but I can't hardcode it to 'ISO-8859-2' as that will only help czech users and not other end-users who don't have the slightest idea what a Windows locale is or by which charset.
So what is the best possible solution for making some universal awesome workaround?
Note: The format is not the problem here. It could be anything, like %b %e %Y %H:%M. The charset identification is the problem.
According to the manual, the issues that strftime() has on Windows include:
Not all conversion specifiers may be supported by your C library, in which case they will not be supported by PHP's strftime(). Additionally, not all platforms support negative timestamps, so your date range may be limited to no earlier than the Unix epoch. This means that %e, %T, %R and, %D (and possibly others) - as well as dates prior to Jan 1, 1970 - will not work on Windows, some Linux distributions, and a few other operating systems.
Your code uses undisclosed parameters ($format
and $timestamp
) so what worries you is rather unclear.
However, the error message you're getting is related to text encoding, not date handling per-se. It's just impossible to handle text properly for unknown encodings, but you can choose the encoding produced by strftime()
by selecting an appropriate locale:
strftime
— Format a local time/date according to locale settingsFormat the time and/or date according to locale settings. Month and weekday names and other language-dependent strings respect the current locale set with setlocale().
Beware though that locale handling is unreliable in certain platforms such as thread-safe builds on Windows. If that's the case, you might need to dump strftime()
entirely and choose a proper localisation tool.
Of course, you can always leave the default locale and just try to guess encoding from it but you'll probably need to maintain a database.
The best solution I could come up with was to detect Windows locale charset and work it out from there.
function strftimefixed($format, $timestamp=null) {
if ($timestamp === null) $timestamp = time();
if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN') {
$format = preg_replace('#(?<!%)((?:%%)*)%e#', '\1%#d', $format);
$locale = setlocale(LC_TIME, 0);
switch(true) {
case (preg_match('#\.(874|1256)$#', $locale, $matches)):
return iconv('UTF-8', "$locale_charset", strftime($format, $timestamp));
case (preg_match('#\.1250$#', $locale)):
return mb_convert_encoding(strftime($format, $timestamp), 'UTF-8', 'ISO-8859-2');
case (preg_match('#\.(1251|1252|1254)$#', $locale, $matches)):
return mb_convert_encoding(strftime($format, $timestamp), 'UTF-8', 'Windows-'.$matches[1]);
case (preg_match('#\.(1255|1256)$#', $locale, $matches)):
return iconv('UTF-8', "Windows-{$matches[1]}", strftime($format, $timestamp));
case (preg_match('#\.1257$#', $locale)):
return mb_convert_encoding(strftime($format, $timestamp), 'UTF-8', 'ISO-8859-13');
case (preg_match('#\.(932|936|950)$#', $locale)):
return mb_convert_encoding(strftime($format, $timestamp), 'UTF-8', 'CP'.$matches[1]);
case (preg_match('#\.(949)$#', $locale)):
return mb_convert_encoding(strftime($format, $timestamp), 'UTF-8', 'EUC-KR');
default:
trigger_error("Unknown charset for system locale ($locale)", E_USER_NOTICE);
return mb_convert_encoding(strftime($format, $timestamp), 'UTF-8', 'auto');
}
}
return strftime($format, $timestamp);
}