283

I have dt = datetime(2013,9,1,11), and I would like to get a Unix timestamp of this datetime object.

When I do (dt - datetime(1970,1,1)).total_seconds() I got the timestamp 1378033200.

When converting it back using datetime.fromtimestamp I got datetime.datetime(2013, 9, 1, 6, 0).

The hour doesn't match. What did I miss here?

Anthony Kong
  • 37,791
  • 46
  • 172
  • 304
kun
  • 3,917
  • 3
  • 15
  • 21
  • 4
    related: [Converting datetime.date to UTC timestamp in Python](http://stackoverflow.com/q/8777753/4279) – jfs Nov 06 '13 at 00:32
  • 2
    As a side note, if you're using Python 3.3+, you really, really want to use the [`timestamp`](http://docs.python.org/3.3/library/datetime.html#datetime.datetime.timestamp) method instead of trying to do it yourself. For one thing, that completely avoids the possibility of subtracting naive times from different timezones. – abarnert Nov 06 '13 at 00:32
  • 1
    Where does `dt` come from? Is it a local time or time in UTC? – jfs Nov 06 '13 at 00:34
  • 2
    @abarnert I'd also like to submit a request to purge all codepages and unicode symbols, that is, to outlaw all non-ascii and non-default-codepage languages. Paintings of symbols are still allowed. – Daniel F Dec 01 '14 at 08:32
  • 7
    @DanielF: Request denied. I'm not interested in creating a one-world language here, and even if we did I wouldn't want to make life impossible for historical linguists and Tolkien scholars. Unicode already solves the problem, except that certain organizations and products (the TRON consortium, Japanese software that uses Shift-JIS over UTF-8, Microsoft still delivering an OS that defaults to cp1252 for user text files, and various SDKs that pretend that UTF-16 is a fixed-width charset and/or the same thing as Unicode) need to be punished to bring them in line. – abarnert Dec 01 '14 at 19:52
  • Just take a `int(dt.timestamp())` – KyungHoon Kim Jul 23 '19 at 05:46
  • `int(dt.strftime("%s"))` does not work correctly and it shows a wrong unix time. The first one works (well it is the definition). – Chenxi Zeng Oct 02 '19 at 22:49

12 Answers12

186

solution is

import time
import datetime
d = datetime.date(2015,1,5)

unixtime = time.mktime(d.timetuple())
DmitrySemenov
  • 9,204
  • 15
  • 76
  • 121
171

If you want to convert a python datetime to seconds since epoch you should do it explicitly:

>>> import datetime
>>> datetime.datetime(2012, 04, 01, 0, 0).strftime('%s')
'1333234800'
>>> (datetime.datetime(2012, 04, 01, 0, 0) - datetime.datetime(1970, 1, 1)).total_seconds()
1333238400.0

In Python 3.3+ you can use timestamp() instead:

>>> import datetime
>>> datetime.datetime(2012, 4, 1, 0, 0).timestamp()
1333234800.0
Francisco Costa
  • 6,713
  • 5
  • 34
  • 43
  • 11
    Doesn't take timezones into account. – Blairg23 Mar 28 '17 at 01:46
  • 8
    You do not want to use `%s`, as it will be localized to the clock of the system you are currently on. You should only ever use `.timestamp()` to get the correct Epoch/UNIX time. – Blairg23 Apr 17 '17 at 17:21
  • 4
    `%s` does not work on Windows systems (`ValueError: Invalid format string`) – chrki May 03 '17 at 08:13
  • 6
    any particular reason for the result of `timestamp()` being a float? Is it safe to turn it into an `int`? IE will the return value of `timestamp()` ever return some decimal I should take into consideration? – Aram Maliachi Jan 29 '21 at 18:57
  • When I input: dt.datetime(2012, 4, 1, 0, 0).strftime('%s'), I get error: Invalid format string! It seems to have to work, but why does it not? – Daniil Apr 14 '22 at 10:24
  • 1
    @Daniil change `dt.datetime` to `datetime.datetime` – Francisco Costa Apr 18 '22 at 09:42
  • 1
    @FranciscoCosta Cannot believe I didn't alias datetime! :D Thank you! I'd like to tell that you can get full time (s + ms) of Unix epoch by writing: `ms = 1e6 * int(now.strftime('%s')) + now.microsecond` where `now = dt.datetime.now()` where `dt` is `datetime` module! – Daniil Apr 18 '22 at 10:01
140

What you missed here is timezones.

Presumably you've five hours off UTC, so 2013-09-01T11:00:00 local and 2013-09-01T06:00:00Z are the same time.

You need to read the top of the datetime docs, which explain about timezones and "naive" and "aware" objects.

If your original naive datetime was UTC, the way to recover it is to use utcfromtimestamp instead of fromtimestamp.

On the other hand, if your original naive datetime was local, you shouldn't have subtracted a UTC timestamp from it in the first place; use datetime.fromtimestamp(0) instead.

Or, if you had an aware datetime object, you need to either use a local (aware) epoch on both sides, or explicitly convert to and from UTC.

If you have, or can upgrade to, Python 3.3 or later, you can avoid all of these problems by just using the timestamp method instead of trying to figure out how to do it yourself. And even if you don't, you may want to consider borrowing its source code.

(And if you can wait for Python 3.4, it looks like PEP 341 is likely to make it into the final release, which means all of the stuff J.F. Sebastian and I were talking about in the comments should be doable with just the stdlib, and working the same way on both Unix and Windows.)

abarnert
  • 354,177
  • 51
  • 601
  • 671
  • 1
    if `dt` is in local timezone then the formula in the question is incorrect `datetime.fromtimestamp(0)` (epoch in the current timezone) should be used instead of `datetime(1970, 1,1)` (unix epoch in UTC). – jfs Nov 06 '13 at 00:38
  • @J.F.Sebastian: I didn't want to cover all of the three possibilities in detail, but I guess you're right, I should. – abarnert Nov 06 '13 at 00:52
  • btw, `fromtimestamp(0)` might fail if the system doesn't store historical timezone information e.g., on Windows. `pytz` could be used in this case. – jfs Nov 06 '13 at 01:14
  • @J.F.Sebastian: But `pytz` doesn't help unless you already know which timezone you're in; to do that programmatically, you need a different library that gets the current Windows timezone and converts it to a `pytz` timezone and/or looks it up by name. – abarnert Nov 06 '13 at 18:37
  • there is `tzlocal` module that also works on Windows. Here's how you could [convert utc time to local time](http://stackoverflow.com/a/13287083/4279). – jfs Nov 06 '13 at 18:59
  • @J.F.Sebastian: Exactly: `tzlocal` is a separate library, not part of `pytz`. You can also do it with `dateutil` and various other alternatives. (When picking one of the many alternatives: on Windows, you want one that maps the Windows names to standard names and looks them up in `pytz`, or you end up with the same incomplete/broken data; on modern POSIX systems, you want one that parses `/etc/localtime` and builds a timezone object on the fly, or you end up with ambiguous name problems…) – abarnert Nov 06 '13 at 19:25
  • `dateutil` fails in this case (when `.fromtimestamp(0)` fails) – jfs Nov 06 '13 at 19:31
  • to clarify: `tzlocal` returns `pytz` timezone i.e., `tzlocal` figures out the local timezone (it supports Windows names too) and `pytz` provides the actual historical timezone data. – jfs Jan 13 '15 at 08:41
54

Rather than this expression to create a POSIX timestamp from dt,

(dt - datetime(1970,1,1)).total_seconds()

Use this:

int(dt.strftime("%s"))

I get the right answer in your example using the second method.

EDIT: Some followup... After some comments (see below), I was curious about the lack of support or documentation for %s in strftime. Here's what I found:

In the Python source for datetime and time, the string STRFTIME_FORMAT_CODES tells us:

"Other codes may be available on your platform.
 See documentation for the C library strftime function."

So now if we man strftime (on BSD systems such as Mac OS X), you'll find support for %s:

"%s is replaced by the number of seconds since the Epoch, UTC (see mktime(3))."

Anyways, that's why %s works on the systems it does. But there are better solutions to OP's problem (that take timezones into account). See @abarnert's accepted answer here.

Darren Stone
  • 2,008
  • 13
  • 16
  • 5
    This is undocumented behaviour (I believe). For example on windows it results in "Invalid format string". – Crescent Fresh Dec 10 '13 at 20:18
  • @CrescentFresh, interesting. You may be right. While I don't see `strftime("%s")` in documentation, I did just confirm this to work on Mac and Linux. Thanks. – Darren Stone Dec 11 '13 at 01:14
  • Also, %s gives you an integer, so you lose precision. – Roy Smith Jan 08 '14 at 18:12
  • 1
    apparently it ignores the tzinfo field. – Daniel F Dec 01 '14 at 08:41
  • `"%s"` is not supported -- it fails on Windows, for timezone-aware datetime objects. You could use [`time.mktime(dt.timetuple())` instead](http://stackoverflow.com/a/27914405/4279) – jfs Jan 13 '15 at 08:46
  • Yup, I've updated my answer above to help explain why some systems have `%s` support and others don't. Anyways, I recommend @abarnert's answer. – Darren Stone Jan 14 '15 at 06:38
  • 1
    @DarrenStone: you don't need to read the source. Both `time.strftime()` and `datetime.strftime` documentation delegate to the platforms `strftime(3)` function for unsupported directives. `%s` may fail even on Mac OS X e.g., [datetime.strftime('%s') should respect tzinfo](http://bugs.python.org/issue12750). – jfs Jan 14 '15 at 08:37
  • 2
    You should never use `%s` as you will only use the localized system time of the system you're on. You want to use `.timestamp()` if you are trying to get the real UNIX timestamp. – Blairg23 Apr 17 '17 at 17:20
  • Can confirm; this does NOT work on the default installation of Python on OS X. It will account for the timezone when it should not. –  Jun 13 '19 at 21:24
12

For working with UTC timezones:

time_stamp = calendar.timegm(dt.timetuple())

datetime.utcfromtimestamp(time_stamp)
Emin Temiz
  • 341
  • 2
  • 6
7

You've missed the time zone info (already answered, agreed)

arrow package allows to avoid this torture with datetimes; It is already written, tested, pypi-published, cross-python (2.6 — 3.xx).

All you need: pip install arrow (or add to dependencies)

Solution for your case

dt = datetime(2013,9,1,11)
arrow.get(dt).timestamp
# >>> 1378033200

bc = arrow.get(1378033200).datetime
print(bc)
# >>> datetime.datetime(2013, 9, 1, 11, 0, tzinfo=tzutc())
print(bc.isoformat())
# >>> '2013-09-01T11:00:00+00:00'
maxkoryukov
  • 4,205
  • 5
  • 33
  • 54
7

If your datetime object represents UTC time, don't use time.mktime, as it assumes the tuple is in your local timezone. Instead, use calendar.timegm:

>>> import datetime, calendar
>>> d = datetime.datetime(1970, 1, 1, 0, 1, 0)
>>> calendar.timegm(d.timetuple())
60
Matt Kramer
  • 724
  • 9
  • 9
3
def dt2ts(dt, utc=False):
    if utc:
        return calendar.timegm(dt.timetuple())
    if dt.tzinfo is None:
        return int(time.mktime(dt.timetuple()))
    utc_dt = dt.astimezone(tz.tzutc()).timetuple()
    return calendar.timegm(utc_dt)

If you want UTC timestamp :time.mktime just for local dt .Use calendar.timegm is safe but dt must the utc zone so change the zone to utc. If dt in UTC just use calendar.timegm.

wyx
  • 3,334
  • 6
  • 24
  • 44
3
def datetime_to_epoch(d1):
"""
January 1st, 1970 at 00:00:00 UTC is referred to as the Unix epoch
:param d1: input date
:return: seconds since unix epoch
"""
if not d1.tzinfo:
    raise ValueError("date is missing timezone information")

d2 = datetime(1970, 1, 1, tzinfo=timezone.utc)
time_delta = d1 - d2
ts = int(time_delta.total_seconds())
return ts


def epoch_to_datetime_string(timestamp, tz_name="UTC", **kwargs):
"""
method to convert unix timestamp to date time string
:param ts: 10 digit unix timestamp in seconds
:param tz_name: timezone name
:param kwargs: formatter=<formatter-string>
:return: date time string in timezone
"""

naive_date = datetime.fromtimestamp(timestamp)
aware_date = naive_date.astimezone(pytz.timezone(tz_name))
formatter = kwargs.pop("formatter", "%d %b %Y %H:%M:%S")
return aware_date.strftime(formatter)
rjha94
  • 4,292
  • 3
  • 30
  • 37
2

Well, when converting TO unix timestamp, python is basically assuming UTC, but while converting back it will give you a date converted to your local timezone.

See this question/answer; Get timezone used by datetime.datetime.fromtimestamp()

Community
  • 1
  • 1
Ronald Portier
  • 221
  • 1
  • 2
1

This class will cover your needs, you can pass the variable into ConvertUnixToDatetime & call which function you want it to operate based off.

from datetime import datetime
import time

class ConvertUnixToDatetime:
    def __init__(self, date):
        self.date = date

    # Convert unix to date object
    def convert_unix(self):
        unix = self.date

        # Check if unix is a string or int & proceeds with correct conversion
        if type(unix).__name__ == 'str':
            unix = int(unix[0:10])
        else:
            unix = int(str(unix)[0:10])

        date = datetime.utcfromtimestamp(unix).strftime('%Y-%m-%d %H:%M:%S')

        return date

    # Convert date to unix object
    def convert_date(self):
        date = self.date

        # Check if datetime object or raise ValueError
        if type(date).__name__ == 'datetime':
            unixtime = int(time.mktime(date.timetuple()))
        else:
            raise ValueError('You are trying to pass a None Datetime object')
        return type(unixtime).__name__, unixtime


if __name__ == '__main__':

    # Test Date
    date_test = ConvertUnixToDatetime(datetime.today())
    date_test = date_test.convert_date()
    print(date_test)

    # Test Unix
    unix_test = ConvertUnixToDatetime(date_test[1])
    print(unix_test.convert_unix())
0
import time
from datetime import datetime
time.mktime(datetime.now().timetuple())
  • exact duplicate of [this answer](https://stackoverflow.com/a/27914405/10197418), except that it's using a datetime instead of a date – FObersteiner Apr 17 '23 at 06:09