/* * 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 . * * Authors: * - Philip Withnall */ #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; }