格式化DateTime对象,遵循Locale::getDefault()

72

我有一个DateTime对象,目前正在通过以下方式进行格式化:

$mytime->format("D d.m.Y")

这正好给了我需要的格式:

周二 2012年3月5日

唯一缺失的是正确的语言。我需要将TueTuesday)翻译成德语,即DieDienstag)。

这为我提供了正确的语言环境设置。

Locale::getDefault()

不过我不知道如何告诉DateTime::format使用它。

难道没有一种方法可以像这样做:

$mytime->format("D d.m.Y", \Locale::getDefault());

请注意:strftime("%a %e.%l.%Y", \Locale::getDefault()) 在我的设置中无法正常工作,因为getDefault()返回的是"de"而不是"de_DE"。 - stoefln
1
strftime的第二个参数应该是时间戳,而不是语言环境。 - Jonathan
哦,抱歉。完全清楚这行不通。我实际想告诉的是 setlocale(LC_TIME, \Locale::getDefault()) 无法在我的Symfony2应用程序中工作。 - stoefln
6个回答

156
你可以使用Intl扩展来格式化日期。它将根据所选择的语言环境格式化日期/时间,或者您可以使用IntlDateFormatter::setPattern()覆盖该格式。
一个快速使用自定义格式的示例,以获得所需的输出格式,可能看起来像这样。
$dt = new DateTime;

$formatter = new IntlDateFormatter('de_DE', IntlDateFormatter::SHORT, IntlDateFormatter::SHORT);
$formatter->setPattern('E d.M.yyyy');

echo $formatter->format($dt);

这将输出以下内容(至少是今天的)。

2013年6月4日,星期三


3
很好,你回答了一个古老的问题,因为这个问题在谷歌搜索结果的首位出现,并且过去只提供过时的信息。 - Timo Huovinen
1
当您(成功地)回复一个旧问题时,您将获得“Necromancer”徽章。 - aesede
3
您可以按以下步骤轻松安装扩展:yum install php-intl``apachectl reload. - Kapitein Witbaard
想要更改输出格式的人可以查看:http://www.icu-project.org/apiref/icu4c/classSimpleDateFormat.html#details - Adam
你真是救星。我开始有点担心,因为setlocale()的文档表明它不是线程安全的。 - James Beninger
3
您应该知道,PHP的IntlDateFormatter类使用ISO日期格式代码而不是PHP的date()格式代码。可以在http://framework.zend.com/manual/1.12/en/zend.date.constants.html#zend.date.constants.selfdefinedformats上找到ISO代码的好列表。 - Koli

73

这是因为format不关注地区设置。你应该使用strftime

例如:

setlocale(LC_TIME, "de_DE"); //only necessary if the locale isn't already set
$formatted_time = strftime("%a %e.%l.%Y", $mytime->getTimestamp())

13
这种行为在 php 文档的 DateTime::format 中有说明:*该方法不使用区域设置,所有输出均为英文。* - user895378
33
strftime()函数不能处理1970年之前的日期,Date_time::format()函数不能与本地化一起使用...有没有同时适用于两者的东西? - Jay K
1
@Jay K:可以通过扩展PHP的本地DateTime类来为其添加本地化功能。 - Duroth
4
实际上,我们发现如果你向strftime()传递一个(负数)Unix时间戳,它确实可以处理1970年之前的日期(至少在我们的服务器上是这样的)...所以我们将这两者结合起来:$datetime = new DateTime('1920-01-01'); setLocale(LC_TIME|LC_CTYPE, $locale_based_on_user_profile); return strftime($format, $datetime->format('U')); - Jay K
4
在PHP 8.1中,strftime()函数已被弃用,并将在PHP 9中被移除。请参阅关于使用Intl扩展的DateTime的得票最高的答案。 - Bob Ray
显示剩余3条评论

12

国际日期格式化程序是目前(2023年)的最佳选择。

<?php
$formatter = new IntlDateFormatter(
    $locale,  // the locale to use, e.g. 'en_GB'
    $dateFormat,  // how the date should be formatted, e.g. IntlDateFormatter::FULL
    $timeFormat,  // how the time should be formatted, e.g. IntlDateFormatter::FULL 
    'Europe/Berlin'  // the time should be returned in which timezone?
);

echo $formatter->format(time());

