(root)/
glib-2.79.0/
glib/
gdatetime-private.c
       1  /*
       2   * Copyright 2023 GNOME Foundation Inc.
       3   *
       4   * SPDX-License-Identifier: LGPL-2.1-or-later
       5   *
       6   * This library is free software; you can redistribute it and/or
       7   * modify it under the terms of the GNU Lesser General Public
       8   * License as published by the Free Software Foundation; either
       9   * version 2.1 of the License, or (at your option) any later version.
      10   *
      11   * This library is distributed in the hope that it will be useful,
      12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
      13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
      14   * Lesser General Public License for more details.
      15   *
      16   * You should have received a copy of the GNU Lesser General Public
      17   * License along with this library; if not, see <http://www.gnu.org/licenses/>.
      18   *
      19   * Authors:
      20   *  - Philip Withnall <pwithnall@gnome.org>
      21   */
      22  
      23  #include "glib.h"
      24  #include "gdatetime-private.h"
      25  
      26  /**
      27   * _g_era_date_compare:
      28   * @date1: first date
      29   * @date2: second date
      30   *
      31   * Compare two #GEraDates for ordering, taking into account negative and
      32   * positive infinity.
      33   *
      34   * Returns: strcmp()-style integer, `<0` indicates `date1 < date2`, `0`
      35   *   indicates `date1 == date2`, `>0` indicates `date1 > date2`
      36   * Since: 2.80
      37   */
      38  int
      39  _g_era_date_compare (const GEraDate *date1,
      40                       const GEraDate *date2)
      41  {
      42    if (date1->type == G_ERA_DATE_SET &&
      43        date2->type == G_ERA_DATE_SET)
      44      {
      45        if (date1->year != date2->year)
      46          return date1->year - date2->year;
      47        if (date1->month != date2->month)
      48          return date1->month - date2->month;
      49        return date1->day - date2->day;
      50      }
      51  
      52    if (date1->type == date2->type)
      53      return 0;
      54  
      55    if (date1->type == G_ERA_DATE_MINUS_INFINITY || date2->type == G_ERA_DATE_PLUS_INFINITY)
      56      return -1;
      57    if (date1->type == G_ERA_DATE_PLUS_INFINITY || date2->type == G_ERA_DATE_MINUS_INFINITY)
      58      return 1;
      59  
      60    g_assert_not_reached ();
      61  }
      62  
      63  static gboolean
      64  parse_era_date (const char *str,
      65                  const char *endptr,
      66                  GEraDate   *out_date)
      67  {
      68    const char *str_endptr = NULL;
      69    int year_multiplier;
      70    guint64 year, month, day;
      71  
      72    year_multiplier = (str[0] == '-') ? -1 : 1;
      73    if (str[0] == '-' || str[0] == '+')
      74      str++;
      75  
      76    year = g_ascii_strtoull (str, (gchar **) &str_endptr, 10);
      77    g_assert (str_endptr <= endptr);
      78    if (str_endptr == endptr || *str_endptr != '/' || year > G_MAXINT)
      79      return FALSE;
      80    str = str_endptr + 1;
      81  
      82    month = g_ascii_strtoull (str, (gchar **) &str_endptr, 10);
      83    g_assert (str_endptr <= endptr);
      84    if (str_endptr == endptr || *str_endptr != '/' || month < 1 || month > 12)
      85      return FALSE;
      86    str = str_endptr + 1;
      87  
      88    day = g_ascii_strtoull (str, (gchar **) &str_endptr, 10);
      89    g_assert (str_endptr <= endptr);
      90    if (str_endptr != endptr || day < 1 || day > 31)
      91      return FALSE;
      92  
      93    /* Success */
      94    out_date->type = G_ERA_DATE_SET;
      95    out_date->year = year_multiplier * year;
      96    out_date->month = month;
      97    out_date->day = day;
      98  
      99    return TRUE;
     100  }
     101  
     102  /**
     103   * _g_era_description_segment_ref:
     104   * @segment: a #GEraDescriptionSegment
     105   *
     106   * Increase the ref count of @segment.
     107   *
     108   * Returns: (transfer full): @segment
     109   * Since: 2.80
     110   */
     111  GEraDescriptionSegment *
     112  _g_era_description_segment_ref (GEraDescriptionSegment *segment)
     113  {
     114    g_atomic_ref_count_inc (&segment->ref_count);
     115    return segment;
     116  }
     117  
     118  /**
     119   * _g_era_description_segment_unref:
     120   * @segment: (transfer full): a #GEraDescriptionSegment to unref
     121   *
     122   * Decreases the ref count of @segment.
     123   *
     124   * Since: 2.80
     125   */
     126  void
     127  _g_era_description_segment_unref (GEraDescriptionSegment *segment)
     128  {
     129    if (g_atomic_ref_count_dec (&segment->ref_count))
     130      {
     131        g_free (segment->era_format);
     132        g_free (segment->era_name);
     133        g_free (segment);
     134      }
     135  }
     136  
     137  /**
     138   * _g_era_description_parse:
     139   * @desc: an `ERA` description string from `nl_langinfo()`
     140   *
     141   * Parse an ERA description string. See [`nl_langinfo(3)`](man:nl_langinfo(3)).
     142   *
     143   * Example description string for th_TH.UTF-8:
     144   * ```
     145   * +:1:-543/01/01:+*:พ.ศ.:%EC %Ey
     146   * ```
     147   *
     148   * @desc must be in UTF-8, so all conversion from the locale encoding must
     149   * happen before this function is called. The resulting `era_name` and
     150   * `era_format` in the returned segments will be in UTF-8.
     151   *
     152   * Returns: (transfer full) (nullable) (element-type GEraDescriptionSegment):
     153   *   array of one or more parsed era segments, or %NULL if parsing failed
     154   * Since: 2.80
     155   */
     156  GPtrArray *
     157  _g_era_description_parse (const char *desc)
     158  {
     159    GPtrArray *segments = g_ptr_array_new_with_free_func ((GDestroyNotify) _g_era_description_segment_unref);
     160  
     161    for (const char *p = desc; *p != '\0';)
     162      {
     163        const char *next_colon, *endptr = NULL;
     164        GEraDescriptionSegment *segment = NULL;
     165        char direction;
     166        guint64 offset;
     167        GEraDate start_date, end_date;
     168        char *era_name = NULL, *era_format = NULL;
     169  
     170        /* direction */
     171        direction = *p++;
     172        if (direction != '+' && direction != '-')
     173          goto error;
     174  
     175        if (*p++ != ':')
     176          goto error;
     177  
     178        /* offset */
     179        next_colon = strchr (p, ':');
     180        if (next_colon == NULL)
     181          goto error;
     182  
     183        offset = g_ascii_strtoull (p, (gchar **) &endptr, 10);
     184        if (endptr != next_colon)
     185          goto error;
     186        p = next_colon + 1;
     187  
     188        /* start_date */
     189        next_colon = strchr (p, ':');
     190        if (next_colon == NULL)
     191          goto error;
     192  
     193        if (!parse_era_date (p, next_colon, &start_date))
     194          goto error;
     195        p = next_colon + 1;
     196  
     197        /* end_date */
     198        next_colon = strchr (p, ':');
     199        if (next_colon == NULL)
     200          goto error;
     201  
     202        if (strncmp (p, "-*", 2) == 0)
     203          end_date.type = G_ERA_DATE_MINUS_INFINITY;
     204        else if (strncmp (p, "+*", 2) == 0)
     205          end_date.type = G_ERA_DATE_PLUS_INFINITY;
     206        else if (!parse_era_date (p, next_colon, &end_date))
     207          goto error;
     208        p = next_colon + 1;
     209  
     210        /* era_name */
     211        next_colon = strchr (p, ':');
     212        if (next_colon == NULL)
     213          goto error;
     214  
     215        if (next_colon - p == 0)
     216          goto error;
     217        era_name = g_strndup (p, next_colon - p);
     218        p = next_colon + 1;
     219  
     220        /* era_format; either the final field in the segment (followed by a
     221         * semicolon) or the description (followed by nul) */
     222        next_colon = strchr (p, ';');
     223        if (next_colon == NULL)
     224          next_colon = p + strlen (p);
     225  
     226        if (next_colon - p == 0)
     227          {
     228            g_free (era_name);
     229            goto error;
     230          }
     231        era_format = g_strndup (p, next_colon - p);
     232        if (*next_colon == ';')
     233          p = next_colon + 1;
     234        else
     235          p = next_colon;
     236  
     237        /* Successfully parsed that segment. */
     238        segment = g_new0 (GEraDescriptionSegment, 1);
     239        g_atomic_ref_count_init (&segment->ref_count);
     240        segment->offset = offset;
     241        segment->start_date = start_date;
     242        segment->end_date = end_date;
     243        segment->direction_multiplier =
     244            ((_g_era_date_compare (&segment->start_date, &segment->end_date) <= 0) ? 1 : -1) *
     245            ((direction == '-') ? -1 : 1);
     246        segment->era_name = g_steal_pointer (&era_name);
     247        segment->era_format = g_steal_pointer (&era_format);
     248  
     249        g_ptr_array_add (segments, g_steal_pointer (&segment));
     250      }
     251  
     252    return g_steal_pointer (&segments);
     253  
     254  error:
     255    g_ptr_array_unref (segments);
     256    return NULL;
     257  }