Blame glib/gtimezone.c

Packit ae235b
/*
Packit ae235b
 * Copyright © 2010 Codethink Limited
Packit ae235b
 *
Packit ae235b
 * This library is free software; you can redistribute it and/or
Packit ae235b
 * modify it under the terms of the GNU Lesser General Public
Packit ae235b
 * License as published by the Free Software Foundation; either
Packit ae235b
 * version 2.1 of the License, or (at your option) any later version.
Packit ae235b
 *
Packit ae235b
 * This library is distributed in the hope that it will be useful,
Packit ae235b
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit ae235b
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Packit ae235b
 * Lesser General Public License for more details.
Packit ae235b
 *
Packit ae235b
 * You should have received a copy of the GNU Lesser General Public
Packit ae235b
 * License along with this library; if not, see <http://www.gnu.org/licenses/>.
Packit ae235b
 *
Packit ae235b
 * Author: Ryan Lortie <desrt@desrt.ca>
Packit ae235b
 */
Packit ae235b
Packit ae235b
/* Prologue {{{1 */
Packit ae235b
Packit ae235b
#include "config.h"
Packit ae235b
Packit ae235b
#include "gtimezone.h"
Packit ae235b
Packit ae235b
#include <string.h>
Packit ae235b
#include <stdlib.h>
Packit ae235b
#include <signal.h>
Packit ae235b
Packit ae235b
#include "gmappedfile.h"
Packit ae235b
#include "gtestutils.h"
Packit ae235b
#include "gfileutils.h"
Packit ae235b
#include "gstrfuncs.h"
Packit ae235b
#include "ghash.h"
Packit ae235b
#include "gthread.h"
Packit ae235b
#include "gbytes.h"
Packit ae235b
#include "gslice.h"
Packit ae235b
#include "gdatetime.h"
Packit ae235b
#include "gdate.h"
Packit ae235b
Packit ae235b
#ifdef G_OS_WIN32
Packit ae235b
#define STRICT
Packit ae235b
#include <windows.h>
Packit ae235b
#endif
Packit ae235b
Packit ae235b
/**
Packit ae235b
 * SECTION:timezone
Packit ae235b
 * @title: GTimeZone
Packit ae235b
 * @short_description: a structure representing a time zone
Packit ae235b
 * @see_also: #GDateTime
Packit ae235b
 *
Packit ae235b
 * #GTimeZone is a structure that represents a time zone, at no
Packit ae235b
 * particular point in time.  It is refcounted and immutable.
Packit ae235b
 *
Packit ae235b
 * A time zone contains a number of intervals.  Each interval has
Packit ae235b
 * an abbreviation to describe it, an offet to UTC and a flag indicating
Packit ae235b
 * if the daylight savings time is in effect during that interval.  A
Packit ae235b
 * time zone always has at least one interval -- interval 0.
Packit ae235b
 *
Packit ae235b
 * Every UTC time is contained within exactly one interval, but a given
Packit ae235b
 * local time may be contained within zero, one or two intervals (due to
Packit ae235b
 * incontinuities associated with daylight savings time).
Packit ae235b
 *
Packit ae235b
 * An interval may refer to a specific period of time (eg: the duration
Packit ae235b
 * of daylight savings time during 2010) or it may refer to many periods
Packit ae235b
 * of time that share the same properties (eg: all periods of daylight
Packit ae235b
 * savings time).  It is also possible (usually for political reasons)
Packit ae235b
 * that some properties (like the abbreviation) change between intervals
Packit ae235b
 * without other properties changing.
Packit ae235b
 *
Packit ae235b
 * #GTimeZone is available since GLib 2.26.
Packit ae235b
 */
Packit ae235b
Packit ae235b
/**
Packit ae235b
 * GTimeZone:
Packit ae235b
 *
Packit ae235b
 * #GTimeZone is an opaque structure whose members cannot be accessed
Packit ae235b
 * directly.
Packit ae235b
 *
Packit ae235b
 * Since: 2.26
Packit ae235b
 **/
Packit ae235b
Packit ae235b
/* IANA zoneinfo file format {{{1 */
Packit ae235b
Packit ae235b
/* unaligned */
Packit ae235b
typedef struct { gchar bytes[8]; } gint64_be;
Packit ae235b
typedef struct { gchar bytes[4]; } gint32_be;
Packit ae235b
typedef struct { gchar bytes[4]; } guint32_be;
Packit ae235b
Packit ae235b
static inline gint64 gint64_from_be (const gint64_be be) {
Packit ae235b
  gint64 tmp; memcpy (&tmp, &be, sizeof tmp); return GINT64_FROM_BE (tmp);
Packit ae235b
}
Packit ae235b
Packit ae235b
static inline gint32 gint32_from_be (const gint32_be be) {
Packit ae235b
  gint32 tmp; memcpy (&tmp, &be, sizeof tmp); return GINT32_FROM_BE (tmp);
Packit ae235b
}
Packit ae235b
Packit ae235b
static inline guint32 guint32_from_be (const guint32_be be) {
Packit ae235b
  guint32 tmp; memcpy (&tmp, &be, sizeof tmp); return GUINT32_FROM_BE (tmp);
Packit ae235b
}
Packit ae235b
Packit ae235b
/* The layout of an IANA timezone file header */
Packit ae235b
struct tzhead
Packit ae235b
{
Packit ae235b
  gchar      tzh_magic[4];
Packit ae235b
  gchar      tzh_version;
Packit ae235b
  guchar     tzh_reserved[15];
Packit ae235b
Packit ae235b
  guint32_be tzh_ttisgmtcnt;
Packit ae235b
  guint32_be tzh_ttisstdcnt;
Packit ae235b
  guint32_be tzh_leapcnt;
Packit ae235b
  guint32_be tzh_timecnt;
Packit ae235b
  guint32_be tzh_typecnt;
Packit ae235b
  guint32_be tzh_charcnt;
Packit ae235b
};
Packit ae235b
Packit ae235b
struct ttinfo
Packit ae235b
{
Packit ae235b
  gint32_be tt_gmtoff;
Packit ae235b
  guint8    tt_isdst;
Packit ae235b
  guint8    tt_abbrind;
Packit ae235b
};
Packit ae235b
Packit ae235b
/* A Transition Date structure for TZ Rules, an intermediate structure
Packit ae235b
   for parsing MSWindows and Environment-variable time zones. It
Packit ae235b
   Generalizes MSWindows's SYSTEMTIME struct.
Packit ae235b
 */
Packit ae235b
typedef struct
Packit ae235b
{
Packit ae235b
  gint     year;
Packit ae235b
  gint     mon;
Packit ae235b
  gint     mday;
Packit ae235b
  gint     wday;
Packit ae235b
  gint     week;
Packit ae235b
  gint     hour;
Packit ae235b
  gint     min;
Packit ae235b
  gint     sec;
Packit ae235b
} TimeZoneDate;
Packit ae235b
Packit ae235b
/* POSIX Timezone abbreviations are typically 3 or 4 characters, but
Packit ae235b
   Microsoft uses 32-character names. We'll use one larger to ensure
Packit ae235b
   we have room for the terminating \0.
Packit ae235b
 */
Packit ae235b
#define NAME_SIZE 33
Packit ae235b
Packit ae235b
/* A MSWindows-style time zone transition rule. Generalizes the
Packit ae235b
   MSWindows TIME_ZONE_INFORMATION struct. Also used to compose time
Packit ae235b
   zones from tzset-style identifiers.
Packit ae235b
 */
Packit ae235b
typedef struct
Packit ae235b
{
Packit ae235b
  gint         start_year;
Packit ae235b
  gint32       std_offset;
Packit ae235b
  gint32       dlt_offset;
Packit ae235b
  TimeZoneDate dlt_start;
Packit ae235b
  TimeZoneDate dlt_end;
Packit ae235b
  gchar std_name[NAME_SIZE];
Packit ae235b
  gchar dlt_name[NAME_SIZE];
Packit ae235b
} TimeZoneRule;
Packit ae235b
Packit ae235b
/* GTimeZone's internal representation of a Daylight Savings (Summer)
Packit ae235b
   time interval.
Packit ae235b
 */
Packit ae235b
typedef struct
Packit ae235b
{
Packit ae235b
  gint32     gmt_offset;
Packit ae235b
  gboolean   is_dst;
Packit ae235b
  gchar     *abbrev;
Packit ae235b
} TransitionInfo;
Packit ae235b
Packit ae235b
/* GTimeZone's representation of a transition time to or from Daylight
Packit ae235b
   Savings (Summer) time and Standard time for the zone. */
Packit ae235b
typedef struct
Packit ae235b
{
Packit ae235b
  gint64 time;
Packit ae235b
  gint   info_index;
Packit ae235b
} Transition;
Packit ae235b
Packit ae235b
/* GTimeZone structure */
Packit ae235b
struct _GTimeZone
Packit ae235b
{
Packit ae235b
  gchar   *name;
Packit ae235b
  GArray  *t_info;         /* Array of TransitionInfo */
Packit ae235b
  GArray  *transitions;    /* Array of Transition */
Packit ae235b
  gint     ref_count;
Packit ae235b
};
Packit ae235b
Packit ae235b
G_LOCK_DEFINE_STATIC (time_zones);
Packit ae235b
static GHashTable/*<string?, GTimeZone>*/ *time_zones;
Packit ae235b
Packit ae235b
#define MIN_TZYEAR 1916 /* Daylight Savings started in WWI */
Packit ae235b
#define MAX_TZYEAR 2999 /* And it's not likely ever to go away, but
Packit ae235b
                           there's no point in getting carried
Packit ae235b
                           away. */
Packit ae235b
Packit ae235b
/**
Packit ae235b
 * g_time_zone_unref:
Packit ae235b
 * @tz: a #GTimeZone
Packit ae235b
 *
Packit ae235b
 * Decreases the reference count on @tz.
Packit ae235b
 *
Packit ae235b
 * Since: 2.26
Packit ae235b
 **/
Packit ae235b
void
Packit ae235b
g_time_zone_unref (GTimeZone *tz)
Packit ae235b
{
Packit ae235b
  int ref_count;
Packit ae235b
Packit ae235b
again:
Packit ae235b
  ref_count = g_atomic_int_get (&tz->ref_count);
Packit ae235b
Packit ae235b
  g_assert (ref_count > 0);
Packit ae235b
Packit ae235b
  if (ref_count == 1)
Packit ae235b
    {
Packit ae235b
      if (tz->name != NULL)
Packit ae235b
        {
Packit ae235b
          G_LOCK(time_zones);
Packit ae235b
Packit ae235b
          /* someone else might have grabbed a ref in the meantime */
Packit ae235b
          if G_UNLIKELY (g_atomic_int_get (&tz->ref_count) != 1)
Packit ae235b
            {
Packit ae235b
              G_UNLOCK(time_zones);
Packit ae235b
              goto again;
Packit ae235b
            }
Packit ae235b
Packit ae235b
          g_hash_table_remove (time_zones, tz->name);
Packit ae235b
          G_UNLOCK(time_zones);
Packit ae235b
        }
Packit ae235b
Packit ae235b
      if (tz->t_info != NULL)
Packit ae235b
        {
Packit ae235b
          gint idx;
Packit ae235b
          for (idx = 0; idx < tz->t_info->len; idx++)
Packit ae235b
            {
Packit ae235b
              TransitionInfo *info = &g_array_index (tz->t_info, TransitionInfo, idx);
Packit ae235b
              g_free (info->abbrev);
Packit ae235b
            }
Packit ae235b
          g_array_free (tz->t_info, TRUE);
Packit ae235b
        }
Packit ae235b
      if (tz->transitions != NULL)
Packit ae235b
        g_array_free (tz->transitions, TRUE);
Packit ae235b
      g_free (tz->name);
Packit ae235b
Packit ae235b
      g_slice_free (GTimeZone, tz);
Packit ae235b
    }
Packit ae235b
Packit ae235b
  else if G_UNLIKELY (!g_atomic_int_compare_and_exchange (&tz->ref_count,
Packit ae235b
                                                          ref_count,
Packit ae235b
                                                          ref_count - 1))
Packit ae235b
    goto again;
Packit ae235b
}
Packit ae235b
Packit ae235b
/**
Packit ae235b
 * g_time_zone_ref:
Packit ae235b
 * @tz: a #GTimeZone
Packit ae235b
 *
Packit ae235b
 * Increases the reference count on @tz.
Packit ae235b
 *
Packit ae235b
 * Returns: a new reference to @tz.
Packit ae235b
 *
Packit ae235b
 * Since: 2.26
Packit ae235b
 **/
