39

In my code, I'm using DateTime objects to manipulate dates, then convert them to timestamp in order to save them in some JSON files.

For some reasons, I want to have the same thing as DateTime (or something close), but with microseconds precision (that I would convert to float when inserting inside the JSON files).

My question is : is there a PHP object that is like DateTime, but can handle microseconds too ?

The goal is to be able to manipulate microtimes with objects.

In the date() documentation, there is something that indicates that DateTime can be created with microseconds, but I wasn't able to find how.

u Microseconds (added in PHP 5.2.2). Note that date() will always generate 000000 since it takes an integer parameter, whereas DateTime::format() does support microseconds if DateTime was created with microseconds.

I have tried to set the timestamp of a DateTime object with a floating value (microtime(true)), but it doesn't work (I think it converts the timestamp to an int, causing the loss of the microseconds).

Here is how i tried

$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'));

The .4 is not taken into account as you can see here (even though we can use the u format, which corresponds to the microseconds).

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)

EDIT : I saw this code, which allows to add microseconds to a DateTime, but I would need to apply a lot of modifications to the microtime before creating the DateTime. Since I will use this a lot, I want to do as little modifications to the microtime as possible before getting the "microtime object".

$d = new DateTime("15-07-2014 18:30:00.111111");
FrancoisBaveye
  • 1,902
  • 1
  • 18
  • 25

10 Answers10

38

Here's a very simple method of creating a DateTime object that includes microtime.

I didn't delve into this question too deeply so if I missed something I apologize but hope you find this helpful.

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

I tested it out and tried various other ways to make this work that seemed logical but this was the sole method that worked for PHP versions prior to 7.1.

However there was a problem, it was returning the correct time portion but not the correct day portion (because of UTC time most likely) Here's what I did (still seems simpler IMHO):

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

Here's a working example: http://sandbox.onlinephpfunctions.com/code/66f20107d4adf87c90b5c8c914393d4edef180a2

UPDATE
As pointed out in comments, as of PHP 7.1, the method recommended by Planplan appears to be superior to the one shown above.

So, again for PHP 7.1 and later it may be better to use the below code instead of the above:

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

Please be aware that the above works only for PHP versions 7.1 and above. Previous versions of PHP will return 0s in place of the microtime, therefore losing all microtime data.

Here's an updated sandbox showing both: http://sandbox.onlinephpfunctions.com/code/a88522835fdad4ae928d023a44b721e392a3295e

NOTE: in testing the above sandbox I did not ever see the microtime(TRUE) failure which Planplan mentioned that he experienced. The updated method does, however, appear to record a higher level of precision as suggested by KristopherWindsor.

NOTE2: Please be aware that there may be rare cases where either approach will fail because of an underlying decision made regarding the handling of microseconds in PHP DateTime code. Either:

  • avoid use of this for scientific purposes or anything where a very high level of accuracy is required.
  • OR be prepared for no microsecond data to return on an exact second mark... (where a microsecond ... which is 1 millionth of a second, will have no data to return as it is complete zeros... from what I've read this is not clear from what's returned and could be done in a better way but is worth creating code to handle ... again, for high precisions uses)

Thanks for the headsup Sz. (see comments).

MER
  • 1,455
  • 20
  • 25
  • 6
    On rare occasion, microtime(true) can return a float with only an integer part, making the 'U.u' format fail. This is a bit ugly, but this will always work with DateTime::createFromFormat('0.u00 U', microtime()); – Planplan Nov 15 '17 at 16:41
  • @Planplan That's worth knowing. I think, though, the loss of any microsecond data would seem to go against what the OP was asking for...? So I suspect catching it as an error, then doing it the way you are talking about would be probably the best approach. – MER Nov 15 '17 at 22:00
  • @Planplan's solution will not lose any precision. microtime() actually returns more precision than microtime(true) will, because of the precision of PHP floats. – Kristopher Windsor May 23 '18 at 23:30
  • 1
    @KristopherWindsor I revisited this because of your comment and it appears you are completely right, so long as the version of PHP is 7.1 and above. As shown on sandbox.onlinephpfunctions.com if you use an early version of PHP you lose ALL microsecond data, (you simply get a line of 0s). Thanks for the elucidation. I'll update the answer. – MER May 23 '18 at 23:37
  • The otherwise perfectly reasonable `$date = DateTime::createFromFormat('U.u', microtime(TRUE));` idiom can still very much (pretty reliably) fail in 7.x, unfortunately. See e.g. https://bugs.php.net/bug.php?id=64414 The answer should not state that it "works"; this is still broken in PHP. – Sz. Jun 02 '20 at 15:25
  • @Sz. The answer appears to suggest a better and best approach for below 7.1 and above 7.1. I don't see any point where I suggest that either is completely reliable. Just relaying what I found to work... as in suggesting that it was good enough for my uses. – MER Jun 02 '20 at 20:49
  • @MER, as you know, people will start reading an answer from the top, and stop reading as soon as they find it good enough for their needs. And right after the very first code snippet, which looks clean & neat, and in fact should work, but doesn't, there's the reassuring sentence, which cold thus unavoidably lead to surprising bugs: *"I tested it out and tried various other ways to make this work that seemed logical but this was the sole method that worked."* (And then the "However there was a problem" part is about not returning the day, and not a word about the real landmine problem there.) – Sz. Jun 05 '20 at 14:45
  • And even if they proceed reading throug, the second code snippet has the exact same problem, and again, not a word about the fact that *it will fail every time `microtime` returns an "exact" second* (a value with no fractional part). The problem here is that even though this is clearly a problem, the PHP team marked it as not a bug, so it won't even be fixed any time soon, making it all the more important to educate people to avoid the problem. – Sz. Jun 05 '20 at 14:49
  • BTW, have you even read the bug report I linked, and do you actually understand what the problem is here? Because you keep saying things like "the above works only for PHP versions 7.1 and above.", and "NOTE: in testing the above sandbox I did not ever see the microtime(TRUE) failure which" and "what I found to work... suggesting that it was good enough for my uses" These all suggest that you are unaware of the real problem here. Read and understand the bug report please. Because the code you suggest **does fail** (kinda "randomly", so you will not see it unless testing rigorously). – Sz. Jun 05 '20 at 14:54
  • Hey Sz. Thanks for pushing a little more with this. I did read it though I admit I did skim the middle comments. I updated the answer again in a couple of ways due to your comments. One was to make it clear that there is more down below, the other is to make it clear that it's likely this will fail sometimes ... if you see any errors please feel free to let me know and I'll update again. – MER Jun 06 '20 at 19:53
