合并数据框时间与时区列时出现时区符号反转问题
问题描述
我有一份包含行程数据的CSV文件,每行记录起点、终点、出发时间、到达时间,其中出发/到达时间为当地时间,对应有时区列。示例数据如下:
origin,destination,depart_time,depart_time_zone,arrival_time,arrival_time_zone A,B,04/04/2023 08:01,GMT-04:00,04/04/2023 09:02,GMT-04:00 C,D,04/04/2023 09:20,GMT-04:00,04/04/2023 16:48,GMT-05:00
尝试用以下代码合并时间与时区列:
data = pd.read_csv('trips.csv', parse_dates={'depart' : ['depart_time','depart_time_zone'],'arrive' : ['arrival_time','arrival_time_zone']})
但发现解析结果异常:CSV第一行中depart_time=04/04/2023 08:00、depart_time_zone=GMT-04:00,输出的depart字段却显示为2023-04-04 08:00:00+04:00,时区符号从-04变成了+04,这是解析错误吗?
问题原因
这确实是解析错误。当pandas用parse_dates合并depart_time和depart_time_zone时,会生成类似"04/04/2023 08:01 GMT-04:00"的字符串,而pandas依赖的日期解析器(如dateutil.parser)对GMT-xx:xx格式的解读存在逻辑偏差:它错误地将GMT-04:00理解为比UTC早4小时(即UTC+4),但实际上GMT-04:00表示的是比UTC晚4小时(UTC-4),最终导致时区符号反转。
解决方案
不要直接依赖parse_dates合并列,而是手动处理时间与时区的解析,以下是两种可行方案:
方案1:手动拼接为ISO兼容格式解析
通过去掉GMT前缀,将时区格式转换为解析器能正确识别的偏移格式,再统一解析:
import pandas as pd from dateutil import parser # 读取CSV,不自动解析日期 df = pd.read_csv('trips.csv') # 处理出发时间:拼接成"时间 时区偏移"格式(移除GMT前缀) df['depart'] = df.apply( lambda row: parser.parse(f"{row['depart_time']} {row['depart_time_zone'].replace('GMT', '')}"), axis=1 ) # 处理到达时间同理 df['arrive'] = df.apply( lambda row: parser.parse(f"{row['arrival_time']} {row['arrival_time_zone'].replace('GMT', '')}"), axis=1 )
方案2:使用时区对象本地化时间
利用zoneinfo(Python 3.9+)将GMT偏移转换为标准时区对象,再为朴素时间添加时区信息:
import pandas as pd from zoneinfo import ZoneInfo # 读取CSV并单独解析时间列 df = pd.read_csv('trips.csv', parse_dates=['depart_time', 'arrival_time']) # 转换GMT偏移为zoneinfo兼容的时区对象 # 注意:Etc时区命名规则与GMT相反,GMT-4对应Etc/GMT+4,GMT+5对应Etc/GMT-5 def gmt_to_zone(gmt_str): offset = gmt_str.lstrip('GMT') if offset.startswith('-'): return ZoneInfo(f"Etc/GMT{offset[1:]}") elif offset.startswith('+'): return ZoneInfo(f"Etc/GMT-{offset[1:]}") return ZoneInfo("Etc/GMT") # 为时间列添加时区信息 df['depart'] = df.apply( lambda row: row['depart_time'].tz_localize(gmt_to_zone(row['depart_time_zone'])), axis=1 ) df['arrive'] = df.apply( lambda row: row['arrival_time'].tz_localize(gmt_to_zone(row['arrival_time_zone'])), axis=1 )
执行上述方案后,第一行的depart字段会正确显示为2023-04-04 08:01:00-04:00,时区符号与原始数据一致。
内容的提问来源于stack exchange,提问作者Joris Kinable

