diff options
Diffstat (limited to 'libkcal/libical/vzic-1.3/vzic-output.c')
-rw-r--r-- | libkcal/libical/vzic-1.3/vzic-output.c | 2325 |
1 files changed, 0 insertions, 2325 deletions
diff --git a/libkcal/libical/vzic-1.3/vzic-output.c b/libkcal/libical/vzic-1.3/vzic-output.c deleted file mode 100644 index 87dd7529c..000000000 --- a/libkcal/libical/vzic-1.3/vzic-output.c +++ /dev/null @@ -1,2325 +0,0 @@ -/* - * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -/* ALGORITHM: - * - * First we expand all the Rule arrays, so that each element only represents 1 - * year. If a Rule extends to infinity we expand it up to a few years past the - * maximum UNTIL year used in any of the timezones. We do this to make sure - * that the last of the expanded Rules (which may be infinite) is only used - * in the last of the time periods (i.e. the last Zone line). - * - * The Rule arrays are also sorted by the start time (FROM + IN + ON + AT). - * Doing all this makes it much easier to find which rules apply to which - * periods. - * - * For each timezone (i.e. ZoneData element), we step through each of the - * time periods, the ZoneLineData elements (which represent each Zone line - * from the Olson file.) - * - * We calculate the start & end time of the period. - * - For the first line the start time is -infinity. - * - For the last line the end time is +infinity. - * - The end time of each line is also the start time of the next. - * - * We create an array of time changes which occur in this period, including - * the one implied by the Zone line itself (though this is later taken out - * if it is found to be at exactly the same time as the first Rule). - * - * Now we iterate over the time changes, outputting them as STANDARD or - * DAYLIGHT components. We also try to merge them together into RRULEs or - * use RDATEs. - */ - - -#include <errno.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <time.h> -#include <sys/stat.h> -#include <unistd.h> - -#include "vzic.h" -#include "vzic-output.h" - -#include "vzic-dump.h" - - -/* These come from the Makefile. See the comments there. */ -char *ProductID = PRODUCT_ID; -char *TZIDPrefix = TZID_PREFIX; - -/* We expand the TZIDPrefix, replacing %D with the date, in here. */ -char TZIDPrefixExpanded[1024]; - - -/* We only use RRULEs if there are at least MIN_RRULE_OCCURRENCES occurrences, - since otherwise RDATEs are more efficient. Actually, I've set this high - so we only use RRULEs for infinite recurrences. Since expanding RRULEs is - very time-consuming, this seems sensible. */ -#define MIN_RRULE_OCCURRENCES 100 - - -/* The year we go up to when dumping the list of timezone changes (used - for testing & debugging). */ -#define MAX_CHANGES_YEAR 2030 - -/* This is the maximum year that time_t value can typically hold on 32-bit - systems. */ -#define MAX_TIME_T_YEAR 2037 - - -/* The year we use to start RRULEs. */ -#define RRULE_START_YEAR 1970 - -/* The year we use for RDATEs. */ -#define RDATE_YEAR 1970 - - -static char *WeekDays[] = { "SU", "MO", "TU", "WE", "TH", "FR", "SA" }; -static int DaysInMonth[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; - -char *CurrentZoneName; - - -typedef struct _VzicTime VzicTime; -struct _VzicTime -{ - /* Normal years, e.g. 2001. */ - int year; - - /* 0 (Jan) to 11 (Dec). */ - int month; - - /* The day, either a simple month day number, 1-31, or a rule such as - the last Sunday, or the first Monday on or after the 8th. */ - DayCode day_code; - int day_number; /* 1 to 31. */ - int day_weekday; /* 0 (Sun) to 6 (Sat). */ - - /* The time, in seconds from midnight. The code specifies whether the - time is a wall clock time, local standard time, or universal time. */ - int time_seconds; - TimeCode time_code; - - /* The offset from UTC for local standard time. */ - int stdoff; - - /* The offset from UTC for local wall clock time. If this is different to - stdoff then this is a DAYLIGHT component. This is TZOFFSETTO. */ - int walloff; - - /* TRUE if the time change recurs every year to infinity. */ - gboolean is_infinite; - - /* TRUE if the change has already been output. */ - gboolean output; - - /* These are the offsets of the previous VzicTime, and are used when - calculating the time of the change. We place them here in - output_zone_components() to simplify the output code. */ - int prev_stdoff; - int prev_walloff; - - /* The abbreviated form of the timezone name. Note that this may not be - unique. */ - char *tzname; -}; - - -static void expand_and_sort_rule_array (gpointer key, - gpointer value, - gpointer data); -static int rule_sort_func (const void *arg1, - const void *arg2); -static void output_zone (char *directory, - ZoneData *zone, - char *zone_name, - GHashTable *rule_data); -static gboolean parse_zone_name (char *name, - char **directory, - char **subdirectory, - char **filename); -static void output_zone_to_files (ZoneData *zone, - char *zone_name, - GHashTable *rule_data, - FILE *fp, - FILE *changes_fp); -static gboolean add_rule_changes (ZoneLineData *zone_line, - char *zone_name, - GArray *changes, - GHashTable *rule_data, - VzicTime *start, - VzicTime *end, - char **start_letter_s, - int *save_seconds); -static char* expand_tzname (char *zone_name, - char *format, - gboolean have_letter_s, - char *letter_s, - gboolean is_daylight); -static int compare_times (VzicTime *time1, - int stdoff1, - int walloff1, - VzicTime *time2, - int stdoff2, - int walloff2); -static gboolean times_match (VzicTime *time1, - int stdoff1, - int walloff1, - VzicTime *time2, - int stdoff2, - int walloff2); -static void output_zone_components (FILE *fp, - char *name, - GArray *changes); -static void set_previous_offsets (GArray *changes); -static gboolean check_for_recurrence (FILE *fp, - GArray *changes, - int idx); -static void check_for_rdates (FILE *fp, - GArray *changes, - int idx); -static gboolean timezones_match (char *tzname1, - char *tzname2); -static int output_component_start (char *buffer, - VzicTime *vzictime, - gboolean output_rdate, - gboolean use_same_tz_offset); -static void output_component_end (FILE *fp, - VzicTime *vzictime); - -static void vzictime_init (VzicTime *vzictime); -static int calculate_actual_time (VzicTime *vzictime, - TimeCode time_code, - int stdoff, - int walloff); -static int calculate_wall_time (int time, - TimeCode time_code, - int stdoff, - int walloff, - int *day_offset); -static int calculate_until_time (int time, - TimeCode time_code, - int stdoff, - int walloff, - int *year, - int *month, - int *day); -static void fix_time_overflow (int *year, - int *month, - int *day, - int day_offset); - -static char* format_time (int year, - int month, - int day, - int time); -static char* format_tz_offset (int tz_offset, - gboolean round_seconds); -static gboolean output_rrule (char *rrule_buffer, - int month, - DayCode day_code, - int day_number, - int day_weekday, - int day_offset, - char *until); -static gboolean output_rrule_2 (char *buffer, - int month, - int day_number, - int day_weekday); - -static char* format_vzictime (VzicTime *vzictime); - -static void dump_changes (FILE *fp, - char *zone_name, - GArray *changes); -static void dump_change (FILE *fp, - char *zone_name, - VzicTime *vzictime, - int year); - -static void expand_tzid_prefix (void); - - -void -output_vtimezone_files (char *directory, - GArray *zone_data, - GHashTable *rule_data, - GHashTable *link_data, - int max_until_year) -{ - ZoneData *zone; - GList *links; - char *link_to; - int i; - - /* Insert today's date into the TZIDs we output. */ - expand_tzid_prefix (); - - /* Expand the rule data so that each entry specifies only one year, and - sort it so we can easily find the rules applicable to each Zone span. */ - g_hash_table_foreach (rule_data, expand_and_sort_rule_array, - GINT_TO_POINTER (max_until_year)); - - /* Output each timezone. */ - for (i = 0; i < zone_data->len; i++) { - zone = &g_array_index (zone_data, ZoneData, i); - output_zone (directory, zone, zone->zone_name, rule_data); - - /* Look for any links from this zone. */ - links = g_hash_table_lookup (link_data, zone->zone_name); - - while (links) { - link_to = links->data; - - /* We ignore Links that don't have a '/' in them (things like 'EST5EDT'). - */ - if (strchr (link_to, '/')) { - output_zone (directory, zone, link_to, rule_data); - } - - links = links->next; - } - } -} - - -static void -expand_and_sort_rule_array (gpointer key, - gpointer value, - gpointer data) -{ - char *name = key; - GArray *rule_array = value; - RuleData *rule, tmp_rule; - int len, max_year, i, from, to, year; - gboolean is_infinite; - - /* We expand the rule data to a year greater than any year used in a Zone - UNTIL value. This is so that we can easily get parts of the array to - use for each Zone line. */ - max_year = GPOINTER_TO_INT (data) + 2; - - /* If any of the rules apply to several years, we turn it into a single rule - for each year. If the Rule is infinite we go up to max_year. - We change the FROM field in the copies of the Rule, setting it to each - of the years, and set TO to FROM, except if TO was YEAR_MAXIMUM we set - the last TO to YEAR_MAXIMUM, so we still know the Rule is infinite. */ - len = rule_array->len; - for (i = 0; i < len; i++) { - rule = &g_array_index (rule_array, RuleData, i); - - /* None of the Rules currently use the TYPE field, but we'd better check. - */ - if (rule->type) { - fprintf (stderr, "Rules %s has a TYPE: %s\n", name, rule->type); - exit (1); - } - - if (rule->from_year != rule->to_year) { - from = rule->from_year; - to = rule->to_year; - - tmp_rule = *rule; - - /* Flag that this is a shallow copy so we don't free anything twice. */ - tmp_rule.is_shallow_copy = TRUE; - - /* See if it is an infinite Rule. */ - if (to == YEAR_MAXIMUM) { - is_infinite = TRUE; - to = max_year; - if (from < to) - rule->to_year = rule->from_year; - } else { - is_infinite = FALSE; - } - - /* Create a copy of the Rule for each year. */ - for (year = from + 1; year <= to; year++) { - tmp_rule.from_year = year; - - /* If the Rule is infinite, mark the last copy as infinite. */ - if (year == to && is_infinite) - tmp_rule.to_year = YEAR_MAXIMUM; - else - tmp_rule.to_year = year; - - g_array_append_val (rule_array, tmp_rule); - } - } - } - - /* Now sort the rules. */ - qsort (rule_array->data, rule_array->len, sizeof (RuleData), rule_sort_func); - -#if 0 - dump_rule_array (name, rule_array, stdout); -#endif -} - - -/* This is used to sort the rules, after the rules have all been expanded so - that each one is only for one year. */ -static int -rule_sort_func (const void *arg1, - const void *arg2) -{ - RuleData *rule1, *rule2; - int time1_year, time1_month, time1_day; - int time2_year, time2_month, time2_day; - int month_diff, result; - VzicTime t1, t2; - - rule1 = (RuleData*) arg1; - rule2 = (RuleData*) arg2; - - time1_year = rule1->from_year; - time1_month = rule1->in_month; - time2_year = rule2->from_year; - time2_month = rule2->in_month; - - /* If there is more that one month difference we don't need to calculate - the day or time. */ - month_diff = (time1_year - time2_year) * 12 + time1_month - time2_month; - - if (month_diff > 1) - return 1; - if (month_diff < -1) - return -1; - - /* Now we have to calculate the day and time of the Rule start and the - VzicTime, using the given offsets. */ - t1.year = time1_year; - t1.month = time1_month; - t1.day_code = rule1->on_day_code; - t1.day_number = rule1->on_day_number; - t1.day_weekday = rule1->on_day_weekday; - t1.time_code = rule1->at_time_code; - t1.time_seconds = rule1->at_time_seconds; - - t2.year = time2_year; - t2.month = time2_month; - t2.day_code = rule2->on_day_code; - t2.day_number = rule2->on_day_number; - t2.day_weekday = rule2->on_day_weekday; - t2.time_code = rule2->at_time_code; - t2.time_seconds = rule2->at_time_seconds; - - /* FIXME: We don't know the offsets yet, but I don't think any Rules are - close enough together that the offsets can make a difference. Should - check this. */ - calculate_actual_time (&t1, TIME_WALL, 0, 0); - calculate_actual_time (&t2, TIME_WALL, 0, 0); - - /* Now we can compare the entire time. */ - if (t1.year > t2.year) - result = 1; - else if (t1.year < t2.year) - result = -1; - - else if (t1.month > t2.month) - result = 1; - else if (t1.month < t2.month) - result = -1; - - else if (t1.day_number > t2.day_number) - result = 1; - else if (t1.day_number < t2.day_number) - result = -1; - - else if (t1.time_seconds > t2.time_seconds) - result = 1; - else if (t1.time_seconds < t2.time_seconds) - result = -1; - - else { - printf ("WARNING: Rule dates matched.\n"); - result = 0; - } - - return result; -} - - -static void -output_zone (char *directory, - ZoneData *zone, - char *zone_name, - GHashTable *rule_data) -{ - FILE *fp, *changes_fp = NULL; - char output_directory[PATHNAME_BUFFER_SIZE]; - char filename[PATHNAME_BUFFER_SIZE]; - char changes_filename[PATHNAME_BUFFER_SIZE]; - char *zone_directory, *zone_subdirectory, *zone_filename; - - /* Set a global for the zone_name, to be used only for debug messages. */ - CurrentZoneName = zone_name; - - /* Use this to only output a particular zone. */ -#if 0 - if (strcmp (zone_name, "Atlantic/Azores")) - return; -#endif - -#if 0 - printf ("Outputting Zone: %s\n", zone_name); -#endif - - if (!parse_zone_name (zone_name, &zone_directory, &zone_subdirectory, - &zone_filename)) - return; - - if (VzicDumpZoneNamesAndCoords) { - VzicTimeZoneNames = g_list_prepend (VzicTimeZoneNames, - g_strdup (zone_name)); - } - - sprintf (output_directory, "%s/%s", directory, zone_directory); - ensure_directory_exists (output_directory); - sprintf (filename, "%s/%s.ics", output_directory, zone_filename); - - if (VzicDumpChanges) { - sprintf (output_directory, "%s/ChangesVzic/%s", directory, zone_directory); - ensure_directory_exists (output_directory); - sprintf (changes_filename, "%s/%s", output_directory, zone_filename); - } - - if (zone_subdirectory) { - sprintf (output_directory, "%s/%s/%s", directory, zone_directory, - zone_subdirectory); - ensure_directory_exists (output_directory); - sprintf (filename, "%s/%s.ics", output_directory, zone_filename); - - if (VzicDumpChanges) { - sprintf (output_directory, "%s/ChangesVzic/%s/%s", directory, - zone_directory, zone_subdirectory); - ensure_directory_exists (output_directory); - sprintf (changes_filename, "%s/%s", output_directory, zone_filename); - } - } - - /* Create the files. */ - fp = fopen (filename, "w"); - if (!fp) { - fprintf (stderr, "Couldn't create file: %s\n", filename); - exit (1); - } - - if (VzicDumpChanges) { - changes_fp = fopen (changes_filename, "w"); - if (!changes_fp) { - fprintf (stderr, "Couldn't create file: %s\n", changes_filename); - exit (1); - } - } - - fprintf (fp, "BEGIN:VCALENDAR\nPRODID:%s\nVERSION:2.0\n", ProductID); - - output_zone_to_files (zone, zone_name, rule_data, fp, changes_fp); - - if (ferror (fp)) { - fprintf (stderr, "Error writing file: %s\n", filename); - exit (1); - } - - fprintf (fp, "END:VCALENDAR\n"); - - fclose (fp); - - g_free (zone_directory); - g_free (zone_subdirectory); - g_free (zone_filename); -} - - -/* This checks that the Zone name only uses the characters in [-+_/a-zA-Z0-9], - and outputs a warning if it isn't. */ -static gboolean -parse_zone_name (char *name, - char **directory, - char **subdirectory, - char **filename) -{ - static int invalid_zone_num = 1; - - char *p, ch, *first_slash_pos = NULL, *second_slash_pos = NULL; - gboolean invalid = FALSE; - - for (p = name; (ch = *p) != 0; p++) { - if ((ch < 'a' || ch > 'z') && (ch < 'A' || ch > 'Z') - && (ch < '0' || ch > '9') && ch != '/' && ch != '_' - && ch != '-' && ch != '+') { - fprintf (stderr, "WARNING: Unusual Zone name: %s\n", name); - invalid = TRUE; - break; - } - - if (ch == '/') { - if (!first_slash_pos) { - first_slash_pos = p; - } else if (!second_slash_pos) { - second_slash_pos = p; - } else { - fprintf (stderr, "WARNING: More than 2 '/' characters in Zone name: %s\n", name); - invalid = TRUE; - break; - } - } - } - - if (!first_slash_pos) { -#if 0 - fprintf (stderr, "No '/' character in Zone name: %s. Skipping.\n", name); -#endif - return FALSE; - } - - if (invalid) { - *directory = g_strdup ("Invalid"); - *filename = g_strdup_printf ("Zone%i", invalid_zone_num++); - } else { - *first_slash_pos = '\0'; - *directory = g_strdup (name); - *first_slash_pos = '/'; - - if (second_slash_pos) { - *second_slash_pos = '\0'; - *subdirectory = g_strdup (first_slash_pos + 1); - *second_slash_pos = '/'; - - *filename = g_strdup (second_slash_pos + 1); - } else { - *subdirectory = NULL; - *filename = g_strdup (first_slash_pos + 1); - } - } - - return invalid ? FALSE : TRUE; -} - - -static void -output_zone_to_files (ZoneData *zone, - char *zone_name, - GHashTable *rule_data, - FILE *fp, - FILE *changes_fp) -{ - ZoneLineData *zone_line; - GArray *changes; - int i, stdoff, walloff, start_index, save_seconds; - VzicTime start, end, *vzictime_start, *vzictime, *vzictime_first_rule_change; - gboolean is_daylight, found_letter_s; - char *start_letter_s; - - changes = g_array_new (FALSE, FALSE, sizeof (VzicTime)); - - vzictime_init (&start); - vzictime_init (&end); - - /* The first period starts at -infinity. */ - start.year = YEAR_MINIMUM; - - for (i = 0; i < zone->zone_line_data->len; i++) { - zone_line = &g_array_index (zone->zone_line_data, ZoneLineData, i); - - /* This is the local standard time offset from GMT for this period. */ - start.stdoff = stdoff = zone_line->stdoff_seconds; - start.walloff = walloff = stdoff + zone_line->save_seconds; - - if (zone_line->until_set) { - end.year = zone_line->until_year; - end.month = zone_line->until_month; - end.day_code = zone_line->until_day_code; - end.day_number = zone_line->until_day_number; - end.day_weekday = zone_line->until_day_weekday; - end.time_seconds = zone_line->until_time_seconds; - end.time_code = zone_line->until_time_code; - } else { - /* The last period ends at +infinity. */ - end.year = YEAR_MAXIMUM; - } - - /* Add a time change for the start of the period. This may be removed - later if one of the rules expands to exactly the same time. */ - start_index = changes->len; - g_array_append_val (changes, start); - - /* If there are Rules associated with this period, add all the relevant - time changes. */ - save_seconds = 0; - if (zone_line->rules) - found_letter_s = add_rule_changes (zone_line, zone_name, changes, - rule_data, &start, &end, - &start_letter_s, &save_seconds); - else - found_letter_s = FALSE; - - /* FIXME: I'm not really sure what to do about finding a LETTER_S for the - first part of the period (i.e. before the first Rule comes into effect). - Currently we try to use the same LETTER_S as the first Rule of the - period which is in local standard time. */ - if (zone_line->save_seconds) - save_seconds = zone_line->save_seconds; - is_daylight = save_seconds ? TRUE : FALSE; - vzictime_start = &g_array_index (changes, VzicTime, start_index); - walloff = vzictime_start->walloff = stdoff + save_seconds; - - /* TEST: See if the first Rule time is exactly the same as the change from - the Zone line. In which case we can remove the Zone line change. */ - if (changes->len > start_index + 1) { - int prev_stdoff, prev_walloff; - - if (start_index > 0) { - VzicTime *v = &g_array_index (changes, VzicTime, start_index - 1); - prev_stdoff = v->stdoff; - prev_walloff = v->walloff; - } else { - prev_stdoff = 0; - prev_walloff = 0; - } - vzictime_first_rule_change = &g_array_index (changes, VzicTime, - start_index + 1); - if (times_match (vzictime_start, prev_stdoff, prev_walloff, - vzictime_first_rule_change, stdoff, walloff)) { -#if 0 - printf ("Removing zone-line change (using new offsets)\n"); -#endif - g_array_remove_index (changes, start_index); - vzictime_start = NULL; - } else if (times_match (vzictime_start, prev_stdoff, prev_walloff, - vzictime_first_rule_change, prev_stdoff, prev_walloff)) { -#if 0 - printf ("Removing zone-line change (using previous offsets)\n"); -#endif - g_array_remove_index (changes, start_index); - vzictime_start = NULL; - } - } - - - if (vzictime_start) { - vzictime_start->tzname = expand_tzname (zone_name, zone_line->format, - found_letter_s, - start_letter_s, is_daylight); - } - - /* The start of the next Zone line is the end time of this one. */ - start = end; - } - - set_previous_offsets (changes); - - output_zone_components (fp, zone_name, changes); - - if (VzicDumpChanges) - dump_changes (changes_fp, zone_name, changes); - - /* Free all the TZNAME fields. */ - for (i = 0; i < changes->len; i++) { - vzictime = &g_array_index (changes, VzicTime, i); - g_free (vzictime->tzname); - } - - g_array_free (changes, TRUE); -} - - -/* This appends any timezone changes specified by the rules associated with - the timezone, that happen between the start and end times. - It returns the letter_s field of the first STANDARD rule found in the - search. We need this to fill in any %s in the FORMAT field of the first - component of the time period (the Zone line). */ -static gboolean -add_rule_changes (ZoneLineData *zone_line, - char *zone_name, - GArray *changes, - GHashTable *rule_data, - VzicTime *start, - VzicTime *end, - char **start_letter_s, - int *save_seconds) -{ - GArray *rule_array; - RuleData *rule, *prev_rule = NULL; - int stdoff, walloff, i, prev_stdoff, prev_walloff; - VzicTime vzictime; - gboolean is_daylight, found_start_letter_s = FALSE; - gboolean checked_for_previous = FALSE; - - *save_seconds = 0; - - rule_array = g_hash_table_lookup (rule_data, zone_line->rules); - if (!rule_array) { - fprintf (stderr, "Couldn't access rules: %s\n", zone_line->rules); - exit (1); - } - - /* The stdoff is the same for all the rules. */ - stdoff = start->stdoff; - - /* The walloff changes as we go through the rules. */ - walloff = start->walloff; - - /* Get the stdoff & walloff from the last change before this period. */ - if (changes->len >= 2) { - VzicTime *change = &g_array_index (changes, VzicTime, changes->len - 2); - prev_stdoff = change->stdoff; - prev_walloff = change->walloff; - } else { - prev_stdoff = prev_walloff = 0; - } - - - for (i = 0; i < rule_array->len; i++) { - rule = &g_array_index (rule_array, RuleData, i); - - is_daylight = rule->save_seconds != 0 ? TRUE : FALSE; - - vzictime_init (&vzictime); - vzictime.year = rule->from_year; - vzictime.month = rule->in_month; - vzictime.day_code = rule->on_day_code; - vzictime.day_number = rule->on_day_number; - vzictime.day_weekday = rule->on_day_weekday; - vzictime.time_seconds = rule->at_time_seconds; - vzictime.time_code = rule->at_time_code; - vzictime.stdoff = stdoff; - vzictime.walloff = stdoff + rule->save_seconds; - vzictime.is_infinite = (rule->to_year == YEAR_MAXIMUM) ? TRUE : FALSE; - - /* If the rule time is before the given start time, skip it. */ - if (compare_times (&vzictime, stdoff, walloff, - start, prev_stdoff, prev_walloff) < 0) - continue; - - /* If the previous Rule was a daylight Rule, then we may want to use the - walloff from that. */ - if (!checked_for_previous) { - checked_for_previous = TRUE; - if (i > 0) { - prev_rule = &g_array_index (rule_array, RuleData, i - 1); - if (prev_rule->save_seconds) { - walloff = start->walloff = stdoff + prev_rule->save_seconds; - *save_seconds = prev_rule->save_seconds; - found_start_letter_s = TRUE; - *start_letter_s = prev_rule->letter_s; -#if 0 - printf ("Could use save_seconds from previous Rule: %s\n", - zone_name); -#endif - } - } - } - - /* If an end time has been given, then if the rule time is on or after it - break out of the loop. */ - if (end->year != YEAR_MAXIMUM - && compare_times (&vzictime, stdoff, walloff, - end, stdoff, walloff) >= 0) - break; - - vzictime.tzname = expand_tzname (zone_name, zone_line->format, TRUE, - rule->letter_s, is_daylight); - - g_array_append_val (changes, vzictime); - - /* When we find the first STANDARD time we set letter_s. */ - if (!found_start_letter_s && !is_daylight) { - found_start_letter_s = TRUE; - *start_letter_s = rule->letter_s; - } - - /* Now that we have added the Rule, the new walloff comes into effect - for any following Rules. */ - walloff = vzictime.walloff; - } - - return found_start_letter_s; -} - - -/* This expands the Zone line FORMAT field, using the given LETTER_S from a - Rule line. There are 3 types of FORMAT field: - 1. a string with an %s in, e.g. "WE%sT". The %s is replaced with LETTER_S. - 2. a string with an '/' in, e.g. "CAT/CAWT". The first part is used for - standard time and the second part for when daylight-saving is in effect. - 3. a plain string, e.g. "LMT", which we leave as-is. - Note that (1) is the only type in which letter_s is required. -*/ -static char* -expand_tzname (char *zone_name, - char *format, - gboolean have_letter_s, - char *letter_s, - gboolean is_daylight) -{ - char *p, buffer[256], *guess = NULL; - int len; - -#if 0 - printf ("Expanding %s with %s\n", format, letter_s); -#endif - - if (!format || !format[0]) { - fprintf (stderr, "Missing FORMAT\n"); - exit (1); - } - - /* 1. Look for a "%s". */ - p = strchr (format, '%'); - if (p && *(p + 1) == 's') { - if (!have_letter_s) { - - /* NOTE: These are a few hard-coded TZNAMEs that I've looked up myself. - These are needed in a few places where a Zone line comes into effect - but no Rule has been found, so we have no LETTER_S to use. - I've tried to use whatever is the normal LETTER_S in the Rules for - the particular zone, in local standard time. */ - if (!strcmp (zone_name, "Asia/Macao") - && !strcmp (format, "C%sT")) - guess = "CST"; - else if (!strcmp (zone_name, "Asia/Macau") - && !strcmp (format, "C%sT")) - guess = "CST"; - else if (!strcmp (zone_name, "Asia/Ashgabat") - && !strcmp (format, "ASH%sT")) - guess = "ASHT"; - else if (!strcmp (zone_name, "Asia/Ashgabat") - && !strcmp (format, "TM%sT")) - guess = "TMT"; - else if (!strcmp (zone_name, "Asia/Samarkand") - && !strcmp (format, "TAS%sT")) - guess = "TAST"; - else if (!strcmp (zone_name, "Atlantic/Azores") - && !strcmp (format, "WE%sT")) - guess = "WET"; - else if (!strcmp (zone_name, "Europe/Paris") - && !strcmp (format, "WE%sT")) - guess = "WET"; - else if (!strcmp (zone_name, "Europe/Warsaw") - && !strcmp (format, "CE%sT")) - guess = "CET"; - else if (!strcmp (zone_name, "America/Phoenix") - && !strcmp (format, "M%sT")) - guess = "MST"; - else if (!strcmp (zone_name, "America/Nome") - && !strcmp (format, "Y%sT")) - guess = "YST"; - - if (guess) { -#if 0 - fprintf (stderr, - "WARNING: Couldn't find a LETTER_S to use in FORMAT: %s in Zone: %s Guessing: %s\n", - format, zone_name, guess); -#endif - return g_strdup (guess); - } - -#if 1 - fprintf (stderr, - "WARNING: Couldn't find a LETTER_S to use in FORMAT: %s in Zone: %s Leaving TZNAME empty\n", - format, zone_name); -#endif - -#if 0 - /* This is useful to spot exactly which component had a problem. */ - sprintf (buffer, "FIXME: %s", format); - return g_strdup (buffer); -#else - /* We give up and don't output a TZNAME. */ - return NULL; -#endif - } - - sprintf (buffer, format, letter_s ? letter_s : ""); - return g_strdup (buffer); - } - - /* 2. Look for a "/". */ - p = strchr (format, '/'); - if (p) { - if (is_daylight) { - return g_strdup (p + 1); - } else { - len = p - format; - strncpy (buffer, format, len); - buffer[len] = '\0'; - return g_strdup (buffer); - } - } - - /* 3. Just use format as it is. */ - return g_strdup (format); -} - - -/* Compares 2 VzicTimes, returning strcmp()-like values, i.e. 0 if equal, - 1 if the 1st is after the 2nd and -1 if the 1st is before the 2nd. */ -static int -compare_times (VzicTime *time1, - int stdoff1, - int walloff1, - VzicTime *time2, - int stdoff2, - int walloff2) -{ - VzicTime t1, t2; - int result; - - t1 = *time1; - t2 = *time2; - - calculate_actual_time (&t1, TIME_UNIVERSAL, stdoff1, walloff1); - calculate_actual_time (&t2, TIME_UNIVERSAL, stdoff2, walloff2); - - /* Now we can compare the entire time. */ - if (t1.year > t2.year) - result = 1; - else if (t1.year < t2.year) - result = -1; - - else if (t1.month > t2.month) - result = 1; - else if (t1.month < t2.month) - result = -1; - - else if (t1.day_number > t2.day_number) - result = 1; - else if (t1.day_number < t2.day_number) - result = -1; - - else if (t1.time_seconds > t2.time_seconds) - result = 1; - else if (t1.time_seconds < t2.time_seconds) - result = -1; - - else - result = 0; - -#if 0 - printf ("%i/%i/%i %i <=> %i/%i/%i %i -> %i\n", - t1.day_number, t1.month + 1, t1.year, t1.time_seconds, - t2.day_number, t2.month + 1, t2.year, t2.time_seconds, - result); -#endif - - return result; -} - - -/* Returns TRUE if the 2 times are exactly the same. It will calculate the - actual day, but doesn't convert times. */ -static gboolean -times_match (VzicTime *time1, - int stdoff1, - int walloff1, - VzicTime *time2, - int stdoff2, - int walloff2) -{ - VzicTime t1, t2; - - t1 = *time1; - t2 = *time2; - - calculate_actual_time (&t1, TIME_UNIVERSAL, stdoff1, walloff1); - calculate_actual_time (&t2, TIME_UNIVERSAL, stdoff2, walloff2); - - if (t1.year == t2.year - && t1.month == t2.month - && t1.day_number == t2.day_number - && t1.time_seconds == t2.time_seconds) - return TRUE; - - return FALSE; -} - - -static void -output_zone_components (FILE *fp, - char *name, - GArray *changes) -{ - VzicTime *vzictime; - int i, start_index = 0; - gboolean only_one_change = FALSE; - char start_buffer[1024]; - - fprintf (fp, "BEGIN:VTIMEZONE\nTZID:%s%s\n", TZIDPrefixExpanded, name); - - if (VzicUrlPrefix != NULL) - fprintf (fp, "TZURL:%s/%s\n", VzicUrlPrefix, name); - - /* We use an 'X-' property to place the city name in. */ - fprintf (fp, "X-LIC-LOCATION:%s\n", name); - - /* We try to find any recurring components first, or they may get output - as lots of RDATES instead. */ - if (!VzicNoRRules) { - int num_rrules_output = 0; - - for (i = 1; i < changes->len; i++) { - if (check_for_recurrence (fp, changes, i)) { - num_rrules_output++; - } - } - -#if 0 - printf ("Zone: %s had %i infinite RRULEs\n", CurrentZoneName, - num_rrules_output); -#endif - - if (!VzicPureOutput && num_rrules_output == 2) { -#if 0 - printf ("Zone: %s using 2 RRULEs\n", CurrentZoneName); -#endif - fprintf (fp, "END:VTIMEZONE\n"); - return; - } - } - - /* We skip the first change, which starts at -infinity, unless it is the only - change for the timezone. */ - if (changes->len > 1) - start_index = 1; - else - only_one_change = TRUE; - - /* For pure output, we start at the start of the array and step through it - outputting RDATEs. For Outlook-compatible output we start at the end - and step backwards to find the first STANDARD time to output. */ - if (VzicPureOutput) - i = start_index - 1; - else - i = changes->len; - - for (;;) { - if (VzicPureOutput) - i++; - else - i--; - - if (VzicPureOutput) { - if (i >= changes->len) - break; - } else { - if (i < start_index) - break; - } - - vzictime = &g_array_index (changes, VzicTime, i); - - /* If we have already output this component as part of an RRULE or RDATE, - then we skip it. */ - if (vzictime->output) - continue; - - /* For Outlook-compatible output we only want to output the last STANDARD - time as a DTSTART, so skip any DAYLIGHT changes. */ - if (!VzicPureOutput && vzictime->stdoff != vzictime->walloff) { - printf ("Skipping DAYLIGHT change\n"); - continue; - } - -#if 0 - printf ("Zone: %s using DTSTART Year: %i\n", CurrentZoneName, - vzictime->year); -#endif - - if (VzicPureOutput) { - output_component_start (start_buffer, vzictime, TRUE, only_one_change); - } else { - /* For Outlook compatability we don't output the RDATE and use the same - TZOFFSET for TZOFFSETFROM and TZOFFSETTO. */ - vzictime->year = RDATE_YEAR; - vzictime->month = 0; - vzictime->day_code = DAY_SIMPLE; - vzictime->day_number = 1; - vzictime->time_code = TIME_WALL; - vzictime->time_seconds = 0; - - output_component_start (start_buffer, vzictime, FALSE, TRUE); - } - - fprintf (fp, "%s", start_buffer); - - /* This will look for matching components and output them as RDATEs - instead of separate components. */ - if (VzicPureOutput && !VzicNoRDates) - check_for_rdates (fp, changes, i); - - output_component_end (fp, vzictime); - - vzictime->output = TRUE; - - if (!VzicPureOutput) - break; - } - - fprintf (fp, "END:VTIMEZONE\n"); -} - - -/* This sets the prev_stdoff and prev_walloff (i.e. the TZOFFSETFROM) of each - VzicTime, using the stdoff and walloff of the previous VzicTime. It makes - the rest of the code much simpler. */ -static void -set_previous_offsets (GArray *changes) -{ - VzicTime *vzictime, *prev_vzictime; - int i; - - prev_vzictime = &g_array_index (changes, VzicTime, 0); - prev_vzictime->prev_stdoff = 0; - prev_vzictime->prev_walloff = 0; - - for (i = 1; i < changes->len; i++) { - vzictime = &g_array_index (changes, VzicTime, i); - - vzictime->prev_stdoff = prev_vzictime->stdoff; - vzictime->prev_walloff = prev_vzictime->walloff; - - prev_vzictime = vzictime; - } -} - - -/* Returns TRUE if we output an infinite recurrence. */ -static gboolean -check_for_recurrence (FILE *fp, - GArray *changes, - int idx) -{ - VzicTime *vzictime_start, *vzictime, vzictime_start_copy; - gboolean is_daylight_start, is_daylight; - int last_match, i, next_year, day_offset; - char until[256], rrule_buffer[2048], start_buffer[1024]; - GList *matching_elements = NULL, *elem; - - vzictime_start = &g_array_index (changes, VzicTime, idx); - - /* If this change has already been output, skip it. */ - if (vzictime_start->output) - return FALSE; - - /* There can't possibly be an RRULE starting from YEAR_MINIMUM. */ - if (vzictime_start->year == YEAR_MINIMUM) - return FALSE; - - is_daylight_start = (vzictime_start->stdoff != vzictime_start->walloff) - ? TRUE : FALSE; - -#if 0 - printf ("\nChecking: %s OFFSETFROM: %i %s\n", - format_vzictime (vzictime_start), vzictime_start->prev_walloff, - is_daylight_start ? "DAYLIGHT" : ""); -#endif - - /* If this is an infinitely recurring change, output the RRULE and return. - There won't be any changes after it that we could merge. */ - if (vzictime_start->is_infinite) { - - /* Change the year to our minimum start year. */ - vzictime_start_copy = *vzictime_start; - if (!VzicPureOutput) - vzictime_start_copy.year = RRULE_START_YEAR; - - day_offset = output_component_start (start_buffer, &vzictime_start_copy, - FALSE, FALSE); - - if (!output_rrule (rrule_buffer, vzictime_start_copy.month, - vzictime_start_copy.day_code, - vzictime_start_copy.day_number, - vzictime_start_copy.day_weekday, day_offset, "")) { - if (vzictime_start->year != MAX_TIME_T_YEAR) { - fprintf (stderr, "WARNING: Failed to output infinite recurrence with start year: %i\n", vzictime_start->year); - } - return TRUE; - } - - fprintf (fp, "%s%s", start_buffer, rrule_buffer); - output_component_end (fp, vzictime_start); - vzictime_start->output = TRUE; - return TRUE; - } - - last_match = idx; - next_year = vzictime_start->year + 1; - for (i = idx + 1; i < changes->len; i++) { - vzictime = &g_array_index (changes, VzicTime, i); - - is_daylight = (vzictime->stdoff != vzictime->walloff) ? TRUE : FALSE; - - if (vzictime->output) - continue; - -#if 0 - printf (" %s OFFSETFROM: %i %s\n", - format_vzictime (vzictime), vzictime->prev_walloff, - is_daylight ? "DAYLIGHT" : ""); -#endif - - /* If it is more than one year ahead, we are finished, since we want - consecutive years. */ - if (vzictime->year > next_year) { - break; - } - - /* It must be the same type of component - STANDARD or DAYLIGHT. */ - if (is_daylight != is_daylight_start) { - continue; - } - - /* It must be the following year, with the same month, day & time. - It is possible that the time has a different code but does in fact - match when normalized, but we don't care (for now at least). */ - if (vzictime->year != next_year - || vzictime->month != vzictime_start->month - || vzictime->day_code != vzictime_start->day_code - || vzictime->day_number != vzictime_start->day_number - || vzictime->day_weekday != vzictime_start->day_weekday - || vzictime->time_seconds != vzictime_start->time_seconds - || vzictime->time_code != vzictime_start->time_code) { - continue; - } - - /* The TZOFFSETFROM and TZOFFSETTO must match. */ - if (vzictime->prev_walloff != vzictime_start->prev_walloff) { - continue; - } - - if (vzictime->walloff != vzictime_start->walloff) { - continue; - } - - /* TZNAME must match. */ - if (!timezones_match (vzictime->tzname, vzictime_start->tzname)) { - continue; - } - - /* We have a match. */ - last_match = i; - next_year = vzictime->year + 1; - - matching_elements = g_list_prepend (matching_elements, vzictime); - } - - if (last_match == idx) - return FALSE; - -#if 0 - printf ("Found recurrence %i - %i!!!\n", vzictime_start->year, - next_year - 1); -#endif - - vzictime = &g_array_index (changes, VzicTime, last_match); - -/* We only use RRULEs if there are at least MIN_RRULE_OCCURRENCES occurrences, - since otherwise RDATEs are more efficient. */ - if (!vzictime->is_infinite) { - int years = vzictime->year - vzictime_start->year + 1; -#if 0 - printf ("RRULE Years: %i\n", years); -#endif - if (years < MIN_RRULE_OCCURRENCES) - return FALSE; - } - - if (vzictime->is_infinite) { - until[0] = '\0'; - } else { - VzicTime t1 = *vzictime; - - printf ("RRULE with UNTIL - aborting\n"); - abort (); - - calculate_actual_time (&t1, TIME_UNIVERSAL, vzictime->prev_stdoff, - vzictime->prev_walloff); - - /* Output UNTIL, in UTC. */ - sprintf (until, ";UNTIL=%sZ", format_time (t1.year, t1.month, - t1.day_number, - t1.time_seconds)); - } - - /* Change the year to our minimum start year. */ - vzictime_start_copy = *vzictime_start; - if (!VzicPureOutput) - vzictime_start_copy.year = RRULE_START_YEAR; - - day_offset = output_component_start (start_buffer, &vzictime_start_copy, - FALSE, FALSE); - if (output_rrule (rrule_buffer, vzictime_start_copy.month, - vzictime_start_copy.day_code, - vzictime_start_copy.day_number, - vzictime_start_copy.day_weekday, day_offset, until)) { - fprintf (fp, "%s%s", start_buffer, rrule_buffer); - output_component_end (fp, vzictime_start); - - /* Mark all the changes as output. */ - vzictime_start->output = TRUE; - for (elem = matching_elements; elem; elem = elem->next) { - vzictime = elem->data; - vzictime->output = TRUE; - } - } - - g_list_free (matching_elements); - - return TRUE; -} - - -static void -check_for_rdates (FILE *fp, - GArray *changes, - int idx) -{ - VzicTime *vzictime_start, *vzictime, tmp_vzictime; - gboolean is_daylight_start, is_daylight; - int i, year, month, day, time; - - vzictime_start = &g_array_index (changes, VzicTime, idx); - - is_daylight_start = (vzictime_start->stdoff != vzictime_start->walloff) - ? TRUE : FALSE; - -#if 0 - printf ("\nChecking: %s OFFSETFROM: %i %s\n", - format_vzictime (vzictime_start), vzictime_start->prev_walloff, - is_daylight_start ? "DAYLIGHT" : ""); -#endif - - /* We want to go backwards through the array now, for Outlook compatability. - (It only looks at the first DTSTART/RDATE.) */ - for (i = idx + 1; i < changes->len; i++) { - vzictime = &g_array_index (changes, VzicTime, i); - - is_daylight = (vzictime->stdoff != vzictime->walloff) ? TRUE : FALSE; - - if (vzictime->output) - continue; - -#if 0 - printf (" %s OFFSETFROM: %i %s\n", format_vzictime (vzictime), - vzictime->prev_walloff, is_daylight ? "DAYLIGHT" : ""); -#endif - - /* It must be the same type of component - STANDARD or DAYLIGHT. */ - if (is_daylight != is_daylight_start) { - continue; - } - - /* The TZOFFSETFROM and TZOFFSETTO must match. */ - if (vzictime->prev_walloff != vzictime_start->prev_walloff) { - continue; - } - - if (vzictime->walloff != vzictime_start->walloff) { - continue; - } - - /* TZNAME must match. */ - if (!timezones_match (vzictime->tzname, vzictime_start->tzname)) { - continue; - } - - /* We have a match. */ - - tmp_vzictime = *vzictime; - calculate_actual_time (&tmp_vzictime, TIME_WALL, vzictime->prev_stdoff, - vzictime->prev_walloff); - - fprintf (fp, "RDATE:%s\n", format_time (tmp_vzictime.year, - tmp_vzictime.month, - tmp_vzictime.day_number, - tmp_vzictime.time_seconds)); - - vzictime->output = TRUE; - } -} - - -static gboolean -timezones_match (char *tzname1, - char *tzname2) -{ - if (tzname1 && tzname2 && !strcmp (tzname1, tzname2)) - return TRUE; - - if (!tzname1 && !tzname2) - return TRUE; - - return FALSE; -} - - -/* Outputs the start of a VTIMEZONE component, with the BEGIN line, - the DTSTART, TZOFFSETFROM, TZOFFSETTO & TZNAME properties. */ -static int -output_component_start (char *buffer, - VzicTime *vzictime, - gboolean output_rdate, - gboolean use_same_tz_offset) -{ - gboolean is_daylight, skip_day_offset = FALSE; - gint year, month, day, time, day_offset = 0; - GDate old_date, new_date; - char *formatted_time; - char line1[1024], line2[1024], line3[1024]; - char line4[1024], line5[1024], line6[1024]; - VzicTime tmp_vzictime; - int prev_walloff; - - is_daylight = (vzictime->stdoff != vzictime->walloff) ? TRUE : FALSE; - - tmp_vzictime = *vzictime; - day_offset = calculate_actual_time (&tmp_vzictime, TIME_WALL, - vzictime->prev_stdoff, - vzictime->prev_walloff); - - sprintf (line1, "BEGIN:%s\n", is_daylight ? "DAYLIGHT" : "STANDARD"); - - /* If the timezone only has one change, that means it uses the same offset - forever, so we use the same TZOFFSETFROM as the TZOFFSETTO. (If the zone - has more than one change, we don't output the first one.) */ - if (use_same_tz_offset) - prev_walloff = vzictime->walloff; - else - prev_walloff = vzictime->prev_walloff; - - sprintf (line2, "TZOFFSETFROM:%s\n", - format_tz_offset (prev_walloff, !VzicPureOutput)); - - sprintf (line3, "TZOFFSETTO:%s\n", - format_tz_offset (vzictime->walloff, !VzicPureOutput)); - - if (vzictime->tzname) - sprintf (line4, "TZNAME:%s\n", vzictime->tzname); - else - line4[0] = '\0'; - - formatted_time = format_time (tmp_vzictime.year, tmp_vzictime.month, - tmp_vzictime.day_number, - tmp_vzictime.time_seconds); - sprintf (line5, "DTSTART:%s\n", formatted_time); - if (output_rdate) - sprintf (line6, "RDATE:%s\n", formatted_time); - else - line6[0] = '\0'; - - sprintf (buffer, "%s%s%s%s%s%s", line1, line2, line3, line4, line5, line6); - - return day_offset; -} - - -/* Outputs the END line of the VTIMEZONE component. */ -static void -output_component_end (FILE *fp, - VzicTime *vzictime) -{ - gboolean is_daylight; - - is_daylight = (vzictime->stdoff != vzictime->walloff) ? TRUE : FALSE; - - fprintf (fp, "END:%s\n", is_daylight ? "DAYLIGHT" : "STANDARD"); -} - - -/* Initializes a VzicTime to 1st Jan in YEAR_MINIMUM at midnight, with all - offsets set to 0. */ -static void -vzictime_init (VzicTime *vzictime) -{ - vzictime->year = YEAR_MINIMUM; - vzictime->month = 0; - vzictime->day_code = DAY_SIMPLE; - vzictime->day_number = 1; - vzictime->day_weekday = 0; - vzictime->time_seconds = 0; - vzictime->time_code = TIME_UNIVERSAL; - vzictime->stdoff = 0; - vzictime->walloff = 0; - vzictime->is_infinite = FALSE; - vzictime->output = FALSE; - vzictime->prev_stdoff = 0; - vzictime->prev_walloff = 0; - vzictime->tzname = NULL; -} - - -/* This calculates the actual local time that a change will occur, given - the offsets from standard and wall-clock time. It returns -1 or 1 if it - had to move backwards or forwards one day while converting to local time. - If it does this then we need to change the RRULEs we output. */ -static int -calculate_actual_time (VzicTime *vzictime, - TimeCode time_code, - int stdoff, - int walloff) -{ - GDate date; - gint day_offset, days_in_month, weekday, offset, result; - - vzictime->time_seconds = calculate_wall_time (vzictime->time_seconds, - vzictime->time_code, - stdoff, walloff, &day_offset); - - if (vzictime->day_code != DAY_SIMPLE) { - if (vzictime->year == YEAR_MINIMUM || vzictime->year == YEAR_MAXIMUM) { - fprintf (stderr, "In calculate_actual_time: invalid year\n"); - exit (0); - } - - g_date_clear (&date, 1); - days_in_month = g_date_days_in_month (vzictime->month + 1, vzictime->year); - - /* Note that the day_code refers to the date before we convert it to - a wall-clock date and time. So we find the day it was referring to, - then make any adjustments needed due to converting the time. */ - if (vzictime->day_code == DAY_LAST_WEEKDAY) { - /* Find out what day the last day of the month is. */ - g_date_set_dmy (&date, days_in_month, vzictime->month + 1, - vzictime->year); - weekday = g_date_weekday (&date) % 7; - - /* Calculate how many days we have to go back to get to day_weekday. */ - offset = (weekday + 7 - vzictime->day_weekday) % 7; - - vzictime->day_number = days_in_month - offset; - } else { - /* Find out what day day_number actually is. */ - g_date_set_dmy (&date, vzictime->day_number, vzictime->month + 1, - vzictime->year); - weekday = g_date_weekday (&date) % 7; - - if (vzictime->day_code == DAY_WEEKDAY_ON_OR_AFTER) - offset = (vzictime->day_weekday + 7 - weekday) % 7; - else - offset = - ((weekday + 7 - vzictime->day_weekday) % 7); - - vzictime->day_number = vzictime->day_number + offset; - } - - vzictime->day_code = DAY_SIMPLE; - - if (vzictime->day_number <= 0 || vzictime->day_number > days_in_month) { - fprintf (stderr, "Day overflow: %i\n", vzictime->day_number); - exit (1); - } - } - -#if 0 - fprintf (stderr, "%s -> %i/%i/%i\n", - dump_day_coded (vzictime->day_code, vzictime->day_number, - vzictime->day_weekday), - vzictime->day_number, vzictime->month + 1, vzictime->year); -#endif - - fix_time_overflow (&vzictime->year, &vzictime->month, - &vzictime->day_number, day_offset); - - /* If we want UTC time, we have to convert it now. */ - if (time_code == TIME_UNIVERSAL) { - vzictime->time_seconds = calculate_until_time (vzictime->time_seconds, - TIME_WALL, stdoff, walloff, - &vzictime->year, - &vzictime->month, - &vzictime->day_number); - } - - return day_offset; -} - - -/* This converts the given time into universal time (UTC), to be used in - the UNTIL property. */ -static int -calculate_until_time (int time, - TimeCode time_code, - int stdoff, - int walloff, - int *year, - int *month, - int *day) -{ - int result, day_offset; - - day_offset = 0; - - switch (time_code) { - case TIME_WALL: - result = time - walloff; - break; - case TIME_STANDARD: - result = time - stdoff; - break; - case TIME_UNIVERSAL: - return time; - default: - fprintf (stderr, "Invalid time code\n"); - exit (1); - } - - if (result < 0) { - result += 24 * 60 * 60; - day_offset = -1; - } else if (result >= 24 * 60 * 60) { - result -= 24 * 60 * 60; - day_offset = 1; - } - - /* Sanity check - we shouldn't have an overflow any more. */ - if (result < 0 || result >= 24 * 60 * 60) { - fprintf (stderr, "Time overflow: %i\n", result); - abort (); - } - - fix_time_overflow (year, month, day, day_offset); - - return result; -} - - -/* This converts the given time into wall clock time (the local standard time - with any adjustment for daylight-saving). */ -static int -calculate_wall_time (int time, - TimeCode time_code, - int stdoff, - int walloff, - int *day_offset) -{ - int result; - - *day_offset = 0; - - switch (time_code) { - case TIME_WALL: - return time; - case TIME_STANDARD: - /* We have a local standard time, so we have to subtract stdoff to get - back to UTC, then add walloff to get wall time. */ - result = time - stdoff + walloff; - break; - case TIME_UNIVERSAL: - result = time + walloff; - break; - default: - fprintf (stderr, "Invalid time code\n"); - exit (1); - } - - if (result < 0) { - result += 24 * 60 * 60; - *day_offset = -1; - } else if (result >= 24 * 60 * 60) { - result -= 24 * 60 * 60; - *day_offset = 1; - } - - /* Sanity check - we shouldn't have an overflow any more. */ - if (result < 0 || result >= 24 * 60 * 60) { - fprintf (stderr, "Time overflow: %i\n", result); - exit (1); - } - -#if 0 - printf ("%s -> ", dump_time (time, time_code, TRUE)); - printf ("%s (%i)\n", dump_time (result, TIME_WALL, TRUE), *day_offset); -#endif - - return result; -} - - -static void -fix_time_overflow (int *year, - int *month, - int *day, - int day_offset) -{ - if (day_offset == -1) { - *day = *day - 1; - - if (*day == 0) { - *month = *month - 1; - if (*month == -1) { - *month = 11; - *year = *year - 1; - } - *day = g_date_days_in_month (*month + 1, *year); - } - } else if (day_offset == 1) { - *day = *day + 1; - - if (*day > g_date_days_in_month (*month + 1, *year)) { - *month = *month + 1; - if (*month == 12) { - *month = 0; - *year = *year + 1; - } - *day = 1; - } - } -} - - -static char* -format_time (int year, - int month, - int day, - int time) -{ - static char buffer[128]; - int hour, minute, second; - - /* When we are outputting the first component year will be YEAR_MINIMUM. - We used to use 1 when outputting this, but Outlook doesn't like any years - less that 1600, so we use 1600 instead. We don't output the first change - for most zones now, so it doesn't matter too much. */ - if (year == YEAR_MINIMUM) - year = 1601; - - /* We just use 9999 here, so we keep to 4 characters. But this should only - be needed when debugging - it shouldn't be needed in the VTIMEZONEs. */ - if (year == YEAR_MAXIMUM) { - fprintf (stderr, "format_time: YEAR_MAXIMUM used\n"); - year = 9999; - } - - hour = time / 3600; - minute = (time % 3600) / 60; - second = time % 60; - - sprintf (buffer, "%04i%02i%02iT%02i%02i%02i", - year, month + 1, day, hour, minute, second); - - return buffer; -} - - -/* Outlook doesn't support 6-digit values, i.e. including the seconds, so - we round to the nearest minute. No current offsets use the seconds value, - so we aren't losing much. */ -static char* -format_tz_offset (int tz_offset, - gboolean round_seconds) -{ - static char buffer[128]; - char *sign = "+"; - int hours, minutes, seconds; - - if (tz_offset < 0) { - tz_offset = -tz_offset; - sign = "-"; - } - - if (round_seconds) - tz_offset += 30; - - hours = tz_offset / 3600; - minutes = (tz_offset % 3600) / 60; - seconds = tz_offset % 60; - - if (round_seconds) - seconds = 0; - - /* Sanity check. Standard timezone offsets shouldn't be much more than 12 - hours, and daylight saving shouldn't change it by more than a few hours. - (The maximum offset is 15 hours 56 minutes at present.) */ - if (hours < 0 || hours >= 24 || minutes < 0 || minutes >= 60 - || seconds < 0 || seconds >= 60) { - fprintf (stderr, "WARNING: Strange timezone offset: H:%i M:%i S:%i\n", - hours, minutes, seconds); - } - - if (seconds == 0) - sprintf (buffer, "%s%02i%02i", sign, hours, minutes); - else - sprintf (buffer, "%s%02i%02i%02i", sign, hours, minutes, seconds); - - return buffer; -} - - -static gboolean -output_rrule (char *rrule_buffer, - int month, - DayCode day_code, - int day_number, - int day_weekday, - int day_offset, - char *until) -{ - char buffer[1024], buffer2[1024]; - - buffer[0] = '\0'; - - if (day_offset > 1 || day_offset < -1) { - fprintf (stderr, "Invalid day_offset: %i\n", day_offset); - exit (0); - } - - /* If the DTSTART time was moved to another day when converting to local - time, we need to adjust the RRULE accordingly. e.g. If the original RRULE - was on the 19th of the month, but DTSTART was moved 1 day forward, then - we output the 20th of the month instead. */ - if (day_offset == 1) { - if (day_code != DAY_LAST_WEEKDAY) - day_number++; - day_weekday = (day_weekday + 1) % 7; - - /* Check we don't use February 29th. */ - if (month == 1 && day_number > 28) { - fprintf (stderr, "Can't format RRULE - out of bounds. Month: %i Day number: %i\n", month + 1, day_number); - exit (0); - } - - /* If we go past the end of the month, move to the next month. */ - if (day_code != DAY_LAST_WEEKDAY && day_number > DaysInMonth[month]) { - month++; - day_number = 1; - } - - } else if (day_offset == -1) { - if (day_code != DAY_LAST_WEEKDAY) - day_number--; - day_weekday = (day_weekday + 6) % 7; - - if (day_code != DAY_LAST_WEEKDAY && day_number < 1) - fprintf (stderr, "Month: %i Day number: %i\n", month + 1, day_number); - } - - switch (day_code) { - case DAY_SIMPLE: - /* Outlook (2000) will not parse the simple YEARLY RRULEs in VTIMEZONEs, - or BYMONTHDAY, or BYYEARDAY, which makes this option difficult! - Currently we use something like BYDAY=1SU, which will be incorrect - at times. This only affects Asia/Baghdad, Asia/Gaza, Asia/Jerusalem & - Asia/Damascus at present (and Jerusalem doesn't have specific rules - at the moment anyway, so that isn't a big loss). */ - if (!VzicPureOutput) { - if (day_number < 8) { - printf ("WARNING: %s: Outputting BYDAY=1SU instead of BYMONTHDAY=1-7 for Outlook compatability\n", CurrentZoneName); - sprintf (buffer, "RRULE:FREQ=YEARLY;BYMONTH=%i;BYDAY=1SU", - month + 1); - } else if (day_number < 15) { - printf ("WARNING: %s: Outputting BYDAY=2SU instead of BYMONTHDAY=8-14 for Outlook compatability\n", CurrentZoneName); - sprintf (buffer, "RRULE:FREQ=YEARLY;BYMONTH=%i;BYDAY=2SU", - month + 1); - } else if (day_number < 22) { - printf ("WARNING: %s: Outputting BYDAY=3SU instead of BYMONTHDAY=15-21 for Outlook compatability\n", CurrentZoneName); - sprintf (buffer, "RRULE:FREQ=YEARLY;BYMONTH=%i;BYDAY=3SU", - month + 1); - } else { - printf ("ERROR: %s: Couldn't output RRULE (day=%i) compatible with Outlook\n", CurrentZoneName, day_number); - exit (1); - } - } else { - sprintf (buffer, "RRULE:FREQ=YEARLY"); - } - break; - - case DAY_WEEKDAY_ON_OR_AFTER: - if (day_number > DaysInMonth[month] - 6) { - /* This isn't actually needed at present. */ -#if 0 - fprintf (stderr, "DAY_WEEKDAY_ON_OR_AFTER: %i %i\n", day_number, - month + 1); -#endif - - if (!VzicPureOutput) { - printf ("ERROR: %s: Couldn't output RRULE (day>=x) compatible with Outlook\n", CurrentZoneName); - exit (1); - } else { - /* We do 6 days at the end of this month, and 1 at the start of the - next. We can't do this if we want Outlook compatability, as it - needs BYMONTHDAY, which Outlook doesn't support. */ - sprintf (buffer, - "RRULE:FREQ=YEARLY;BYMONTH=%i;BYMONTHDAY=%i,%i,%i,%i,%i,%i;BYDAY=%s", - month + 1, - day_number, day_number + 1, day_number + 2, day_number + 3, - day_number + 4, day_number + 5, - WeekDays[day_weekday]); - - sprintf (buffer2, - "RRULE:FREQ=YEARLY;BYMONTH=%i;BYMONTHDAY=1;BYDAY=%s", - (month + 1) % 12 + 1, - WeekDays[day_weekday]); - - sprintf (rrule_buffer, "%s%s\n%s%s\n", - buffer, until, buffer2, until); - - return TRUE; - } - } - - if (!output_rrule_2 (buffer, month, day_number, day_weekday)) - return FALSE; - - break; - - case DAY_WEEKDAY_ON_OR_BEFORE: - if (day_number < 7) { - /* FIXME: This is unimplemented, but it isn't needed at present anway. */ - fprintf (stderr, "DAY_WEEKDAY_ON_OR_BEFORE: %i. Unimplemented. Exiting...\n", day_number); - exit (0); - } - - if (!output_rrule_2 (buffer, month, day_number - 6, day_weekday)) - return FALSE; - - break; - - case DAY_LAST_WEEKDAY: - if (day_offset == 1) { - if (month == 1) { - fprintf (stderr, "DAY_LAST_WEEKDAY - day moved, in February - can't fix\n"); - exit (0); - } - - /* This is only used once at present, for Africa/Cairo. */ -#if 0 - fprintf (stderr, "DAY_LAST_WEEKDAY - day moved\n"); -#endif - - if (!VzicPureOutput) { - printf ("WARNING: %s: Modifying RRULE (last weekday) for Outlook compatability\n", CurrentZoneName); - sprintf (buffer, - "RRULE:FREQ=YEARLY;BYMONTH=%i;BYDAY=-1%s", - month + 1, WeekDays[day_weekday]); - printf (" Outputting: %s\n", buffer); - } else { - /* We do 6 days at the end of this month, and 1 at the start of the - next. We can't do this if we want Outlook compatability, as it needs - BYMONTHDAY, which Outlook doesn't support. */ - day_number = DaysInMonth[month]; - sprintf (buffer, - "RRULE:FREQ=YEARLY;BYMONTH=%i;BYMONTHDAY=%i,%i,%i,%i,%i,%i;BYDAY=%s", - month + 1, - day_number - 5, day_number - 4, day_number - 3, - day_number - 2, day_number - 1, day_number, - WeekDays[day_weekday]); - - sprintf (buffer2, - "RRULE:FREQ=YEARLY;BYMONTH=%i;BYMONTHDAY=1;BYDAY=%s", - (month + 1) % 12 + 1, - WeekDays[day_weekday]); - - sprintf (rrule_buffer, "%s%s\n%s%s\n", - buffer, until, buffer2, until); - - return TRUE; - } - - } else if (day_offset == -1) { - /* We do 7 days 1 day before the end of this month. */ - day_number = DaysInMonth[month]; - - if (!output_rrule_2 (buffer, month, day_number - 7, day_weekday)) - return FALSE; - - sprintf (rrule_buffer, "%s%s\n", buffer, until); - return TRUE; - } - - sprintf (buffer, - "RRULE:FREQ=YEARLY;BYMONTH=%i;BYDAY=-1%s", - month + 1, WeekDays[day_weekday]); - break; - - default: - fprintf (stderr, "Invalid day code\n"); - exit (1); - } - - sprintf (rrule_buffer, "%s%s\n", buffer, until); - return TRUE; -} - - -/* This tries to convert a RRULE like 'BYMONTHDAY=8,9,10,11,12,13,14;BYDAY=FR' - into 'BYDAY=2FR'. We need this since Outlook doesn't accept BYMONTHDAY. - It returns FALSE if conversion is not possible. */ -static gboolean -output_rrule_2 (char *buffer, - int month, - int day_number, - int day_weekday) -{ - - if (day_number == 1) { - /* Convert it to a BYDAY=1SU type of RRULE. */ - sprintf (buffer, "RRULE:FREQ=YEARLY;BYMONTH=%i;BYDAY=1%s", - month + 1, WeekDays[day_weekday]); - - } else if (day_number == 8) { - /* Convert it to a BYDAY=2SU type of RRULE. */ - sprintf (buffer, "RRULE:FREQ=YEARLY;BYMONTH=%i;BYDAY=2%s", - month + 1, WeekDays[day_weekday]); - - } else if (day_number == 15) { - /* Convert it to a BYDAY=3SU type of RRULE. */ - sprintf (buffer, "RRULE:FREQ=YEARLY;BYMONTH=%i;BYDAY=3%s", - month + 1, WeekDays[day_weekday]); - - } else if (day_number == 22) { - /* Convert it to a BYDAY=4SU type of RRULE. (Currently not used.) */ - sprintf (buffer, "RRULE:FREQ=YEARLY;BYMONTH=%i;BYDAY=4%s", - month + 1, WeekDays[day_weekday]); - - } else if (month != 1 && day_number == DaysInMonth[month] - 6) { - /* Convert it to a BYDAY=-1SU type of RRULE. (But never for February.) */ - sprintf (buffer, "RRULE:FREQ=YEARLY;BYMONTH=%i;BYDAY=-1%s", - month + 1, WeekDays[day_weekday]); - - } else { - /* Can't convert to a correct RRULE. If we want Outlook compatability we - have to use a slightly incorrect RRULE, so the time change will be 1 - week out every 7 or so years. Alternatively we could possibly move the - change by an hour or so so we would always be 1 or 2 hours out, but - never 1 week out. Yes, that sounds a better idea. */ - if (!VzicPureOutput) { - printf ("WARNING: %s: Modifying RRULE to be compatible with Outlook (day >= %i, month = %i)\n", CurrentZoneName, day_number, month + 1); - - if (day_number == 2) { - /* Convert it to a BYDAY=1SU type of RRULE. - This is needed for Asia/Karachi. */ - sprintf (buffer, "RRULE:FREQ=YEARLY;BYMONTH=%i;BYDAY=1%s", - month + 1, WeekDays[day_weekday]); - } else if (day_number == 9) { - /* Convert it to a BYDAY=2SU type of RRULE. - This is needed for Antarctica/Palmer & America/Santiago. */ - sprintf (buffer, "RRULE:FREQ=YEARLY;BYMONTH=%i;BYDAY=2%s", - month + 1, WeekDays[day_weekday]); - } else if (month != 1 && day_number == DaysInMonth[month] - 7) { - /* Convert it to a BYDAY=-1SU type of RRULE. (But never for February.) - This is needed for America/Godthab. */ - sprintf (buffer, "RRULE:FREQ=YEARLY;BYMONTH=%i;BYDAY=-1%s", - month + 1, WeekDays[day_weekday]); - } else { - printf ("ERROR: %s: Couldn't modify RRULE to be compatible with Outlook (day >= %i, month = %i)\n", CurrentZoneName, day_number, month + 1); - exit (1); - } - - } else { - sprintf (buffer, - "RRULE:FREQ=YEARLY;BYMONTH=%i;BYMONTHDAY=%i,%i,%i,%i,%i,%i,%i;BYDAY=%s", - month + 1, - day_number, day_number + 1, day_number + 2, day_number + 3, - day_number + 4, day_number + 5, day_number + 6, - WeekDays[day_weekday]); - } - } - - return TRUE; -} - - -static char* -format_vzictime (VzicTime *vzictime) -{ - static char buffer[1024]; - - sprintf (buffer, "%s %2i %s %s %i %i %s", - dump_year (vzictime->year), vzictime->month + 1, - dump_day_coded (vzictime->day_code, vzictime->day_number, - vzictime->day_weekday), - dump_time (vzictime->time_seconds, vzictime->time_code, TRUE), - vzictime->stdoff, vzictime->walloff, - vzictime->is_infinite ? "INFINITE" : ""); - - return buffer; -} - - -static void -dump_changes (FILE *fp, - char *zone_name, - GArray *changes) -{ - VzicTime *vzictime, *vzictime2 = NULL; - int i, year_offset, year; - - for (i = 0; i < changes->len; i++) { - vzictime = &g_array_index (changes, VzicTime, i); - - if (vzictime->year > MAX_CHANGES_YEAR) - return; - - dump_change (fp, zone_name, vzictime, vzictime->year); - } - - if (changes->len < 2) - return; - - /* Now see if the changes array ends with a pair of recurring changes. */ - vzictime = &g_array_index (changes, VzicTime, changes->len - 2); - vzictime2 = &g_array_index (changes, VzicTime, changes->len - 1); - if (!vzictime->is_infinite || !vzictime2->is_infinite) - return; - - year_offset = 1; - for (;;) { - year = vzictime->year + year_offset; - if (year > MAX_CHANGES_YEAR) - break; - dump_change (fp, zone_name, vzictime, year); - - year = vzictime2->year + year_offset; - if (year > MAX_CHANGES_YEAR) - break; - dump_change (fp, zone_name, vzictime2, year); - - year_offset++; - } -} - - -static void -dump_change (FILE *fp, - char *zone_name, - VzicTime *vzictime, - int year) -{ - int hour, minute, second; - VzicTime tmp_vzictime; - static char *months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", - "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; - - /* Output format is: - - Zone-Name [tab] Date [tab] Time [tab] UTC-Offset - - The Date and Time fields specify the time change in UTC. - - The UTC Offset is for local (wall-clock) time. It is the amount of time - to add to UTC to get local time. - */ - - fprintf (fp, "%s\t", zone_name); - - if (year == YEAR_MINIMUM) { - fprintf (fp, " 1 Jan 0001\t 0:00:00", zone_name); - } else if (year == YEAR_MAXIMUM) { - fprintf (stderr, "Maximum year found in change time\n"); - exit (1); - } else { - tmp_vzictime = *vzictime; - tmp_vzictime.year = year; - calculate_actual_time (&tmp_vzictime, TIME_UNIVERSAL, - vzictime->prev_stdoff, vzictime->prev_walloff); - - hour = tmp_vzictime.time_seconds / 3600; - minute = (tmp_vzictime.time_seconds % 3600) / 60; - second = tmp_vzictime.time_seconds % 60; - - fprintf (fp, "%2i %s %04i\t%2i:%02i:%02i", - tmp_vzictime.day_number, months[tmp_vzictime.month], - tmp_vzictime.year, hour, minute, second); - } - - fprintf (fp, "\t%s", format_tz_offset (vzictime->walloff, FALSE)); - - fprintf (fp, "\n"); -} - - -void -ensure_directory_exists (char *directory) -{ - struct stat filestat; - - if (stat (directory, &filestat) != 0) { - /* If the directory doesn't exist, try to create it. */ - if (errno == ENOENT) { - if (mkdir (directory, 0777) != 0) { - fprintf (stderr, "Can't create directory: %s\n", directory); - exit (1); - } - } else { - fprintf (stderr, "Error calling stat() on directory: %s\n", directory); - exit (1); - } - } else if (!S_ISDIR (filestat.st_mode)) { - fprintf (stderr, "Can't create directory, already exists: %s\n", - directory); - exit (1); - } -} - - -static void -expand_tzid_prefix (void) -{ - char *src, *dest; - char date_buf[16]; - char ch1, ch2; - time_t t; - struct tm *tm; - - /* Get today's date as a string in the format "YYYYMMDD". */ - t = time (NULL); - tm = localtime (&t); - sprintf (date_buf, "%4i%02i%02i", tm->tm_year + 1900, - tm->tm_mon + 1, tm->tm_mday); - - src = TZIDPrefix; - dest = TZIDPrefixExpanded; - - while (ch1 = *src++) { - - /* Look for a '%'. */ - if (ch1 == '%') { - ch2 = *src++; - - if (ch2 == 'D') { - /* '%D' gets expanded into the date string. */ - strcpy (dest, date_buf); - dest += strlen (dest); - } else if (ch2 == '%') { - /* '%%' gets converted into one '%'. */ - *dest++ = '%'; - } else { - /* Anything else is output as is. */ - *dest++ = '%'; - *dest++ = ch2; - } - } else { - *dest++ = ch1; - } - } - -#if 0 - printf ("TZID : %s\n", TZIDPrefix); - printf ("Expanded: %s\n", TZIDPrefixExpanded); -#endif -} |