# 如何使用 DateTime class(处理转换、格式、差异和时区)

# How to use the DateTime class (Dealing with Conversions, Formatting, Diffs, and Time zones)

每个人在编程的某个时候都必须处理 date/time 格式化、时区、奇怪的时间格式等。 PHP 几乎没有办法处理这些问题,其中最值得注意的是 PHP 内置于 DateTime class。这是因为 DateTime class 可以作为所有这些问题的一体化解决方案。

但是,DateTime class 有一个缺陷:如果您还不熟悉它,使用起来可能会感到困惑。

这篇post是为了帮助那些想进一步了解DateTimeclassand/or发现自己的人以下问题:

  1. 如何将字符串转换为可修改的 date/time?
  2. 如何格式化 date/time 字符串?
  3. 如何计算 2 次之间的差值?
  4. 如何考虑时区?

请注意:

此 post 不会解决 date()time()strtotime() 或其任何相关函数的用法。本post纯粹是为了说明DateTime及其相关的class在PHP中的正确用法。虽然这些答案中的许多也可以通过 date() 及其相关函数来实现,但 DateTime 将所有这些功能包装到一些干净的 classes 中;这样可以更容易理解整体。

以下大部分信息都可以从PHP's documentation of the DateTime class的各个部分获得。然而,这个答案的格式应该回答 most 人们关于 DateTime class 的问题,并且应该适用于 most 个用例。

如果你想用 Dates/Times 做一些更高级的事情,比如创建一个 DateTime 包装器,使用不可变的 DateTime 实例,或者其他特定于你的应用程序需要的东西,我强烈建议您查看完整的 Date and Time documentation.


1。如何将字符串转换为可修改的 date/time?

编程中最困难的事情之一就是尝试使最终用户的输入可用。然而,当涉及到日期和时间时,DateTime class 使这几乎是儿戏。

如何

DateTime 的构造函数使用强大的解析器,可以接受最广为人知的格式,包括相对格式。

$datetime = new DateTime($datetime_string);

从那里您可以使用以下任何方法修改时间:

查看 DateTime::__construct() supports check out: Supported Date and Time Formats.

格式的完整列表

示例 - 解释最终用户输入

假设您有一个表单,允许用户说出他们想在哪一天进行约会,但此输入不是具有强制格式的日期选择器,而是纯文本输入。

典型的最终用户会在该输入中输入类似这些的内容,当被要求支持时,典型的程序员会以下列方式响应:

  • 12/31/2000 - "OK"
  • 2000-12-31 - "Sure"
  • Today - "Um, I guess we could support that?"
  • Tomorrow - "I guess we should support that too."
  • wednesday next week - "No."

一段时间后,您要么强制使用特定格式(无论如何您都应该这样做),要么为糟糕的表单设计而哭泣。然而,DateTime 允许所有这些作为有效输入并完美地解释它们。

// 2000-12-31 00:00:00.000000
new DateTime('12/31/2000');

// 2000-12-31 00:00:00.000000
new DateTime('2000-12-31');

// 2000-12-31 00:00:00.000000
new DateTime('Today');

// 2001-01-01 00:00:00.000000
new DateTime('Tomorrow');

// 2001-01-03 00:00:00.000000
new DateTime('wednesday next week');

然而,与大多数东西一样,DateTime class 并不完美,并不支持所有格式。这就是为什么你应该始终使用 try ... catch 块和 DateTime 并与你的最终用户确认你解释的日期是最终用户想要的。一个很好的例子是欧洲日期格式:

try {
    new DateTime('31/12/2000');
} catch (Exception $e) {
    echo $e->getMessage();
}

输出:

DateTime::__construct(): Failed to parse time string (31/12/2000) at position 0 (3): Unexpected character

示例 - 修改 A Date/Time

您可以使用 $datetime->modify() 方法轻松调整任何 date/time:

$datetime = new DateTime('2001-01-01');