Packit ae235b
GTimeZone *
Packit ae235b
g_time_zone_ref (GTimeZone *tz)
Packit ae235b
{
Packit ae235b
  g_assert (tz->ref_count > 0);
Packit ae235b
Packit ae235b
  g_atomic_int_inc (&tz->ref_count);
Packit ae235b
Packit ae235b
  return tz;
Packit ae235b
}
Packit ae235b
Packit ae235b
/* fake zoneinfo creation (for RFC3339/ISO 8601 timezones) {{{1 */
Packit ae235b
/*
Packit ae235b
 * parses strings of the form h or hh[[:]mm[[[:]ss]]] where:
Packit ae235b
 *  - h[h] is 0 to 23
Packit ae235b
 *  - mm is 00 to 59
Packit ae235b
 *  - ss is 00 to 59
Packit ae235b
 */
Packit ae235b
static gboolean
Packit ae235b
parse_time (const gchar *time_,
Packit ae235b
            gint32      *offset)
Packit ae235b
{
Packit ae235b
  if (*time_ < '0' || '9' < *time_)
Packit ae235b
    return FALSE;
Packit ae235b
Packit ae235b
  *offset = 60 * 60 * (*time_++ - '0');
Packit ae235b
Packit ae235b
  if (*time_ == '\0')
Packit ae235b
    return TRUE;
Packit ae235b
Packit ae235b
  if (*time_ != ':')
Packit ae235b
    {
Packit ae235b
      if (*time_ < '0' || '9' < *time_)
Packit ae235b
        return FALSE;
Packit ae235b
Packit ae235b
      *offset *= 10;
Packit ae235b
      *offset += 60 * 60 * (*time_++ - '0');
Packit ae235b
Packit ae235b
      if (*offset > 23 * 60 * 60)
Packit ae235b
        return FALSE;
Packit ae235b
Packit ae235b
      if (*time_ == '\0')
Packit ae235b
        return TRUE;
Packit ae235b
    }
Packit ae235b
Packit ae235b
  if (*time_ == ':')
Packit ae235b
    time_++;
Packit ae235b
Packit ae235b
  if (*time_ < '0' || '5' < *time_)
Packit ae235b
    return FALSE;
Packit ae235b
Packit ae235b
  *offset += 10 * 60 * (*time_++ - '0');
Packit ae235b
Packit ae235b
  if (*time_ < '0' || '9' < *time_)
Packit ae235b
    return FALSE;
Packit ae235b
Packit ae235b
  *offset += 60 * (*time_++ - '0');
Packit ae235b
Packit ae235b
  if (*time_ == '\0')
Packit ae235b
    return TRUE;
Packit ae235b
Packit ae235b
  if (*time_ == ':')
Packit ae235b
    time_++;
Packit ae235b
Packit ae235b
  if (*time_ < '0' || '5' < *time_)
Packit ae235b
    return FALSE;
Packit ae235b
Packit ae235b
  *offset += 10 * (*time_++ - '0');
Packit ae235b
Packit ae235b
  if (*time_ < '0' || '9' < *time_)
Packit ae235b
    return FALSE;
Packit ae235b
Packit ae235b
  *offset += *time_++ - '0';
Packit ae235b
Packit ae235b
  return *time_ == '\0';
Packit ae235b
}
Packit ae235b
Packit ae235b
static gboolean
Packit ae235b
parse_constant_offset (const gchar *name,
Packit ae235b
                       gint32      *offset)
Packit ae235b
{
Packit ae235b
  if (g_strcmp0 (name, "UTC") == 0)
Packit ae235b
    {
Packit ae235b
      *offset = 0;
Packit ae235b
      return TRUE;
Packit ae235b
    }
Packit ae235b
Packit ae235b
  if (*name >= '0' && '9' >= *name)
Packit ae235b
    return parse_time (name, offset);
Packit ae235b
Packit ae235b
  switch (*name++)
Packit ae235b
    {
Packit ae235b
    case 'Z':
Packit ae235b
      *offset = 0;
Packit ae235b
      return !*name;
Packit ae235b
Packit ae235b
    case '+':
Packit ae235b
      return parse_time (name, offset);
Packit ae235b
Packit ae235b
    case '-':
Packit ae235b
      if (parse_time (name, offset))
Packit ae235b
        {
Packit ae235b
          *offset = -*offset;
Packit ae235b
          return TRUE;
Packit ae235b
        }
Packit ae235b
Packit ae235b
    default:
Packit ae235b
      return FALSE;
Packit ae235b
    }
Packit ae235b
}
Packit ae235b
Packit ae235b
static void
Packit ae235b
zone_for_constant_offset (GTimeZone *gtz, const gchar *name)
Packit ae235b
{
Packit ae235b
  gint32 offset;
Packit ae235b
  TransitionInfo info;
Packit ae235b
Packit ae235b
  if (name == NULL || !parse_constant_offset (name, &offset))
Packit ae235b
    return;
Packit ae235b
Packit ae235b
  info.gmt_offset = offset;
Packit ae235b
  info.is_dst = FALSE;
Packit ae235b
  info.abbrev =  g_strdup (name);
Packit ae235b
Packit ae235b
Packit ae235b
  gtz->t_info = g_array_sized_new (FALSE, TRUE, sizeof (TransitionInfo), 1);
Packit ae235b
  g_array_append_val (gtz->t_info, info);
Packit ae235b
Packit ae235b
  /* Constant offset, no transitions */
Packit ae235b
  gtz->transitions = NULL;
Packit ae235b
}
Packit ae235b
Packit ae235b
#ifdef G_OS_UNIX
Packit ae235b
static GBytes*
Packit ae235b
zone_info_unix (const gchar *identifier)
Packit ae235b
{
Packit ae235b
  gchar *filename;
Packit ae235b
  GMappedFile *file = NULL;
Packit ae235b
  GBytes *zoneinfo = NULL;
Packit ae235b
Packit ae235b
  /* identifier can be a relative or absolute path name;
Packit ae235b
     if relative, it is interpreted starting from /usr/share/zoneinfo
Packit ae235b
     while the POSIX standard says it should start with :,
Packit ae235b
     glibc allows both syntaxes, so we should too */
Packit ae235b
  if (identifier != NULL)
Packit ae235b
    {
Packit ae235b
      const gchar *tzdir;
Packit ae235b
Packit ae235b
      tzdir = getenv ("TZDIR");
Packit ae235b
      if (tzdir == NULL)
Packit ae235b
        tzdir = "/usr/share/zoneinfo";
Packit ae235b
Packit ae235b
      if (*identifier == ':')
Packit ae235b
        identifier ++;
Packit ae235b
Packit ae235b
      if (g_path_is_absolute (identifier))
Packit ae235b
        filename = g_strdup (identifier);
Packit ae235b
      else
Packit ae235b
        filename = g_build_filename (tzdir, identifier, NULL);
Packit ae235b
    }
Packit ae235b
  else
Packit ae235b
    filename = g_strdup ("/etc/localtime");
Packit ae235b
Packit ae235b
  file = g_mapped_file_new (filename, FALSE, NULL);
Packit ae235b
  if (file != NULL)
Packit ae235b
    {
Packit ae235b
      zoneinfo = g_bytes_new_with_free_func (g_mapped_file_get_contents (file),
Packit ae235b
                                             g_mapped_file_get_length (file),
Packit ae235b
                                             (GDestroyNotify)g_mapped_file_unref,
Packit ae235b
                                             g_mapped_file_ref (file));
Packit ae235b
      g_mapped_file_unref (file);
Packit ae235b
    }
Packit ae235b
  g_free (filename);
Packit ae235b
  return zoneinfo;
Packit ae235b
}
Packit ae235b
Packit ae235b
static void
Packit ae235b
init_zone_from_iana_info (GTimeZone *gtz, GBytes *zoneinfo)
Packit ae235b
{
Packit ae235b
  gsize size;
Packit ae235b
  guint index;
Packit ae235b
  guint32 time_count, type_count;
Packit ae235b
  guint8 *tz_transitions, *tz_type_index, *tz_ttinfo;
Packit ae235b
  guint8 *tz_abbrs;
Packit ae235b
  gsize timesize = sizeof (gint32);
Packit ae235b
  const struct tzhead *header = g_bytes_get_data (zoneinfo, &size);
Packit ae235b
Packit ae235b
  g_return_if_fail (size >= sizeof (struct tzhead) &&
Packit ae235b
                    memcmp (header, "TZif", 4) == 0);
Packit ae235b
Packit ae235b
  if (header->tzh_version == '2')
Packit ae235b
      {
Packit ae235b
        /* Skip ahead to the newer 64-bit data if it's available. */
Packit ae235b
        header = (const struct tzhead *)
Packit ae235b
          (((const gchar *) (header + 1)) +
Packit ae235b
           guint32_from_be(header->tzh_ttisgmtcnt) +
Packit ae235b
           guint32_from_be(header->tzh_ttisstdcnt) +
Packit ae235b
           8 * guint32_from_be(header->tzh_leapcnt) +
Packit ae235b
           5 * guint32_from_be(header->tzh_timecnt) +
Packit ae235b
           6 * guint32_from_be(header->tzh_typecnt) +
Packit ae235b
           guint32_from_be(header->tzh_charcnt));
Packit ae235b
        timesize = sizeof (gint64);
Packit ae235b
      }
Packit ae235b
  time_count = guint32_from_be(header->tzh_timecnt);
Packit ae235b
  type_count = guint32_from_be(header->tzh_typecnt);
Packit ae235b
Packit ae235b
  tz_transitions = ((guint8 *) (header) + sizeof (*header));
Packit ae235b
  tz_type_index = tz_transitions + timesize * time_count;
Packit ae235b
  tz_ttinfo = tz_type_index + time_count;
Packit ae235b
  tz_abbrs = tz_ttinfo + sizeof (struct ttinfo) * type_count;
Packit ae235b
Packit ae235b
  gtz->t_info = g_array_sized_new (FALSE, TRUE, sizeof (TransitionInfo),
Packit ae235b
                                   type_count);
Packit ae235b
  gtz->transitions = g_array_sized_new (FALSE, TRUE, sizeof (Transition),
Packit ae235b
                                        time_count);
Packit ae235b
Packit ae235b
  for (index = 0; index < type_count; index++)
Packit ae235b
    {
Packit ae235b
      TransitionInfo t_info;
Packit ae235b
      struct ttinfo info = ((struct ttinfo*)tz_ttinfo)[index];
Packit ae235b
      t_info.gmt_offset = gint32_from_be (info.tt_gmtoff);
Packit ae235b
      t_info.is_dst = info.tt_isdst ? TRUE : FALSE;
Packit ae235b
      t_info.abbrev = g_strdup ((gchar *) &tz_abbrs[info.tt_abbrind]);
Packit ae235b
      g_array_append_val (gtz->t_info, t_info);
Packit ae235b
    }
Packit ae235b
Packit ae235b
  for (index = 0; index < time_count; index++)
Packit ae235b
    {
Packit ae235b
      Transition trans;
Packit ae235b
      if (header->tzh_version == '2')
Packit ae235b
        trans.time = gint64_from_be (((gint64_be*)tz_transitions)[index]);
Packit ae235b
      else
Packit ae235b
        trans.time = gint32_from_be (((gint32_be*)tz_transitions)[index]);
Packit ae235b
      trans.info_index = tz_type_index[index];
Packit ae235b
      g_assert (trans.info_index >= 0);
Packit ae235b
      g_assert (trans.info_index < gtz->t_info->len);
Packit ae235b
      g_array_append_val (gtz->transitions, trans);
Packit ae235b
    }
Packit ae235b
}
Packit ae235b
Packit ae235b
#elif defined (G_OS_WIN32)
Packit ae235b
Packit ae235b
static void
Packit ae235b
copy_windows_systemtime (SYSTEMTIME *s_time, TimeZoneDate *tzdate)
Packit ae235b
{
Packit ae235b
  tzdate->sec = s_time->wSecond;
Packit ae235b
  tzdate->min = s_time->wMinute;
Packit ae235b
  tzdate->hour = s_time->wHour;
Packit ae235b
  tzdate->mon = s_time->wMonth;
Packit ae235b
  tzdate->year = s_time->wYear;
Packit ae235b
  tzdate->wday = s_time->wDayOfWeek ? s_time->wDayOfWeek : 7;
Packit ae235b
Packit ae235b
  if (s_time->wYear)
Packit ae235b
    {
Packit ae235b
      tzdate->mday = s_time->wDay;
Packit ae235b
      tzdate->wday = 0;
Packit ae235b
    }
Packit ae235b
  else
Packit ae235b
    tzdate->week = s_time->wDay;
Packit ae235b
}
Packit ae235b
Packit ae235b
/* UTC = local time + bias while local time = UTC + offset */
Packit ae235b
static void
Packit ae235b
rule_from_windows_time_zone_info (TimeZoneRule *rule,
Packit ae235b
                                  TIME_ZONE_INFORMATION *tzi)
