楼主: CDA网校
236 0

[每天一个数据分析师] 5个实用DIY Python日期时间解析函数 [推广有奖]

管理员

已卖:189份资源

泰斗

4%

还不是VIP/贵宾

-

威望
3
论坛币
123742 个
通用积分
11685.8360
学术水平
278 点
热心指数
286 点
信用等级
253 点
经验
230666 点
帖子
7052
精华
19
在线时间
4401 小时
注册时间
2019-9-13
最后登录
2026-2-6

初级热心勋章

楼主
CDA网校 学生认证  发表于 2026-2-3 14:25:19 |AI写论文

+2 论坛币
k人 参与回答

经管之家送您一份

应届毕业生专属福利!

求职就业群
赵安豆老师微信:zhaoandou666

经管之家联合CDA

送您一个全额奖学金名额~ !

感谢您参与论坛问题回答

经管之家送您两个论坛币!

+2 论坛币

日期时间解析本不应让代码出错,但实际开发中却常常踩坑。这5个DIY Python函数可帮助你将现实中的杂乱日期时间数据,转化为干净可用的格式。

作者:巴拉·普里娅·C(Bala Priya C),KDnuggets特约编辑兼技术内容专家

发布时间:2026年1月26日

领域:Python

引言

日期时间解析看似简单,实际操作起来却处处是难点。Python的datetime模块能很好地处理标准格式,但现实中的数据往往杂乱无章——用户输入、网页爬取的数据、遗留系统输出,总能出现各种意想不到的格式,让人防不胜防。

本文将带你一步步实现5个实用函数,解决日期时间解析中的常见问题。读完本文,你将学会如何构建灵活的解析器,应对项目中遇到的各种杂乱日期格式。

1. 解析相对时间字符串

社交媒体应用、聊天软件和活动动态中,常会显示“5分钟前”“2天前”这类时间戳。当你爬取或处理这类数据时,需要将这些相对时间字符串转换为实际的datetime对象。

以下函数可处理常见的相对时间表达式:

from datetime import datetime, timedelta
import re

def parse_relative_time(time_string, reference_time=None):
    """
    将相对时间字符串转换为datetime对象。
    
    示例:"2 hours ago"(2小时前)、"3 days ago"(3天前)、"1 week ago"(1周前)
    """

    if reference_time is None:
        reference_time = datetime.now()
    
    # 标准化字符串(统一转为小写、去除首尾空格)
    time_string = time_string.lower().strip()
    
    # 正则表达式:匹配“数字 + 时间单位 + ago”格式
    pattern = r'(\d+)\s*(second|minute|hour|day|week|month|year)s?\s*ago'
    match = re.match(pattern, time_string)
    
    if not match:
        raise ValueError(f"无法解析该时间字符串: {time_string}")
    
    amount = int(match.group(1))  # 提取数字部分
    unit = match.group(2)         # 提取时间单位
    
    # 时间单位与timedelta参数的映射关系
    unit_mapping = {
        'second''seconds',
        'minute''minutes',
        'hour''hours',
        'day''days',
        'week''weeks',
    }
    
    if unit in unit_mapping:
        # 对timedelta支持的单位,直接计算时间差
        delta_kwargs = {unit_mapping[unit]: amount}
        return reference_time - timedelta(**delta_kwargs)
    elif unit == 'month':
        # 月份近似处理:按每月30天计算
        return reference_time - timedelta(days=amount * 30)
    elif unit == 'year':
        # 年份近似处理:按每年365天计算
        return reference_time - timedelta(days=amount * 365)

该函数使用正则表达式提取字符串中的数字和时间单位:(\d+)捕获一个或多个数字,(second|minute|hour|day|week|month|year)匹配时间单位,s?让复数后缀“s”可选,因此“hour”和“hours”均可正常解析。

对于timedelta直接支持的单位(秒到周),我们创建对应的时间差对象,再从参考时间中减去该时间差;对于月份和年份,由于其天数不固定,我们分别按30天和365天进行近似处理——这种方式虽不绝对精确,但能满足大多数使用场景。

reference_time参数允许你指定自定义的“当前时间”,方便测试或处理历史数据时使用。

测试示例:

result1 = parse_relative_time("2 hours ago")
result2 = parse_relative_time("3 days ago")
result3 = parse_relative_time("1 week ago")

print(f"2 hours ago: {result1}")
print(f"3 days ago: {result2}")
print(f"1 week ago: {result3}")

输出结果:

2 hours ago: 2026-01-06 12:09:34.584107
3 days ago: 2026-01-03 14:09:34.584504
1 week ago: 2025-12-30 14:09:34.584558

2. 从自然语言文本中提取日期

有时你需要从文本中提取隐藏的日期,比如“会议定于2026年1月15日举行”或“请于3月3日前回复”。此时无需手动解析整个句子,只需提取其中的日期部分即可。

