简体   繁体   中英

Django filter datetime field by time, irrespective of date

I have a datetime field in Django, and I want to filter this based on time. I don't care about the particular date, but I want to find all transactions before 7:30, for example.

I know I can filter by hour and minute such as:

Q(datetime__hour=7) & \
Q(datetime__minute=30)

However, this would find all transactions AT 7:30. You are also unable to apply gte or lte. ie

(Q(datetime__hour__lte=7) & \
Q(datetime__minute__lte=30)) | \
Q(datetime__hour__lt=7)

The only thing that appears to be a potential solution is to have many queries, such as:

(Q(datetime__hour=7) & \
(Q(datetime__minute=30) | \
 Q(datetime__minute=29) | \
 Q(datetime__minute=28) | \
 ....
 Q(datetime__minute=2) | \
 Q(datetime__minute=1) | \
 Q(datetime__minute=0))) | \
Q(datetime__hour=6) | \
Q(datetime__hour=5) | \
Q(datetime__hour=4) | \
Q(datetime__hour=3) | \
Q(datetime__hour=2) | \
Q(datetime__hour=1) | \
Q(datetime__hour=0)

But this seems ridiculous.

Anyone have any ideas?

Just split the datetime field into a date and a time field. Than you can filter on time only:

from datetime import combine

class MyModel(models.Model):
    start_date = models.DateField()
    start_time = models.TimeField()

    class Meta:
        ordering = ['start_date', 'start_time']

    def start_datetime(self):
        return combine(self.date, self.time)

I added the Meta.ordering and start_datetime model method to show that this model can present data in the same way a model with a DateTimeField can.

Now you can filter on time:

objects = MyModel.objects.filter(start_time__lt=time(7, 30))

Update

If you have a established project and many queries depend on having a normal DateTime field. Splitting the datetime into a date and time fields come a cost: Rewriting queries threw out your project. There is an alternative: Only add a time field and leave the datetime field untouched. A save method can add the time based on datetime. The down side is that you have duplicated data in your db. The good thing it solves your problem with minimal effort.

class MyModel(models.Model):
    start_datetime = models.DateTimeField()
    start_time = models.TimeField(blank=True)

    def save(self)
        self.start_time = self.start_datetime.time

All existing queries will be the same as before and filter on time only:

objects = MyModel.objects.filter(start_time__lt=time(7, 30))

In Django 1.7+ you could use a transform to extract minute and hour from the timestamp. See https://docs.djangoproject.com/en/1.7/howto/custom-lookups/#a-simple-transformer-example for details.

Not sure when the filter by time was introduced, but in recent versions of Django (4.0+) this is done by the following query syntax:

YourModel.objects.all().filter(datetime__hour__lte=7, datetime__minute__lte=30)

Where YourModel is the model you are filtering, and datetime is the field from that model.

Ideas:

  • use raw sql
  • (mem)cache your results (eq::pseudocode:: md5(sql_string) = sql_result).
  • create a middle table with 5 indexed integer columns (year, month, day, hour, minute). Before your main sql extraction check this table for the needed data (the query should be really fast). If nothing exists, make your "magic" transaction (preferably not using Django's ORM). The result use it for your purpose, but also duplicate it into your secondary table.
  • when you get tired of doing unnecessary operations, kill some sleep hours and optimize your sql structure, based on the projects needs: what data is needed, amount of db writes vs amount of db reads, inner/outer joins and so on...

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM