How to create a non-LMT timezone object by pytz directly? - python

When I try to create a timezone object by pytz, I get a LMT timezone object like this:
>>> import pytz
>>> wrong_tz=pytz.timezone("Asia/Shanghai")
>>> wrong_tz
<DstTzInfo 'Asia/Shanghai' LMT+8:06:00 STD>
If I use this timezone object in datetime constructor, 6 min is missing (see the code in the end).
So I have to do this to get the right timezone object:
>>> from datetime import datetime
>>> dt = datetime(2020, 2, 2)
>>> right_tz = wrong_tz.localize(dt).tzinfo
>>> right_tz
<DstTzInfo 'Asia/Shanghai' CST+8:00:00 STD>
Here is my question: How to get this right_tz directly?
Using a datetime to do the converting is very ugly.
By the way, here is the different between wrong_tz and right_tz:
>>> datetime(2020, 2, 2, tzinfo=right_tz) - datetime(2020, 2, 2, tzinfo=wrong_tz)
datetime.timedelta(seconds=360)

Related

How to serialize time objects while preserving their timezone?

I'm trying to serialize time-objects to ISO8601 format while preserving their timezone information with Python 3.8.
The documentation for time.isoformat states:
time.isoformat(timespec='auto')
Return a string representing the time in ISO 8601 format, one of:
HH:MM:SS.ffffff, if microsecond is not 0
HH:MM:SS, if microsecond is 0
HH:MM:SS.ffffff+HH:MM[:SS[.ffffff]], if utcoffset() does not return None
HH:MM:SS+HH:MM[:SS[.ffffff]], if microsecond is 0 and utcoffset() does not return None
As far as I understand from that documentation I just have to generate a time-object whose utcoffset() isn't None to get the timezone information as part of the ISO8601-formatted string. That works fine when using datetime.timezone.utc as timezone:
>>> from datetime import time, timezone
>>> t = time(1, 2, 3, tzinfo=timezone.utc)
>>> t
datetime.time(1, 2, 3, tzinfo=datetime.timezone.utc)
>>> t.utcoffset()
datetime.timedelta(0)
>>> t.isoformat()
'01:02:03+00:00'
However as soon as I want to use a timezone which isn't provided by the Python standard library, that doesn't seem to work anymore. I tried dateutil and pytz and in both cases got the same result:
>>> from datetime import time
>>> from dateutil.tz import gettz
>>> t = time(1, 2, 3, tzinfo=gettz("US/Eastern"))
>>> t
datetime.time(1, 2, 3, tzinfo=tzfile('/usr/share/zoneinfo/US/Eastern'))
>>> t.utcoffset()
>>> t.isoformat()
'01:02:03'
>>> from datetime import time
>>> from pytz import timezone
>>> t = time(1, 2, 3, tzinfo=timezone("US/Eastern"))
>>> t
datetime.time(1, 2, 3, tzinfo=<DstTzInfo 'US/Eastern' LMT-1 day, 19:04:00 STD>)
>>> t.utcoffset()
>>> t.isoformat()
'01:02:03'
I also tried using time.strftime() instead, however that doesn't solve my problem either:
>>> from datetime import time
>>> from dateutil.tz import gettz
>>> t = time(1, 2, 3, tzinfo=gettz("US/Eastern"))
>>> t.strftime("%H:%M:%S%z")
'01:02:03'
Why is that the case and how can I solve this issue?
Without a date, time doesn't give an unambiguous UTC offset. For example in your case, time zone "US/Eastern" has daylight saving time partly over the year, with UTC offsets EST (UTC-5) and EDT (UTC-4). Python is kind of quiet there, not telling you about it e.g. in a warning.
If you add a date, strftime works:
import datetime as dt
from dateutil.tz import gettz
t_est = dt.datetime.combine(dt.date(2020,1,1), dt.time(1, 2, 3, tzinfo=gettz("US/Eastern")))
t_est.strftime("%H:%M:%S%z")
# '01:02:03-0500'
t_edt = dt.datetime.combine(dt.date(2020,6,6), dt.time(1, 2, 3, tzinfo=gettz("US/Eastern")))
t_edt.strftime("%H:%M:%S%z")
# '01:02:03-0400'
On the other hand, if you provide tzinfo as a pure UTC offset, your code would work. Quoting from the docs:
t = dt.time.fromisoformat('04:23:01+04:00')
# datetime.time(4, 23, 1, tzinfo=datetime.timezone(datetime.timedelta(seconds=14400)))
t.isoformat()
# '04:23:01+04:00'
If you know for example that all your time objects refer to EST, you could use the respective UTC offset:
t = dt.time(1, 2, 3, tzinfo=dt.timezone(dt.timedelta(seconds=3600*-5)))
t.isoformat()
# '01:02:03-05:00'