Packit ae235b
{
Packit ae235b
  /* Set offset */
Packit ae235b
  if (tzi->StandardDate.wMonth)
Packit ae235b
    {
Packit ae235b
      rule->std_offset = -(tzi->Bias + tzi->StandardBias) * 60;
Packit ae235b
      rule->dlt_offset = -(tzi->Bias + tzi->DaylightBias) * 60;
Packit ae235b
      copy_windows_systemtime (&(tzi->DaylightDate), &(rule->dlt_start));
Packit ae235b
Packit ae235b
      copy_windows_systemtime (&(tzi->StandardDate), &(rule->dlt_end));
Packit ae235b
Packit ae235b
    }
Packit ae235b
Packit ae235b
  else
Packit ae235b
    {
Packit ae235b
      rule->std_offset = -tzi->Bias * 60;
Packit ae235b
      rule->dlt_start.mon = 0;
Packit ae235b
    }
Packit ae235b
  strncpy (rule->std_name, (gchar*)tzi->StandardName, NAME_SIZE - 1);
Packit ae235b
  strncpy (rule->dlt_name, (gchar*)tzi->DaylightName, NAME_SIZE - 1);
Packit ae235b
}
Packit ae235b
Packit ae235b
static gchar*
Packit ae235b
windows_default_tzname (void)
Packit ae235b
{
Packit ae235b
  const gchar *subkey =
Packit ae235b
    "SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation";
Packit ae235b
  HKEY key;
Packit ae235b
  gchar *key_name = NULL;
Packit ae235b
  if (RegOpenKeyExA (HKEY_LOCAL_MACHINE, subkey, 0,
Packit ae235b
                     KEY_QUERY_VALUE, &key) == ERROR_SUCCESS)
Packit ae235b
    {
Packit ae235b
      DWORD size = 0;
Packit ae235b
      if (RegQueryValueExA (key, "TimeZoneKeyName", NULL, NULL,
Packit ae235b
                            NULL, &size) == ERROR_SUCCESS)
Packit ae235b
        {
Packit ae235b
          key_name = g_malloc ((gint)size);
Packit ae235b
          if (RegQueryValueExA (key, "TimeZoneKeyName", NULL, NULL,
Packit ae235b
                                (LPBYTE)key_name, &size) != ERROR_SUCCESS)
Packit ae235b
            {
Packit ae235b
              g_free (key_name);
Packit ae235b
              key_name = NULL;
Packit ae235b
            }
Packit ae235b
        }
Packit ae235b
      RegCloseKey (key);
Packit ae235b
    }
Packit ae235b
  return key_name;
Packit ae235b
}
Packit ae235b
Packit ae235b
typedef   struct
Packit ae235b
{
Packit ae235b
  LONG Bias;
Packit ae235b
  LONG StandardBias;
Packit ae235b
  LONG DaylightBias;
Packit ae235b
  SYSTEMTIME StandardDate;
Packit ae235b
  SYSTEMTIME DaylightDate;
Packit ae235b
} RegTZI;
Packit ae235b
Packit ae235b
static void
Packit ae235b
system_time_copy (SYSTEMTIME *orig, SYSTEMTIME *target)
Packit ae235b
{
Packit ae235b
  g_return_if_fail (orig != NULL);
Packit ae235b
  g_return_if_fail (target != NULL);
Packit ae235b
Packit ae235b
  target->wYear = orig->wYear;
Packit ae235b
  target->wMonth = orig->wMonth;
Packit ae235b
  target->wDayOfWeek = orig->wDayOfWeek;
Packit ae235b
  target->wDay = orig->wDay;
Packit ae235b
  target->wHour = orig->wHour;
Packit ae235b
  target->wMinute = orig->wMinute;
Packit ae235b
  target->wSecond = orig->wSecond;
Packit ae235b
  target->wMilliseconds = orig->wMilliseconds;
Packit ae235b
}
Packit ae235b
Packit ae235b
static void
Packit ae235b
register_tzi_to_tzi (RegTZI *reg, TIME_ZONE_INFORMATION *tzi)
Packit ae235b
{
Packit ae235b
  g_return_if_fail (reg != NULL);
Packit ae235b
  g_return_if_fail (tzi != NULL);
Packit ae235b
  tzi->Bias = reg->Bias;
Packit ae235b
  system_time_copy (&(reg->StandardDate), &(tzi->StandardDate));
Packit ae235b
  tzi->StandardBias = reg->StandardBias;
Packit ae235b
  system_time_copy (&(reg->DaylightDate), &(tzi->DaylightDate));
Packit ae235b
  tzi->DaylightBias = reg->DaylightBias;
Packit ae235b
}
Packit ae235b
Packit ae235b
static gint
Packit ae235b
rules_from_windows_time_zone (const gchar *identifier, TimeZoneRule **rules)
Packit ae235b
{
Packit ae235b
  HKEY key;
Packit ae235b
  gchar *subkey, *subkey_dynamic;
Packit ae235b
  gchar *key_name = NULL;
Packit ae235b
  const gchar *reg_key =
Packit ae235b
    "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones\\";
Packit ae235b
  TIME_ZONE_INFORMATION tzi;
Packit ae235b
  DWORD size;
Packit ae235b
  gint rules_num = 0;
Packit ae235b
  RegTZI regtzi, regtzi_prev;
Packit ae235b
Packit ae235b
  *rules = NULL;
Packit ae235b
  key_name = NULL;
Packit ae235b
Packit ae235b
  if (!identifier)
Packit ae235b
    key_name = windows_default_tzname ();
Packit ae235b
  else
Packit ae235b
    key_name = g_strdup (identifier);
Packit ae235b
Packit ae235b
  if (!key_name)
Packit ae235b
    return 0;
Packit ae235b
Packit ae235b
  subkey = g_strconcat (reg_key, key_name, NULL);
Packit ae235b
  subkey_dynamic = g_strconcat (subkey, "\\Dynamic DST", NULL);
Packit ae235b
Packit ae235b
  if (RegOpenKeyExA (HKEY_LOCAL_MACHINE, subkey, 0,
Packit ae235b
                     KEY_QUERY_VALUE, &key) != ERROR_SUCCESS)
Packit ae235b
      return 0;
Packit ae235b
  size = sizeof tzi.StandardName;
Packit ae235b
  if (RegQueryValueExA (key, "Std", NULL, NULL,
Packit ae235b
                        (LPBYTE)&(tzi.StandardName), &size) != ERROR_SUCCESS)
Packit ae235b
    goto failed;
Packit ae235b
Packit ae235b
  size = sizeof tzi.DaylightName;
Packit ae235b
Packit ae235b
  if (RegQueryValueExA (key, "Dlt", NULL, NULL,
Packit ae235b
                        (LPBYTE)&(tzi.DaylightName), &size) != ERROR_SUCCESS)
Packit ae235b
    goto failed;
Packit ae235b
Packit ae235b
  RegCloseKey (key);
Packit ae235b
  if (RegOpenKeyExA (HKEY_LOCAL_MACHINE, subkey_dynamic, 0,
Packit ae235b
                     KEY_QUERY_VALUE, &key) == ERROR_SUCCESS)
Packit ae235b
    {
Packit ae235b
      DWORD first, last;
Packit ae235b
      int year, i;
Packit ae235b
      gchar *s;
Packit ae235b
Packit ae235b
      size = sizeof first;
Packit ae235b
      if (RegQueryValueExA (key, "FirstEntry", NULL, NULL,
Packit ae235b
                            (LPBYTE) &first, &size) != ERROR_SUCCESS)
Packit ae235b
        goto failed;
Packit ae235b
Packit ae235b
      size = sizeof last;
Packit ae235b
      if (RegQueryValueExA (key, "LastEntry", NULL, NULL,
Packit ae235b
                            (LPBYTE) &last, &size) != ERROR_SUCCESS)
Packit ae235b
        goto failed;
Packit ae235b
Packit ae235b
      rules_num = last - first + 2;
Packit ae235b
      *rules = g_new0 (TimeZoneRule, rules_num);
Packit ae235b
Packit ae235b
      for (year = first, i = 0; year <= last; year++)
Packit ae235b
        {
Packit ae235b
          s = g_strdup_printf ("%d", year);
Packit ae235b
Packit ae235b
          size = sizeof regtzi;
Packit ae235b
          if (RegQueryValueExA (key, s, NULL, NULL,
Packit ae235b
                            (LPBYTE) &regtzi, &size) != ERROR_SUCCESS)
Packit ae235b
            {
Packit ae235b
              g_free (*rules);
Packit ae235b
              *rules = NULL;
Packit ae235b
              break;
Packit ae235b
            }
Packit ae235b
Packit ae235b
          g_free (s);
Packit ae235b
Packit ae235b
          if (year > first && memcmp (&regtzi_prev, &regtzi, sizeof regtzi) == 0)
Packit ae235b
              continue;
Packit ae235b
          else
Packit ae235b
            memcpy (&regtzi_prev, &regtzi, sizeof regtzi);
Packit ae235b
Packit ae235b
          register_tzi_to_tzi (&regtzi, &tzi);
Packit ae235b
          rule_from_windows_time_zone_info (&(*rules)[i], &tzi);
Packit ae235b
          (*rules)[i++].start_year = year;
Packit ae235b
        }
Packit ae235b
Packit ae235b
      rules_num = i + 1;
Packit ae235b
Packit ae235b
failed:
Packit ae235b
      RegCloseKey (key);
Packit ae235b
    }
Packit ae235b
  else if (RegOpenKeyExA (HKEY_LOCAL_MACHINE, subkey, 0,
Packit ae235b
                          KEY_QUERY_VALUE, &key) == ERROR_SUCCESS)
Packit ae235b
    {
Packit ae235b
      size = sizeof regtzi;
Packit ae235b
      if (RegQueryValueExA (key, "TZI", NULL, NULL,
Packit ae235b
                            (LPBYTE) &regtzi, &size) == ERROR_SUCCESS)
Packit ae235b
        {
Packit ae235b
          rules_num = 2;
Packit ae235b
          *rules = g_new0 (TimeZoneRule, 2);
Packit ae235b
          register_tzi_to_tzi (&regtzi, &tzi);
Packit ae235b
          rule_from_windows_time_zone_info (&(*rules)[0], &tzi);
Packit ae235b
        }
Packit ae235b
Packit ae235b
      RegCloseKey (key);
Packit ae235b
    }
Packit ae235b
Packit ae235b
  g_free (subkey_dynamic);
Packit ae235b
  g_free (subkey);
Packit ae235b
  g_free (key_name);
Packit ae235b
Packit ae235b
  if (*rules)
Packit ae235b
    {
Packit ae235b
      (*rules)[0].start_year = MIN_TZYEAR;
Packit ae235b
      if ((*rules)[rules_num - 2].start_year < MAX_TZYEAR)
Packit ae235b
        (*rules)[rules_num - 1].start_year = MAX_TZYEAR;
Packit ae235b
      else
Packit ae235b
        (*rules)[rules_num - 1].start_year = (*rules)[rules_num - 2].start_year + 1;
Packit ae235b
Packit ae235b
      return rules_num;
Packit ae235b
    }
Packit ae235b
  else
Packit ae235b
    return 0;
Packit ae235b
}
Packit ae235b
Packit ae235b
#endif
Packit ae235b
Packit ae235b
static void
Packit ae235b
find_relative_date (TimeZoneDate *buffer)
Packit ae235b
{
Packit ae235b
  gint wday;
Packit ae235b
  GDate date;
Packit ae235b
  g_date_clear (&date, 1);
Packit ae235b
  wday = buffer->wday;
Packit ae235b
Packit ae235b
  /* Get last day if last is needed, first day otherwise */
Packit ae235b
  if (buffer->mon == 13 || buffer->mon == 14) /* Julian Date */
Packit ae235b
    {
Packit ae235b
      g_date_set_dmy (&date, 1, 1, buffer->year);
Packit ae235b
      if (wday >= 59 && buffer->mon == 13 && g_date_is_leap_year (buffer->year))
Packit ae235b
        g_date_add_days (&date, wday);
Packit ae235b
      else
Packit ae235b
        g_date_add_days (&date, wday - 1);
Packit ae235b
      buffer->mon = (int) g_date_get_month (&date);
Packit ae235b
      buffer->mday = (int) g_date_get_day (&date);
Packit ae235b
      buffer->wday = 0;
Packit ae235b
    }
Packit ae235b
  else /* M.W.D */
Packit ae235b
    {
Packit ae235b
      guint days;
Packit ae235b
      guint days_in_month = g_date_days_in_month (buffer->mon, buffer->year);
Packit ae235b
      GDateWeekday first_wday;
Packit ae235b
Packit ae235b
      g_date_set_dmy (&date, 1, buffer->mon, buffer->year);
Packit ae235b
      first_wday = g_date_get_weekday (&date);
Packit ae235b
Packit ae235b
      if (first_wday > wday)
Packit ae235b
        ++(buffer->week);
Packit ae235b
      /* week is 1 <= w <= 5, we need 0-based */
Packit ae235b
      days = 7 * (buffer->week - 1) + wday - first_wday;
Packit ae235b
Packit ae235b
      while (days > days_in_month)
Packit ae235b
        days -= 7;
Packit ae235b
Packit ae235b
      g_date_add_days (&date, days);
Packit ae235b
Packit ae235b
      buffer->mday = g_date_get_day (&date);
Packit ae235b
    }
Packit ae235b
}
Packit ae235b
Packit ae235b
/* Offset is previous offset of local time. Returns 0 if month is 0 */
Packit ae235b
static gint64
Packit ae235b
boundary_for_year (TimeZoneDate *boundary,
Packit ae235b
                   gint          year,
Packit ae235b
                   gint32        offset)
