gdatetime.c 95.2 KB
Newer Older
Thiago Santos's avatar
Thiago Santos committed
1 2 3
/* gdatetime.c
 *
 * Copyright (C) 2009-2010 Christian Hergert <chris@dronelabs.com>
4 5
 * Copyright (C) 2010 Thiago Santos <thiago.sousa.santos@collabora.co.uk>
 * Copyright (C) 2010 Emmanuele Bassi <ebassi@linux.intel.com>
6
 * Copyright © 2010 Codethink Limited
7
 * Copyright © 2018 Tomasz Miąsko
Thiago Santos's avatar
Thiago Santos committed
8
 *
9 10 11 12
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of the
 * licence, or (at your option) any later version.
Thiago Santos's avatar
Thiago Santos committed
13
 *
14 15 16 17
 * This is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
 * License for more details.
Thiago Santos's avatar
Thiago Santos committed
18
 *
19 20
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library; if not, see <http://www.gnu.org/licenses/>.
21 22 23 24 25
 *
 * Authors: Christian Hergert <chris@dronelabs.com>
 *          Thiago Santos <thiago.sousa.santos@collabora.co.uk>
 *          Emmanuele Bassi <ebassi@linux.intel.com>
 *          Ryan Lortie <desrt@desrt.ca>
26
 *          Robert Ancell <robert.ancell@canonical.com>
Thiago Santos's avatar
Thiago Santos committed
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
 */

/* Algorithms within this file are based on the Calendar FAQ by
 * Claus Tondering.  It can be found at
 * http://www.tondering.dk/claus/cal/calendar29.txt
 *
 * Copyright and disclaimer
 * ------------------------
 *   This document is Copyright (C) 2008 by Claus Tondering.
 *   E-mail: claus@tondering.dk. (Please include the word
 *   "calendar" in the subject line.)
 *   The document may be freely distributed, provided this
 *   copyright notice is included and no money is charged for
 *   the document.
 *
 *   This document is provided "as is". No warranties are made as
 *   to its correctness.
 */

46 47
/* Prologue {{{1 */

Thiago Santos's avatar
Thiago Santos committed
48 49
#include "config.h"

50 51 52 53 54
/* langinfo.h in glibc 2.27 defines ALTMON_* only if _GNU_SOURCE is defined.  */
#ifndef _GNU_SOURCE
#define _GNU_SOURCE 1
#endif

Thiago Santos's avatar
Thiago Santos committed
55 56 57
#include <stdlib.h>
#include <string.h>

58
#ifdef HAVE_LANGINFO_TIME
59
#include <langinfo.h>
60
#endif
Thiago Santos's avatar
Thiago Santos committed
61 62 63

#include "gdatetime.h"

64
#include "gslice.h"
65
#include "gatomic.h"
66 67
#include "gcharset.h"
#include "gconvert.h"
68
#include "gfileutils.h"
69
#include "ghash.h"
70
#include "gmain.h"
71
#include "gmappedfile.h"
72
#include "gstrfuncs.h"
73
#include "gtestutils.h"
74
#include "gthread.h"
75
#include "gtimezone.h"
76

77 78
#include "glibintl.h"

79 80 81 82 83
#ifndef G_OS_WIN32
#include <sys/time.h>
#include <time.h>
#endif /* !G_OS_WIN32 */

Thiago Santos's avatar
Thiago Santos committed
84 85 86
/**
 * SECTION:date-time
 * @title: GDateTime
87
 * @short_description: a structure representing Date and Time
88
 * @see_also: #GTimeZone
Thiago Santos's avatar
Thiago Santos committed
89
 *
90 91 92 93 94 95
 * #GDateTime is a structure that combines a Gregorian date and time
 * into a single structure.  It provides many conversion and methods to
 * manipulate dates and times.  Time precision is provided down to
 * microseconds and the time can range (proleptically) from 0001-01-01
 * 00:00:00 to 9999-12-31 23:59:59.999999.  #GDateTime follows POSIX
 * time in the sense that it is oblivious to leap seconds.
Thiago Santos's avatar
Thiago Santos committed
96
 *
97 98 99 100
 * #GDateTime is an immutable object; once it has been created it cannot
 * be modified further.  All modifiers will create a new #GDateTime.
 * Nearly all such functions can fail due to the date or time going out
 * of range, in which case %NULL will be returned.
Thiago Santos's avatar
Thiago Santos committed
101 102 103 104 105 106
 *
 * #GDateTime is reference counted: the reference count is increased by calling
 * g_date_time_ref() and decreased by calling g_date_time_unref(). When the
 * reference count drops to 0, the resources allocated by the #GDateTime
 * structure are released.
 *
107 108 109 110 111 112
 * Many parts of the API may produce non-obvious results.  As an
 * example, adding two months to January 31st will yield March 31st
 * whereas adding one month and then one month again will yield either
 * March 28th or March 29th.  Also note that adding 24 hours is not
 * always the same as adding one day (since days containing daylight
 * savings time transitions are either 23 or 25 hours in length).
Thiago Santos's avatar
Thiago Santos committed
113 114 115 116
 *
 * #GDateTime is available since GLib 2.26.
 */

117 118 119 120 121 122 123 124 125
struct _GDateTime
{
  /* Microsecond timekeeping within Day */
  guint64 usec;

  /* TimeZone information */
  GTimeZone *tz;
  gint interval;

126 127 128
  /* 1 is 0001-01-01 in Proleptic Gregorian */
  gint32 days;

129 130 131 132 133
  volatile gint ref_count;
};

/* Time conversion {{{1 */

134
#define UNIX_EPOCH_START     719163
135 136 137
#define INSTANT_TO_UNIX(instant) \
  ((instant)/USEC_PER_SECOND - UNIX_EPOCH_START * SEC_PER_DAY)
#define UNIX_TO_INSTANT(unix) \
138
  (((gint64) (unix) + UNIX_EPOCH_START * SEC_PER_DAY) * USEC_PER_SECOND)
139 140
#define UNIX_TO_INSTANT_IS_VALID(unix) \
  ((gint64) (unix) <= INSTANT_TO_UNIX (G_MAXINT64))
141 142 143 144 145

#define DAYS_IN_4YEARS    1461    /* days in 4 years */
#define DAYS_IN_100YEARS  36524   /* days in 100 years */
#define DAYS_IN_400YEARS  146097  /* days in 400 years  */

Thiago Santos's avatar
Thiago Santos committed
146 147 148 149 150
#define USEC_PER_SECOND      (G_GINT64_CONSTANT (1000000))
#define USEC_PER_MINUTE      (G_GINT64_CONSTANT (60000000))
#define USEC_PER_HOUR        (G_GINT64_CONSTANT (3600000000))
#define USEC_PER_MILLISECOND (G_GINT64_CONSTANT (1000))
#define USEC_PER_DAY         (G_GINT64_CONSTANT (86400000000))
151
#define SEC_PER_DAY          (G_GINT64_CONSTANT (86400))
Thiago Santos's avatar
Thiago Santos committed
152

153 154 155 156 157 158
#define SECS_PER_MINUTE (60)
#define SECS_PER_HOUR   (60 * SECS_PER_MINUTE)
#define SECS_PER_DAY    (24 * SECS_PER_HOUR)
#define SECS_PER_YEAR   (365 * SECS_PER_DAY)
#define SECS_PER_JULIAN (DAYS_PER_PERIOD * SECS_PER_DAY)

Matthias Clasen's avatar
Matthias Clasen committed
159 160 161 162
#define GREGORIAN_LEAP(y)    ((((y) % 4) == 0) && (!((((y) % 100) == 0) && (((y) % 400) != 0))))
#define JULIAN_YEAR(d)       ((d)->julian / 365.25)
#define DAYS_PER_PERIOD      (G_GINT64_CONSTANT (2914695))

