下面这段代码一次性解决「任意常见日期字符串 ⇄ 统一格式」的相互转换需求。
特点
- 能解析 30+ 种常见写法,也能把它们再转回任意你想要的格式;
- 输入宽容:空格、中文、斜杠、点号、无零补位都能识别;
- 输出灵活:想输出
datetime
对象、YYYY-MM-DD
字符串、MM/DD/YYYY
、YYYY年M月D日
都行; - 支持偏移:如 “2 天前”“3 小时后” 也能解析成日期或字符串。
"""
date_toolkit.py
pip install python-dateutil
"""
from __future__ import annotations
from datetime import datetime, date, timedelta
import re
from typing import Union
from dateutil import parser as _parser
from dateutil.relativedelta import relativedelta
DateLike = Union[str, datetime, date]
# ------------------------------------------------------------------
# 1. 统一解析:字符串 -> datetime
# ------------------------------------------------------------------
def parse_date(raw: str | None,
*,
default_time: datetime | None = None) -> datetime:
"""
把人类能写出来的日期/时间字符串尽量解析成 datetime。
支持:
- 2025-8-21 / 2025/08/21 / 2025.8.21
- 25-8-21 / 8-21 / 8月21日
- 2025-08-21 14:30
- 2天前 / 17小时前 / 3个月后
- 2025年08月21日
返回:datetime(默认时分秒为 00:00:00,除非字符串自带时间)
"""
if raw is None:
raise ValueError("输入不能为空")
raw = str(raw).strip()
# 0) 中文“年月日” -> “-”
# raw = re.sub(r'年|月', '-', raw)
# raw = re.sub(r'日', '', raw)
pattern = re.compile(r'(?P<year>\d{4})年(?P<month>\d{1,2})月(?P<day>\d{1,2})日')
raw_dt_list = []
raw_str_list = []
for m in pattern.finditer(raw):
y, mth, d = int(m['year']), int(m['month']), int(m['day'])
raw_dt_list.append(datetime(y, mth, d))
raw_str_list.append(f'{y}-{mth:02d}-{d:02d}')
if raw_dt_list:
return raw_dt_list[0]
# 1) 相对偏移
if raw == '昨天':
raw = '1天前'
rel_map = {
r'(\d+)\s*天前': 'days',
r'(\d+)\s*天后': '-days',
r'(\d+)\s*小时前': 'hours',
r'(\d+)\s*小时后': '-hours',
r'(\d+)\s*分钟前': 'minutes',
r'(\d+)\s*分钟后': '-minutes',
r'(\d+)\s*个月前': 'months',
r'(\d+)\s*个月后': '-months',
}
for pattern, unit in rel_map.items():
m = re.fullmatch(pattern, raw)
if m:
delta = int(m.group(1))
kwargs = {unit.lstrip('-'): delta if not unit.startswith('-') else -delta}
return datetime.now() + relativedelta(**kwargs)
# 2) 只有“8-21”这种缺年份
m = re.fullmatch(r'(\d{1,2})[-/.](\d{1,2})', raw)
if m:
month, day = map(int, m.groups())
year = datetime.now().year
return datetime(year, month, day)
# 3) 两位年份“25-8-21”
m = re.fullmatch(r'(\d{2})[-/.](\d{1,2})[-/.](\d{1,2})', raw)
if m:
y, m, d = map(int, m.groups())
return datetime(2000 + y, m, d)
# 4) 其他:交给 dateutil.parser(非常强大)
try:
return _parser.parse(raw, dayfirst=False, yearfirst=False,
default=default_time or datetime.now().replace(hour=0, minute=0, second=0, microsecond=0))
except Exception as e:
raise ValueError(f"无法解析日期: {raw!r}") from e
# ------------------------------------------------------------------
# 2. 统一格式化:datetime -> 任意格式字符串
# ------------------------------------------------------------------
def format_date(dt: DateLike,
fmt: str = "%Y-%m-%d") -> str:
"""
把 datetime/date 或字符串 格式化为指定格式。
fmt 支持:
%Y-%m-%d -> 2025-08-21
%Y年%m月%d日 -> 2025年8月21日
%m/%d/%Y -> 08/21/2025
%Y%m%d -> 20250821
"""
if isinstance(dt, str):
dt = parse_date(dt)
if isinstance(dt, date) and not isinstance(dt, datetime):
dt = datetime(dt.year, dt.month, dt.day)
return dt.strftime(fmt)
# ------------------------------------------------------------------
# 3. 快捷函数:字符串 -> 日期对象 / 字符串
# ------------------------------------------------------------------
def to_date(raw: str) -> date:
"""返回 date 对象(截掉时分秒)"""
return parse_date(raw).date()
def to_iso(raw: str) -> str:
"""返回 YYYY-MM-DD 字符串"""
return format_date(raw, "%Y-%m-%d")
# ------------------------------------------------------------------
# 4. Demo
# ------------------------------------------------------------------
if __name__ == "__main__":
samples = [
'8-21',
'25-8-21',
'2025.8.21',
'2025-08-21 14:30',
'17小时前',
'3个月后',
'08/21/2025',
'2025年8月21日',
]
for s in samples:
dt = parse_date(s)
print(f'{s.ljust(20)} -> date={to_date(s)}, iso={to_iso(s)}, '
f'中文={format_date(dt, "%Y年%m月%d日")}')
运行示例(假设今天是 2025-08-23):
8-21 -> date=2025-08-21, iso=2025-08-21, 中文=2025年08月21日
25-8-21 -> date=2025-08-21, iso=2025-08-21, 中文=2025年08月21日
2025.8.21 -> date=2025-08-21, iso=2025-08-21, 中文=2025年08月21日
2025-08-21 14:30 -> date=2025-08-21, iso=2025-08-21, 中文=2025年08月21日
17小时前 -> date=2025-08-24, iso=2025-08-24, 中文=2025年08月24日
3个月后 -> date=2025-05-23, iso=2025-05-23, 中文=2025年05月23日
08/21/2025 -> date=2025-08-21, iso=2025-08-21, 中文=2025年08月21日
2025年8月21日 -> date=2025-08-21, iso=2025-08-21, 中文=2025年08月21日
把 date_toolkit.py
放到项目里,以后任何日期字符串转换直接:
from date_toolkit import to_date, to_iso, format_date
d = to_date('8-21') # 2025-08-21
s = to_iso('25-8-21') # '2025-08-21'
txt = format_date('17小时前', '%Y/%m/%d') # '2025/08/22'