Packit ae235b
{
Packit ae235b
  TimeZoneDate buffer;
Packit ae235b
  GDate date;
Packit ae235b
  const guint64 unix_epoch_start = 719163L;
Packit ae235b
  const guint64 seconds_per_day = 86400L;
Packit ae235b
Packit ae235b
  if (!boundary->mon)
Packit ae235b
    return 0;
Packit ae235b
  buffer = *boundary;
Packit ae235b
Packit ae235b
  if (boundary->year == 0)
Packit ae235b
    {
Packit ae235b
      buffer.year = year;
Packit ae235b
Packit ae235b
      if (buffer.wday)
Packit ae235b
        find_relative_date (&buffer);
Packit ae235b
    }
Packit ae235b
Packit ae235b
  g_assert (buffer.year == year);
Packit ae235b
  g_date_clear (&date, 1);
Packit ae235b
  g_date_set_dmy (&date, buffer.mday, buffer.mon, buffer.year);
Packit ae235b
  return ((g_date_get_julian (&date) - unix_epoch_start) * seconds_per_day +
Packit ae235b
          buffer.hour * 3600 + buffer.min * 60 + buffer.sec - offset);
Packit ae235b
}
Packit ae235b
Packit ae235b
static void
Packit ae235b
fill_transition_info_from_rule (TransitionInfo *info,
Packit ae235b
                                TimeZoneRule   *rule,
Packit ae235b
                                gboolean        is_dst)
Packit ae235b
{
Packit ae235b
  gint offset = is_dst ? rule->dlt_offset : rule->std_offset;
Packit ae235b
  gchar *name = is_dst ? rule->dlt_name : rule->std_name;
Packit ae235b
Packit ae235b
  info->gmt_offset = offset;
Packit ae235b
  info->is_dst = is_dst;
Packit ae235b
Packit ae235b
  if (name)
Packit ae235b
    info->abbrev = g_strdup (name);
Packit ae235b
Packit ae235b
  else
Packit ae235b
    info->abbrev = g_strdup_printf ("%+03d%02d",
Packit ae235b
                                      (int) offset / 3600,
Packit ae235b
                                      (int) abs (offset / 60) % 60);
Packit ae235b
}
Packit ae235b
Packit ae235b
static void
Packit ae235b
init_zone_from_rules (GTimeZone    *gtz,
Packit ae235b
                      TimeZoneRule *rules,
Packit ae235b
                      gint          rules_num)
Packit ae235b
{
Packit ae235b
  guint type_count = 0, trans_count = 0, info_index = 0;
Packit ae235b
  guint ri; /* rule index */
Packit ae235b
  gboolean skip_first_std_trans = TRUE;
Packit ae235b
  gint32 last_offset;
Packit ae235b
Packit ae235b
  type_count = 0;
Packit ae235b
  trans_count = 0;
Packit ae235b
Packit ae235b
  /* Last rule only contains max year */
Packit ae235b
  for (ri = 0; ri < rules_num - 1; ri++)
Packit ae235b
    {
Packit ae235b
      if (rules[ri].dlt_start.mon || rules[ri].dlt_end.mon)
Packit ae235b
        {
Packit ae235b
          guint rulespan = (rules[ri + 1].start_year - rules[ri].start_year);
Packit ae235b
          guint transitions = rules[ri].dlt_start.mon > 0 ? 1 : 0;
Packit ae235b
          transitions += rules[ri].dlt_end.mon > 0 ? 1 : 0;
Packit ae235b
          type_count += rules[ri].dlt_start.mon > 0 ? 2 : 1;
Packit ae235b
          trans_count += transitions * rulespan;
Packit ae235b
        }
Packit ae235b
      else
Packit ae235b
        type_count++;
Packit ae235b
    }
Packit ae235b
Packit ae235b
  gtz->t_info = g_array_sized_new (FALSE, TRUE, sizeof (TransitionInfo), type_count);
Packit ae235b
  gtz->transitions = g_array_sized_new (FALSE, TRUE, sizeof (Transition), trans_count);
Packit ae235b
Packit ae235b
  last_offset = rules[0].std_offset;
Packit ae235b
Packit ae235b
  for (ri = 0; ri < rules_num - 1; ri++)
Packit ae235b
    {
Packit ae235b
      if ((rules[ri].std_offset || rules[ri].dlt_offset) &&
Packit ae235b
          rules[ri].dlt_start.mon == 0 && rules[ri].dlt_end.mon == 0)
Packit ae235b
        {
Packit ae235b
          TransitionInfo std_info;
Packit ae235b
          /* Standard */
Packit ae235b
          fill_transition_info_from_rule (&std_info, &(rules[ri]), FALSE);
Packit ae235b
          g_array_append_val (gtz->t_info, std_info);
Packit ae235b
Packit ae235b
          if (ri > 0 &&
Packit ae235b
              ((rules[ri - 1].dlt_start.mon > 12 &&
Packit ae235b
                rules[ri - 1].dlt_start.wday > rules[ri - 1].dlt_end.wday) ||
Packit ae235b
                rules[ri - 1].dlt_start.mon > rules[ri - 1].dlt_end.mon))
Packit ae235b
            {
Packit ae235b
              /* The previous rule was a southern hemisphere rule that
Packit ae235b
                 starts the year with DST, so we need to add a
Packit ae235b
                 transition to return to standard time */
Packit ae235b
              guint year = rules[ri].start_year;
Packit ae235b
              gint64 std_time =  boundary_for_year (&rules[ri].dlt_end,
Packit ae235b
                                                    year, last_offset);
Packit ae235b
              Transition std_trans = {std_time, info_index};
Packit ae235b
              g_array_append_val (gtz->transitions, std_trans);
Packit ae235b
Packit ae235b
            }
Packit ae235b
          last_offset = rules[ri].std_offset;
Packit ae235b
          ++info_index;
Packit ae235b
          skip_first_std_trans = TRUE;
Packit ae235b
         }
Packit ae235b
      else
Packit ae235b
        {
Packit ae235b
          const guint start_year = rules[ri].start_year;
Packit ae235b
          const guint end_year = rules[ri + 1].start_year;
Packit ae235b
          gboolean dlt_first;
Packit ae235b
          guint year;
Packit ae235b
          TransitionInfo std_info, dlt_info;
Packit ae235b
          if (rules[ri].dlt_start.mon > 12)
Packit ae235b
            dlt_first = rules[ri].dlt_start.wday > rules[ri].dlt_end.wday;
Packit ae235b
          else
Packit ae235b
            dlt_first = rules[ri].dlt_start.mon > rules[ri].dlt_end.mon;
Packit ae235b
          /* Standard rules are always even, because before the first
Packit ae235b
             transition is always standard time, and 0 is even. */
Packit ae235b
          fill_transition_info_from_rule (&std_info, &(rules[ri]), FALSE);
Packit ae235b
          fill_transition_info_from_rule (&dlt_info, &(rules[ri]), TRUE);
Packit ae235b
Packit ae235b
          g_array_append_val (gtz->t_info, std_info);
Packit ae235b
          g_array_append_val (gtz->t_info, dlt_info);
Packit ae235b
Packit ae235b
          /* Transition dates. We hope that a year which ends daylight
Packit ae235b
             time in a southern-hemisphere country (i.e., one that
Packit ae235b
             begins the year in daylight time) will include a rule
Packit ae235b
             which has only a dlt_end. */
Packit ae235b
          for (year = start_year; year < end_year; year++)
Packit ae235b
            {
Packit ae235b
              gint32 dlt_offset = (dlt_first ? last_offset :
Packit ae235b
                                   rules[ri].dlt_offset);
Packit ae235b
              gint32 std_offset = (dlt_first ? rules[ri].std_offset :
Packit ae235b
                                   last_offset);
Packit ae235b
              /* NB: boundary_for_year returns 0 if mon == 0 */
Packit ae235b
              gint64 std_time =  boundary_for_year (&rules[ri].dlt_end,
Packit ae235b
                                                    year, dlt_offset);
Packit ae235b
              gint64 dlt_time = boundary_for_year (&rules[ri].dlt_start,
Packit ae235b
                                                   year, std_offset);
Packit ae235b
              Transition std_trans = {std_time, info_index};
Packit ae235b
              Transition dlt_trans = {dlt_time, info_index + 1};
Packit ae235b
              last_offset = (dlt_first ? rules[ri].dlt_offset :
Packit ae235b
                             rules[ri].std_offset);
Packit ae235b
              if (dlt_first)
Packit ae235b
                {
Packit ae235b
                  if (skip_first_std_trans)
Packit ae235b
                    skip_first_std_trans = FALSE;
Packit ae235b
                  else if (std_time)
Packit ae235b
                    g_array_append_val (gtz->transitions, std_trans);
Packit ae235b
                  if (dlt_time)
Packit ae235b
                    g_array_append_val (gtz->transitions, dlt_trans);
Packit ae235b
                }
Packit ae235b
              else
Packit ae235b
                {
Packit ae235b
                  if (dlt_time)
Packit ae235b
                    g_array_append_val (gtz->transitions, dlt_trans);
Packit ae235b
                  if (std_time)
Packit ae235b
                    g_array_append_val (gtz->transitions, std_trans);
Packit ae235b
                }
Packit ae235b
            }
Packit ae235b
Packit ae235b
          info_index += 2;
Packit ae235b
        }
Packit ae235b
    }
Packit ae235b
  if (ri > 0 &&
Packit ae235b
      ((rules[ri - 1].dlt_start.mon > 12 &&
Packit ae235b
        rules[ri - 1].dlt_start.wday > rules[ri - 1].dlt_end.wday) ||
Packit ae235b
       rules[ri - 1].dlt_start.mon > rules[ri - 1].dlt_end.mon))
Packit ae235b
    {
Packit ae235b
      /* The previous rule was a southern hemisphere rule that
Packit ae235b
         starts the year with DST, so we need to add a
Packit ae235b
         transition to return to standard time */
Packit ae235b
      TransitionInfo info;
Packit ae235b
      guint year = rules[ri].start_year;
Packit ae235b
      Transition trans;
Packit ae235b
      fill_transition_info_from_rule (&info, &(rules[ri - 1]), FALSE);
Packit ae235b
      g_array_append_val (gtz->t_info, info);
Packit ae235b
      trans.time = boundary_for_year (&rules[ri - 1].dlt_end,
Packit ae235b
                                      year, last_offset);
Packit ae235b
      trans.info_index = info_index;
Packit ae235b
      g_array_append_val (gtz->transitions, trans);
Packit ae235b
     }
Packit ae235b
}
Packit ae235b
Packit ae235b
/*
Packit ae235b
 * parses date[/time] for parsing TZ environment variable
Packit ae235b
 *
Packit ae235b
 * date is either Mm.w.d, Jn or N
Packit ae235b
 * - m is 1 to 12
Packit ae235b
 * - w is 1 to 5
Packit ae235b
 * - d is 0 to 6
Packit ae235b
 * - n is 1 to 365
Packit ae235b
 * - N is 0 to 365
Packit ae235b
 *
Packit ae235b
 * time is either h or hh[[:]mm[[[:]ss]]]
Packit ae235b
 *  - h[h] is 0 to 23
Packit ae235b
 *  - mm is 00 to 59
Packit ae235b
 *  - ss is 00 to 59
Packit ae235b
 */
