Python Polars中带时区Datetime过滤的偏移问题及解决方法
Polars时区感知日期时间过滤时的偏移问题
问题背景
我有一个DataFrame,其“datetime”列包含带时区感知的日期时间,原始时区为UTC。将时区转换为欧洲/柏林时区(CEST)的操作正常,但过滤datetime时出现了2小时偏移:原始数据集中的第一行本应被保留却未出现在结果中,第三行本应被过滤却被保留。即使使用无时区感知的datetime对象(如datetime(2023,8,1)),结果仍相同。
原始代码如下:
from zoneinfo import ZoneInfo from datetime import datetime ( pl.DataFrame( { "datetime": [ "[01/Aug/2023:00:00:02 +0200]", "[01/Aug/2023:02:00:02 +0200]", "[03/Aug/2023:01:00:02 +0200]", ] } ) .with_columns(pl.col("datetime").str.to_datetime("[%d/%b/%Y:%H:%M:%S %z]")) .with_columns(pl.col("datetime").dt.convert_time_zone("Europe/Berlin")) .filter( pl.col("datetime") .cast(pl.Date) .is_between( datetime(2023, 8, 1, tzinfo=ZoneInfo("Europe/Berlin")), datetime(2023, 8, 3, tzinfo=ZoneInfo("Europe/Berlin")), ) ) )
过滤结果:
datetime datetime[μs, Europe/Berlin] 2023-08-01 02:00:02 CEST 2023-08-03 01:00:02 CEST
问题原因
问题出在cast(pl.Date)这一步:当把带时区的datetime转换为Date类型时,Polars会先将datetime转换为UTC时间,再提取日期。
- 第一行的
2023-08-01 00:00:02 CEST对应的UTC时间是2023-07-31 22:00:02 UTC,转换为Date后是2023-07-31,不在过滤范围2023-08-01至2023-08-03内,因此被过滤。 - 第三行的
2023-08-03 01:00:02 CEST对应的UTC时间是2023-08-02 23:00:02 UTC,转换为Date后是2023-08-02,符合过滤范围,因此被保留。
解决方案
有两种方式可以让Polars在过滤时正确考虑时区:
方案1:直接比较带时区的datetime范围(推荐)
无需转换为Date,直接用目标时区的datetime范围进行过滤,注意结束时间的边界(如果要包含8月3日全天,需要设置为8月4日0点,或者明确指定当天的最后一刻):
from zoneinfo import ZoneInfo from datetime import datetime ( pl.DataFrame( { "datetime": [ "[01/Aug/2023:00:00:02 +0200]", "[01/Aug/2023:02:00:02 +0200]", "[03/Aug/2023:01:00:02 +0200]", ] } ) .with_columns(pl.col("datetime").str.to_datetime("[%d/%b/%Y:%H:%M:%S %z]")) .with_columns(pl.col("datetime").dt.convert_time_zone("Europe/Berlin")) .filter( pl.col("datetime").is_between( datetime(2023, 8, 1, tzinfo=ZoneInfo("Europe/Berlin")), datetime(2023, 8, 4, tzinfo=ZoneInfo("Europe/Berlin")) # 包含8月3日全天的结束边界 ) ) )
方案2:使用dt.date()提取本地时区的日期
dt.date()方法会基于datetime所在的时区(而非UTC)提取日期,替代cast(pl.Date)即可:
from zoneinfo import ZoneInfo from datetime import datetime ( pl.DataFrame( { "datetime": [ "[01/Aug/2023:00:00:02 +0200]", "[01/Aug/2023:02:00:02 +0200]", "[03/Aug/2023:01:00:02 +0200]", ] } ) .with_columns(pl.col("datetime").str.to_datetime("[%d/%b/%Y:%H:%M:%S %z]")) .with_columns(pl.col("datetime").dt.convert_time_zone("Europe/Berlin")) .filter( pl.col("datetime").dt.date().is_between( datetime(2023, 8, 1).date(), datetime(2023, 8, 3).date(), ) ) )
两种方案都能得到正确的过滤结果:
datetime datetime[μs, Europe/Berlin] 2023-08-01 00:00:02 CEST 2023-08-01 02:00:02 CEST
内容的提问来源于stack exchange,提问作者Laxas
相关产品推荐
相关产品推荐

