Python datetime实战:Web开发中时间数据的接收、转换与SQL查询
1. 为什么Web开发必须掌握时间处理刚入行时我最头疼的就是处理各种时间格式。前端传时间戳数据库存UTC时间业务逻辑要用本地时间报表导出又要求特定格式的字符串。有次因为时区没处理好导致用户看到的订单创建时间比实际晚了8小时差点被投诉。今天就结合我踩过的坑手把手教你用Python的datetime模块搞定这些烦人的时间问题。时间处理的核心在于理解三个概念时间对象datetime、时间戳timestamp和时间字符串string。就像货币兑换需要中间汇率这三种形态的转换就是时间处理的基石。在Web开发中我们经常遇到这样的场景前端通过API传参可能用时间戳如1625097600000或ISO格式字符串2021-06-30T00:00:00Z数据库存储通常要求UTC时间的datetime对象业务逻辑中需要计算时间间隔如优惠券有效期返回给客户端的数据又得转成特定格式的字符串2. 前端时间参数的标准化处理2.1 识别前端传入的时间格式实际项目中前端传时间参数就像开盲盒。我见过最离谱的情况是同一个接口不同客户端传的时间格式居然不一样。所以首先要做好类型判断from datetime import datetime import time def parse_input_time(time_input): if isinstance(time_input, datetime): return time_input # 已经是datetime对象 elif isinstance(time_input, (int, float)): # 处理时间戳注意判断是秒级还是毫秒级 if time_input 1e12: # 毫秒级时间戳 return datetime.fromtimestamp(time_input/1000) return datetime.fromtimestamp(time_input) elif isinstance(time_input, str): # 尝试常见字符串格式 for fmt in (%Y-%m-%d %H:%M:%S, %Y-%m-%dT%H:%M:%SZ, %Y/%m/%d %H:%M:%S): try: return datetime.strptime(time_input, fmt) except ValueError: continue raise ValueError(f无法解析的时间格式: {time_input}) else: raise TypeError(不支持的时间参数类型)2.2 时区处理的最佳实践时区问题就像定时炸弹我强烈建议在系统内部统一使用UTC时间只在最终展示时转换。Django等框架已经内置了时区支持from django.utils import timezone # 自动处理时区转换 now timezone.now() # 带时区信息的datetime local_time timezone.localtime(now) # 转换为本地时间纯Python项目可以用pytz库import pytz utc_time datetime.now(pytz.UTC) # 当前UTC时间 shanghai_time utc_time.astimezone(pytz.timezone(Asia/Shanghai))3. 数据库操作中的时间陷阱3.1 原生SQL查询的时间参数处理直接拼接SQL语句是危险操作但有些复杂查询不得不写原生SQL时要注意时间参数的格式化# 错误示范SQL注入风险 query fSELECT * FROM orders WHERE create_time {some_time_str} # 正确做法1使用参数化查询 start_time datetime(2023, 1, 1).strftime(%Y-%m-%d %H:%M:%S) end_time datetime(2023, 1, 31).strftime(%Y-%m-%d %H:%M:%S) cursor.execute( SELECT * FROM orders WHERE create_time BETWEEN %s AND %s, (start_time, end_time) ) # 正确做法2ORM的raw SQL以Django为例 from django.db import connection with connection.cursor() as cursor: cursor.execute( SELECT * FROM orders WHERE create_time BETWEEN %s AND %s , [start_time, end_time]) rows cursor.fetchall()3.2 时间范围查询的优化技巧在大数据量下时间范围查询很容易成为性能瓶颈。我有次优化了一个慢查询从10秒降到0.1秒关键点在于为时间字段创建索引避免在时间字段上使用函数如DATE()对于固定周期查询如按月统计可以使用预计算字段# 反例无法使用索引 Entry.objects.filter(created_at__datedate(2023, 1, 1)) # 正例范围查询能用上索引 start datetime(2023, 1, 1) end datetime(2023, 1, 2) Entry.objects.filter(created_at__gtestart, created_at__ltend)4. 业务逻辑中的时间计算4.1 常见时间运算场景计算30天前的日期from datetime import timedelta thirty_days_ago datetime.now() - timedelta(days30)获取本月第一天和最后一天import calendar today datetime.now() first_day today.replace(day1) _, last_day_num calendar.monthrange(today.year, today.month) last_day today.replace(daylast_day_num)4.2 处理工作日计算金融类项目经常需要排除节假日计算工作日我的经验是维护一个节假日表然后def add_business_days(start_date, days, holidays): current start_date added_days 0 while added_days days: current timedelta(days1) if current.weekday() 5: # 周末 continue if current in holidays: # 节假日 continue added_days 1 return current5. 时间数据的序列化与API响应5.1 返回给前端的时间格式化RESTful API通常返回ISO8601格式的时间字符串from datetime import datetime now datetime.now() iso_format now.isoformat() # 2023-07-20T14:30:45.123456 # 如果需要带时区 iso_with_tz now.astimezone().isoformat() # 2023-07-20T22:30:45.12345608:005.2 DRF中的时间序列化Django REST Framework中处理时间字段的推荐方式from rest_framework import serializers class OrderSerializer(serializers.ModelSerializer): created_at serializers.DateTimeField(format%Y-%m-%d %H:%M:%S) class Meta: model Order fields __all__或者在settings.py中全局设置REST_FRAMEWORK { DATETIME_FORMAT: %Y-%m-%d %H:%M:%S }6. 实战一个完整的时间处理流程假设我们要实现一个订单查询接口支持按时间范围筛选# views.py from django.db.models import Q from django.utils import timezone from rest_framework import generics class OrderListView(generics.ListAPIView): serializer_class OrderSerializer def get_queryset(self): queryset Order.objects.all() # 处理时间范围参数 start_param self.request.query_params.get(start) end_param self.request.query_params.get(end) try: if start_param: start parse_input_time(start_param) queryset queryset.filter(created_at__gtestart) if end_param: end parse_input_time(end_param) queryset queryset.filter(created_at__lteend) except (ValueError, TypeError) as e: raise serializers.ValidationError(str(e)) return queryset配套的前端时间处理建议使用moment.js或day.js库处理时间格式化所有时间参数统一传UTC时间戳在HTTP头中传递用户时区信息7. 那些年我踩过的时间坑夏令时问题2019年某次上线后发现欧洲用户的时间显示错乱原来是没考虑夏令时切换。解决方案是始终用UTC存储前端按需转换。闰秒事件某次金融结算系统在闰秒时刻出现异常后来改用time.time()代替datetime.now()获取时间戳。时间精度丢失JavaScript的Date精度只到毫秒而Python是微秒前后端传参时要注意# 前端传的毫秒时间戳 js_timestamp 1689840000123 # 后端处理保持毫秒精度 dt datetime.fromtimestamp(js_timestamp / 1000) formatted dt.strftime(%Y-%m-%d %H:%M:%S.%f)[:-3] # 截断到毫秒