How to create custom time zone in python? - python

I want to create a Eastern European Time zone use US daylight saving time rules.
Here is what I have try:
from datetime import tzinfo, timedelta, datetime, timezone
import pytz
from backports.zoneinfo import ZoneInfo
class eastern_eur(tzinfo):
def utcoffset(self, dt: datetime):
return timedelta(hours=2) + self.dst(dt)
def dst(self, dt: datetime):
dt = dt.astimezone(ZoneInfo("US/Eastern"))
return dt.dst()
START = datetime(2018, 11, 2, tzinfo=timezone.utc).astimezone(eastern_eur())
Got RecursionError: maximum recursion depth exceeded while calling a Python object
Is there any simpler way to achieve that without create a new class? Like is add offset to a ZoneInfo instance?

Related

Sub-Class Python datetime Object

I am attempting to sub-class a datetime object to add more methods/utility functions:
from datetime import datetime
import pandas as pd
from pandas.tseries.offsets import BDay
class DateTime(datetime):
def __new__(cls, dt=None, **kwargs):
if dt is None:
cls._datetime = datetime.today()
elif isinstance(dt, datetime):
cls._datetime = dt
else:
cls._datetime = pd.to_datetime(dt, **kwargs)
return datetime.__new__(cls, cls._datetime.year, cls._datetime.month, cls._datetime.day,
cls._datetime.hour, cls._datetime.minute, cls._datetime.second,
cls._datetime.microsecond, cls._datetime.tzinfo)
def datetime(self):
return self._datetime
I have a simple function which bumps the date by n business days and I create two variables; today and bumped:
def date_add(start_date, days=5):
return DateTime(start_date + BDay(days))
today = DateTime()
bumped = date_add(today, days=5)
Assuming today is the 22nd May 2020 and I print the two variables, I see the numbers I expect:
print(today)
print(bumped)
2020-05-22 00:48:00.760808
2020-05-29 00:48:00.760808
However, if I look at the datetime method, I see the value of the bumped item in both instances:
today.datetime()
Out[12]: DateTime(2020, 5, 29, 0, 48, 0, 760808)
bumped.datetime()
Out[13]: DateTime(2020, 5, 29, 0, 48, 0, 760808)
I appreciate what I am doing is wrong and I probably shouldn't attempt to subclass the datetime object anyway but what is wrong here?
I think you want something like:
import datetime
class DateTime:
def __init__(self, some_datetime=None):
if some_datetime is None:
some_datetime = datetime.datetime.today()
self._datetime = some_datetime
def next_day(self):
return DateTime(self._datetime + datetime.timedelta(1))
print(DateTime(datetime.datetime(1,2,3))._datetime)
print(DateTime(datetime.datetime(1,2,3)).next_day()._datetime)
However you might want to ask if it really makes sense to encapsulate the datetime like that. It would be much simpler to just do:
def next_day(some_datetime):
return some_datetime + datetime.timedelta(1)

Mock datetime.datetime.now() while unit testing

#pytest.mark.parametrize("test_input,expected_output", data)
def test_send_email(test_input, expected_output):
emails = SendEmails(email_client=MagicMock())
emails.send_email = MagicMock()
emails.send_new_email(*test_input)
emails.send_email.assert_called_with(*expected_output)
I am looking to mock datetime.datetime.now() which is called in the send_new_email method. I am unsure how to do it however.
I tried creating a new datetime object
datetime_object = datetime.datetime.strptime('Jun 1 2017 1:33PM',
'%b %d %Y %I:%M%p')
and then overriding datetime.datetime.now
datetime.datetime.now = MagicMock(return_value=datetime_object)
However, I get the error
TypeError: can't set attributes of built-in/extension type 'datetime.datetime'
This question was marked as duplicate of Python: Trying to mock datetime.date.today() but not working
Python: Trying to mock datetime.date.today() but not working
I had already tried this solution but I could not get it to work. I cannot install freezegun due to the project requirements.
I have created a new class in the test file
class NewDate(datetime.date):
#classmethod
def today(cls):
return cls(2010, 1, 1)
datetime.date = NewDate
But I have no idea how to get the SendEmails class to use this.
You could replace the whole class:
_FAKE_TIME = 0
class _FakeDateTime(datetime.datetime):
#staticmethod
def now():
return _FAKE_TIME
then to use it:
_FAKE_TIME = whatever
datetime.datetime = _FakeDateTime
The class needs some refinements like comparison operators to make a FakeDateTime comparable to a datetime, but it should work.

