GCC Code Coverage Report


Directory: ./
File: panels/system/datetime/tz.c
Date: 2024-05-03 09:46:52
Exec Total Coverage
Lines: 0 167 0.0%
Functions: 0 18 0.0%
Branches: 0 116 0.0%

Line Branch Exec Source
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* Generic timezone utilities.
3 *
4 * Copyright (C) 2000-2001 Ximian, Inc.
5 *
6 * Authors: Hans Petter Jansson <hpj@ximian.com>
7 *
8 * Largely based on Michael Fulbright's work on Anaconda.
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, see <http://www.gnu.org/licenses/>.
22 */
23
24
25 #include <glib.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <unistd.h>
29 #include <time.h>
30 #include <math.h>
31 #include <string.h>
32 #include <ctype.h>
33 #include "tz.h"
34 #include "cc-system-resources.h"
35
36
37 /* Forward declarations for private functions */
38
39 static float convert_pos (gchar *pos, int digits);
40 static int compare_country_names (const void *a, const void *b);
41 static void sort_locations_by_country (GPtrArray *locations);
42 static gchar * tz_data_file_get (void);
43 static void load_backward_tz (TzDB *tz_db);
44
45 /* ---------------- *
46 * Public interface *
47 * ---------------- */
48 TzDB *
49 tz_load_db (void)
50 {
51 g_autofree gchar *tz_data_file = NULL;
52 TzDB *tz_db;
53 FILE *tzfile;
54 char buf[4096];
55
56 tz_data_file = tz_data_file_get ();
57 if (!tz_data_file) {
58 g_warning ("Could not get the TimeZone data file name");
59 return NULL;
60 }
61 tzfile = fopen (tz_data_file, "r");
62 if (!tzfile) {
63 g_warning ("Could not open *%s*\n", tz_data_file);
64 return NULL;
65 }
66
67 tz_db = g_new0 (TzDB, 1);
68 tz_db->locations = g_ptr_array_new ();
69
70 while (fgets (buf, sizeof(buf), tzfile))
71 {
72 g_auto(GStrv) tmpstrarr = NULL;
73 g_autofree gchar *latstr = NULL;
74 g_autofree gchar *lngstr = NULL;
75 gchar *p;
76 TzLocation *loc;
77
78 if (*buf == '#') continue;
79
80 g_strchomp(buf);
81 tmpstrarr = g_strsplit(buf,"\t", 6);
82
83 latstr = g_strdup (tmpstrarr[1]);
84 p = latstr + 1;
85 while (*p != '-' && *p != '+') p++;
86 lngstr = g_strdup (p);
87 *p = '\0';
88
89 loc = g_new0 (TzLocation, 1);
90 loc->country = g_strdup (tmpstrarr[0]);
91 loc->zone = g_strdup (tmpstrarr[2]);
92 loc->latitude = convert_pos (latstr, 2);
93 loc->longitude = convert_pos (lngstr, 3);
94
95 #ifdef __sun
96 if (tmpstrarr[3] && *tmpstrarr[3] == '-' && tmpstrarr[4])
97 loc->comment = g_strdup (tmpstrarr[4]);
98
99 if (tmpstrarr[3] && *tmpstrarr[3] != '-' && !islower(loc->zone)) {
100 TzLocation *locgrp;
101
102 /* duplicate entry */
103 locgrp = g_new0 (TzLocation, 1);
104 locgrp->country = g_strdup (tmpstrarr[0]);
105 locgrp->zone = g_strdup (tmpstrarr[3]);
106 locgrp->latitude = convert_pos (latstr, 2);
107 locgrp->longitude = convert_pos (lngstr, 3);
108 locgrp->comment = (tmpstrarr[4]) ? g_strdup (tmpstrarr[4]) : NULL;
109
110 g_ptr_array_add (tz_db->locations, (gpointer) locgrp);
111 }
112 #else
113 loc->comment = (tmpstrarr[3]) ? g_strdup(tmpstrarr[3]) : NULL;
114 #endif
115
116 g_ptr_array_add (tz_db->locations, (gpointer) loc);
117 }
118
119 fclose (tzfile);
120
121 /* now sort by country */
122 sort_locations_by_country (tz_db->locations);
123
124 /* Load up the hashtable of backward links */
125 load_backward_tz (tz_db);
126
127 return tz_db;
128 }
129
130 static void
131 tz_location_free (TzLocation *loc)
132 {
133 g_free (loc->country);
134 g_free (loc->zone);
135 g_free (loc->comment);
136
137 g_free (loc);
138 }
139
140 void
141 tz_db_free (TzDB *db)
142 {
143 g_ptr_array_foreach (db->locations, (GFunc) tz_location_free, NULL);
144 g_ptr_array_free (db->locations, TRUE);
145 g_hash_table_destroy (db->backward);
146 g_free (db);
147 }
148
149 GPtrArray *
150 tz_get_locations (TzDB *db)
151 {
152 return db->locations;
153 }
154
155
156 gchar *
157 tz_location_get_country (TzLocation *loc)
158 {
159 return loc->country;
160 }
161
162
163 gchar *
164 tz_location_get_zone (TzLocation *loc)
165 {
166 return loc->zone;
167 }
168
169
170 gchar *
171 tz_location_get_comment (TzLocation *loc)
172 {
173 return loc->comment;
174 }
175
176
177 void
178 tz_location_get_position (TzLocation *loc, double *longitude, double *latitude)
179 {
180 *longitude = loc->longitude;
181 *latitude = loc->latitude;
182 }
183
184 /* For timezone map display purposes, we try to highlight regions of the
185 * world that keep the same time. There is no reasonable API to discover
186 * this; at the moment we just group timezones by their non-daylight-savings
187 * UTC offset and hope that's good enough. However, in some cases that
188 * produces confusing results. For example, Irish Standard Time is legally
189 * defined as the country's summer time, with a negative DST offset in
190 * winter; but this results in the same observed clock times as countries
191 * that observe Western European (Summer) Time, not those that observe
192 * Central European (Summer) Time, so we should group Ireland with the
193 * former, matching the grouping implied by data/timezone_*.png.
194 *
195 * This is something of a hack, and there remain other problems with
196 * timezone grouping: for example, grouping timezones north and south of the
197 * equator together where DST is observed at different times of the year is
198 * dubious.
199 */
200 struct {
201 const char *zone;
202 gint offset;
203 } base_offset_overrides[] = {
204 { "Europe/Dublin", 0 },
205 };
206
207 glong
208 tz_location_get_base_utc_offset (TzLocation *loc)
209 {
210 g_autoptr(TzInfo) tz_info = NULL;
211 glong offset;
212 guint i;
213
214 tz_info = tz_info_from_location (loc);
215 offset = tz_info->utc_offset + (tz_info->daylight ? -3600 : 0);
216
217 for (i = 0; i < G_N_ELEMENTS (base_offset_overrides); i++) {
218 if (g_str_equal (loc->zone, base_offset_overrides[i].zone)) {
219 offset = base_offset_overrides[i].offset;
220 break;
221 }
222 }
223
224 return offset;
225 }
226
227 TzInfo *
228 tz_info_from_location (TzLocation *loc)
229 {
230 TzInfo *tzinfo;
231 time_t curtime;
232 struct tm *curzone;
233 g_autofree gchar *tz_env_value = NULL;
234
235 g_return_val_if_fail (loc != NULL, NULL);
236 g_return_val_if_fail (loc->zone != NULL, NULL);
237
238 tz_env_value = g_strdup (getenv ("TZ"));
239 setenv ("TZ", loc->zone, 1);
240
241 #if 0
242 tzset ();
243 #endif
244 tzinfo = g_new0 (TzInfo, 1);
245
246 curtime = time (NULL);
247 curzone = localtime (&curtime);
248
249 #ifndef __sun
250 tzinfo->tzname = g_strdup (curzone->tm_zone);
251 tzinfo->utc_offset = curzone->tm_gmtoff;
252 #else
253 tzinfo->tzname = NULL;
254 tzinfo->utc_offset = 0;
255 #endif
256
257 tzinfo->daylight = curzone->tm_isdst;
258
259 if (tz_env_value)
260 setenv ("TZ", tz_env_value, 1);
261 else
262 unsetenv ("TZ");
263
264 return tzinfo;
265 }
266
267
268 void
269 tz_info_free (TzInfo *tzinfo)
270 {
271 g_return_if_fail (tzinfo != NULL);
272
273 if (tzinfo->tzname) g_free (tzinfo->tzname);
274 g_free (tzinfo);
275 }
276
277 struct {
278 const char *orig;
279 const char *dest;
280 } aliases[] = {
281 { "Asia/Istanbul", "Europe/Istanbul" }, /* Istanbul is in both Europe and Asia */
282 { "Europe/Nicosia", "Asia/Nicosia" }, /* Ditto */
283 { "EET", "Europe/Istanbul" }, /* Same tz as the 2 above */
284 { "HST", "Pacific/Honolulu" },
285 { "WET", "Europe/Brussels" }, /* Other name for the mainland Europe tz */
286 { "CET", "Europe/Brussels" }, /* ditto */
287 { "MET", "Europe/Brussels" },
288 { "Etc/Zulu", "Etc/GMT" },
289 { "Etc/UTC", "Etc/GMT" },
290 { "GMT", "Etc/GMT" },
291 { "Greenwich", "Etc/GMT" },
292 { "Etc/UCT", "Etc/GMT" },
293 { "Etc/GMT0", "Etc/GMT" },
294 { "Etc/GMT+0", "Etc/GMT" },
295 { "Etc/GMT-0", "Etc/GMT" },
296 { "Etc/Universal", "Etc/GMT" },
297 { "PST8PDT", "America/Los_Angeles" }, /* Other name for the Atlantic tz */
298 { "EST", "America/New_York" }, /* Other name for the Eastern tz */
299 { "EST5EDT", "America/New_York" }, /* ditto */
300 { "CST6CDT", "America/Chicago" }, /* Other name for the Central tz */
301 { "MST", "America/Denver" }, /* Other name for the mountain tz */
302 { "MST7MDT", "America/Denver" }, /* ditto */
303 };
304
305 static gboolean
306 compare_timezones (const char *a,
307 const char *b)
308 {
309 if (g_str_equal (a, b))
310 return TRUE;
311 if (strchr (b, '/') == NULL) {
312 g_autofree gchar *prefixed = NULL;
313
314 prefixed = g_strdup_printf ("/%s", b);
315 if (g_str_has_suffix (a, prefixed))
316 return TRUE;
317 }
318
319 return FALSE;
320 }
321
322 char *
323 tz_info_get_clean_name (TzDB *tz_db,
324 const char *tz)
325 {
326 char *ret;
327 const char *timezone;
328 guint i;
329 gboolean replaced;
330
331 /* Remove useless prefixes */
332 if (g_str_has_prefix (tz, "right/"))
333 tz = tz + strlen ("right/");
334 else if (g_str_has_prefix (tz, "posix/"))
335 tz = tz + strlen ("posix/");
336
337 /* Here start the crazies */
338 replaced = FALSE;
339
340 for (i = 0; i < G_N_ELEMENTS (aliases); i++) {
341 if (compare_timezones (tz, aliases[i].orig)) {
342 replaced = TRUE;
343 timezone = aliases[i].dest;
344 break;
345 }
346 }
347
348 /* Try again! */
349 if (!replaced) {
350 /* Ignore crazy solar times from the '80s */
351 if (g_str_has_prefix (tz, "Asia/Riyadh") ||
352 g_str_has_prefix (tz, "Mideast/Riyadh")) {
353 timezone = "Asia/Riyadh";
354 replaced = TRUE;
355 }
356 }
357
358 if (!replaced)
359 timezone = tz;
360
361 ret = g_hash_table_lookup (tz_db->backward, timezone);
362 if (ret == NULL)
363 return g_strdup (timezone);
364 return g_strdup (ret);
365 }
366
367 /* ----------------- *
368 * Private functions *
369 * ----------------- */
370
371 static gchar *
372 tz_data_file_get (void)
373 {
374 gchar *file;
375
376 file = g_strdup (TZ_DATA_FILE);
377
378 return file;
379 }
380
381 static float
382 convert_pos (gchar *pos, int digits)
383 {
384 gchar whole[10];
385 gchar *fraction;
386 gint i;
387 float t1, t2;
388
389 if (!pos || strlen(pos) < 4 || digits > 9) return 0.0;
390
391 for (i = 0; i < digits + 1; i++) whole[i] = pos[i];
392 whole[i] = '\0';
393 fraction = pos + digits + 1;
394
395 t1 = g_strtod (whole, NULL);
396 t2 = g_strtod (fraction, NULL);
397
398 if (t1 >= 0.0) return t1 + t2/pow (10.0, strlen(fraction));
399 else return t1 - t2/pow (10.0, strlen(fraction));
400 }
401
402
403 #if 0
404
405 /* Currently not working */
406 static void
407 free_tzdata (TzLocation *tz)
408 {
409
410 if (tz->country)
411 g_free(tz->country);
412 if (tz->zone)
413 g_free(tz->zone);
414 if (tz->comment)
415 g_free(tz->comment);
416
417 g_free(tz);
418 }
419 #endif
420
421
422 static int
423 compare_country_names (const void *a, const void *b)
424 {
425 const TzLocation *tza = * (TzLocation **) a;
426 const TzLocation *tzb = * (TzLocation **) b;
427
428 return strcmp (tza->zone, tzb->zone);
429 }
430
431
432 static void
433 sort_locations_by_country (GPtrArray *locations)
434 {
435 qsort (locations->pdata, locations->len, sizeof (gpointer),
436 compare_country_names);
437 }
438
439 static void
440 load_backward_tz (TzDB *tz_db)
441 {
442 g_auto(GStrv) lines = NULL;
443 g_autoptr(GBytes) bytes = NULL;
444 const char *contents;
445 guint i;
446
447 tz_db->backward = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
448
449 bytes = g_resources_lookup_data ("/org/gnome/control-center/system/datetime/backward",
450 G_RESOURCE_LOOKUP_FLAGS_NONE, NULL);
451 contents = (const char *) g_bytes_get_data (bytes, NULL);
452
453 lines = g_strsplit (contents, "\n", -1);
454
455 for (i = 0; lines[i] != NULL; i++)
456 {
457 g_auto(GStrv) items = NULL;
458 guint j;
459 char *real, *alias;
460
461 if (g_ascii_strncasecmp (lines[i], "Link\t", 5) != 0)
462 continue;
463
464 items = g_strsplit (lines[i], "\t", -1);
465 real = NULL;
466 alias = NULL;
467 /* Skip the "Link<tab>" part */
468 for (j = 1; items[j] != NULL; j++)
469 {
470 if (items[j][0] == '\0')
471 continue;
472 if (real == NULL)
473 {
474 real = items[j];
475 continue;
476 }
477 alias = items[j];
478 break;
479 }
480
481 if (real == NULL || alias == NULL)
482 g_warning ("Could not parse line: %s", lines[i]);
483
484 /* We don't need more than one name for it */
485 if (g_str_equal (real, "Etc/UTC") ||
486 g_str_equal (real, "Etc/UCT"))
487 real = "Etc/GMT";
488
489 g_hash_table_insert (tz_db->backward, g_strdup (alias), g_strdup (real));
490 }
491 }
492
493