···11+# TidFace Clock Logic: Closest to Noon (with DST and Randomization)
22+33+## Core Goal
44+55+Display a continuously updating time in the format `[CityName]:[MM]:[SS]`. The chosen `CityName` is determined by finding the timezone whose *current local time* is closest to 12:00:00 noon at specific intervals.
66+77+## Key Principles
88+99+1. **Base Time:** The system operates based on the current Coordinated Universal Time (UTC), also referred to as GMT.
1010+2. **Re-evaluation Intervals:** The process of choosing the "best" timezone/city occurs precisely when the UTC time hits **:00, :15, or :30** minutes past the hour.
1111+ * *Reasoning:* We evaluate at these specific intervals to catch timezones hitting exactly 12:00:00 local time.
1212+ * `:00` UTC is needed for integer offsets (e.g., UTC+5 -> 12:00 at 07:00 UTC).
1313+ * `:15` UTC is needed for `:45` offsets (e.g., UTC+5:45 -> 12:00 at 06:15 UTC; UTC+12:45 -> 12:00 at 23:15 UTC).
1414+ * `:30` UTC is needed for `:30` offsets (e.g., UTC+5:30 -> 12:00 at 06:30 UTC).
1515+ * The `:45` UTC evaluation is omitted as no standard timezones have `:15` offsets that would hit noon precisely at XX:45 UTC.
1616+3. **DST Awareness:** Daylight Saving Time rules, as defined for the year 2025 in `tz_list.c`, *must* be considered when calculating local times.
1717+4. **Randomization:** If multiple timezones are equally "close" to noon, one is chosen randomly. Subsequently, a city name within that chosen timezone is selected randomly.
1818+5. **Continuous Display:** Between re-evaluation intervals, the displayed time (`MM:SS`) simply ticks forward second-by-second based on the *currently selected* city's timezone offset. The city name remains constant during this period.
1919+6. **Allowed Time Jumps:** When a new city/timezone is selected at a re-evaluation interval, the displayed `MM:SS` will jump to reflect the *new* local time. This can result in the displayed minutes/seconds appearing to go backward or jump forward significantly.
2020+2121+## Re-evaluation Logic (Executed at UTC XX:00:00, XX:15:00, XX:30:00)
2222+2323+1. **Get Current UTC:** Obtain the current UTC time as a timestamp (`current_utc_t`, e.g., a `time_t`).
2424+2. **Define Noon:** Define the target time as 12:00:00 (represented as seconds past midnight, e.g., `NOON_SECONDS = 12 * 3600`).
2525+3. **Iterate Through Timezones (`tz_list`) - Find Minimum Valid Time:**
2626+ * Initialize `min_valid_local_seconds` to a very large value (e.g., `DAY_SECONDS`).
2727+ * Create a temporary list/array (`candidates`) to store potential winners.
2828+ * For each `TzInfo` entry in `tz_list`:
2929+ * **Determine Active Offset:** Check if `current_utc_t` falls within the `dst_start_utc` and `dst_end_utc` for that entry. Use `dst_offset_hours` if true, `std_offset_hours` otherwise.
3030+ * **Calculate Local Time:** Determine the local time for this timezone at `current_utc_t`, expressed as seconds past local midnight (`local_seconds_today`). Ensure the result is correctly handled (e.g., modulo `DAY_SECONDS`, handle negative results).
3131+ * **Filter for Validity:** Check if `local_seconds_today >= NOON_SECONDS`.
3232+ * **If Valid:**
3333+ * Store the `TzInfo` index, `local_seconds_today`, and the `active_offset_hours` in the `candidates` list.
3434+ * Compare `local_seconds_today` with `min_valid_local_seconds`. If it's smaller, update `min_valid_local_seconds`.
3535+4. **Collect Best Candidates:**
3636+ * Create a new list (`best_candidates`).
3737+ * Iterate through the `candidates` list gathered in step 3.
3838+ * If a candidate's `local_seconds_today` equals `min_valid_local_seconds`, add its index (or a reference to it) to the `best_candidates` list.
3939+5. **Handle No Valid Candidates:** If `best_candidates` is empty (meaning no timezone had local time >= noon), set a fallback state (e.g., `selected_city_name = "Wait..."`, `selected_offset_hours = 0.0`).
4040+6. **Select Winning Timezone (if candidates exist):** Randomly choose one entry from the `best_candidates` list. This gives you the winning candidate structure (containing the `TzInfo` index and active offset).
4141+7. **Select Winning City:** Get the `TzInfo` entry using the index from the winning candidate. Randomly choose a city name (`selected_city_name`) from the `names` array within that `TzInfo`.
4242+8. **Store Results:** Store the `selected_city_name` and the `active_offset_hours` from the winning candidate (`selected_offset_hours`). These will be used for display until the next re-evaluation. If a fallback state was set in step 5, store those values instead.
4343+4444+## Continuous Display Logic (Executed Every Second)
4545+4646+1. **Get Current UTC:** Obtain the current UTC timestamp (`current_utc_t`).
4747+2. **Calculate Current Local Time:** Use the *stored* `selected_offset_hours` from the last re-evaluation.
4848+ `current_local_time = current_utc_t + (selected_offset_hours * 3600)`
4949+3. **Format Time:** Extract the minutes (`MM`) and seconds (`SS`) from `current_local_time`.
5050+4. **Display:** Output the string `selected_city_name:MM:SS`.
+34-14
generate_tz_list.py
···126126 available_zones = zoneinfo.available_timezones()
127127 print(f"Found {len(available_zones)} available timezones.")
128128129129- processed_zones = {} # Key: std_offset_hours, Value: Dict of zone data
129129+ processed_zones = {} # Key: TUPLE(std_offset_s, dst_offset_s, start_utc, end_utc), Value: Dict of zone data
130130131131 for tz_name in available_zones:
132132 # Basic filtering (no dead code here)
···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('_', ' ')
138138139139- # Convert offsets back to hours
139139+ # --- Filter out generic names ---
140140+ # Comprehensive list based on review of tz_list.c
141141+ generic_names_to_exclude = {
142142+ "Samoa", "Hawaii", "Aleutian", "Alaska", "Pacific", "Arizona", "Yukon",
143143+ "Mountain", "General", "Saskatchewan", "Central", "Knox IN", "EasterIsland",
144144+ "Acre", "Jamaica", "Michigan", "Eastern", "East-Indiana", "Atlantic",
145145+ "Continental", "Newfoundland", "East", "Bahia", "Noronha", "South Georgia",
146146+ "Canary", "Faeroe", "Faroe", "Guernsey", "Isle of Man", "Jersey",
147147+ "Madeira", "Jan Mayen", "West", "North", "South", "ACT", "NSW",
148148+ "Tasmania", "Victoria", "Queensland", "Yap", "South Pole", "Kanton",
149149+ # Add or remove names as needed
150150+ }
151151+ # Case-insensitive check for exclusion
152152+ if city_name.lower() in {name.lower() for name in generic_names_to_exclude}:
153153+ continue # Skip this generic name
154154+155155+ # Convert offsets back to hours for potential display, but keep seconds for key
140156 std_offset_h = std_offset_s / 3600.0
141157 dst_offset_h = dst_offset_s / 3600.0
142158143143- # Group by standard offset
144144- key_offset = std_offset_h
159159+ # Group by the unique combination of std offset, dst offset, and transitions
160160+ key_tuple = (std_offset_s, dst_offset_s, start_utc, end_utc)
145161 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,
162162+ if key_tuple not in processed_zones:
163163+ processed_zones[key_tuple] = {
164164+ "std_offset_s": std_offset_s, # Store seconds internally
165165+ "dst_offset_s": dst_offset_s,
150166 "start_utc": start_utc,
151167 "end_utc": end_utc,
152168 "names": []
153169 }
154170 # 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)
171171+ if city_name not in processed_zones[key_tuple]["names"]:
172172+ processed_zones[key_tuple]["names"].append(city_name)
157173158158- # 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.")
174174+ # Convert dict values to a list and sort primarily by standard offset, then DST offset
175175+ # Sort key uses the seconds offset stored in the dictionary value
176176+ tz_data_list = sorted(processed_zones.values(), key=lambda x: (x["std_offset_s"], x["dst_offset_s"]))
177177+ print(f"Generated data for {len(tz_data_list)} unique offset/DST rule combinations.")
161178162179 # --- C Code Generation ---
163180 # Build C code string (no obvious dead code here)
···199216 c_code += "// Main list mapping offsets/DST info to their respective name arrays\n"
200217 c_code += "static const TzInfo tz_list[] = {\n"
201218 for zone_data in tz_data_list:
219219+ # Convert offsets to hours ONLY for C code generation
220220+ std_offset_h_c = zone_data['std_offset_s'] / 3600.0
221221+ dst_offset_h_c = zone_data['dst_offset_s'] / 3600.0
202222 c_array_name = zone_data["c_array_name"]
203223 name_count = len(zone_data["names"])
204224 # 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, "
225225+ c_code += (f" {{ {std_offset_h_c:.2f}f, {dst_offset_h_c:.2f}f, "
206226 f"{zone_data['start_utc']}LL, {zone_data['end_utc']}LL, "
207227 f"{c_array_name}, {name_count} }},\n")
208228
+160-120
src/c/clock_closest_noon.c
···22#include <pebble.h> // Always include pebble for device builds
33#include <stdlib.h> // For rand()
44#include <stdint.h> // For uint32_t, int64_t
55-#include <time.h> // For time_t, struct tm, gmtime
55+#include <time.h> // For time_t, struct tm, gmtime, strcmp, strncmp
66#include <stdbool.h>// For bool
77+#include <limits.h> // For LONG_MAX // Not strictly needed anymore, but harmless
88+#include <string.h> // For strcmp, strncmp
79810// Include the generated timezone data structure definitions and list
911// This must contain TzInfo, tz_list[], and TZ_LIST_COUNT
1012#include "tz_list.c"
11131212-#define DAY_LENGTH 86400
1414+#define DAY_SECONDS (24 * 3600L)
1315#define NOON_SECONDS (12 * 3600L)
14161517// --- Static variables specific to Closest Noon Clock ---
1616-static char s_closest_noon_buffer[32];
1717-static int last_closest_noon_update_secs = -1;
1818+static char s_closest_noon_buffer[40]; // Increased size for potentially longer names + MM:SS
1919+static time_t s_last_update_time = -1;
2020+static time_t s_last_re_evaluation_time = -1;
18211919-// Cache for stabilization
2020-static int last_chosen_zone_index = -1; // Index in tz_list of the last chosen zone
2121-static const char* last_displayed_name = NULL; // Specific name displayed last time
2222+// Stores the results of the last re-evaluation
2323+static const char *s_selected_city_name = "Wait...";
2424+static float s_selected_offset_hours = 0.0f; // Store the active offset
22252326// --- Static helper functions ---
2727+2428/**
2525- * Helper function to find the current noon-hour zone, handle stabilization,
2626- * and calculate display values. Switches proactively to the zone(s)
2727- * closest to 12:00:00 local time.
2929+ * @brief Re-evaluates and selects the timezone whose local time is >= 12:00:00
3030+ * and closest to 12:00:00, based on current UTC.
3131+ * Updates the static s_selected_city_name and s_selected_offset_hours.
3232+ * Should only be called at :00, :15, :30, :45 minutes past the hour (UTC).
2833 *
2929- * Returns: true if a zone was found and display values calculated, false otherwise.
3030- * Outputs: Pointers to store the chosen display name, minutes past noon, and seconds past noon.
3434+ * @param current_utc_t The current UTC time as time_t.
3135 */
3232-static bool calculate_noon_zone_display(time_t current_seconds_utc,
3333- const char** out_display_name,
3434- long* out_display_minutes,
3535- long* out_display_seconds)
3636-{
3737- time_t current_time_t = (time_t)current_seconds_utc;
3838- uint32_t utc_seconds_today = current_seconds_utc % DAY_LENGTH;
3636+static void update_selected_timezone_and_city(time_t current_utc_t) {
3737+ // Seed random number generator (important for random selection)
3838+ srand(current_utc_t);
39394040- // --- Find candidate zones currently in the noon hour (12:00:00 - 12:59:59) ---
4040+ // Find the minimum local time that is >= noon
4141+ long min_valid_local_seconds = DAY_SECONDS; // Initialize higher than any possible time
4242+4343+ // Structure to hold candidate timezone info
4144 typedef struct {
4242- int index;
4343- long local_secs;
4444- } NoonCandidate;
4545- NoonCandidate current_candidates[TZ_LIST_COUNT];
4646- int current_candidate_count = 0;
4747- long min_local_secs_in_noon_hour = DAY_LENGTH; // Find the earliest time past 12:00
4545+ int index; // Index in tz_list
4646+ long local_secs_today; // Local time in seconds past midnight
4747+ float active_offset_h; // The offset used for calculation
4848+ } ZoneCandidate;
4949+5050+ ZoneCandidate candidates[TZ_LIST_COUNT];
5151+ int candidate_count = 0; // Count of zones with time >= noon
5252+5353+ uint32_t utc_seconds_today = current_utc_t % DAY_SECONDS;
48545555+ // --- Pass 1: Calculate local time for all zones, filter >= noon, find minimum valid time ---
4956 for (int i = 0; i < TZ_LIST_COUNT; ++i) {
5057 const TzInfo *tz = &tz_list[i];
5158 bool is_dst_active = false;
5252- // --- Determine if DST is active ---
5959+6060+ // --- Determine if DST is active ---
5361 if (tz->dst_start_utc != 0LL && tz->dst_end_utc != 0LL) {
5462 int64_t start_time = tz->dst_start_utc;
5563 int64_t end_time = tz->dst_end_utc;
5656- int64_t current_time_64 = (int64_t)current_time_t;
6464+ int64_t current_time_64 = (int64_t)current_utc_t;
6565+ // Standard check for non-wrapping interval
5766 if (start_time <= end_time) {
5858- if (current_time_64 >= start_time && current_time_64 < end_time) is_dst_active = true;
5959- } else {
6060- if (current_time_64 >= start_time || current_time_64 < end_time) is_dst_active = true;
6767+ if (current_time_64 >= start_time && current_time_64 < end_time) {
6868+ is_dst_active = true;
6969+ }
6170 }
6262- }
6363- float chosen_offset_hours = is_dst_active ? tz->dst_offset_hours : tz->std_offset_hours;
6464- long offset_seconds = (long)(chosen_offset_hours * 3600.0f);
6565- long local_seconds_today = ((long)utc_seconds_today + offset_seconds) % DAY_LENGTH;
6666- if (local_seconds_today < 0) local_seconds_today += DAY_LENGTH;
6767-6868- // --- Check if this zone is in the noon hour ---
6969- if (local_seconds_today >= NOON_SECONDS && local_seconds_today < (NOON_SECONDS + 3600)) {
7070- if (current_candidate_count < TZ_LIST_COUNT) {
7171- current_candidates[current_candidate_count].index = i;
7272- current_candidates[current_candidate_count].local_secs = local_seconds_today;
7373- current_candidate_count++;
7474- // Track the minimum local time among candidates
7575- if (local_seconds_today < min_local_secs_in_noon_hour) {
7676- min_local_secs_in_noon_hour = local_seconds_today;
7171+ // Check for wrapping interval (e.g., Southern Hemisphere DST)
7272+ else {
7373+ if (current_time_64 >= start_time || current_time_64 < end_time) {
7474+ is_dst_active = true;
7775 }
7876 }
7977 }
8080- }
7878+7979+ float active_offset_hours = is_dst_active ? tz->dst_offset_hours : tz->std_offset_hours;
8080+ long offset_seconds = (long)(active_offset_hours * 3600.0f);
81818282- // --- Identify the "Best" Candidates (those with the minimum local time) ---
8383- NoonCandidate best_candidates[TZ_LIST_COUNT];
8484- int best_candidate_count = 0;
8585- if (current_candidate_count > 0) {
8686- for (int k = 0; k < current_candidate_count; ++k) {
8787- if (current_candidates[k].local_secs == min_local_secs_in_noon_hour) {
8888- if (best_candidate_count < TZ_LIST_COUNT) {
8989- best_candidates[best_candidate_count++] = current_candidates[k];
9090- }
8282+ // Calculate local time in seconds past local midnight
8383+ long local_seconds_today = ((long)utc_seconds_today + offset_seconds);
8484+ // Ensure positive modulo result within the day
8585+ local_seconds_today %= DAY_SECONDS;
8686+ if (local_seconds_today < 0) {
8787+ local_seconds_today += DAY_SECONDS;
8888+ }
8989+9090+ // --- Check if local time is at or after noon ---
9191+ if (local_seconds_today >= NOON_SECONDS) {
9292+ // Store as a potential candidate
9393+ if (candidate_count < TZ_LIST_COUNT) {
9494+ candidates[candidate_count].index = i;
9595+ candidates[candidate_count].local_secs_today = local_seconds_today;
9696+ candidates[candidate_count].active_offset_h = active_offset_hours;
9797+ candidate_count++; // Increment count of valid candidates
9898+ }
9999+ // Update the minimum valid local time found so far
100100+ if (local_seconds_today < min_valid_local_seconds) {
101101+ min_valid_local_seconds = local_seconds_today;
91102 }
92103 }
93104 }
941059595- // --- Select the zone and name to display ---
9696- int chosen_zone_index = -1;
9797- long chosen_local_seconds = -1;
9898- const char *current_display_name = "Wait...";
9999-100100- bool use_last_chosen = false;
101101- if (last_chosen_zone_index != -1 && best_candidate_count > 0) {
102102- // Check if the last chosen zone is among the *best* current candidates
103103- for (int k = 0; k < best_candidate_count; ++k) {
104104- if (best_candidates[k].index == last_chosen_zone_index) {
105105- chosen_zone_index = last_chosen_zone_index;
106106- chosen_local_seconds = best_candidates[k].local_secs;
107107- current_display_name = last_displayed_name; // Reuse the specific name
108108- use_last_chosen = true;
109109- break;
106106+ // --- Pass 2: Collect all candidates matching the minimum valid local time ---
107107+ int best_candidates_indices[TZ_LIST_COUNT]; // Stores indices into 'candidates' array
108108+ int best_candidate_count = 0;
109109+ if (candidate_count > 0) { // Only proceed if we found any zones >= noon
110110+ for (int k = 0; k < candidate_count; ++k) {
111111+ // Check if this candidate's time is the minimum valid time we found
112112+ if (candidates[k].local_secs_today == min_valid_local_seconds) {
113113+ if (best_candidate_count < TZ_LIST_COUNT) {
114114+ best_candidates_indices[best_candidate_count++] = k; // Store index into 'candidates' array
115115+ }
110116 }
111117 }
112118 }
113119114114- if (!use_last_chosen) { // If last wasn't valid *among the best*, or first run, or no candidates
115115- if (best_candidate_count > 0) {
116116- // Select an index randomly *from the best candidates*
117117- int list_pos = (best_candidate_count == 1) ? 0 : (rand() % best_candidate_count);
118118- chosen_zone_index = best_candidates[list_pos].index;
119119- chosen_local_seconds = best_candidates[list_pos].local_secs;
120120+121121+ // --- Select the winning timezone and city ---
122122+ if (best_candidate_count > 0) {
123123+ // Randomly select one of the best candidates
124124+ int list_pos = (best_candidate_count == 1) ? 0 : (rand() % best_candidate_count);
125125+ int winning_candidate_idx_in_candidates = best_candidates_indices[list_pos]; // Index in 'candidates' array
120126121121- // Select a specific city name from that chosen zone's list
122122- const TzInfo *chosen_tz = &tz_list[chosen_zone_index];
123123- if (chosen_tz->name_count > 0) {
124124- int name_index = (chosen_tz->name_count == 1) ? 0 : (rand() % chosen_tz->name_count);
125125- if (name_index < chosen_tz->name_count) {
126126- current_display_name = chosen_tz->names[name_index].name;
127127- } else { current_display_name = "ERR:NAME"; } // Safety fallback
128128- } else { current_display_name = "ERR:NO_NM"; } // Safety fallback
127127+ int chosen_zone_list_index = candidates[winning_candidate_idx_in_candidates].index; // Index in tz_list
128128+ const TzInfo *chosen_tz = &tz_list[chosen_zone_list_index];
129129130130- // Update static cache for next time
131131- last_chosen_zone_index = chosen_zone_index;
132132- last_displayed_name = current_display_name;
130130+ // Randomly select a city name from the winning timezone
131131+ if (chosen_tz->name_count > 0) {
132132+ int name_index = (chosen_tz->name_count == 1) ? 0 : (rand() % chosen_tz->name_count);
133133+ // Basic bounds check (should always be true if rand() works correctly)
134134+ if (name_index >= 0 && name_index < chosen_tz->name_count) {
135135+ s_selected_city_name = chosen_tz->names[name_index].name;
136136+ } else {
137137+ s_selected_city_name = "ERR:NAME"; // Safety fallback
138138+ }
133139 } else {
134134- // No zones currently in noon hour - reset static cache
135135- last_chosen_zone_index = -1;
136136- last_displayed_name = NULL;
137137- current_display_name = "Wait..."; // Explicitly set placeholder
138138- chosen_zone_index = -1; // Ensure calculation is skipped
140140+ s_selected_city_name = "ERR:NO_NM"; // Safety fallback
139141 }
140140- }
142142+ // Store the active offset that was used for this winning timezone
143143+ s_selected_offset_hours = candidates[winning_candidate_idx_in_candidates].active_offset_h;
141144142142- // --- Calculate display values and return ---
143143- if (chosen_zone_index != -1) {
144144- long seconds_since_noon_in_zone = chosen_local_seconds - NOON_SECONDS;
145145- *out_display_minutes = seconds_since_noon_in_zone / 60;
146146- *out_display_seconds = seconds_since_noon_in_zone % 60;
147147- *out_display_name = current_display_name;
148148- return true; // Success
149145 } else {
150150- *out_display_name = current_display_name; // Return "Wait..." or error
151151- return false; // Indicate failure
146146+ // This case handles when NO timezone has local time >= 12:00:00 PM
147147+ s_selected_city_name = "Wait..."; // Indicate searching or fallback state
148148+ s_selected_offset_hours = 0.0f; // Reset offset
152149 }
150150+ s_last_re_evaluation_time = current_utc_t; // Record when we last did this
153151}
152152+154153155154// --- Interface Functions (Pebble only) ---
156155···158157 TextLayer* layer = text_layer_create(bounds);
159158 text_layer_set_background_color(layer, GColorClear);
160159 text_layer_set_text_color(layer, GColorBlack);
161161- text_layer_set_text(layer, "City:--:--"); // Initial placeholder
160160+ text_layer_set_text(layer, "Wait..."); // Initial placeholder
162161 #define CLOSEST_FONT FONT_KEY_GOTHIC_18_BOLD // Keep font definitions near usage
163162 text_layer_set_font(layer, fonts_get_system_font(CLOSEST_FONT));
164163 text_layer_set_text_alignment(layer, GTextAlignmentCenter);
165164 layer_add_child(window_layer, text_layer_get_layer(layer));
165165+166166+ // Initialize static vars
167167+ s_selected_city_name = "Wait...";
168168+ s_selected_offset_hours = 0.0f;
169169+ s_last_update_time = -1;
170170+ s_last_re_evaluation_time = -1; // Force re-evaluation on first update
171171+166172 return layer;
167173}
168174···172178 }
173179}
174180175175-void clock_closest_noon_update(TextLayer *layer, time_t current_seconds_utc) {
181181+void clock_closest_noon_update(TextLayer *layer, time_t current_utc_t) {
176182 if (!layer) return;
177183178178- // Avoid update if time hasn't changed
179179- if (current_seconds_utc == last_closest_noon_update_secs) {
184184+ // Avoid redundant updates for the same second
185185+ if (current_utc_t == s_last_update_time) {
180186 return;
181187 }
188188+ s_last_update_time = current_utc_t;
182189183183- const char *display_name;
184184- long display_minutes;
185185- long display_seconds;
190190+ // --- Check if it's time for re-evaluation ---
191191+ struct tm *utc_tm_struct = gmtime(¤t_utc_t); // Use a different name to avoid shadowing
192192+ bool needs_re_evaluation = false;
186193187187- bool found_zone = calculate_noon_zone_display(current_seconds_utc,
188188- &display_name,
189189- &display_minutes,
190190- &display_seconds);
194194+ // Check if gmtime succeeded and if it's a re-evaluation time point (:00, :15, :30)
195195+ if (utc_tm_struct && (utc_tm_struct->tm_min % 15 == 0) && (utc_tm_struct->tm_min != 45) && (utc_tm_struct->tm_sec == 0)) {
196196+ // Avoid re-evaluating multiple times within the same second if update is called rapidly
197197+ if (current_utc_t != s_last_re_evaluation_time) {
198198+ needs_re_evaluation = true;
199199+ }
200200+ } else if (s_last_re_evaluation_time == -1) {
201201+ // Ensure initial evaluation happens if starting between intervals
202202+ needs_re_evaluation = true;
203203+ }
191204192192- if (found_zone) {
193193- snprintf(s_closest_noon_buffer, sizeof(s_closest_noon_buffer),
194194- "%s:%02ld:%02ld", display_name, display_minutes, display_seconds);
205205+ if (needs_re_evaluation) {
206206+ update_selected_timezone_and_city(current_utc_t);
207207+ }
208208+209209+210210+ // --- Calculate current local time based on selected zone ---
211211+ // Use the stored offset from the last re-evaluation
212212+ long current_offset_seconds = (long)(s_selected_offset_hours * 3600.0f);
213213+ time_t local_time_epoch = current_utc_t + current_offset_seconds; // Use a distinct name
214214+215215+ // --- Format and display the time ---
216216+ struct tm *current_local_tm_struct = gmtime(&local_time_epoch); // Use gmtime to format based on the calculated local epoch time
217217+218218+ // Check if a valid city was selected in the last evaluation or if it's fallback
219219+ if (s_selected_city_name && strcmp(s_selected_city_name, "Wait...") != 0 &&
220220+ strncmp(s_selected_city_name, "ERR:", 4) != 0)
221221+ {
222222+ if (current_local_tm_struct) {
223223+ // Display format: CityName:MM:SS using actual local time
224224+ snprintf(s_closest_noon_buffer, sizeof(s_closest_noon_buffer),
225225+ "%s:%02d:%02d",
226226+ s_selected_city_name, // Already checked for null/error states
227227+ current_local_tm_struct->tm_min,
228228+ current_local_tm_struct->tm_sec);
229229+ } else {
230230+ // Handle gmtime failure for a selected city
231231+ snprintf(s_closest_noon_buffer, sizeof(s_closest_noon_buffer), "%s:ERR:TIME",
232232+ s_selected_city_name);
233233+ }
195234 } else {
196196- // Use the placeholder/error name returned by the helper
197197- snprintf(s_closest_noon_buffer, sizeof(s_closest_noon_buffer), "%s", display_name);
235235+ // Display the fallback/error name directly (e.g., "Wait...", "ERR:NOZONE")
236236+ snprintf(s_closest_noon_buffer, sizeof(s_closest_noon_buffer), "%s",
237237+ s_selected_city_name ? s_selected_city_name : "ERR:NULL");
198238 }
199239240240+200241 text_layer_set_text(layer, s_closest_noon_buffer);
201201- last_closest_noon_update_secs = current_seconds_utc;
202242}