datetime: Round/trim number of digits in microseconds

Currently I am logging stuff and I am using my own formatter with a custom formatTime():

def formatTime(self, _record, _datefmt):
    t = datetime.datetime.now()
    return t.strftime('%Y-%m-%d %H:%M:%S.%f')

My issue is that the microseconds, %f, are six digits. Is there anyway to spit out less digits, like the first three digits of the microseconds?


Solution 1:

The simplest way would be to use slicing to just chop off the last three digits of the microseconds:

def format_time():
    t = datetime.datetime.now()
    s = t.strftime('%Y-%m-%d %H:%M:%S.%f')
    return s[:-3]

I strongly recommend just chopping. I once wrote some logging code that rounded the timestamps rather than chopping, and I found it actually kind of confusing when the rounding changed the last digit. There was timed code that stopped running at a certain timestamp yet there were log events with that timestamp due to the rounding. Simpler and more predictable to just chop.

If you want to actually round the number rather than just chopping, it's a little more work but not horrible:

def format_time():
    t = datetime.datetime.now()
    s = t.strftime('%Y-%m-%d %H:%M:%S.%f')
    head = s[:-7] # everything up to the '.'
    tail = s[-7:] # the '.' and the 6 digits after it
    f = float(tail)
    temp = "{:.03f}".format(f)  # for Python 2.x: temp = "%.3f" % f
    new_tail = temp[1:] # temp[0] is always '0'; get rid of it
    return head + new_tail

Obviously you can simplify the above with fewer variables; I just wanted it to be very easy to follow.

Solution 2:

As of Python 3.6 the language has this feature built in:

def format_time():
    t = datetime.datetime.now()
    s = t.isoformat(timespec='milliseconds')
    return s

Solution 3:

This method should always return a timestamp that looks exactly like this (with or without the timezone depending on whether the input dt object contains one):

2016-08-05T18:18:54.776+0000

It takes a datetime object as input (which you can produce with datetime.datetime.now()). To get the time zone like in my example output you'll need to import pytz and pass datetime.datetime.now(pytz.utc).

import pytz, datetime


time_format(datetime.datetime.now(pytz.utc))

def time_format(dt):
    return "%s:%.3f%s" % (
        dt.strftime('%Y-%m-%dT%H:%M'),
        float("%.3f" % (dt.second + dt.microsecond / 1e6)),
        dt.strftime('%z')
    )   

I noticed that some of the other methods above would omit the trailing zero if there was one (e.g. 0.870 became 0.87) and this was causing problems for the parser I was feeding these timestamps into. This method does not have that problem.

Solution 4:

An easy solution that should work in all cases:

def format_time():
    t = datetime.datetime.now()
    if t.microsecond % 1000 >= 500:  # check if there will be rounding up
        t = t + datetime.timedelta(milliseconds=1)  # manually round up
    return t.strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]

Basically you do manual rounding on the date object itself first, then you can safely trim the microseconds.