3

Let's say I receive an arbitrary datetime object in a request, like this, which could be coming from any possible timezone - I don't know which one. For the sake of example, pretend it comes from the East Coast

import pytz
from colander import iso8601
...
    ests1 = iso8601.parse_date('2016-04-01T00:00:00.0000-04:00')

pretend ests1 is the object that comes in

Using pytz, I can find out a bit about it's timezone

    etz = ests1.timetz()  # 00:00:00-04:00
    etzi = ests1.tzinfo   # <FixedOffset '-04:00' datetime.timedelta(-1, 72000)>
    etzn = ests1.tzname() # '-04:00'  # EST
    edst = ests1.dst()    #  '0:00:00'

Note that no matter what date ests1 is - dst() always seems to return the same value

I would like to do something like this:

    psts1 = pytz.timezone(etzn
                     ).localize(dtime.datetime.fromtimestamp(ests1)
                     ).astimezone(pytz.timezone('US/Pacific'))

but that just results in

UnknownTimeZoneError: '-04:00'

I have also tried this:

def convert_dt_to_pstz(dt):
    pstoffset = -25200 # -7 * 60 * 60   - but what about DST?  how do we tell which offset to use? -8 or -7
    return dt.astimezone(tzoffset(None,pstoffset)) # convert to PST

But the problem with that is I don't know how to tell if the date needs to be adjusted for daylight savings time.

I also tried this:

    utc_date = ests1.astimezone(pytz.utc)   # 2016-04-01T04:00:00+00:00
    ptz = pytz.timezone('US/Pacific')
    new_date = utc_date.replace(tzinfo=ptz) # 2016-04-01T04:00:00-07:53

But look at the strange result for new_date: -07:53 ??

Please note that I cannot use any information corresponding to "now" or the location of the server as it could be anywhere

slashdottir
  • 7,835
  • 7
  • 55
  • 71
  • 3
    Note that a pure numeric offset gives you no information about whether daylight saving time is in effect or not. You have to know more information about how the time was generated to be able to deduce that `-04:00` is from the US/Eastern time zone rather than, say, from Brazil. However, that's a detail — I think your question is interesting. One part of the answer is 'convert to UTC from source time zone; convert to target time zone from UTC'. There are then issues with how to do those conversions precisely. – Jonathan Leffler Apr 01 '16 at 17:57
  • from iso8601 format you are not able to tell, it will say -0400 for EDT and -0500 for EST, but -0400 could be AMT as well – scope Apr 01 '16 at 18:10
  • @scope: Well, there are ways to determine Daylight Savings Time - but it's complex and I wonder if there is something out there already built that will do the job. I haven't found it yet though – slashdottir Apr 01 '16 at 18:12
  • 1
    @slashdottir, you can from timezone (with pytz), but not from UTC offset – scope Apr 01 '16 at 18:17

2 Answers2

3

To convert a timezone-aware datetime object into a different timezone, use datetime.astimezone() method:

pacific_time = ests1.astimezone(pytz.timezone('US/Pacific'))

In general, pytz docs recommend to call pytz.timezone('US/Pacific').normalize() on the result but you don't need it here because ests1 timezone has a fixed utc offset.

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

I think I got it right this time:

from datetime import datetime as dtime
import pytz
from colander import iso8601

ests1 = iso8601.parse_date('2016-04-01T00:00:00.0000-04:00')
epoch = dtime(1970, 1, 1, tzinfo=pytz.UTC)          # 1970-01-01T00:00:00+00:00
ept = (ests1 - epoch).total_seconds()               # 1459483200.0

utc_date = dtime.fromtimestamp(ept, tz=pytz.UTC)    # 2016-04-01T04:00:00+00:00
ptz = pytz.timezone('US/Pacific')
pst_date = dtime.fromtimestamp(ept, tz=ptz)         # 2016-03-31T21:00:00-07:00

ests2 = iso8601.parse_date('2016-02-01T00:00:00.0000-04:00')
ept2 = (ests2 - epoch).total_seconds()              # 1454299200.00

utc_date2 = dtime.fromtimestamp(ept2, tz=pytz.UTC)  # 2016-02-01T04:00:00+00:00
pst_date2 = dtime.fromtimestamp(ept2, tz=ptz)       # 2016-01-31T20:00:00-08:00

so, it seems to be as simple as

def convert_to_tz(dt,tz_new):
   seconds = (dt - epoch).total_seconds()
   return dtime.fromtimestamp(seconds, tz=tz_new)
slashdottir
  • 7,835
  • 7
  • 55
  • 71