Packit ae235b
static gboolean
Packit ae235b
parse_mwd_boundary (gchar **pos, TimeZoneDate *boundary)
Packit ae235b
{
Packit ae235b
  gint month, week, day;
Packit ae235b
Packit ae235b
  if (**pos == '\0' || **pos < '0' || '9' < **pos)
Packit ae235b
    return FALSE;
Packit ae235b
Packit ae235b
  month = *(*pos)++ - '0';
Packit ae235b
Packit ae235b
  if ((month == 1 && **pos >= '0' && '2' >= **pos) ||
Packit ae235b
      (month == 0 && **pos >= '0' && '9' >= **pos))
Packit ae235b
    {
Packit ae235b
      month *= 10;
Packit ae235b
      month += *(*pos)++ - '0';
Packit ae235b
    }
Packit ae235b
Packit ae235b
  if (*(*pos)++ != '.' || month == 0)
Packit ae235b
    return FALSE;
Packit ae235b
Packit ae235b
  if (**pos == '\0' || **pos < '1' || '5' < **pos)
Packit ae235b
    return FALSE;
Packit ae235b
Packit ae235b
  week = *(*pos)++ - '0';
Packit ae235b
Packit ae235b
  if (*(*pos)++ != '.')
Packit ae235b
    return FALSE;
Packit ae235b
Packit ae235b
  if (**pos == '\0' || **pos < '0' || '6' < **pos)
Packit ae235b
    return FALSE;
Packit ae235b
Packit ae235b
  day = *(*pos)++ - '0';
Packit ae235b
Packit ae235b
  if (!day)
Packit ae235b
    day += 7;
Packit ae235b
Packit ae235b
  boundary->year = 0;
Packit ae235b
  boundary->mon = month;
Packit ae235b
  boundary->week = week;
Packit ae235b
  boundary->wday = day;
Packit ae235b
  return TRUE;
Packit ae235b
}
Packit ae235b
Packit ae235b
/* Different implementations of tzset interpret the Julian day field
Packit ae235b
   differently. For example, Linux specifies that it should be 1-based
Packit ae235b
   (1 Jan is JD 1) for both Jn and n formats, while zOS and BSD
Packit ae235b
   specify that a Jn JD is 1-based while an n JD is 0-based. Rather
Packit ae235b
   than trying to follow different specs, we will follow GDate's
Packit ae235b
   practice thatIn order to keep it simple, we will follow Linux's
Packit ae235b
   practice. */
Packit ae235b
Packit ae235b
static gboolean
Packit ae235b
parse_julian_boundary (gchar** pos, TimeZoneDate *boundary,
Packit ae235b
                       gboolean ignore_leap)
Packit ae235b
{
Packit ae235b
  gint day = 0;
Packit ae235b
  GDate date;
Packit ae235b
Packit ae235b
  while (**pos >= '0' && '9' >= **pos)
Packit ae235b
    {
Packit ae235b
      day *= 10;
Packit ae235b
      day += *(*pos)++ - '0';
Packit ae235b
    }
Packit ae235b
Packit ae235b
  if (day < 1 || 365 < day)
Packit ae235b
    return FALSE;
Packit ae235b
Packit ae235b
  g_date_clear (&date, 1);
Packit ae235b
  g_date_set_julian (&date, day);
Packit ae235b
  boundary->year = 0;
Packit ae235b
  boundary->mon = (int) g_date_get_month (&date);
Packit ae235b
  boundary->mday = (int) g_date_get_day (&date);
Packit ae235b
  boundary->wday = 0;
Packit ae235b
Packit ae235b
  if (!ignore_leap && day >= 59)
Packit ae235b
    boundary->mday++;
Packit ae235b
Packit ae235b
  return TRUE;
Packit ae235b
}
Packit ae235b
Packit ae235b
static gboolean
Packit ae235b
parse_tz_boundary (const gchar  *identifier,
Packit ae235b
                   TimeZoneDate *boundary)
