← Notes

time.strftime uses the system time zone: write with the configured zone, read with the system one, green locally and a day off on UTC CI

2 min read#Python#Time#Testing

The project had a helper that takes today’s date in the configured time zone, and scattered time.strftime("%Y-%m-%d") calls next to it. On a dev machine whose zone matches the config, both always agree; CI runs in UTC, and between midnight and 8 a.m. Beijing time the reading side goes looking for yesterday’s file.

time.strftime, time.localtime, date.today() and a tz-less datetime.now() all use the process’s system time zone. If the project also has a helper that takes the date in a configured zone, the two always agree on a machine whose system zone equals the configured one, and only diverge on a different machine.

The side writing learning events chose which day’s file to use by the configured zone (default Asia/Shanghai):

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

The side building the daily digest fell back like this when no date was passed:

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

The dev machine was Windows on GMT+8, so both lines always gave the same day and tests passed. The GitHub Actions runner is UTC: between midnight and 8 a.m. Beijing time the writer is already on today while the digest is still on yesterday, reads yesterday’s event file, and finds nothing. On CI it showed up as an empty “concepts taught today” list, which looks like missing data rather than a date problem. CI failed five times in a row before it was fixed.

The fix changed both fallbacks to the configured-zone today_str(). Trying to reproduce locally, I found Windows has no time.tzset(), so setting TZ=UTC does nothing; the only option was to monkeypatch the date function.

Later a more direct option occurred to me: don’t change the system zone, change the configured one. Set the configured zone far from the dev machine and the two will disagree for most of the day, on any OS. Counting hour by hour from UTC+8, configuring UTC gives 8 mismatched hours a day, while Pacific/Honolulu (UTC-10) gives 18:

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

I haven’t added this fixture to the main project yet; it’s in my list of fixes. On HEAD the time.strftime date calls are gone, but date.today() still appears ten times, and ruff’s DTZ011 finds all of them at once.

Related

Generator Exceptions Surface on the First next(): try Around create_chat_completion(stream=True) Never Sees the Context Window Error2026-11-08Calling a generator function only builds a generator; the body, including any raise in it, runs on the first next(). llama-cpp-python checks the context window inside such a body, so the try around a streaming call, and the shrink-and-retry under it, never fire with the real library. The test double raised at call time, so the tests passed with 100% coverage.#Python#LLM#Error Handling#Testing#AutarkiaMagicMock accepts any arguments: the caller passes one too many, the test passes, production raises TypeError and except swallows it2026-10-17A MagicMock without a spec doesn’t check call signatures; when the code under test passes the wrong arguments, the double still returns whatever you configured. In Autarkia this meant a block of prompt context was never injected: the real method took only self, the caller passed a user_id, the test double accepted it, and in production the TypeError was swallowed by an outer except.#Python#TestingFiltering kwargs with inspect.signature: a function that takes **kwargs looks like it accepts nothing, and a retry lost its model_path2026-11-08inspect.signature(f).parameters lists only declared parameters; **kwargs shows up as a single entry named kwargs. Checking by name whether a callee accepts an argument lets nothing through to the most permissive function. Autarkia’s empty-reply retry lost its model_path this way and fell back to the default model; the resulting 400s landed among upstream 503s, and that day’s error triage filed them as gateway noise.#Python#LLM#Debugging