0

I have a string with a date in UTC and tzinfo

"Thu, 01 Oct 2015 00:02:01 +0200"

How can I do to convert it to my local time so it outputs

"2015-10-02 02:02:01"

I tried

parser.parse("Thu, 01 Oct 2015 00:02:01 +0200")

but I can't find a way to sum this tzinfo to the time to get my local time.

Thanks in advance

Edit: The question is diferent as It includes time diference in the given string and the point is to add or sutract this time diference that sometimes requires to change the date as in the provided example.

Pep
  • 59
  • 5
  • Possible duplicate of [Python - Convert UTC datetime string to local datetime](http://stackoverflow.com/questions/4770297/python-convert-utc-datetime-string-to-local-datetime) – Prune Oct 01 '15 at 22:32
  • 1
    Do you mean `datetime.strptime(dte, "%a, %d %b %Y %H:%M:%S %z").astimezone(tz=None)`? – Padraic Cunningham Oct 01 '15 at 22:38
  • @Prune not quite the same, in this case the timezone itself isn't known, only the offset (as a string). It might still be a duplicate, but I don't know where it might be. – Mark Ransom Oct 01 '15 at 22:55
  • @PadraicCunningham it depends on the platform - Windows doesn't recognize `%z`, although I think they might have fixed that in some version of Python 3. – Mark Ransom Oct 01 '15 at 23:01
  • @MarkRansom, I think it is good for >= 3.3 but I don' t ever use windows so I could be wrong – Padraic Cunningham Oct 01 '15 at 23:07
  • %z doesn't seems to work. 'z' is a bad directive in format 'a, %d %b %Y %H:%M:%S %z'. It's both a windows and linux plataform script. Python version is 2.7 – Pep Oct 02 '15 at 06:48
  • @PadraicCunningham: even on Python 3, `astimezone(tz=None)` may fail for past/future dates -- you may need the tz database, to get the correct answer. – jfs Oct 02 '15 at 09:34

2 Answers2

4

Here's a stdlib solution:

#!/usr/bin/env python
from datetime import datetime
from email.utils import parsedate_tz, mktime_tz

timestamp = mktime_tz(parsedate_tz("Thu, 01 Oct 2015 00:02:01 +0200"))
print(datetime.fromtimestamp(timestamp)) # get local time

The input time format is rfc 5322 time format that is understood by email module. datetime.fromtimestamp() may fail for past/future dates if it doesn't use a historical time zone database on a given platform. A portable solution is to use pytz module, to get access to the tz database:

#!/usr/bin/env python
from datetime import datetime
from email.utils import parsedate_tz, mktime_tz
import tzlocal # $ pip install tzlocal

local_timezone = tzlocal.get_localzone() # return pytz tzinfo
timestamp = mktime_tz(parsedate_tz("Thu, 01 Oct 2015 00:02:01 +0200"))
print(datetime.fromtimestamp(timestamp, local_timezone))
Community
  • 1
  • 1
jfs
  • 399,953
  • 195
  • 994
  • 1,670
  • The input time format is also compatible with the older [rfc 2822](http://tools.ietf.org/html/rfc2822.html#section-3.3) specification, which is what the Python 2.x [documentation](https://docs.python.org/2/library/email.util.html#module-email.utils) references. Nice portable and succinct answer. +1 – martineau Oct 02 '15 at 11:02
  • @martineau: rfc 5322 obsoletes 2822 – jfs Oct 02 '15 at 11:03
  • Yes, I know...was only pointing out why older code written per rfc 2822 also works. – martineau Oct 02 '15 at 11:10
0

I think the following does what you want by implementing a couple of concrete tzinfo classes:

from datetime import datetime, timedelta, tzinfo
import time as _time

_ZERO = timedelta(0)
_STDOFFSET = timedelta(seconds=-_time.timezone)
if _time.daylight:
    _DSTOFFSET = timedelta(seconds=-_time.altzone)
else:
    _DSTOFFSET = _STDOFFSET

_DSTDIFF = _DSTOFFSET - _STDOFFSET

class UTC(tzinfo):
    """ Concrete tzinfo time zone class for UTC
    """
    def utcoffset(self, dt):
        return _ZERO
    def tzname(self, dt):
        return "UTC"
    def dst(self, dt):
        return _ZERO

UTC = UTC()

class LocalTimezone(tzinfo):
    """ Concrete tzinfo time zone class for current timezone
    """
    def utcoffset(self, dt):
        if self._isdst(dt):
            return _DSTOFFSET
        else:
            return _STDOFFSET

    def dst(self, dt):
        if self._isdst(dt):
            return _DSTDIFF
        else:
            return _ZERO

    def tzname(self, dt):
        return _time.tzname[self._isdst(dt)]

    # everything depends on this private method which assumes 'dt' argument
    # is in local time
    def _isdst(self, dt):
        tt = (dt.year, dt.month, dt.day,
              dt.hour, dt.minute, dt.second,
              dt.weekday(), 0, -1)  # last -1 means dst unknown (mktime will fill in)
        stamp = _time.mktime(tt)  # mktime returns secs
        tt = _time.localtime(stamp)  # localtime returns time_struct with tm_isdst attribute
        return tt.tm_isdst > 0

LOCAL = LocalTimezone()

stamp = 'Thu, 01 Oct 2015 00:02:01 +0200'
dt = datetime(*_time.strptime(' '.join(stamp.split()[:-1]),
                              '%a, %d %b %Y %H:%M:%S')[:6], tzinfo=UTC)
local_dt = dt.astimezone(LOCAL)
print(local_dt.strftime('%Y-%M-%d %H:%M:%S'))
martineau
  • 119,623
  • 25
  • 170
  • 301
  • ¡Uau! That works defintely but I never imagine that It would be neccesary to much work. I though that could be done in a couple of lines. – Pep Oct 02 '15 at 07:28
  • @Pep: `LocalTimezone` that uses [`time.timezone`, `time.daylight` may fail in some edge cases for the current time](http://stackoverflow.com/a/3168394/4279) and it definitely may fail for past/future dates if the local utc offset might have been different in the past. – jfs Oct 02 '15 at 09:42
  • Yes, a fair amount of code is needed because stock Python only defines an abstract `tzinfo` class. Despite that, what I've provided is an attempt to only enough of a concrete implementation to solve your problem in a generic way using only built-in modules. Alternatives like the [`pytz`](https://pypi.python.org/pypi/pytz/) module contains a whole lot more code. The need for so much code is due to the surprising complexity of a seemingly simple but extremely idiomatic practice (which, ironically, has varied over time). – martineau Oct 02 '15 at 10:30
  • do you understand that utc offset may be different at different times (unrelated to DST transitions) and therefore `time.timezone` that is a _constant_ can't possibly provide a correct answer in this case? – jfs Oct 04 '15 at 05:08