Thiago Santos's avatar
Thiago Santos committed
163 164 165 166 167 168 169 170 171 172 173 174
static const guint16 days_in_months[2][13] =
{
  { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
  { 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
};

static const guint16 days_in_year[2][13] =
{
  {  0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 },
  {  0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 }
};

175 176 177 178 179
#ifdef HAVE_LANGINFO_TIME

#define GET_AMPM(d) ((g_date_time_get_hour (d) < 12) ? \
                     nl_langinfo (AM_STR) : \
                     nl_langinfo (PM_STR))
180
#define GET_AMPM_IS_LOCALE TRUE
181

182
#define PREFERRED_DATE_TIME_FMT nl_langinfo (D_T_FMT)
183 184
#define PREFERRED_DATE_FMT nl_langinfo (D_FMT)
#define PREFERRED_TIME_FMT nl_langinfo (T_FMT)
185
#define PREFERRED_12HR_TIME_FMT nl_langinfo (T_FMT_AMPM)
186 187 188 189 190 191 192 193 194 195 196 197 198 199

static const gint weekday_item[2][7] =
{
  { ABDAY_2, ABDAY_3, ABDAY_4, ABDAY_5, ABDAY_6, ABDAY_7, ABDAY_1 },
  { DAY_2, DAY_3, DAY_4, DAY_5, DAY_6, DAY_7, DAY_1 }
};

static const gint month_item[2][12] =
{
  { ABMON_1, ABMON_2, ABMON_3, ABMON_4, ABMON_5, ABMON_6, ABMON_7, ABMON_8, ABMON_9, ABMON_10, ABMON_11, ABMON_12 },
  { MON_1, MON_2, MON_3, MON_4, MON_5, MON_6, MON_7, MON_8, MON_9, MON_10, MON_11, MON_12 },
};

#define WEEKDAY_ABBR(d) nl_langinfo (weekday_item[0][g_date_time_get_day_of_week (d) - 1])
200
#define WEEKDAY_ABBR_IS_LOCALE TRUE
201
#define WEEKDAY_FULL(d) nl_langinfo (weekday_item[1][g_date_time_get_day_of_week (d) - 1])
202
#define WEEKDAY_FULL_IS_LOCALE TRUE
203
#define MONTH_ABBR(d) nl_langinfo (month_item[0][g_date_time_get_month (d) - 1])
204
#define MONTH_ABBR_IS_LOCALE TRUE
205
#define MONTH_FULL(d) nl_langinfo (month_item[1][g_date_time_get_month (d) - 1])
206
#define MONTH_FULL_IS_LOCALE TRUE
207 208 209

#else

210
#define GET_AMPM(d)          (get_fallback_ampm (g_date_time_get_hour (d)))
211
#define GET_AMPM_IS_LOCALE   FALSE
Matthias Clasen's avatar
Matthias Clasen committed
212

213 214 215
/* Translators: this is the preferred format for expressing the date and the time */
#define PREFERRED_DATE_TIME_FMT C_("GDateTime", "%a %b %e %H:%M:%S %Y")

Matthias Clasen's avatar
Matthias Clasen committed
216 217 218 219 220 221
/* Translators: this is the preferred format for expressing the date */
#define PREFERRED_DATE_FMT C_("GDateTime", "%m/%d/%y")

/* Translators: this is the preferred format for expressing the time */
#define PREFERRED_TIME_FMT C_("GDateTime", "%H:%M:%S")

222 223
/* Translators: this is the preferred format for expressing 12 hour time */
#define PREFERRED_12HR_TIME_FMT C_("GDateTime", "%I:%M:%S %p")
Matthias Clasen's avatar
Matthias Clasen committed
224

225
#define WEEKDAY_ABBR(d)       (get_weekday_name_abbr (g_date_time_get_day_of_week (d)))
226
#define WEEKDAY_ABBR_IS_LOCALE FALSE
227
#define WEEKDAY_FULL(d)       (get_weekday_name (g_date_time_get_day_of_week (d)))
228
#define WEEKDAY_FULL_IS_LOCALE FALSE
229 230 231 232 233 234 235
/* We don't yet know if nl_langinfo (MON_n) returns standalone or complete-date
 * format forms but if nl_langinfo (ALTMON_n) is not supported then we will
 * have to use MONTH_FULL as standalone.  The same if nl_langinfo () does not
 * exist at all.  MONTH_ABBR is similar: if nl_langinfo (_NL_ABALTMON_n) is not
 * supported then we will use MONTH_ABBR as standalone.
 */
#define MONTH_ABBR(d)         (get_month_name_abbr_standalone (g_date_time_get_month (d)))
236
#define MONTH_ABBR_IS_LOCALE  FALSE
237
#define MONTH_FULL(d)         (get_month_name_standalone (g_date_time_get_month (d)))
238
#define MONTH_FULL_IS_LOCALE  FALSE
Matthias Clasen's avatar
Matthias Clasen committed
239

Thiago Santos's avatar
Thiago Santos committed
240
static const gchar *
241
get_month_name_standalone (gint month)
Thiago Santos's avatar
Thiago Santos committed
242 243 244 245
{
  switch (month)
    {
    case 1:
246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261
      /* Translators: Some languages (Baltic, Slavic, Greek, and some more)
       * need different grammatical forms of month names depending on whether
       * they are standalone or in a complete date context, with the day
       * number.  Some other languages may prefer starting with uppercase when
       * they are standalone and with lowercase when they are in a complete
       * date context.  Here are full month names in a form appropriate when
       * they are used standalone.  If your system is Linux with the glibc
       * version 2.27 (released Feb 1, 2018) or newer or if it is from the BSD
       * family (which includes OS X) then you can refer to the date command
       * line utility and see what the command `date +%OB' produces.  Also in
       * the latest Linux the command `locale alt_mon' in your native locale
       * produces a complete list of month names almost ready to copy and
       * paste here.  Note that in most of the languages (western European,
       * non-European) there is no difference between the standalone and
       * complete date form.
       */
262
      return C_("full month name", "January");
Thiago Santos's avatar
Thiago Santos committed
263
    case 2:
264
      return C_("full month name", "February");
Thiago Santos's avatar
Thiago Santos committed
265
    case 3:
266
      return C_("full month name", "March");
Thiago Santos's avatar
Thiago Santos committed
267
    case 4:
268
      return C_("full month name", "April");
Thiago Santos's avatar
Thiago Santos committed
269
    case 5:
270
      return C_("full month name", "May");
Thiago Santos's avatar
Thiago Santos committed
271
    case 6:
272
      return C_("full month name", "June");
Thiago Santos's avatar
Thiago Santos committed
273
    case 7:
274
      return C_("full month name", "July");
Thiago Santos's avatar
Thiago Santos committed
275
    case 8:
276
      return C_("full month name", "August");
Thiago Santos's avatar
Thiago Santos committed
277
    case 9:
278
      return C_("full month name", "September");
Thiago Santos's avatar
Thiago Santos committed
279
    case 10:
280
      return C_("full month name", "October");
Thiago Santos's avatar
Thiago Santos committed
281
    case 11:
282
      return C_("full month name", "November");
Thiago Santos's avatar
Thiago Santos committed
283
    case 12:
284
      return C_("full month name", "December");
Thiago Santos's avatar
Thiago Santos committed
285 286 287 288 289 290 291 292 293

    default:
      g_warning ("Invalid month number %d", month);
    }

  return NULL;
}

static const gchar *
294
get_month_name_abbr_standalone (gint month)
Thiago Santos's avatar
Thiago Santos committed
295 296 297 298
{
  switch (month)
    {
    case 1:
299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315
      /* Translators: Some languages need different grammatical forms of
       * month names depending on whether they are standalone or in a complete
       * date context, with the day number.  Some may prefer starting with
       * uppercase when they are standalone and with lowercase when they are
       * in a full date context.  However, as these names are abbreviated
       * the grammatical difference is visible probably only in Belarusian
       * and Russian.  In other languages there is no difference between
       * the standalone and complete date form when they are abbreviated.
       * If your system is Linux with the glibc version 2.27 (released
       * Feb 1, 2018) or newer then you can refer to the date command line
       * utility and see what the command `date +%Ob' produces.  Also in
       * the latest Linux the command `locale ab_alt_mon' in your native
       * locale produces a complete list of month names almost ready to copy
       * and paste here.  Note that this feature is not yet supported by any
       * other platform.  Here are abbreviated month names in a form
       * appropriate when they are used standalone.
       */
316
      return C_("abbreviated month name", "Jan");
Thiago Santos's avatar
Thiago Santos committed
317
    case 2:
318
      return C_("abbreviated month name", "Feb");
Thiago Santos's avatar
Thiago Santos committed
319
    case 3:
320
      return C_("abbreviated month name", "Mar");
Thiago Santos's avatar
Thiago Santos committed
321
    case 4:
322
      return C_("abbreviated month name", "Apr");
Thiago Santos's avatar
Thiago Santos committed
323
    case 5:
324
      return C_("abbreviated month name", "May");
Thiago Santos's avatar
Thiago Santos committed
325
    case 6:
326
      return C_("abbreviated month name", "Jun");
Thiago Santos's avatar
Thiago Santos committed
327
    case 7:
328
      return C_("abbreviated month name", "Jul");
Thiago Santos's avatar
Thiago Santos committed
329
    case 8:
330
      return C_("abbreviated month name", "Aug");
Thiago Santos's avatar
Thiago Santos committed
331
    case 9:
332
      return C_("abbreviated month name", "Sep");
Thiago Santos's avatar
Thiago Santos committed
333
    case 10:
334
      return C_("abbreviated month name", "Oct");
Thiago Santos's avatar
Thiago Santos committed
335
    case 11:
336
      return C_("abbreviated month name", "Nov");
Thiago Santos's avatar
Thiago Santos committed
337
    case 12:
338
      return C_("abbreviated month name", "Dec");
Thiago Santos's avatar
Thiago Santos committed
339 340 341 342 343 344 345 346 347 348 349 350 351 352

    default:
      g_warning ("Invalid month number %d", month);
    }

  return NULL;
}

static const gchar *
get_weekday_name (gint day)
{
  switch (day)
    {
    case 1:
353
      return C_("full weekday name", "Monday");
Thiago Santos's avatar
Thiago Santos committed
354
    case 2:
355
      return C_("full weekday name", "Tuesday");
Thiago Santos's avatar
Thiago Santos committed
356
    case 3:
357
      return C_("full weekday name", "Wednesday");
Thiago Santos's avatar
Thiago Santos committed
358
    case 4:
359
      return C_("full weekday name", "Thursday");
Thiago Santos's avatar
Thiago Santos committed
360
    case 5:
361
      return C_("full weekday name", "Friday");
Thiago Santos's avatar
Thiago Santos committed
362
    case 6:
363
      return C_("full weekday name", "Saturday");
Thiago Santos's avatar
Thiago Santos committed
364
    case 7:
365
      return C_("full weekday name", "Sunday");
Thiago Santos's avatar
Thiago Santos committed
366 367 368 369 370 371 372 373 374 375 376 377 378 379

    default:
      g_warning ("Invalid week day number %d", day);
    }

  return NULL;
}

static const gchar *
get_weekday_name_abbr (gint day)
{
  switch (day)
    {
    case 1:
380
      return C_("abbreviated weekday name", "Mon");
Thiago Santos's avatar
Thiago Santos committed
381
    case 2:
382
      return C_("abbreviated weekday name", "Tue");
Thiago Santos's avatar
Thiago Santos committed
383
    case 3:
384
      return C_("abbreviated weekday name", "Wed");
Thiago Santos's avatar
Thiago Santos committed
385
    case 4:
386
      return C_("abbreviated weekday name", "Thu");
Thiago Santos's avatar
Thiago Santos committed
387
    case 5:
388
      return C_("abbreviated weekday name", "Fri");
Thiago Santos's avatar
Thiago Santos committed
389
    case 6:
390
      return C_("abbreviated weekday name", "Sat");
Thiago Santos's avatar
Thiago Santos committed
391
    case 7:
392
      return C_("abbreviated weekday name", "Sun");
393

Thiago Santos's avatar
Thiago Santos committed
394
    default:
395
      g_warning ("Invalid week day number %d", day);
Thiago Santos's avatar
Thiago Santos committed
396 397 398 399 400
    }

  return NULL;
}

401 402
#endif  /* HAVE_LANGINFO_TIME */

403 404 405 406 407 408 409
#ifdef HAVE_LANGINFO_ALTMON

/* If nl_langinfo () supports ALTMON_n then MON_n returns full date format
 * forms and ALTMON_n returns standalone forms.
 */

#define MONTH_FULL_WITH_DAY(d) MONTH_FULL(d)
410
#define MONTH_FULL_WITH_DAY_IS_LOCALE MONTH_FULL_IS_LOCALE
411 412 413 414 415 416 417 418

static const gint alt_month_item[12] =
{
  ALTMON_1, ALTMON_2, ALTMON_3, ALTMON_4, ALTMON_5, ALTMON_6,
  ALTMON_7, ALTMON_8, ALTMON_9, ALTMON_10, ALTMON_11, ALTMON_12
};

#define MONTH_FULL_STANDALONE(d) nl_langinfo (alt_month_item[g_date_time_get_month (d) - 1])
419
#define MONTH_FULL_STANDALONE_IS_LOCALE TRUE
420 421 422 423 424 425 426 427 428

#else

/* If nl_langinfo () does not support ALTMON_n then either MON_n returns
 * standalone forms or nl_langinfo (MON_n) does not work so we have defined
 * it as standalone form.
 */

#define MONTH_FULL_STANDALONE(d) MONTH_FULL(d)
429
#define MONTH_FULL_STANDALONE_IS_LOCALE MONTH_FULL_IS_LOCALE
430
#define MONTH_FULL_WITH_DAY(d) (get_month_name_with_day (g_date_time_get_month (d)))
431
#define MONTH_FULL_WITH_DAY_IS_LOCALE FALSE
432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495

static const gchar *
get_month_name_with_day (gint month)
{
  switch (month)
    {
    case 1:
      /* Translators: Some languages need different grammatical forms of
       * month names depending on whether they are standalone or in a full
       * date context, with the day number.  Some may prefer starting with
       * uppercase when they are standalone and with lowercase when they are
       * in a full date context.  Here are full month names in a form
       * appropriate when they are used in a full date context, with the
       * day number.  If your system is Linux with the glibc version 2.27
       * (released Feb 1, 2018) or newer or if it is from the BSD family
       * (which includes OS X) then you can refer to the date command line
       * utility and see what the command `date +%B' produces.  Also in
       * the latest Linux the command `locale mon' in your native locale
       * produces a complete list of month names almost ready to copy and
       * paste here.  In older Linux systems due to a bug the result is
       * incorrect in some languages.  Note that in most of the languages
       * (western European, non-European) there is no difference between the
       * standalone and complete date form.
       */
      return C_("full month name with day", "January");
    case 2:
      return C_("full month name with day", "February");
    case 3:
      return C_("full month name with day", "March");
    case 4:
      return C_("full month name with day", "April");
    case 5:
      return C_("full month name with day", "May");
    case 6:
      return C_("full month name with day", "June");
    case 7:
      return C_("full month name with day", "July");
    case 8:
      return C_("full month name with day", "August");
    case 9:
      return C_("full month name with day", "September");
    case 10:
      return C_("full month name with day", "October");
    case 11:
      return C_("full month name with day", "November");
    case 12:
      return C_("full month name with day", "December");

    default:
      g_warning ("Invalid month number %d", month);
    }

  return NULL;
}

#endif  /* HAVE_LANGINFO_ALTMON */

#ifdef HAVE_LANGINFO_ABALTMON

/* If nl_langinfo () supports _NL_ABALTMON_n then ABMON_n returns full
 * date format forms and _NL_ABALTMON_n returns standalone forms.
 */

#define MONTH_ABBR_WITH_DAY(d) MONTH_ABBR(d)
496
#define MONTH_ABBR_WITH_DAY_IS_LOCALE MONTH_ABBR_IS_LOCALE
497 498 499 500 501 502 503 504 505

static const gint ab_alt_month_item[12] =
{
  _NL_ABALTMON_1, _NL_ABALTMON_2, _NL_ABALTMON_3, _NL_ABALTMON_4,
  _NL_ABALTMON_5, _NL_ABALTMON_6, _NL_ABALTMON_7, _NL_ABALTMON_8,
  _NL_ABALTMON_9, _NL_ABALTMON_10, _NL_ABALTMON_11, _NL_ABALTMON_12
};

#define MONTH_ABBR_STANDALONE(d) nl_langinfo (ab_alt_month_item[g_date_time_get_month (d) - 1])
506
#define MONTH_ABBR_STANDALONE_IS_LOCALE TRUE
507 508 509 510 511 512 513 514 515

#else

/* If nl_langinfo () does not support _NL_ABALTMON_n then either ABMON_n
 * returns standalone forms or nl_langinfo (ABMON_n) does not work so we
 * have defined it as standalone form. Now it's time to swap.
 */

#define MONTH_ABBR_STANDALONE(d) MONTH_ABBR(d)
516
#define MONTH_ABBR_STANDALONE_IS_LOCALE MONTH_ABBR_IS_LOCALE
517
#define MONTH_ABBR_WITH_DAY(d) (get_month_name_abbr_with_day (g_date_time_get_month (d)))
518
#define MONTH_ABBR_WITH_DAY_IS_LOCALE FALSE
519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575

static const gchar *
get_month_name_abbr_with_day (gint month)
{
  switch (month)
    {
    case 1:
      /* Translators: Some languages need different grammatical forms of
       * month names depending on whether they are standalone or in a full
       * date context, with the day number.  Some may prefer starting with
       * uppercase when they are standalone and with lowercase when they are
       * in a full date context.  Here are abbreviated month names in a form
       * appropriate when they are used in a full date context, with the
       * day number.  However, as these names are abbreviated the grammatical
       * difference is visible probably only in Belarusian and Russian.
       * In other languages there is no difference between the standalone
       * and complete date form when they are abbreviated.  If your system
       * is Linux with the glibc version 2.27 (released Feb 1, 2018) or newer
       * then you can refer to the date command line utility and see what the
       * command `date +%b' produces.  Also in the latest Linux the command
       * `locale abmon' in your native locale produces a complete list of
       * month names almost ready to copy and paste here.  In other systems
       * due to a bug the result is incorrect in some languages.
       */
      return C_("abbreviated month name with day", "Jan");
    case 2:
      return C_("abbreviated month name with day", "Feb");
    case 3:
      return C_("abbreviated month name with day", "Mar");
    case 4:
      return C_("abbreviated month name with day", "Apr");
    case 5:
      return C_("abbreviated month name with day", "May");
    case 6:
      return C_("abbreviated month name with day", "Jun");
    case 7:
      return C_("abbreviated month name with day", "Jul");
    case 8:
      return C_("abbreviated month name with day", "Aug");
    case 9:
      return C_("abbreviated month name with day", "Sep");
    case 10:
      return C_("abbreviated month name with day", "Oct");
    case 11:
      return C_("abbreviated month name with day", "Nov");
    case 12:
      return C_("abbreviated month name with day", "Dec");

    default:
      g_warning ("Invalid month number %d", month);
    }

  return NULL;
}

#endif  /* HAVE_LANGINFO_ABALTMON */

576 577 578 579 580 581 582 583 584 585 586 587
/* Format AM/PM indicator if the locale does not have a localized version. */
static const gchar *
get_fallback_ampm (gint hour)
{
  if (hour < 12)
    /* Translators: 'before midday' indicator */
    return C_("GDateTime", "AM");
  else
    /* Translators: 'after midday' indicator */
    return C_("GDateTime", "PM");
}

588 589 590 591
static inline gint
ymd_to_days (gint year,
             gint month,
             gint day)
Thiago Santos's avatar
Thiago Santos committed
592
{
593
  gint64 days;
Thiago Santos's avatar
Thiago Santos committed
594

595 596 597 598 599 600 601 602
  days = (year - 1) * 365 + ((year - 1) / 4) - ((year - 1) / 100)
      + ((year - 1) / 400);

  days += days_in_year[0][month - 1];
  if (GREGORIAN_LEAP (year) && month > 2)
    day++;

  days += day;
Thiago Santos's avatar
Thiago Santos committed
603

604
  return days;
Thiago Santos's avatar
Thiago Santos committed
605 606
}

607 608 609 610 611
static void
g_date_time_get_week_number (GDateTime *datetime,
                             gint      *week_number,
                             gint      *day_of_week,
                             gint      *day_of_year)
612
{
613
  gint a, b, c, d, e, f, g, n, s, month, day, year;
Thiago Santos's avatar
Thiago Santos committed
614

615
  g_date_time_get_ymd (datetime, &year, &month, &day);
Thiago Santos's avatar
Thiago Santos committed
616

617
  if (month <= 2)
Thiago Santos's avatar
Thiago Santos committed
618
    {
619 620 621 622 623 624
      a = g_date_time_get_year (datetime) - 1;
      b = (a / 4) - (a / 100) + (a / 400);
      c = ((a - 1) / 4) - ((a - 1) / 100) + ((a - 1) / 400);
      s = b - c;
      e = 0;
      f = day - 1 + (31 * (month - 1));
Thiago Santos's avatar
Thiago Santos committed
625
    }
626
  else
Thiago Santos's avatar
Thiago Santos committed
627
    {
628 629 630 631 632 633
      a = year;
      b = (a / 4) - (a / 100) + (a / 400);
      c = ((a - 1) / 4) - ((a - 1) / 100) + ((a - 1) / 400);
      s = b - c;
      e = s + 1;
      f = day + (((153 * (month - 3)) + 2) / 5) + 58 + s;
Thiago Santos's avatar
Thiago Santos committed
634 635
    }

636 637 638
  g = (a + b) % 7;
  d = (f + g - e) % 7;
  n = f + 3 - d;
Thiago Santos's avatar
Thiago Santos committed
639

640 641 642 643 644 645 646 647 648
  if (week_number)
    {
      if (n < 0)
        *week_number = 53 - ((g - s) / 5);
      else if (n > 364 + s)
        *week_number = 1;
      else
        *week_number = (n / 7) + 1;
    }
Thiago Santos's avatar
Thiago Santos committed
649

650 651
  if (day_of_week)
    *day_of_week = d + 1;
Thiago Santos's avatar
Thiago Santos committed
652

653 654
  if (day_of_year)
    *day_of_year = f + 1;
Thiago Santos's avatar
Thiago Santos committed
655 656
}

657
/* Lifecycle {{{1 */
658

659 660 661 662
static GDateTime *
g_date_time_alloc (GTimeZone *tz)
{
  GDateTime *datetime;
663

664 665 666
  datetime = g_slice_new0 (GDateTime);
  datetime->tz = g_time_zone_ref (tz);
  datetime->ref_count = 1;
667

668
  return datetime;
669 670
}

671 672 673 674 675 676
/**
 * g_date_time_ref:
 * @datetime: a #GDateTime
 *
 * Atomically increments the reference count of @datetime by one.
 *
677
 * Returns: the #GDateTime with the reference count increased
678 679 680 681 682
 *
 * Since: 2.26
 */
GDateTime *
g_date_time_ref (GDateTime *datetime)
683
{
684 685
  g_return_val_if_fail (datetime != NULL, NULL);
  g_return_val_if_fail (datetime->ref_count > 0, NULL);
686

687
  g_atomic_int_inc (&datetime->ref_count);
688

689
  return datetime;
690 691
}

692 693 694 695 696 697 698 699 700 701 702 703 704
/**
 * g_date_time_unref:
 * @datetime: a #GDateTime
 *
 * Atomically decrements the reference count of @datetime by one.
 *
 * When the reference count reaches zero, the resources allocated by
 * @datetime are freed
 *
 * Since: 2.26
 */
void
g_date_time_unref (GDateTime *datetime)
705
{
706 707
  g_return_if_fail (datetime != NULL);
  g_return_if_fail (datetime->ref_count > 0);
708

709
  if (g_atomic_int_dec_and_test (&datetime->ref_count))
710
    {
711 712
      g_time_zone_unref (datetime->tz);
      g_slice_free (GDateTime, datetime);
713 714 715
    }
}

716
/* Internal state transformers {{{1 */
Thiago Santos's avatar
Thiago Santos committed
717
/*< internal >
718 719
 * g_date_time_to_instant:
 * @datetime: a #GDateTime
720
 *
721
 * Convert a @datetime into an instant.
722
 *
723 724 725
 * An instant is a number that uniquely describes a particular
 * microsecond in time, taking time zone considerations into account.
 * (ie: "03:00 -0400" is the same instant as "02:00 -0500").
Thiago Santos's avatar
Thiago Santos committed
726
 *
727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744
 * An instant is always positive but we use a signed return value to
 * avoid troubles with C.
 */
static gint64
g_date_time_to_instant (GDateTime *datetime)
{
  gint64 offset;

  offset = g_time_zone_get_offset (datetime->tz, datetime->interval);
  offset *= USEC_PER_SECOND;

  return datetime->days * USEC_PER_DAY + datetime->usec - offset;
}

/*< internal >
 * g_date_time_from_instant:
 * @tz: a #GTimeZone
 * @instant: a instant in time
745
 *
746
 * Creates a #GDateTime from a time zone and an instant.
747
 *
748
 * This might fail if the time ends up being out of range.
Thiago Santos's avatar
Thiago Santos committed
749
 */
750 751 752
static GDateTime *
g_date_time_from_instant (GTimeZone *tz,
                          gint64     instant)
Thiago Santos's avatar
Thiago Santos committed
753
{
754 755
  GDateTime *datetime;
  gint64 offset;
Thiago Santos's avatar
Thiago Santos committed
756

757
  if (instant < 0 || instant > G_GINT64_CONSTANT (1000000000000000000))
758
    return NULL;
759

760 761 762 763 764 765
  datetime = g_date_time_alloc (tz);
  datetime->interval = g_time_zone_find_interval (tz,
                                                  G_TIME_TYPE_UNIVERSAL,
                                                  INSTANT_TO_UNIX (instant));
  offset = g_time_zone_get_offset (datetime->tz, datetime->interval);
  offset *= USEC_PER_SECOND;
766

767
  instant += offset;
768

769 770
  datetime->days = instant / USEC_PER_DAY;
  datetime->usec = instant % USEC_PER_DAY;
Thiago Santos's avatar
Thiago Santos committed
771

772 773 774 775 776
  if (datetime->days < 1 || 3652059 < datetime->days)
    {
      g_date_time_unref (datetime);
      datetime = NULL;
    }
777

778
  return datetime;
779 780
}

781 782 783 784

/*< internal >
 * g_date_time_deal_with_date_change:
 * @datetime: a #GDateTime
785
 *
786 787
 * This function should be called whenever the date changes by adding
 * days, months or years.  It does three things.
788
 *
789 790
 * First, we ensure that the date falls between 0001-01-01 and
 * 9999-12-31 and return %FALSE if it does not.
791
 *
792
 * Next we update the ->interval field.
793
 *
794 795 796 797 798 799
 * Finally, we ensure that the resulting date and time pair exists (by
 * ensuring that our time zone has an interval containing it) and
 * adjusting as required.  For example, if we have the time 02:30:00 on
 * March 13 2010 in Toronto and we add 1 day to it, we would end up with
 * 2:30am on March 14th, which doesn't exist.  In that case, we bump the
 * time up to 3:00am.
800
 */
801 802
static gboolean
g_date_time_deal_with_date_change (GDateTime *datetime)
803
{
804 805 806
  GTimeType was_dst;
  gint64 full_time;
  gint64 usec;
807

808 809
  if (datetime->days < 1 || datetime->days > 3652059)
    return FALSE;
810

811
  was_dst = g_time_zone_is_dst (datetime->tz, datetime->interval);
812

813
  full_time = datetime->days * USEC_PER_DAY + datetime->usec;
814

Thiago Santos's avatar
Thiago Santos committed
815

816 817 818
  usec = full_time % USEC_PER_SECOND;
  full_time /= USEC_PER_SECOND;
  full_time -= UNIX_EPOCH_START * SEC_PER_DAY;
819

820 821 822 823 824 825
  datetime->interval = g_time_zone_adjust_time (datetime->tz,
                                                was_dst,
                                                &full_time);
  full_time += UNIX_EPOCH_START * SEC_PER_DAY;
  full_time *= USEC_PER_SECOND;
  full_time += usec;
826

827 828
  datetime->days = full_time / USEC_PER_DAY;
  datetime->usec = full_time % USEC_PER_DAY;
829

830 831 832 833
  /* maybe daylight time caused us to shift to a different day,
   * but it definitely didn't push us into a different year */
  return TRUE;
}
834

835 836 837 838 839 840 841 842 843 844
static GDateTime *
g_date_time_replace_days (GDateTime *datetime,
                          gint       days)
{
  GDateTime *new;

  new = g_date_time_alloc (datetime->tz);
  new->interval = datetime->interval;
  new->usec = datetime->usec;
  new->days = days;
845

846 847 848 849 850
  if (!g_date_time_deal_with_date_change (new))
    {
      g_date_time_unref (new);
      new = NULL;
    }
851

852
  return new;
853
}
Thiago Santos's avatar
Thiago Santos committed
854

855 856 857 858 859 860
/* now/unix/timeval Constructors {{{1 */

/*< internal >
 * g_date_time_new_from_timeval:
 * @tz: a #GTimeZone
 * @tv: a #GTimeVal
861
 *
862 863
 * Creates a #GDateTime corresponding to the given #GTimeVal @tv in the
 * given time zone @tz.
864
 *
865 866 867 868 869 870 871 872 873 874 875
 * The time contained in a #GTimeVal is always stored in the form of
 * seconds elapsed since 1970-01-01 00:00:00 UTC, regardless of the
 * given time zone.
 *
 * This call can fail (returning %NULL) if @tv represents a time outside
 * of the supported range of #GDateTime.
 *
 * You should release the return value by calling g_date_time_unref()
 * when you are done with it.
 *
 * Returns: a new #GDateTime, or %NULL
876 877
 *
 * Since: 2.26
878 879 880 881
 **/
static GDateTime *
g_date_time_new_from_timeval (GTimeZone      *tz,
                              const GTimeVal *tv)
Thiago Santos's avatar
Thiago Santos committed
882
{
883 884 885 886
  if ((gint64) tv->tv_sec > G_MAXINT64 - 1 ||
      !UNIX_TO_INSTANT_IS_VALID ((gint64) tv->tv_sec + 1))
    return NULL;

887 888
  return g_date_time_from_instant (tz, tv->tv_usec +
                                   UNIX_TO_INSTANT (tv->tv_sec));
889
}
Thiago Santos's avatar
Thiago Santos committed
890

891 892 893 894 895 896 897
/*< internal >
 * g_date_time_new_from_unix:
 * @tz: a #GTimeZone
 * @t: the Unix time
 *
 * Creates a #GDateTime corresponding to the given Unix time @t in the
 * given time zone @tz.
898
 *
899 900
 * Unix time is the number of seconds that have elapsed since 1970-01-01
 * 00:00:00 UTC, regardless of the time zone given.
901
 *
902 903
 * This call can fail (returning %NULL) if @t represents a time outside
 * of the supported range of #GDateTime.
904
 *
905 906 907 908
 * You should release the return value by calling g_date_time_unref()
 * when you are done with it.
 *
 * Returns: a new #GDateTime, or %NULL
909 910
 *
 * Since: 2.26
911 912 913 914
 **/
static GDateTime *
g_date_time_new_from_unix (GTimeZone *tz,
                           gint64     secs)
915
{
916 917 918
  if (!UNIX_TO_INSTANT_IS_VALID (secs))
    return NULL;

919
  return g_date_time_from_instant (tz, UNIX_TO_INSTANT (secs));
920
}
Thiago Santos's avatar
Thiago Santos committed
921

922
/**
923 924
 * g_date_time_new_now:
 * @tz: a #GTimeZone
925
 *
926 927 928
 * Creates a #GDateTime corresponding to this exact instant in the given
 * time zone @tz.  The time is as accurate as the system allows, to a
 * maximum accuracy of 1 microsecond.
929
 *
930 931 932 933 934 935 936 937
 * This function will always succeed unless the system clock is set to
 * truly insane values (or unless GLib is still being used after the
 * year 9999).
 *
 * You should release the return value by calling g_date_time_unref()
 * when you are done with it.
 *
 * Returns: a new #GDateTime, or %NULL
938 939
 *
 * Since: 2.26
940 941 942
 **/
GDateTime *
g_date_time_new_now (GTimeZone *tz)
943
{
944
  GTimeVal tv;
945

946
  g_get_current_time (&tv);
947

948
  return g_date_time_new_from_timeval (tz, &tv);
949 950 951
}

/**
952 953 954 955
 * g_date_time_new_now_local:
 *
 * Creates a #GDateTime corresponding to this exact instant in the local
 * time zone.
956
 *
957 958 959 960
 * This is equivalent to calling g_date_time_new_now() with the time
 * zone returned by g_time_zone_new_local().
 *
 * Returns: a new #GDateTime, or %NULL
961 962
 *
 * Since: 2.26
963 964 965
 **/
GDateTime *
g_date_time_new_now_local (void)
966
{
967 968
  GDateTime *datetime;
  GTimeZone *local;
969

970 971 972
  local = g_time_zone_new_local ();
  datetime = g_date_time_new_now (local);
  g_time_zone_unref (local);
973

974
  return datetime;
975 976 977
}

/**
978
 * g_date_time_new_now_utc:
979
 *
980
 * Creates a #GDateTime corresponding to this exact instant in UTC.
981
 *
982 983 984 985
 * This is equivalent to calling g_date_time_new_now() with the time
 * zone returned by g_time_zone_new_utc().
 *
 * Returns: a new #GDateTime, or %NULL
986 987
 *
 * Since: 2.26
988 989 990
 **/
GDateTime *
g_date_time_new_now_utc (void)
991
{
992 993
  GDateTime *datetime;
  GTimeZone *utc;
994

995 996 997
  utc = g_time_zone_new_utc ();
  datetime = g_date_time_new_now (utc);
  g_time_zone_unref (utc);
998

999
  return datetime;
1000 1001 1002
}

/**
1003 1004 1005 1006 1007 1008 1009 1010
 * g_date_time_new_from_unix_local:
 * @t: the Unix time
 *
 * Creates a #GDateTime corresponding to the given Unix time @t in the
 * local time zone.
 *
 * Unix time is the number of seconds that have elapsed since 1970-01-01
 * 00:00:00 UTC, regardless of the local time offset.
1011
 *
1012 1013
 * This call can fail (returning %NULL) if @t represents a time outside
 * of the supported range of #GDateTime.
1014
 *
1015 1016 1017 1018
 * You should release the return value by calling g_date_time_unref()
 * when you are done with it.
 *
 * Returns: a new #GDateTime, or %NULL
1019 1020
 *
 * Since: 2.26
1021 1022 1023
 **/
GDateTime *
g_date_time_new_from_unix_local (gint64 t)
1024
{
1025 1026
  GDateTime *datetime;
  GTimeZone *local;
1027

1028 1029 1030
  local = g_time_zone_new_local ();
  datetime = g_date_time_new_from_unix (local, t);
  g_time_zone_unref (local);
1031

1032
  return datetime;
1033 1034 1035
}

/**
1036 1037 1038 1039
 * g_date_time_new_from_unix_utc:
 * @t: the Unix time
 *
 * Creates a #GDateTime corresponding to the given Unix time @t in UTC.
1040
 *
1041 1042
 * Unix time is the number of seconds that have elapsed since 1970-01-01
 * 00:00:00 UTC.
1043
 *
1044 1045 1046 1047 1048 1049 1050
 * This call can fail (returning %NULL) if @t represents a time outside
 * of the supported range of #GDateTime.
 *
 * You should release the return value by calling g_date_time_unref()
 * when you are done with it.
 *
 * Returns: a new #GDateTime, or %NULL
1051 1052
 *
 * Since: 2.26
1053 1054 1055
 **/
GDateTime *
g_date_time_new_from_unix_utc (gint64 t)
1056
{
1057 1058
  GDateTime *datetime;
  GTimeZone *utc;
1059

1060 1061 1062
  utc = g_time_zone_new_utc ();
  datetime = g_date_time_new_from_unix (utc, t);
  g_time_zone_unref (utc);
1063

1064
  return datetime;
1065 1066 1067
}

/**
1068 1069
 * g_date_time_new_from_timeval_local:
 * @tv: a #GTimeVal
1070
 *
1071 1072
 * Creates a #GDateTime corresponding to the given #GTimeVal @tv in the
 * local time zone.
1073
 *
1074 1075 1076
 * The time contained in a #GTimeVal is always stored in the form of
 * seconds elapsed since 1970-01-01 00:00:00 UTC, regardless of the
 * local time offset.
1077
 *
1078 1079
 * This call can fail (returning %NULL) if @tv represents a time outside
 * of the supported range of #GDateTime.
Thiago Santos's avatar
Thiago Santos committed
1080
 *
1081 1082
 * You should release the return value by calling g_date_time_unref()
 * when you are done with it.
Thiago Santos's avatar
Thiago Santos committed
1083
 *
1084 1085 1086 1087 1088 1089
 * Returns: a new #GDateTime, or %NULL
 *
 * Since: 2.26
 **/
GDateTime *
g_date_time_new_from_timeval_local (const GTimeVal *tv)
1090
{
1091 1092
  GDateTime *datetime;
  GTimeZone *local;
1093

1094 1095 1096
  local = g_time_zone_new_local ();
  datetime = g_date_time_new_from_timeval (local, tv);
  g_time_zone_unref (local);
1097

1098
  return datetime;
1099 1100
}

1101 1102 1103
/**
 * g_date_time_new_from_timeval_utc:
 * @tv: a #GTimeVal
1104
 *
1105
 * Creates a #GDateTime corresponding to the given #GTimeVal @tv in UTC.
1106
 *
1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121
 * The time contained in a #GTimeVal is always stored in the form of
 * seconds elapsed since 1970-01-01 00:00:00 UTC.
 *
 * This call can fail (returning %NULL) if @tv represents a time outside
 * of the supported range of #GDateTime.
 *
 * You should release the return value by calling g_date_time_unref()
 * when you are done with it.
 *
 * Returns: a new #GDateTime, or %NULL
 *
 * Since: 2.26
 **/
GDateTime *
g_date_time_new_from_timeval_utc (const GTimeVal *tv)
Thiago Santos's avatar
Thiago Santos committed
1122 1123
{
  GDateTime *datetime;
1124
  GTimeZone *utc;
Thiago Santos's avatar
Thiago Santos committed
1125

1126 1127 1128
  utc = g_time_zone_new_utc ();
  datetime = g_date_time_new_from_timeval (utc, tv);
  g_time_zone_unref (utc);
Thiago Santos's avatar
Thiago Santos committed
1129 1130 1131 1132

  return datetime;
}

1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158
/* Parse integers in the form d (week days), dd (hours etc), ddd (ordinal days) or dddd (years) */
static gboolean
get_iso8601_int (const gchar *text, gsize length, gint *value)
{
  gint i, v = 0;

  if (length < 1 || length > 4)
    return FALSE;

  for (i = 0; i < length; i++)
    {
      const gchar c = text[i];
      if (c < '0' || c > '9')
        return FALSE;
      v = v * 10 + (c - '0');
    }

  *value = v;
  return TRUE;
}

/* Parse seconds in the form ss or ss.sss (variable length decimal) */
static gboolean
get_iso8601_seconds (const gchar *text, gsize length, gdouble *value)
{
  gint i;
1159
  gdouble divisor = 1, v = 0;
1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182

  if (length < 2)
    return FALSE;

  for (i = 0; i < 2; i++)
    {
      const gchar c = text[i];
      if (c < '0' || c > '9')
        return FALSE;
      v = v * 10 + (c - '0');
    }

  if (length > 2 && !(text[i] == '.' || text[i] == ','))
    return FALSE;
  i++;
  if (i == length)
    return FALSE;

  for (; i < length; i++)
    {
      const gchar c = text[i];
      if (c < '0' || c > '9')
        return FALSE;
1183 1184
      v = v * 10 + (c - '0');
      divisor *= 10;
1185 1186
    }

1187
  *value = v / divisor;
1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219
  return TRUE;
}

static GDateTime *
g_date_time_new_ordinal (GTimeZone *tz, gint year, gint ordinal_day, gint hour, gint minute, gdouble seconds)
{
  GDateTime *dt;

  if (ordinal_day < 1 || ordinal_day > (GREGORIAN_LEAP (year) ? 366 : 365))
    return NULL;

  dt = g_date_time_new (tz, year, 1, 1, hour, minute, seconds);
  dt->days += ordinal_day - 1;

  return dt;
}

static GDateTime *
g_date_time_new_week (GTimeZone *tz, gint year, gint week, gint week_day, gint hour, gint minute, gdouble seconds)
{
  gint64 p;
  gint max_week, jan4_week_day, ordinal_day;
  GDateTime *dt;

  p = (year * 365 + (year / 4) - (year / 100) + (year / 400)) % 7;
  max_week = p == 4 ? 53 : 52;

  if (week < 1 || week > max_week || week_day < 1 || week_day > 7)
    return NULL;

  dt = g_date_time_new (tz, year, 1, 4, 0, 0, 0);
  g_date_time_get_week_number (dt, NULL, &jan4_week_day, NULL);
1220 1221
  g_date_time_unref (dt);

1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470
  ordinal_day = (week * 7) + week_day - (jan4_week_day + 3);
  if (ordinal_day < 0)
    {
      year--;
      ordinal_day += GREGORIAN_LEAP (year) ? 366 : 365;
    }
  else if (ordinal_day > (GREGORIAN_LEAP (year) ? 366 : 365))
    {
      ordinal_day -= (GREGORIAN_LEAP (year) ? 366 : 365);
      year++;
    }

  return g_date_time_new_ordinal (tz, year, ordinal_day, hour, minute, seconds);
}

static GDateTime *
parse_iso8601_date (const gchar *text, gsize length,
                    gint hour, gint minute, gdouble seconds, GTimeZone *tz)
{
  /* YYYY-MM-DD */
  if (length == 10 && text[4] == '-' && text[7] == '-')
    {
      int year, month, day;
      if (!get_iso8601_int (text, 4, &year) ||
          !get_iso8601_int (text + 5, 2, &month) ||
          !get_iso8601_int (text + 8, 2, &day))
        return NULL;
      return g_date_time_new (tz, year, month, day, hour, minute, seconds);
    }
  /* YYYY-DDD */
  else if (length == 8 && text[4] == '-')
    {
      gint year, ordinal_day;
      if (!get_iso8601_int (text, 4, &year) ||
          !get_iso8601_int (text + 5, 3, &ordinal_day))
        return NULL;
      return g_date_time_new_ordinal (tz, year, ordinal_day, hour, minute, seconds);
    }
  /* YYYY-Www-D */
  else if (length == 10 && text[4] == '-' && text[5] == 'W' && text[8] == '-')
    {
      gint year, week, week_day;
      if (!get_iso8601_int (text, 4, &year) ||
          !get_iso8601_int (text + 6, 2, &week) ||
          !get_iso8601_int (text + 9, 1, &week_day))
        return NULL;
      return g_date_time_new_week (tz, year, week, week_day, hour, minute, seconds);
    }
  /* YYYYWwwD */
  else if (length == 8 && text[4] == 'W')
    {
      gint year, week, week_day;
      if (!get_iso8601_int (text, 4, &year) ||
          !get_iso8601_int (text + 5, 2, &week) ||
          !get_iso8601_int (text + 7, 1, &week_day))
        return NULL;
      return g_date_time_new_week (tz, year, week, week_day, hour, minute, seconds);
    }
  /* YYYYMMDD */
  else if (length == 8)
    {
      int year, month, day;
      if (!get_iso8601_int (text, 4, &year) ||
          !get_iso8601_int (text + 4, 2, &month) ||
          !get_iso8601_int (text + 6, 2, &day))
        return NULL;
      return g_date_time_new (tz, year, month, day, hour, minute, seconds);
    }
  /* YYYYDDD */
  else if (length == 7)
    {
      gint year, ordinal_day;
      if (!get_iso8601_int (text, 4, &year) ||
          !get_iso8601_int (text + 4, 3, &ordinal_day))
        return NULL;
      return g_date_time_new_ordinal (tz, year, ordinal_day, hour, minute, seconds);
    }
  else
    return FALSE;
}

static GTimeZone *
parse_iso8601_timezone (const gchar *text, gsize length, gssize *tz_offset)
{
  gint i, tz_length, offset_sign = 1, offset_hours, offset_minutes;
  GTimeZone *tz;

  /* UTC uses Z suffix  */
  if (length > 0 && text[length - 1] == 'Z')
    {
      *tz_offset = length - 1;
      return g_time_zone_new_utc ();
    }

  /* Look for '+' or '-' of offset */
  for (i = length - 1; i >= 0; i--)
    if (text[i] == '+' || text[i] == '-')
      {
        offset_sign = text[i] == '-' ? -1 : 1;
        break;
      }
  if (i < 0)
    return NULL;
  tz_length = length - i;

  /* +hh:mm or -hh:mm */
  if (tz_length == 6 && text[i+3] == ':')
    {
      if (!get_iso8601_int (text + i + 1, 2, &offset_hours) ||
          !get_iso8601_int (text + i + 4, 2, &offset_minutes))
        return NULL;
    }
  /* +hhmm or -hhmm */
  else if (tz_length == 5)
    {
      if (!get_iso8601_int (text + i + 1, 2, &offset_hours) ||
          !get_iso8601_int (text + i + 3, 2, &offset_minutes))
        return NULL;
    }
  /* +hh or -hh */
  else if (tz_length == 3)
    {
      if (!get_iso8601_int (text + i + 1, 2, &offset_hours))
        return NULL;
      offset_minutes = 0;
    }
  else
    return NULL;

  *tz_offset = i;
  tz = g_time_zone_new (text + i);

  /* Double-check that the GTimeZone matches our interpretation of the timezone.
   * Failure would indicate a bug either here of in the GTimeZone code. */
  g_assert (g_time_zone_get_offset (tz, 0) == offset_sign * (offset_hours * 3600 + offset_minutes * 60));

  return tz;
}

static gboolean
parse_iso8601_time (const gchar *text, gsize length,
                    gint *hour, gint *minute, gdouble *seconds, GTimeZone **tz)
{
  gssize tz_offset = -1;

  /* Check for timezone suffix */
  *tz = parse_iso8601_timezone (text, length, &tz_offset);
  if (tz_offset >= 0)
    length = tz_offset;

  /* hh:mm:ss(.sss) */
  if (length >= 8 && text[2] == ':' && text[5] == ':')
    {
      return get_iso8601_int (text, 2, hour) &&
             get_iso8601_int (text + 3, 2, minute) &&
             get_iso8601_seconds (text + 6, length - 6, seconds);
    }
  /* hhmmss(.sss) */
  else if (length >= 6)
    {
      return get_iso8601_int (text, 2, hour) &&
             get_iso8601_int (text + 2, 2, minute) &&
             get_iso8601_seconds (text + 4, length - 4, seconds);
    }
  else
    return FALSE;
}

/**
 * g_date_time_new_from_iso8601:
 * @text: an ISO 8601 formatted time string.
 * @default_tz: (nullable): a #GTimeZone to use if the text doesn't contain a
 *                          timezone, or %NULL.
 *
 * Creates a #GDateTime corresponding to the given
 * [ISO 8601 formatted string](https://en.wikipedia.org/wiki/ISO_8601)
 * @text. ISO 8601 strings of the form <date><sep><time><tz> are supported.
 *
 * <sep> is the separator and can be either 'T', 't' or ' '.
 *
 * <date> is in the form:
 *
 * - `YYYY-MM-DD` - Year/month/day, e.g. 2016-08-24.
 * - `YYYYMMDD` - Same as above without dividers.
 * - `YYYY-DDD` - Ordinal day where DDD is from 001 to 366, e.g. 2016-237.
 * - `YYYYDDD` - Same as above without dividers.
 * - `YYYY-Www-D` - Week day where ww is from 01 to 52 and D from 1-7,
 *   e.g. 2016-W34-3.
 * - `YYYYWwwD` - Same as above without dividers.
 *
 * <time> is in the form:
 *
 * - `hh:mm:ss(.sss)` - Hours, minutes, seconds (subseconds), e.g. 22:10:42.123.
 * - `hhmmss(.sss)` - Same as above without dividers.
 *
 * <tz> is an optional timezone suffix of the form:
 *
 * - `Z` - UTC.
 * - `+hh:mm` or `-hh:mm` - Offset from UTC in hours and minutes, e.g. +12:00.
 * - `+hh` or `-hh` - Offset from UTC in hours, e.g. +12.
 *
 * If the timezone is not provided in @text it must be provided in @default_tz
 * (this field is otherwise ignored).
 *
 * This call can fail (returning %NULL) if @text is not a valid ISO 8601
 * formatted string.
 *
 * You should release the return value by calling g_date_time_unref()
 * when you are done with it.
 *
 * Returns: (transfer full) (nullable): a new #GDateTime, or %NULL
 *
 * Since: 2.56
 */
GDateTime *
g_date_time_new_from_iso8601 (const gchar *text, GTimeZone *default_tz)
{
  gint length, date_length = -1;
  gint hour = 0, minute = 0;
  gdouble seconds = 0.0;
  GTimeZone *tz = NULL;
  GDateTime *datetime = NULL;

  g_return_val_if_fail (text != NULL, NULL);

  /* Count length of string and find date / time separator ('T', 't', or ' ') */
  for (length = 0; text[length] != '\0'; length++)
    {
      if (date_length < 0 && (text[length] == 'T' || text[length] == 't' || text[length] == ' '))
        date_length = length;
    }

  if (date_length < 0)
    return NULL;

  if (!parse_iso8601_time (text + date_length + 1, length - (date_length + 1),
                           &hour, &minute, &seconds, &tz))
    goto out;
  if (tz == NULL && default_tz == NULL)
    return NULL;

  datetime = parse_iso8601_date (text, date_length, hour, minute, seconds, tz ? tz : default_tz);

out:
    if (tz != NULL)
      g_time_zone_unref (tz);
    return datetime;
}

1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481
/* full new functions {{{1 */

/**
 * g_date_time_new:
 * @tz: a #GTimeZone
 * @year: the year component of the date
 * @month: the month component of the date
 * @day: the day component of the date
 * @hour: the hour component of the date
 * @minute: the minute component of the date
 * @seconds: the number of seconds past the minute
1482
 *
1483 1484
 * Creates a new #GDateTime corresponding to the given date and time in
 * the time zone @tz.
1485
 *
1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523
 * The @year must be between 1 and 9999, @month between 1 and 12 and @day
 * between 1 and 28, 29, 30 or 31 depending on the month and the year.
 *
 * @hour must be between 0 and 23 and @minute must be between 0 and 59.
 *
 * @seconds must be at least 0.0 and must be strictly less than 60.0.
 * It will be rounded down to the nearest microsecond.
 *
 * If the given time is not representable in the given time zone (for
 * example, 02:30 on March 14th 2010 in Toronto, due to daylight savings
 * time) then the time will be rounded up to the nearest existing time
 * (in this case, 03:00).  If this matters to you then you should verify
 * the return value for containing the same as the numbers you gave.
 *
 * In the case that the given time is ambiguous in the given time zone
 * (for example, 01:30 on November 7th 2010 in Toronto, due to daylight
 * savings time) then the time falling within standard (ie:
 * non-daylight) time is taken.
 *
 * It not considered a programmer error for the values to this function
 * to be out of range, but in the case that they are, the function will
 * return %NULL.
 *
 * You should release the return value by calling g_date_time_unref()
 * when you are done with it.
 *
 * Returns: a new #GDateTime, or %NULL
 *
 * Since: 2.26
 **/
GDateTime *
g_date_time_new (GTimeZone *tz,
                 gint       year,
                 gint       month,
                 gint       day,
                 gint       hour,
                 gint       minute,
                 gdouble    seconds)
Thiago Santos's avatar
Thiago Santos committed
1524
{
1525 1526
  GDateTime *datetime;
  gint64 full_time;
1527 1528 1529 1530 1531
  /* keep these variables as volatile. We do not want them ending up in
   * registers - them doing so may cause us to hit precision problems on i386.
   * See: https://bugzilla.gnome.org/show_bug.cgi?id=792410 */
  volatile gint64 usec;
  volatile gdouble usecd;
Thiago Santos's avatar
Thiago Santos committed
1532

1533 1534
  g_return_val_if_fail (tz != NULL, NULL);

1535 1536
  if (year < 1 || year > 9999 ||
      month < 1 || month > 12 ||
1537
      day < 1 || day > days_in_months[GREGORIAN_LEAP (year)][month] ||
1538 1539 1540 1541 1542
      hour < 0 || hour > 23 ||
      minute < 0 || minute > 59 ||
      seconds < 0.0 || seconds >= 60.0)
    return NULL;

1543 1544 1545 1546 1547
  datetime = g_date_time_alloc (tz);
  datetime->days = ymd_to_days (year, month, day);
  datetime->usec = (hour   * USEC_PER_HOUR)
                 + (minute * USEC_PER_MINUTE)
                 + (gint64) (seconds * USEC_PER_SECOND);
Thiago Santos's avatar
Thiago Santos committed
1548

1549 1550 1551 1552 1553
  full_time = SEC_PER_DAY *
                (ymd_to_days (year, month, day) - UNIX_EPOCH_START) +
              SECS_PER_HOUR * hour +
              SECS_PER_MINUTE * minute +
              (int) seconds;
Thiago Santos's avatar
Thiago Santos committed
1554

1555 1556 1557
  datetime->interval = g_time_zone_adjust_time (datetime->tz,
                                                G_TIME_TYPE_STANDARD,
                                                &full_time);
Thiago Santos's avatar
Thiago Santos committed
1558

1559 1560 1561 1562 1563 1564
  /* This is the correct way to convert a scaled FP value to integer.
   * If this surprises you, please observe that (int)(1.000001 * 1e6)
   * is 1000000.  This is not a problem with precision, it's just how
   * FP numbers work.
   * See https://bugzilla.gnome.org/show_bug.cgi?id=697715. */
  usec = seconds * USEC_PER_SECOND;
1565 1566
  usecd = (usec + 1) * 1e-6;
  if (usecd <= seconds) {
1567 1568 1569
    usec++;
  }

1570 1571 1572
  full_time += UNIX_EPOCH_START * SEC_PER_DAY;
  datetime->days = full_time / SEC_PER_DAY;
  datetime->usec = (full_time % SEC_PER_DAY) * USEC_PER_SECOND;
1573
  datetime->usec += usec % USEC_PER_SECOND;
Thiago Santos's avatar
Thiago Santos committed
1574

1575
  return datetime;
Thiago Santos's avatar
Thiago Santos committed
1576 1577
}

1578
/**
1579 1580 1581 1582 1583 1584 1585
 * g_date_time_new_local:
 * @year: the year component of the date
 * @month: the month component of the date
 * @day: the day component of the date
 * @hour: the hour component of the date
 * @minute: the minute component of the date
 * @seconds: the number of seconds past the minute