Django - DateTimeField received a naive datetime

Basically I have the following model:
class Event(models.Model):
start = models.DateTimeField(default=0)
and when I try to create an object using datetime.datetime.strptime I get
Event.objects.create(start=datetime.datetime.strptime("02/03/2014 12:00 UTC",
"%d/%m/%Y %H:%M %Z"))
/usr/local/lib/python2.7/dist-packages/django/db/models/fields/__init__.py:903:
RuntimeWarning: DateTimeField Event.start received a naive datetime (2014-03-02
12:00:00) while time zone support is active.
I've gone through many post similar to this, but I can't figure out why it gives an error eventhough I'm putting the UTC (%Z) argument.
Thanks in advance.
That warning is logged since you are using time zones and you are creating a datetime object "manually". I also suggest you to turn the warning into an error by adding:
import warnings
warnings.filterwarnings('error',
r"DateTimeField .* received a naive datetime",
RuntimeWarning, r'django\.db\.models\.fields')
in your settings.py, in this way you can spot such irregularities more easily.
Honestly I don't know why, but actually your date seems unaware (if you use timezone.is_aware() it should return false).
To fix your current code I suggest you to rely on django utilis for timezones:
from django.utils import timezone
timezone.make_aware(yourdate, timezone.get_current_timezone())
For my project I created an utility class for dates, since I was facing such problems, you can take a look (especially to the method dateFromString):
class DateUtils(object):
#classmethod
def currentTimezoneDate(cls):
"""
Returns an aware datetime object based on current timezone.
:return: datetime: date
"""
return timezone.make_aware(datetime.now(), timezone.get_current_timezone())
#classmethod
def currentTimezoneOffset(cls):
"""
Returns the offset (expressed in hours) between current timezone and UTC.
:return: int: offset
"""
offset = cls.currentTimezoneDate().utcoffset()
return int(offset.total_seconds() / 60 / 60)
#classmethod
def UTCDate(cls, year, month, day, hour=0, minute=0, second=0, microsecond=0):
"""
Creates an aware UTC datetime object.
:return: datetime: date
"""
d = datetime(year, month, day, hour, minute, second, microsecond)
return timezone.make_aware(d, timezone.utc)
#classmethod
def asUTCDate(cls, date):
"""
Get the UTC version of the given date.
:param date: datetime: Date to be converted into UTC
:return: datetime UTC date
"""
if type(date) is Date:
return timezone.make_aware(datetime(date.year, date.month, date.day), timezone.utc)
if not timezone.is_aware(date):
return timezone.make_aware(date, timezone.utc)
return date.replace(tzinfo=timezone.utc)
#classmethod
def getJavaScriptDateFormatForCurrentLocale(cls):
"""
Return a date mask string that will be understood and used by JavaScript.
:return: str: Date mask string for JavaScript.
"""
f = get_format('SHORT_DATE_FORMAT')
return f.replace('Y', 'yyyy').replace('m', 'mm').replace('d', 'dd')
#classmethod
def getPythonDateFormatForCurrentLocale(cls):
"""
Return a date mask string that will be understood and used by Python.
:return: str: Date mask string for Python.
"""
f = get_format('SHORT_DATE_FORMAT')
return f.replace('Y', '%Y').replace('m', '%m').replace('d', '%d')
#classmethod
def dateFromString(cls, string, format=None, utc=True):
"""
Returns a datetime object from the given string.
:param string: str: A date string
:param format: str: The format of the date
:return: datetime: date
"""
date = datetime.strptime(string, format or cls.getPythonDateFormatForCurrentLocale())
if utc:
return cls.asUTCDate(date)
return date
#classmethod
def getFormattedStringForCurrentLocale(cls, date):
"""
Return a date string formatted using current locale settings.
:param date: datetime:
:return: str: Formatted Date string.
"""
return date.strftime(cls.getPythonDateFormatForCurrentLocale())
#classmethod
def randomDate(cls, start, end):
"""
Return a random date between the 2 dates provided.
See: http://stackoverflow.com/a/8170651/267719
:param start: datetime: Min date.
:param end: datetime: Max date.
:return: datetime: Random date in range.
"""
return start + timedelta(seconds=randint(0, int((end - start).total_seconds())))
#classmethod
def hourRange(cls, fromHour, toHour):
n = fromHour
hRange = [fromHour]
while n != toHour:
n += 1
if n > 23:
n = 0
hRange.append(n)
hRange.pop()
return hRange
You need to use Django's make_aware() function.
from django.utils import timezone
#....
aware_date = timezone.make_aware(datetime.strptime("02/03/2014 12:00 UTC", "%d/%m/%Y %H:%M %Z"), \
timezone.get_default_timezone())