Test if datetime.datetime is in daylight savings with .dst() function not returning value with pytz timezone

I am trying to test if an arbitrary localized datetime object is in daylight savings time or not.
This is a similar question to many already asked. eg. (Daylight savings time in Python)
In these boards, a way to test if a datetime is in DST is to use the built-in function .dst(). This is wrapped into bool like:
if bool(mydatetimeobject.dst()):
print ("date is DST!")
However, whenever I make an aribtrary datetime and pass a tztimezone the .dst() function does not report anything. Its as if the timezone was not set!
Check this code snippet to see the issue:
>>> from datetime import datetime
>>> import pytz
>>> localtime = pytz.timezone('US/Central')
>>> mydate = datetime(2004,3,1) # day in standard time
>>> localtime.localize(mydate)
datetime.datetime(2004, 3, 1, 0, 0, tzinfo=<DstTzInfo 'US/Central' CST-1 day, 18:00:00 STD>)
>>> mydate.dst()
>>> mydate = datetime(2004,5,1) #day in daylight time
>>> localtime.localize(mydate)
datetime.datetime(2004, 5, 1, 0, 0, tzinfo=<DstTzInfo 'US/Central' CDT-1 day, 19:00:00 DST>)
>>> mydate.dst()
>>> bool(mydate.dst())
False
It works but you need to assign localtime.localize() result to a variable and compare that. Now you just display the output and check .dst() of the original, naive datetime.
a = localtime.localize(mydate)
bool(a.dst())
returns True (assuming mydate is in DST).

Getting the UTC hour from a UTC+2 datetime object

My Time string looks like this:
03/16/16 15:50 UTC+02:00
so I parse it like so
from dateutil import parser
my_date = parser.parse(date_string)
Since this is UTC+2 time, how do I convert this dateobject to UTC?
Using datetime.datetime.astimezone with pytz.UTC (datetime.timezone.utc if you use Python 3.x), you can get the datetime with UTC timezone:
>>> import pytz
>>> from dateutil import parser
>>>
>>> date_string = '03/16/16 15:50 UTC+02:00'
>>> my_date = parser.parse(date_string)
>>> my_date.astimezone(pytz.UTC)
datetime.datetime(2016, 3, 16, 17, 50, tzinfo=<UTC>)

Python convert date string to python date and subtract

I have a situation where I need to find the previous date from the date_entry where the date_entry is string, I managed to do this:
>>> from datetime import timedelta, datetime
>>> from time import strptime, mktime
>>> date_str = '20130723'
>>> date_ = strptime(date_str, '%Y%m%d')
>>> date_
time.struct_time(tm_year=2013, tm_mon=7, tm_mday=23, tm_hour=0, tm_min=0, tm_sec=0, tm_wday=1, tm_yday=204,tm_isdst=-1)
>>> datetime.fromtimestamp(mktime(date_))-timedelta(days=1)
datetime.datetime(2013, 7, 22, 0, 0)
>>>
But, for this I have to import the modules timedelta, datetime, strptime and mktime. I think this really an overkill to solve this simple problem.
Is there any more elegant way to solve this (using Python 2.7) ?
Just use datetime.datetime.strptime class method:
>>> import datetime
>>> date_str = '20130723'
>>> datetime.datetime.strptime(date_str, '%Y%m%d') - datetime.timedelta(days=1)
datetime.datetime(2013, 7, 22, 0, 0)
Chosen answer is old and works on python 2, returns bellow error for python 3.
Error:
AttributeError: type object 'datetime.datetime' has no attribute 'datetime'
Fix + Doing it with PYTHON 3 :
from datetime import datetime,timedelta
date_str = '20130723'
datetime.strptime(date_str, '%Y%m%d') - timedelta(days=1)
Also, use up to date document on Python 3 here: https://docs.python.org/3/library/datetime.html#datetime.datetime.strptime

Converting timezone-aware datetime to local time in Python