以下函数可从自然语言文本中查找并提取日期:

import re
from datetime import datetime

def extract_date_from_text(text, current_year=None):
    """
    从自然语言文本中提取日期。
    
    支持格式:
    - "January 15th, 2024"(2024年1月15日)
    - "March 3rd"(3月3日)
    - "Dec 25th, 2023"(2023年12月25日)
    """

    if current_year is None:
        current_year = datetime.now().year
    
    # 月份映射字典(支持完整名称和缩写)
    months = {
        'january'1'jan'1,
        'february'2'feb'2,
        'march'3'mar'3,
        'april'4'apr'4,
        'may'5,
        'june'6'jun'6,
        'july'7'jul'7,
        'august'8'aug'8,
        'september'9'sep'9'sept'9,
        'october'10'oct'10,
        'november'11'nov'11,
        'december'12'dec'12
    }
    
    # 正则表达式:匹配“月份 日期(序数后缀),年份”(年份可选)
    pattern = r'(january|jan|february|feb|march|mar|april|apr|may|june|jun|july|jul|august|aug|september|sep|sept|october|oct|november|nov|december|dec)\s+(\d{1,2})(?:st|nd|rd|th)?(?:,?\s+(\d{4}))?'
    
    matches = re.findall(pattern, text.lower())
    
    if not matches:
        return None
    
    # 取第一个匹配结果(若有多个日期,可根据需求调整)
    month_str, day_str, year_str = matches[0]
    
    month = months[month_str]  # 将月份字符串转为数字
    day = int(day_str)         # 日期转为整数
    year = int(year_str) if year_str else current_year  # 无年份时默认当前年份
    
    return datetime(year, month, day)

该函数首先创建一个月份映射字典,将月份的完整名称(如January)和缩写(如Jan)对应到对应的数字(如1)。正则表达式则用于匹配“月份+日期+可选年份”的格式,其中序数后缀(st、nd、rd、th)和年份均为可选。

正则表达式中的(?:...)表示非捕获组,即匹配该模式但不单独保存结果,适用于序数后缀、年份分隔符等可选部分,避免捕获无用的冗余信息。

当文本中未指定年份时,函数默认使用当前年份——这符合日常逻辑:比如在1月份提到“3月3日”,通常指当年的3月3日,而非上一年。

测试不同文本格式:

text1 = "The meeting is scheduled for January 15th, 2026 at 3pm"
text2 = "Please respond by March 3rd"
text3 = "Deadline: Dec 25th, 2026"

date1 = extract_date_from_text(text1)
date2 = extract_date_from_text(text2)
date3 = extract_date_from_text(text3)

print(f"From '{text1}': {date1}")
print(f"From '{text2}': {date2}")
print(f"From '{text3}': {date3}")

输出结果:

From 'The meeting is scheduled for January 15th, 2026 at 3pm': 2026-01-15 00:00:00
From 'Please respond by March 3rd': 2026-03-03 00:00:00
From 'Deadline: Dec 25th, 2026': 2026-12-25 00:00:00

3. 智能检测并解析多种灵活日期格式

现实中的日期数据格式繁多,为每种格式单独编写解析器十分繁琐。因此,我们构建一个可自动尝试多种格式的智能日期解析函数。

以下函数可处理多种常见日期格式:

from datetime import datetime

def parse_flexible_date(date_string):
    """
    解析多种常见格式的日期。
    
    依次尝试多种格式,返回第一个匹配成功的结果。
    """

    date_string = date_string.strip()
    
    # 常见日期格式列表(可根据需求扩展)
    formats = [
        '%Y-%m-%d',           # ISO标准格式:2026-01-15
        '%Y/%m/%d',           # 斜杠分隔格式:2026/01/15
        '%d-%m-%Y',           # 日-月-年格式:15-01-2026
        '%d/%m/%Y',           # 日/月/年格式:15/01/2026
        '%m/%d/%Y',           # 月/日/年格式:01/15/2026
        '%d.%m.%Y',           # 点分隔格式:15.01.2026
        '%Y%m%d',             # 无分隔符格式:20260115
        '%B %d, %Y',          # 完整月份+日+年:January 15, 2026
        '%b %d, %Y',          # 月份缩写+日+年:Jan 15, 2026
        '%d %B %Y',           # 日+完整月份+年:15 January 2026
        '%d %b %Y',           # 日+月份缩写+年:15 Jan 2026
    ]
    
    # 依次尝试每种格式,匹配成功则返回解析结果
    for fmt in formats:
        try:
            return datetime.strptime(date_string, fmt)
        except ValueError:
            continue
    
    # 所有格式均匹配失败时,抛出异常
    raise ValueError(f"无法解析该日期: {date_string}")

