927 lines
26 KiB
C
927 lines
26 KiB
C
/*
|
|
* Vzic - a program to convert Olson timezone database files into VZTIMEZONE
|
|
* files compatible with the iCalendar specification (RFC2445).
|
|
*
|
|
* SPDX-FileCopyrightText: 2000-2001 Ximian, Inc.
|
|
* SPDX-FileCopyrightText: 2003, Damon Chaplin <damon@ximian.com>
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later
|
|
*
|
|
* Author: Damon Chaplin <damon@gnome.org>
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <ctype.h>
|
|
#include <limits.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <libgen.h>
|
|
|
|
#include "vzic.h"
|
|
#include "vzic-output.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(const ParsingData *data,
|
|
char *field,
|
|
gboolean accept_only,
|
|
int only_value);
|
|
static int parse_month(const ParsingData *data,
|
|
char *field);
|
|
static DayCode parse_day(const ParsingData *data,
|
|
char *field,
|
|
int *day,
|
|
int *weekday);
|
|
static int parse_weekday(const ParsingData *data,
|
|
char *field);
|
|
static int parse_time(const ParsingData *data,
|
|
char *field,
|
|
TimeCode *time_code);
|
|
static int parse_number(const ParsingData *data,
|
|
char **num);
|
|
static int parse_rules_save(const ParsingData *data,
|
|
char *field,
|
|
char **rules);
|
|
|
|
static void parse_coord(const 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;
|
|
|
|
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;
|
|
}
|
|
|
|
memset(data.buffer, 0, MAX_LINE_LEN);
|
|
snprintf(data.buffer, MAX_LINE_LEN, "%s", 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);
|
|
|
|
#ifdef VZIC_DEBUG_PRINT
|
|
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++;
|
|
}
|
|
}
|
|
|
|
#ifdef VZIC_DEBUG_PRINT
|
|
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);
|
|
}
|
|
|
|
/* We also want to know the maximum year used in any TO/FROM value, so we
|
|
know where to expand all the infinite Rule data to. */
|
|
if (rule.to_year != YEAR_MAXIMUM) {
|
|
data->max_until_year = MAX(data->max_until_year, rule.to_year);
|
|
} else if (rule.from_year != YEAR_MINIMUM) {
|
|
data->max_until_year = MAX(data->max_until_year, rule.from_year);
|
|
}
|
|
|
|
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)
|
|
{
|
|
const char *from;
|
|
char *to;
|
|
|
|
/* 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];
|
|
|
|
#ifdef VZIC_DEBUG_PRINT
|
|
printf("LINK FROM: %s\tTO: %s\n", from, to);
|
|
#endif
|
|
|
|
#if CREATE_SYMLINK
|
|
int len = strnlen(to, 254);
|
|
int dirs = 0;
|
|
int i;
|
|
for (i = 0; i < len; i++) {
|
|
dirs += to[i] == '/' ? 1 : 0;
|
|
}
|
|
if (dirs >= 0) {
|
|
char rel_from[255];
|
|
char to_dir[255];
|
|
char to_path[255];
|
|
if (dirs == 0) {
|
|
snprintf(rel_from, sizeof(rel_from), "%s.ics", from);
|
|
} else if (dirs == 1) {
|
|
snprintf(rel_from, sizeof(rel_from), "../%s.ics", from);
|
|
} else if (dirs == 2) {
|
|
snprintf(rel_from, sizeof(rel_from), "../../%s.ics", from);
|
|
} else {
|
|
return;
|
|
}
|
|
snprintf(to_path, sizeof(to_path), "%s/%s.ics", VzicOutputDir, to);
|
|
snprintf(to_dir, sizeof(to_dir), "%s", to_path);
|
|
ensure_directory_exists(dirname(to_dir));
|
|
//printf("Creating symlink from %s to %s\n", rel_from, to_path);
|
|
if (symlink(rel_from, to_path) != 0) {
|
|
fprintf(stderr, "Unable to create symbolic link from %s to %s: %s\n", rel_from, to_path, strerror(errno));
|
|
exit(1);
|
|
}
|
|
}
|
|
#else
|
|
GList *zone_list;
|
|
char *old_from;
|
|
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, (gpointer)from, zone_list);
|
|
#endif
|
|
}
|
|
|
|
static int
|
|
parse_year(const ParsingData *data,
|
|
char *field,
|
|
gboolean accept_only,
|
|
int only_value)
|
|
{
|
|
size_t len;
|
|
int 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 > 2100) {
|
|
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(const ParsingData *data,
|
|
char *field)
|
|
{
|
|
static const char *months[] = {"january", "february", "march", "april", "may",
|
|
"june", "july", "august", "september", "october",
|
|
"november", "december"};
|
|
char *p;
|
|
size_t len;
|
|
int 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(const ParsingData *data,
|
|
char *field,
|
|
int *day,
|
|
int *weekday)
|
|
{
|
|
char *day_part, *p;
|
|
DayCode day_code;
|
|
|
|
*day = *weekday = 0;
|
|
|
|
if (!field) {
|
|
*day = 1;
|
|
return DAY_SIMPLE;
|
|
}
|
|
|
|
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(const ParsingData *data,
|
|
char *field)
|
|
{
|
|
static const char *weekdays[] = {"sunday", "monday", "tuesday", "wednesday",
|
|
"thursday", "friday", "saturday"};
|
|
char *p;
|
|
size_t len;
|
|
int 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(const 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);
|
|
}
|
|
}
|
|
|
|
/* Japan can use a 30-hour clock, per
|
|
https://en.wikipedia.org/wiki/Date_and_time_notation_in_Japan */
|
|
if (hours < 0 || hours > 30 || 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);
|
|
}
|
|
|
|
#ifdef VZIC_DEBUG_PRINT
|
|
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(const ParsingData *data,
|
|
char **num)
|
|
{
|
|
char *p;
|
|
int result;
|
|
|
|
p = *num;
|
|
|
|
// potential null value where '-' is specified. assume zero.
|
|
if (!p || !*p) {
|
|
return 0;
|
|
}
|
|
|
|
#ifdef VZIC_DEBUG_PRINT
|
|
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(const 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(const char *filename)
|
|
{
|
|
GHashTable *zones_hash;
|
|
ZoneDescription *zone_desc;
|
|
FILE *fp;
|
|
char buf[4096];
|
|
gchar **fields, *zone_name, *latitude;
|
|
const gchar *longitude;
|
|
|
|
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);
|
|
|
|
#ifdef VZIC_DEBUG_PRINT
|
|
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(const char *coord,
|
|
int len,
|
|
int *result)
|
|
{
|
|
int degrees = 0, minutes = 0, seconds = 0;
|
|
|
|
if (len == 5) {
|
|
sscanf(coord + 1, "%2d%2d", °rees, &minutes);
|
|
} else if (len == 6) {
|
|
sscanf(coord + 1, "%3d%2d", °rees, &minutes);
|
|
} else if (len == 7) {
|
|
sscanf(coord + 1, "%2d%2d%2d", °rees, &minutes, &seconds);
|
|
} else if (len == 8) {
|
|
sscanf(coord + 1, "%3d%2d%2d", °rees, &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;
|
|
}
|