mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2025-01-26 05:56:14 +01:00
GTimeZone: Parse POSIX format for TZ
Parse the "std offset [dst [offset],start[/time],end[/time]]" (POSIX) format used in Unix for environment variable TZ. https://bugzilla.gnome.org/show_bug.cgi?id=686128
This commit is contained in:
parent
9a4a53c03d
commit
87436e6b3d
341
glib/gtimezone.c
341
glib/gtimezone.c
@ -38,6 +38,7 @@
|
||||
#include "gbytes.h"
|
||||
#include "gslice.h"
|
||||
#include "gdatetime.h"
|
||||
#include "gdate.h"
|
||||
|
||||
/**
|
||||
* SECTION:timezone
|
||||
@ -737,6 +738,304 @@ init_zone_from_rules (GTimeZone *gtz,
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* parses date[/time] for parsing TZ environment variable
|
||||
*
|
||||
* date is either Mm.w.d, Jn or N
|
||||
* - m is 1 to 12
|
||||
* - w is 1 to 5
|
||||
* - d is 0 to 6
|
||||
* - n is 1 to 365
|
||||
* - N is 0 to 365
|
||||
*
|
||||
* time is either h or hh[[:]mm[[[:]ss]]]
|
||||
* - h[h] is 0 to 23
|
||||
* - mm is 00 to 59
|
||||
* - ss is 00 to 59
|
||||
*/
|
||||
static gboolean
|
||||
parse_tz_boundary (const gchar *identifier,
|
||||
TimeZoneDate *boundary)
|
||||
{
|
||||
const gchar *pos;
|
||||
gint month, week, day;
|
||||
GDate *date;
|
||||
|
||||
pos = identifier;
|
||||
|
||||
if (*pos == 'M') /* Relative date */
|
||||
{
|
||||
pos++;
|
||||
|
||||
if (*pos == '\0' || *pos < '0' || '9' < *pos)
|
||||
return FALSE;
|
||||
|
||||
month = *pos++ - '0';
|
||||
|
||||
if ((month == 1 && *pos >= '0' && '2' >= *pos) ||
|
||||
(month == 0 && *pos >= '0' && '9' >= *pos))
|
||||
{
|
||||
month *= 10;
|
||||
month += *pos++ - '0';
|
||||
}
|
||||
|
||||
if (*pos++ != '.' || month == 0)
|
||||
return FALSE;
|
||||
|
||||
if (*pos == '\0' || *pos < '1' || '5' < *pos)
|
||||
return FALSE;
|
||||
|
||||
week = *pos++ - '0';
|
||||
|
||||
if (*pos++ != '.')
|
||||
return FALSE;
|
||||
|
||||
if (*pos == '\0' || *pos < '0' || '6' < *pos)
|
||||
return FALSE;
|
||||
|
||||
day = *pos++ - '0';
|
||||
|
||||
if (!day)
|
||||
day += 7;
|
||||
|
||||
boundary->year = 0;
|
||||
boundary->mon = month;
|
||||
boundary->week = week;
|
||||
boundary->wday = day;
|
||||
}
|
||||
|
||||
else if (*pos == 'J') /* Julian day */
|
||||
{
|
||||
pos++;
|
||||
|
||||
day = 0;
|
||||
while (*pos >= '0' && '9' >= *pos)
|
||||
{
|
||||
day *= 10;
|
||||
day += *pos++ - '0';
|
||||
}
|
||||
|
||||
if (day < 1 || 365 < day)
|
||||
return FALSE;
|
||||
|
||||
date = g_date_new_julian (day);
|
||||
boundary->year = 0;
|
||||
boundary->mon = (int) g_date_get_month (date);
|
||||
boundary->mday = (int) g_date_get_day (date);
|
||||
boundary->wday = 0;
|
||||
g_date_free (date);
|
||||
}
|
||||
|
||||
else if (*pos >= '0' && '9' >= *pos) /* Zero-based Julian day */
|
||||
{
|
||||
day = 0;
|
||||
while (*pos >= '0' && '9' >= *pos)
|
||||
{
|
||||
day *= 10;
|
||||
day += *pos++ - '0';
|
||||
}
|
||||
|
||||
if (day < 0 || 365 < day)
|
||||
return FALSE;
|
||||
|
||||
date = g_date_new_julian (day >= 59? day : day + 1);
|
||||
boundary->year = 0;
|
||||
boundary->mon = (int) g_date_get_month (date);
|
||||
boundary->mday = (int) g_date_get_day (date);
|
||||
boundary->wday = 0;
|
||||
g_date_free (date);
|
||||
|
||||
/* February 29 */
|
||||
if (day == 59)
|
||||
boundary->mday++;
|
||||
}
|
||||
|
||||
else
|
||||
return FALSE;
|
||||
|
||||
/* Time */
|
||||
boundary->isstd = FALSE;
|
||||
boundary->isgmt = FALSE;
|
||||
|
||||
if (*pos == '/')
|
||||
{
|
||||
gint32 offset;
|
||||
|
||||
if (!parse_time (++pos, &offset))
|
||||
return FALSE;
|
||||
|
||||
boundary->hour = offset / 3600;
|
||||
boundary->min = (offset / 60) % 60;
|
||||
boundary->sec = offset % 3600;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
boundary->hour = 2;
|
||||
boundary->min = 0;
|
||||
boundary->sec = 0;
|
||||
|
||||
return *pos == '\0';
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Creates an array of TimeZoneRule from a TZ environment variable
|
||||
* type of identifier. Should free rules, std_name and dlt_name
|
||||
* afterwards
|
||||
*/
|
||||
static gboolean
|
||||
rules_from_identifier (const gchar *identifier,
|
||||
TimeZoneRule **rules,
|
||||
gint *rules_num,
|
||||
gchar **std_name,
|
||||
gchar **dlt_name)
|
||||
{
|
||||
const gchar *std_name_pos, *std_offset_pos;
|
||||
const gchar *dlt_name_pos, *dlt_offset_pos;
|
||||
|
||||
const gchar *start_date_pos, *end_date_pos;
|
||||
|
||||
const gchar *pos;
|
||||
gchar *buffer;
|
||||
gboolean ret;
|
||||
|
||||
gint32 std_offset, dlt_offset;
|
||||
TimeZoneDate dlt_start, dlt_end;
|
||||
|
||||
if (!identifier)
|
||||
return FALSE;
|
||||
|
||||
pos = identifier;
|
||||
|
||||
std_name_pos = pos;
|
||||
|
||||
/* Name is alpha */
|
||||
while ((*pos >= 'a' && 'z' >= *pos) || (*pos >= 'A' && 'Z' >= *pos))
|
||||
pos++;
|
||||
|
||||
/* Offset for standard required (format 1) */
|
||||
if (*pos == '\0')
|
||||
return FALSE;
|
||||
|
||||
/* Name should be three or more alphabetic characters */
|
||||
if (pos - identifier < 3)
|
||||
return FALSE;
|
||||
|
||||
std_offset_pos = pos;
|
||||
|
||||
/* Standard offset */
|
||||
while (*pos == '+' || *pos == '-' || *pos == ':' || (*pos >= '0' && '9' >= *pos))
|
||||
pos++;
|
||||
|
||||
buffer = g_strndup (std_offset_pos, pos - std_offset_pos);
|
||||
ret = parse_constant_offset (buffer, &std_offset);
|
||||
g_free (buffer);
|
||||
|
||||
if (!ret)
|
||||
return FALSE;
|
||||
|
||||
dlt_name_pos = pos;
|
||||
|
||||
/* Format 2 */
|
||||
if (*pos != '\0')
|
||||
{
|
||||
/* Name is alpha */
|
||||
while ((*pos >= 'a' && 'z' >= *pos) || (*pos >= 'A' && 'Z' >= *pos))
|
||||
pos++;
|
||||
|
||||
/* Name should be three or more alphabetic characters */
|
||||
if (pos - identifier < 3)
|
||||
return FALSE;
|
||||
|
||||
dlt_offset_pos = pos;
|
||||
|
||||
/* Start and end required (format 2) */
|
||||
if (*pos == '\0')
|
||||
return FALSE;
|
||||
|
||||
/* Default offset is 1 hour less from standard offset */
|
||||
if (*pos++ == ',')
|
||||
dlt_offset = std_offset - 60 * 60;
|
||||
|
||||
else
|
||||
{
|
||||
/* Daylight offset */
|
||||
while (*pos == '+' || *pos == '-' || *pos == ':' || (*pos >= '0' && '9' >= *pos))
|
||||
pos++;
|
||||
|
||||
buffer = g_strndup (dlt_offset_pos, pos - dlt_offset_pos);
|
||||
ret = parse_constant_offset (buffer, &dlt_offset);
|
||||
g_free (buffer);
|
||||
|
||||
if (!ret)
|
||||
return FALSE;
|
||||
|
||||
/* Start and end required (format 2) */
|
||||
if (*pos++ != ',')
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* Start date */
|
||||
start_date_pos = pos;
|
||||
|
||||
while (*pos != ',' && *pos != '\0')
|
||||
pos++;
|
||||
|
||||
/* End required (format 2) */
|
||||
if (*pos == '\0')
|
||||
return FALSE;
|
||||
|
||||
buffer = g_strndup (start_date_pos, pos++ - start_date_pos);
|
||||
ret = parse_tz_boundary (buffer, &dlt_start);
|
||||
g_free (buffer);
|
||||
|
||||
if (!ret)
|
||||
return FALSE;
|
||||
|
||||
/* End date */
|
||||
end_date_pos = pos;
|
||||
|
||||
while (*pos != '\0')
|
||||
pos++;
|
||||
|
||||
buffer = g_strndup (end_date_pos, pos - end_date_pos);
|
||||
ret = parse_tz_boundary (buffer, &dlt_end);
|
||||
g_free (buffer);
|
||||
|
||||
if (!ret)
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
||||
*std_name = g_strndup (std_name_pos, std_offset_pos - std_name_pos);
|
||||
|
||||
if (dlt_name_pos != pos)
|
||||
*dlt_name = g_strndup (dlt_name_pos, dlt_offset_pos - dlt_name_pos);
|
||||
else
|
||||
{
|
||||
*dlt_name = NULL;
|
||||
dlt_start.mon = 0;
|
||||
}
|
||||
|
||||
*rules_num = 2;
|
||||
*rules = g_new0 (TimeZoneRule, 2);
|
||||
|
||||
(*rules)[0].start_year = MIN_TZYEAR;
|
||||
(*rules)[1].start_year = MAX_TZYEAR;
|
||||
|
||||
(*rules)[0].std_offset = -std_offset;
|
||||
(*rules)[0].dlt_offset = -dlt_offset;
|
||||
(*rules)[0].dlt_start = dlt_start;
|
||||
(*rules)[0].dlt_end = dlt_end;
|
||||
(*rules)[0].std_name = *std_name;
|
||||
(*rules)[0].dlt_name = *dlt_name;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* Construction {{{1 */
|
||||
/**
|
||||
* g_time_zone_new:
|
||||
@ -750,12 +1049,24 @@ init_zone_from_rules (GTimeZone *gtz,
|
||||
*
|
||||
* Valid RFC3339 time offsets are <literal>"Z"</literal> (for UTC) or
|
||||
* <literal>"±hh:mm"</literal>. ISO 8601 additionally specifies
|
||||
* <literal>"±hhmm"</literal> and <literal>"±hh"</literal>.
|
||||
* <literal>"±hhmm"</literal> and <literal>"±hh"</literal>. Offsets are
|
||||
* time values to be added to Coordinated Universal Time (UTC) to get
|
||||
* the local time.
|
||||
*
|
||||
* The <varname>TZ</varname> environment variable typically corresponds
|
||||
* to the name of a file in the zoneinfo database, but there are many
|
||||
* other possibilities. Note that those other possibilities are not
|
||||
* currently implemented, but are planned.
|
||||
* In Unix, the <varname>TZ</varname> environment variable typically
|
||||
* corresponds to the name of a file in the zoneinfo database, or
|
||||
* string in "std offset [dst [offset],start[/time],end[/time]]"
|
||||
* (POSIX) format. There are no spaces in the specification. The
|
||||
* name of standard and daylight savings time zone must be three or more
|
||||
* alphabetic characters. Offsets are time values to be added to local
|
||||
* time to get Coordinated Universal Time (UTC) and should be
|
||||
* <literal>"[±]hh[[:]mm[:ss]]"</literal>. Dates are either
|
||||
* <literal>"Jn"</literal> (Julian day with n between 1 and 365, leap
|
||||
* years not counted), <literal>"n"</literal> (zero-based Julian day
|
||||
* with n between 0 and 365) or <literal>"Mm.w.d"</literal> (day d
|
||||
* (0 <= d <= 6) of week w (1 <= w <= 5) of month m (1 <= m <= 12), day
|
||||
* 0 is a Sunday). Times are in local wall clock time, the default is
|
||||
* 02:00:00.
|
||||
*
|
||||
* g_time_zone_new_local() calls this function with the value of the
|
||||
* <varname>TZ</varname> environment variable. This function itself is
|
||||
@ -763,6 +1074,12 @@ init_zone_from_rules (GTimeZone *gtz,
|
||||
* is %NULL then <filename>/etc/localtime</filename> will be consulted
|
||||
* to discover the correct timezone.
|
||||
*
|
||||
* If intervals are not available, only time zone rules from
|
||||
* <varname>TZ</varname> environment variable or other means, then they
|
||||
* will be computed from year 1900 to 2037. If the maximum year for the
|
||||
* rules is available and it is greater than 2037, then it will followed
|
||||
* instead.
|
||||
*
|
||||
* See <ulink
|
||||
* url='http://tools.ietf.org/html/rfc3339#section-5.6'>RFC3339
|
||||
* §5.6</ulink> for a precise definition of valid RFC3339 time offsets
|
||||
@ -783,6 +1100,9 @@ GTimeZone *
|
||||
g_time_zone_new (const gchar *identifier)
|
||||
{
|
||||
GTimeZone *tz = NULL;
|
||||
TimeZoneRule *rules;
|
||||
gint rules_num;
|
||||
gchar *std_name, *dlt_name;
|
||||
|
||||
G_LOCK (time_zones);
|
||||
if (time_zones == NULL)
|
||||
@ -805,6 +1125,17 @@ g_time_zone_new (const gchar *identifier)
|
||||
|
||||
zone_for_constant_offset (tz, identifier);
|
||||
|
||||
if (tz->t_info == NULL &&
|
||||
rules_from_identifier (identifier,
|
||||
&rules, &rules_num,
|
||||
&std_name, &dlt_name))
|
||||
{
|
||||
init_zone_from_rules (tz, rules, rules_num);
|
||||
g_free (rules);
|
||||
g_free (std_name);
|
||||
g_free (dlt_name);
|
||||
}
|
||||
|
||||
if (tz->t_info == NULL)
|
||||
{
|
||||
#ifdef G_OS_UNIX
|
||||
|
Loading…
Reference in New Issue
Block a user