该函数采用“暴力匹配”思路:依次尝试列表中的每种日期格式,使用strptime函数进行解析。若解析失败(抛出ValueError异常),则跳过当前格式,尝试下一种,直到匹配成功。

格式列表的顺序很重要:我们将ISO标准格式(%Y-%m-%d)放在首位,因为它在技术场景中最常用;像%d/%m/%Y和%m/%d/%Y这类易混淆的格式,放在后面避免误判。若你明确知道数据的主流格式,可调整列表顺序,优先匹配常用格式,提升解析效率。

测试多种日期格式:

# 测试不同格式的日期
dates = [
    "2026-01-15",
    "15/01/2026",
    "01/15/2026",
    "15.01.2026",
    "20260115",
    "January 15, 2026",
    "15 Jan 2026"
]

for date_str in dates:
    parsed = parse_flexible_date(date_str)
    print(f"{date_str:20} -> {parsed}")

输出结果:

2026-01-15           -> 2026-01-15 00:00:00
15/01/2026           -> 2026-01-15 00:00:00
01/15/2026           -> 2026-01-15 00:00:00
15.01.2026           -> 2026-01-15 00:00:00
20260115             -> 2026-01-15 00:00:00
January 15, 2026     -> 2026-01-15 00:00:00
15 Jan 2026          -> 2026-01-15 00:00:00

这种方法虽不是最高效的,但胜在简单易懂,且能覆盖绝大多数实际开发中遇到的日期格式。

4. 解析时间时长

视频播放器、运动追踪器、时间管理应用中,常会显示“1h 30m”“2:45:30”这类时长格式。当解析用户输入或爬取的数据时,需要将这些时长转换为timedelta对象,以便进行后续的计算(如时长累加、差值对比)。

以下函数可解析多种常见的时长格式:

from datetime import timedelta
import re

def parse_duration(duration_string):
    """
    将时长字符串解析为timedelta对象。
    
    支持格式:
    - "1h 30m 45s"(1小时30分钟45秒)
    - "2:45:30"(时:分:秒)
    - "90 minutes"(90分钟)
    - "1.5 hours"(1.5小时)
    """

    duration_string = duration_string.strip().lower()
    
    # 先尝试解析冒号分隔格式(时:分:秒 或 分:秒)
    if ':' in duration_string:
        parts = duration_string.split(':')
        if len(parts) == 2:
            # 分:秒格式
            minutes, seconds = map(int, parts)
            return timedelta(minutes=minutes, seconds=seconds)
        elif len(parts) == 3:
            # 时:分:秒格式
            hours, minutes, seconds = map(int, parts)
            return timedelta(hours=hours, minutes=minutes, seconds=seconds)
    
    # 再尝试解析单位标识格式(如1h 30m 45s)
    total_seconds = 0
    
    # 匹配小时(支持整数和小数,如1h、1.5hours)
    hours_match = re.search(r'(\d+(?:\.\d+)?)\s*h(?:ours?)?', duration_string)
    if hours_match:
        total_seconds += float(hours_match.group(1)) * 3600  # 小时转为秒
    
    # 匹配分钟(支持整数和小数,如30m、45.5minutes)
    minutes_match = re.search(r'(\d+(?:\.\d+)?)\s*m(?:in(?:ute)?s?)?', duration_string)
    if minutes_match:
        total_seconds += float(minutes_match.group(1)) * 60  # 分钟转为秒
    
    # 匹配秒(支持整数和小数,如45s、120.5seconds)
    seconds_match = re.search(r'(\d+(?:\.\d+)?)\s*s(?:ec(?:ond)?s?)?', duration_string)
    if seconds_match:
        total_seconds += float(seconds_match.group(1))  # 秒直接累加
    
    if total_seconds > 0:
        return timedelta(seconds=total_seconds)
    
    # 所有格式均匹配失败时,抛出异常
    raise ValueError(f"无法解析该时长: {duration_string}")

该函数主要处理两种主流时长格式:冒号分隔格式和单位标识格式。对于冒号分隔格式,根据分隔后的部分数量,分别解析为“时:分:秒”或“分:秒”,再转换为timedelta对象。

对于单位标识格式,使用三个独立的正则表达式分别匹配小时、分钟、秒:(\d+(?:.\d+)?)可匹配整数(如1)或小数(如1.5);\s*h(?:ours?)?可匹配“h”“hour”“hours”,且允许单位前有空格。

解析出的每个时长单位,都会先转换为秒并累加到总秒数中,最后通过总秒数创建timedelta对象。这种方式支持部分时长格式(如仅“45s”“2h 15m”),无需所有单位同时存在。

测试多种时长格式:

durations = [
    "1h 30m 45s",
    "2:45:30",
    "90 minutes",
    "1.5 hours",
    "45s",
    "2h 15m"
]