根据传递给$locale和日期时间格式的不同,将会产生不同的输出。我想添加一些示例供以后参考。请注意,IntlDateFormatter::GREGORIANIntlDateFormatter::LONG是可以互换的。

Locale: en_US
Format for Date & Time:           Results in:
IntlDateFormatter::FULL           Friday, August 5, 2022 at 3:26:37 PM Central European Summer Time 
IntlDateFormatter::LONG           August 5, 2022 at 3:26:37 PM GMT+2 
IntlDateFormatter::MEDIUM         Aug 5, 2022, 3:26:37 PM 
IntlDateFormatter::SHORT          8/5/22, 3:26 PM 



Locale: en_GB
Format for Date & Time:           Results in:
IntlDateFormatter::FULL           Friday, 5 August 2022 at 15:26:37 Central European Summer Time 
IntlDateFormatter::LONG           5 August 2022 at 15:26:37 CEST 
IntlDateFormatter::MEDIUM         5 Aug 2022, 15:26:37 
IntlDateFormatter::SHORT          05/08/2022, 15:26 



Locale: de_DE
Format for Date & Time:           Results in:
IntlDateFormatter::FULL           Freitag, 5. August 2022 um 15:26:37 Mitteleuropäische Sommerzeit 
IntlDateFormatter::LONG           5. August 2022 um 15:26:37 MESZ 
IntlDateFormatter::MEDIUM         05.08.2022, 15:26:37 
IntlDateFormatter::SHORT          05.08.22, 15:26 



Locale: fr_FR
Format for Date & Time:           Results in:
IntlDateFormatter::FULL           vendredi 5 août 2022 à 15:26:37 heure d’été d’Europe centrale 
IntlDateFormatter::LONG           5 août 2022 à 15:26:37 UTC+2 
IntlDateFormatter::MEDIUM         5 août 2022 à 15:26:37 
IntlDateFormatter::SHORT          05/08/2022 15:26 


正如salathe所说,如果需要进一步自定义输出,您也可以使用$formatter->setPattern

2

setlocale()是正确的答案,但现在已过时。

strftime自PHP 8.1.0起已被弃用,强烈不建议依赖于此函数。

Intl扩展工作得很好,但并不总是方便。

使用Carbon2CakePHP Chronos或类似库是处理日期和时间最简单的方法之一。它提供了一个统一的接口来处理所有日期操作、格式化和计算。如果你经常处理日期,我建议使用Carbon,然后像这样做:

$date = Carbon::now()->locale('fr_FR');
echo $date->isoFormat('dd DD.MM.YYYY');  

请注意,这种格式与date()的格式不同。完整列表请参见Carbon文档中的内容 ,但提到的D d.m.Y可能类似于 dd DD.MM.YYYY
如果您的项目接受第三方库,那么它确实是一种好方法。此外,如果您正在使用框架,请检查是否已包含Carbon(或其包装器)。

1
我制作了一个东西来实现这个功能,因为似乎在网上除了strftime(已经非常过时)之外没有简单的解决方案!我的解决方案通过国际化月份和日期名称扩展了DateTime::format(),不需要安装一堆模块、学习新的日期格式等等。在包含下面提供的类之后,您可以按照以下方式使用它。而不是:
$date = new DateTime("2010-01-01 1:23");
echo $date->format("l (D) Y-M-d (F)");

结果: 星期五 (周五) 2010年1月1日 (一月)

现在您可以使用

$date = new DateTimeIntl("2010-01-01 1:23");
echo $date->format("l (D) Y-M-d (F)");

结果: 2010年1月1日星期五(荷兰语环境).

如果需要,您可以动态更改$datetime->locale

$date = new DateTimeIntl("2010-01-01 1:23");
$date->locale = "it_IT" ;
echo $date->format("l (D) Y-M-d (F)");

结果:venerdì (ven) 2010-gen-01 (gennaio)

包含以下内容:

class DateTimePatternReplace {
    function __construct(public string $DateTimeCode, 
                         public string $IntDateFormatterCode,
                         public string $tempDateTimePlaceHolder) {}
}

trait addIntlDate {

    public string $locale="nl_NL" ;  // REPLACE BY YOUR FAVORITE LOCALE