// 2001-01-04 00:00:00.000000
$datetime->modify('+3 days');

// 2001-02-04 00:00:00.000000
$datetime->modify('+1 month');

// 2001-02-03 23:59:00.000000
$datetime->modify('-60 seconds');

// 2001-02-02 00:00:00.000000
$datetime->modify('yesterday');

// 2001-02-02 18:00:00.000000
$datetime->modify('6pm');

$datetime->modify() 方法是修改任何 DateTime 实例的最简单方法。

但是,由于解析的原因,效率有点低。如果您要修改 1000 个 dates/times 并且需要更好的性能,请使用 add(), sub(), setDate(), setISODate(), setTime(), and setTimestamp() 而不是 modify().

$datetime = new DateTime('2001-01-01');

// 2001-06-01 00:00:00.000000
$datetime->setDate(2001, 6, 1);

// 2001-06-01 06:30:00.000000
$datetime->setTime(6, 30, 0);

// No sane person should ever do the below when they could just add 10,000
// seconds, but it's a good way to test how fast your system will handle
// updating DateTime.
$timestamp = $datetime->getTimestamp();
foreach (range(1, 10000) as $i) {
    $timestamp++;
    $datetime->setTimestamp($timestamp);
}
// 2001-06-01 09:16:40.000000

2。如何格式化 date/time 字符串?

通常需要获取 1 个 date/time 字符串并将其格式化为另一个 date/time 字符串,或者甚至只获取现有的 date/time 并更新它。 DateTime class 也使这变得简单。

如何

DateTime 有方法 format(),其中 returns date/time 作为格式化字符串。

$datetime = new DateTime;
$format   = 'Y-m-d H:i:s';
echo $datetime->format($format);

我们只会使用这些示例中提供的格式设置选项的一小部分,因此我强烈建议您查看 formatting dates/times as well as the predefined DateTime constants.

上的文档

注意: 请注意,如果您尝试转义可能是 PHP 字符串转义字符的字符,您可能会得到意想不到的结果。

不正确的结果

// output: Da   e 2000-12-31
echo $datetime->format("\D\a\t\e\: Y-m-d").PHP_EOL;

正确结果

// output: Date 2000-12-31
echo $datetime->format("\D\a\t\e\: Y-m-d").PHP_EOL;

// output: Date 2000-12-31
echo $datetime->format('\D\a\t\e\: Y-m-d').PHP_EOL;

例子

这些是您可能需要的一些常见格式:

SQLDates/Times

// output: 2000-12-31
echo $datetime->format('Y-m-d').PHP_EOL;

// output: 23:59:59
echo $datetime->format('H:i:s').PHP_EOL;

// output: 2000-12-31 23:59:59
echo $datetime->format('Y-m-d H:i:s').PHP_EOL;

最终用户可读Dates/Times

// output: 12/31/2000
echo $datetime->format('n/j/Y').PHP_EOL;

// output: 11:59pm
echo $datetime->format('g:ia').PHP_EOL;

// output: 12/31/2000 at 11:59pm
echo $datetime->format('n/j/Y \a\t g:ia').PHP_EOL;

// output: Sunday the 31st of December 2000 at 11:59:59 PM
echo $datetime->format('l \t\h\e jS \o\f F Y \a\t g:i:s A').PHP_EOL;

Dates/Times 有时区

date_default_timezone_set('America/New_York');
$datetime = new DateTime('2000-12-31 23:59:59');

// output: 2000-12-31 23:59:59 America/New_York
echo $datetime->format('Y-m-d H:i:s e').PHP_EOL;

// output: 2000-12-31 23:59:59 EST
echo $datetime->format('Y-m-d H:i:s T').PHP_EOL;

// output: 2000-12-31 23:59:59 -0500
echo $datetime->format('Y-m-d H:i:s O').PHP_EOL;

3。我如何得到 2 次之间的差异?

通常需要知道 2 dates/times 之间的时间差。 DateTime 实际上有 3 种不同的方法可以实现这一点,您要使用哪一种取决于您的需要。

