wttr/scripts/generate_timezone_table.py

118 lines
3.9 KiB
Python
Executable file

#!/usr/bin/env -S uv run --script
# /// script
# dependencies = ["timezonefinder", "pytz"]
# ///
"""
Generate timezone offset lookup table based on longitude.
Samples at latitudes 0°, 30°N, 30°S and uses most common offset.
"""
from timezonefinder import TimezoneFinder
import pytz
from datetime import datetime
from collections import Counter
OUTPUT_FILE = "src/location/timezone_offsets.zig"
LATITUDES = [0, 30, -30]
def get_offset_minutes(tz_name):
"""Get UTC offset in minutes for a timezone at current time"""
if not tz_name:
return 0
tz = pytz.timezone(tz_name)
now = datetime.now(tz)
offset = now.utcoffset()
return int(offset.total_seconds() / 60)
def main():
tf = TimezoneFinder()
print(f"Generating timezone offset table...")
print(f"Sampling at latitudes: {LATITUDES}")
print()
offsets = []
for lon in range(-180, 180):
# Collect offsets from all latitudes
offset_list = []
for lat in LATITUDES:
tz_name = tf.timezone_at(lat=lat, lng=lon)
offset = get_offset_minutes(tz_name)
offset_list.append(offset)
# Find most common offset (handles political boundaries)
most_common = Counter(offset_list).most_common(1)[0][0]
offsets.append(most_common)
if (lon + 180) % 30 == 0:
print(f"Progress: {lon + 180 + 1}/360 longitudes processed")
# Write output file
with open(OUTPUT_FILE, 'w') as f:
f.write("""// Auto-generated timezone offset table
// Generated by scripts/generate_timezone_table.py
//
// Maps longitude (0-359) to UTC offset in minutes
// Sampled at latitudes: 0°, 30°N, 30°S and uses most common offset
//
// ACCURACY NOTES:
// - This is a simplified longitude-only lookup for weather display purposes
// - Samples 3 latitudes per longitude and uses the most common offset
// - Works well for mid-latitudes (±30°) where most population lives
// - Can be off by 1-2.5 hours at extreme latitudes (e.g., Russia vs Australia)
// - Acceptable for "morning/afternoon/evening/night" weather labels
// - If higher accuracy is needed, modify getTimezoneOffset() to use latitude
const std = @import("std");
const Coordinates = @import("../Coordinates.zig");
/// Timezone offset in minutes from UTC for each degree of longitude (0-359)
/// Negative values = west of UTC, positive = east of UTC
pub const timezone_offsets: [360]i16 = .{
""")
for offset in offsets:
f.write(f" {offset},\n")
f.write("""};
/// Get timezone offset in minutes for given coordinates
/// Currently only uses longitude; latitude is available for future improvements
/// coords: Location coordinates
/// Returns: offset in minutes from UTC
pub fn getTimezoneOffset(coords: Coordinates) i16 {
// Currently only uses longitude (see ACCURACY NOTES above)
// Latitude could be used in future for better accuracy at extreme latitudes
_ = coords.latitude;
// Normalize longitude to 0-359
const normalized = @mod(@as(i32, @intFromFloat(@round(coords.longitude))) + 180, 360);
return timezone_offsets[@intCast(normalized)];
}
test "timezone offset lookup" {
// London (0°) should be close to UTC
const london_offset = getTimezoneOffset(.{ .latitude = 51.5, .longitude = 0.0 });
try std.testing.expect(london_offset >= -60 and london_offset <= 60);
// New York (-74°) should be around UTC-5 (-300 minutes)
const ny_offset = getTimezoneOffset(.{ .latitude = 40.7, .longitude = -74.0 });
try std.testing.expect(ny_offset >= -360 and ny_offset <= -240);
// Tokyo (139°) should be around UTC+9 (540 minutes)
const tokyo_offset = getTimezoneOffset(.{ .latitude = 35.7, .longitude = 139.0 });
try std.testing.expect(tokyo_offset >= 480 and tokyo_offset <= 600);
}
""")
print(f"\nGenerated {OUTPUT_FILE}")
print("Running zig fmt...")
import subprocess
subprocess.run(["zig", "fmt", OUTPUT_FILE])
print("Done!")
if __name__ == "__main__":
main()