2001-10-27 Damon Chaplin <damon@ximian.com> * gui/print.c (print_week_view): (range_selector_new): when the week start day is set to Sunday, we have to be careful to make sure we print the correct week, since the previous Saturday is actually printed first. Fixes bug #13687. (print_week_summary): always set compress_weekend to true if multi_week_view is FALSE (i.e. we are printing the week view). Fixes bug #13688. * gui/e-itip-control.c (send_freebusy): use the timezones from the DTSTART and DTEND. (write_label_piece): output the date-time and the timezone after it. Note that we may want to convert it to the current timezone and display that as well. Also converted COMPLETED to the current timezone. And fixed all uses of old timezone functions. * gui/dialogs/comp-editor.c (commit_all_fields): added function to set the focus in the window to NULL, so all fields lose their focus, so they emit "changed" signals and update their values if needed. We call this when most menu commands are used, e.g. 'Save and Close', 'Print' etc. Fixes bug #11434. In future we should also check fields are valid and show dialogs if they are not. * gui/calendar-model.c (get_completed): use the completed value properly. Fixes bug #13694. * cal-util/timeutil.c (icaltimetype_to_tm_with_zone): don't check from_zone and to_zone != NULL. A NULL zone is valid, it is for floating times. svn path=/trunk/; revision=14266
601 lines
15 KiB
C
601 lines
15 KiB
C
/* Miscellaneous time-related utilities
|
||
*
|
||
* Copyright (C) 1998 The Free Software Foundation
|
||
* Copyright (C) 2000 Ximian, Inc.
|
||
*
|
||
* Authors: Federico Mena <federico@ximian.com>
|
||
* Miguel de Icaza <miguel@ximian.com>
|
||
* Damon Chaplin <damon@ximian.com>
|
||
*/
|
||
|
||
#include <string.h>
|
||
#include <ctype.h>
|
||
#include <glib.h>
|
||
#include <ical.h>
|
||
#include "timeutil.h"
|
||
|
||
|
||
|
||
#define REFORMATION_DAY 639787 /* First day of the reformation, counted from 1 Jan 1 */
|
||
#define MISSING_DAYS 11 /* They corrected out 11 days */
|
||
#define THURSDAY 4 /* First day of reformation */
|
||
#define SATURDAY 6 /* Offset value; 1 Jan 1 was a Saturday */
|
||
|
||
|
||
/* Number of days in a month, using 0 (Jan) to 11 (Dec). For leap years,
|
||
add 1 to February (month 1). */
|
||
static const int days_in_month[12] = {
|
||
31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
|
||
};
|
||
|
||
|
||
|
||
/**************************************************************************
|
||
* time_t manipulation functions.
|
||
*
|
||
* NOTE: these use the Unix timezone functions like mktime() and localtime()
|
||
* and so should not be used in Evolution. New Evolution code should use
|
||
* icaltimetype values rather than time_t values wherever possible.
|
||
**************************************************************************/
|
||
|
||
static void
|
||
print_time_t (time_t t)
|
||
{
|
||
struct tm *tm = localtime (&t);
|
||
|
||
printf ("%d/%02d/%02d %02d:%02d:%02d",
|
||
1900 + tm->tm_year, tm->tm_mon+1, tm->tm_mday,
|
||
tm->tm_hour, tm->tm_min, tm->tm_sec);
|
||
}
|
||
|
||
/* Adds a day onto the time, using local time.
|
||
Note that if clocks go forward due to daylight savings time, there are
|
||
some non-existent local times, so the hour may be changed to make it a
|
||
valid time. This also means that it may not be wise to keep calling
|
||
time_add_day() to step through a certain period - if the hour gets changed
|
||
to make it valid time, any further calls to time_add_day() will also return
|
||
this hour, which may not be what you want. */
|
||
time_t
|
||
time_add_day (time_t time, int days)
|
||
{
|
||
struct tm *tm = localtime (&time);
|
||
time_t new_time;
|
||
|
||
tm->tm_mday += days;
|
||
tm->tm_isdst = -1;
|
||
|
||
if ((new_time = mktime (tm)) == -1) {
|
||
g_message ("time_add_day(): mktime() could not handling adding %d days with\n",
|
||
days);
|
||
print_time_t (time);
|
||
printf ("\n");
|
||
return time;
|
||
}
|
||
|
||
return new_time;
|
||
}
|
||
|
||
time_t
|
||
time_add_week (time_t time, int weeks)
|
||
{
|
||
return time_add_day (time, weeks * 7);
|
||
}
|
||
|
||
/* Returns the start of the day, according to the local time. */
|
||
time_t
|
||
time_day_begin (time_t t)
|
||
{
|
||
struct tm tm;
|
||
|
||
tm = *localtime (&t);
|
||
tm.tm_hour = 0;
|
||
tm.tm_min = 0;
|
||
tm.tm_sec = 0;
|
||
tm.tm_isdst = -1;
|
||
|
||
return mktime (&tm);
|
||
}
|
||
|
||
/* Returns the end of the day, according to the local time. */
|
||
time_t
|
||
time_day_end (time_t t)
|
||
{
|
||
struct tm tm;
|
||
|
||
tm = *localtime (&t);
|
||
tm.tm_mday++;
|
||
tm.tm_hour = 0;
|
||
tm.tm_min = 0;
|
||
tm.tm_sec = 0;
|
||
tm.tm_isdst = -1;
|
||
|
||
return mktime (&tm);
|
||
}
|
||
|
||
|
||
/**************************************************************************
|
||
* time_t manipulation functions, using timezones in libical.
|
||
*
|
||
* NOTE: these are only here to make the transition to the timezone
|
||
* functions easier. New code should use icaltimetype values rather than
|
||
* time_t values wherever possible.
|
||
**************************************************************************/
|
||
|
||
|
||
/* Adds or subtracts a number of days to/from the given time_t value, using
|
||
the given timezone.
|
||
NOTE: this function is only here to make the transition to the timezone
|
||
functions easier. New code should use icaltimetype values and
|
||
icaltime_adjust() to add or subtract days, hours, minutes & seconds. */
|
||
time_t
|
||
time_add_day_with_zone (time_t time, int days, icaltimezone *zone)
|
||
{
|
||
struct icaltimetype tt;
|
||
|
||
/* Convert to an icaltimetype. */
|
||
tt = icaltime_from_timet_with_zone (time, FALSE, zone);
|
||
|
||
/* Add/subtract the number of days. */
|
||
icaltime_adjust (&tt, days, 0, 0, 0);
|
||
|
||
/* Convert back to a time_t. */
|
||
return icaltime_as_timet_with_zone (tt, zone);
|
||
}
|
||
|
||
|
||
/* Adds or subtracts a number of weeks to/from the given time_t value, using
|
||
the given timezone.
|
||
NOTE: this function is only here to make the transition to the timezone
|
||
functions easier. New code should use icaltimetype values and
|
||
icaltime_adjust() to add or subtract days, hours, minutes & seconds. */
|
||
time_t
|
||
time_add_week_with_zone (time_t time, int weeks, icaltimezone *zone)
|
||
{
|
||
return time_add_day_with_zone (time, weeks * 7, zone);
|
||
}
|
||
|
||
|
||
/* Adds or subtracts a number of months to/from the given time_t value, using
|
||
the given timezone.
|
||
|
||
If the day would be off the end of the month (e.g. adding 1 month to
|
||
30th January, would lead to an invalid day, 30th February), it moves it
|
||
down to the last day in the month, e.g. 28th Feb (or 29th in a leap year.)
|
||
|
||
NOTE: this function is only here to make the transition to the timezone
|
||
functions easier. New code should use icaltimetype values and
|
||
icaltime_adjust() to add or subtract days, hours, minutes & seconds. */
|
||
time_t
|
||
time_add_month_with_zone (time_t time, int months, icaltimezone *zone)
|
||
{
|
||
struct icaltimetype tt;
|
||
int day, days_in_month;
|
||
|
||
/* Convert to an icaltimetype. */
|
||
tt = icaltime_from_timet_with_zone (time, FALSE, zone);
|
||
|
||
/* Add on the number of months. */
|
||
tt.month += months;
|
||
|
||
/* Save the day, and set it to 1, so we don't overflow into the next
|
||
month. */
|
||
day = tt.day;
|
||
tt.day = 1;
|
||
|
||
/* Normalize it, fixing any month overflow. */
|
||
tt = icaltime_normalize (tt);
|
||
|
||
/* If we go past the end of a month, set it to the last day. */
|
||
days_in_month = time_days_in_month (tt.year, tt.month - 1);
|
||
if (day > days_in_month)
|
||
day = days_in_month;
|
||
|
||
tt.day = day;
|
||
|
||
/* Convert back to a time_t. */
|
||
return icaltime_as_timet_with_zone (tt, zone);
|
||
}
|
||
|
||
|
||
/* Returns the start of the year containing the given time_t, using the given
|
||
timezone.
|
||
NOTE: this function is only here to make the transition to the timezone
|
||
functions easier. New code should use icaltimetype values and
|
||
icaltime_adjust() to add or subtract days, hours, minutes & seconds. */
|
||
time_t
|
||
time_year_begin_with_zone (time_t time, icaltimezone *zone)
|
||
{
|
||
struct icaltimetype tt;
|
||
|
||
/* Convert to an icaltimetype. */
|
||
tt = icaltime_from_timet_with_zone (time, FALSE, zone);
|
||
|
||
/* Set it to the start of the year. */
|
||
tt.month = 1;
|
||
tt.day = 1;
|
||
tt.hour = 0;
|
||
tt.minute = 0;
|
||
tt.second = 0;
|
||
|
||
/* Convert back to a time_t. */
|
||
return icaltime_as_timet_with_zone (tt, zone);
|
||
}
|
||
|
||
|
||
/* Returns the start of the month containing the given time_t, using the given
|
||
timezone.
|
||
NOTE: this function is only here to make the transition to the timezone
|
||
functions easier. New code should use icaltimetype values and
|
||
icaltime_adjust() to add or subtract days, hours, minutes & seconds. */
|
||
time_t
|
||
time_month_begin_with_zone (time_t time, icaltimezone *zone)
|
||
{
|
||
struct icaltimetype tt;
|
||
|
||
/* Convert to an icaltimetype. */
|
||
tt = icaltime_from_timet_with_zone (time, FALSE, zone);
|
||
|
||
/* Set it to the start of the month. */
|
||
tt.day = 1;
|
||
tt.hour = 0;
|
||
tt.minute = 0;
|
||
tt.second = 0;
|
||
|
||
/* Convert back to a time_t. */
|
||
return icaltime_as_timet_with_zone (tt, zone);
|
||
}
|
||
|
||
|
||
/* Returns the start of the week containing the given time_t, using the given
|
||
timezone. week_start_day should use the same values as mktime(),
|
||
i.e. 0 (Sun) to 6 (Sat).
|
||
NOTE: this function is only here to make the transition to the timezone
|
||
functions easier. New code should use icaltimetype values and
|
||
icaltime_adjust() to add or subtract days, hours, minutes & seconds. */
|
||
time_t
|
||
time_week_begin_with_zone (time_t time, int week_start_day, icaltimezone *zone)
|
||
{
|
||
struct icaltimetype tt;
|
||
int weekday, offset;
|
||
|
||
/* Convert to an icaltimetype. */
|
||
tt = icaltime_from_timet_with_zone (time, FALSE, zone);
|
||
|
||
/* Get the weekday. */
|
||
weekday = time_day_of_week (tt.day, tt.month - 1, tt.year);
|
||
|
||
/* Calculate the current offset from the week start day. */
|
||
offset = (weekday + 7 - week_start_day) % 7;
|
||
|
||
/* Set it to the start of the month. */
|
||
tt.day -= offset;
|
||
tt.hour = 0;
|
||
tt.minute = 0;
|
||
tt.second = 0;
|
||
|
||
/* Normalize it, to fix any overflow. */
|
||
tt = icaltime_normalize (tt);
|
||
|
||
/* Convert back to a time_t. */
|
||
return icaltime_as_timet_with_zone (tt, zone);
|
||
}
|
||
|
||
|
||
/* Returns the start of the day containing the given time_t, using the given
|
||
timezone.
|
||
NOTE: this function is only here to make the transition to the timezone
|
||
functions easier. New code should use icaltimetype values and
|
||
icaltime_adjust() to add or subtract days, hours, minutes & seconds. */
|
||
time_t
|
||
time_day_begin_with_zone (time_t time, icaltimezone *zone)
|
||
{
|
||
struct icaltimetype tt;
|
||
|
||
/* Convert to an icaltimetype. */
|
||
tt = icaltime_from_timet_with_zone (time, FALSE, zone);
|
||
|
||
/* Set it to the start of the day. */
|
||
tt.hour = 0;
|
||
tt.minute = 0;
|
||
tt.second = 0;
|
||
|
||
/* Convert back to a time_t. */
|
||
return icaltime_as_timet_with_zone (tt, zone);
|
||
}
|
||
|
||
|
||
/* Returns the end of the day containing the given time_t, using the given
|
||
timezone. (The end of the day is the start of the next day.)
|
||
NOTE: this function is only here to make the transition to the timezone
|
||
functions easier. New code should use icaltimetype values and
|
||
icaltime_adjust() to add or subtract days, hours, minutes & seconds. */
|
||
time_t
|
||
time_day_end_with_zone (time_t time, icaltimezone *zone)
|
||
{
|
||
struct icaltimetype tt;
|
||
|
||
/* Convert to an icaltimetype. */
|
||
tt = icaltime_from_timet_with_zone (time, FALSE, zone);
|
||
|
||
/* Set it to the start of the next day. */
|
||
tt.day++;
|
||
tt.hour = 0;
|
||
tt.minute = 0;
|
||
tt.second = 0;
|
||
|
||
/* Normalize it, to fix any overflow. */
|
||
tt = icaltime_normalize (tt);
|
||
|
||
/* Convert back to a time_t. */
|
||
return icaltime_as_timet_with_zone (tt, zone);
|
||
}
|
||
|
||
/**
|
||
* time_to_gdate_with_zone:
|
||
* @date: Destination #GDate value.
|
||
* @time: A time value.
|
||
* @zone: Desired timezone for destination @date, or NULL if the UTC timezone
|
||
* is desired.
|
||
*
|
||
* Converts a time_t value to a #GDate structure using the specified timezone.
|
||
* This is analogous to g_date_set_time() but takes the timezone into account.
|
||
**/
|
||
void
|
||
time_to_gdate_with_zone (GDate *date, time_t time, icaltimezone *zone)
|
||
{
|
||
struct icaltimetype tt;
|
||
|
||
g_return_if_fail (date != NULL);
|
||
g_return_if_fail (time != -1);
|
||
|
||
tt = icaltime_from_timet_with_zone (time, FALSE,
|
||
zone ? zone : icaltimezone_get_utc_timezone ());
|
||
|
||
g_date_set_dmy (date, tt.day, tt.month, tt.year);
|
||
}
|
||
|
||
|
||
/**************************************************************************
|
||
* General time functions.
|
||
**************************************************************************/
|
||
|
||
|
||
/* Returns the number of days in the month. Year is the normal year, e.g. 2001.
|
||
Month is 0 (Jan) to 11 (Dec). */
|
||
int
|
||
time_days_in_month (int year, int month)
|
||
{
|
||
int days;
|
||
|
||
g_return_val_if_fail (year >= 1900, 0);
|
||
g_return_val_if_fail ((month >= 0) && (month < 12), 0);
|
||
|
||
days = days_in_month[month];
|
||
if (month == 1 && time_is_leap_year (year))
|
||
days++;
|
||
|
||
return days;
|
||
}
|
||
|
||
|
||
/* Returns the 1-based day number within the year of the specified date.
|
||
Year is the normal year, e.g. 2001. Month is 0 to 11. */
|
||
int
|
||
time_day_of_year (int day, int month, int year)
|
||
{
|
||
int i;
|
||
|
||
for (i = 0; i < month; i++) {
|
||
day += days_in_month[i];
|
||
|
||
if (month == 1 && time_is_leap_year (year))
|
||
day++;
|
||
}
|
||
|
||
return day;
|
||
}
|
||
|
||
|
||
/* Returns the day of the week for the specified date, 0 (Sun) to 6 (Sat).
|
||
For the days that were removed on the Gregorian reformation, it returns
|
||
Thursday. Year is the normal year, e.g. 2001. Month is 0 to 11. */
|
||
int
|
||
time_day_of_week (int day, int month, int year)
|
||
{
|
||
int n;
|
||
|
||
n = (year - 1) * 365 + time_leap_years_up_to (year - 1)
|
||
+ time_day_of_year (day, month, year);
|
||
|
||
if (n < REFORMATION_DAY)
|
||
return (n - 1 + SATURDAY) % 7;
|
||
|
||
if (n >= (REFORMATION_DAY + MISSING_DAYS))
|
||
return (n - 1 + SATURDAY - MISSING_DAYS) % 7;
|
||
|
||
return THURSDAY;
|
||
}
|
||
|
||
|
||
/* Returns whether the specified year is a leap year. Year is the normal year,
|
||
e.g. 2001. */
|
||
gboolean
|
||
time_is_leap_year (int year)
|
||
{
|
||
if (year <= 1752)
|
||
return !(year % 4);
|
||
else
|
||
return (!(year % 4) && (year % 100)) || !(year % 400);
|
||
}
|
||
|
||
|
||
/* Returns the number of leap years since year 1 up to (but not including) the
|
||
specified year. Year is the normal year, e.g. 2001. */
|
||
int
|
||
time_leap_years_up_to (int year)
|
||
{
|
||
/* There is normally a leap year every 4 years, except at the turn of
|
||
centuries since 1700. But there is a leap year on centuries since 1700
|
||
which are divisible by 400. */
|
||
return (year / 4
|
||
- ((year > 1700) ? (year / 100 - 17) : 0)
|
||
+ ((year > 1600) ? ((year - 1600) / 400) : 0));
|
||
}
|
||
|
||
|
||
/**
|
||
* isodate_from_time_t:
|
||
* @t: A time value.
|
||
*
|
||
* Creates an ISO 8601 UTC representation from a time value.
|
||
*
|
||
* Return value: String with the ISO 8601 representation of the UTC time.
|
||
**/
|
||
char *
|
||
isodate_from_time_t (time_t t)
|
||
{
|
||
struct tm *tm;
|
||
char isotime[40];
|
||
|
||
tm = gmtime (&t);
|
||
strftime (isotime, sizeof (isotime)-1, "%Y%m%dT%H%M%SZ", tm);
|
||
return g_strdup (isotime);
|
||
}
|
||
|
||
/**
|
||
* time_from_isodate:
|
||
* @str: Date/time value in ISO 8601 format.
|
||
*
|
||
* Converts an ISO 8601 UTC time string into a time_t value.
|
||
*
|
||
* Return value: Time_t corresponding to the specified ISO string.
|
||
* Note that we only allow UTC times at present.
|
||
**/
|
||
time_t
|
||
time_from_isodate (const char *str)
|
||
{
|
||
struct icaltimetype tt = icaltime_null_time ();
|
||
icaltimezone *utc_zone;
|
||
int len, i;
|
||
|
||
g_return_val_if_fail (str != NULL, -1);
|
||
|
||
/* yyyymmdd[Thhmmss[Z]] */
|
||
|
||
len = strlen (str);
|
||
|
||
if (!(len == 8 || len == 15 || len == 16))
|
||
return -1;
|
||
|
||
for (i = 0; i < len; i++)
|
||
if (!((i != 8 && i != 15 && isdigit (str[i]))
|
||
|| (i == 8 && str[i] == 'T')
|
||
|| (i == 15 && str[i] == 'Z')))
|
||
return -1;
|
||
|
||
#define digit_at(x,y) (x[y] - '0')
|
||
|
||
tt.year = digit_at (str, 0) * 1000
|
||
+ digit_at (str, 1) * 100
|
||
+ digit_at (str, 2) * 10
|
||
+ digit_at (str, 3);
|
||
|
||
tt.month = digit_at (str, 4) * 10
|
||
+ digit_at (str, 5);
|
||
|
||
tt.day = digit_at (str, 6) * 10
|
||
+ digit_at (str, 7);
|
||
|
||
if (len > 8) {
|
||
tt.hour = digit_at (str, 9) * 10
|
||
+ digit_at (str, 10);
|
||
tt.minute = digit_at (str, 11) * 10
|
||
+ digit_at (str, 12);
|
||
tt.second = digit_at (str, 13) * 10
|
||
+ digit_at (str, 14);
|
||
}
|
||
|
||
utc_zone = icaltimezone_get_utc_timezone ();
|
||
|
||
return icaltime_as_timet_with_zone (tt, utc_zone);
|
||
}
|
||
|
||
struct tm
|
||
icaltimetype_to_tm (struct icaltimetype *itt)
|
||
{
|
||
struct tm tm;
|
||
|
||
memset (&tm, 0, sizeof (struct tm));
|
||
|
||
if (!itt->is_date) {
|
||
tm.tm_sec = itt->second;
|
||
tm.tm_min = itt->minute;
|
||
tm.tm_hour = itt->hour;
|
||
}
|
||
|
||
tm.tm_mday = itt->day;
|
||
tm.tm_mon = itt->month - 1;
|
||
tm.tm_year = itt->year - 1900;
|
||
tm.tm_wday = time_day_of_week (itt->day, itt->month - 1, itt->year);
|
||
tm.tm_isdst = -1;
|
||
|
||
return tm;
|
||
}
|
||
|
||
/**
|
||
* icaltimetype_to_tm_with_zone:
|
||
* @itt: A time value.
|
||
* @from_zone: Source timezone.
|
||
* @to_zone: Destination timezone.
|
||
*
|
||
* Converts a time value from one timezone to another, and returns a struct tm
|
||
* representation of the time.
|
||
*
|
||
* Return value: The converted time as a struct tm. All fields will be
|
||
* set properly except for tm.tm_yday.
|
||
**/
|
||
struct tm
|
||
icaltimetype_to_tm_with_zone (struct icaltimetype *itt,
|
||
icaltimezone *from_zone,
|
||
icaltimezone *to_zone)
|
||
{
|
||
struct tm tm;
|
||
struct icaltimetype itt_copy;
|
||
|
||
memset (&tm, 0, sizeof (tm));
|
||
tm.tm_isdst = -1;
|
||
|
||
g_return_val_if_fail (itt != NULL, tm);
|
||
|
||
itt_copy = *itt;
|
||
|
||
icaltimezone_convert_time (&itt_copy, from_zone, to_zone);
|
||
tm = icaltimetype_to_tm (&itt_copy);
|
||
|
||
return tm;
|
||
}
|
||
|
||
struct icaltimetype
|
||
tm_to_icaltimetype (struct tm *tm, gboolean is_date)
|
||
{
|
||
struct icaltimetype itt;
|
||
|
||
memset (&itt, 0, sizeof (struct icaltimetype));
|
||
|
||
if (!is_date) {
|
||
itt.second = tm->tm_sec;
|
||
itt.minute = tm->tm_min;
|
||
itt.hour = tm->tm_hour;
|
||
}
|
||
|
||
itt.day = tm->tm_mday;
|
||
itt.month = tm->tm_mon + 1;
|
||
itt.year = tm->tm_year+ 1900;
|
||
|
||
itt.is_utc = 0;
|
||
itt.is_date = is_date;
|
||
|
||
return itt;
|
||
}
|
||
|