Packit ae235b
{
Packit ae235b
  gchar *pos;
Packit ae235b
Packit ae235b
  pos = (gchar*)identifier;
Packit ae235b
  /* Month-week-weekday */
Packit ae235b
  if (*pos == 'M')
Packit ae235b
    {
Packit ae235b
      ++pos;
Packit ae235b
      if (!parse_mwd_boundary (&pos, boundary))
Packit ae235b
        return FALSE;
Packit ae235b
    }
Packit ae235b
  /* Julian date which ignores Feb 29 in leap years */
Packit ae235b
  else if (*pos == 'J')
Packit ae235b
    {
Packit ae235b
      ++pos;
Packit ae235b
      if (!parse_julian_boundary (&pos, boundary, FALSE))
Packit ae235b
        return FALSE ;
Packit ae235b
    }
Packit ae235b
  /* Julian date which counts Feb 29 in leap years */
Packit ae235b
  else if (*pos >= '0' && '9' >= *pos)
Packit ae235b
    {
Packit ae235b
      if (!parse_julian_boundary (&pos, boundary, TRUE))
Packit ae235b
        return FALSE;
Packit ae235b
    }
Packit ae235b
  else
Packit ae235b
    return FALSE;
Packit ae235b
Packit ae235b
  /* Time */
Packit ae235b
Packit ae235b
  if (*pos == '/')
Packit ae235b
    {
Packit ae235b
      gint32 offset;
Packit ae235b
Packit ae235b
      if (!parse_time (++pos, &offset))
Packit ae235b
        return FALSE;
Packit ae235b
Packit ae235b
      boundary->hour = offset / 3600;
Packit ae235b
      boundary->min = (offset / 60) % 60;
Packit ae235b
      boundary->sec = offset % 3600;
Packit ae235b
Packit ae235b
      return TRUE;
Packit ae235b
    }
Packit ae235b
Packit ae235b
  else
Packit ae235b
    {
Packit ae235b
      boundary->hour = 2;
Packit ae235b
      boundary->min = 0;
Packit ae235b
      boundary->sec = 0;
Packit ae235b
Packit ae235b
      return *pos == '\0';
Packit ae235b
    }
Packit ae235b
}
Packit ae235b
Packit ae235b
static gint
Packit ae235b
create_ruleset_from_rule (TimeZoneRule **rules, TimeZoneRule *rule)
Packit ae235b
{
Packit ae235b
  *rules = g_new0 (TimeZoneRule, 2);
Packit ae235b
Packit ae235b
  (*rules)[0].start_year = MIN_TZYEAR;
Packit ae235b
  (*rules)[1].start_year = MAX_TZYEAR;
Packit ae235b
Packit ae235b
  (*rules)[0].std_offset = -rule->std_offset;
Packit ae235b
  (*rules)[0].dlt_offset = -rule->dlt_offset;
Packit ae235b
  (*rules)[0].dlt_start  = rule->dlt_start;
Packit ae235b
  (*rules)[0].dlt_end = rule->dlt_end;
Packit ae235b
  strcpy ((*rules)[0].std_name, rule->std_name);
Packit ae235b
  strcpy ((*rules)[0].dlt_name, rule->dlt_name);
Packit ae235b
  return 2;
Packit ae235b
}
Packit ae235b
Packit ae235b
static gboolean
Packit ae235b
parse_offset (gchar **pos, gint32 *target)
Packit ae235b
{
Packit ae235b
  gchar *buffer;
Packit ae235b
  gchar *target_pos = *pos;
Packit ae235b
  gboolean ret;
Packit ae235b
Packit ae235b
  while (**pos == '+' || **pos == '-' || **pos == ':' ||
Packit ae235b
         (**pos >= '0' && '9' >= **pos))
Packit ae235b
    ++(*pos);
Packit ae235b
Packit ae235b
  buffer = g_strndup (target_pos, *pos - target_pos);
Packit ae235b
  ret = parse_constant_offset (buffer, target);
Packit ae235b
  g_free (buffer);
Packit ae235b
Packit ae235b
  return ret;
Packit ae235b
}
Packit ae235b
Packit ae235b
static gboolean
Packit ae235b
parse_identifier_boundary (gchar **pos, TimeZoneDate *target)
Packit ae235b
{
Packit ae235b
  gchar *buffer;
Packit ae235b
  gchar *target_pos = *pos;
Packit ae235b
  gboolean ret;
Packit ae235b
Packit ae235b
  while (**pos != ',' && **pos != '\0')
Packit ae235b
    ++(*pos);
Packit ae235b
  buffer = g_strndup (target_pos, *pos - target_pos);
Packit ae235b
  ret = parse_tz_boundary (buffer, target);
Packit ae235b
  g_free (buffer);
Packit ae235b
Packit ae235b
  return ret;
Packit ae235b
}
Packit ae235b
Packit ae235b
static gboolean
Packit ae235b
set_tz_name (gchar **pos, gchar *buffer, guint size)
Packit ae235b
{
Packit ae235b
  gchar *name_pos = *pos;
Packit ae235b
  guint len;
Packit ae235b
Packit ae235b
  /* Name is ASCII alpha (Is this necessarily true?) */
Packit ae235b
  while (g_ascii_isalpha (**pos))
Packit ae235b
    ++(*pos);
Packit ae235b
Packit ae235b
  /* Name should be three or more alphabetic characters */
Packit ae235b
  if (*pos - name_pos < 3)
Packit ae235b
    return FALSE;
Packit ae235b
Packit ae235b
  memset (buffer, 0, NAME_SIZE);
Packit ae235b
  /* name_pos isn't 0-terminated, so we have to limit the length expressly */
Packit ae235b
  len = *pos - name_pos > size - 1 ? size - 1 : *pos - name_pos;
Packit ae235b
  strncpy (buffer, name_pos, len);
Packit ae235b
  return TRUE;
Packit ae235b
}
Packit ae235b
Packit ae235b
static gboolean
Packit ae235b
parse_identifier_boundaries (gchar **pos, TimeZoneRule *tzr)
Packit ae235b
{
Packit ae235b
  if (*(*pos)++ != ',')
Packit ae235b
    return FALSE;
Packit ae235b
Packit ae235b
  /* Start date */
Packit ae235b
  if (!parse_identifier_boundary (pos, &(tzr->dlt_start)) || *(*pos)++ != ',')
Packit ae235b
    return FALSE;
Packit ae235b
Packit ae235b
  /* End date */
Packit ae235b
  if (!parse_identifier_boundary (pos, &(tzr->dlt_end)))
Packit ae235b
    return FALSE;
Packit ae235b
  return TRUE;
Packit ae235b
}
Packit ae235b
Packit ae235b
/*
Packit ae235b
 * Creates an array of TimeZoneRule from a TZ environment variable
Packit ae235b
 * type of identifier.  Should free rules afterwards
Packit ae235b
 */
Packit ae235b
static gint
Packit ae235b
rules_from_identifier (const gchar   *identifier,
Packit ae235b
                       TimeZoneRule **rules)
Packit ae235b
{
Packit ae235b
  gchar *pos;
Packit ae235b
  TimeZoneRule tzr;
Packit ae235b
Packit ae235b
  if (!identifier)
Packit ae235b
    return 0;
Packit ae235b
Packit ae235b
  pos = (gchar*)identifier;
Packit ae235b
  memset (&tzr, 0, sizeof (tzr));
Packit ae235b
  /* Standard offset */
Packit ae235b
  if (!(set_tz_name (&pos, tzr.std_name, NAME_SIZE)) ||
Packit ae235b
      !parse_offset (&pos, &(tzr.std_offset)))
Packit ae235b
    return 0;
Packit ae235b
Packit ae235b
  if (*pos == 0)
Packit ae235b
    return create_ruleset_from_rule (rules, &tzr);
Packit ae235b
Packit ae235b
  /* Format 2 */
Packit ae235b
  if (!(set_tz_name (&pos, tzr.dlt_name, NAME_SIZE)))
Packit ae235b
    return 0;
Packit ae235b
  parse_offset (&pos, &(tzr.dlt_offset));
Packit ae235b
  if (tzr.dlt_offset == 0) /* No daylight offset given, assume it's 1
Packit ae235b
                              hour earlier that standard */
Packit ae235b
    tzr.dlt_offset = tzr.std_offset - 3600;
Packit ae235b
  if (*pos == '\0')
Packit ae235b
#ifdef G_OS_WIN32
Packit ae235b
    /* Windows allows us to use the US DST boundaries if they're not given */
Packit ae235b
    {
Packit ae235b
      int i;
Packit ae235b
      guint rules_num = 0;
Packit ae235b
Packit ae235b
      /* Use US rules, Windows' default is Pacific Standard Time */
Packit ae235b
      if ((rules_num = rules_from_windows_time_zone ("Pacific Standard Time",
Packit ae235b
                                                     rules)))
Packit ae235b
        {
Packit ae235b
          for (i = 0; i < rules_num - 1; i++)
Packit ae235b
            {
Packit ae235b
              (*rules)[i].std_offset = - tzr.std_offset;
Packit ae235b
              (*rules)[i].dlt_offset = - tzr.dlt_offset;
Packit ae235b
              strcpy ((*rules)[i].std_name, tzr.std_name);
Packit ae235b
              strcpy ((*rules)[i].dlt_name, tzr.dlt_name);
Packit ae235b
            }
Packit ae235b
Packit ae235b
          return rules_num;
Packit ae235b
        }
Packit ae235b
      else
Packit ae235b
        return 0;
Packit ae235b
    }
Packit ae235b
#else
Packit ae235b
  return 0;
Packit ae235b
#endif
Packit ae235b
  /* Start and end required (format 2) */
Packit ae235b
  if (!parse_identifier_boundaries (&pos, &tzr))
Packit ae235b
    return 0;
Packit ae235b
Packit ae235b
  return create_ruleset_from_rule (rules, &tzr);
Packit ae235b
}
Packit ae235b
Packit ae235b
/* Construction {{{1 */
Packit ae235b
/**
Packit ae235b
 * g_time_zone_new:
Packit ae235b
 * @identifier: (nullable): a timezone identifier
Packit ae235b
 *
Packit ae235b
 * Creates a #GTimeZone corresponding to @identifier.
Packit ae235b
 *
Packit ae235b
 * @identifier can either be an RFC3339/ISO 8601 time offset or
Packit ae235b
 * something that would pass as a valid value for the `TZ` environment
Packit ae235b
 * variable (including %NULL).
Packit ae235b
 *
Packit ae235b
 * In Windows, @identifier can also be the unlocalized name of a time
Packit ae235b
 * zone for standard time, for example "Pacific Standard Time".
Packit ae235b
 *
Packit ae235b
 * Valid RFC3339 time offsets are `"Z"` (for UTC) or
Packit ae235b
 * `"±hh:mm"`.  ISO 8601 additionally specifies
Packit ae235b
 * `"±hhmm"` and `"±hh"`.  Offsets are
Packit ae235b
 * time values to be added to Coordinated Universal Time (UTC) to get
Packit ae235b
 * the local time.
Packit ae235b
 *
Packit ae235b
 * In UNIX, the `TZ` environment variable typically corresponds
Packit ae235b
 * to the name of a file in the zoneinfo database, or string in
Packit ae235b
 * "std offset [dst [offset],start[/time],end[/time]]" (POSIX) format.
Packit ae235b
 * There  are  no spaces in the specification. The name of standard
Packit ae235b
 * and daylight savings time zone must be three or more alphabetic
Packit ae235b
 * characters. Offsets are time values to be added to local time to
Packit ae235b
 * get Coordinated Universal Time (UTC) and should be
Packit ae235b
 * `"[±]hh[[:]mm[:ss]]"`.  Dates are either
Packit ae235b
 * `"Jn"` (Julian day with n between 1 and 365, leap
Packit ae235b
 * years not counted), `"n"` (zero-based Julian day
Packit ae235b
 * with n between 0 and 365) or `"Mm.w.d"` (day d
Packit ae235b
 * (0 <= d <= 6) of week w (1 <= w <= 5) of month m (1 <= m <= 12), day
Packit ae235b
 * 0 is a Sunday).  Times are in local wall clock time, the default is
Packit ae235b
 * 02:00:00.
Packit ae235b
 *
Packit ae235b
 * In Windows, the "tzn[+|–]hh[:mm[:ss]][dzn]" format is used, but also
Packit ae235b
 * accepts POSIX format.  The Windows format uses US rules for all time
Packit ae235b
 * zones; daylight savings time is 60 minutes behind the standard time
Packit ae235b
 * with date and time of change taken from Pacific Standard Time.
Packit ae235b
 * Offsets are time values to be added to the local time to get
Packit ae235b
 * Coordinated Universal Time (UTC).
Packit ae235b
 *
Packit ae235b
 * g_time_zone_new_local() calls this function with the value of the
Packit ae235b
 * `TZ` environment variable. This function itself is independent of
Packit ae235b
 * the value of `TZ`, but if @identifier is %NULL then `/etc/localtime`
Packit ae235b
 * will be consulted to discover the correct time zone on UNIX and the
Packit ae235b
 * registry will be consulted or GetTimeZoneInformation() will be used
Packit ae235b
 * to get the local time zone on Windows.
Packit ae235b
 *
Packit ae235b
 * If intervals are not available, only time zone rules from `TZ`
Packit ae235b
 * environment variable or other means, then they will be computed
Packit ae235b
 * from year 1900 to 2037.  If the maximum year for the rules is
Packit ae235b
 * available and it is greater than 2037, then it will followed
Packit ae235b
 * instead.
Packit ae235b
 *
Packit ae235b
 * See
Packit ae235b
 * [RFC3339 §5.6](http://tools.ietf.org/html/rfc3339#section-5.6)
Packit ae235b
 * for a precise definition of valid RFC3339 time offsets
Packit ae235b
 * (the `time-offset` expansion) and ISO 8601 for the
Packit ae235b
 * full list of valid time offsets.  See
Packit ae235b
 * [The GNU C Library manual](http://www.gnu.org/s/libc/manual/html_node/TZ-Variable.html)
Packit ae235b
 * for an explanation of the possible
Packit ae235b
 * values of the `TZ` environment variable. See
Packit ae235b
 * [Microsoft Time Zone Index Values](http://msdn.microsoft.com/en-us/library/ms912391%28v=winembedded.11%29.aspx)
Packit ae235b
 * for the list of time zones on Windows.
Packit ae235b
 *
Packit ae235b
 * You should release the return value by calling g_time_zone_unref()
Packit ae235b
 * when you are done with it.
Packit ae235b
 *
Packit ae235b
 * Returns: the requested timezone
Packit ae235b
 *
Packit ae235b
 * Since: 2.26
Packit ae235b
 **/