如何(有例子)

场景一:只需要知道$datetime1是大于、小于还是等于$datetime2

在这种情况下,您可以直接比较DateTime的实例。

$datetime1 = new DateTime;
sleep(1);
$datetime2 = new DateTime;

var_dump($datetime1 > $datetime2); // FALSE
var_dump($datetime1 < $datetime2); // TRUE
var_dump($datetime1 == $datetime2); // FALSE

场景2:你需要$datetime1$datetime2之间的差异表示为分解years/months/days/etc.

这适用于大多数情况,但是 DateInterval instance you get back from $datetime->diff() 有自己的 "gotchas",可能不适用于您的特定用例。

$datetime1 = new DateTime('2000-01-01 00:00:00.000000');
$datetime2 = new DateTime('2001-02-03 04:05:06.789012');
$diff      = $datetime1->diff($datetime2);

// output: 1 Years, 1 Months, 2 Days, 4 Hours, 5 Minutes, 6 Seconds
echo $diff->format('%y Years, %m Months, %d Days, %h Hours, %i Minutes, %s Seconds');

场景三:你需要用另一种方式表达$datetime1$datetime2的区别

这将在任何上下文中工作,但需要一些额外的代码。

$interval   = 60 * 60 * 24; // 1 day in seconds
$datetime1  = new DateTime('2000-01-01');
$datetime2  = new DateTime;
$diff       = $datetime2->getTimestamp() - $datetime1->getTimestamp();

// output: It has been 6956 days since 2000-01-01!
printf('It has been %s days since %s!', floor($diff / $interval), $datetime1->format('Y-m-d'));

4。我如何考虑时区?

当谈到在编程中处理时间时,到目前为止最糟糕的部分是处理时区。幸运的是,这是 DateTime class 优雅处理的另一件事。

如何

DateTime's constructor allows you to specify the source time zone in either the date/time string or as the 2nd Argument. After that, just set a new timezone with $datetime->setTimezone()DateTime 会处理剩下的事情。

// These 2 lines are functionally identical
$datetime = new DateTime('2001-01-01 00:00:00', new DateTimeZone('UTC')); // recommended, may be faster
$datetime = new DateTime('2001-01-01 00:00:00 UTC');

$datetime->setTimezone(new DateTimeZone('EST'));

我建议查看 PHP's supported time zones as well as the docs on the DateTimeZone class 的完整列表。

例子

假设您想向最终用户显示您的客户支持热线在他们所在时区的开通时间。使用 DateTime 代码看起来像这样:

$support_opens      = new DateTime('08:00:00', new DateTimeZone('America/New_York'));
$customer_timezones = array('America/New_York', 'America/Chicago', 'America/Denver', 'America/Phoenix', 'America/Los_Angeles', 'America/Anchorage', 'America/Adak', 'Pacific/Honolulu');

echo "Today we open at the following times:".PHP_EOL;
foreach ($customer_timezones as $timezone) {
    $support_opens->setTimezone(new DateTimeZone($timezone));
    echo '* '.$support_opens->format('g:ia \f\o\r \t\h\e e').' time zone'.PHP_EOL;
}

输出:

Today we open at the following times:
* 8:00am for the America/New_York time zone
* 7:00am for the America/Chicago time zone
* 6:00am for the America/Denver time zone
* 6:00am for the America/Phoenix time zone
* 5:00am for the America/Los_Angeles time zone
* 4:00am for the America/Anchorage time zone
* 3:00am for the America/Adak time zone
* 3:00am for the Pacific/Honolulu time zone

注意: 如果您在 date/time 字符串和第二个参数中都提供时区,则参数时区将被忽略。

$datetime = new DateTime('2001-01-01 00:00:00 EST', new DateTimeZone('UTC'));

// output: 2001-01-01 00:00:00 EST
echo $datetime1->format('Y-m-d H:i:s');