MongoDB如何按月/日时间范围查询未来1个月内的用户生日
问题描述
我需要对存储了Date类型生日字段的用户数据库进行查询,筛选出即将过生日的用户。示例用户存储数据如下:
- name: "John Doe"
- birthday:
1980-06-30T00:00:00.000+00:00
需求为检索未来1个月内过生日的用户。
此前在pymongo中编写的查询逻辑存在缺陷,仅能返回生日恰好为当天、以及恰好为1个月后当天的用户,无法匹配区间内所有符合条件的结果,原有错误代码如下:
find({ "$and": [ {"$and": [ {"$expr": {"$gte": [{"$month": "$birthday"}, today.month]}}, {"$expr": {"$gte": [{"$dayOfMonth": "$birthday"}, today.day]}} ]}, {"$and": [ {"$expr": {"$lte": [{"$month": "$birthday"}, one_month_from_today.month]}}, {"$expr": {"$lte": [{"$dayOfMonth": "$birthday"}, one_month_from_today.day]}} ]} ] })
错误原因
原有逻辑将月份和日期拆分为独立条件做大小比较,完全没有处理跨月、跨年的边界场景:
- 跨月场景:例如今天是6月20日,7月5日过生日的用户,月份7大于6满足第一个判断条件,但日期5小于20,会被原逻辑的日期大于等于判断直接过滤
- 跨年场景:例如今天是12月20日,1个月后是次年1月20日,原逻辑要求生日月份既要大于等于12又要小于等于1,逻辑本身矛盾,完全匹配不到1月过生日的用户
正确实现方案
核心思路是将用户生日统一换算为距离当前最近一次生日的日期,再和「今日0点」「30天后0点」两个基准时间做区间比较,同时兼容闰日、跨月、跨年的特殊情况。
实现逻辑
- 先计算两个基准时间:当日0点时间、30天后0点时间
- 提取用户生日的月、日字段,拼接为当前年份的对应日期,做闰日兼容(如果用户生日是2月29日且当前年为平年,自动按2月28日计算,避免日期转换报错)
- 如果拼接出的当前年生日早于当日,说明今年的生日已经过了,将生日日期加1年取明年的生日
- 判断换算后的最近一次生日是否落在两个基准时间的区间内即可
pymongo可直接使用的代码
from datetime import datetime, timedelta # 计算时间基准 today = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0) end_date = today + timedelta(days=30) current_year = today.year db.user.find({ "$expr": { "$let": { "vars": { "birth_month": {"$month": "$birthday"}, "birth_day": {"$dayOfMonth": "$birthday"}, # 生成当前年对应的生日,自动兼容2月29日闰日场景 "current_year_birth": { "$dateFromParts": { "year": current_year, "month": "$$birth_month", "day": { "$min": [ "$$birth_day", # 取出生日月的最后一天,避免平年2月29日转换报错 {"$dayOfMonth": {"$dateFromParts": {"year": current_year, "month": "$$birth_month", "day": 1}}} ] } } } }, "in": { "$and": [ # 生日大于等于今日 {"$gte": [ {"$cond": [ {"$gte": ["$$current_year_birth", today]}, "$$current_year_birth", # 今年生日已过则取明年的生日 {"$dateAdd": {"startDate": "$$current_year_birth", "unit": "year", "amount": 1}} ]}, today ]}, # 生日早于30天后的日期 {"$lt": [ {"$cond": [ {"$gte": ["$$current_year_birth", today]}, "$$current_year_birth", {"$dateAdd": {"startDate": "$$current_year_birth", "unit": "year", "amount": 1}} ]}, end_date ]} ] } } } })
注意:如果使用的MongoDB版本低于5.0,不支持
$dateAdd操作符,可以用$dateFromParts手动给年份加1替换对应逻辑即可。
内容的提问来源于stack exchange,提问作者Joel
相关产品推荐
相关产品推荐

