9

In the code below, I am calculating now epoch and beginning of current day epoch.

import time
import pytz
from datetime import datetime

tz1 = pytz.timezone('CST6CDT')
utc = pytz.timezone('UTC')
now = pytz.UTC.localize(datetime.utcnow())
now_tz = now.astimezone(tz1)
print now_tz
print now_tz.strftime('%s')

begin_day = now_tz.replace(hour=0, minute=0, second=0)
print begin_day

print begin_day.strftime('%s')

print statements:

2012-08-28 13:52:21.595718-05:00
1346187141
2012-08-28 00:00:00.595718-05:00
1346137200

Converting epochs to timestamp with CDT timezone: 1346187141 - Aug 28 2012 15:52:21, 1346137200 - Aug 28 2012 02:00:00

I'd like the second epoch to be beginning of the day but it's 2 am. It looks like it is still using local timezone PST when converting to epoch.

What am I doing wrong ? or can this be done a different way?

Thanks!

jfs
  • 399,953
  • 195
  • 994
  • 1,670
t0x13
  • 351
  • 2
  • 10
  • 24

3 Answers3

33

To convert a datetime with timezone to epoch (POSIX timestamp):

from datetime import datetime
import pytz

tz = pytz.timezone('CST6CDT')

# a datetime with timezone
dt_with_tz = tz.localize(datetime(2012, 8, 28, 19, 33, 50), is_dst=None)

# get timestamp
ts = (dt_with_tz - datetime(1970, 1, 1, tzinfo=pytz.utc)).total_seconds()
# -> 1346200430.0

It is how datetime.timestamp method is implemented for timezone-aware datetime objects in Python 3.

To get "now epoch":

from datetime import datetime

now_epoch = (datetime.utcnow() - datetime(1970, 1, 1)).total_seconds()

Or (assuming time uses POSIX epoch):

import time

now_epoch = time.time()

Getting "beginning of current day epoch" is more complex because current day may be different in different timezones:

from datetime import datetime, time
import pytz

tz = pytz.timezone('CST6CDT')

# get current date in given timezone
today = datetime.now(tz).date()
# -> datetime.date(2013, 6, 22)

# get beginning of current day in given timezone as a datetime with timezone
midnight = tz.localize(datetime.combine(today, time(0, 0)), is_dst=None)
# -> datetime.datetime(2013, 6, 22, 0, 0, tzinfo=<DstTzInfo 'CST6CDT'...>)

# get timestamp
ts = (midnight - datetime(1970, 1, 1, tzinfo=pytz.utc)).total_seconds()
# -> 1371877200.0 

See How do I get the UTC time of “midnight” for a given timezone?.

To get "beginning of current day epoch" assuming UTC date:

from datetime import datetime, date

# get current date in UTC
utc_date = datetime.utcnow().date()
# -> datetime.date(2013, 6, 23)

# get timestamp
ts = (utc_date - date(1970, 1, 1)).days * 86400
# -> 1371945600

See Converting datetime.date/datetime.datetime to UTC timestamp in Python.

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

