summaryrefslogtreecommitdiffstats
path: root/libkcal/libical/vzic-1.3/vzic-parse.c
diff options
context:
space:
mode:
Diffstat (limited to 'libkcal/libical/vzic-1.3/vzic-parse.c')
-rw-r--r--libkcal/libical/vzic-1.3/vzic-parse.c901
1 files changed, 901 insertions, 0 deletions
diff --git a/libkcal/libical/vzic-1.3/vzic-parse.c b/libkcal/libical/vzic-1.3/vzic-parse.c
new file mode 100644
index 000000000..6ecdb7218
--- /dev/null
+++ b/libkcal/libical/vzic-1.3/vzic-parse.c
@@ -0,0 +1,901 @@
+/*
+ * Vzic - a program to convert Olson timezone database files into VZTIMEZONE
+ * files compatible with the iCalendar specification (RFC2445).
+ *
+ * Copyright (C) 2000-2001 Ximian, Inc.
+ * Copyright (C) 2003 Damon Chaplin.
+ *
+ * Author: Damon Chaplin <damon@gnome.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include <ctype.h>
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "vzic.h"
+#include "vzic-parse.h"
+
+/* This is the maximum line length we allow. */
+#define MAX_LINE_LEN 1024
+
+/* The maximum number of fields on a line. */
+#define MAX_FIELDS 12
+
+
+typedef enum
+{
+ ZONE_ID = 0, /* The 'Zone' at the start of the line. */
+ ZONE_NAME = 1,
+ ZONE_GMTOFF = 2,
+ ZONE_RULES_SAVE = 3,
+ ZONE_FORMAT = 4,
+ ZONE_UNTIL_YEAR = 5,
+ ZONE_UNTIL_MONTH = 6,
+ ZONE_UNTIL_DAY = 7,
+ ZONE_UNTIL_TIME = 8
+} ZoneFieldNumber;
+
+
+typedef enum
+{
+ RULE_ID = 0, /* The 'Rule' at the start of the line. */
+ RULE_NAME = 1,
+ RULE_FROM = 2,
+ RULE_TO = 3,
+ RULE_TYPE = 4,
+ RULE_IN = 5,
+ RULE_ON = 6,
+ RULE_AT = 7,
+ RULE_SAVE = 8,
+ RULE_LETTER_S = 9
+} RuleFieldNumber;
+
+
+typedef enum
+{
+ LINK_ID = 0, /* The 'Link' at the start of the line. */
+ LINK_FROM = 1,
+ LINK_TO = 2
+} LinkFieldNumber;
+
+
+/* This struct contains information used while parsing the files, and is
+ passed to most parsing functions. */
+typedef struct _ParsingData ParsingData;
+struct _ParsingData
+{
+ /* This is the line being parsed. buffer is a copy that we break into fields
+ and sub-fields as it is parsed. */
+ char line[MAX_LINE_LEN];
+ char buffer[MAX_LINE_LEN];
+
+ /* These are pointers to the start of each field in buffer. */
+ char *fields[MAX_FIELDS];
+ int num_fields;
+
+ /* These are just for producing error messages. */
+ char *filename;
+ int line_number;
+
+
+ /* This is an array of ZoneData structs, 1 for each timezone read. */
+ GArray *zone_data;
+
+ /* This is a hash table of arrays of RuleData structs. As each Rule line is
+ read in, a new RuleData struct is filled in and appended to the
+ appropriate GArray in the hash table. */
+ GHashTable *rule_data;
+
+ /* A hash containing data on the Link lines. The keys are the timezones
+ where the link is from (i.e. the timezone we will be outputting anyway)
+ and the data is a GList of timezones to link to (where we will copy the
+ timezone data to). */
+ GHashTable *link_data;
+
+ int max_until_year;
+};
+
+
+/*
+ * Parsing functions, used when reading the Olson timezone data file.
+ */
+static void parse_fields (ParsingData *data);
+static gboolean parse_zone_line (ParsingData *data);
+static gboolean parse_zone_continuation_line (ParsingData *data);
+static gboolean parse_zone_common (ParsingData *data,
+ int offset);
+static void parse_rule_line (ParsingData *data);
+static void parse_link_line (ParsingData *data);
+
+static int parse_year (ParsingData *data,
+ char *field,
+ gboolean accept_only,
+ int only_value);
+static int parse_month (ParsingData *data,
+ char *field);
+static DayCode parse_day (ParsingData *data,
+ char *field,
+ int *day,
+ int *weekday);
+static int parse_weekday (ParsingData *data,
+ char *field);
+static int parse_time (ParsingData *data,
+ char *field,
+ TimeCode *time_code);
+static int parse_number (ParsingData *data,
+ char **num);
+static int parse_rules_save (ParsingData *data,
+ char *field,
+ char **rules);
+
+static void parse_coord (char *coord,
+ int len,
+ int *result);
+
+void
+parse_olson_file (char *filename,
+ GArray **zone_data,
+ GHashTable **rule_data,
+ GHashTable **link_data,
+ int *max_until_year)
+{
+ ParsingData data;
+ FILE *fp;
+ int zone_continues = 0;
+
+ *zone_data = g_array_new (FALSE, FALSE, sizeof (ZoneData));
+ *rule_data = g_hash_table_new (g_str_hash, g_str_equal);
+ *link_data = g_hash_table_new (g_str_hash, g_str_equal);
+
+ fp = fopen (filename, "r");
+ if (!fp) {
+ fprintf (stderr, "Couldn't open file: %s\n", filename);
+ exit (1);
+ }
+
+ data.filename = filename;
+ data.zone_data = *zone_data;
+ data.rule_data = *rule_data;
+ data.link_data = *link_data;
+ data.max_until_year = 0;
+
+ for (data.line_number = 0; ; data.line_number++) {
+ if (fgets (data.line, sizeof (data.line), fp) != data.line)
+ break;
+
+ strcpy (data.buffer, data.line);
+
+ parse_fields (&data);
+ if (data.num_fields == 0)
+ continue;
+
+ if (zone_continues) {
+ zone_continues = parse_zone_continuation_line (&data);
+ } else if (!strcmp (data.fields[0], "Zone")) {
+ zone_continues = parse_zone_line (&data);
+ } else if (!strcmp (data.fields[0], "Rule")) {
+ parse_rule_line (&data);
+ } else if (!strcmp (data.fields[0], "Link")) {
+ parse_link_line (&data);
+ } else if (!strcmp (data.fields[0], "Leap")) {
+ /* We don't care about Leap lines. */
+ } else {
+ fprintf (stderr, "%s:%i: Invalid line.\n%s\n", filename,
+ data.line_number, data.line);
+ exit (1);
+ }
+ }
+
+ if (ferror (fp)) {
+ fprintf (stderr, "Error reading file: %s\n", filename);
+ exit (1);
+ }
+
+ if (zone_continues) {
+ fprintf (stderr, "%s:%i: Zone continuation line expected.\n%s\n",
+ filename, data.line_number, data.line);
+ exit (1);
+ }
+
+ fclose (fp);
+
+#if 0
+ printf ("Max UNTIL year: %i\n", data.max_until_year);
+#endif
+ *max_until_year = data.max_until_year;
+}
+
+
+/* Converts the line into fields. */
+static void
+parse_fields (ParsingData *data)
+{
+ int i;
+ char *p, *s, ch;
+
+ /* Reset all fields to NULL. */
+ for (i = 0; i < MAX_FIELDS; i++)
+ data->fields[i] = 0;
+
+ data->num_fields = 0;
+ p = data->buffer;
+
+ for (;;) {
+ /* Skip whitespace. */
+ while (isspace (*p))
+ p++;
+
+ /* See if we have reached the end of the line or a comment. */
+ if (*p == '\0' || *p == '#')
+ break;
+
+ /* We must have another field, so save the start position. */
+ data->fields[data->num_fields++] = p;
+
+ /* Now find the end of the field. If the field contains '"' characters
+ they are removed and we have to move the rest of the chars back. */
+ s = p;
+ for (;;) {
+ ch = *p;
+ if (ch == '\0' || ch == '#') {
+ /* Don't move p on since this is the end of the line. */
+ *s = '\0';
+ break;
+ } else if (isspace (ch)) {
+ *s = '\0';
+ p++;
+ break;
+ } else if (ch == '"') {
+ p++;
+ for (;;) {
+ ch = *p;
+ if (ch == '\0') {
+ fprintf (stderr,
+ "%s:%i: Closing quote character ('\"') missing.\n%s\n",
+ data->filename, data->line_number, data->line);
+ exit (1);
+ } else if (ch == '"') {
+ p++;
+ break;
+ } else {
+ *s++ = ch;
+ }
+ p++;
+ }
+ } else {
+ *s++ = ch;
+ }
+ p++;
+ }
+ }
+
+#if 0
+ printf ("%i fields: ", data->num_fields);
+ for (i = 0; i < data->num_fields; i++)
+ printf ("'%s' ", data->fields[i]);
+ printf ("\n");
+#endif
+}
+
+
+static gboolean
+parse_zone_line (ParsingData *data)
+{
+ ZoneData zone;
+
+ /* All 5 fields up to FORMAT must be present. */
+ if (data->num_fields < 5 || data->num_fields > 9) {
+ fprintf (stderr, "%s:%i: Invalid Zone line - %i fields.\n%s\n",
+ data->filename, data->line_number, data->num_fields,
+ data->line);
+ exit (1);
+ }
+
+ zone.zone_name = g_strdup (data->fields[ZONE_NAME]);
+ zone.zone_line_data = g_array_new (FALSE, FALSE, sizeof (ZoneLineData));
+
+ g_array_append_val (data->zone_data, zone);
+
+ return parse_zone_common (data, 0);
+}
+
+
+static gboolean
+parse_zone_continuation_line (ParsingData *data)
+{
+ /* All 3 fields up to FORMAT must be present. */
+ if (data->num_fields < 3 || data->num_fields > 7) {
+ fprintf (stderr,
+ "%s:%i: Invalid Zone continuation line - %i fields.\n%s\n",
+ data->filename, data->line_number, data->num_fields,
+ data->line);
+ exit (1);
+ }
+
+ return parse_zone_common (data, -2);
+}
+
+
+static gboolean
+parse_zone_common (ParsingData *data,
+ int offset)
+{
+ ZoneData *zone;
+ ZoneLineData zone_line;
+ TimeCode time_code;
+
+ zone_line.stdoff_seconds = parse_time (data,
+ data->fields[ZONE_GMTOFF + offset],
+ &time_code);
+ zone_line.save_seconds = parse_rules_save (data,
+ data->fields[ZONE_RULES_SAVE + offset],
+ &zone_line.rules);
+
+ if (!VzicPureOutput) {
+ /* We round the UTC offsets to the nearest minute, to be compatible with
+ Outlook. This also works with -ve numbers, I think.
+ -56 % 60 = -59. -61 % 60 = -1. */
+ if (zone_line.stdoff_seconds >= 0)
+ zone_line.stdoff_seconds += 30;
+ else
+ zone_line.stdoff_seconds -= 29;
+ zone_line.stdoff_seconds -= zone_line.stdoff_seconds % 60;
+
+ if (zone_line.save_seconds >= 0)
+ zone_line.save_seconds += 30;
+ else
+ zone_line.save_seconds -= 29;
+ zone_line.save_seconds -= zone_line.save_seconds % 60;
+ }
+
+ zone_line.format = g_strdup (data->fields[ZONE_FORMAT + offset]);
+
+ if (data->num_fields - offset >= 6) {
+ zone_line.until_set = TRUE;
+ zone_line.until_year = parse_year (data,
+ data->fields[ZONE_UNTIL_YEAR + offset],
+ FALSE, 0);
+ zone_line.until_month = parse_month (data,
+ data->fields[ZONE_UNTIL_MONTH + offset]);
+ zone_line.until_day_code = parse_day (data,
+ data->fields[ZONE_UNTIL_DAY + offset],
+ &zone_line.until_day_number,
+ &zone_line.until_day_weekday);
+ zone_line.until_time_seconds = parse_time (data,
+ data->fields[ZONE_UNTIL_TIME + offset],
+ &zone_line.until_time_code);
+
+ /* We also want to know the maximum year used in any UNTIL value, so we
+ know where to expand all the infinite Rule data to. */
+ if (zone_line.until_year != YEAR_MAXIMUM
+ && zone_line.until_year != YEAR_MINIMUM)
+ data->max_until_year = MAX (data->max_until_year, zone_line.until_year);
+
+ } else {
+ zone_line.until_set = FALSE;
+ }
+
+ /* Append it to the last Zone, since that is the one we are currently
+ reading. */
+ zone = &g_array_index (data->zone_data, ZoneData, data->zone_data->len - 1);
+ g_array_append_val (zone->zone_line_data, zone_line);
+
+ return zone_line.until_set;
+}
+
+
+static void
+parse_rule_line (ParsingData *data)
+{
+ GArray *rule_array;
+ RuleData rule;
+ char *name;
+ TimeCode time_code;
+
+ /* All 10 fields must be present. */
+ if (data->num_fields != 10) {
+ fprintf (stderr, "%s:%i: Invalid Rule line - %i fields.\n%s\n",
+ data->filename, data->line_number, data->num_fields,
+ data->line);
+ exit (1);
+ }
+
+ name = data->fields[RULE_NAME];
+
+ /* Create the GArray and add it to the hash table if it doesn't already
+ exist. */
+ rule_array = g_hash_table_lookup (data->rule_data, name);
+ if (!rule_array) {
+ rule_array = g_array_new (FALSE, FALSE, sizeof (RuleData));
+ g_hash_table_insert (data->rule_data, g_strdup (name), rule_array);
+ }
+
+ rule.from_year = parse_year (data, data->fields[RULE_FROM], FALSE, 0);
+ if (rule.from_year == YEAR_MAXIMUM) {
+ fprintf (stderr, "%s:%i: Invalid Rule FROM value: '%s'\n",
+ data->filename, data->line_number, data->fields[RULE_FROM]);
+ exit (1);
+ }
+
+ rule.to_year = parse_year (data, data->fields[RULE_TO], TRUE,
+ rule.from_year);
+ if (rule.to_year == YEAR_MINIMUM) {
+ fprintf (stderr, "%s:%i: Invalid Rule TO value: %s\n",
+ data->filename, data->line_number, data->fields[RULE_TO]);
+ exit (1);
+ }
+
+ if (!strcmp (data->fields[RULE_TYPE], "-"))
+ rule.type = NULL;
+ else {
+ printf ("Type: %s\n", data->fields[RULE_TYPE]);
+ rule.type = g_strdup (data->fields[RULE_TYPE]);
+ }
+
+ rule.in_month = parse_month (data, data->fields[RULE_IN]);
+ rule.on_day_code = parse_day (data, data->fields[RULE_ON],
+ &rule.on_day_number, &rule.on_day_weekday);
+ rule.at_time_seconds = parse_time (data, data->fields[RULE_AT],
+ &rule.at_time_code);
+ rule.save_seconds = parse_time (data, data->fields[RULE_SAVE], &time_code);
+
+ if (!strcmp (data->fields[RULE_LETTER_S], "-")) {
+ rule.letter_s = NULL;
+ } else {
+ rule.letter_s = g_strdup (data->fields[RULE_LETTER_S]);
+ }
+
+ rule.is_shallow_copy = FALSE;
+
+ g_array_append_val (rule_array, rule);
+}
+
+
+static void
+parse_link_line (ParsingData *data)
+{
+ char *from, *to, *old_from;
+ GList *zone_list;
+
+ /* We must have 3 fields for a Link. */
+ if (data->num_fields != 3) {
+ fprintf (stderr, "%s:%i: Invalid Rule line - %i fields.\n%s\n",
+ data->filename, data->line_number, data->num_fields,
+ data->line);
+ exit (1);
+ }
+
+ from = data->fields[LINK_FROM];
+ to = data->fields[LINK_TO];
+
+#if 0
+ printf ("LINK FROM: %s\tTO: %s\n", from, to);
+#endif
+
+ if (g_hash_table_lookup_extended (data->link_data, from,
+ (gpointer) &old_from,
+ (gpointer) &zone_list)) {
+ from = old_from;
+ } else {
+ from = g_strdup (from);
+ zone_list = NULL;
+ }
+
+ zone_list = g_list_prepend (zone_list, g_strdup (to));
+
+ g_hash_table_insert (data->link_data, from, zone_list);
+}
+
+
+static int
+parse_year (ParsingData *data,
+ char *field,
+ gboolean accept_only,
+ int only_value)
+{
+ int len, year = 0;
+ char *p;
+
+ if (!field) {
+ fprintf (stderr, "%s:%i: Missing year.\n%s\n", data->filename,
+ data->line_number, data->line);
+ exit (1);
+ }
+
+ len = strlen (field);
+ if (accept_only && !strncmp (field, "only", len))
+ return only_value;
+ if (len >= 2) {
+ if (!strncmp (field, "maximum", len))
+ return YEAR_MAXIMUM;
+ else if (!strncmp (field, "minimum", len))
+ return YEAR_MINIMUM;
+ }
+
+ for (p = field; *p; p++) {
+ if (*p < '0' || *p > '9') {
+ fprintf (stderr, "%s:%i: Invalid year: %s\n%s\n", data->filename,
+ data->line_number, field, data->line);
+ exit (1);
+ }
+
+ year = year * 10 + *p - '0';
+ }
+
+ if (year < 1000 || year > 2037) {
+ fprintf (stderr, "%s:%i: Strange year: %s\n%s\n", data->filename,
+ data->line_number, field, data->line);
+ exit (1);
+ }
+
+ return year;
+}
+
+
+/* Parses a month name, returning 0 (Jan) to 11 (Dec). */
+static int
+parse_month (ParsingData *data,
+ char *field)
+{
+ static char* months[] = { "january", "february", "march", "april", "may",
+ "june", "july", "august", "september", "october",
+ "november", "december" };
+ char *p;
+ int len, i;
+
+ /* If the field is missing, it must be the optional UNTIL month, so we return
+ 0 for January. */
+ if (!field)
+ return 0;
+
+ for (p = field, len = 0; *p; p++, len++) {
+ *p = tolower (*p);
+ }
+
+ for (i = 0; i < 12; i++) {
+ if (!strncmp (field, months[i], len))
+ return i;
+ }
+
+ fprintf (stderr, "%s:%i: Invalid month: %s\n%s\n", data->filename,
+ data->line_number, field, data->line);
+ exit (1);
+}
+
+
+/* Parses a day specifier, returning a code representing the type of match
+ together with a day of the month and a weekday number (0=Sun). */
+static DayCode
+parse_day (ParsingData *data,
+ char *field,
+ int *day,
+ int *weekday)
+{
+ char *day_part, *p;
+ DayCode day_code;
+
+ if (!field) {
+ *day = 1;
+ return DAY_SIMPLE;
+ }
+
+ *day = *weekday = 0;
+
+ if (!strncmp (field, "last", 4)) {
+ *weekday = parse_weekday (data, field + 4);
+ /* We set the day to the end of the month to make sorting Rules easy. */
+ *day = 31;
+ return DAY_LAST_WEEKDAY;
+ }
+
+ day_part = field;
+ day_code = DAY_SIMPLE;
+
+ for (p = field; *p; p++) {
+ if (*p == '<' || *p == '>') {
+ if (*(p + 1) == '=') {
+ day_code = (*p == '<') ? DAY_WEEKDAY_ON_OR_BEFORE
+ : DAY_WEEKDAY_ON_OR_AFTER;
+ *p = '\0';
+ *weekday = parse_weekday (data, field);
+ day_part = p + 2;
+ break;
+ }
+
+ fprintf (stderr, "%s:%i: Invalid day: %s\n%s\n", data->filename,
+ data->line_number, field, data->line);
+ exit (1);
+ }
+ }
+
+ for (p = day_part; *p; p++) {
+ if (*p < '0' || *p > '9') {
+ fprintf (stderr, "%s:%i: Invalid day: %s\n%s\n", data->filename,
+ data->line_number, field, data->line);
+ exit (1);
+ }
+
+ *day = *day * 10 + *p - '0';
+ }
+
+ if (*day < 1 || *day > 31) {
+ fprintf (stderr, "%s:%i: Invalid day: %s\n%s\n", data->filename,
+ data->line_number, field, data->line);
+ exit (1);
+ }
+
+ return day_code;
+}
+
+
+/* Parses a weekday name, returning 0 (Sun) to 6 (Sat). */
+static int
+parse_weekday (ParsingData *data,
+ char *field)
+{
+ static char* weekdays[] = { "sunday", "monday", "tuesday", "wednesday",
+ "thursday", "friday", "saturday" };
+ char *p;
+ int len, i;
+
+ for (p = field, len = 0; *p; p++, len++) {
+ *p = tolower (*p);
+ }
+
+ for (i = 0; i < 7; i++) {
+ if (!strncmp (field, weekdays[i], len))
+ return i;
+ }
+
+ fprintf (stderr, "%s:%i: Invalid weekday: %s\n%s\n", data->filename,
+ data->line_number, field, data->line);
+ exit (1);
+}
+
+
+/* Parses a time (hour + minute + second) and returns the result in seconds,
+ together with a time code specifying whether it is Wall clock time,
+ local standard time, or universal time.
+ The time can start with a '-' in which case it will be negative. */
+static int
+parse_time (ParsingData *data,
+ char *field,
+ TimeCode *time_code)
+{
+ char *p;
+ int hours = 0, minutes = 0, seconds = 0, result, negative = 0;
+
+ if (!field) {
+ *time_code = TIME_WALL;
+ return 0;
+ }
+
+ p = field;
+ if (*p == '-') {
+ p++;
+ negative = 1;
+ }
+
+ hours = parse_number (data, &p);
+
+ if (*p == ':') {
+ p++;
+ minutes = parse_number (data, &p);
+
+ if (*p == ':') {
+ p++;
+ seconds = parse_number (data, &p);
+ }
+ }
+
+ if (hours < 0 || hours > 24
+ || minutes < 0 || minutes > 59
+ || seconds < 0 || seconds > 59
+ || (hours == 24 && (minutes != 0 || seconds != 0))) {
+ fprintf (stderr, "%s:%i: Invalid time: %s\n%s\n", data->filename,
+ data->line_number, field, data->line);
+ exit (1);
+ }
+
+ if (hours == 24) {
+ hours = 23;
+ minutes = 59;
+ seconds = 59;
+ }
+
+#if 0
+ printf ("Time: %s -> %i:%02i:%02i\n", field, hours, minutes, seconds);
+#endif
+
+ result = hours * 3600 + minutes * 60 + seconds;
+ if (negative)
+ result = -result;
+
+ if (*p == '\0') {
+ *time_code = TIME_WALL;
+ return result;
+ }
+
+ if (*(p + 1) == '\0') {
+ if (*p == 'w') {
+ *time_code = TIME_WALL;
+ return result;
+ } else if (*p == 's') {
+ *time_code = TIME_STANDARD;
+ return result;
+ } else if (*p == 'u' || *p == 'g' || *p == 'z') {
+ *time_code = TIME_UNIVERSAL;
+ return result;
+ }
+ }
+
+ fprintf (stderr, "%s:%i: Invalid time: %s\n%s\n", data->filename,
+ data->line_number, field, data->line);
+ exit (1);
+}
+
+
+/* Parses a simple number and returns the result. The pointer argument
+ is moved to the first character after the number. */
+static int
+parse_number (ParsingData *data,
+ char **num)
+{
+ char *p;
+ int result;
+
+ p = *num;
+
+#if 0
+ printf ("In parse_number p:%s\n", p);
+#endif
+
+ if (*p < '0' || *p > '9') {
+ fprintf (stderr, "%s:%i: Invalid number: %s\n%s\n", data->filename,
+ data->line_number, *num, data->line);
+ exit (1);
+ }
+
+ result = *p++ - '0';
+
+ while (*p >= '0' && *p <= '9')
+ result = result * 10 + *p++ - '0';
+
+ *num = p;
+ return result;
+}
+
+
+static int
+parse_rules_save (ParsingData *data,
+ char *field,
+ char **rules)
+{
+ TimeCode time_code;
+
+ *rules = NULL;
+
+ /* Check for just "-". */
+ if (field[0] == '-' && field[1] == '\0')
+ return 0;
+
+ /* Check for a time to add to local standard time. We don't care about a
+ time code here, since it is just an offset. */
+ if (*field == '-' || (*field >= '0' && *field <= '9'))
+ return parse_time (data, field, &time_code);
+
+ /* It must be a rules name. */
+ *rules = g_strdup (field);
+ return 0;
+}
+
+
+
+
+
+GHashTable*
+parse_zone_tab (char *filename)
+{
+ GHashTable *zones_hash;
+ ZoneDescription *zone_desc;
+ FILE *fp;
+ char buf[4096];
+ gchar **fields, *zone_name, *latitude, *longitude, *p;
+
+
+ fp = fopen (filename, "r");
+ if (!fp) {
+ fprintf (stderr, "Couldn't open file: %s\n", filename);
+ exit (1);
+ }
+
+ zones_hash = g_hash_table_new (g_str_hash, g_str_equal);
+
+ while (fgets (buf, sizeof(buf), fp)) {
+ if (*buf == '#') continue;
+
+ g_strchomp (buf);
+ fields = g_strsplit (buf,"\t", 4);
+
+ if (strlen (fields[0]) != 2) {
+ fprintf (stderr, "Invalid zone description line: %s\n", buf);
+ exit (1);
+ }
+
+ zone_name = g_strdup (fields[2]);
+
+ zone_desc = g_new (ZoneDescription, 1);
+ zone_desc->country_code[0] = fields[0][0];
+ zone_desc->country_code[1] = fields[0][1];
+ zone_desc->comment = (fields[3] && fields[3][0]) ? g_strdup (fields[3])
+ : NULL;
+
+ /* Now parse the latitude and longitude. */
+ latitude = fields[1];
+ longitude = latitude + 1;
+ while (*longitude != '+' && *longitude != '-')
+ longitude++;
+
+ parse_coord (latitude, longitude - latitude, zone_desc->latitude);
+ parse_coord (longitude, strlen (longitude), zone_desc->longitude);
+
+ g_hash_table_insert (zones_hash, zone_name, zone_desc);
+
+#if 0
+ g_print ("Found zone: %s %i %02i %02i,%i %02i %02i\n", zone_name,
+ zone_desc->latitude[0], zone_desc->latitude[1],
+ zone_desc->latitude[2],
+ zone_desc->longitude[0], zone_desc->longitude[1],
+ zone_desc->longitude[2]);
+#endif
+ }
+
+ fclose (fp);
+
+ return zones_hash;
+}
+
+
+static void
+parse_coord (char *coord,
+ int len,
+ int *result)
+{
+ int degrees = 0, minutes = 0, seconds = 0;
+
+ if (len == 5)
+ sscanf (coord + 1, "%2d%2d", &degrees, &minutes);
+ else if (len == 6)
+ sscanf (coord + 1, "%3d%2d", &degrees, &minutes);
+ else if (len == 7)
+ sscanf (coord + 1, "%2d%2d%2d", &degrees, &minutes, &seconds);
+ else if (len == 8)
+ sscanf (coord + 1, "%3d%2d%2d", &degrees, &minutes, &seconds);
+ else {
+ fprintf (stderr, "Invalid coordinate: %s\n", coord);
+ exit (1);
+ }
+
+ if (coord[0] == '-')
+ degrees = -degrees;
+
+ result[0] = degrees;
+ result[1] = minutes;
+ result[2] = seconds;
+}
+