Arbitrary input parameters to python functions - python

I am currently working with the timedelta function in the datetime package. The default list of arguments for the timedelta is datetime.timedelta(days=0, seconds=0, microseconds=0, milliseconds=0, minutes=0, hours=0, weeks=0).
I will be taking two inputs for my function: frequency and timespan, where frequency is an integer to specify in the designated timespan and the timespan is a string which is one of the days, seconds, microseconds, milliseconds, minutes, hours, weeks.
Currently I am writing a bunches of if-else to decide which parameters of the timedelta I should populate using the frequency. I am wondering if there is a more elegant way, e.g. parametrize the arguments for timedelta?
Thanks!
---Edit---
My current code:
from datetime import datetime, timedelta
def myfunction(frequency, timespan):
if timespan == "days":
return datetime.now() + timedelta(days=frequency)
elif timespan == "seconds":
return datetime.now() + timedelta(seconds=frequency)
elif timespan == "microseconds":
return datetime.now() + timedelta(microseconds=frequency)
elif timespan == "milliseconds":
return datetime.now() + timedelta(milliseconds=frequency)
elif timespan == "minutes":
return datetime.now() + timedelta(minutes=frequency)
elif timespan == "hours":
return datetime.now() + timedelta(hours=frequency)
else:
return datetime.now() + timedelta(weeks=frequency)

You can just put the parameters in a dict and let python do the work:
def myfunction(frequency, timespan):
return timedelta(**{timespan:frequency})

Related

unittest Mock - patch return value

