4

So I have two functions for converting python datetime.datetime() objects to and from milliseconds. I cannot figure out where this is going wrong. Here's what I'm working with:

>>> import datetime
>>> def mil_to_date(mil):
    """date items from REST services are reported in milliseconds,
    this function will convert milliseconds to datetime objects

    Required:
        mil -- time in milliseconds
    """
    if mil == None:
        return None
    elif mil < 0:
        return datetime.datetime.utcfromtimestamp(0) + datetime.timedelta(seconds=(mil/1000))
    else:
        return datetime.datetime.fromtimestamp(mil / 1000)

>>> def date_to_mil(date):
    """converts datetime.datetime() object to milliseconds

    date -- datetime.datetime() object"""
    if isinstance(date, datetime.datetime):
        epoch = datetime.datetime.utcfromtimestamp(0)
        return long((date - epoch).total_seconds() * 1000.0)

>>> mil = 1394462888000
>>> date = mil_to_date(mil)
>>> date
datetime.datetime(2014, 3, 10, 9, 48, 8)  #this is correct
>>> d2m = date_to_mil(date)
>>> d2m
1394444888000L
>>> mil
1394462888000L
>>> date2 = mil_to_date(d2m)
>>> date2
datetime.datetime(2014, 3, 10, 4, 48, 8) #why did I lose 5 hours??

For some reason, I am losing 5 hours. Am I overlooking something obvious? Or is there a problem with one or both of my functions?

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
crmackey
  • 349
  • 1
  • 5
  • 20
  • 1
    This is because `fromtimestamp` returns the local time: "If optional argument tz is None or not specified, the timestamp is converted to the platform’s local date and time, and the returned datetime object is naive" – Ella Sharakanski Apr 24 '15 at 17:36
  • 1
    unrelated: `timedelta(milliseconds=mil)` works. – jfs Apr 24 '15 at 20:16
  • related: [Get current time in milliseconds in Python?](http://stackoverflow.com/a/21858377/4279) – jfs Apr 24 '15 at 20:41
  • unrelated: you can use `int(2**300)` even if the result is `long`. – jfs Apr 24 '15 at 20:42
  • `1394462888000 != 1394444888000` – jfs Apr 24 '15 at 21:27

2 Answers2

7

The reason for this is that date_to_mil works with UTC and mil_to_date doesn't. You should replace utcfromtimestamp with fromtimestamp.

Further explanation:

In your code, epoch is the date of the epoch in UTC (but the object is without any time-zone). But date is local since fromtimestamp returns a local time:

If optional argument tz is None or not specified, the timestamp is converted to the platform’s local date and time, and the returned datetime object is naive

So you subtract the UTC epoch from the local datetime and you get a delay which is your local delay to UTC.

Ella Sharakanski
  • 2,683
  • 3
  • 27
  • 47
  • 1
    Oh wow, I was missing something obvious! I should have been treating both as UTC. I didn't notice that my else statement for all dates after the epoch was not using the `.utcfromtimestamp()` method. Thanks! – crmackey Apr 24 '15 at 18:21
  • 1
    @crmackey: it should be emphasized that subtracting UTC epoch from the local datetime is like subtracting pounds from kilograms--the result is non-sense (one should subtract utc epoch from the utc time. If the local timezone had different UTC offset in the past then performing arithmetic on naive datetime objects representing local time with different utc offsets is also wrong. See [Find if 24 hrs have passed between datetimes](http://stackoverflow.com/q/26313520/4279)) – jfs Feb 15 '17 at 16:06
4

If input is UTC then to get POSIX timestamp as integer milliseconds:

from datetime import datetime, timedelta

def timestamp_millis(utc_time, epoch=datetime(1970, 1, 1)):
    """Return milliseconds since Epoch as integer."""
    td = utc_time - epoch
    return (td.microseconds + (td.seconds + td.days * 86400) * 10**6) // 10**3

Note: the formula may produce a different result from: int(td.total_seconds() * 1000).

And in reverse: to get UTC time from POSIX time given as milliseconds:

def datetime_from_millis(millis, epoch=datetime(1970, 1, 1)):
    """Return UTC time that corresponds to milliseconds since Epoch."""
    return epoch + timedelta(milliseconds=millis)

It supports both positive and negative millis.

Note: None handling happens outside these functions.

Example:

>>> datetime_from_millis(1394462888000)
datetime.datetime(2014, 3, 10, 14, 48, 8)
>>> datetime.utcfromtimestamp(1394462888)
datetime.datetime(2014, 3, 10, 14, 48, 8)
>>> timestamp_millis(_)
1394462888000

The result is different from the one in your question!

gmtime(0).year != 1970 and TZ=right/UTC cases are ignored.

Community
  • 1
  • 1
jfs
  • 399,953
  • 195
  • 994
  • 1,670