DEV Community

Stefano Ferrari
Stefano Ferrari

Posted on • Updated on

Django: timedelta, DurationField and Total Time

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

Image description

then I navigate into my project folder and I create mydateapp. After this, I made migrations.

Image description

Then I create superuser...

Image description

...and added my app to installed apps in settings.py

Image description

And finally check if is all ok running server...

Image description

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":

Image description

Then we have to set the path of our templates in the file "settings.py" under "TEMPLATES" section.

Image description

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()
Enter fullscreen mode Exit fullscreen mode

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")
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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')),
]
Enter fullscreen mode Exit fullscreen mode

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"),
]
Enter fullscreen mode Exit fullscreen mode

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:

Image description

Now, if we add /admin to the path, we should be asked for user and password:

Image description

Type them and you will see the admin panel:

Image description

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.

Image description

So, I created 3 records:

Image description

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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

Let me explain that code.

day_since=timezone.now().date()-timedelta(days=7) 
Enter fullscreen mode Exit fullscreen mode

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()))
Enter fullscreen mode Exit fullscreen mode

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.

Image description

Then start our server and let's go to our page.

Image description

Ok. It works!
So we can improve it. We can sum the duration field with:

total_time = query_times.aggregate(total_time=Sum('duration'))
Enter fullscreen mode Exit fullscreen mode

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')
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

And I passed them to my context:

context= {"query_times": query_times,
                "hours": hours,
                "minutes": minutes
                }
Enter fullscreen mode Exit fullscreen mode

Then I added an h5 tag under the table closing tag with my new data:

<h5>Total time: {{ hours }} hours, {{ minutes }} minutes</h5>
Enter fullscreen mode Exit fullscreen mode

And this will be the final result:

Image description

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

Latest comments (2)

Collapse
 
scisterna profile image
scisterna

Hi Stefano!
I'm not sure if who wrote this: stackoverflow.com/a/72443908/20611500 is you or not. But if you do, thank you! I spent like 3 hours trying to figure out how to solve a similar problem and I could not find a way.

Collapse
 
rquattrogtl profile image
Stefano Ferrari

Sorry for the delay.
Yes, I wrote that post, and I'm glad it was helpful to you.