Packit ae235b
GTimeZone *
Packit ae235b
g_time_zone_new (const gchar *identifier)
Packit ae235b
{
Packit ae235b
  GTimeZone *tz = NULL;
Packit ae235b
  TimeZoneRule *rules;
Packit ae235b
  gint rules_num;
Packit ae235b
Packit ae235b
  G_LOCK (time_zones);
Packit ae235b
  if (time_zones == NULL)
Packit ae235b
    time_zones = g_hash_table_new (g_str_hash, g_str_equal);
Packit ae235b
Packit ae235b
  if (identifier)
Packit ae235b
    {
Packit ae235b
      tz = g_hash_table_lookup (time_zones, identifier);
Packit ae235b
      if (tz)
Packit ae235b
        {
Packit ae235b
          g_atomic_int_inc (&tz->ref_count);
Packit ae235b
          G_UNLOCK (time_zones);
Packit ae235b
          return tz;
Packit ae235b
        }
Packit ae235b
    }
Packit ae235b
Packit ae235b
  tz = g_slice_new0 (GTimeZone);
Packit ae235b
  tz->name = g_strdup (identifier);
Packit ae235b
  tz->ref_count = 0;
Packit ae235b
Packit ae235b
  zone_for_constant_offset (tz, identifier);
Packit ae235b
Packit ae235b
  if (tz->t_info == NULL &&
Packit ae235b
      (rules_num = rules_from_identifier (identifier, &rules)))
Packit ae235b
    {
Packit ae235b
      init_zone_from_rules (tz, rules, rules_num);
Packit ae235b
      g_free (rules);
Packit ae235b
    }
Packit ae235b
Packit ae235b
  if (tz->t_info == NULL)
Packit ae235b
    {
Packit ae235b
#ifdef G_OS_UNIX
Packit ae235b
      GBytes *zoneinfo = zone_info_unix (identifier);
Packit ae235b
      if (!zoneinfo)
Packit ae235b
        zone_for_constant_offset (tz, "UTC");
Packit ae235b
      else
Packit ae235b
        {
Packit ae235b
          init_zone_from_iana_info (tz, zoneinfo);
Packit ae235b
          g_bytes_unref (zoneinfo);
Packit ae235b
        }
Packit ae235b
#elif defined (G_OS_WIN32)
Packit ae235b
      if ((rules_num = rules_from_windows_time_zone (identifier, &rules)))
Packit ae235b
        {
Packit ae235b
          init_zone_from_rules (tz, rules, rules_num);
Packit ae235b
          g_free (rules);
Packit ae235b
        }
Packit ae235b
    }
Packit ae235b
Packit ae235b
  if (tz->t_info == NULL)
Packit ae235b
    {
Packit ae235b
      if (identifier)
Packit ae235b
        zone_for_constant_offset (tz, "UTC");
Packit ae235b
      else
Packit ae235b
        {
Packit ae235b
          TIME_ZONE_INFORMATION tzi;
Packit ae235b
Packit ae235b
          if (GetTimeZoneInformation (&tzi) != TIME_ZONE_ID_INVALID)
Packit ae235b
            {
Packit ae235b
              rules = g_new0 (TimeZoneRule, 2);
Packit ae235b
Packit ae235b
              rule_from_windows_time_zone_info (&rules[0], &tzi);
Packit ae235b
Packit ae235b
              memset (rules[0].std_name, 0, NAME_SIZE);
Packit ae235b
              memset (rules[0].dlt_name, 0, NAME_SIZE);
Packit ae235b
Packit ae235b
              rules[0].start_year = MIN_TZYEAR;
Packit ae235b
              rules[1].start_year = MAX_TZYEAR;
Packit ae235b
Packit ae235b
              init_zone_from_rules (tz, rules, 2);
Packit ae235b
Packit ae235b
              g_free (rules);
Packit ae235b
            }
Packit ae235b
        }
Packit ae235b
#endif
Packit ae235b
    }
Packit ae235b
Packit ae235b
  if (tz->t_info != NULL)
Packit ae235b
    {
Packit ae235b
      if (identifier)
Packit ae235b
        g_hash_table_insert (time_zones, tz->name, tz);
Packit ae235b
    }
Packit ae235b
  g_atomic_int_inc (&tz->ref_count);
Packit ae235b
  G_UNLOCK (time_zones);
Packit ae235b
Packit ae235b
  return tz;
Packit ae235b
}
Packit ae235b
Packit ae235b
/**
Packit ae235b
 * g_time_zone_new_utc:
Packit ae235b
 *
Packit ae235b
 * Creates a #GTimeZone corresponding to UTC.
Packit ae235b
 *
Packit ae235b
 * This is equivalent to calling g_time_zone_new() with a value like
Packit ae235b
 * "Z", "UTC", "+00", etc.
Packit ae235b
 *
Packit ae235b
 * You should release the return value by calling g_time_zone_unref()
Packit ae235b
 * when you are done with it.
Packit ae235b
 *
Packit ae235b
 * Returns: the universal timezone
Packit ae235b
 *
Packit ae235b
 * Since: 2.26
Packit ae235b
 **/
Packit ae235b
GTimeZone *
Packit ae235b
g_time_zone_new_utc (void)
Packit ae235b
{
Packit ae235b
  return g_time_zone_new ("UTC");
Packit ae235b
}
Packit ae235b
Packit ae235b
/**
Packit ae235b
 * g_time_zone_new_local:
Packit ae235b
 *
Packit ae235b
 * Creates a #GTimeZone corresponding to local time.  The local time
Packit ae235b
 * zone may change between invocations to this function; for example,
Packit ae235b
 * if the system administrator changes it.
Packit ae235b
 *
Packit ae235b
 * This is equivalent to calling g_time_zone_new() with the value of
Packit ae235b
 * the `TZ` environment variable (including the possibility of %NULL).
Packit ae235b
 *
Packit ae235b
 * You should release the return value by calling g_time_zone_unref()
Packit ae235b
 * when you are done with it.
Packit ae235b
 *
Packit ae235b
 * Returns: the local timezone
Packit ae235b
 *
Packit ae235b
 * Since: 2.26
Packit ae235b
 **/
Packit ae235b
GTimeZone *
Packit ae235b
g_time_zone_new_local (void)
Packit ae235b
{
Packit ae235b
  return g_time_zone_new (getenv ("TZ"));
Packit ae235b
}
Packit ae235b
Packit ae235b
#define TRANSITION(n)         g_array_index (tz->transitions, Transition, n)
Packit ae235b
#define TRANSITION_INFO(n)    g_array_index (tz->t_info, TransitionInfo, n)
Packit ae235b
Packit ae235b
/* Internal helpers {{{1 */
Packit ae235b
/* NB: Interval 0 is before the first transition, so there's no
Packit ae235b
 * transition structure to point to which TransitionInfo to
Packit ae235b
 * use. Rule-based zones are set up so that TI 0 is always standard
Packit ae235b
 * time (which is what's in effect before Daylight time got started
Packit ae235b
 * in the early 20th century), but IANA tzfiles don't follow that
Packit ae235b
 * convention. The tzfile documentation says to use the first
Packit ae235b
 * standard-time (i.e., non-DST) tinfo, so that's what we do.
Packit ae235b
 */
Packit ae235b
inline static const TransitionInfo*
Packit ae235b
interval_info (GTimeZone *tz,
Packit ae235b
               guint      interval)
Packit ae235b
{
Packit ae235b
  guint index;
Packit ae235b
  g_return_val_if_fail (tz->t_info != NULL, NULL);
Packit ae235b
  if (interval && tz->transitions && interval <= tz->transitions->len)
Packit ae235b
    index = (TRANSITION(interval - 1)).info_index;
Packit ae235b
  else
Packit ae235b
    {
Packit ae235b
      for (index = 0; index < tz->t_info->len; index++)
Packit ae235b
        {
Packit ae235b
          TransitionInfo *tzinfo = &(TRANSITION_INFO(index));
Packit ae235b
          if (!tzinfo->is_dst)
Packit ae235b
            return tzinfo;
Packit ae235b
        }
Packit ae235b
      index = 0;
Packit ae235b
    }
Packit ae235b
Packit ae235b
  return &(TRANSITION_INFO(index));
Packit ae235b
}
Packit ae235b
Packit ae235b
inline static gint64
Packit ae235b
interval_start (GTimeZone *tz,
Packit ae235b
                guint      interval)
Packit ae235b
{
Packit ae235b
  if (!interval || tz->transitions == NULL || tz->transitions->len == 0)
Packit ae235b
    return G_MININT64;
Packit ae235b
  if (interval > tz->transitions->len)
Packit ae235b
    interval = tz->transitions->len;
Packit ae235b
  return (TRANSITION(interval - 1)).time;
Packit ae235b
}
Packit ae235b
Packit ae235b
inline static gint64
Packit ae235b
interval_end (GTimeZone *tz,
Packit ae235b
              guint      interval)
Packit ae235b
{
Packit ae235b
  if (tz->transitions && interval < tz->transitions->len)
Packit ae235b
    return (TRANSITION(interval)).time - 1;
Packit ae235b
  return G_MAXINT64;
Packit ae235b
}
Packit ae235b
Packit ae235b
inline static gint32
Packit ae235b
interval_offset (GTimeZone *tz,
Packit ae235b
                 guint      interval)
Packit ae235b
{
Packit ae235b
  g_return_val_if_fail (tz->t_info != NULL, 0);
Packit ae235b
  return interval_info (tz, interval)->gmt_offset;
Packit ae235b
}
Packit ae235b
Packit ae235b
inline static gboolean
Packit ae235b
interval_isdst (GTimeZone *tz,
Packit ae235b
                guint      interval)
Packit ae235b
{
Packit ae235b
  g_return_val_if_fail (tz->t_info != NULL, 0);
Packit ae235b
  return interval_info (tz, interval)->is_dst;
Packit ae235b
}
Packit ae235b
Packit ae235b
Packit ae235b
inline static gchar*
Packit ae235b
interval_abbrev (GTimeZone *tz,
Packit ae235b
                  guint      interval)
Packit ae235b
{
Packit ae235b
  g_return_val_if_fail (tz->t_info != NULL, 0);
Packit ae235b
  return interval_info (tz, interval)->abbrev;
Packit ae235b
}
Packit ae235b
Packit ae235b
inline static gint64
Packit ae235b
interval_local_start (GTimeZone *tz,
Packit ae235b
                      guint      interval)
Packit ae235b
{
Packit ae235b
  if (interval)
Packit ae235b
    return interval_start (tz, interval) + interval_offset (tz, interval);
Packit ae235b
Packit ae235b
  return G_MININT64;
Packit ae235b
}
Packit ae235b
Packit ae235b
inline static gint64
Packit ae235b
interval_local_end (GTimeZone *tz,
Packit ae235b
                    guint      interval)
Packit ae235b
{
Packit ae235b
  if (tz->transitions && interval < tz->transitions->len)
Packit ae235b
    return interval_end (tz, interval) + interval_offset (tz, interval);
Packit ae235b
Packit ae235b
  return G_MAXINT64;
Packit ae235b
}
Packit ae235b
Packit ae235b
static gboolean
Packit ae235b
interval_valid (GTimeZone *tz,
Packit ae235b
                guint      interval)