for duration in durations:
    parsed = parse_duration(duration)
    print(f"{duration:15} -> {parsed}")

输出结果:

1h 30m 45s      -> 1:30:45
2:45:30         -> 2:45:30
90 minutes      -> 1:30:00
1.5 hours       -> 1:30:00
45s             -> 0:00:45
2h 15m          -> 2:15:00

5. 解析ISO周日期

部分系统会使用ISO周日期格式,而非常规的日历日期。例如“2026-W03-2”表示“2026年第3周,第2天(星期二)”。这种格式在商业场景中较为常见,因为这类场景的规划多以周为单位。

以下函数可解析ISO周日期:

from datetime import datetime, timedelta

def parse_iso_week_date(iso_week_string):
    """
    解析ISO周日期格式:YYYY-Www-D
    
    示例:"2024-W03-2" = 2024年第3周,星期二
    
    ISO周编号规则:
    - 第1周是包含该年第一个星期四的周
    - 星期几编号:1(星期一)至7(星期日)
    """

    # 拆分ISO周日期字符串(格式:YYYY-Www-D)
    parts = iso_week_string.split('-')
    
    # 校验格式合法性
    if len(parts) != 3 or not parts[1].startswith('W'):
        raise ValueError(f"无效的ISO周日期格式: {iso_week_string}")
    
    year = int(parts[0])                # 年份
    week = int(parts[1][1:])            # 周数(去除前缀W)
    day = int(parts[2])                 # 一周中的第几天
    
    # 校验周数和天数的合法性
    if not (1 <= week <= 53):
        raise ValueError(f"周数必须在1-53之间: {week}")
    
    if not (1 <= day <= 7):
        raise ValueError(f"天数必须在1-7之间: {day}")
    
    # 找到该年的1月4日(该日期一定在第1周内,ISO标准规定)
    jan_4 = datetime(year, 14)
    
    # 找到第1周的星期一(jan_4.weekday()返回0=周一,6=周日)
    week_1_monday = jan_4 - timedelta(days=jan_4.weekday())
    
    # 计算目标日期:第1周周一 + (目标周数-1)周 + (目标天数-1)天
    target_date = week_1_monday + timedelta(weeks=week - 1, days=day - 1)
    
    return target_date

ISO周日期遵循严格的规则:第1周定义为包含该年第一个星期四的周,这意味着第1周可能始于上一年的12月;一周中的天数从1(星期一)到7(星期日)编号,与Python中weekday()方法的返回值(0=周一,6=周日)逻辑一致。

该函数采用一种可靠的计算方法:首先找到该年的1月4日(ISO标准规定,该日期一定在第1周内),再通过该日期推算出第1周的星期一;最后以第1周星期一为基准,加上目标周数和目标天数的偏移量,得到最终的日期。

测试ISO周日期:

# 测试ISO周日期解析
iso_dates = [
    "2024-W01-1",  # 2024年第1周,星期一
    "2024-W03-2",  # 2024年第3周,星期二
    "2024-W10-5",  # 2024年第10周,星期五
]

for iso_date in iso_dates:
    parsed = parse_iso_week_date(iso_date)
    print(f"{iso_date} -> {parsed.strftime('%Y-%m-%d (%A)')}")

输出结果:

2024-W01-1 -> 2024-01-01 (Monday)
2024-W03-2 -> 2024-01-16 (Tuesday)
2024-W10-5 -> 2024-03-08 (Friday)

这种格式虽不如常规日期常见,但在遇到时,提前准备好解析函数能节省大量开发时间。

总结

本文中的每个函数,都利用正则表达式和日期时间运算,解决了日期时间解析中的一种常见问题。这些技术思路可迁移到其他解析场景中,你可以根据自己项目中的自定义日期格式,调整正则表达式和解析逻辑。

自己编写解析器,能帮助你更深入理解日期时间解析的底层逻辑。当遇到标准库无法处理的非标准日期格式时,你将具备编写自定义解决方案的能力。

这些函数特别适用于小型脚本、原型开发和学习项目——在这些场景中,引入重量级的第三方依赖往往得不偿失。祝你编码愉快!

推荐学习书籍 《CDA一级教材》适合CDA一级考生备考,也适合业务及数据分析岗位的从业者提升自我。完整电子版已上线CDA网校,累计已有10万+在读~ !

免费加入阅读:https://edu.cda.cn/goods/show/3151?targetId=5147&preview=0

二维码

扫码加我 拉你入群

请注明:姓名-公司-职位

以便审核进群资格,未注明则拒绝

关键词:python DIY Reference scheduled September

您需要登录后才可以回帖 登录 | 我要注册

本版微信群
加好友,备注cda
拉您进交流群
GMT+8, 2026-2-7 07:46