日期时间与微秒

39

在我的代码中,我使用 DateTime 对象来操作日期,然后将它们转换为时间戳以便保存到一些 JSON 文件中。

由于某些原因,我想要拥有与 DateTime(或类似的东西)相同但精度为微秒的对象(当插入到 JSON 文件中时会将其转换为浮点数)。

我的问题是:是否有一个 PHP 对象类似于 DateTime,但也可以处理 微秒

目标是能够使用对象来操作微秒。

date() 文档中,有一些指示 DateTime 可以创建带有微秒的内容,但我找不到如何创建。

u 微秒 (在PHP 5.2.2 中添加)。请注意,date() 总是生成000000,因为它需要一个整数参数,而DateTime::format()如果使用微秒创建DateTime,则支持微秒。

我尝试将 DateTime 对象的时间戳设置为浮点值 (microtime(true)),但它不起作用 (我认为它会将时间戳转换为整数,从而导致微秒丢失)。

这是我尝试的方法:

$dt = new DateTime();
$dt->setTimestamp(3.4); // I replaced 3.4 by microtime(true), this is just to give an example
var_dump($dt);
var_dump($dt->format('u'));

正如您在此处所见,.4 不会被计入(即使我们可以使用微秒格式 u)。

object(DateTime)[1]
  public 'date' => string '1970-01-01 01:00:03' (length=19)
  public 'timezone_type' => int 3
  public 'timezone' => string 'Europe/Berlin' (length=13)

string '000000' (length=6)

编辑:我看到了这段代码,它允许在DateTime中添加微秒,但是在创建DateTime之前,我需要对微秒进行大量修改。由于我将经常使用它,所以我希望在获取“微秒对象”之前尽可能少地修改微秒。

$d = new DateTime("15-07-2014 18:30:00.111111");

你的问题刚刚变得模糊了,你在寻找什么? - davejal
我正在寻找一种有效的方式来存储带有微秒的日期,具有与DateTime相同的可能性(比较、格式化等)。 - FrancoisBaveye
存储它们在数据库中,我假设是哪一个? - davejal
对于存储部分,我会将它们转换为浮点数,并将其存储在一个JSON文件中。 - FrancoisBaveye
10个回答

38

以下是一个非常简单的创建包含微秒的DateTime对象的方法。

我没有深入探讨这个问题,如果我错过了什么,我很抱歉,但希望你能发现这很有用。

$date = DateTime::createFromFormat('U.u', microtime(TRUE));
var_dump($date->format('Y-m-d H:i:s.u')); 

我尝试过各种逻辑上合理的方法来使其工作,但这是在PHP 7.1之前版本中有效的唯一方法。

然而,存在一个问题,它返回了正确的时间部分但不是正确的日期部分(可能是由于UTC时间)。 这是我所做的(在我看来,仍然更简单):

$dateObj = DateTime::createFromFormat('U.u', microtime(TRUE));
$dateObj->setTimeZone(new DateTimeZone('America/Denver'));
var_dump($dateObj->format('Y-m-d H:i:s:u'));

这是一个可用的示例:http://sandbox.onlinephpfunctions.com/code/66f20107d4adf87c90b5c8c914393d4edef180a2

更新
如评论中所指出,从PHP 7.1开始,Planplan推荐的方法似乎比上面显示的方法更好。

因此,对于PHP 7.1及以上版本,最好使用以下代码而不是上述代码:

$dateObj = DateTime::createFromFormat('0.u00 U', microtime());
$dateObj->setTimeZone(new DateTimeZone('America/Denver'));
var_dump($dateObj->format('Y-m-d H:i:s:u'));

请注意,以上方法仅适用于PHP版本7.1及以上。早期版本的PHP将在microtime的位置返回0,从而丢失所有microtime数据。

这里有一个更新的沙箱,显示了两个版本: http://sandbox.onlinephpfunctions.com/code/a88522835fdad4ae928d023a44b721e392a3295e

注意:在测试上述沙箱时,我并没有看到Planplan提到的microtime(TRUE)故障。然而,更新的方法似乎记录了更高级别的精度,正如KristopherWindsor所建议的那样。

注意2:请注意,由于PHP DateTime代码中关于微秒处理的基本决策,可能会出现罕见情况其中任何一种方法都会失败。要么:

  • 避免在科学目的或需要非常高的精度的任何地方使用此方法。
  • 或者准备好在确切的秒标记上返回没有微秒数据的情况...(其中微秒...即1百万分之一秒,将没有数据返回,因为它是完全的零...从我所读的内容来看,这不清楚从返回的内容来看,可以通过更好的方式来完成,但值得创建处理代码...再次,对于高精度使用)

感谢Sz的提示。(见评论)。


