目 录CONTENT

文章目录

解析日期和时间的5个实用Python DIY函数

Administrator
2026-01-28 / 0 评论 / 0 点赞 / 1 阅读 / 0 字

📢 转载信息

原文链接:https://www.kdnuggets.com/5-useful-diy-python-functions-for-parsing-dates-and-times

原文作者:Bala Priya C


5 Useful DIY Python Functions for Parsing Dates and Times
Image by Author

 

# 引言

 
解析日期和时间是那种看似简单,但等你真正尝试去做时才会发现困难重重的任务。Python的datetime模块能很好地处理标准格式,但现实世界中的数据是混乱的。用户输入、抓取的网页数据以及遗留系统经常会带来意外情况。

本文将引导您了解处理常见日期时间解析任务的五个实用函数。读完后,您将了解如何构建灵活的解析器,以应对项目中遇到的各种混乱的日期格式。

GitHub上的代码链接

 

# 1. 解析相对时间字符串

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

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

from datetime import datetime, timedelta import re def parse_relative_time(time_string, reference_time=None): """ Convert relative time strings to datetime objects. Examples: "2 hours ago", "3 days ago", "1 week ago" """ if reference_time is None: reference_time = datetime.now() # Normalize the string time_string = time_string.lower().strip() # Pattern: number + time unit + "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"Cannot parse: {time_string}") amount = int(match.group(1)) unit = match.group(2) # Map units to timedelta kwargs unit_mapping = { 'second': 'seconds', 'minute': 'minutes', 'hour': 'hours', 'day': 'days', 'week': 'weeks', } if unit in unit_mapping: delta_kwargs = {unit_mapping[unit]: amount} return reference_time - timedelta(**delta_kwargs) elif unit == 'month': # Approximate: 30 days per month return reference_time - timedelta(days=amount * 30) elif unit == 'year': # Approximate: 365 days per year return reference_time - timedelta(days=amount * 365)

 

该函数使用正则表达式(regex)从字符串中提取数字和时间单位。模式(\d+)捕获一个或多个数字,而(second|minute|hour|day|week|month|year)匹配时间单位。s?使复数形式的's'成为可选,因此“hour”和“hours”都可以匹配。

对于timedelta直接支持的单位(从秒到周),我们创建一个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): """ Extract dates from natural language text. Handles formats like: - "January 15th, 2024" - "March 3rd" - "Dec 25th, 2023" """ if current_year is None: current_year = datetime.now().year # Month names (full and abbreviated) 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: Month Day(st/nd/rd/th), Year (year optional) 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 # Take the first match 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)

 

该函数构建一个字典,将月份名称(完整和缩写)映射到它们的数字值。正则表达式模式匹配月份名称后跟带有可选序数后缀(st, nd, rd, th)和可选年份的日期数字。

(?:...)语法创建了一个非捕获组。这意味着我们匹配该模式,但不会单独保存它。这对于序数后缀和年份等可选部分很有用。

当未提供年份时,函数默认为当前年份。这是合乎逻辑的,因为如果有人在一月份提到“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): """ Parse dates in multiple common formats. Tries various formats and returns the first match. """ date_string = date_string.strip() # List of common date formats formats = [ '%Y-%m-%d', '%Y/%m/%d', '%d-%m-%Y', '%d/%m/%Y', '%m/%d/%Y', '%d.%m.%Y', '%Y%m%d', '%B %d, %Y', '%b %d, %Y', '%d %B %Y', '%d %b %Y', ] # Try each format for fmt in formats: try: return datetime.strptime(date_string, fmt) except ValueError: continue # If nothing worked, raise an error raise ValueError(f"Unable to parse date: {date_string}")

 

此函数采用一种蛮力方法。它会尝试每种格式,直到找到一个匹配的为止。如果日期字符串与格式不匹配,strptime函数会引发ValueError,因此我们捕获该异常并移至下一个格式。

格式的顺序很重要。我们将国际标准化组织(ISO)格式(%Y-%m-%d)放在最前面,因为它在技术环境中最为常见。像%d/%m/%Y%m/%d/%Y这样的模糊格式出现在后面。如果您知道数据始终使用其中一种,请重新排列列表以优先处理它。

让我们用各种日期格式进行测试:

# Test different formats 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. 解析时间时长

 
视频播放器、锻炼追踪器和时间跟踪应用会显示“1小时30分钟”或“2:45:30”等时长。解析用户输入或抓取数据时,需要将这些转换为timedelta对象以进行计算。

这是一个解析常见时长格式的函数:

