Django:如何忽略年份按日/月范围过滤课程可用时段
课程可用时段存储与过滤方案解析
一、存储方案对比
针对无需考虑年份的可选可用时段,两种存储方案的优缺点如下:
方案1:存储两个日期字段(如available_start_at、available_end_at)
- 优势:
- 利用数据库原生日期类型,自动校验日期合法性(避免出现2月30日这类无效日期)
- ORM层面支持直接使用日期相关查询API,代码更简洁
- 劣势:存储时需填充冗余年份(可统一用固定年份如2000年),查询时需额外处理年份忽略逻辑
方案2:存储四个数字字段(from_day、from_month、to_day、to_month)
- 优势:完全剥离年份,字段语义直接,无冗余数据
- 劣势:需自行实现日期合法性校验,ORM查询时需拼接大量条件,代码复杂度更高
结论:优先选择方案1,数据库日期类型的合法性校验和便捷的ORM查询是核心优势,年份问题可通过统一固定年份或查询时提取月日解决。
二、当前日期过滤可用课程逻辑实现
需求:忽略年份,判断当前月日是否在课程时段范围内,同时支持跨年时段(如12/20至次年1/10)的过滤。
以下是完整的Django ORM实现代码:
from django.db.models import Q, F import datetime def get_available_courses(self): today = datetime.date.today() today_month = today.month today_day = today.day # 判断时段是否跨年(结束月日早于开始月日) is_year_crossed = Q(available_end_at__month__lt=F('available_start_at__month')) | \ Q(available_end_at__month=F('available_start_at__month'), available_end_at__day__lt=F('available_start_at__day')) # 非跨年时段:当前月日需在开始和结束之间 filter_not_year_crossed = ( # 当前 >= 开始月日 Q(available_start_at__month__lt=today_month) | Q(available_start_at__month=today_month, available_start_at__day__lte=today_day) ) & ( # 当前 <= 结束月日 Q(available_end_at__month__gt=today_month) | Q(available_end_at__month=today_month, available_end_at__day__gte=today_day) ) # 跨年时段:当前月日 >= 开始月日 或 当前月日 <= 结束月日 filter_year_crossed = ( Q(available_start_at__month__lt=today_month) | Q(available_start_at__month=today_month, available_start_at__day__lte=today_day) ) | ( Q(available_end_at__month__gt=today_month) | Q(available_end_at__month=today_month, available_end_at__day__gte=today_day) ) # 最终过滤规则:无时段限制 或 符合对应时段的可用条件 final_filter = Q(available_start_at__isnull=True, available_end_at__isnull=True) | \ (is_year_crossed & filter_year_crossed) | \ (~is_year_crossed & filter_not_year_crossed) return self.account.get_courses().filter(final_filter)
关键逻辑说明
- 使用
F()表达式在数据库层面比较开始和结束的月日,避免内存处理数据,提升查询效率 - 非跨年时段需同时满足「当前晚于等于开始月日」和「当前早于等于结束月日」
- 跨年时段只需满足「当前晚于等于开始月日(到当年年底)」或「当前早于等于结束月日(当年年初到结束日)」
- 支持未设置时段的课程(默认全部可用)
内容的提问来源于stack exchange,提问作者Jer
相关产品推荐
相关产品推荐