6

Looking at a response on the PHP DateTime manual:

DateTime does not support split seconds (microseconds or milliseconds etc.) I don't know why this isn't documented. The class constructor will accept them without complaint, but they are discarded. There does not appear to be a way to take a string like "2012-07-08 11:14:15.638276" and store it in an objective form in a complete way.

So you cannot do date math on two strings such as:

<?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
)

*/
?>

You get back 0 when you actually want to get 0.251066 seconds.


However, taking a response from here:

$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>";

Recommended and use dateTime() class from referenced:

$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).

Reference of dateTime() on php.net: http://php.net/manual/en/datetime.construct.php#

Community
  • 1
  • 1
Ben
  • 8,894
  • 7
  • 44
  • 80
  • With this solution, I can't compare microtimes with ease, and creating a DateTime object with microtime takes a lot of ressources (I'm going to use it a LOT). – FrancoisBaveye Nov 13 '15 at 11:32
  • Everything above was true when you wrote it, but recent versions of PHP do support native construction of `DateTime` objects using microsecond, so your first example does now print 0.251066 seconds. I'm not sure when it changed, but it works fine in PHP 7.3, which is the oldest version I have to hand. Assuming you don't care about ancient versions of PHP, just do `$d = new DateTime(); print $d->format('Y-m-d H:i:s.u');`, and it'll work. – Richard Smith Feb 06 '23 at 19:39
4

/!\ EDIT /!\

I now use https://github.com/briannesbitt/Carbon, the rest of this answer is just here for historical reasons.

END EDIT

I decided to extend the class DateTime using the tips you all gave me.

The constructor takes a float (from microtime) or nothing (in this case it will be initialized with the current "micro-timestamp"). I also overrided 2 functions that were important : setTimestamp and getTimestamp.

Unfortunately, I couldn't solve the performances issue, although it's not as slow as I thought.

Here's the whole class :

<?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;
    }
}
FrancoisBaveye
  • 1,902
  • 1
  • 18
  • 25
3

There are multiple options. But as already provided by Ben, I will try to give you another solution.

If you provided more details on what kind of calculations you want to do it could be changed further.

$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");

or

$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");

or

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");

or

list($usec, $sec) = explode(' ', microtime());
print date('Y-m-d H:i:s', $sec) . $usec;
davejal
  • 6,009
  • 10
  • 39
  • 82
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;
  }
Valerii
  • 11
  • 1
1

This worked for me in PHP 7.2:

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

I got to thinking that since the format code 'u' would output only the microsecond part of a date when converting to a string then doing the reverse would be the same. And that it also expects a period character '.' so if $aFloat happened to be a whole number then default conversion to a string would leave off the decimal point. Initially I thought the float to string conversion needed '%.6f' but the 'u' is expecting a string which is left justified. Trailing zeros are unnecessary.

diskerror
  • 21
  • 4
  • This was chosen because if a number is handed to the constructor then it must be an integer and setTimestamp only accepts an integer. I haven't looked at how DateInterval works but that would require more steps. – diskerror Aug 01 '19 at 06:06
0

since I resolved my issue i want to share it with You. Php71+ have microsecconds accuracy, if You want to convert it into nano accuracy just multiply it by 1000 (10^3).

$nsTimestamp = (int) (new \DateTime())->getTimestamp() * 1000
0
$micro_seconds = microtime(false) * 1000000;
echo date('Y-m-d H:i:s.'. floor($micro_seconds));

more about date: https://www.php.net/manual/en/function.date.php

more about microtime: https://www.php.net/manual/en/function.microtime.php

Onitech
  • 85
  • 1
  • 15
0

I'm a little late with this, but I had to develop a solution that works for PHP 5, PHP 7.0, and PHP 7.1+.

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

This gives you a valid UTC timestring with microseconds. If you need still need it as a DateTime object, you can just pass this string directly:

$dt = new DateTime($z);

This works from PHP 5.2 if you need the DateTime, but if you just need the string with microseconds it's good all the way back to PHP 4.

Sarke
  • 2,805
  • 2
  • 18
  • 28
0

Beautiful date and time, step by step:

Note that the microtime() tells time AND microtime (numbers after the period)

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

So let's take the numbers after the period:

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

But if for a moment you only had 1 or 2 digits after the period? We complete with zeros...

Ok, now we always have 4 digits which are the microseconds

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

So, let's add the date... Final result:

$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
Fellipe Sanches
  • 7,395
  • 4
  • 32
  • 33