python一次性解决任意常见日期字符串 ⇄ 统一格式

发布于:2025-08-28 ⋅ 阅读:(13) ⋅ 点赞:(0)

下面这段代码一次性解决「任意常见日期字符串 ⇄ 统一格式」的相互转换需求。

特点

  1. 能解析 30+ 种常见写法,也能把它们再转回任意你想要的格式;
  2. 输入宽容:空格、中文、斜杠、点号、无零补位都能识别;
  3. 输出灵活:想输出 datetime 对象、YYYY-MM-DD 字符串、MM/DD/YYYYYYYY年M月D日 都行;
  4. 支持偏移:如 “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'

网站公告

今日签到

点亮在社区的每一天
去签到