    private function getIntResult(string $pattern) {
        if ( ! isset($this->formatter) || $this->formatter->getLocale(Locale::VALID_LOCALE) != $this->locale ) { 
            $this->formatter = new IntlDateFormatter($this->locale);
            $this->locale = $this->formatter->getLocale(Locale::VALID_LOCALE); // store the valid version of the locale
        }
        this->formatter->setPattern($pattern);
        return $this->formatter->format($this);
    }

    function format(string $pattern): string {
        // The third parameter can NOT contain normal latin letters, these are random, 
        // distinctive codes not likely to be in a date format string
        $replacePatterns = [/*weekdays*/new DateTimePatternReplace('l', 'EEEE', '[*ł*]'), 
                                        new DateTimePatternReplace('D', 'EEE', '[*Đ*]'),
                            /*month*/   new DateTimePatternReplace('F', 'MMMM', '[*ƒ*]'), 
                                        new DateTimePatternReplace('M', 'MMM', '[*μ*]'),
                                        // add new replacements here if needed
                           ] ;
        $codesFound=[] ;
        foreach($replacePatterns as $p) {
            if ( str_contains($pattern, $p->DateTimeCode)) {
                // replace codes not prepended by a backslash.
                // known bug: codes prepended by double backslashes will not be translated. Whatever.
                $pattern = preg_replace('/(?<!\\\)'.preg_quote($p->DateTimeCode)."/", $p->tempDateTimePlaceHolder, $pattern);
                $codesFound[] = $p ;
            }
        }
        $result = parent::format($pattern) ;
        foreach($codesFound as $p) {
            $code = $this->getIntResult($p->IntDateFormatterCode);
            $result = str_replace($p->tempDateTimePlaceHolder, $code, $result);
        }
        return $result ;
    }
} 

// you can remove this str_contains addition in PHP 8 or higher
if (!function_exists('str_contains')) {
    function str_contains($haystack, $needle) {
        return $needle !== '' && mb_strpos($haystack, $needle) !== false;
    }
}
// end str_contains addition


class DateTimeIntl extends DateTime {
    use addIntlDate;
}

class DateTimeImmutableIntl extends DateTimeImmutable {
    use addIntlDate;  
}

这段代码扩展了DateTime和DateTimeImmutable,通过区域设置扩展了它们的普通格式。因此,这使一切变得非常简单。

如果需要,您可以通过将代码添加到数组中来添加要翻译的新模式:在DateTime::format()语法中的格式化模式,对应的IntlDateFormatter::format语法中的格式化模式,以及一个占位符,用于DateTime::format,该占位符不包含将由DateTime::format方法使用/替换的字母/代码/模式。例如,查看当前使用ASCII小于128个字母的四个代码示例(它们仅出于乐趣使用波兰语、希腊语、荷兰语和斯洛伐克语字母)。

在PHP 8.1中构建和测试。对于一些旧版本的PHP,您需要更改第一个类。

class DateTimePatternReplace {
    public string $DateTimeCode;
    public string $IntDateFormatterCode;
    public string $tempDateTimePlaceHolder;

    function __construct(string $DateTimeCode, string $IntDateFormatterCode, string $tempDateTimePlaceHolder) {
         $this->DateTimeCode = $DateTimeCode;
         $this->IntDateFormatterCode = $IntDateFormatterCode;
         $this->tempDateTimePlaceHolder = $tempDateTimePlaceHolder;
    }
}

-2

这是我如何解决结合DateTimestrftime()的特性。

第一个允许我们管理具有奇怪日期格式的字符串,例如“Ymd”(从日期选择器存储在数据库中)。 第二个允许我们将日期字符串翻译成某种语言。

例如,我们从值“20201129”开始,我们想要以意大利可读的日期结束,包括星期几和月份的名称,以及首字母大写:“Domenica 29 novembre 2020”。

// for example we start from a variable like this
$yyyymmdd = '20201129';

// set the local time to italian
date_default_timezone_set('Europe/Rome');
setlocale(LC_ALL, 'it_IT.utf8');

// convert the variable $yyyymmdd to a real date with DateTime
$truedate = DateTime::createFromFormat('Ymd', $yyyymmdd);

// check if the result is a date (true) else do nothing
if($truedate){

  // output the date using strftime
  // note the value passed using format->('U'), it is a conversion to timestamp
  echo ucfirst(strftime('%A %d %B %Y', $truedate->format('U')));

}

// final result: Domenica 29 novembre 2020

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接