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)
Related
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)
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.
I need to know how to get the time elapsed between the edit_date(a column from one of my models) and datetime.now(). My edit_date column is under the DateTimeField format. (I'm using Python 2.7 and Django 1.10)
This is the function I'm trying to do:
def time_in_status(request):
for item in Reporteots.objects.exclude(edit_date__exact=None):
date_format = "%Y-%m-%d %H:%M:%S"
a = datetime.now()
b = item.edit_date
c = a - b
dif = divmod(c.days * 86400 + c.minute, 60)
days = str(dif)
print days
The only thing I'm getting from this fuction are the minutes elapsed and seconds. What I need is to get this date in the following format:
Time_elapsed = 3d 47m 23s
Any ideas? let me know if I'm not clear of if you need more information
Thanks for your attention,
Take a look at dateutil.relativedelta:
http://dateutil.readthedocs.io/en/stable/relativedelta.html
from dateutil.relativedelta import relativedelta
from datetime import datetime
now = datetime.now()
ago = datetime(2017, 2, 11, 13, 5, 22)
diff = relativedelta(ago, now)
print "%dd %dm %ds" % (diff.days, diff.minutes, diff.seconds)
I did that code from memory, so you may have to tweak it to your needs.
Try something like
c = a - b
minutes = (c.seconds % 3600) // 60
seconds = c.seconds % 60
print "%sd %sm %ss" % (c.days, minutes, seconds)
I have a dataframe that contains a column which holds:
Date:
31062005
072005
12005
2012
I would like to convert these dates to the format:
Date:
31/06/2005
07/2005
01/2005
2012
What is the simplest way to do this? The fields are not in a date format yet, only strings.
Here:
df = pd.DataFrame(['30/06/2005', '07/2005', '1/2005', '2012'], columns=['Date'])
temp = pd.DataFrame(df['Date'].str.split('/').apply(reversed).tolist())\
.fillna('01')
df['Date'] = pd.to_datetime(temp[0].str.cat(temp[1].str.zfill(2))\
.str.cat(temp[2].str.zfill(2)), format='%Y%m%d')
suppose you write a function
def convert_date(s):
if len(s) == 4:
return s
elif len(s) < 7:
return s[: -4].zfill(2) + '/' + s[-4: ]
else:
return s[: -6].zfill(2) + '/' + s[-6: -4].zfill(2) + '/' + s[-4]
Then if your dates are in df.dates, you can use
>>> df.dates.apply(convert_date)
0 31/06/2
1 07/2005
2 01/2005
3 2012
Name: dates, dtype: object
Note that this converts a string in one form to a string in a different form, meaning you can't really manipulate dates further. If you want to do that, I'd suggest you amend the preceding function to use the appropriate datetime.datetime.strptime for the format matching the length of the string. It could look something like this:
def convert_date(s):
if len(s) == 4:
return datetime.datetime.strptime('%Y')
elif len(s) < 8:
return datetime.datetime.strptime('%m%Y')
else:
return datetime.datetime.strptime('%d%m%Y')
Note that your first date (with the 31 days) seems illegal, though.
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.