Convert UTC string to local datetime string in python? [duplicate]

This question already has answers here:
Convert UTC datetime string to local datetime
(16 answers)
Closed 8 years ago.
I don't have the name of the Time Zone, only have an offset value, like +0400.
I have the datetime string in UTC: like 2014-01-07T09:29:35Z.
I want a string in local time, like 2014-01-07T13:29:35.
How to do this?
You can write a function to convert string format.
from datetime import datetime, timedelta
old_time = '2014-01-07T09:29:35Z'
def time_converter(old_time, time_zone):
time_zone = float(time_zone[:3] + ('.5' if time_zone[3] == '3' else '.0'))
str_time = datetime.strptime(old_time, "%Y-%m-%dT%H:%M:%SZ")
return (str_time + timedelta(hours=time_zone)).strftime("%Y-%m-%dT%H:%M:%SZ")
if __name__ == '__main__':
for time_zone in ('+0400', '+0430', '-1400'):
print(time_converter(old_time, time_zone))
Output:
2014-01-07T13:29:35Z
2014-01-07T13:59:35Z
2014-01-06T19:29:35Z
You can also create timezone classes for creating timezone aware datetime objects:
from datetime import tzinfo, timedelta, datetime
class myTimeZone(tzinfo):
def utcoffset(self, dt):
return timedelta(hours=4)
def dst(self, dt):
return timedelta(hours=0)
class utcTimeZone(tzinfo):
def utcoffset(self, dt):
return timedelta(hours=0)
def dst(self, dt):
return timedelta(hours=0)
d = datetime.strptime("2014-01-07T09:29:35Z","%Y-%m-%dT%H:%M:%SZ").replace(tzinfo=utcTimeZone())
print(d.astimezone(myTimeZone()).isoformat()) # Prints '2014-01-07T13:29:35+04:00'

How to create tzinfo when I have UTC offset?

