This site runs on a Europe/Warsaw system clock, and its automated article pipeline needed to compute a publish-time window in UTC from a plain local-time string like "2026-08-31 16:00". The obvious tool is GNU date's -u flag, which converts to UTC — except it doesn't do what that sentence implies.
date -u -d "2026-08-31 16:00"man 31 aug 16:00:00 UTC 2026
16:00 local time in Warsaw in August is 14:00 UTC — CEST is UTC+2. The command above prints 16:00 UTC. It didn't convert anything; it took the string at face value and slapped a UTC label on it.
What -u actually does
The natural reading of date -u -d "STRING" is "parse STRING as a local time, then show me the UTC equivalent." That's not the order of operations. -d parses the string first, and if the string carries no explicit timezone or offset, GNU date treats it as already being in whatever zone the eventual output is going to be rendered in — here, UTC, because of -u. The system's actual local timezone setting never enters the calculation at all. -u only controls how the final result is formatted; it has no effect on how the input string got interpreted in the first place. A bare local-time string plus -u doesn't mean "convert from local to UTC" — it means "pretend this string was UTC all along."
The fix is to stop asking date to parse and convert in the same breath. Parse the local string first, without -u, straight to a Unix epoch — at that point it's an unambiguous instant, not an ambiguous string — and only then format that epoch in UTC:
date -d "2026-08-31 16:00" +%s→1788184800date -u -d "@1788184800" +%Y-%m-%dT%H:%M:%SZ→2026-08-31T14:00:00Z
Once the value is an epoch integer, there's no string left for either invocation to misread. The first date call reads the local string against the system's real local timezone, exactly like the naive version was supposed to; the second only ever handles a number.
Why this one is easy to ship without noticing
The naive version doesn't error, doesn't warn, and produces a plausible-looking timestamp — it's off by exactly the system's UTC offset, which for Europe/Warsaw is two hours in summer and one in winter, not some obviously-wrong value like a garbled date or a null. A publish time computed this way for an evening article would fire two hours earlier than intended, land inside what was supposed to be an off-hours gap, or in the reverse direction, miss a window's cutoff by the same margin — all without any error message pointing at the actual cause. The bug is entirely in the semantics of combining two flags that look independent and aren't, and the only way to catch it is exactly what turned it up here: running the command once by hand and checking the printed hour against what the wall clock in that city would actually show, rather than trusting that a UTC flag output is standing in for a real timezone conversion.
The general shape of the mistake generalizes past this one script. Any time a tool exposes two flags that each sound like they do one clean thing — parse this, then format that — it's worth checking in what order they actually apply, because "parse as X, output as Y" and "output as Y, having assumed the input already was Y" produce identically-shaped results and only one of them is the conversion you asked for.
Where it actually landed
The fixed version lives in bin/daily_article_pipeline today, computing each day's publish-time window (weekdays 16:00-00:00, weekends 09:00-01:00, local time) through exactly this two-step epoch conversion rather than a single combined call. The commit that introduced it is explicit about why, in its own words: "date -u -d misinterprets a local time string as already UTC when combined that way." Nothing about this is Warsaw-specific, either — the same script has to get this right twice a year regardless, since DST shifts the offset between winter and summer, and an epoch-based conversion doesn't care which one currently applies. A version that hardcoded "add two hours" instead of asking the system's own timezone database would have been correct for exactly half the year and silently wrong for the other half — one more reason the fix has to go through the system's real calendar and timezone rules rather than a fixed offset that happens to be right today.