今天继续记录一题的内容,共有三个版本。
第六十四题,日期减法练习:
1.首先是最原初的版本:
#定义一个函数,该函数接收两个参数:一个遵循‘YYYY-MM-DD’格式的日期字符串pdate以及一个整数days。该函数的任务是计算出从给定日期向前days后的新日期
#导入datetime模块
import datetime
#定义函数
def calculate_previous_date(pdate, days):
"""
计算从给定日期向前days后的日期
:param pdate: 遵循'YYYY-MM-DD'格式的日期字符串
:param days: 整数,表示天数
:return: 从给定日期向前days后的日期字符串,遵循'YYYY-MM-DD'格式
"""
pdate_obj = datetime.datetime.strptime(pdate, '%Y-%m-%d')
time_gap = datetime.timedelta(days=days)
new_date = pdate_obj - time_gap
return new_date.strftime('%Y-%m-%d')
#调用函数并接收返回值
original_date = '2023-04-01'
days = 5
result_date = calculate_previous_date(original_date, days)
print(f"从 {original_date} 往前推 {days} 天后的日期是:{result_date}")
这个是最原始版本的代码,完成了基本需求但也仅限如此,思路分析如下:
题目要求:写一个函数,接收 YYYY-MM-DD 日期字符串和天数,算出往前推 N 天后的日期。
我的思路:把 63 题的"加法"反过来用"减法"
63 题我用 日期 + timedelta 往后挪,这题就是往前推,所以核心一行改成 pdate_obj - time_gap。整个计算我拆成四步流水线:字符串转对象 → 造时长 → 相减 → 转回字符串,最后 return 一个排好 YYYY-MM-DD 格式的结果。
关键设计:计算逻辑封装进函数
这题我没把减法直接写在主程序里,而是抽成 calculate_previous_date(pdate, days),参数和返回值都清楚。好处是主程序只负责"喂进去、拿回来",怎么算的全藏在函数体内。唯一的小遗憾是:日期和天数是我在代码里写死的常量('2023-04-01'、5),换个数就得改代码重跑。
2.接下来是优化版本的代码:
# 定义一个函数,该函数接收两个参数:一个遵循'YYYY-MM-DD'格式的日期字符串pdate以及一个整数days。
# 该函数的任务是计算出从给定日期向前days天后的新日期
# 导入datetime模块
import datetime
# 定义函数
def calculate_previous_date(pdate, days):
"""
计算从给定日期向前days天后的日期
:param pdate: 遵循'YYYY-MM-DD'格式的日期字符串
:param days: 整数,表示天数
:return: 从给定日期向前days天后的日期字符串,遵循'YYYY-MM-DD'格式
"""
pdate_obj = datetime.datetime.strptime(pdate, '%Y-%m-%d')
time_gap = datetime.timedelta(days=days)
new_date = pdate_obj - time_gap
return new_date.strftime('%Y-%m-%d')
# 输入日期,并校验格式是否正确(YYYY-MM-DD)
while True:
original_date = input("请输入一个日期(格式:YYYY-MM-DD,例如 2023-04-01):").strip()
try:
datetime.datetime.strptime(original_date, '%Y-%m-%d')
break
except ValueError:
print("日期格式不正确,请按照 YYYY-MM-DD 的格式重新输入!")
# 输入要往前推的天数,并校验是否为正整数
while True:
days_input = input("请输入要往前推的天数(正整数):").strip()
try:
days = int(days_input)
if days < 0:
print("天数不能为负数,请重新输入!")
continue
break
except ValueError:
print("输入无效,请输入一个整数!")
# 调用函数并输出结果
result_date = calculate_previous_date(original_date, days)
print(f"从 {original_date} 往前推 {days} 天后的日期是:{result_date}")
可以看到代码量有了明显的提升,代码思路分析如下:
优化目标:把原版写死的日期/天数改成交互输入,并防止用户乱输导致崩溃。
核心计算一字没改,改的全在"入口"
函数 calculate_previous_date 我原封不动沿用,这次动的是怎么拿到那俩参数。原版是写死的常量,这版换成了两段 while True + try/except 的验证关卡,正是 43/44 题和 62 优化版练熟的老手艺。
两道输入关卡
第一关验日期格式。 用 strptime(输入, '%Y-%m-%d') 试探------能解析就 break 放行,报 ValueError 就说明写法不对,提示后自然进下一轮重输。第二关验天数更讲究,要过两道:先 int() 试转换接住字母,再用独立的 if days < 0 判负数。这里我特意记住 44 题的教训:范围检查必须放在 try/except 之外单独做,否则正常数字会绕过检查;而负数命中时用 continue 明确"打回重填"。
3.再接下来是最终优化的版本:
# 定义一个函数,该函数接收两个参数:一个遵循'YYYY-MM-DD'格式的日期字符串pdate以及一个整数days。
# 该函数的任务是计算出从给定日期向前days天后的新日期
# 本版本在“优化版”的基础上,进一步放宽了日期输入的要求:
# 借助 dateutil.parser 自动识别多种日期写法(例如 2023-04-01、2023/4/1、2023年4月1日、
# Apr 1 2023、04-01-2023 等),用户不必严格按照固定格式输入也能完成计算。
# 导入datetime模块
import datetime
# 导入dateutil的parser模块,用于灵活解析多种日期输入格式
from dateutil import parser
# 定义函数
def calculate_previous_date(pdate, days):
"""
计算从给定日期向前days天后的日期
:param pdate: 遵循'YYYY-MM-DD'格式的日期字符串
:param days: 整数,表示天数
:return: 从给定日期向前days天后的日期字符串,遵循'YYYY-MM-DD'格式
"""
pdate_obj = datetime.datetime.strptime(pdate, '%Y-%m-%d')
time_gap = datetime.timedelta(days=days)
new_date = pdate_obj - time_gap
return new_date.strftime('%Y-%m-%d')
# 输入日期:使用 dateutil.parser 自动识别多种格式,
# 解析成功后统一转换成标准的 YYYY-MM-DD 字符串,无法识别时提示重新输入
while True:
date_input = input("请输入一个日期(支持多种写法,例如 2023-04-01、2023/4/1、2023年4月1日、Apr 1 2023):").strip()
try:
parsed_date_obj = parser.parse(date_input)
original_date = parsed_date_obj.strftime('%Y-%m-%d')
break
except (ValueError, parser.parser.ParserError):
print("无法识别该日期,请重新输入一个有效的日期!")
# 输入要往前推的天数,并校验是否为正整数
while True:
days_input = input("请输入要往前推的天数(正整数):").strip()
try:
days = int(days_input)
if days < 0:
print("天数不能为负数,请重新输入!")
continue
break
except ValueError:
print("输入无效,请输入一个整数!")
# 调用函数并输出结果
result_date = calculate_previous_date(original_date, days)
print(f"从 {original_date} 往前推 {days} 天后的日期是:{result_date}")
代码思路分析如下:
优化目标:在优化版基础上彻底放宽日期输入,用户怎么写都能识别。
核心矛盾没变:只升级"日期关卡"
函数计算、天数验证这两块我都没动,唯一升级的是第一道日期关卡------把"死磕一种格式"换成了"谁来都能认"。
关键改动:用第三方 dateutil.parser 替我"识格式"
优化版之所以只认 YYYY-MM-DD,是因为 strptime 有个死规矩:我必须亲手报一张一模一样的格式模板。而用户的写法千奇百怪(2023/4/1、2023年4月1日、Apr 1 2023......),我不可能把所有模板都列全。
于是这版请出 58 题见过的第三方模块 dateutil 的 parser.parse():把整颗字符串丢进去,它自己认------月份缩写、分隔符、中文年月日都能识破,我不再需要指定格式。识别成功后我立刻用 strftime 统一折算回标准 YYYY-MM-DD 字符串,这样后面喂给原封不动的计算函数时依然对得上。失败时我多接了一个 ParserError 异常来兜底乱码输入。
三级进化的总结
回头看这三版,我做的其实是同一件事的三次加固:原版只能算写死的值 → 优化版能问用户要、但只认一种格式 → 再优化版连格式都不挑、怎么输都认。计算逻辑从头到尾没变过,一路优化的全是"程序对用户输入的包容度"。这也印证了我越来越深的一个体会:程序能不能跑是及格线,扛不扛得住用户的各种"乱输",才是它好不好用的真正分水岭。