9

I'm trying to make a proper date handling setup for my web app. I have a model that looks something like this

class Entity(models.Model):
    name = models.CharField(max_length=255)
    date = models.DateTimeField()

User can send a request to my DRF endpoint /api/v1/entity/ to get a list of such entities. Now there is a requirement that user should be able to request all Entity objects for a single day, which is determined by the date parameter. Dates are stored in UTC in the database while users of this app are not in a UTC timezone.

User can create an entity with the following date 2018-06-19T01:00:00+02:00, which is stored as 2018-06-18T23:00:00Z in the database. Now if I try to list all entities user has created for 2018-06-19 nothing's returned, but filtering by 2018-06-18 returns one entry.

This is the code setup I'm using: http://127.0.0.1:8000/api/v1/entity/?date=2018-06-18.

def get_queryset(self):
    user = self.request.user
    entities = Entity.objects.filter(owner=user)
    date = self.request.query_params.get('date')
    if date:
        entities = entities.filter(date__date=date)
    return entities

So in this case the appropriate date range would be 2018-06-18T23:00:00Z - 2018-06-19T23:00:00Z. What's the correct approach to fetch all entities for a single day (or a date range) in user's timezone?

Eugene
  • 10,006
  • 4
  • 37
  • 55
  • did you tried [this solution](https://stackoverflow.com/a/20379589/8283848) – JPG Jun 19 '18 at 17:13
  • @JerinPeterGeorge Thanks, but the solution you've linked is basically the next step I'll need to do. Before I can use a range lookup, I need to somehow construct that range relative to user's timezone though, which is what this question is about. – Eugene Jun 19 '18 at 20:14

3 Answers3

1

WhIf I understood correctly, You have to convert the local DateTime to equivalent UTC time and query to the DB. So I defined a simple timeconversion function below


import pytz
from datetime import datetime
def convert_to_UTC(local_tz,dt_1,dt_2):
    """
    local_tz : any possible timezone which supports pytz lib (https://stackoverflow.com/questions/13866926/is-there-a-list-of-pytz-timezones)
    dt_1 and dt_2 : local datetime in string in this format ->> '%Y-%m-%dT%H:%M:%S'

    return a list as ->> [utc_equivalent_of_dt_1_in_string,utc_equivalent_of_dt_2_in_string]
    """
    format='%Y-%m-%dT%H:%M:%S'
    pytz_local_tz = pytz.timezone(local_time_zone)
    dt_obj_from_date_time = datetime.strptime(dt_1,format)
    dt_obj_to_date_time = datetime.strptime(dt_2,format)
    return [pytz_local_tz.localize(dt_obj_from_date_time).astimezone(tz=pytz.utc).strftime(format),
            pytz_local_tz.localize(dt_obj_to_date_time).astimezone(tz=pytz.utc).strftime(format)]

To utilize this fuction, change your get_queryset() method as below,


def get_queryset(self):
    user = self.request.user
    entities = Entity.objects.filter(owner=user)
    date_from = self.request.query_params.get('date_from')
    date_to = self.request.query_params.get('date_to')

    if date_from and date_to:
        entities = entities.filter(date__range=convert_to_UTC('Asia/Kolkata', date_from, date_to))
    return entities

hence the url will be like, /api/v1/entity/?date_from=2018-06-18T23:00:00&date_to=2018-06-19T23:00:00

JPG
  • 82,442
  • 19
  • 127
  • 206
  • Thanks for your answer. `'Asia/Kolkata'` - is there a way to get user's local timezone dynamically or should I just design my APIs around sending this information? Are there any best practices? I don't want the client application to do any calculations on start and end dates, so in the end it should probably work like this: `/api/v1/entity/?date=2018-06-18&tz=+02:00`. – Eugene Jun 20 '18 at 09:46
  • As far as I knew, you have to detect the `timezone` from the client (angular/react etc). – JPG Jun 20 '18 at 10:14
  • In my opinion, If you could detect the timezone from the client and do the timezone conversion, that's the better thing – JPG Jun 20 '18 at 10:14
0
# simplified functions

def localize(usertimzone, date, type):
    """
    type = 'date', 'time', 'datetime'
    """
    from dateutil import parser
    date = parser.parse(date)
    if not date.tzinfo:
        # "if the POST request like `2018-06-19T01:00:00`"
        usertimzone = pytz.timezone(usertimzone)
        date = usertimzone.localize(date)

    # "if the POST request like `2018-06-19T01:00:00+02:00`" then just do this
    utc_date = date.astimezone(pytz.utc)
    return utc_date
#usages example
    def post(self, request, *args, **kwargs):
        alert_date = request.data.get('alert_date')
        if alert_date:
            request.data['alert_date'] = localize(request.user.timezone, alert_date, 'datetime')
Ali Husham
  • 816
  • 10
  • 31
0

Let's think of it this way: any timestamp with timezone info is really absolute value, as it can be translated to any other timezone, while timestamp without timezone is relative and is reliable only in context of single timezone across the system.

According to above thoughts, you should

  1. Determine required period boundaries from client's (app) perspective (start and end, not only date). For example, if app is running in timezone +03:00 and we want entities for 2023-04-01, then boundaries would be:

    {
        "start": "2023-04-01T00:00:00.000000+03:00",
        "end": "2023-04-01T23:59:59.999999+03:00"
    }
    
  2. Convert it to UTC (not necessary, though: this can be done in backend or even you can push datetime values to ORM/database as is, they just should contain timezone info) and use it as query parameters.

    {
        "start": "2023-03-31T21:00:00.000000Z",
        "end": "2023-04-01T20:59:59.999999Z"
    }
    
oxfn
  • 6,590
  • 2
  • 26
  • 34