タイムレンジを自在に操る: Djangoのpostgres.fields.DateTimeRangeFieldを使いこなすための実践ガイド


デフォルトでは、DateTimeRangeField は下限を含み、上限を除いた範囲を表現します。これは、範囲は開始日時を含み、終了日時を含まないことを意味します。

しかし、default_bounds 引数を使用して、このデフォルトの動作を変更することができます。この引数には、4 つの文字列のいずれかを指定できます。

  • (]: 範囲は開始日時を含まず、終了日時を含む
  • [): 範囲は開始日時を含み、終了日時を含まない
  • (): 範囲は開始日時も終了日時も含まない
  • []: 範囲は開始日時と終了日時を含む

たとえば、以下のコードは、2023 年 1 月 1 日から 2023 年 12 月 31 日までのすべての時間を表す DateTimeRangeField を定義します。

from django.contrib.postgres.fields import DateTimeRangeField

class MyModel(models.Model):
    date_range = DateTimeRangeField(default_bounds='[]')

このコードは、以下のクエリと同等です。

SELECT * FROM my_model WHERE date_range @> '[2023-01-01, 2024-01-01)';

default_bounds の使用例

default_bounds 引数は、さまざまな状況で使用できます。たとえば、以下のような場合があります。

  • 重複する予約を防止する場合
  • ユーザーが特定の時間帯にのみアクセスできるリソースを定義する場合
  • 特定の期間内に発生したイベントを追跡する場合

postgres.fields.DateTimeRangeField.default_bounds は、Django で時間の範囲を表現するための強力なツールです。デフォルトの動作を変更することで、さまざまな要件に合わせてフィールドをカスタマイズすることができます。

  • DateTimeRangeField は、非連続範囲フィールドであるため、+/- ステップをサポートしていません。
  • default_bounds 引数は、Django 2.0 以降でのみ使用できます。


特定の期間内に発生したイベントを追跡

from django.contrib.postgres.fields import DateTimeRangeField
from django.db import models

class Event(models.Model):
    name = models.CharField(max_length=255)
    start_time = models.DateTimeField()
    end_time = models.DateTimeField()
    date_range = DateTimeRangeField(default_bounds='[]')

    def __str__(self):
        return self.name

このモデルを使用して、以下のクエリを実行できます。

SELECT * FROM event WHERE date_range @> '[2023-01-01, 2024-01-01)';

このクエリは、2023 年 1 月 1 日から 2023 年 12 月 31 日までの間に発生したすべてのイベントを返します。

ユーザーが特定の時間帯にのみアクセスできるリソースを定義

この例では、DateTimeRangeField を使用して、ユーザーが月曜日から金曜日の午前 9 時から午後 5 時までの間にのみアクセスできるリソースを定義するモデルを定義します。

from django.contrib.postgres.fields import DateTimeRangeField
from django.db import models

class Resource(models.Model):
    name = models.CharField(max_length=255)
    access_range = DateTimeRangeField(default_bounds='[)')

    def __str__(self):
        return self.name
SELECT * FROM resource WHERE access_range @> '[2024-06-13 09:00:00, 2024-06-13 17:00:00)';

このクエリは、2024 年 6 月 13 日午前 9 時から午後 5 時までの間にアクセスできるすべてのリソースを返します。

この例では、DateTimeRangeField を使用して、ユーザーが同じ時間帯に複数の予約を行うことを防止するモデルを定義します。

from django.contrib.postgres.fields import DateTimeRangeField
from django.db import models

class Appointment(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    start_time = models.DateTimeField()
    end_time = models.DateTimeField()
    date_range = DateTimeRangeField(default_bounds='[]')

    def __str__(self):
        return f'{self.user.username} - {self.start_time} - {self.end_time}'

    def save(self, *args, **kwargs):
        if Appointment.objects.filter(user=self.user, date_range__overlap=self.date_range).exists():
            raise ValidationError('重複する予約があります。')
        super().save(*args, **kwargs)

このモデルを使用すると、ユーザーが同じ時間帯に複数の予約を作成しようとした場合、ValidationError が発生します。



以下に、postgres.fields.DateTimeRangeField.default_bounds の代替方法をいくつか紹介します。

2 つの DateTimeField フィールドを使用する

最も単純な代替方法は、2 つの DateTimeField フィールドを使用して、範囲の開始と終了を個別に保存することです。

from django.db import models

class MyModel(models.Model):
    start_time = models.DateTimeField()
    end_time = models.DateTimeField()

この方法では、default_bounds 引数を使用する必要がなくなり、より柔軟なクエリが可能になります。たとえば、以下のクエリを使用して、特定の時間帯内に発生したすべてのイベントを検索できます。

SELECT * FROM my_model WHERE start_time <= '[2024-06-13 18:56:00]' AND end_time >= '[2024-06-13 18:56:00]';

カスタムフィールドを使用する

より複雑な要件の場合は、カスタムフィールドを作成することを検討できます。カスタムフィールドを使用すると、独自のロジックを実装して、範囲の表現と操作方法を制御することができます。

たとえば、以下のようなカスタムフィールドを作成できます。

from django.db import models
from django.contrib.postgres.fields import DateTimeRangeField

class MyDateRangeField(DateTimeRangeField):
    def __init__(self, *args, **kwargs):
        kwargs['default_bounds'] = '[)'
        super().__init__(*args, **kwargs)

    def get_db_prep_value(self, value, connection):
        if value is None:
            return None
        return [value.start, value.end + datetime.timedelta(seconds=1)]

    def from_db_value(self, value, expression, connection):
        if value is None:
            return None
        return DateTimeRange(value[0], value[1] - datetime.timedelta(seconds=1))

このカスタムフィールドは、default_bounds 引数を使用して、範囲の開始日時を含み、終了日時を含まないように設定されています。また、get_db_prep_value メソッドと from_db_value メソッドをオーバーライドして、独自のロジックを実装しています。

Django の標準機能に満足できない場合は、サードパーティライブラリを使用することを検討できます。

たとえば、django-range-filtering ライブラリを使用すると、より高度な範囲クエリを実行することができます。

これらの代替方法は、さまざまな要件に合わせて postgres.fields.DateTimeRangeField.default_bounds をカスタマイズする方法を示すほんの一例です。最適な方法は、具体的な要件によって異なります。

  • サードパーティライブラリを使用する場合は、ライブラリのドキュメントをよく読んで、使用方法を理解する必要があります。
  • カスタムフィールドを使用する場合は、データベーススキーマとの互換性を確保する必要があります。

postgres.fields.DateTimeRangeField.default_bounds は、時間の範囲を表現するための強力なツールですが、状況によっては代替方法の方が適切な場合もあります。