关于php:#如何使用DateTime类(处理转换,格式化,差异和时区)

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

本问题已经有最佳答案,请猛点这里访问。

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

但是,DateTime类有一个缺陷:如果你还不熟悉它,可能会让人感到困惑。

这篇文章是为了帮助那些想要了解更多关于DateTime类和/或发现自己提出以下问题之一的人:

  • 如何将字符串转换为可修改的日期/时间?
  • 如何格式化日期/时间字符串?
  • 如何获得2次之间的差异?
  • 我如何计算时区?
  • 请注意:

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


    以下大部分信息可以从PHP的DateTime类文档的各个部分获得。但是,这个答案的格式应该能够回答大多数人关于DateTime类的问题,并且应该适用于大多数用例。

    如果您正在尝试使用Dates / Times进行更高级的操作,例如创建DateTime包装器,使用不可变的DateTime实例,或者特定于您的应用程序需要的其他内容,我强烈建议您查看完整的日期和时间文档。

    1.如何将字符串转换为可修改的日期/时间?

    编程中最困难的事情之一是尝试使最终用户输入可用。然而,当涉及日期和时间时,DateTime类使这几乎成为孩子们的游戏。

    怎么样

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

    1
    $datetime = new DateTime($datetime_string);

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

  • $datetime->modify() - 改变时间戳(适用于相对格式!)
  • $datetime->add() - 为DateTime对象添加天,月,年,小时,分钟和秒
  • $datetime->sub() - 从DateTime对象中减去天,月,年,小时,分钟和秒
  • $datetime->setDate() - 设置日期
  • $datetime->setISODate() - 设置ISO日期
  • $datetime->setTime() - 设置时间
  • $datetime->setTimestamp() - 根据Unix时间戳设置日期和时间(非常适合处理跨时区的绝对时间!)
  • 要查看DateTime::__construct()支持的格式的完整列表,请检查:支持的日期和时间格式。

    示例 - 解释最终用户输入

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

    典型的最终用户会在这个输入中添加类似这样的内容,并且当被要求支持时,典型的程序员将以下列方式做出响应:

  • 12/31/2000 -"好的"
  • 2000-12-31 -"当然"
  • Today -"嗯,我想我们可以支持吗?"
  • Tomorrow -"我想我们也应该支持这一点。"
  • wednesday next week -"不"
  • 过了一段时间,你要么强迫一个特定的格式(你应该总是这样做)或者在你糟糕的表格设计中哭泣。但是,DateTime允许所有这些作为有效输入并完美地解释它们。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // 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类并不完美,并且不支持每种格式。这就是为什么你应该总是使用带有DateTimetry ... catch块,并与你的最终用户确认你解释的日期是最终用户所希望的。一个很好的例子是欧洲日期格式:

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

    输出:

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

    示例 - 修改日期/时间

    您可以使用$datetime->modify()方法轻松调整任何日期/时间:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    $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的日期/时间并且需要更好的性能,请使用add()sub()setDate()setISODate()setTime()setTimestamp()而不是modify()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    $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.如何格式化日期/时间字符串?

    通常需要取1个日期/时间字符串并将其格式化为另一个日期/时间字符串,或者甚至只需采用现有日期/时间并更新它。 DateTime类也使这一点变得简单。

    怎么样

    DateTime具有方法format(),它将日期/时间作为格式化字符串返回。

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

    我们只会使用这些示例中提供的一小部分格式化选项,因此我强烈建议您查看有关格式化日期/时间以及预定义的DateTime常量的文档。

    注意:请注意,如果您尝试转义可能是PHP字符串转义字符的字符,则可能会出现意外结果。

    结果不正确

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

    正确的结果

    1
    2
    3
    4
    5
    // 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;

    例子

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

    SQL日期/时间

    1
    2
    3
    4
    5
    6
    7
    8
    // 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;

    最终用户可读日期/时间

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 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;

    日期/时间与时区

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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个日期/时间之间的时间差异。使用DateTime实际上有3种不同的方法可以实现这一点,您想要使用哪种方法取决于您的需求。

    如何(与例子)

    场景1:您只需要知道$datetime1是否大于,小于或等于$datetime2

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

    1
    2
    3
    4
    5
    6
    7
    $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之间的差异,表示为细分年/月/日/等。

    这适用于大多数情况,但是从$datetime->diff()返回的DateInterval实例有自己的"陷阱",可能不适用于您的特定用例。

    1
    2
    3
    4
    5
    6
    $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');

    场景3:您需要以另一种方式表达的$datetime1$datetime2之间的差异。

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

    1
    2
    3
    4
    5
    6
    7
    $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类优雅处理的其他内容。

    怎么样

    DateTime的构造函数允许您在日期/时间字符串或第二个参数中指定源时区。之后,只需用$datetime->setTimezone()设置一个新的时区,DateTime将处理其余的时区。

    1
    2
    3
    4
    5
    // 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支持的时区的完整列表以及DateTimeZone类上的文档。

    假设您希望向最终用户显示客户支持热线在其时区中打开的时间。 使用DateTime代码看起来像这样:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    $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
     \t\h\e e'
    ).' time zone'.PHP_EOL;
    }

    输出:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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

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

    1
    2
    3
    4
    $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');

    好。