Packit ae235b
{
Packit ae235b
  if ( tz->transitions == NULL)
Packit ae235b
    return interval == 0;
Packit ae235b
  return interval <= tz->transitions->len;
Packit ae235b
}
Packit ae235b
Packit ae235b
/* g_time_zone_find_interval() {{{1 */
Packit ae235b
Packit ae235b
/**
Packit ae235b
 * g_time_zone_adjust_time:
Packit ae235b
 * @tz: a #GTimeZone
Packit ae235b
 * @type: the #GTimeType of @time_
Packit ae235b
 * @time_: a pointer to a number of seconds since January 1, 1970
Packit ae235b
 *
Packit ae235b
 * Finds an interval within @tz that corresponds to the given @time_,
Packit ae235b
 * possibly adjusting @time_ if required to fit into an interval.
Packit ae235b
 * The meaning of @time_ depends on @type.
Packit ae235b
 *
Packit ae235b
 * This function is similar to g_time_zone_find_interval(), with the
Packit ae235b
 * difference that it always succeeds (by making the adjustments
Packit ae235b
 * described below).
Packit ae235b
 *
Packit ae235b
 * In any of the cases where g_time_zone_find_interval() succeeds then
Packit ae235b
 * this function returns the same value, without modifying @time_.
Packit ae235b
 *
Packit ae235b
 * This function may, however, modify @time_ in order to deal with
Packit ae235b
 * non-existent times.  If the non-existent local @time_ of 02:30 were
Packit ae235b
 * requested on March 14th 2010 in Toronto then this function would
Packit ae235b
 * adjust @time_ to be 03:00 and return the interval containing the
Packit ae235b
 * adjusted time.
Packit ae235b
 *
Packit ae235b
 * Returns: the interval containing @time_, never -1
Packit ae235b
 *
Packit ae235b
 * Since: 2.26
Packit ae235b
 **/
Packit ae235b
gint
Packit ae235b
g_time_zone_adjust_time (GTimeZone *tz,
Packit ae235b
                         GTimeType  type,
Packit ae235b
                         gint64    *time_)
Packit ae235b
{
Packit ae235b
  gint i;
Packit ae235b
  guint intervals;
Packit ae235b
Packit ae235b
  if (tz->transitions == NULL)
Packit ae235b
    return 0;
Packit ae235b
Packit ae235b
  intervals = tz->transitions->len;
Packit ae235b
Packit ae235b
  /* find the interval containing *time UTC
Packit ae235b
   * TODO: this could be binary searched (or better) */
Packit ae235b
  for (i = 0; i <= intervals; i++)
Packit ae235b
    if (*time_ <= interval_end (tz, i))
Packit ae235b
      break;
Packit ae235b
Packit ae235b
  g_assert (interval_start (tz, i) <= *time_ && *time_ <= interval_end (tz, i));
Packit ae235b
Packit ae235b
  if (type != G_TIME_TYPE_UNIVERSAL)
Packit ae235b
    {
Packit ae235b
      if (*time_ < interval_local_start (tz, i))
Packit ae235b
        /* if time came before the start of this interval... */
Packit ae235b
        {
Packit ae235b
          i--;
Packit ae235b
Packit ae235b
          /* if it's not in the previous interval... */
Packit ae235b
          if (*time_ > interval_local_end (tz, i))
Packit ae235b
            {
Packit ae235b
              /* it doesn't exist.  fast-forward it. */
Packit ae235b
              i++;
Packit ae235b
              *time_ = interval_local_start (tz, i);
Packit ae235b
            }
Packit ae235b
        }
Packit ae235b
Packit ae235b
      else if (*time_ > interval_local_end (tz, i))
Packit ae235b
        /* if time came after the end of this interval... */
Packit ae235b
        {
Packit ae235b
          i++;
Packit ae235b
Packit ae235b
          /* if it's not in the next interval... */
Packit ae235b
          if (*time_ < interval_local_start (tz, i))
Packit ae235b
            /* it doesn't exist.  fast-forward it. */
Packit ae235b
            *time_ = interval_local_start (tz, i);
Packit ae235b
        }
Packit ae235b
Packit ae235b
      else if (interval_isdst (tz, i) != type)
Packit ae235b
        /* it's in this interval, but dst flag doesn't match.
Packit ae235b
         * check neighbours for a better fit. */
Packit ae235b
        {
Packit ae235b
          if (i && *time_ <= interval_local_end (tz, i - 1))
Packit ae235b
            i--;
Packit ae235b
Packit ae235b
          else if (i < intervals &&
Packit ae235b
                   *time_ >= interval_local_start (tz, i + 1))
Packit ae235b
            i++;
Packit ae235b
        }
Packit ae235b
    }
Packit ae235b
Packit ae235b
  return i;
Packit ae235b
}
Packit ae235b
Packit ae235b
/**
Packit ae235b
 * g_time_zone_find_interval:
Packit ae235b
 * @tz: a #GTimeZone
Packit ae235b
 * @type: the #GTimeType of @time_
Packit ae235b
 * @time_: a number of seconds since January 1, 1970
Packit ae235b
 *
Packit ae235b
 * Finds an the interval within @tz that corresponds to the given @time_.
Packit ae235b
 * The meaning of @time_ depends on @type.
Packit ae235b
 *
Packit ae235b
 * If @type is %G_TIME_TYPE_UNIVERSAL then this function will always
Packit ae235b
 * succeed (since universal time is monotonic and continuous).
Packit ae235b
 *
Packit ae235b
 * Otherwise @time_ is treated as local time.  The distinction between
Packit ae235b
 * %G_TIME_TYPE_STANDARD and %G_TIME_TYPE_DAYLIGHT is ignored except in
Packit ae235b
 * the case that the given @time_ is ambiguous.  In Toronto, for example,
Packit ae235b
 * 01:30 on November 7th 2010 occurred twice (once inside of daylight
Packit ae235b
 * savings time and the next, an hour later, outside of daylight savings
Packit ae235b
 * time).  In this case, the different value of @type would result in a
Packit ae235b
 * different interval being returned.
Packit ae235b
 *
Packit ae235b
 * It is still possible for this function to fail.  In Toronto, for
Packit ae235b
 * example, 02:00 on March 14th 2010 does not exist (due to the leap
Packit ae235b
 * forward to begin daylight savings time).  -1 is returned in that
Packit ae235b
 * case.
Packit ae235b
 *
Packit ae235b
 * Returns: the interval containing @time_, or -1 in case of failure
Packit ae235b
 *
Packit ae235b
 * Since: 2.26
Packit ae235b
 */
Packit ae235b
gint
Packit ae235b
g_time_zone_find_interval (GTimeZone *tz,
Packit ae235b
                           GTimeType  type,
Packit ae235b
                           gint64     time_)
Packit ae235b
{
Packit ae235b
  gint i;
Packit ae235b
  guint intervals;
Packit ae235b
Packit ae235b
  if (tz->transitions == NULL)
Packit ae235b
    return 0;
Packit ae235b
  intervals = tz->transitions->len;
Packit ae235b
  for (i = 0; i <= intervals; i++)
Packit ae235b
    if (time_ <= interval_end (tz, i))
Packit ae235b
      break;
Packit ae235b
Packit ae235b
  if (type == G_TIME_TYPE_UNIVERSAL)
Packit ae235b
    return i;
Packit ae235b
Packit ae235b
  if (time_ < interval_local_start (tz, i))
Packit ae235b
    {
Packit ae235b
      if (time_ > interval_local_end (tz, --i))
Packit ae235b
        return -1;
Packit ae235b
    }
Packit ae235b
Packit ae235b
  else if (time_ > interval_local_end (tz, i))
Packit ae235b
    {
Packit ae235b
      if (time_ < interval_local_start (tz, ++i))
Packit ae235b
        return -1;
Packit ae235b
    }
Packit ae235b
Packit ae235b
  else if (interval_isdst (tz, i) != type)
Packit ae235b
    {
Packit ae235b
      if (i && time_ <= interval_local_end (tz, i - 1))
Packit ae235b
        i--;
Packit ae235b
Packit ae235b
      else if (i < intervals && time_ >= interval_local_start (tz, i + 1))
Packit ae235b
        i++;
Packit ae235b
    }
Packit ae235b
Packit ae235b
  return i;
Packit ae235b
}
Packit ae235b
Packit ae235b
/* Public API accessors {{{1 */
Packit ae235b
Packit ae235b
/**
Packit ae235b
 * g_time_zone_get_abbreviation:
Packit ae235b
 * @tz: a #GTimeZone
Packit ae235b
 * @interval: an interval within the timezone
Packit ae235b
 *
Packit ae235b
 * Determines the time zone abbreviation to be used during a particular
Packit ae235b
 * @interval of time in the time zone @tz.
Packit ae235b
 *
Packit ae235b
 * For example, in Toronto this is currently "EST" during the winter
Packit ae235b
 * months and "EDT" during the summer months when daylight savings time
Packit ae235b
 * is in effect.
Packit ae235b
 *
Packit ae235b
 * Returns: the time zone abbreviation, which belongs to @tz
Packit ae235b
 *
Packit ae235b
 * Since: 2.26
Packit ae235b
 **/
Packit ae235b
const gchar *
Packit ae235b
g_time_zone_get_abbreviation (GTimeZone *tz,
Packit ae235b
                              gint       interval)
Packit ae235b
{
Packit ae235b
  g_return_val_if_fail (interval_valid (tz, (guint)interval), NULL);
Packit ae235b
Packit ae235b
  return interval_abbrev (tz, (guint)interval);
Packit ae235b
}
Packit ae235b
Packit ae235b
/**
Packit ae235b
 * g_time_zone_get_offset:
Packit ae235b
 * @tz: a #GTimeZone
Packit ae235b
 * @interval: an interval within the timezone
Packit ae235b
 *
Packit ae235b
 * Determines the offset to UTC in effect during a particular @interval
Packit ae235b
 * of time in the time zone @tz.
Packit ae235b
 *
Packit ae235b
 * The offset is the number of seconds that you add to UTC time to
Packit ae235b
 * arrive at local time for @tz (ie: negative numbers for time zones
Packit ae235b
 * west of GMT, positive numbers for east).
Packit ae235b
 *
Packit ae235b
 * Returns: the number of seconds that should be added to UTC to get the
Packit ae235b
 *          local time in @tz
Packit ae235b
 *
Packit ae235b
 * Since: 2.26
Packit ae235b
 **/
Packit ae235b
gint32
Packit ae235b
g_time_zone_get_offset (GTimeZone *tz,
Packit ae235b
                        gint       interval)
Packit ae235b
{
Packit ae235b
  g_return_val_if_fail (interval_valid (tz, (guint)interval), 0);
Packit ae235b
Packit ae235b
  return interval_offset (tz, (guint)interval);
Packit ae235b
}
Packit ae235b
Packit ae235b
/**
Packit ae235b
 * g_time_zone_is_dst:
Packit ae235b
 * @tz: a #GTimeZone
Packit ae235b
 * @interval: an interval within the timezone
Packit ae235b
 *
Packit ae235b
 * Determines if daylight savings time is in effect during a particular
Packit ae235b
 * @interval of time in the time zone @tz.
Packit ae235b
 *
Packit ae235b
 * Returns: %TRUE if daylight savings time is in effect
Packit ae235b
 *
Packit ae235b
 * Since: 2.26
Packit ae235b
 **/
Packit ae235b
gboolean
Packit ae235b
g_time_zone_is_dst (GTimeZone *tz,
Packit ae235b
                    gint       interval)
Packit ae235b
{
Packit ae235b
  g_return_val_if_fail (interval_valid (tz, interval), FALSE);
Packit ae235b
Packit ae235b
  if (tz->transitions == NULL)
Packit ae235b
    return FALSE;
Packit ae235b
Packit ae235b
  return interval_isdst (tz, (guint)interval);
Packit ae235b
}
Packit ae235b
Packit ae235b
/* Epilogue {{{1 */
Packit ae235b
/* vim:set foldmethod=marker: */