I think I misunderstand how to use mocks for changing a function return value. Here is my test :
from path.to import programme_finder
#patch('path.to.programme_finder._calc_update_interval')
def test_refresh_interval(self,method_mock):
today = datetime.datetime.now()
dd = datetime.timedelta(millisecond=20)
earlier_date = today - dd
#The normal function returns a 5 day interval.
# For tests I want it down to 20ms
method_mock.return_value = earlier_date
#Here I would expect a date object, instead I get MagicMock
print("Calc returns %s " % programme_finder._calc_update_interval)
# rest of the test irrelevant
self.fail("need to time responce time")
What am I doing wrong ? How do I get the programme_finder._calc_update_interval to return my patched datetime?
Tried
assert programme_finder._calc_update_interval == earlier_date
as well and it fails.
#programme_finder.py
def _calc_update_interval():
today = datetime.datetime.now()
# The days we want to subtract for today.
update_interval = current_app.config.get("RESOURCE_UPDATE_INTERVAL")
dd = datetime.timedelta(days=update_interval)
# Find which date it was x days ago
earlier_date = today - dd
return earlier_date
It looks to me like you're not calling the function -- you're referencing it by name, so you get back the mocked function instead of your mock return value.
print("Calc returns %s " % programme_finder._calc_update_interval )
^reference
Should be
print("Calc returns %s " % programme_finder._calc_update_interval() )
^^call
The problem seems to be that _calc_update_interval is a property (I'm guessing created with the #property decorator) and not a method. The easiest approach is to simply use the PropertyMock class provided with mock (Documented here):
#patch('path.to.programme_finder._calc_update_interval', new_callable=PropertyMock):
def test_refresh_interval(self,method_mock):
#...

Accept a single string instead of normal parameters

Hello everyone I have a class that works with time, for example you enter two times and you can sum them (for now it doesn't matter that it goes beyond 23:59:59) or multiply them etc. The default input are zeros. There is also a function that returns current time. I can call time = Time() which returns 0:00:00, time = Time(12) returns 12:00:00.
The problem that I have is, I want to call time = Time('now') and it should store there current time using function now(), edits should be done in __init__() (if you don't like now() you can change it). But if i put time_now = '' as first parameter it doesn't work, same when i put it as a last, because when I write time = Time('now') it takes string 'now' and wants to put it to hours.
I know there are time modules which can do this, but this is for learning purposes.
My code:
import random, time
class Time:
def __init__(self, hours=0, minutes=0, seconds=0, time_now=''):
# if time_now == 'now':
# now()
time = abs(3600*hours + 60*minutes + seconds)
self.hour = time//3600
self.min = time//60%60
self.sec = time%60
def __repr__(self):
return '{}:{:02}:{:02}'.format(self.hour,self.min,self.sec)
def __add__(self, other):
if isinstance(other, Time):
return Time(self.hour+other.hour, self.min+other.min, self.sec+other.sec)
if isinstance(other, int):
return Time(self.hour, self.min, self.sec+other)
__radd__ = __add__
def __sub__(self, other):
return Time(seconds = self.__int__() - other.__int__())
def __mul__(self, other):
return Time(self.hour*other.hour, self.min*other.min, self.sec*other.sec)
def __int__(self):
return 3600*self.hour + 60*self.min + self.sec
def __eq__(self, other):
return self.__int__() == other.__int__()
def __lt__(self, other):
return self.__int__() < other.__int__()
#writing out hour/min/sec what we want
def __getitem__(self, key):
if key == 0:
return self.hour
if key == 1:
return self.min
if key == 2:
return self.sec
#time now
def now():
a=(time.localtime()[3:6])
return Time(a[0],a[1],a[2])
Python doesn't have method overloading, so your only option is to "play" with the arguments:
You can do something that IMHO is very bad (downgraded very bad to meeeeh... so, so after reading #ivan-pozdeev's comments in this answer)
class Time:
def __init__(self, hours=0, minutes=0, seconds=0, time_now=''):
if hours == 'now':
tmp_t = now()
self.hour = tmp_t.hour
self.min = tmp_t.min
self.sec = tmp_t.sec
else:
t = abs(3600*hours + 60*minutes + seconds)
self.hour = t//3600
self.min = t//60%60
self.sec = t%60
That... well, that works:
>>> a = Time('now')
>>> print vars(a)
{'sec': 20, 'hour': 15, 'min': 18}
>>>
>>> a = Time(hours=19, minutes=4, seconds=5)
>>> print vars(a)
{'sec': 5, 'hour': 19, 'min': 4}
But that leaves the code in a very weird state. Is very difficult to read. I certainly would try to come with a different approach altogether...
I also changed the time variable within the __init__ to t because that conflicted with the time name from import time
You can use a call with named parameters: Time(time_now='now'), but that looks too reiterative.
If I was using named parameters, I'd instead change it to a boolean and use something like Time(now=True), which looks a bit cleaner.
But I think a better alternative is to make your "now" objects be constructed from your now() function, which I'd move to a static method:
class Time(object):
#...
#classmethod
def now(cls):
a=(time.localtime()[3:6])
return cls(a[0],a[1],a[2])
So you'd construct your now objects like x = Time.now() instead of x = Time('now')
For flexible argument handling beyond what's available with static arguments, you may use *args and/or **kwargs:
def __init__(self, *args, **kwargs):
a_seq=('hours','minutes','seconds')
if len(args)>0 and args[0]=='now':
now_=now()
self.__dict__.update(a,getattr(now_,a) for a in a_seq)
else:
t=0
for i,a in enumerate(a_seq):
try: v=args[i]
except IndexError: v=kwargs.get(a,0)
else: if a in kwargs: raise TypeError("multiple values given for argument `%s'"%a)
t+=v
t*=60
for a in a_seq[-1:0:-1]:
setattr(self,a,t%60)
t//=60
setattr(self,a_seq[0],t)
(I renamed the fields to the same names as arguments to make it easier to use reflection. Reflection is used here to eliminate code duplication (without it, it'd be about 1.5 times longer).)
In this particular case, though, BorrajaX's solution is much simpler because the deviation from a normal static argument list here is only minor (in fact, the try-except-else in the 2nd half of my code do nothing but emulate it).
Almost never try to implement your own time module. It's time consuming and defective. Use time or datetime. All your needs are already implemented somehow.

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