How to get only date after subtraction of two date field - django

I'm trying to have subtraction of two data fields and the result in days. But I'm having time also at the output. How do I get only days not the time.
Here is my code:
class ItemTable(models.Model):
expdate = models.DateField("EXP Date", null=True, blank=True)
def days_to_exp(self):
if self.expdate:
now = datetime.date.today()
exp = str(self.expdate - now)
if exp > "1":
return exp
elif exp < "1" and exp > "0":
return "Today"
else:
return "Expired"
output:
12 days, 0:00:00,
4 days, 0:00:00... etc
I just want the result as:
12 days,
4 days..... etc

The result of subtracting one datetime.date from another is a timedelta object. You can access the .days attribute of that timedelta object to get what you're after.
> today = datetime.now().date()
> tomorrow = today + timedelta(days=1)
> (tomorrow - today).days
1
> (today - tomorrow).days
-1

Result of subtraction between datetime.date instances is an object with type of datetime.timedelta that represents a duration not datetime.date nor datetime.datatime. you can get how long a timedelta is by accessing it's .days property.
for example:
result = now().today() - (now()+timedelta(days=10))
assert(result.days==10)

Related

average spending per day - django model

I have a model that looks something like that:
class Payment(TimeStampModel):
timestamp = models.DateTimeField(auto_now_add=True)
amount = models.FloatField()
creator = models.ForeignKey(to='Payer')
What is the correct way to calculate average spending per day?
I can aggregate by day, but then the days when a payer does not spend anything won't count, which is not correct
UPDATE:
So, let's say I have only two records in my db, one from March 1, and one from January 1. The average spending per day should be something
(Sum of all spendings) / (March 1 - January 1)
that is divided by 60
however this of course give me just an average spending per item, and number of days will give me 2:
for p in Payment.objects.all():
print(p.timestamp, p.amount)
p = Payment.objects.all().dates('timestamp','day').aggregate(Sum('amount'), Avg('amount'))
print(p
Output:
2019-03-05 17:33:06.490560+00:00 456.0
2019-01-05 17:33:06.476395+00:00 123.0
{'amount__sum': 579.0, 'amount__avg': 289.5}
You can aggregate min and max timestamp and the sum of amount:
from django.db.models import Min, Max, Sum
def average_spending_per_day():
aggregate = Payment.objects.aggregate(Min('timestamp'), Max('timestamp'), Sum('amount'))
min_datetime = aggregate.get('timestamp__min')
if min_datetime is not None:
min_date = min_datetime.date()
max_date = aggregate.get('timestamp__max').date()
total_amount = aggregate.get('amount__sum')
days = (max_date - min_date).days + 1
return total_amount / days
return 0
If there is a min_datetime then there is some data in the db table, and there is also max date and total amount, otherwise we return 0 or whatever you want.
It depends on your backend, but you want to divide the sum of amount by the difference in days between your max and min timestamp. In Postgres, you can simply subtract two dates to get the number of days between them. With MySQL there is a function called DateDiff that takes two dates and returns the number of days between them.
class Date(Func):
function = 'DATE'
class MySQLDateDiff(Func):
function = 'DATEDIFF'
def __init__(self, *expressions, **extra):
expressions = [Date(exp) for exp in expressions]
extra['output_field'] = extra.get('output_field', IntegerField())
super().__init__(*expressions, **extra)
class PgDateDiff(Func):
template = "%(expressions)s"
arg_joiner = ' - '
def __init__(self, *expressions, **extra):
expressions = [Date(exp) for exp in expressions]
extra['output_field'] = extra.get('output_field', IntegerField())
super().__init__(*expressions, **extra)
agg = {
avg_spend: ExpressionWrapper(
Sum('amount') / (PgDateDiff(Max('timestamp'), Min('timestamp')) + Value(1)),
output_field=DecimalField())
}
avg_spend = Payment.objects.aggregate(**agg)
That looks roughly right to me, of course, I haven't tested it. Of course, use MySQLDateDiff if that's your backend.

Django: DurationField with resolution in microseconds

The django DurationField displays only HH:MM:SS in the django admin interface.
Unfortunately this is not enough in my current context.
I need to be able to show/edit microseconds in the admin interface.
How could this be done?
Update
This was a mistake. My data in the database was wrong. The microseconds where removed in a process before the data came into the database.
Django displayes the microseconds if there are any. You don't need to do anything to show them.
Have a look on source:
https://docs.djangoproject.com/en/2.0/_modules/django/db/models/fields/#DurationField
I think the way is to override forms.DurationField (https://docs.djangoproject.com/en/2.0/_modules/django/forms/fields/#DurationField) and to be exact these method:
from django.utils.duration import duration_string
def duration_string(duration):
"""Version of str(timedelta) which is not English specific."""
days, hours, minutes, seconds, microseconds = _get_duration_components(duration)
string = '{:02d}:{:02d}:{:02d}'.format(hours, minutes, seconds)
if days:
string = '{} '.format(days) + string
if microseconds:
string += '.{:06d}'.format(microseconds)
return string
be aware that there may be need to override these too django.utils.dateparse.parse_duration
def parse_duration(value):
"""Parse a duration string and return a datetime.timedelta.
The preferred format for durations in Django is '%d %H:%M:%S.%f'.
Also supports ISO 8601 representation and PostgreSQL's day-time interval
format.
"""
match = standard_duration_re.match(value)
if not match:
match = iso8601_duration_re.match(value) or postgres_interval_re.match(value)
if match:
kw = match.groupdict()
days = datetime.timedelta(float(kw.pop('days', 0) or 0))
sign = -1 if kw.pop('sign', '+') == '-' else 1
if kw.get('microseconds'):
kw['microseconds'] = kw['microseconds'].ljust(6, '0')
if kw.get('seconds') and kw.get('microseconds') and kw['seconds'].startswith('-'):
kw['microseconds'] = '-' + kw['microseconds']
kw = {k: float(v) for k, v in kw.items() if v is not None}
return days + sign * datetime.timedelta(**kw)

How to query to fetch last 5 months records?

I have a model named 'DemoModel' it has a field called demo_date.
I want to fetch the last 5 months i.e;(from current month records to past 5 months records) records by querying on the demo_date field.
My models look like
class DemoModel(models.Model):
demo_date = models.DateTimeField()
from datetime import datetime, timedelta
today = datetime.today()
long_ago = today + timedelta(days=-150)
retrieved_data = DemoModel.objects.filter(demo_date__gte=long_ago)
Use
dateutil.relativedelta import relativedelta
to calculate the five_months_ago parameter accurately.
And then get the objects like this:
target_set = DemoModel.objects.filter(demo_date__gte=five_months_ago)
This function give subscription or add months
def monthdelta(date, delta):
m, y = (date.month+delta) % 12, date.year + ((date.month)+delta-1) // 12
if not m: m = 12
d = min(date.day, [31,
29 if y%4==0 and not y%400==0 else 28,31,30,31,30,31,31,30,31,30,31][m-1])
return date.replace(day=d,month=m, year=y)
query goes here
from datetime import datetime
query= DemoModel.objects.filter(demo_date__gte=monthdelta(datetime.now(), -5)
)

date using either raw_input() or input() coming up as integer

New to Python and have read so many other SO questions that I feel like I am missing something with how to massage user input to string format. I have this simple code and I get the AttributeError: 'int' object has no attribute 'split' so I added exception handiling and am getting error everytime. I have tried almost everything with the str(), datetime() and std.readline() and nothing.
def dateConverter(userDate):
try:
#split the substrings for month day year
date = userDate.split("/")
#day
day = date[:2]
#month
month = date[3:5]#[ beginning : beginning + LENGTH]
months = {1:'January', 2:'February', 3:'March', 4:'April', 5:'May', 6:'June', 7:'July', 8:'August', 9:'September', 10:'October', 11:'November', 12:'December'}
for key,value in months:
month=value
#year
year = date[4:]
print(str(month + ' ' + day + ',' + year))
return True
except:
print('Error')
return False
print('Enter a date in the format: mm/dd/yyyy \n')
userInput = raw_input()
dateConverter(userInput)
main()
Note: I have both Python27 and Python34 installed on Win7
Edit
vaibhav-sagar was correct, I wasn't slicing the string the right way and had nothing to do with the input. Although, I have Python27 & Python34 installed and even though I set my variable path to Python34 I have to use raw_input() which I heard was deprecated in Python34 so look out for that too. That is what was stumping me! Sorry, this was my second look at Python so it was really new territory. I actually got the slicing examples from another SO answer so that is what I get for assuming. Here is the solution:
#custom date converter func
def dateConverter(userDate):
try:
#split the substrings for month day year
date = userDate.split("/")
#day
day = date[1]#[ beginning : beginning + LENGTH]
#month
month = date[0]
months = {1:'January', 2:'February', 3:'March', 4:'April', 5:'May', 6:'June', 7:'July', 8:'August', 9:'September', 10:'October', 11:'November', 12:'December'}
month=months[int(month)]
#year
year = date[2]
print(month + ' ' + day + ',' + year)
return True
except:
print('Error')
return False
Next step is to validate using re to validate the date is valid
I am using Python 3.3.5 and getting a different error. An exception is being raised at
for key, value in months:
Because iterating over a dictionary yields only keys, and not keys and values. What you want can be accomplished by:
for key, value in months.items():
More generally, your issues seem unrelated to your massaging of user input. This can be verified by using IDLE or another REPL. For example:
>>> someDate = '12/10/2014'
>>> date = someDate.split('/')
>>> date
['12', '10', '2014']
>>> day = date[:2]
>>> day
['12', '10']
>>> month = date[3:5]
>>> month
[]
>>> year = date[4:]
>>> year
[]
Python's slice syntax is doing something different to what I think you want. I also think you don't need a for loop, instead you can do:
month = months[int(month)]
This will assign the month name to month, like you expect. A function that does what I think you want would look something like this:
def dateConverter(userDate):
#split the substrings for month day year
date = userDate.split("/")
#day
day = date[1]
#month
month = date[0]
months = {1:'January', 2:'February', 3:'March', 4:'April', 5:'May', 6:'June', 7:'July', 8:'August', 9:'September', 10:'October', 11:'November', 12:'December'}
month = months[int(month)]
#year
year = date[2]
print(str(month + ' ' + day + ',' + year))
return True
I hope that helps.

Django: Total birthdays each day for the next 30 days

I've got a model similar to this:
class Person(models.Model):
name = models.CharField(max_length=40)
birthday = DateTimeField() # their next birthday
I would like to get a list of the total birthdays for each day for the next 30 days. So for example, the list would look like this:
[[9, 0], [10, 3], [11, 1], [12, 1], [13, 5], ... #30 entries in list
Each list entry in the list is a date number followed by the number of birthdays on that day. So for example on the 9th of May there are 0 birthdays.
UPDATES
My db is sqlite3 - will be moving to postgres in the future.
from django.db.models import Count
import datetime
today = datetime.date.today()
thirty_days = today + datetime.timedelta(days=30)
birthdays = dict(Person.objects.filter(
birthday__range=[today, thirty_days]
).values_list('birthday').annotate(Count('birthday')))
for day in range(30):
date = today + datetime.timedelta(day)
print "[%s, %s]" % (date, birthdays.get(date, 0))
I would get the list of days and birthday count this way:
from datetime import date, timedelta
today = date.today()
thirty_days = today + timedelta(days=30)
# get everyone with a birthday
people = Person.objects.filter(birthday__range=[today, thirty_days])
birthday_counts = []
for date in [today + timedelta(x) for x in range(30)]:
# use filter to get only birthdays on given date's day, use len to get total
birthdays = [date.day, len(filter(lambda x: x.birthday.day == date.day, people))]
birthday_counts.append(birthdays)
Something like this --
from datetime import date, timedelta
class Person(models.Model):
name = models.CharField(max_length=40)
birthday = models.DateField()
#staticmethod
def upcoming_birthdays(days=30):
today = date.today()
where = 'DATE_ADD(birthday, INTERVAL (YEAR(NOW()) - YEAR(birthday)) YEAR) BETWEEN DATE(NOW()) AND DATE_ADD(NOW(), INTERVAL %S DAY)'
birthdays = Person.objects.extra(where=where, params=[days]).values_list('birthday', flat=True)
data = []
for offset in range(0, days):
i = 0
d = today + timedelta(days=offset)
for b in birthdays:
if b.day == d.day and b.month == d.month:
i += 1
data.append((d.day, i))
return data
print Person.upcoming_birthdays()
(Queryset of people with a birthday in the next X days)
Found cool solution for this!
For me it works!
from datetime import datetime, timedelta
import operator
from django.db.models import Q
def birthdays_within(days):
now = datetime.now()
then = now + timedelta(days)
# Build the list of month/day tuples.
monthdays = [(now.month, now.day)]
while now <= then:
monthdays.append((now.month, now.day))
now += timedelta(days=1)
# Tranform each into queryset keyword args.
monthdays = (dict(zip(("birthday__month", "birthday__day"), t))
for t in monthdays)
# Compose the djano.db.models.Q objects together for a single query.
query = reduce(operator.or_, (Q(**d) for d in monthdays))
# Run the query.
return Person.objects.filter(query)
But it get a list of persons that have a birthday in date range. You should change a bit.