Python zoneinfo: Count Log Events by Local Date Across Timezones
Server logs use UTC while a business report closes at midnight in Shanghai. Taking the first ten timestamp characters can put events on the wrong day. Building on the site’s log filtering and statistics workflow, this tutorial isolates the time conversion: use Python’s standard library to convert each event to a reporting zone before counting local dates.
This is for beginner-to-intermediate readers. Input contains one ISO 8601 timestamp with an offset per line, rather than complete Nginx log entries. Export the timestamp field using your source log format first. The script rejects timestamps without timezone information so host settings cannot silently determine their meaning.
1. Method: identify the instant before taking its local date
- Parse an explicit offset such as
Zor+08:00. - Convert to the reporting zone with
astimezone(ZoneInfo(...)). - Call
.date()and count events for that date.
Python’s datetime documentation distinguishes aware objects from naive ones. Use astimezone() for conversion; replace(tzinfo=...) changes the timezone label without converting the clock time.
In our fixture, 2026-10-14T16:00:00Z is already midnight on October 15 in Shanghai. Adding eight hours is unsuitable as a general cross-region strategy. We use named zones and separately test a New York daylight saving rollback.
2. Setup: check Python and timezone data
Use Python 3.11 or newer: its fromisoformat() supports the trailing Z used here. No third-party Python packages are required. These examples were tested on Linux with Python 3.11.4.
- Work in a new writable directory and stay there for subsequent commands. Check that
Asia/Shanghailoads, then create five test events:
python3 --version
python3 -c "from zoneinfo import ZoneInfo; print(ZoneInfo('Asia/Shanghai'))"
mkdir zoneinfo-demo
cd zoneinfo-demo
cat > timestamps.txt <<'EOF'
2026-10-14T15:59:59Z
2026-10-14T16:00:00Z
2026-10-15T00:00:00+08:00
2026-10-15T23:59:59+08:00
2026-10-16T00:00:00+08:00
EOF
- Save the next section’s complete code as
log_days.py. Each line is an event. Lines two and three describe the same instant but count as two records; this script does not deduplicate events.
3. Implementation: emit JSON only after reading succeeds
"""Count offset-aware ISO 8601 timestamps by local date. Python 3.11+."""
import argparse
from collections import Counter
from datetime import datetime
import json
from pathlib import Path
import sys
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
def parse_instant(text):
value = datetime.fromisoformat(text.strip())
if value.tzinfo is None or value.utcoffset() is None:
raise ValueError("timestamp needs Z or an explicit UTC offset")
return value
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("input", type=Path, help="UTF-8 file; one timestamp per line")
parser.add_argument("--zone", default="Asia/Shanghai", help="IANA reporting zone")
args = parser.parse_args()
try:
zone = ZoneInfo(args.zone)
except (ZoneInfoNotFoundError, ValueError):
parser.error("unknown zone or missing timezone data: " + args.zone)
counts = Counter()
records = 0
try:
with args.input.open(encoding="utf-8-sig") as source:
for line_no, line in enumerate(source, 1):
try:
day = parse_instant(line).astimezone(zone).date().isoformat()
except (ValueError, OverflowError) as exc:
print(f"ERROR: line {line_no}: {exc}", file=sys.stderr)
return 1
counts[day] += 1
records += 1
except (OSError, UnicodeError) as exc:
print(f"ERROR: cannot read input: {exc}", file=sys.stderr)
return 1
if records == 0:
print("ERROR: input contains no timestamps", file=sys.stderr)
return 1
report = {
"zone": args.zone,
"records": records,
"days": dict(sorted(counts.items())),
}
print(json.dumps(report, indent=2))
return 0
if __name__ == "__main__":
raise SystemExit(main())
The script reads one line at a time and keeps counts by date, so memory grows with the number of distinct dates. Blank lines, an empty file, parse failures, and timestamps without offsets fail validation. A normal final newline does not create an extra blank line. Errors go to stderr; JSON reaches stdout only after the entire input passes.
4. Usage: generate the Shanghai daily report
python3 log_days.py --help
python3 log_days.py timestamps.txt --zone Asia/Shanghai
Expected output:
{
"zone": "Asia/Shanghai",
"records": 5,
"days": {
"2026-10-14": 1,
"2026-10-15": 3,
"2026-10-16": 1
}
}
--zone UTC produces different groups because it defines a different reporting calendar. Keep the argument explicit when scheduling the job. To fix the interpreter and working directory too, follow the Linux scheduled task environment tutorial.
5. Validation: midnight, host settings, and daylight saving
Check midnight boundaries and the process environment
Set two different TZ values for these processes without changing server timezone configuration. Successful cmp produces no output; the assertions then check the five events:
TZ=UTC python3 log_days.py timestamps.txt --zone Asia/Shanghai > utc-report.json
TZ=Pacific/Honolulu python3 log_days.py timestamps.txt --zone Asia/Shanghai > hawaii-report.json
cmp utc-report.json hawaii-report.json
python3 - <<'PY'
import json
from pathlib import Path
report = json.loads(Path("utc-report.json").read_text())
assert report["records"] == 5
assert report["days"] == {"2026-10-14": 1, "2026-10-15": 3, "2026-10-16": 1}
print("OK: date boundaries and explicit zone")
PY
Check the repeated daylight saving hour
Use two historical instants during New York’s 2024 rollback so this check does not depend on today’s date:
python3 - <<'PY'
from zoneinfo import ZoneInfo
from log_days import parse_instant
zone = ZoneInfo("America/New_York")
for text in ("2024-11-03T05:30:00Z", "2024-11-03T06:30:00Z"):
local = parse_instant(text).astimezone(zone)
print(local.isoformat(), "fold=" + str(local.fold))
PY
Expected output:
2024-11-03T01:30:00-04:00 fold=0
2024-11-03T01:30:00-05:00 fold=1
These instants are an hour apart despite both showing 01:30 locally. The zoneinfo documentation explains that conversion sets the appropriate fold. Offsets identify the occurrence; a bare 01:30 cannot acquire that meaning just from a reporting zone.
Check that a bad row produces no partial report
printf '%s\n' '2026-10-14T16:00:00Z' '2026-10-15T00:00:00' > bad.txt
python3 log_days.py bad.txt --zone Asia/Shanghai > bad-report.json
printf 'exit=%s\n' "$?"
test ! -s bad-report.json && printf 'OK: no partial report\n'
Expect ERROR: line 2: timestamp needs Z or an explicit UTC offset on stderr, followed by exit=1 and OK: no partial report. Redirection creates an empty file: check the exit status instead of treating file existence as success. Repeating these commands overwrites the demonstration reports, so run them only in the newly created test directory.
6. Troubleshooting: errors and direct fixes
| Symptom | Cause and action |
|---|---|
timestamp needs Z or an explicit UTC offset |
The source lacks an offset. Confirm its timezone contract and configure the exporter to include Z or the actual offset. Do not append Z to arbitrary historical timestamps. |
unknown zone or missing timezone data, exit 2 |
Check the --zone spelling and run the diagnostic below. Confirm that the container or host provides IANA data. |
cannot read input, exit 1 |
Check the path, read permission, and UTF-8 encoding. See Python CSV encoding troubleshooting for encoding conversion; avoid discarding bytes with ignored decoding errors. |
Invalid isoformat string |
The field contains a log prefix, timezone abbreviation, or blank line. Parse the source format and export complete ISO timestamps. This script does not guess arbitrary date formats. |
Inspect timezone search paths and the named zone:
python3 -c 'from zoneinfo import TZPATH, ZoneInfo; print(TZPATH); print(ZoneInfo("Asia/Shanghai"))'
Zoneinfo’s data source documentation describes system database and tzdata package lookup. For a minimal environment missing data, have its maintainer supply system data or declare tzdata in the project’s virtual environment. Neither changing the server’s default timezone nor installing Python packages globally is necessary.
7. Summary
Keep explicit offsets in event timestamps, choose a named reporting zone, and take the local date after conversion. Check midnight boundaries, daylight saving rollback, and bad-row exit behavior before a scheduled job produces its report. This script counts event dates; it does not schedule execution or guess the origin of naive timestamps.
- 原文作者:春江暮客
- 原文链接:https://www.bobobk.com/en/python-zoneinfo-log-dates.html
- 版权声明:本作品采用 知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议 进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。
相关文章
- Python Too Many Open Files: Find File Handle Leaks on Linux
- Fix CSV Encoding with Python: BOMs, GB18030, and UTF-16
- Codex Leaves Files in /tmp: A Step-by-Step Guide to Automatic Cleanup
- Stream gzip Logs with Python and Detect Truncation and CRC Errors
- Back Up SQLite with Python: WAL Snapshots and Restore Checks