I have one timezone's offset from UTC in seconds (19800) and also have it in string format - +0530.
How do I use them to create a tzinfo instance? I looked into pytz, but there I could only find APIs that take timezone name as input.
With Python 3.2 or higher, you can do this using the builtin datetime library:
import datetime
datetime.timezone(-datetime.timedelta(hours=5, minutes=30)
To solve your specific problem, you could use regex:
sign, hours, minutes = re.match('([+\-]?)(\d{2})(\d{2})', '+0530').groups()
sign = -1 if sign == '-' else 1
hours, minutes = int(hours), int(minute)
tzinfo = datetime.timezone(sign * datetime.timedelta(hours=hours, minutes=minutes))
datetime.datetime(2013, 2, 3, 9, 45, tzinfo=tzinfo)
If you can, take a look at the excellent dateutil package instead of implementing this yourself.
Specifically, tzoffset. It's a fixed offset tzinfo instance initialized with offset, given in seconds, which is what you're looking for.
Update
Here's an example. Be sure to run pip install python-dateutil first.
from datetime import datetime
from dateutil import tz
# First create the tzinfo object
tzlocal = tz.tzoffset('IST', 19800)
# Now add it to a naive datetime
local = naive.replace(tzinfo=tzlocal)
# Or convert another timezone to it
utcnow = datetime.utcnow().replace(tzinfo=tz.tzutc())
now = utcnow.astimezone(tzlocal)
I looked up the name IST from here. The name can really be anything. Just be careful if you deviate, since if you use code that relies on the name, it could lead to bugs later on.
By the way, if you have the timezone name upfront, and your operating system supports it, you can use gettz instead:
# Replace the above with this
tzlocal = tz.gettz('IST')
Python Standard Library (8.1.6) says that :
tzinfo is an abstract base class
the datetime module does not supply any concrete subclasses of tzinfo
you need to derive a concrete subclass, and (at least) supply implementations of the standard tzinfo methods needed by the datetime methods you use
a concrete subclass of tzinfo may need to implement the following methods ... If in doubt, simply implement all of them
tzinfo.utcoffset(self, dt) : return offset of local time from UTC, in minutes east of UTC
tzinfo.dst(self, dt) : return the daylight saving time (DST) adjustment, in minutes east of UTC, or None if DST information isn’t known
tzinfo.tzname(self, dt) : return the time zone name corresponding to the datetime object dt, as a string
All that means that you will have to provide your own implementation for the tzinfo. For example :
class UTC0530(datetime.tzinfo):
"""tzinfo derived concrete class named "+0530" with offset of 19800"""
# can be configured here
_offset = datetime.timedelta(seconds = 19800)
_dst = datetime.timedelta(0)
_name = "+0530"
def utcoffset(self, dt):
return self.__class__._offset
def dst(self, dt):
return self.__class__._dst
def tzname(self, dt):
return self.__class__._name
Usage :
tz = UTC0530()
d = datetime.datetime.now(tz)
d.isoformat()
output :
2015-01-27T20:19:41.257000+05:30
You have to implement subclass of datetime.tzinfo class. General guide is described here, where you also can find excellent examples of custom tzinfo implementations.
Here is example (given that there is no daylight saving time) :
from datetime import tzinfo, timedelta, datetime
from pytz import UTC
class MyUTCOffsetTimezone (tzinfo):
def __init__(self, offset=19800, name=None):
self.offset = timedelta(seconds=offset)
self.name = name or self.__class__.__name__
def utcoffset(self, dt):
return self.offset
def tzname(self, dt):
return self.name
def dst(self, dt):
return timedelta(0)
now = datetime.now(tz=UTC)
print now
# -> 2015-01-28 10:46:42.256841+00:00
print now.astimezone(MyUTCOffsetTimezone())
# -> 2015-01-28 16:16:42.256841+05:30
print datetime.now(MyUTCOffsetTimezone())
# -> 2015-01-28 16:16:42.256915+05:30
If you have pytz:
tz = pytz.FixedOffset(180)
now = timezone.now()
local_now = tz.normalize(now.astimezone(tz))
It's simple, only import datetime
>>> tz = datetime.timezone(datetime.timedelta(seconds=19800))
Next, you can, for example
>>> datetime.datetime.now(tz).isoformat(timespec='minutes')
'2021-08-03T18:07+05:30'
Based on the excellent answer from #Joe here, I wrote a function which monkey-patches pytz to support named timezones such as 'UTC-06:00' or 'UTC+11:30'. I can construct one of these names based on an offset sent to me from a browser, which only has an integer given by Javascript new Date().getTimezoneOffset() as described here and referenced here, and then I can post the name as a normal timezone name usable by the rest of my application which uses pytz.
This mechanism would also work for the op in this question who has an offset in seconds.
Example construct tzname using the offset the op has in this question:
minutes = offset // 60
tzname = 'UTC%s%02d:%02d' % (
'-' if minutes < 0 else '+',
abs(minutes) // 60, abs(minutes) % 60))
Example construct tzname using a browser timezone offset returned by
JavaScript new Date().getTimezoneOffset(), which of note has a reversed sign:
tzname = (
'UTC%s%02d:%02d' % (
'-' if browser_tz_offset > 0 else '+', # reverse sign
abs(browser_tz_offset) // 60, abs(browser_tz_offset) % 60))
Use the named zone to construct a tzinfo object:
from datetime import datetime
import pytz
tz = pytz.timezone(tzname) # tzname = e.g. 'UTC-06:00' or 'Europe/Madrid'
localized_now = datetime.now(tz)
I call this function during application startup.
import re
import pytz
from dateutil import tz as dateutil_tz
def post_load_pytz_offset_timezones_server_wide():
pristine_pytz_timezone = pytz.timezone
def extended_pytz_timezone(zone):
matches = re.match('^UTC([+-])([0-9][0-9]):([0-9][0-9])$', zone) if zone else None
if matches:
sign = -1 if matches.group(1) == '-' else 1
minutes = int(matches.group(2)) * 60 + int(matches.group(3))
tzinfo = dateutil_tz.tzoffset(zone, sign*minutes*60)
else:
tzinfo = pristine_pytz_timezone(zone)
return tzinfo
pytz.timezone = extended_pytz_timezone

Categories