···11+# Requires Python 3.9+ for zoneinfo
22+import zoneinfo
33+from datetime import datetime, timedelta, timezone
44+# No unused imports found (time is used for .timestamp())
55+66+# Helper to get offset and DST component safely
77+def get_tz_details(tz_name: str, dt_utc: datetime) -> tuple[int, timedelta] | None:
88+ """Gets total offset in seconds and DST component as timedelta."""
99+ try:
1010+ tz = zoneinfo.ZoneInfo(tz_name)
1111+ offset_td = tz.utcoffset(dt_utc)
1212+ dst_td = tz.dst(dt_utc)
1313+1414+ # Ensure DST is not None, default to zero if it is (e.g., for UTC)
1515+ if dst_td is None:
1616+ dst_td = timedelta(0)
1717+1818+ if offset_td is not None:
1919+ return int(offset_td.total_seconds()), dst_td
2020+ # If offset_td is None, implicitly returns None below
2121+ except Exception:
2222+ # print(f"Warning: Could not get details for {tz_name} at {dt_utc}: {e}")
2323+ pass # Silently ignore errors for individual lookups
2424+ return None
2525+2626+# Function to find DST transitions within a year
2727+def find_dst_transitions_accurate(tz_name: str, year: int) -> tuple[int, int, int, int]:
2828+ """ Finds precise DST transition UTC timestamps for a given year.
2929+ Returns (std_offset_sec, dst_offset_sec, last_start_utc_ts, last_end_utc_ts)
3030+ Timestamps are UTC seconds (epoch). 0 if no transition/no DST found in the year.
3131+ """
3232+ start_ts = 0
3333+ end_ts = 0
3434+ std_offset_sec = None
3535+ dst_offset_sec = None # Offset *during* DST
3636+ initial_offset_sec = None # Store the very first valid offset
3737+3838+ try:
3939+ # Start iterating from one hour before the target year begins
4040+ # Ensures transitions exactly at year start are caught
4141+ current_dt = datetime(year , 1, 1, 0, 0, 0, tzinfo=timezone.utc) - timedelta(hours=1)
4242+ initial_details = get_tz_details(tz_name, current_dt)
4343+4444+ if not initial_details:
4545+ # Fallback if start fails: try noon on Jan 1st
4646+ current_dt = datetime(year , 1, 1, 12, 0, 0, tzinfo=timezone.utc)
4747+ initial_details = get_tz_details(tz_name, current_dt)
4848+ if not initial_details:
4949+ print(f"Warning: Cannot get initial offset for {tz_name} in {year}")
5050+ return 0, 0, 0, 0
5151+5252+ prev_offset_sec, prev_dst_td = initial_details
5353+ initial_offset_sec = prev_offset_sec # Store the first offset we found
5454+5555+ # Iterate hour by hour through the target year plus a few hours into the next
5656+ total_hours_to_check = (366 * 24) + 3 # Cover leap year + buffer
5757+5858+ for _ in range(total_hours_to_check):
5959+ current_dt += timedelta(hours=1)
6060+ details = get_tz_details(tz_name, current_dt)
6161+6262+ if not details: continue # Skip if data unavailable for this hour
6363+6464+ current_offset_sec, current_dst_td = details
6565+6666+ # --- Determine Standard vs DST offset ---
6767+ # Continuously update std/dst based on whether DST is active
6868+ if current_dst_td == timedelta(0):
6969+ std_offset_sec = current_offset_sec
7070+ if current_dst_td > timedelta(0):
7171+ dst_offset_sec = current_offset_sec
7272+7373+ # --- Detect transition based on change in DST component ---
7474+ if prev_dst_td != current_dst_td:
7575+ transition_ts = int(current_dt.timestamp())
7676+7777+ # Record transition timestamp if it happens *within* the target year
7878+ if current_dt.year == year:
7979+ if prev_dst_td == timedelta(0) and current_dst_td > timedelta(0):
8080+ # Entered DST (Std -> Dst)
8181+ start_ts = transition_ts
8282+ # Ensure offsets are recorded based on this transition
8383+ if std_offset_sec is None: std_offset_sec = prev_offset_sec
8484+ if dst_offset_sec is None: dst_offset_sec = current_offset_sec
8585+8686+ elif prev_dst_td > timedelta(0) and current_dst_td == timedelta(0):
8787+ # Exited DST (Dst -> Std)
8888+ end_ts = transition_ts
8989+ # Ensure offsets are recorded based on this transition
9090+ if std_offset_sec is None: std_offset_sec = current_offset_sec
9191+ if dst_offset_sec is None: dst_offset_sec = prev_offset_sec
9292+9393+ prev_offset_sec, prev_dst_td = current_offset_sec, current_dst_td
9494+9595+ # --- Post-processing ---
9696+ # Use the very first offset seen if std/dst couldn't be determined otherwise
9797+ if std_offset_sec is None: std_offset_sec = initial_offset_sec
9898+ if dst_offset_sec is None: dst_offset_sec = std_offset_sec # Default DST offset to STD if not seen
9999+100100+ # If offsets are effectively the same, clear transition timestamps
101101+ OFFSET_DIFF_THRESHOLD_SECONDS = 60 # Use a named constant
102102+ if abs(std_offset_sec - dst_offset_sec) < OFFSET_DIFF_THRESHOLD_SECONDS:
103103+ start_ts = 0
104104+ end_ts = 0
105105+ dst_offset_sec = std_offset_sec # Ensure they are identical if no DST
106106+107107+ except zoneinfo.ZoneInfoNotFoundError:
108108+ print(f"Warning: Timezone '{tz_name}' not found during transition check.")
109109+ return 0, 0, 0, 0
110110+ except Exception as e:
111111+ print(f"Error finding transitions for {tz_name}: {e}")
112112+ return 0, 0, 0, 0
113113+114114+ # Ensure we return non-None values
115115+ std_offset_sec = std_offset_sec if std_offset_sec is not None else 0
116116+ dst_offset_sec = dst_offset_sec if dst_offset_sec is not None else 0
117117+118118+ return std_offset_sec, dst_offset_sec, start_ts, end_ts
119119+120120+def generate_tz_list_c_code():
121121+ """Generates C code for a static timezone list with DST transition timestamps."""
122122+123123+ target_year = datetime.now().year # Use current year for transitions
124124+ print(f"Finding DST transitions for year {target_year}...")
125125+126126+ available_zones = zoneinfo.available_timezones()
127127+ print(f"Found {len(available_zones)} available timezones.")
128128+129129+ processed_zones = {} # Key: std_offset_hours, Value: Dict of zone data
130130+131131+ for tz_name in available_zones:
132132+ # Basic filtering (no dead code here)
133133+ if tz_name.startswith("Etc/") or "/" not in tz_name: continue
134134+ if tz_name in ["Factory", "factory"] or tz_name.lower().startswith("right/") or tz_name.lower().startswith("posix/"): continue
135135+136136+ std_offset_s, dst_offset_s, start_utc, end_utc = find_dst_transitions_accurate(tz_name, target_year)
137137+ city_name = tz_name.split('/')[-1].replace('_', ' ')
138138+139139+ # Convert offsets back to hours
140140+ std_offset_h = std_offset_s / 3600.0
141141+ dst_offset_h = dst_offset_s / 3600.0
142142+143143+ # Group by standard offset
144144+ key_offset = std_offset_h
145145+ if city_name and city_name[0].isupper():
146146+ if key_offset not in processed_zones:
147147+ processed_zones[key_offset] = {
148148+ "std_offset": std_offset_h,
149149+ "dst_offset": dst_offset_h,
150150+ "start_utc": start_utc,
151151+ "end_utc": end_utc,
152152+ "names": []
153153+ }
154154+ # Add city name if not already present
155155+ if city_name not in processed_zones[key_offset]["names"]:
156156+ processed_zones[key_offset]["names"].append(city_name)
157157+158158+ # Convert dict values to a list and sort by standard offset
159159+ tz_data_list = sorted(processed_zones.values(), key=lambda x: x["std_offset"])
160160+ print(f"Generated data for {len(tz_data_list)} unique standard offsets.")
161161+162162+ # --- C Code Generation ---
163163+ # Build C code string (no obvious dead code here)
164164+ c_code = "// Generated by Python script using zoneinfo\n"
165165+ c_code += f"// Includes Standard & DST offsets and UTC transition timestamps for {target_year}.\n"
166166+ c_code += "// WARNING: DST rules accurate only for the generated year.\n\n"
167167+ # Include stdint.h for int64_t type used below
168168+ c_code += "#include <stdint.h>\n\n"
169169+ c_code += "// Holds a single city name string\n"
170170+ c_code += "typedef struct {\n"
171171+ c_code += " const char* name;\n"
172172+ c_code += "} TzCityName;\n\n"
173173+ c_code += "// Holds offset info and points to an array of names\n"
174174+ c_code += "typedef struct {\n"
175175+ c_code += " float std_offset_hours; // Offset during standard time\n"
176176+ c_code += " float dst_offset_hours; // Offset during daylight time (if applicable)\n"
177177+ # Using int64_t for Pebble time_t safety, check SDK if 32-bit preferred
178178+ c_code += " int64_t dst_start_utc; // UTC timestamp (time_t) for DST start (0 if N/A)\n"
179179+ c_code += " int64_t dst_end_utc; // UTC timestamp (time_t) for DST end (0 if N/A)\n"
180180+ c_code += " const TzCityName* names; // Pointer to the array of names\n"
181181+ c_code += " int name_count; // How many names are in the array\n"
182182+ c_code += "} TzInfo;\n\n"
183183+184184+ # Generate the static arrays of names first
185185+ name_array_definitions = ""
186186+ unique_names_id = 0
187187+ for zone_data in tz_data_list:
188188+ c_array_name = f"tz_names_{unique_names_id}"
189189+ zone_data["c_array_name"] = c_array_name # Store for later use
190190+ unique_names_id += 1
191191+ name_array_definitions += f"static const TzCityName {c_array_name}[] = {{\n"
192192+ # Sort names alphabetically for consistency
193193+ for name in sorted(zone_data["names"]):
194194+ name_array_definitions += f" {{ \"{name}\" }},\n"
195195+ name_array_definitions += "};\n\n"
196196+ c_code += name_array_definitions
197197+198198+ # Generate the main tz_list array
199199+ c_code += "// Main list mapping offsets/DST info to their respective name arrays\n"
200200+ c_code += "static const TzInfo tz_list[] = {\n"
201201+ for zone_data in tz_data_list:
202202+ c_array_name = zone_data["c_array_name"]
203203+ name_count = len(zone_data["names"])
204204+ # Use 'LL' suffix for int64_t timestamp constants in C
205205+ c_code += (f" {{ {zone_data['std_offset']:.2f}f, {zone_data['dst_offset']:.2f}f, "
206206+ f"{zone_data['start_utc']}LL, {zone_data['end_utc']}LL, "
207207+ f"{c_array_name}, {name_count} }},\n")
208208+209209+ c_code += "};\n\n"
210210+ c_code += f"#define TZ_LIST_COUNT {len(tz_data_list)}\n"
211211+212212+ return c_code
213213+214214+# --- Main execution ---
215215+if __name__ == "__main__":
216216+ c_code_output = generate_tz_list_c_code()
217217+ output_filename = "src/c/tz_list.c" # Output path
218218+ try:
219219+ with open(output_filename, "w") as f:
220220+ f.write(c_code_output)
221221+ print(f"\nSuccessfully written timezone data (with accurate DST timestamps) to {output_filename}")
222222+ except IOError as e:
223223+ print(f"\nError: Could not write to file {output_filename}: {e}")