7
偶尔,microtime(true)返回的浮点数只有整数部分,导致 'U.u' 格式失效。虽然这有点丑,但使用 DateTime::createFromFormat('0.u00 U', microtime()) 总是有效的。 - Planplan
@Planplan 这值得知道。不过,丢失任何微秒数据似乎与 OP 所要求的相反...?所以我猜测将其捕获为错误,然后按照你所说的方式进行可能是最好的方法。 - MER
@Planplan的解决方案不会失去任何精度。microtime()实际上比microtime(true)返回更高的精度,因为PHP浮点数的精度。 - Kristopher Windsor
1
@KristopherWindsor,我重新审视了这个问题,因为你的评论。如果使用的PHP版本是7.1及以上,则完全正确。正如sandbox.onlinephpfunctions.com所示,如果使用早期版本的PHP,则会丢失所有微秒数据(只会得到一行0)。感谢您的阐述,我将更新答案。 - MER
@MER,正如您所知,人们会从顶部开始阅读答案,并在找到满足其需求的答案后停止阅读。而在第一个代码片段之后,它看起来干净整洁,实际上应该可以工作,但却没有工作,接着就是令人放心的句子,这可能不可避免地导致意外的错误:“我测试过了,并尝试了其他各种逻辑上似乎可行的方法,但这是唯一有效的方法。”(然后,“然而存在问题”部分是关于未返回日期,而不是真正的雷区问题。) - Sz.
显示剩余5条评论

6

在 PHP DateTime 手册上查看响应:

DateTime 不支持分裂秒(微秒或毫秒等)。我不知道为什么没有记录这一点。该类构造函数将接受它们而不抱怨,但它们会被丢弃。似乎没有办法以完整的方式以客观形式存储类似“2012-07-08 11:14:15.638276”这样的字符串。

因此,您不能对两个字符串进行日期计算,例如:

<?php
$d1=new DateTime("2012-07-08 11:14:15.638276");
$d2=new DateTime("2012-07-08 11:14:15.889342");
$diff=$d2->diff($d1);
print_r( $diff ) ;

/* returns:

DateInterval Object
(
    [y] => 0
    [m] => 0
    [d] => 0
    [h] => 0
    [i] => 0
    [s] => 0
    [invert] => 0
    [days] => 0
)

*/
?>

当你实际上想要得到0.251066秒时,你获得了0。


然而,从这里获取响应:

$micro_date = microtime();
$date_array = explode(" ",$micro_date);
$date = date("Y-m-d H:i:s",$date_array[1]);
echo "Date: $date:" . $date_array[0]."<br>";

建议使用引用中的 dateTime() 类:

$t = microtime(true);
$micro = sprintf("%06d",($t - floor($t)) * 1000000);
$d = new DateTime( date('Y-m-d H:i:s.'.$micro, $t) );

print $d->format("Y-m-d H:i:s.u"); //note "u" is microseconds (1 seconds = 1000000 µs).

在php.net上,dateTime()的参考文档如下:http://php.net/manual/zh/datetime.construct.php#


通过这个解决方案,我不能轻松地比较微秒,而使用微秒来创建 DateTime 对象会消耗大量资源(我将频繁使用它)。 - FrancoisBaveye
当你写这篇文章时,以上所有内容都是正确的。但是最近版本的PHP支持使用微秒本地构建DateTime对象,因此你的第一个示例现在会打印0.251066秒。我不确定它是何时更改的,但在我手头拥有的最旧版本的PHP 7.3中可以正常工作。假设你不关心古老版本的PHP,只需执行$d = new DateTime(); print $d->format('Y-m-d H:i:s.u');,它就能正常工作。 - Richard Smith

4

/!\ 编辑 /!\

我现在使用https://github.com/briannesbitt/Carbon,这个答案的其余部分只是为了历史原因而存在。

结束编辑

我决定使用你们给我的建议来扩展DateTime类。

构造函数接受一个浮点数(来自microtime)或什么也不接受(在这种情况下,它将用当前的“微时间戳”初始化)。我还覆盖了两个重要的函数:setTimestampgetTimestamp

不幸的是,我无法解决性能问题,尽管它没有我想象中那么慢。

以下是整个类:

<?php
class MicroDateTime extends DateTime
{
    public $microseconds = 0;

    public function __construct($time = 'now')
    {
        if ($time == 'now')
            $time = microtime(true);

        if (is_float($time + 0)) // "+ 0" implicitly converts $time to a numeric value
        {
            list($ts, $ms) = explode('.', $time);
            parent::__construct(date('Y-m-d H:i:s.', $ts).$ms);
            $this->microseconds = $time - (int)$time;
        }
        else
            throw new Exception('Incorrect value for time "'.print_r($time, true).'"');
    }

    public function setTimestamp($timestamp)
    {
        parent::setTimestamp($timestamp);
        $this->microseconds = $timestamp - (int)$timestamp;
    }

