Django: timedelta, DurationField and Total Time
Stefano Ferrari
Posted on March 24, 2022
Some months ago, I had to do some operations with my Model's date fields. I search for solutions and I found some useful python packages. So I will share those solutions. I will share also the github repository.
Creating project
I create a project folder called Datefunctionsproject and I started a project called mydateproject
then I navigate into my project folder and I create mydateapp. After this, I made migrations.
Then I create superuser...
...and added my app to installed apps in settings.py
And finally check if is all ok running server...
Setting up a basic template
We need a basic template. To do this, we can create a new folder on mydateapp folder called "templates" and inside this we create a new file called "index.html":
Then we have to set the path of our templates in the file "settings.py" under "TEMPLATES" section.
Creating Model
We can create a basic Model to see the behaviour of our time fields. So in models.py in mydateapp, we will create this model:
from django.db import models
# Create your models here.
class Times(models.Model):
my_date = models.DateField()
start_time=models.TimeField()
end_time=models.TimeField()
We can build our basic view. We will build it better going ahead. So, in our views.py file in mydateapp folder, we will have:
from django.shortcuts import render
from .models import Times
# Create your views here.
def home_view(request):
return render(request, "index.html")
Now we can register our model in order to work with it in admin section. To do this, we can register it in admin.py file in mydateapp folder we will have:
from django.contrib import admin
from .models import Times
# Register your models here.
admin.site.register(Times)
Setup of urls.py files
In the urls.py file of the project, we can include the urls of our app:
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('mydateapp.urls')),
]
Then in the mydateapp folder, we must create a new file called urls.py and write down some stuff like this:
from django.urls import path
from .views import home_view
urlpatterns = [
path('', home_view, name="home_view"),
]
Adding some records
At his point, we must run
python manage.py makemigrations
python manage.py migrate
in order to create the table in our database.
Now, we are ready to try some stuff. First of all, launching python manage.py runserver command, we will see if everything is ok and we should see our index.html page:
Now, if we add /admin to the path, we should be asked for user and password:
Type them and you will see the admin panel:
Click on our model and then on "Add" button. You will be able to add some new records. Pay attention to put correct data in there, because we haven't used a control system to check if start_time is greater than end_time.
So, I created 3 records:
Start playing with times and dates
Finally, we are ready to play.
The first thing we can do is to build a simple table that shows our records. First of all in our views.py file, we have to import some modules:
from django.utils import timezone
from django.db.models import F, DurationField, Sum, ExpressionWrapper
from datetime import datetime, time, timedelta
timezone: When time zone support is enabled (USE_TZ=True), Django uses time-zone-aware datetime objects. If your code creates datetime objects, they should be aware too.
F: Django uses the F() object to generate an SQL expression that describes the required operation at the database level.
DurationField: DurationField is a field for storing periods of time.
Sum: is an aggregation clause info.
ExpressionWrapper: If the fields that you’re combining are of different types you’ll need to tell Django what kind of field will be returned. Since F() does not directly support output_field you will need to wrap the expression with ExpressionWrapper.
datetime: The datetime module supplies classes for manipulating dates and times.
time: Time access and conversions.
timedelta: A timedelta object represents a duration, the difference between two dates or times.
You can find more informations here
Now we can rewrite our view as this:
def home_view(request):
day_since=timezone.now().date()-timedelta(days=7)
query_times = Times.objects.filter(end_time__isnull = False).filter(my_date__gte=day_since).annotate(duration=ExpressionWrapper(
F('end_time') - F('start_time'), output_field=DurationField()))
context= {"query_times": query_times}
return render(request, "index.html", context)
Let me explain that code.
day_since=timezone.now().date()-timedelta(days=7)
With this row, I use timedelta to get the date of 7 days ago. So I will be able to filter only dates in that range.
query_times = Times.objects.filter(end_time__isnull = False).filter(my_date__gte=day_since).annotate(duration=ExpressionWrapper(
F('end_time') - F('start_time'), output_field=DurationField()))
With this I will get my records filtered with some criteria. The first is that my end_time must not be null; the second is that my date must be in the range from seven days ago to now. With annotate I create another field with the difference between end_time and start_time and this field will be called "duration" and will be a DurationField.
Let's improve our index.html file. To give it some style, copy the bootstrap starter template and paste it over index.html code. Then let's build a simple table to show our data.
Then start our server and let's go to our page.
Ok. It works!
So we can improve it. We can sum the duration field with:
total_time = query_times.aggregate(total_time=Sum('duration'))
With this we will get a dictionary. If you print the result you will get
{'total_time': datetime.timedelta(seconds=21563)}
So we can get our total with:
sum_time=total_time.get('total_time')
So, we can get all what we need. For example, I tried to get hours and minutes without days and seconds. My sum_time could be None so I build an If statement:
if sum_time is not None:
days=sum_time.days*24
seconds=sum_time.seconds
hours=seconds//3600+days
minutes=(seconds//60)%60
else:
days=0
seconds=0
hours=0
minutes=0
And I passed them to my context:
context= {"query_times": query_times,
"hours": hours,
"minutes": minutes
}
Then I added an h5 tag under the table closing tag with my new data:
<h5>Total time: {{ hours }} hours, {{ minutes }} minutes</h5>
And this will be the final result:
Conclusions
Congratulations! You reached the end of this very long post.
I know that there are many ways to manage date and times with python. This was my approach for a project I was building.
Django and Python are full of packages that help you in many ways.
Feel free to drop your suggestions, I will appreciate.
Link to github repo
Posted on March 24, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.