How do you convert a timezone-aware datetime object to the equivalent non-timezone-aware datetime for the local timezone?
My particular application uses Django (although, this is in reality a generic Python question):
import iso8601
....
date_str="2010-10-30T17:21:12Z"
....
d = iso8601.parse_date(date_str)
foo = app.models.FooModel(the_date=d)
foo.save()
This causes Django to throw an error:
raise ValueError("MySQL backend does not support timezone-aware datetimes.")
What I need is:
d = iso8601.parse_date(date_str)
local_d = SOME_FUNCTION(d)
foo = app.models.FooModel(the_date=local_d)
What would SOME_FUNCTION be?
In general, to convert an arbitrary timezone-aware datetime to a naive (local) datetime, I'd use the pytz module and astimezone to convert to local time, and replace to make the datetime naive:
In [76]: import pytz
In [77]: est=pytz.timezone('US/Eastern')
In [78]: d.astimezone(est)
Out[78]: datetime.datetime(2010, 10, 30, 13, 21, 12, tzinfo=<DstTzInfo 'US/Eastern' EDT-1 day, 20:00:00 DST>)
In [79]: d.astimezone(est).replace(tzinfo=None)
Out[79]: datetime.datetime(2010, 10, 30, 13, 21, 12)
But since your particular datetime seems to be in the UTC timezone, you could do this instead:
In [65]: d
Out[65]: datetime.datetime(2010, 10, 30, 17, 21, 12, tzinfo=tzutc())
In [66]: import datetime
In [67]: import calendar
In [68]: datetime.datetime.fromtimestamp(calendar.timegm(d.timetuple()))
Out[68]: datetime.datetime(2010, 10, 30, 13, 21, 12)
By the way, you might be better off storing the datetimes as naive UTC datetimes instead of naive local datetimes. That way, your data is local-time agnostic, and you only convert to local-time or any other timezone when necessary. Sort of analogous to working in unicode as much as possible, and encoding only when necessary.
So if you agree that storing the datetimes in naive UTC is the best way, then all you'd need to do is define:
local_d = d.replace(tzinfo=None)
In recent versions of Django (at least 1.4.1):
from django.utils.timezone import localtime
result = localtime(some_time_object)
A portable robust solution should use the tz database. To get local timezone as pytz tzinfo object, use tzlocal module:
#!/usr/bin/env python
import iso8601
import tzlocal # $ pip install tzlocal
local_timezone = tzlocal.get_localzone()
aware_dt = iso8601.parse_date("2010-10-30T17:21:12Z") # some aware datetime object
naive_local_dt = aware_dt.astimezone(local_timezone).replace(tzinfo=None)
Note: it might be tempting to use something like:
#!/usr/bin/env python3
# ...
naive_local_dt = aware_dt.astimezone().replace(tzinfo=None)
but it may fail if the local timezone has a variable utc offset but python does not use a historical timezone database on a given platform.
Using python-dateutil you can parse the date in iso-8561 format with dateutil.parsrser.parse() that will give you an aware datetime in UTC/Zulu timezone.
Using .astimezone() you can convert it to an aware datetime in another timezone.
Using .replace(tzinfo=None) will convert the aware datetime into a naive datetime.
from datetime import datetime
from dateutil import parser as datetime_parser
from dateutil.tz import tzutc,gettz
aware = datetime_parser.parse('2015-05-20T19:51:35.998931Z').astimezone(gettz("CET"))
naive = aware.replace(tzinfo=None)
In general the best idea is to convert all dates to UTC and store them that way, and convert them back to local as needed. I use aware.astimezone(tzutc()).replace(tzinfo=None) to make sure is in UTC and convert to naive.
I use this helper function all the time.
from datetime import datetime
import pytz
def tz_convert(t: datetime, tz=pytz.utc):
'''
Convert a timestamp to the target timezone.
If the timestamp is naive, the timezone is set to the target timezone.
'''
if not t.tzinfo:
tc = t.replace(tzinfo=tz)
else:
tc = t.astimezone(tz)
return tc
Demo
# tz-aware timestamp
>>> t = datetime.now(tz=pytz.utc)
>>> t.isoformat()
'2022-09-15T08:24:38.093312+00:00'
>>> tc = tz_convert(t, pytz.timezone('est'))
>>> tc.isoformat()
'2022-09-15T03:24:38.093312-05:00'
# tz-naive timestamp
>>> t = datetime.now()
>>> t.isoformat()
'2022-09-15T10:22:41.464200'
>>> tc = tz_convert(t, pytz.timezone('est'))
>>> tc.isoformat()
'2022-09-15T10:22:41.464200-05:00'

Categories

Resources