    public function getTimestamp()
    {
        return parent::getTimestamp() + $this->microseconds;
    }
}

3

有多种选项。但是如Ben提供的,我会尝试给你提供另一种解决方案。

如果您提供更多关于您想要做的计算类型的细节,它可以进一步改变。

$time =microtime(true);
$micro_time=sprintf("%06d",($time - floor($time)) * 1000000);
$date=new DateTime( date('Y-m-d H:i:s.'.$micro_time,$time) );
print "Date with microseconds :<br> ".$date->format("Y-m-d H:i:s.u");

或者
$time =microtime(true);
var_dump($time);

$micro_time=sprintf("%06d",($time - floor($time)) * 1000000);
$date=new DateTime( date('Y-m-d H:i:s.'.$micro_time,$time) );
print "Date with microseconds :<br> ".$date->format("Y-m-d H:i:s.u");

或者
list($ts,$ms) = explode(".",microtime(true));
$dt = new DateTime(date("Y-m-d H:i:s.",$ts).$ms);
echo $dt->format("Y-m-d H:i:s.u");

或者

list($usec, $sec) = explode(' ', microtime());
print date('Y-m-d H:i:s', $sec) . $usec;

1
/*
 * More or less standard complete example. Tested.
 */
  private static function utime($format, $utime = null, $timezone = null) {
    if(!$utime) {
      // microtime(true) had too fiew digits of microsecconds
      $time_arr = explode( " ", microtime( false ) );
      $utime = $time_arr[1] . substr( $time_arr[0], 1, 7 );
    }
    if(!$timezone) {
      $timezone = date_default_timezone_get();
    }
    $date_time_zone = timezone_open( $timezone );
    //date_create_from_format( "U.u", $utime ) - had a bug with 3-rd parameter
    $date_time = date_create_from_format( "U.u", $utime );
    date_timezone_set( $date_time, $date_time_zone );
    $timestr = date_format( $date_time, $format );
    return $timestr;
  }

这是我最终得出的代码。我想要6位小数时间戳,而不是microtime(true)提供的4位小数时间戳。 - Richard A Quadling

1

以下是在PHP 7.2中的解决方法:

$dateTime = \DateTime::createFromFormat('U.u', sprintf('%f', $aFloat), $optionalTimezone);

我认为,由于格式代码'u'仅在将日期转换为字符串时输出日期的微秒部分,因此反向操作也是一样的。它还期望一个句点字符'.',因此如果$aFloat恰好是一个整数,则默认转换为字符串会省略小数点。最初,我认为浮点转字符串需要'%.6f',但'u'期望的是左对齐的字符串。尾随零是不必要的。


这是因为如果一个数字被传递给构造函数,则它必须是整数,而<code>setTimestamp</code>只接受整数。我没有研究过<code>DateInterval</code>的工作原理,但这需要更多的步骤。 - diskerror

0

自从我解决了我的问题,我想与您分享。 Php71+具有微秒精度,如果您想将其转换为纳秒精度,只需将其乘以1000(10 ^ 3)。

$nsTimestamp = (int) (new \DateTime())->getTimestamp() * 1000

0

0

我有点晚了,但我必须开发一个适用于PHP 5、PHP 7.0和PHP 7.1+的解决方案。

list($msec, $now) = explode(' ', microtime(false));
$z = gmdate('Y-m-d\TH:i:s' . substr($msec, 1) . '\Z', $now);

这将为您提供一个带有微秒的有效UTC时间字符串。如果您仍需要将其作为DateTime对象,您可以直接传递此字符串:
$dt = new DateTime($z);

如果你需要使用 DateTime,这段代码可以在PHP 5.2及以上版本中运行。但如果你只需要带微秒的字符串,它可以在PHP 4中运行。


0

美丽的日期和时间,逐步展示:

注意microtime()会显示时间微秒(小数点后的数字)

echo microtime(true) ."<br>"; //1601674357.9448
sleep(0.99);
echo microtime(true) ."<br>"; //1601674357.9449
sleep(0.99);
echo microtime(true) ."<br>"; //1601674357.945

那么我们来看一下小数点后面的数字:

echo substr(microtime(true), 11,4) . "<br>"; //945

但是如果小数点后只有1或2位呢?我们用零来补全...

好的,现在我们总是有4个微秒的数字

echo str_pad(substr(microtime(true), 11,4), 4, '0', STR_PAD_RIGHT) . "<br>"; //9450

所以,让我们添加日期...最终结果:

$date = gmdate('Y-m-d h:i:s.');
$time = str_pad(substr(microtime(true), 11,4), 4, '0', STR_PAD_RIGHT);

echo $date . $time; //2020-10-02 09:43:57.9450

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