gdate: Add week-of-year APIs for weeks starting on any day

`GDate` currently has some API which is specific to which day of the week
you think the week starts on:
 * `g_date_get_monday_week_of_year()`
 * `g_date_get_monday_weeks_in_year()`
 * `g_date_get_sunday_week_of_year()`
 * `g_date_get_sunday_weeks_in_year()`

This is to deal with the difference between locales which think that the
week starts on a Sunday (such as `LANG=en_US.utf8` or `LANG=he_IL.utf8`),
or a Monday (such as `LANG=en_GB.utf8`).

However, there are some locales which think that the week starts on a
Saturday (such as `LANG=ar_EG.utf8`). Currently, GLib provides no API for
those.

So, add some API which is parameterised by the first day of the week,
which will deal with weeks which start on a Saturday (and also any other
day of the week, although I don’t believe there are any countries which
use a day other than Saturday, Sunday or Monday).

Signed-off-by: Philip Withnall <pwithnall@gnome.org>

Fixes: #3617
This commit is contained in:
Philip Withnall
2025-02-20 15:04:25 +00:00
parent 8b3c34a523
commit 8d9e21ff14
3 changed files with 176 additions and 64 deletions

View File

@@ -742,27 +742,9 @@ g_date_get_day_of_year (const GDate *d)
* Returns: week of the year
*/
guint
g_date_get_monday_week_of_year (const GDate *d)
g_date_get_monday_week_of_year (const GDate *date)
{
GDateWeekday wd;
guint day;
GDate first;
g_return_val_if_fail (g_date_valid (d), 0);
if (!d->dmy)
g_date_update_dmy (d);
g_return_val_if_fail (d->dmy, 0);
g_date_clear (&first, 1);
g_date_set_dmy (&first, 1, 1, d->year);
wd = g_date_get_weekday (&first) - 1; /* make Monday day 0 */
day = g_date_get_day_of_year (d) - 1;
return ((day + wd)/7U + (wd == 0 ? 1 : 0));
return g_date_get_week_of_year (date, G_DATE_MONDAY);
}
/**
@@ -776,28 +758,52 @@ g_date_get_monday_week_of_year (const GDate *d)
* Returns: week number
*/
guint
g_date_get_sunday_week_of_year (const GDate *d)
g_date_get_sunday_week_of_year (const GDate *date)
{
GDateWeekday wd;
guint day;
GDate first;
g_return_val_if_fail (g_date_valid (d), 0);
if (!d->dmy)
g_date_update_dmy (d);
return g_date_get_week_of_year (date, G_DATE_SUNDAY);
}
g_return_val_if_fail (d->dmy, 0);
g_date_clear (&first, 1);
g_date_set_dmy (&first, 1, 1, d->year);
wd = g_date_get_weekday (&first);
if (wd == 7) wd = 0; /* make Sunday day 0 */
day = g_date_get_day_of_year (d) - 1;
return ((day + wd)/7U + (wd == 0 ? 1 : 0));
/**
* g_date_get_week_of_year:
* @date: a [struct@GLib.Date]
* @first_day_of_week: the day which is considered the first day of the week
* (for example, this would be [enum@GLib.DateWeekday.SUNDAY] in US locales,
* [enum@GLib.DateWeekday.MONDAY] in British locales, and
* [enum@GLib.DateWeekday.SATURDAY] in Egyptian locales
*
* Calculates the week of the year during which this date falls.
*
* The result depends on which day is considered the first day of the week,
* which varies by locale. Both `date` and `first_day_of_week` must be valid.
*
* If @date is before the start of the first week of the year (for example,
* before the first Monday in January if @first_day_of_week is
* [enum@GLib.DateWeekday.MONDAY]) then zero will be returned.
*
* Returns: week number (starting from 1), or `0` if @date is before the start
* of the first week of the year
* Since: 2.86
*/
unsigned int
g_date_get_week_of_year (const GDate *date,
GDateWeekday first_day_of_week)
{
GDate first_day_of_year;
unsigned int n_days_before_first_week;
g_return_val_if_fail (g_date_valid (date), 0);
g_return_val_if_fail (first_day_of_week != G_DATE_BAD_WEEKDAY, 0);
if (!date->dmy)
g_date_update_dmy (date);
g_return_val_if_fail (date->dmy, 0);
g_date_clear (&first_day_of_year, 1);
g_date_set_dmy (&first_day_of_year, 1, 1, date->year);
n_days_before_first_week = (first_day_of_week - g_date_get_weekday (&first_day_of_year) + 7) % 7;
return (g_date_get_day_of_year (date) + 6 - n_days_before_first_week) / 7;
}
/**
@@ -1964,23 +1970,7 @@ g_date_get_days_in_month (GDateMonth month,
guint8
g_date_get_monday_weeks_in_year (GDateYear year)
{
GDate d;
g_return_val_if_fail (g_date_valid_year (year), 0);
g_date_clear (&d, 1);
g_date_set_dmy (&d, 1, 1, year);
if (g_date_get_weekday (&d) == G_DATE_MONDAY) return 53;
g_date_set_dmy (&d, 31, 12, year);
if (g_date_get_weekday (&d) == G_DATE_MONDAY) return 53;
if (g_date_is_leap_year (year))
{
g_date_set_dmy (&d, 2, 1, year);
if (g_date_get_weekday (&d) == G_DATE_MONDAY) return 53;
g_date_set_dmy (&d, 30, 12, year);
if (g_date_get_weekday (&d) == G_DATE_MONDAY) return 53;
}
return 52;
return g_date_get_weeks_in_year (year, G_DATE_MONDAY);
}
/**
@@ -1999,22 +1989,51 @@ g_date_get_monday_weeks_in_year (GDateYear year)
*/
guint8
g_date_get_sunday_weeks_in_year (GDateYear year)
{
return g_date_get_weeks_in_year (year, G_DATE_SUNDAY);
}
/**
* g_date_get_weeks_in_year:
* @year: year to count weeks in
* @first_day_of_week: the day which is considered the first day of the week
* (for example, this would be [enum@GLib.DateWeekday.SUNDAY] in US locales,
* [enum@GLib.DateWeekday.MONDAY] in British locales, and
* [enum@GLib.DateWeekday.SATURDAY] in Egyptian locales
*
* Calculates the number of weeks in the year.
*
* The result depends on which day is considered the first day of the week,
* which varies by locale. `first_day_of_week` must be valid.
*
* The result will be either 52 or 53. Years always have 52 seven-day periods,
* plus one or two extra days depending on whether its a leap year. This
* function effectively calculates how many @first_day_of_week days there are in
* the year.
*
* Returns: the number of weeks in @year
* Since: 2.86
*/
guint8
g_date_get_weeks_in_year (GDateYear year,
GDateWeekday first_day_of_week)
{
GDate d;
g_return_val_if_fail (g_date_valid_year (year), 0);
g_return_val_if_fail (first_day_of_week != G_DATE_BAD_WEEKDAY, 0);
g_date_clear (&d, 1);
g_date_set_dmy (&d, 1, 1, year);
if (g_date_get_weekday (&d) == G_DATE_SUNDAY) return 53;
if (g_date_get_weekday (&d) == first_day_of_week) return 53;
g_date_set_dmy (&d, 31, 12, year);
if (g_date_get_weekday (&d) == G_DATE_SUNDAY) return 53;
if (g_date_is_leap_year (year))
if (g_date_get_weekday (&d) == first_day_of_week) return 53;
if (g_date_is_leap_year (year))
{
g_date_set_dmy (&d, 2, 1, year);
if (g_date_get_weekday (&d) == G_DATE_SUNDAY) return 53;
if (g_date_get_weekday (&d) == first_day_of_week) return 53;
g_date_set_dmy (&d, 30, 12, year);
if (g_date_get_weekday (&d) == G_DATE_SUNDAY) return 53;
if (g_date_get_weekday (&d) == first_day_of_week) return 53;
}
return 52;
}

