mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2024-12-26 15:36:14 +01:00
9eda0b2fcd
This was pointed out in the review for !3696 but owing to a mistake by me, the fix was not included in the version of the MR which was merged. Signed-off-by: Philip Withnall <pwithnall@gnome.org> Helps: #3119
258 lines
7.1 KiB
C
258 lines
7.1 KiB
C
/*
|
|
* Copyright 2023 GNOME Foundation Inc.
|
|
*
|
|
* SPDX-License-Identifier: LGPL-2.1-or-later
|
|
*
|
|
* 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 License, or (at your option) any later version.
|
|
*
|
|
* This library 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.
|
|
*
|
|
* 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/>.
|
|
*
|
|
* Authors:
|
|
* - Philip Withnall <pwithnall@gnome.org>
|
|
*/
|
|
|
|
#include "glib.h"
|
|
#include "gdatetime-private.h"
|
|
|
|
/**
|
|
* _g_era_date_compare:
|
|
* @date1: first date
|
|
* @date2: second date
|
|
*
|
|
* Compare two #GEraDates for ordering, taking into account negative and
|
|
* positive infinity.
|
|
*
|
|
* Returns: strcmp()-style integer, `<0` indicates `date1 < date2`, `0`
|
|
* indicates `date1 == date2`, `>0` indicates `date1 > date2`
|
|
* Since: 2.80
|
|
*/
|
|
int
|
|
_g_era_date_compare (const GEraDate *date1,
|
|
const GEraDate *date2)
|
|
{
|
|
if (date1->type == G_ERA_DATE_SET &&
|
|
date2->type == G_ERA_DATE_SET)
|
|
{
|
|
if (date1->year != date2->year)
|
|
return date1->year - date2->year;
|
|
if (date1->month != date2->month)
|
|
return date1->month - date2->month;
|
|
return date1->day - date2->day;
|
|
}
|
|
|
|
if (date1->type == date2->type)
|
|
return 0;
|
|
|
|
if (date1->type == G_ERA_DATE_MINUS_INFINITY || date2->type == G_ERA_DATE_PLUS_INFINITY)
|
|
return -1;
|
|
if (date1->type == G_ERA_DATE_PLUS_INFINITY || date2->type == G_ERA_DATE_MINUS_INFINITY)
|
|
return 1;
|
|
|
|
g_assert_not_reached ();
|
|
}
|
|
|
|
static gboolean
|
|
parse_era_date (const char *str,
|
|
const char *endptr,
|
|
GEraDate *out_date)
|
|
{
|
|
const char *str_endptr = NULL;
|
|
int year_multiplier;
|
|
guint64 year, month, day;
|
|
|
|
year_multiplier = (str[0] == '-') ? -1 : 1;
|
|
if (str[0] == '-' || str[0] == '+')
|
|
str++;
|
|
|
|
year = g_ascii_strtoull (str, (gchar **) &str_endptr, 10);
|
|
g_assert (str_endptr <= endptr);
|
|
if (str_endptr == endptr || *str_endptr != '/' || year > G_MAXINT)
|
|
return FALSE;
|
|
str = str_endptr + 1;
|
|
|
|
month = g_ascii_strtoull (str, (gchar **) &str_endptr, 10);
|
|
g_assert (str_endptr <= endptr);
|
|
if (str_endptr == endptr || *str_endptr != '/' || month < 1 || month > 12)
|
|
return FALSE;
|
|
str = str_endptr + 1;
|
|
|
|
day = g_ascii_strtoull (str, (gchar **) &str_endptr, 10);
|
|
g_assert (str_endptr <= endptr);
|
|
if (str_endptr != endptr || day < 1 || day > 31)
|
|
return FALSE;
|
|
|
|
/* Success */
|
|
out_date->type = G_ERA_DATE_SET;
|
|
out_date->year = year_multiplier * year;
|
|
out_date->month = month;
|
|
out_date->day = day;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* _g_era_description_segment_ref:
|
|
* @segment: a #GEraDescriptionSegment
|
|
*
|
|
* Increase the ref count of @segment.
|
|
*
|
|
* Returns: (transfer full): @segment
|
|
* Since: 2.80
|
|
*/
|
|
GEraDescriptionSegment *
|
|
_g_era_description_segment_ref (GEraDescriptionSegment *segment)
|
|
{
|
|
g_atomic_ref_count_inc (&segment->ref_count);
|
|
return segment;
|
|
}
|
|
|
|
/**
|
|
* _g_era_description_segment_unref:
|
|
* @segment: (transfer full): a #GEraDescriptionSegment to unref
|
|
*
|
|
* Decreases the ref count of @segment.
|
|
*
|
|
* Since: 2.80
|
|
*/
|
|
void
|
|
_g_era_description_segment_unref (GEraDescriptionSegment *segment)
|
|
{
|
|
if (g_atomic_ref_count_dec (&segment->ref_count))
|
|
{
|
|
g_free (segment->era_format);
|
|
g_free (segment->era_name);
|
|
g_free (segment);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* _g_era_description_parse:
|
|
* @desc: an `ERA` description string from `nl_langinfo()`
|
|
*
|
|
* Parse an ERA description string. See [`nl_langinfo(3)`](man:nl_langinfo(3)).
|
|
*
|
|
* Example description string for th_TH.UTF-8:
|
|
* ```
|
|
* +:1:-543/01/01:+*:พ.ศ.:%EC %Ey
|
|
* ```
|
|
*
|
|
* @desc must be in UTF-8, so all conversion from the locale encoding must
|
|
* happen before this function is called. The resulting `era_name` and
|
|
* `era_format` in the returned segments will be in UTF-8.
|
|
*
|
|
* Returns: (transfer full) (nullable) (element-type GEraDescriptionSegment):
|
|
* array of one or more parsed era segments, or %NULL if parsing failed
|
|
* Since: 2.80
|
|
*/
|
|
GPtrArray *
|
|
_g_era_description_parse (const char *desc)
|
|
{
|
|
GPtrArray *segments = g_ptr_array_new_with_free_func ((GDestroyNotify) _g_era_description_segment_unref);
|
|
|
|
for (const char *p = desc; *p != '\0';)
|
|
{
|
|
const char *next_colon, *endptr = NULL;
|
|
GEraDescriptionSegment *segment = NULL;
|
|
char direction;
|
|
guint64 offset;
|
|
GEraDate start_date, end_date;
|
|
char *era_name = NULL, *era_format = NULL;
|
|
|
|
/* direction */
|
|
direction = *p++;
|
|
if (direction != '+' && direction != '-')
|
|
goto error;
|
|
|
|
if (*p++ != ':')
|
|
goto error;
|
|
|
|
/* offset */
|
|
next_colon = strchr (p, ':');
|
|
if (next_colon == NULL)
|
|
goto error;
|
|
|
|
offset = g_ascii_strtoull (p, (gchar **) &endptr, 10);
|
|
if (endptr != next_colon)
|
|
goto error;
|
|
p = next_colon + 1;
|
|
|
|
/* start_date */
|
|
next_colon = strchr (p, ':');
|
|
if (next_colon == NULL)
|
|
goto error;
|
|
|
|
if (!parse_era_date (p, next_colon, &start_date))
|
|
goto error;
|
|
p = next_colon + 1;
|
|
|
|
/* end_date */
|
|
next_colon = strchr (p, ':');
|
|
if (next_colon == NULL)
|
|
goto error;
|
|
|
|
if (strncmp (p, "-*", 2) == 0)
|
|
end_date.type = G_ERA_DATE_MINUS_INFINITY;
|
|
else if (strncmp (p, "+*", 2) == 0)
|
|
end_date.type = G_ERA_DATE_PLUS_INFINITY;
|
|
else if (!parse_era_date (p, next_colon, &end_date))
|
|
goto error;
|
|
p = next_colon + 1;
|
|
|
|
/* era_name */
|
|
next_colon = strchr (p, ':');
|
|
if (next_colon == NULL)
|
|
goto error;
|
|
|
|
if (next_colon - p == 0)
|
|
goto error;
|
|
era_name = g_strndup (p, next_colon - p);
|
|
p = next_colon + 1;
|
|
|
|
/* era_format; either the final field in the segment (followed by a
|
|
* semicolon) or the description (followed by nul) */
|
|
next_colon = strchr (p, ';');
|
|
if (next_colon == NULL)
|
|
next_colon = p + strlen (p);
|
|
|
|
if (next_colon - p == 0)
|
|
{
|
|
g_free (era_name);
|
|
goto error;
|
|
}
|
|
era_format = g_strndup (p, next_colon - p);
|
|
if (*next_colon == ';')
|
|
p = next_colon + 1;
|
|
else
|
|
p = next_colon;
|
|
|
|
/* Successfully parsed that segment. */
|
|
segment = g_new0 (GEraDescriptionSegment, 1);
|
|
g_atomic_ref_count_init (&segment->ref_count);
|
|
segment->offset = offset;
|
|
segment->start_date = start_date;
|
|
segment->end_date = end_date;
|
|
segment->direction_multiplier =
|
|
((_g_era_date_compare (&segment->start_date, &segment->end_date) <= 0) ? 1 : -1) *
|
|
((direction == '-') ? -1 : 1);
|
|
segment->era_name = g_steal_pointer (&era_name);
|
|
segment->era_format = g_steal_pointer (&era_format);
|
|
|
|
g_ptr_array_add (segments, g_steal_pointer (&segment));
|
|
}
|
|
|
|
return g_steal_pointer (&segments);
|
|
|
|
error:
|
|
g_ptr_array_unref (segments);
|
|
return NULL;
|
|
}
|