← 笔记

time.strftime 取的是系统时区:写入用配置时区、读取用系统时区,本地全绿,CI 在 UTC 上差一天

约 2 分钟#Python#时间#测试

项目里有一个按配置时区取日期的工具函数,同时还有零散的 time.strftime("%Y-%m-%d")。开发机的时区和配置一样,两种写法永远同一天;CI 跑在 UTC,北京时间零点到八点之间,读的一方会去找前一天的文件。

time.strftime、time.localtime、date.today()、不带 tz 的 datetime.now(),用的都是进程的系统时区。项目里如果另有一个按配置时区取日期的函数,两种写法在「系统时区等于配置时区」的机器上结果永远一样,换一台机器才会分开。

写学习记录的一方按配置时区(默认 Asia/Shanghai)决定落到哪天的文件:

python
day = str(timestamp or "")[:10] or now_str("%Y-%m-%d")      # 配置时区

生成每日摘要的一方,日期参数为空时这样兜底:

python
date_key = str(date or "").strip() or time.strftime("%Y-%m-%d")   # 系统时区

开发机是 Windows、GMT+8,两行永远得到同一天,测试全绿。GitHub Actions 的 runner 是 UTC,北京时间 0 点到 8 点之间,写入方已经是今天,摘要方还是昨天,去读昨天的事件文件,读到的是空的。CI 上的表现是摘要里「当天讲过的知识点」为空,看起来像数据没写进去,不像日期问题。修好之前 CI 连挂了 5 次。

修法是两处兜底都改成配置时区的 today_str()。当时想在本地复现,发现 Windows 上没有 time.tzset(),设 TZ=UTC 不起作用,只能 monkeypatch 固定日期函数。

后来想到一个更直接的办法:不改系统时区,改配置时区。把配置时区设成离开发机很远的地方,两种口径就会在一天的大部分时间里分开,任何操作系统上都能复现。我在 UTC+8 下逐小时算过,配成 UTC 每天有 8 小时不在同一天,配成 Pacific/Honolulu(UTC-10)有 18 小时:

python
@pytest.fixture
def far_away_tz(monkeypatch):
    monkeypatch.setattr(settings.system, "timezone", "Pacific/Honolulu")

在主项目里我还没把这个 fixture 加进去,只记进了修复建议。HEAD 上 time.strftime 取日期的写法已经清掉了,但 date.today() 还有 10 处,ruff 的 DTZ011 能把它们一次找出来。

相关阅读

生成器的异常在第一次 next() 时才抛:try 包住 create_chat_completion(stream=True) 接不到超窗口错误2026-11-08调用生成器函数只会造出生成器,函数体要等第一次 next() 才运行,里面的 raise 也一样。llama-cpp-python 的超窗口检查就在这样的函数体里,所以流式调用外面那层 try 和它的自动收缩重试在真实库下走不到;测试替身在调用时就抛错,测试全绿、覆盖率 100%。#Python#LLM#错误处理#测试#AutarkiaMagicMock 接受任意实参:调用方多传一个参数,测试照样通过,生产抛 TypeError 被 except 吞掉2026-10-17不带 spec 的 MagicMock 不检查调用签名,被测代码传错参数时,替身照样返回你设好的值。Autarkia 里一段 prompt 上下文因此从来没被注入过:真实方法只收 self,调用方多传了 user_id,测试里的替身收下了,生产里的 TypeError 被外层 except 吞掉。#Python#测试用 inspect.signature 过滤关键字参数:接受 **kwargs 的函数被当成什么都不收,重试丢了 model_path2026-11-08inspect.signature(f).parameters 只列声明过的形参,**kwargs 在里面是一个叫 kwargs 的条目。按名字检查「对方收不收这个参数」,最宽松的函数反而什么都过不去。Autarkia 的空回复重试因此丢了 model_path,换成默认模型去请求,返回的 400 混在上游过载的 503 里,同一天的错误分诊把它判成了网关误报。#Python#LLM#调试