View File

@@ -167,6 +167,9 @@ GLIB_AVAILABLE_IN_ALL
guint g_date_get_monday_week_of_year (const GDate *date);
GLIB_AVAILABLE_IN_ALL
guint g_date_get_sunday_week_of_year (const GDate *date);
GLIB_AVAILABLE_IN_2_86
guint g_date_get_week_of_year (const GDate *date,
GDateWeekday first_day_of_week);
GLIB_AVAILABLE_IN_ALL
guint g_date_get_iso8601_week_of_year (const GDate *date);
@@ -250,6 +253,9 @@ GLIB_AVAILABLE_IN_ALL
guint8 g_date_get_monday_weeks_in_year (GDateYear year) G_GNUC_CONST;
GLIB_AVAILABLE_IN_ALL
guint8 g_date_get_sunday_weeks_in_year (GDateYear year) G_GNUC_CONST;
GLIB_AVAILABLE_IN_2_86
guint8 g_date_get_weeks_in_year (GDateYear year,
GDateWeekday first_day_of_week) G_GNUC_CONST;
/* Returns the number of days between the two dates. If date2 comes
before date1, a negative value is return. */

View File

@@ -448,6 +448,18 @@ test_dates (void)
g_assert_cmpint (g_date_get_sunday_weeks_in_year (0), ==, 0);
g_test_assert_expected_messages ();
/* g_date_get_week_of_year (d) */
g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL,
"*assertion *failed*");
g_assert_cmpint (g_date_get_week_of_year (d, G_DATE_MONDAY), ==, 0);
g_test_assert_expected_messages ();
/* g_date_get_weeks_in_year (y) */
g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL,
"*assertion *failed*");
g_assert_cmpint (g_date_get_weeks_in_year (0, G_DATE_MONDAY), ==, 0);
g_test_assert_expected_messages ();
/* g_date_get_iso8601_week_of_year (d) */
g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL,
"*assertion *failed*");
@@ -494,6 +506,8 @@ test_dates (void)
g_assert_cmpint (g_date_get_sunday_weeks_in_year (1792), ==, 53);
g_assert_cmpint (g_date_get_weeks_in_year (2000, G_DATE_SATURDAY), ==, 53);
/* Trigger the update of the dmy/julian parts */
d = g_date_new_julian (1);
g_assert_cmpint (g_date_get_day_of_year (d), ==, 1);
@@ -1412,6 +1426,8 @@ test_year (gconstpointer t)
guint32 first_day_of_year = G_DATE_BAD_JULIAN;
guint16 days_in_year = g_date_is_leap_year (y) ? 366 : 365;
guint saturday_week_of_year = 0;
guint saturday_weeks_in_year = g_date_get_weeks_in_year (y, G_DATE_SATURDAY);
guint sunday_week_of_year = 0;
guint sunday_weeks_in_year = g_date_get_sunday_weeks_in_year (y);
guint monday_week_of_year = 0;
@@ -1420,6 +1436,7 @@ test_year (gconstpointer t)
g_assert_true (g_date_valid_year (y));
/* Years ought to have roundabout 52 weeks */
g_assert (saturday_weeks_in_year == 52 || saturday_weeks_in_year == 53);
g_assert (sunday_weeks_in_year == 52 || sunday_weeks_in_year == 53);
g_assert (monday_weeks_in_year == 52 || monday_weeks_in_year == 53);
@@ -1511,6 +1528,21 @@ test_year (gconstpointer t)
sunday_week_of_year = g_date_get_sunday_week_of_year (d);
g_assert_cmpint (g_date_get_week_of_year (d, G_DATE_SATURDAY),
<=, saturday_weeks_in_year);
g_assert_cmpint (g_date_get_week_of_year (d, G_DATE_SATURDAY),
>=, saturday_week_of_year);
if (g_date_get_weekday (d) == G_DATE_SATURDAY)
g_assert_cmpint (g_date_get_week_of_year (d, G_DATE_SATURDAY) -
saturday_week_of_year,
==, 1);
else
g_assert_cmpint (g_date_get_week_of_year (d, G_DATE_SATURDAY) -
saturday_week_of_year,
==, 0);
saturday_week_of_year = g_date_get_week_of_year (d, G_DATE_SATURDAY);
g_assert_cmpint (g_date_compare (d, d), ==, 0);
i = 1;
@@ -1753,6 +1785,60 @@ test_valid_dmy (void)
}
}
static void
test_week_of_year (void)
{
const struct
{
unsigned int year;
unsigned int month;
unsigned int day;
unsigned int expected_saturday_week_of_year;
unsigned int expected_sunday_week_of_year;
unsigned int expected_monday_week_of_year;
}
vectors[] =
{
{ 2000, 1, 1, 1, 0, 0 }, /* first day of the year is a Saturday */
{ 2000, 1, 7, 1, 1, 1 },
{ 2000, 1, 8, 2, 1, 1 },
{ 2001, 1, 1, 0, 0, 1 }, /* first day of the year is a Monday */
{ 2001, 1, 7, 1, 1, 1 },
{ 2001, 1, 8, 1, 1, 2 },
{ 2002, 1, 1, 0, 0, 0 }, /* first day of the year is a Tuesday */
{ 2002, 1, 7, 1, 1, 1 },
{ 2002, 1, 8, 1, 1, 1 },
{ 2003, 1, 1, 0, 0, 0 }, /* first day of the year is a Wednesday */
{ 2003, 1, 7, 1, 1, 1 },
{ 2003, 1, 8, 1, 1, 1 },
{ 2004, 1, 1, 0, 0, 0 }, /* first day of the year is a Thursday */
{ 2004, 1, 7, 1, 1, 1 },
{ 2004, 1, 8, 1, 1, 1 },
{ 2006, 1, 1, 0, 1, 0 }, /* first day of the year is a Sunday */
{ 2006, 1, 7, 1, 1, 1 },
{ 2006, 1, 8, 1, 2, 1 },
{ 2010, 1, 1, 0, 0, 0 }, /* first day of the year is a Friday */
{ 2010, 1, 7, 1, 1, 1 },
{ 2010, 1, 8, 1, 1, 1 },
};
for (size_t i = 0; i < G_N_ELEMENTS (vectors); i++)
{
GDate date;
g_date_clear (&date, 1);
g_date_set_dmy (&date, vectors[i].day, vectors[i].month, vectors[i].year);
g_test_message ("Considering %04u-%02u-%02u",
vectors[i].year, vectors[i].month, vectors[i].day);
g_assert_cmpuint (g_date_get_week_of_year (&date, G_DATE_SATURDAY), ==, vectors[i].expected_saturday_week_of_year);
g_assert_cmpuint (g_date_get_week_of_year (&date, G_DATE_SUNDAY), ==, vectors[i].expected_sunday_week_of_year);
g_assert_cmpuint (g_date_get_week_of_year (&date, G_DATE_MONDAY), ==, vectors[i].expected_monday_week_of_year);
g_assert_cmpuint (g_date_get_sunday_week_of_year (&date), ==, vectors[i].expected_sunday_week_of_year);
g_assert_cmpuint (g_date_get_monday_week_of_year (&date), ==, vectors[i].expected_monday_week_of_year);
}
}
int
main (int argc, char** argv)
{
@@ -1806,6 +1892,7 @@ main (int argc, char** argv)
}
g_test_add_func ("/date/copy", test_copy);
g_test_add_func ("/date/valid-dmy", test_valid_dmy);
g_test_add_func ("/date/week-of-year", test_week_of_year);
return g_test_run ();
}