from datetime import timedelta import re def parse_duration(duration_string): """ Parse duration strings into timedelta objects. Handles formats like: - "1h 30m 45s" - "2:45:30" (H:M:S) - "90 minutes" - "1.5 hours" """ duration_string = duration_string.strip().lower() # Try colon format first (H:M:S or M:S) if ':' in duration_string: parts = duration_string.split(':') if len(parts) == 2: # M:S format minutes, seconds = map(int, parts) return timedelta(minutes=minutes, seconds=seconds) elif len(parts) == 3: # H:M:S format hours, minutes, seconds = map(int, parts) return timedelta(hours=hours, minutes=minutes, seconds=seconds) # Try unit-based format (1h 30m 45s) total_seconds = 0 # Find hours hours_match = re.search(r'(\d+(?:\.\d+)?)\s*h(?:ours?)?', duration_string) if hours_match: total_seconds += float(hours_match.group(1)) * 3600 # Find minutes 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 # Find seconds 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"Unable to parse duration: {duration_string}")

 

该函数处理两种主要格式:以冒号分隔的时间和基于单位的字符串。对于冒号格式,我们按冒号分割,并将部分解释为小时、分钟和秒(或仅分钟和秒,如果是两部分时长)。

对于基于单位的格式,我们使用三个单独的正则表达式模式来查找小时、分钟和秒。模式(\d+(?:\.\d+)?)匹配整数或小数,如“1.5”。模式\s*h(?:ours?)?匹配“h”、“hour”或“hours”,并允许可选的空格。

每个匹配到的值都会转换为秒并加到总数中。这种方法允许函数处理部分时长,如“45秒”或“2小时15分钟”,而无需要求所有单位都存在。

现在让我们用各种时长格式测试该函数:

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”这样的ISO周日期表示“2026年的第3周,第2天(星期二)”。这种格式在业务环境中很常见,因为规划是按周进行的。

这是一个解析ISO周日期的函数:

from datetime import datetime, timedelta def parse_iso_week_date(iso_week_string): """ Parse ISO week date format: YYYY-Www-D Example: "2024-W03-2" = Week 3 of 2024, Tuesday ISO week numbering: - Week 1 is the week with the first Thursday of the year - Days are numbered 1 (Monday) through 7 (Sunday) """ # Parse the format: YYYY-Www-D parts = iso_week_string.split('-') if len(parts) != 3 or not parts[1].startswith('W'): raise ValueError(f"Invalid ISO week format: {iso_week_string}") year = int(parts[0]) week = int(parts[1][1:]) # Remove 'W' prefix day = int(parts[2]) if not (1 <= week <= 53): raise ValueError(f"Week must be between 1 and 53: {week}") if not (1 <= day <= 7): raise ValueError(f"Day must be between 1 and 7: {day}") # Find January 4th (always in week 1) jan_4 = datetime(year, 1, 4) # Find Monday of week 1 week_1_monday = jan_4 - timedelta(days=jan_4.weekday()) # Calculate the target date target_date = week_1_monday + timedelta(weeks=week - 1, days=day - 1) return target_date

 

ISO周日期遵循特定规则。第1周被定义为包含当年第一个星期四的那一周。这意味着第1周可能从上一年的十二月开始。

该函数采用可靠的方法:找到1月4日(它总是在第1周),然后找到那一周的星期一。从那里,我们加上适当的周数和天数来达到目标日期。

计算jan_4.weekday()返回0代表星期一到6代表星期日。从1月4日减去这个值可以得到第1周的星期一。然后我们加上(week - 1)周和(day - 1)天以得到最终日期。

让我们测试一下:

# Test ISO week dates iso_dates = [ "2024-W01-1", # Week 1, Monday "2024-W03-2", # Week 3, Tuesday "2024-W10-5", # Week 10, Friday ] 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)

 

这种格式不如常规日期常见,但在遇到时,准备好解析器可以节省大量时间。

 

# 总结

 
本文中介绍的每个函数都使用正则表达式模式和datetime算术来处理格式变化。这些技术可以转移到其他解析挑战中,因为您可以根据项目中自定义的日期格式调整这些模式。

构建自己的解析器可以帮助您了解日期解析是如何运作的。当您遇到标准库无法处理的非标准日期格式时,您将准备好编写自定义解决方案。

这些函数特别适用于小型脚本、原型和学习项目,在这些项目中,引入重量级的外部依赖可能有点大材小用。祝您编码愉快!
 
 

Bala Priya C是一位来自印度的开发者和技术作家。她喜欢在数学、编程、数据科学和内容创作的交叉点工作。她的兴趣和专长领域包括DevOps、数据科学和自然语言处理。她喜欢阅读、写作、编码和咖啡!目前,她正致力于通过撰写教程、操作指南、观点文章等方式学习并与开发者社区分享她的知识。Bala还创建引人入胜的资源概述和编码教程。




🚀 想要体验更好更全面的AI调用?

欢迎使用青云聚合API,约为官网价格的十分之一,支持300+全球最新模型,以及全球各种生图生视频模型,无需翻墙高速稳定,文档丰富,小白也可以简单操作。

0

评论区