我试图创建一个函数,可以获得不同格式和(语言)的日期,并将其转换为DD-MM-YYYY
。例如,这个函数可以得到22 Fev 2011
(葡萄牙语)和22 Feb 2011
(英语)。对于两者,它都应该返回22-02-2011
。
让我们假设我有有限的语言,所以我可以有某种数据结构来携带月份和这些月份的缩写。我的问题是:"假设strtotime适用于英语字符串,那么创建一个函数的最佳选择是什么?该函数给定X语言中的日期字符串,返回格式为DD-MM-YYY
的日期?"
日期/时间操作确实很麻烦。:)
解决方案
1。语言环境数据
我刚刚访问了Yii的svn存储库,无耻地复制了这些:
$locales = array(
'pt' => array(
'monthNames' => array(
'wide' => array (
1 => 'janeiro',
2 => 'fevereiro',
3 => 'marco',
4 => 'abril',
5 => 'maio',
6 => 'junho',
7 => 'julho',
8 => 'agosto',
9 => 'setembro',
10 => 'outubro',
11 => 'novembro',
12 => 'dezembro',
),
'abbreviated' => array(
1 => 'jan',
2 => 'fev',
3 => 'mar',
4 => 'abr',
5 => 'mai',
6 => 'jun',
7 => 'jul',
8 => 'ago',
9 => 'set',
10 => 'out',
11 => 'nov',
12 => 'dez',
),
),
'weekDayNames' => array(
'wide' => array (
0 => 'domingo',
1 => 'segunda-feira',
2 => 'terca-feira',
3 => 'quarta-feira',
4 => 'quinta-feira',
5 => 'sexta-feira',
6 => 'sabado',
),
'abbreviated' => array(
0 => 'dom',
1 => 'seg',
2 => 'ter',
3 => 'qua',
4 => 'qui',
5 => 'sex',
6 => 'sab',
),
),
),
'en' => array(
'monthNames' => array(
'wide' => array (
1 => 'January',
2 => 'February',
3 => 'March',
4 => 'April',
5 => 'May',
6 => 'June',
7 => 'July',
8 => 'August',
9 => 'September',
10 => 'October',
11 => 'November',
12 => 'December',
),
'abbreviated' => array(
1 => 'Jan',
2 => 'Feb',
3 => 'Mar',
4 => 'Apr',
5 => 'May',
6 => 'Jun',
7 => 'Jul',
8 => 'Aug',
9 => 'Sep',
10 => 'Oct',
11 => 'Nov',
12 => 'Dec',
),
),
'weekDayNames' => array(
'wide' => array (
0 => 'Sunday',
1 => 'Monday',
2 => 'Tuesday',
3 => 'Wednesday',
4 => 'Thursday',
5 => 'Friday',
6 => 'Saturday',
),
'abbreviated' => array(
0 => 'Sun',
1 => 'Mon',
2 => 'Tue',
3 => 'Wed',
4 => 'Thu',
5 => 'Fri',
6 => 'Sat',
),
),
),
);
2。暴力破解问题
假设你的应用程序没有把所有的时间都花在转换人类可读的日期上,那么速度应该不是真正的问题。因此,我选择了一个具有良好可扩展性的短期解决方案,代价是不尝试优化和稍微模糊。
function strtotimeIntl($timeString, $locales, $normalizeCallback = 'strtolower') {
// STEP 1 -- TRY ENGLISH
$ts = strtotime($timeString);
if ($ts !== false) {
return $ts;
}
// STEP 2 -- BRUTE FORCE
$english = $locales['en'];
foreach($locales as $code => $localeInfo) {
if($code == 'en') {
continue; // don't try english again
}
$subject = $normalizeCallback($timeString); // reset
// These reflect the structure of $localeInfo
$replacementKeys = array(
array('monthNames', 'wide'),
array('monthNames', 'abbreviated'),
array('weekDayNames', 'wide'),
array('weekDayNames', 'abbreviated'),
);
// Replace everything present in the string with english equivalents
foreach($replacementKeys as $keys) {
$map = array_map($normalizeCallback, $localeInfo[$keys[0]][$keys[1]]);
$flipped = array_flip($map);
$subject = preg_replace('/'b('.implode('|', $map).')'b/e',
'$english[$keys[0]][$keys[1]][$flipped[''$1'']]',
$subject);
}
// Does this look right?
$ts = strtotime($subject);
if ($ts !== false) {
return $ts;
}
}
// Give up, it's not like we didn't try
return false;
}
里面的foreach
看起来确实很臭,但我认为它是可以接受的。它所做的是尝试替换由索引$keys[0]
和$keys[1]
标识的$localeInfo
(正在测试的当前语言环境)子数组中任何看起来像项之一的子字符串。为了使替换尽可能简洁,它在求值模式下使用辅助数组$flipped
和preg_replace
;如果您不喜欢这种代码,当然可以用更熟悉的基于循环的方法来替换它。
3。如何使用
$timeString = '22 Feb 2011';
echo strtotimeIntl($timeString, $locales);
$timeString = '22 Fev 2011';
echo strtotimeIntl($timeString, $locales);
4。第三个论点是什么?
嗯,如果替换能以不区分大小写的方式工作就好了。这样做的问题是,您不能盲目地使用strtolower
和/i
正则表达式修饰符,因为至少前者将无法工作,除非您更改LC_TEXT
区域设置,这是一个痛苦的要求,并且不可靠的引导(区域设置名称依赖于操作系统)。霰弹枪的论点是,即使到目前为止一切都很顺利,您仍然需要以ANSI编码保存您的区域设置数据(这意味着您不能将它们全部保存在同一个文件中)。
因此,如果需要,调用者可以选择提供自己的规范化函数;如果您的数据以UTF-8保存,mb_strtolower
将是一个很好的选择。
5。这有用吗?
6。没有任何警告吗?
嗯,除了标准化函数,我还能想到一个:strtotime
内部使用本地时区将解析后的日期转换为时间戳。这意味着在给定适当的区域设置数据的情况下,将正确解析法语格式的日期,但是将为本地时区生成时间戳,而不是为CET/CEST(法国使用的时区)生成时间戳。根据您的需求,您可能还需要考虑时区差异。
除了使用(即付费)翻译API服务之外,您还可以为languages
, weekdays
, abbreviated weekdays
, months
, abbreviated months
创建数据库表。四个工作日/月表将有一个language_id外键。您可以在行中存储英文对等物,或者更好地将它们规范化。
然后让函数从日期字符串(preg_match)中获取alpha标记,并查询表中与标记和语言匹配的行。然后,如果返回了适当的行,则将英文标记替换为日期字符串并传递给date函数。