NOTE: My answer is flat-out wrong. (I'd like to delete it, but am unable to do so until the accept flag is removed.)

Please see J.F.Sebastian's answer.

Here is code demonstrating a value of now_tz for which our two methods produce different results.

import calendar
import pytz
import datetime as dt

tz1 = pytz.timezone('US/Eastern')
utc = pytz.timezone('UTC')
now = utc.localize(dt.datetime(2002, 10, 28), is_dst=None)
now_tz = now.astimezone(tz1)
now_epoch = calendar.timegm(now_tz.utctimetuple())
begin_day = tz1.normalize(now_tz.replace(hour=0, minute=0, second=0))

midnight = tz1.localize(dt.datetime.combine(now_tz, dt.time(0, 0)), is_dst=None)
if begin_day != midnight:
    print(begin_day)
    # 2002-10-27 01:00:00-04:00  # my result -- is not midnight
    print(midnight)
    # 2002-10-27 00:00:00-04:00  # J.F.Sebastian's result is correct

(Original answer redacted)

Community
  • 1
  • 1
unutbu
  • 842,883
  • 184
  • 1,785
  • 1,677
  • Use `America/Chicago` instead of `CST6CST`. Only a handful of POSIX time zones are in the tzdb, and they're only there for backwards compatibility support. – Matt Johnson-Pint Jul 16 '14 at 15:51
  • note: `now_tz.replace(hour=0,...)` can return unnormalized datetime object if midnight has different utc offset from now_tz in tz1 timezone. `tz1.normalize()` call might be necessary so that tzinfo would reflect the correct timezone. The result is undefined if there is a DST change around midnight (Brazil). [`tz.localize(is_dst=None)` raises an exception in this case](http://stackoverflow.com/a/17257177/4279) instead of silently returning a wrong answer (though it might be appropriate to suppress exceptions sometimes). – jfs Aug 18 '14 at 19:05
  • @J.F.Sebastian: Thanks for the comment. I'm not able to [reproduce the problem](http://ideone.com/04FzKy). Would you please provide an example? – unutbu Aug 18 '14 at 20:22
  • @unutbu: easy. There are **two** issues: 1. [unnormalized datetime object due to `.replace()`](https://gist.github.com/zed/f6d6e0d10cf0ead414d6) 2. Midnight (local time) might be ambiguous or even non-existent (e.g., due to DST): `pytz.timezone('Brazil/East').localize(datetime(2014, 10, 19), is_dst=None)` – jfs Aug 18 '14 at 21:24
  • @J.F.Sebastian: Thanks for the help; I feel your understanding of these issues is much better than mine. Please correct me if I'm wrong: (1) normalizing after calling `replace` is a good thing, even though [it does not change the final answer](https://gist.github.com/unutbu/85d555969e304cedda2e). (2) While localizing to a timezone which observes DST may produce ambiguous or non-existent dates, there is never any difficulty localizing to UTC since it does not observe DST. So `utc.localize` is always safe, and therefore `is_dst=None` is not needed in the code above. – unutbu Aug 18 '14 at 22:09
  • (1) `begin_day` in [your example](https://gist.github.com/unutbu/85d555969e304cedda2e) is in inconsistent state: break-down time doesn't correspond to tzname, utcoffset (wrong tzinfo). I don't know which quirk of the implementation makes `utctimetuple()` values equal while utcoffset() are different for un/normalized dates. (2) UTC is a special case (same utc offset, always). You don't need `localize()` in this case, you could use `tzinfo` parameter directly e.g., `datetime(1970, 1, 1, tzinfo=pytz.utc)`. – jfs Aug 18 '14 at 22:41
  • @J.F.Sebastian: Regarding (1), could it be that a non-normalized date is not *inconsistent*, it is merely non-standard: `begin_day == begin_day2` is True. – unutbu Aug 18 '14 at 22:45
  • note: `.normalize()` call ("standard" representation) makes it clear that `begin_day` is not a midnight. Midnight (local time, US/Eastern) on `2002-10-27` is a different moment in time ([`midnight = tz.localize(datetime(2002, 10, 27), is_dst=None)`](http://stackoverflow.com/a/17257177/4279)) i.e., your answer can produce *wrong* result. – jfs Aug 19 '14 at 00:10
  • now, `begin_day.hour != 0` (not a midnight) sometimes i.e., `normalize()` exposes the issue but it doesn't fix it. My answer shows one way to avoid the issue. – jfs Aug 19 '14 at 01:39
0

the latest release of simple-date (version 0.2 on pypi) will manage the details for you:

>>> from simpledate import *
>>> now_utc = SimpleDate(tz='UTC')
>>> now_tz = now_utc.convert(tz='CST6CDT')
>>> begin_day = now_tz.replace(hour=0, minute=0, second=0, microsecond=0)
>>> now_utc.timestamp
1371950295.777453
>>> now_tz.timestamp
1371950295.777453
>>> begin_day.timestamp
1371877200.0

we can go backwards to check the timestamps (although it's clear above that switching timezone didn't change the epoch, while moving to start of day did):

>>> SimpleDate(1371877200.0, tz='CST6CDT')
SimpleDate('2013-06-22 00:00:00.000000 CDT', tz='CST6CDT')
>>> SimpleDate(1371877200.0, tz='UTC')
SimpleDate('2013-06-22 05:00:00.000000 UTC')
andrew cooke
  • 45,717
  • 10
  • 93
  • 143
  • `.replace()` call fails (infinite recursion) e.g., `SimpleDate('2014-08-20 03:29:35.203607 UTC').replace(hour=0, minute=0, second=0, microsecond=0)` (simple-date==0.4.8). – jfs Aug 20 '14 at 03:33