@@ -72,6 +72,7 @@ extern char *tzname[];
# define mbstate_t int
# define mbrlen(s, n, ps) mblen (s, n)
# define mbsinit(ps) (*(ps) == 0)
+# define mbrtowc(pwc, s, n, ps) mbtowc (pwc, s, n)
# endif
static const mbstate_t mbstate_zero;
#endif
@@ -432,6 +433,125 @@ static CHAR_T const month_name[][10] =
# define ut 0
#endif
+/* Check if the characters pointed to by S are a valid suffix: if they
+ start with a valid letter (in the current locale) or a single
+ punctuation character (a dash, an apostrophe, etc.) followed by
+ a letter, and they are not a format specifier. If a suffix is appended
+ at the end of a month name then probably a caller already tries to
+ workaround the problem of missing genitive forms so we should not
+ interfere with that. */
+static bool is_suffix (const CHAR_T *s)
+{
+#if defined COMPILE_WIDE || DO_MULTIBYTE
+# define ISALPHA iswalpha
+# define ISPUNCT iswpunct
+#else
+# define ISALPHA isalpha
+# define ISPUNCT ispunct
+#endif
+
+#if DO_MULTIBYTE && !defined COMPILE_WIDE
+ wchar_t c, next_char;
+ size_t mb_char_len, mb_next_char_len;
+ mbstate_t mbstate = mbstate_zero;
+
+ mb_char_len = mbrtowc (&c, s, strlen (s), &mbstate);
+ if ((ssize_t) mb_char_len <= 0)
+ c = L_('\0');
+
+ if (c == L_('\0'))
+ {
+ next_char = L_('\0');
+ mb_next_char_len = 0;
+ }
+ else
+ {
+ mbsinit (&mbstate);
+ mb_next_char_len = mbrtowc (&next_char, s + mb_char_len,
+ strlen (s + mb_char_len), &mbstate);
+ if ((ssize_t) mb_next_char_len <= 0)
+ next_char = L_('\0');
+ }
+# define NEXT_CHAR_OFFSET mb_char_len
+# define NEXT_NEXT_CHAR_OFFSET (mb_char_len + mb_next_char_len)
+#else
+ const CHAR_T c = s[0];
+ const CHAR_T next_char = c == L_('\0') ? L_('\0') : s[1];
+# define NEXT_CHAR_OFFSET 1
+# define NEXT_NEXT_CHAR_OFFSET 2
+#endif
+
+ if (ISALPHA (c))
+ return true;
+
+ if (c == L_('%'))
+ {
+ if (next_char == L_('%'))
+ return is_suffix (s + NEXT_NEXT_CHAR_OFFSET);
+ else
+ return false;
+ }
+
+ if (ISPUNCT (c))
+ return is_suffix (s + NEXT_CHAR_OFFSET);
+
+ return false;
+}
+
+/* Checks if the nearest format specifier in the format string pointed
+ to by Sis a day of the month specifier: "%e" or "%d". The format
+ specifier may be preceded with any number of other characters and may
+ contain modifiers. This function does not check if the modifiers are
+ valid and actually produce the day number so the result may be sometimes
+ wrong but in these cases the output will be wrong anyway because the
+ format is wrong. */
+static bool next_is_day (const CHAR_T *s)
+{
+ /* Skip until the percent sign is found. */
+ while (1)
+ {
+ switch (*s++)
+ {
+ case L_('\0'):
+ return false;
+ case L_('%'):
+ if (*s == L_('%'))
+ {
+ ++s;
+ continue;
+ }
+ break;
+ default:
+ continue;
+ }
+ break;
+ }
+
+ while (1)
+ {
+ switch (*s++)
+ {
+ /* Skip the modifiers. Here we don't check if they are valid. */
+ case L_('_'):
+ case L_('-'):
+ case L_('^'):
+ case L_('#'):
+ case L_('E'): case L_('O'):
+ case L_('0'): case L_('1'): case L_('2'): case L_('3'): case L_('4'):
+ case L_('5'): case L_('6'): case L_('7'): case L_('8'): case L_('9'):
+ continue;
+
+ /* Day found. */
+ case L_('d'): case L_('e'):
+ return true;
+
+ /* Something else found, including the terminating zero. */
+ default:
+ return false;
+ }
+ }
+}
+
static size_t __strftime_internal (CHAR_T *, size_t, const CHAR_T *,
const struct tm *, bool *
ut_argument_spec
@@ -492,6 +612,9 @@ __strftime_internal (CHAR_T *s, size_t maxsize, const CHAR_T
*format,
# define f_month \
((const CHAR_T *) (tp->tm_mon < 0 || tp->tm_mon > 11 \
? "?" : _NL_CURRENT (LC_TIME, NLW(MON_1) + tp->tm_mon)))
+# define f_altmonth \
+ ((const CHAR_T *) (tp->tm_mon < 0 || tp->tm_mon > 11 \
+ ? "?" : _NL_CURRENT (LC_TIME, NLW(ALTMON_1) + tp->tm_mon)))
# define ampm \
((const CHAR_T *) _NL_CURRENT (LC_TIME, tp->tm_hour > 11 \
? NLW(PM_STR) : NLW(AM_STR)))
@@ -499,6 +622,8 @@ __strftime_internal (CHAR_T *s, size_t maxsize, const CHAR_T
*format,
# define aw_len STRLEN (a_wkday)
# define am_len STRLEN (a_month)
# define ap_len STRLEN (ampm)
+# define day_month_order \
+ (current->values[_NL_ITEM_INDEX (_NL_DAY_MONTH_ORDER)].string[0])
#else
# if !HAVE_STRFTIME
# define f_wkday (tp->tm_wday < 0 || tp->tm_wday > 6 \
@@ -507,13 +632,16 @@ __strftime_internal (CHAR_T *s, size_t maxsize, const
CHAR_T *format,
? "?" : month_name[tp->tm_mon])
# define a_wkday f_wkday
# define a_month f_month
+# define f_altmonth (L_(""))
# define ampm (L_("AMPM") + 2 * (tp->tm_hour > 11))
size_t aw_len = 3;
size_t am_len = 3;
size_t ap_len = 2;
+# define day_month_order 2
# endif
#endif
+ int last_was_day = 0;
const char *zone;
size_t i = 0;
CHAR_T *p = s;
@@ -783,7 +911,20 @@ __strftime_internal (CHAR_T *s, size_t maxsize, const
CHAR_T *format,
to_lowcase = 0;
}
#if defined _NL_CURRENT || !HAVE_STRFTIME
- cpy (STRLEN (f_month), f_month);
+ /* Use f_altmonth only if f_altmonth is provided. */
+ if (f_altmonth[0]
+ /* Don't use f_altmonth if there is a suffix after %B because
+ it means that a caller already provides its own workaround. */
+ && !is_suffix (f + 1)
+ /* Use f_altmonth if the day-month order is valid and the last
+ format specifier was a day specifier. */
+ && ( (day_month_order <= 2 && last_was_day)
+ /* Use f_altmonth if the month-day order is valid and the next
+ format specifier will be a day specifier. */
+ || (day_month_order >= 2 && next_is_day (f + 1)) ))
+ cpy (STRLEN (f_altmonth), f_altmonth);
+ else
+ cpy (STRLEN (f_month), f_month);
break;
#else
goto underlying_strftime;
@@ -1400,6 +1541,18 @@ __strftime_internal (CHAR_T *s, size_t maxsize, const
CHAR_T *format,
}
break;
}
+
+ /* Update the previous_was_day flag. */
+ switch (format_char)
+ {
+ case L_('d'):
+ case L_('e'):
+ last_was_day = 1;
+ break;
+ default:
+ last_was_day = 0;
+ break;
+ }
}
if (p && maxsize != 0)
@@ -124,6 +124,8 @@ extern const struct __locale_data _nl_C_LC_TIME
attribute_hidden;
(&_nl_C_LC_TIME.values[_NL_ITEM_INDEX (ABDAY_1)].string)
# define month_name (&_nl_C_LC_TIME.values[_NL_ITEM_INDEX (MON_1)].string)
# define ab_month_name (&_nl_C_LC_TIME.values[_NL_ITEM_INDEX (ABMON_1)].string)
+# define alt_month_name \
+ (&_nl_C_LC_TIME.values[_NL_ITEM_INDEX (ALTMON_1)].string)
# define HERE_D_T_FMT (_nl_C_LC_TIME.values[_NL_ITEM_INDEX (D_T_FMT)].string)
# define HERE_D_FMT (_nl_C_LC_TIME.values[_NL_ITEM_INDEX (D_FMT)].string)