libstdc++: Some time_get fixes [PR78714]

Message ID 20211209141953.GH2646553@tucnak
State Committed
Headers
Series libstdc++: Some time_get fixes [PR78714] |

Commit Message

Jakub Jelinek Dec. 9, 2021, 2:19 p.m. UTC
  Hi!

The following patch is an attempt to fix various time_get related issues.
Sorry, it is long...

One of them is PR78714.  It seems _M_extract_via_format has been written
with how strftime behaves in mind rather than how strptime behaves.
There is a significant difference between the two, for strftime %a and %A
behave differently etc., one emits an abbreviated name, the other full name.
For strptime both should behave the same and accept both the full or
abbreviated names.  This needed large changes in _M_extract_name, which
was assuming the names are unique and names aren't prefixes of other names.
The _M_extract_name changes allow to deal with those cases.  As can be
seen in the new testcase, e.g. for %b and english locales we need to
accept both Apr and April.  If we see Apr in the input, the code looks
at whether there is end right after those 3 chars or if the next
character doesn't match characters in the longer names; in that case
it accepts the abbreviated name.  Otherwise, if the input has Apri, it
commits to a longer name and fails if it isn't April.  This behavior is
different from strptime, which for %bix and Aprix accepts it, but for
an input iterator I'm afraid we can't do better, we can't go back (peek
more than the current character).

Another case is that %d and %e in strptime should work the same, while
previously the code was hardcoding that %d would be 01 to 31 and %e
 1 to 31 (with leading 0 replaced by space).
strptime POSIX 2009 documentation seems to suggest for numbers it should
accept up to the specified number of digits rather than exactly that number
of digits:
The pattern "[x,y]" indicates that the value shall fall within the range
given (both bounds being inclusive), and the maximum number of characters scanned
shall be the maximum required to represent any value in the range without leading
zeros.
so by my reading "1:" is valid for "%H:".
The glibc strptime implementation actually skips any amount of whitespace
in all the cases where a number is read, my current patch skips a single
space at the start of %d/%e but not the others, but doesn't subtract the
space length from the len characters.
One option would be to do the leading whitespace skipping in _M_extract_num
but take it into account how many digits can be read.
This matters for " 12:" and "%H:", but not for " 12:" and " %H:"
as in the latter case the space in the format string results in all the
whitespace at the start to be consumed.
Note, the allowing of a single digit rather than 2 changes a behavior in
other ways, e.g. when seeing 40 in a number for range [1, 31] we reject
it as before, but previously we'd keep *ret == '4' because it was assuming
it has to be 2 digits and 40 isn't valid, so we know error already on the
4, but now we accept the 4 as value and fail iff the next format string
doesn't match the 0.
Also, previously it wasn't really checking the number was in the right
range, it would accept 00 for [1, 31] numbers, or would accept 39.

Another thing is that %I was parsing 12 as tm_hour 12 rather than as tm_hour 0
like e.g. glibc does.

Another thing is that %t was matching a single tab and %n a single newline,
while strptime docs say it skips over whitespace (again, zero or more).

Another thing is that %p wasn't handled at all, I think this was the main
cause of
FAIL: 22_locale/time_get/get_time/char/2.cc execution test
FAIL: 22_locale/time_get/get_time/char/wrapped_env.cc execution test
FAIL: 22_locale/time_get/get_time/char/wrapped_locale.cc execution test
FAIL: 22_locale/time_get/get_time/wchar_t/2.cc execution test
FAIL: 22_locale/time_get/get_time/wchar_t/wrapped_env.cc execution test
FAIL: 22_locale/time_get/get_time/wchar_t/wrapped_locale.cc execution test
before this patch, because en_HK* locales do use %I and %p in it.
The patch handles %p only if it follows %I (i.e. when the hour is parsed
first), which is the more usual case (in glibc):
grep '%I' localedata/locales/* | grep '%I.*%p' | wc -l
282
grep '%I' localedata/locales/* | grep -v '%I.*%p' | wc -l
44
grep '%I' localedata/locales/* | grep -v '%p' | wc -l
17
The last case use %P instead of %p in t_fmt_ampm, not sure if that one
is never used by strptime because %P isn't handled by strptime.
Anyway, the right thing to handle even %p%I would be to pass some state
around through all the _M_extract_via_format calls like glibc passes
  struct __strptime_state
  {
    unsigned int have_I : 1;
    unsigned int have_wday : 1;
    unsigned int have_yday : 1;
    unsigned int have_mon : 1;
    unsigned int have_mday : 1;
    unsigned int have_uweek : 1;
    unsigned int have_wweek : 1;
    unsigned int is_pm : 1;
    unsigned int want_century : 1;
    unsigned int want_era : 1;
    unsigned int want_xday : 1;
    enum ptime_locale_status decided : 2;
    signed char week_no;
    signed char century;
    int era_cnt;
  } s;
around.  That is for the %p case used like:
  if (s.have_I && s.is_pm)
    tm->tm_hour += 12;
during finalization, but handles tons of other cases which it is unclear
if libstdc++ needs or doesn't need to handle, e.g. strptime if one
specifies year and yday computes wday/mon/day from it, etc. basically for
the redundant fields computes them from other fields if those have been
parsed and are sufficient to determine it.
To do this we'd need to change ABI for the _M_extract_via_format,
though sure, we could add a wrapper around the new one with the old
arguments that would just use a dummy state.  And we'd need a new
_M_whatever finalizer that would do those post parsing tweaks.

Also, %% wasn't handled.

For a whitespace in the strings there was inconsistent behavior,
_M_extract_via_format would require exactly that whitespace char (say
matching space, or matching tab), while the caller follows what
https://eel.is/c++draft/locale.time.get#members-8.5 says, that
when encountering whitespace it skips whitespace in the format and
then whitespace in the input if any.  I've changed _M_extract_via_format
to skip whitespace in the input (looping over format isn't IMHO necessary,
because next iteration of the loop will handle that too).

Tested on x86_64-linux by make check-target-libstdc++-v3, ok for trunk
if it passes full bootstrap/regtest?

For the new 3.cc testcases, I have included hopefully correctly
corresponding C testcase using strptime in an attachment, and to the
extent where it can be compared (e.g. strptime on failure just
returns NULL, doesn't tell where it exactly stopped) I think the
only difference is that 
  str = "Novembur";
  format = "%bembur";
  ret = strptime (str, format, &time);
case where strptime accepts it but there is no way to do it with input
operator.

I admit I don't have libc++ or other STL libraries around to be able to
check how much the new 3.cc matches or disagrees with other implementations.

Now, the things not handled by this patch but which should be fixed (I
probably need to go back to compiler work) or at least looked at:

1) seems %j, %r, %U, %w and %W aren't handled (not sure if all of them
   are already in POSIX 2009 or some are later)
2) I haven't touched the %y/%Y/%C and year handling stuff, that is
   definitely not matching what POSIX 2009 says:
       C       All  but the last two digits of the year {2}; leading zeros shall be permitted but shall not be required. A leading '+' or '−' character shall be permitted before
               any leading zeros but shall not be required.
       y       The  last  two  digits of the year. When format contains neither a C conversion specifier nor a Y conversion specifier, values in the range [69,99] shall refer to
               years 1969 to 1999 inclusive and values in the range [00,68] shall refer to years 2000 to 2068 inclusive; leading zeros shall be permitted but shall  not  be  re‐
               quired. A leading '+' or '−' character shall be permitted before any leading zeros but shall not be required.

               Note:     It is expected that in a future version of this standard the default century inferred from a 2-digit year will change. (This would apply to all commands
                         accepting a 2-digit year as input.)
       Y       The full year {4}; leading zeros shall be permitted but shall not be required. A leading '+' or '−' character shall be permitted  before  any  leading  zeros  but
               shall not be required.
   I've tried to avoid making changes to _M_extract_num for these as well
   to keep current status quo (the __len == 4 cases).  One thing is what
   to do for things with %C %y and/or %Y in the formats, another thing
   is what to do in the methods that directly perform _M_extract_num
   for year
3) the above question what to do for leading whitespace of any numbers
   being parsed
4) the %p%I issue mentioned above and generally what to do if we
   pass state and have finalizers at the end of parsing
5) _M_extract_via_format is also inconsistent with its callers on handling
   the non-whitespace characters in between format specifiers, the caller
   follows https://eel.is/c++draft/locale.time.get#members-8.6 and does
   case insensitive comparison:
          // TODO real case-insensitive comparison
          else if (__ctype.tolower(*__s) == __ctype.tolower(*__fmt) ||
                   __ctype.toupper(*__s) == __ctype.toupper(*__fmt))
   while _M_extract_via_format only compares exact characters:
              // Verify format and input match, extract and discard.
              if (__format[__i] == *__beg)
                ++__beg;
   (another question is if there is a better way how to do real
   case-insensitive comparison of 2 characters and whether we e.g. need
   to handle the Turkish i/İ and ı/I which have different number of bytes
   in UTF-8)
6) _M_extract_name does something weird for case-sensitivity,
      // NB: Some of the locale data is in the form of all lowercase
      // names, and some is in the form of initially-capitalized
      // names. Look for both.
      if (__beg != __end)
   and
            if (__c == __names[__i1][0]
                || __c == __ctype.toupper(__names[__i1][0]))
   for the first letter while just
	__name[__pos] == *__beg
   on all the following letters.  strptime says:
   In case a text string (such as the name of a day of the week or a month
   name) is to be matched, the comparison is case insensitive.
   so supposedly all the _M_extract_name comparisons should be case
   insensitive.

2021-12-09  Jakub Jelinek  <jakub@redhat.com>

	PR libstdc++/78714
	* include/bits/locale_facets_nonio.tcc (_M_extract_via_format):
	Mention in function comment it interprets strptime format string
	rather than strftime.  Handle %a and %A the same by accepting both
	full and abbreviated names.  Similarly handle %h, %b and %B the same.
	Handle %d and %e the same by accepting possibly optional single space
	and 1 or 2 digits.  For %I store tm_hour 0 instead of tm_hour 12.  For
	%t and %n skip any whitespace.  Handle %p and %%.  For whitespace in
	the string skip any whitespace.
	(_M_extract_num): For __len == 2 accept 1 or 2 digits rather than
	always 2.  Don't punt early if __value * __mult is larget than __max
	or smaller than __min - __mult, instead punt if __value > __max.
	At the end verify __value is in between __min and __max and punt
	otherwise.
	(_M_extract_name): Allow non-unique names or names which are prefixes
	of other names.  Don't recompute lengths of names for every character.
	* testsuite/22_locale/time_get/get/char/3.cc: New test.
	* testsuite/22_locale/time_get/get/wchar_t/3.cc: New test.
	* testsuite/22_locale/time_get/get_date/char/12791.cc (test01): Use
	62 instead 60 and expect 6 to be accepted and thus *ret01 == '2'.
	* testsuite/22_locale/time_get/get_date/wchar_t/12791.cc (test01):
	Similarly.
	* testsuite/22_locale/time_get/get_time/char/2.cc (test02): Add " PM"
	to the string.
	* testsuite/22_locale/time_get/get_time/char/5.cc (test01): Expect
	tm_hour 1 rather than 0.
	* testsuite/22_locale/time_get/get_time/wchar_t/2.cc (test02): Add
	" PM" to the string.
	* testsuite/22_locale/time_get/get_time/wchar_t/5.cc (test01): Expect
	tm_hour 1 rather than 0.


	Jakub
#define _XOPEN_SOURCE
#include <time.h>
#include <stdio.h>

#define VERIFY(x) if (!(x)) printf ("%d %s failed\n", __LINE__, #x)

int
main ()
{
  const char *str, *format;
  char *ret;
  struct tm time;
  /* PR78714 tests */
  str = "Mon";
  format = "%a";
  ret = strptime (str, format, &time);
  VERIFY( ret && *ret == '\0' );
  VERIFY( time.tm_wday == 1 );

  str = "Tue ";
  format = "%a";
  ret = strptime (str, format, &time);
  VERIFY( ret != NULL && *ret == ' ' );
  VERIFY( time.tm_wday == 2 );

  str = "Wednesday";
  format = "%a";
  ret = strptime (str, format, &time);
  VERIFY( ret && *ret == '\0' );
  VERIFY( time.tm_wday == 3 );

  str = "Thu";
  format = "%A";
  ret = strptime (str, format, &time);
  VERIFY( ret && *ret == '\0' );
  VERIFY( time.tm_wday == 4 );

  str = "Fri ";
  format = "%A";
  ret = strptime (str, format, &time);
  VERIFY( ret != NULL && *ret == ' ' );
  VERIFY( time.tm_wday == 5 );

  str = "Saturday";
  format = "%A";
  ret = strptime (str, format, &time);
  VERIFY( ret && *ret == '\0' );
  VERIFY( time.tm_wday == 6 );

  str = "Feb";
  format = "%b";
  ret = strptime (str, format, &time);
  VERIFY( ret && *ret == '\0' );
  VERIFY( time.tm_mon == 1 );

  str = "Mar ";
  format = "%b";
  ret = strptime (str, format, &time);
  VERIFY( ret != NULL && *ret == ' ' );
  VERIFY( time.tm_mon == 2 );

  str = "April";
  format = "%b";
  ret = strptime (str, format, &time);
  VERIFY( ret && *ret == '\0' );
  VERIFY( time.tm_mon == 3 );

  str = "May";
  format = "%B";
  ret = strptime (str, format, &time);
  VERIFY( ret && *ret == '\0' );
  VERIFY( time.tm_mon == 4 );

  str = "Jun ";
  format = "%B";
  ret = strptime (str, format, &time);
  VERIFY( ret != NULL && *ret == ' ' );
  VERIFY( time.tm_mon == 5 );

  str = "July";
  format = "%B";
  ret = strptime (str, format, &time);
  VERIFY( ret && *ret == '\0' );
  VERIFY( time.tm_mon == 6 );

  str = "Aug";
  format = "%h";
  ret = strptime (str, format, &time);
  VERIFY( ret && *ret == '\0' );
  VERIFY( time.tm_mon == 7 );

  str = "May ";
  format = "%h";
  ret = strptime (str, format, &time);
  VERIFY( ret != NULL && *ret == ' ' );
  VERIFY( time.tm_mon == 4 );

  str = "October";
  format = "%h";
  ret = strptime (str, format, &time);
  VERIFY( ret && *ret == '\0' );
  VERIFY( time.tm_mon == 9 );

  /* Other tests.  */
  str = " 1.";
  format = "%d.";
  ret = strptime (str, format, &time);
  VERIFY( ret && *ret == '\0' );
  VERIFY( time.tm_mday == 1 );

  str = "2.";
  format = "%d.";
  ret = strptime (str, format, &time);
  VERIFY( ret && *ret == '\0' );
  VERIFY( time.tm_mday == 2 );

  str = "03.";
  format = "%d.";
  ret = strptime (str, format, &time);
  VERIFY( ret && *ret == '\0' );
  VERIFY( time.tm_mday == 3 );

  str = "0.";
  format = "%d.";
  ret = strptime (str, format, &time);
  VERIFY( ret == NULL );

  str = "32.";
  format = "%d.";
  ret = strptime (str, format, &time);
  VERIFY( ret == NULL );

  str = " 4.";
  format = "%e.";
  ret = strptime (str, format, &time);
  VERIFY( ret && *ret == '\0' );
  VERIFY( time.tm_mday == 4 );

  str = "5.";
  format = "%e.";
  ret = strptime (str, format, &time);
  VERIFY( ret && *ret == '\0' );
  VERIFY( time.tm_mday == 5 );

  str = "06.";
  format = "%e.";
  ret = strptime (str, format, &time);
  VERIFY( ret && *ret == '\0' );
  VERIFY( time.tm_mday == 6 );

  str = "0";
  format = "%e";
  ret = strptime (str, format, &time);
  VERIFY( ret == NULL );

  str = "35";
  format = "%e";
  ret = strptime (str, format, &time);
  VERIFY( ret == NULL );

  str = " \t\t 02";
  format = "%t%m";
  ret = strptime (str, format, &time);
  VERIFY( ret && *ret == '\0' );
  VERIFY( time.tm_mon == 1 );

  str = " \t \t 03";
  format = "%n%m";
  ret = strptime (str, format, &time);
  VERIFY( ret && *ret == '\0' );
  VERIFY( time.tm_mon == 2 );

  str = " \t \t 4";
  format = " %m";
  ret = strptime (str, format, &time);
  VERIFY( ret && *ret == '\0' );
  VERIFY( time.tm_mon == 3 );

  str = " \t \t 5";
  format = "\t%m";
  ret = strptime (str, format, &time);
  VERIFY( ret && *ret == '\0' );
  VERIFY( time.tm_mon == 4 );

  str = "12:00AM";
  format = "%I:%M%p";
  ret = strptime (str, format, &time);
  VERIFY( ret && *ret == '\0' );
  VERIFY( time.tm_hour == 0 );
  VERIFY( time.tm_min == 0 );

  str = "12:37AM";
  format = "%I:%M%p";
  ret = strptime (str, format, &time);
  VERIFY( ret && *ret == '\0' );
  VERIFY( time.tm_hour == 0 );
  VERIFY( time.tm_min == 37 );

  str = "01:25AM";
  format = "%I:%M%p";
  ret = strptime (str, format, &time);
  VERIFY( ret && *ret == '\0' );
  VERIFY( time.tm_hour == 1 );
  VERIFY( time.tm_min == 25 );

  str = "12:00PM";
  format = "%I:%M%p";
  ret = strptime (str, format, &time);
  VERIFY( ret && *ret == '\0' );
  VERIFY( time.tm_hour == 12 );
  VERIFY( time.tm_min == 0 );

  str = "12:42PM";
  format = "%I:%M%p";
  ret = strptime (str, format, &time);
  VERIFY( ret && *ret == '\0' );
  VERIFY( time.tm_hour == 12 );
  VERIFY( time.tm_min == 42 );

  str = "07:23PM";
  format = "%I:%M%p";
  ret = strptime (str, format, &time);
  VERIFY( ret && *ret == '\0' );
  VERIFY( time.tm_hour == 19 );
  VERIFY( time.tm_min == 23 );

  str = "17%20";
  format = "%H%%%M";
  ret = strptime (str, format, &time);
  VERIFY( ret && *ret == '\0' );
  VERIFY( time.tm_hour == 17 );
  VERIFY( time.tm_min == 20 );

  str = "24:30";
  format = "%H:%M";
  ret = strptime (str, format, &time);
  VERIFY( ret == NULL );

  /* This one behaves differently from strptime, in a single
     pass scaning we can't go back.  */
  str = "Novembur";
  format = "%bembur";
  ret = strptime (str, format, &time);
  VERIFY( ret != NULL && *ret == '\0' );
  return 0;
}
  

Comments

Jonathan Wakely Dec. 10, 2021, 3:48 p.m. UTC | #1
On Thu, 9 Dec 2021 at 14:20, Jakub Jelinek wrote:
>
> Hi!
>
> The following patch is an attempt to fix various time_get related issues.
> Sorry, it is long...
>
> One of them is PR78714.  It seems _M_extract_via_format has been written
> with how strftime behaves in mind rather than how strptime behaves.
> There is a significant difference between the two, for strftime %a and %A
> behave differently etc., one emits an abbreviated name, the other full name.
> For strptime both should behave the same and accept both the full or
> abbreviated names.  This needed large changes in _M_extract_name, which
> was assuming the names are unique and names aren't prefixes of other names.
> The _M_extract_name changes allow to deal with those cases.  As can be
> seen in the new testcase, e.g. for %b and english locales we need to
> accept both Apr and April.  If we see Apr in the input, the code looks
> at whether there is end right after those 3 chars or if the next
> character doesn't match characters in the longer names; in that case
> it accepts the abbreviated name.  Otherwise, if the input has Apri, it
> commits to a longer name and fails if it isn't April.  This behavior is
> different from strptime, which for %bix and Aprix accepts it, but for
> an input iterator I'm afraid we can't do better, we can't go back (peek
> more than the current character).

Yes, I think that's the best we can do.

> Another case is that %d and %e in strptime should work the same, while
> previously the code was hardcoding that %d would be 01 to 31 and %e
>  1 to 31 (with leading 0 replaced by space).
> strptime POSIX 2009 documentation seems to suggest for numbers it should
> accept up to the specified number of digits rather than exactly that number
> of digits:
> The pattern "[x,y]" indicates that the value shall fall within the range
> given (both bounds being inclusive), and the maximum number of characters scanned
> shall be the maximum required to represent any value in the range without leading
> zeros.
> so by my reading "1:" is valid for "%H:".

I agree.

> The glibc strptime implementation actually skips any amount of whitespace
> in all the cases where a number is read, my current patch skips a single
> space at the start of %d/%e but not the others, but doesn't subtract the
> space length from the len characters.
> One option would be to do the leading whitespace skipping in _M_extract_num
> but take it into account how many digits can be read.
> This matters for " 12:" and "%H:", but not for " 12:" and " %H:"
> as in the latter case the space in the format string results in all the
> whitespace at the start to be consumed.
> Note, the allowing of a single digit rather than 2 changes a behavior in
> other ways, e.g. when seeing 40 in a number for range [1, 31] we reject
> it as before, but previously we'd keep *ret == '4' because it was assuming
> it has to be 2 digits and 40 isn't valid, so we know error already on the
> 4, but now we accept the 4 as value and fail iff the next format string
> doesn't match the 0.

Again, I think that's the best we can do. And I think that's fine. The
primary purpose of these interfaces is to read valid dates, not check
whether input forms a valid date or not.

If you want to be able to tentatively parse a date (or number, or
monetary quantity etc) and then try recovering and parsing it
differently (e.g. "if it doesn't parse as a date in format X, try
format Y") then you can copy the input to a new buffer and re-parse
it. We don't need to make that work by rewinding the input iterator or
trying to peek ahead until we know if the parse will succeed.

> Also, previously it wasn't really checking the number was in the right
> range, it would accept 00 for [1, 31] numbers, or would accept 39.

Doh.

>
> Another thing is that %I was parsing 12 as tm_hour 12 rather than as tm_hour 0
> like e.g. glibc does.
>
> Another thing is that %t was matching a single tab and %n a single newline,
> while strptime docs say it skips over whitespace (again, zero or more).
>
> Another thing is that %p wasn't handled at all, I think this was the main
> cause of

Yes, and that's PR 71367

> FAIL: 22_locale/time_get/get_time/char/2.cc execution test
> FAIL: 22_locale/time_get/get_time/char/wrapped_env.cc execution test
> FAIL: 22_locale/time_get/get_time/char/wrapped_locale.cc execution test
> FAIL: 22_locale/time_get/get_time/wchar_t/2.cc execution test
> FAIL: 22_locale/time_get/get_time/wchar_t/wrapped_env.cc execution test
> FAIL: 22_locale/time_get/get_time/wchar_t/wrapped_locale.cc execution test
> before this patch, because en_HK* locales do use %I and %p in it.
> The patch handles %p only if it follows %I (i.e. when the hour is parsed
> first), which is the more usual case (in glibc):
> grep '%I' localedata/locales/* | grep '%I.*%p' | wc -l
> 282
> grep '%I' localedata/locales/* | grep -v '%I.*%p' | wc -l
> 44
> grep '%I' localedata/locales/* | grep -v '%p' | wc -l
> 17
> The last case use %P instead of %p in t_fmt_ampm, not sure if that one
> is never used by strptime because %P isn't handled by strptime.
> Anyway, the right thing to handle even %p%I would be to pass some state
> around through all the _M_extract_via_format calls like glibc passes
>   struct __strptime_state
>   {
>     unsigned int have_I : 1;
>     unsigned int have_wday : 1;
>     unsigned int have_yday : 1;
>     unsigned int have_mon : 1;
>     unsigned int have_mday : 1;
>     unsigned int have_uweek : 1;
>     unsigned int have_wweek : 1;
>     unsigned int is_pm : 1;
>     unsigned int want_century : 1;
>     unsigned int want_era : 1;
>     unsigned int want_xday : 1;
>     enum ptime_locale_status decided : 2;
>     signed char week_no;
>     signed char century;
>     int era_cnt;
>   } s;
> around.  That is for the %p case used like:
>   if (s.have_I && s.is_pm)
>     tm->tm_hour += 12;
> during finalization, but handles tons of other cases which it is unclear
> if libstdc++ needs or doesn't need to handle, e.g. strptime if one
> specifies year and yday computes wday/mon/day from it, etc. basically for
> the redundant fields computes them from other fields if those have been
> parsed and are sufficient to determine it.
> To do this we'd need to change ABI for the _M_extract_via_format,
> though sure, we could add a wrapper around the new one with the old
> arguments that would just use a dummy state.  And we'd need a new
> _M_whatever finalizer that would do those post parsing tweaks.
>
> Also, %% wasn't handled.
>
> For a whitespace in the strings there was inconsistent behavior,
> _M_extract_via_format would require exactly that whitespace char (say
> matching space, or matching tab), while the caller follows what
> https://eel.is/c++draft/locale.time.get#members-8.5 says, that
> when encountering whitespace it skips whitespace in the format and
> then whitespace in the input if any.  I've changed _M_extract_via_format
> to skip whitespace in the input (looping over format isn't IMHO necessary,
> because next iteration of the loop will handle that too).
>
> Tested on x86_64-linux by make check-target-libstdc++-v3, ok for trunk
> if it passes full bootstrap/regtest?

Yes, thanks very much.

>
> For the new 3.cc testcases, I have included hopefully correctly
> corresponding C testcase using strptime in an attachment, and to the
> extent where it can be compared (e.g. strptime on failure just
> returns NULL, doesn't tell where it exactly stopped) I think the
> only difference is that
>   str = "Novembur";
>   format = "%bembur";
>   ret = strptime (str, format, &time);
> case where strptime accepts it but there is no way to do it with input
> operator.
>
> I admit I don't have libc++ or other STL libraries around to be able to
> check how much the new 3.cc matches or disagrees with other implementations.

You can just "dnf install clang libcxx" and then "clang++
-stdlib=libc++ ..." to use it.

But I can check that myself (probably not until next week though).
I'll also see what difference this makes for running the libc++ tests
against libstdc++.

Either way, it's a big improvement on what we have now, thanks!


>
> Now, the things not handled by this patch but which should be fixed (I
> probably need to go back to compiler work) or at least looked at:
>
> 1) seems %j, %r, %U, %w and %W aren't handled (not sure if all of them
>    are already in POSIX 2009 or some are later)

I think that's all in POSIX 2009.

> 2) I haven't touched the %y/%Y/%C and year handling stuff, that is
>    definitely not matching what POSIX 2009 says:
>        C       All  but the last two digits of the year {2}; leading zeros shall be permitted but shall not be required. A leading '+' or '−' character shall be permitted before
>                any leading zeros but shall not be required.
>        y       The  last  two  digits of the year. When format contains neither a C conversion specifier nor a Y conversion specifier, values in the range [69,99] shall refer to
>                years 1969 to 1999 inclusive and values in the range [00,68] shall refer to years 2000 to 2068 inclusive; leading zeros shall be permitted but shall  not  be  re‐
>                quired. A leading '+' or '−' character shall be permitted before any leading zeros but shall not be required.
>
>                Note:     It is expected that in a future version of this standard the default century inferred from a 2-digit year will change. (This would apply to all commands
>                          accepting a 2-digit year as input.)
>        Y       The full year {4}; leading zeros shall be permitted but shall not be required. A leading '+' or '−' character shall be permitted  before  any  leading  zeros  but
>                shall not be required.
>    I've tried to avoid making changes to _M_extract_num for these as well
>    to keep current status quo (the __len == 4 cases).  One thing is what
>    to do for things with %C %y and/or %Y in the formats, another thing
>    is what to do in the methods that directly perform _M_extract_num
>    for year
> 3) the above question what to do for leading whitespace of any numbers
>    being parsed
> 4) the %p%I issue mentioned above and generally what to do if we
>    pass state and have finalizers at the end of parsing
> 5) _M_extract_via_format is also inconsistent with its callers on handling
>    the non-whitespace characters in between format specifiers, the caller
>    follows https://eel.is/c++draft/locale.time.get#members-8.6 and does
>    case insensitive comparison:
>           // TODO real case-insensitive comparison
>           else if (__ctype.tolower(*__s) == __ctype.tolower(*__fmt) ||
>                    __ctype.toupper(*__s) == __ctype.toupper(*__fmt))
>    while _M_extract_via_format only compares exact characters:
>               // Verify format and input match, extract and discard.
>               if (__format[__i] == *__beg)
>                 ++__beg;
>    (another question is if there is a better way how to do real
>    case-insensitive comparison of 2 characters and whether we e.g. need
>    to handle the Turkish i/İ and ı/I which have different number of bytes
>    in UTF-8)
> 6) _M_extract_name does something weird for case-sensitivity,
>       // NB: Some of the locale data is in the form of all lowercase
>       // names, and some is in the form of initially-capitalized
>       // names. Look for both.
>       if (__beg != __end)
>    and
>             if (__c == __names[__i1][0]
>                 || __c == __ctype.toupper(__names[__i1][0]))
>    for the first letter while just
>         __name[__pos] == *__beg
>    on all the following letters.  strptime says:
>    In case a text string (such as the name of a day of the week or a month
>    name) is to be matched, the comparison is case insensitive.
>    so supposedly all the _M_extract_name comparisons should be case
>    insensitive.
>
> 2021-12-09  Jakub Jelinek  <jakub@redhat.com>
>
>         PR libstdc++/78714
>         * include/bits/locale_facets_nonio.tcc (_M_extract_via_format):
>         Mention in function comment it interprets strptime format string
>         rather than strftime.  Handle %a and %A the same by accepting both
>         full and abbreviated names.  Similarly handle %h, %b and %B the same.
>         Handle %d and %e the same by accepting possibly optional single space
>         and 1 or 2 digits.  For %I store tm_hour 0 instead of tm_hour 12.  For
>         %t and %n skip any whitespace.  Handle %p and %%.  For whitespace in
>         the string skip any whitespace.
>         (_M_extract_num): For __len == 2 accept 1 or 2 digits rather than
>         always 2.  Don't punt early if __value * __mult is larget than __max
>         or smaller than __min - __mult, instead punt if __value > __max.
>         At the end verify __value is in between __min and __max and punt
>         otherwise.
>         (_M_extract_name): Allow non-unique names or names which are prefixes
>         of other names.  Don't recompute lengths of names for every character.
>         * testsuite/22_locale/time_get/get/char/3.cc: New test.
>         * testsuite/22_locale/time_get/get/wchar_t/3.cc: New test.
>         * testsuite/22_locale/time_get/get_date/char/12791.cc (test01): Use
>         62 instead 60 and expect 6 to be accepted and thus *ret01 == '2'.
>         * testsuite/22_locale/time_get/get_date/wchar_t/12791.cc (test01):
>         Similarly.
>         * testsuite/22_locale/time_get/get_time/char/2.cc (test02): Add " PM"
>         to the string.
>         * testsuite/22_locale/time_get/get_time/char/5.cc (test01): Expect
>         tm_hour 1 rather than 0.
>         * testsuite/22_locale/time_get/get_time/wchar_t/2.cc (test02): Add
>         " PM" to the string.
>         * testsuite/22_locale/time_get/get_time/wchar_t/5.cc (test01): Expect
>         tm_hour 1 rather than 0.
>
> --- libstdc++-v3/include/bits/locale_facets_nonio.tcc.jj        2021-01-05 00:13:58.292297226 +0100
> +++ libstdc++-v3/include/bits/locale_facets_nonio.tcc   2021-12-09 12:41:53.117583447 +0100
> @@ -684,7 +684,7 @@ _GLIBCXX_END_NAMESPACE_LDBL_OR_CXX11
>      time_get<_CharT, _InIter>::do_date_order() const
>      { return time_base::no_order; }
>
> -  // Expand a strftime format string and parse it.  E.g., do_get_date() may
> +  // Expand a strptime format string and parse it.  E.g., do_get_date() may
>    // pass %m/%d/%Y => extracted characters.
>    template<typename _CharT, typename _InIter>
>      _InIter
> @@ -714,41 +714,27 @@ _GLIBCXX_END_NAMESPACE_LDBL_OR_CXX11
>                   const char* __cs;
>                   _CharT __wcs[10];
>                 case 'a':
> -                 // Abbreviated weekday name [tm_wday]
> -                 const char_type*  __days1[7];
> -                 __tp._M_days_abbreviated(__days1);
> -                 __beg = _M_extract_name(__beg, __end, __mem, __days1,
> -                                         7, __io, __tmperr);
> -                 if (!__tmperr)
> -                   __tm->tm_wday = __mem;
> -                 break;
>                 case 'A':
> -                 // Weekday name [tm_wday].
> -                 const char_type*  __days2[7];
> -                 __tp._M_days(__days2);
> -                 __beg = _M_extract_name(__beg, __end, __mem, __days2,
> -                                         7, __io, __tmperr);
> +                 // Weekday name (possibly abbreviated) [tm_wday]
> +                 const char_type*  __days[14];
> +                 __tp._M_days(&__days[0]);
> +                 __tp._M_days_abbreviated(&__days[7]);
> +                 __beg = _M_extract_name(__beg, __end, __mem, __days,
> +                                         14, __io, __tmperr);
>                   if (!__tmperr)
> -                   __tm->tm_wday = __mem;
> +                   __tm->tm_wday = __mem % 7;
>                   break;
>                 case 'h':
>                 case 'b':
> -                 // Abbreviated month name [tm_mon]
> -                 const char_type*  __months1[12];
> -                 __tp._M_months_abbreviated(__months1);
> -                 __beg = _M_extract_name(__beg, __end, __mem,
> -                                         __months1, 12, __io, __tmperr);
> -                 if (!__tmperr)
> -                   __tm->tm_mon = __mem;
> -                 break;
>                 case 'B':
> -                 // Month name [tm_mon].
> -                 const char_type*  __months2[12];
> -                 __tp._M_months(__months2);
> +                 // Month name (possibly abbreviated) [tm_mon]
> +                 const char_type*  __months[24];
> +                 __tp._M_months(&__months[0]);
> +                 __tp._M_months_abbreviated(&__months[12]);
>                   __beg = _M_extract_name(__beg, __end, __mem,
> -                                         __months2, 12, __io, __tmperr);
> +                                         __months, 24, __io, __tmperr);
>                   if (!__tmperr)
> -                   __tm->tm_mon = __mem;
> +                   __tm->tm_mon = __mem % 12;
>                   break;
>                 case 'c':
>                   // Default time and date representation.
> @@ -758,21 +744,12 @@ _GLIBCXX_END_NAMESPACE_LDBL_OR_CXX11
>                                                 __tm, __dt[0]);
>                   break;
>                 case 'd':
> -                 // Day [01, 31]. [tm_mday]
> -                 __beg = _M_extract_num(__beg, __end, __mem, 1, 31, 2,
> -                                        __io, __tmperr);
> -                 if (!__tmperr)
> -                   __tm->tm_mday = __mem;
> -                 break;
>                 case 'e':
> -                 // Day [1, 31], with single digits preceded by
> -                 // space. [tm_mday]
> +                 // Day [1, 31]. [tm_mday]
>                   if (__ctype.is(ctype_base::space, *__beg))
> -                   __beg = _M_extract_num(++__beg, __end, __mem, 1, 9,
> -                                          1, __io, __tmperr);
> -                 else
> -                   __beg = _M_extract_num(__beg, __end, __mem, 10, 31,
> -                                          2, __io, __tmperr);
> +                   ++__beg;
> +                 __beg = _M_extract_num(__beg, __end, __mem, 1, 31, 2,
> +                                        __io, __tmperr);
>                   if (!__tmperr)
>                     __tm->tm_mday = __mem;
>                   break;
> @@ -795,7 +772,7 @@ _GLIBCXX_END_NAMESPACE_LDBL_OR_CXX11
>                   __beg = _M_extract_num(__beg, __end, __mem, 1, 12, 2,
>                                          __io, __tmperr);
>                   if (!__tmperr)
> -                   __tm->tm_hour = __mem;
> +                   __tm->tm_hour = __mem % 12;
>                   break;
>                 case 'm':
>                   // Month [01, 12]. [tm_mon]
> @@ -812,10 +789,22 @@ _GLIBCXX_END_NAMESPACE_LDBL_OR_CXX11
>                     __tm->tm_min = __mem;
>                   break;
>                 case 'n':
> -                 if (__ctype.narrow(*__beg, 0) == '\n')
> +               case 't':
> +                 while (__beg != __end
> +                        && __ctype.is(ctype_base::space, *__beg))
>                     ++__beg;
> -                 else
> -                   __tmperr |= ios_base::failbit;
> +                 break;
> +               case 'p':
> +                 // Locale's a.m. or p.m.
> +                 const char_type*  __ampm[2];
> +                 __tp._M_am_pm(&__ampm[0]);
> +                 if (!__ampm[0][0] || !__ampm[1][0])
> +                   break;
> +                 __beg = _M_extract_name(__beg, __end, __mem, __ampm,
> +                                         2, __io, __tmperr);
> +                 // FIXME: This only works if %I comes before %p.
> +                 if (!__tmperr && __mem)
> +                   __tm->tm_hour += 12;
>                   break;
>                 case 'R':
>                   // Equivalent to (%H:%M).
> @@ -836,12 +825,6 @@ _GLIBCXX_END_NAMESPACE_LDBL_OR_CXX11
>                   if (!__tmperr)
>                   __tm->tm_sec = __mem;
>                   break;
> -               case 't':
> -                 if (__ctype.narrow(*__beg, 0) == '\t')
> -                   ++__beg;
> -                 else
> -                   __tmperr |= ios_base::failbit;
> -                 break;
>                 case 'T':
>                   // Equivalent to (%H:%M:%S).
>                   __cs = "%H:%M:%S";
> @@ -899,11 +882,24 @@ _GLIBCXX_END_NAMESPACE_LDBL_OR_CXX11
>                   else
>                     __tmperr |= ios_base::failbit;
>                   break;
> +               case '%':
> +                 if (*__beg == __ctype.widen('%'))
> +                   ++__beg;
> +                 else
> +                   __tmperr |= ios_base::failbit;
> +                 break;
>                 default:
>                   // Not recognized.
>                   __tmperr |= ios_base::failbit;
>                 }
>             }
> +         else if (__ctype.is(ctype_base::space, __format[__i]))
> +           {
> +             // Skip any whitespace.
> +             while (__beg != __end
> +                    && __ctype.is(ctype_base::space, *__beg))
> +               ++__beg;
> +           }
>           else
>             {
>               // Verify format and input match, extract and discard.
> @@ -930,10 +926,6 @@ _GLIBCXX_END_NAMESPACE_LDBL_OR_CXX11
>        const locale& __loc = __io._M_getloc();
>        const ctype<_CharT>& __ctype = use_facet<ctype<_CharT> >(__loc);
>
> -      // As-is works for __len = 1, 2, 4, the values actually used.
> -      int __mult = __len == 2 ? 10 : (__len == 4 ? 1000 : 1);
> -
> -      ++__min;
>        size_t __i = 0;
>        int __value = 0;
>        for (; __beg != __end && __i < __len; ++__beg, (void)++__i)
> @@ -942,19 +934,20 @@ _GLIBCXX_END_NAMESPACE_LDBL_OR_CXX11
>           if (__c >= '0' && __c <= '9')
>             {
>               __value = __value * 10 + (__c - '0');
> -             const int __valuec = __value * __mult;
> -             if (__valuec > __max || __valuec + __mult < __min)
> +             if (__value > __max)
>                 break;
> -             __mult /= 10;
>             }
>           else
>             break;
>         }
> -      if (__i == __len)
> -       __member = __value;
>        // Special encoding for do_get_year, 'y', and 'Y' above.
> -      else if (__len == 4 && __i == 2)
> +      if (__len == 4 && __i == 2)
>         __member = __value - 100;
> +      else if (__len == 4 && __i == 4)
> +       __member = __value;
> +      else if (__len == 2 && __i && __i <= 2
> +              && __value >= __min && __value <= __max)
> +       __member = __value;
>        else
>         __err |= ios_base::failbit;
>
> @@ -962,7 +955,10 @@ _GLIBCXX_END_NAMESPACE_LDBL_OR_CXX11
>      }
>
>    // Assumptions:
> -  // All elements in __names are unique.
> +  // All elements in __names are unique, except if __indexlen is
> +  // even __names in the first half could be the same as corresponding
> +  // __names in the second half (May is abbreviated as May).  Some __names
> +  // elements could be prefixes of other __names elements.
>    template<typename _CharT, typename _InIter>
>      _InIter
>      time_get<_CharT, _InIter>::
> @@ -974,12 +970,15 @@ _GLIBCXX_END_NAMESPACE_LDBL_OR_CXX11
>        const locale& __loc = __io._M_getloc();
>        const ctype<_CharT>& __ctype = use_facet<ctype<_CharT> >(__loc);
>
> -      int* __matches = static_cast<int*>(__builtin_alloca(sizeof(int)
> -                                                         * __indexlen));
> +      size_t* __matches
> +       = static_cast<size_t*>(__builtin_alloca(2 * sizeof(size_t)
> +                                               * __indexlen));
> +      size_t* __lengths = __matches + __indexlen;
>        size_t __nmatches = 0;
>        size_t __pos = 0;
>        bool __testvalid = true;
>        const char_type* __name;
> +      bool __begupdated = false;
>
>        // Look for initial matches.
>        // NB: Some of the locale data is in the form of all lowercase
> @@ -991,26 +990,88 @@ _GLIBCXX_END_NAMESPACE_LDBL_OR_CXX11
>           for (size_t __i1 = 0; __i1 < __indexlen; ++__i1)
>             if (__c == __names[__i1][0]
>                 || __c == __ctype.toupper(__names[__i1][0]))
> -             __matches[__nmatches++] = __i1;
> +             {
> +               __lengths[__nmatches]
> +                 = __traits_type::length(__names[__i1]);
> +               __matches[__nmatches++] = __i1;
> +             }
>         }
>
>        while (__nmatches > 1)
>         {
>           // Find smallest matching string.
> -         size_t __minlen = __traits_type::length(__names[__matches[0]]);
> +         size_t __minlen = __lengths[0];
>           for (size_t __i2 = 1; __i2 < __nmatches; ++__i2)
> -           __minlen = std::min(__minlen,
> -                             __traits_type::length(__names[__matches[__i2]]));
> -         ++__beg;
> +           __minlen = std::min(__minlen, __lengths[__i2]);
>           ++__pos;
> +         ++__beg;
> +         if (__pos == __minlen)
> +           {
> +             // If some match has remaining length of 0,
> +             // need to decide if any match with remaining
> +             // length non-zero matches the next character.
> +             // If so, remove all matches with remaining length
> +             // 0 from consideration, otherwise keep only matches
> +             // with remaining length 0.
> +             bool __match_longer = false;
> +
> +             if (__beg != __end)
> +               for (size_t __i3 = 0; __i3 < __nmatches; ++__i3)
> +                 {
> +                   __name = __names[__matches[__i3]];
> +                   if (__lengths[__i3] > __pos && (__name[__pos] == *__beg))
> +                     {
> +                       __match_longer = true;
> +                       break;
> +                     }
> +                 }
> +             for (size_t __i4 = 0; __i4 < __nmatches;)
> +               if (__match_longer == (__lengths[__i4] == __pos))
> +                 {
> +                   __matches[__i4] = __matches[--__nmatches];
> +                   __lengths[__i4] = __lengths[__nmatches];
> +                 }
> +               else
> +                 ++__i4;
> +             if (__match_longer)
> +               {
> +                 __minlen = __lengths[0];
> +                 for (size_t __i5 = 1; __i5 < __nmatches; ++__i5)
> +                   __minlen = std::min(__minlen, __lengths[__i5]);
> +               }
> +             else
> +               {
> +                 // Deal with May being full as well as abbreviated month
> +                 // name.  Pick the smaller index.
> +                 if (__nmatches == 2 && (__indexlen & 1) == 0)
> +                   {
> +                     if (__matches[0] < __indexlen / 2)
> +                       {
> +                         if (__matches[1] == __matches[0] + __indexlen / 2)
> +                           __nmatches = 1;
> +                       }
> +                     else if (__matches[1] == __matches[0] - __indexlen / 2)
> +                       {
> +                         __matches[0] = __matches[1];
> +                         __lengths[0] = __lengths[1];
> +                         __nmatches = 1;
> +                       }
> +                   }
> +                 __begupdated = true;
> +                 break;
> +               }
> +           }
>           if (__pos < __minlen && __beg != __end)
> -           for (size_t __i3 = 0; __i3 < __nmatches;)
> +           for (size_t __i6 = 0; __i6 < __nmatches;)
>               {
> -               __name = __names[__matches[__i3]];
> +               __name = __names[__matches[__i6]];
>                 if (!(__name[__pos] == *__beg))
> -                 __matches[__i3] = __matches[--__nmatches];
> +                 {
> +                   __matches[__i6] = __matches[--__nmatches];
> +                   __lengths[__i6] = __lengths[__nmatches];
> +                 }
>                 else
> -                 ++__i3;
> +                 ++__i6;
>               }
>           else
>             break;
> @@ -1019,10 +1080,13 @@ _GLIBCXX_END_NAMESPACE_LDBL_OR_CXX11
>        if (__nmatches == 1)
>         {
>           // Make sure found name is completely extracted.
> -         ++__beg;
> -         ++__pos;
> +         if (!__begupdated)
> +           {
> +             ++__beg;
> +             ++__pos;
> +           }
>           __name = __names[__matches[0]];
> -         const size_t __len = __traits_type::length(__name);
> +         const size_t __len = __lengths[0];
>           while (__pos < __len && __beg != __end && __name[__pos] == *__beg)
>             ++__beg, (void)++__pos;
>
> --- libstdc++-v3/testsuite/22_locale/time_get/get/char/3.cc.jj  2021-12-09 11:10:32.472129017 +0100
> +++ libstdc++-v3/testsuite/22_locale/time_get/get/char/3.cc     2021-12-09 13:11:04.103251278 +0100
> @@ -0,0 +1,356 @@
> +// { dg-do run { target c++11 } }
> +
> +// Copyright (C) 2021 Free Software Foundation, Inc.
> +//
> +// This file is part of the GNU ISO C++ Library.  This library is free
> +// software; you can redistribute it and/or modify it under the
> +// terms of the GNU General Public License as published by the
> +// Free Software Foundation; either version 3, 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 General Public License for more details.
> +
> +// You should have received a copy of the GNU General Public License along
> +// with this library; see the file COPYING3.  If not see
> +// <http://www.gnu.org/licenses/>.
> +
> +#include <locale>
> +#include <sstream>
> +#include <iterator>
> +#include <testsuite_hooks.h>
> +
> +void
> +test01()
> +{
> +  using namespace std;
> +
> +  locale loc_c = locale::classic();
> +
> +  istringstream iss;
> +  iss.imbue(loc_c);
> +  const time_get<char>& tget = use_facet<time_get<char>>(iss.getloc());
> +  typedef istreambuf_iterator<char> iter;
> +  const iter end;
> +
> +  tm time;
> +  ios_base::iostate err = ios_base::badbit;
> +
> +  // PR78714 tests
> +  iss.str("Mon");
> +  string format = "%a";
> +  auto ret = tget.get(iter(iss), end, iss, err, &time,
> +                     format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::eofbit );
> +  VERIFY( ret == end );
> +  VERIFY( time.tm_wday == 1 );
> +
> +  iss.str("Tue ");
> +  format = "%a";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::goodbit );
> +  VERIFY( ret != end && *ret == ' ' );
> +  VERIFY( time.tm_wday == 2 );
> +
> +  iss.str("Wednesday");
> +  format = "%a";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::eofbit );
> +  VERIFY( ret == end );
> +  VERIFY( time.tm_wday == 3 );
> +
> +  iss.str("Thu");
> +  format = "%A";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::eofbit );
> +  VERIFY( ret == end );
> +  VERIFY( time.tm_wday == 4 );
> +
> +  iss.str("Fri ");
> +  format = "%A";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::goodbit );
> +  VERIFY( ret != end && *ret == ' ' );
> +  VERIFY( time.tm_wday == 5 );
> +
> +  iss.str("Saturday");
> +  format = "%A";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::eofbit );
> +  VERIFY( ret == end );
> +  VERIFY( time.tm_wday == 6 );
> +
> +  iss.str("Feb");
> +  format = "%b";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::eofbit );
> +  VERIFY( ret == end );
> +  VERIFY( time.tm_mon == 1 );
> +
> +  iss.str("Mar ");
> +  format = "%b";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::goodbit );
> +  VERIFY( ret != end && *ret == ' ' );
> +  VERIFY( time.tm_mon == 2 );
> +
> +  iss.str("April");
> +  format = "%b";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::eofbit );
> +  VERIFY( ret == end );
> +  VERIFY( time.tm_mon == 3 );
> +
> +  iss.str("May");
> +  format = "%B";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::eofbit );
> +  VERIFY( ret == end );
> +  VERIFY( time.tm_mon == 4 );
> +
> +  iss.str("Jun ");
> +  format = "%B";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::goodbit );
> +  VERIFY( ret != end && *ret == ' ' );
> +  VERIFY( time.tm_mon == 5 );
> +
> +  iss.str("July");
> +  format = "%B";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::eofbit );
> +  VERIFY( ret == end );
> +  VERIFY( time.tm_mon == 6 );
> +
> +  iss.str("Aug");
> +  format = "%h";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::eofbit );
> +  VERIFY( ret == end );
> +  VERIFY( time.tm_mon == 7 );
> +
> +  iss.str("May ");
> +  format = "%h";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::goodbit );
> +  VERIFY( ret != end && *ret == ' ' );
> +  VERIFY( time.tm_mon == 4 );
> +
> +  iss.str("October");
> +  format = "%h";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::eofbit );
> +  VERIFY( ret == end );
> +  VERIFY( time.tm_mon == 9 );
> +
> +  // Other tests.
> +  iss.str(" 1.");
> +  format = "%d.";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::goodbit );
> +  VERIFY( ret == end );
> +  VERIFY( time.tm_mday == 1 );
> +
> +  iss.str("2.");
> +  format = "%d.";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::goodbit );
> +  VERIFY( ret == end );
> +  VERIFY( time.tm_mday == 2 );
> +
> +  iss.str("03.");
> +  format = "%d.";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::goodbit );
> +  VERIFY( ret == end );
> +  VERIFY( time.tm_mday == 3 );
> +
> +  iss.str("0.");
> +  format = "%d.";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::failbit );
> +  VERIFY( ret != end && *ret == '.' );
> +
> +  iss.str("32.");
> +  format = "%d.";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::failbit );
> +  VERIFY( ret != end && *ret == '2' );
> +
> +  iss.str(" 4.");
> +  format = "%e.";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::goodbit );
> +  VERIFY( ret == end );
> +  VERIFY( time.tm_mday == 4 );
> +
> +  iss.str("5.");
> +  format = "%e.";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::goodbit );
> +  VERIFY( ret == end );
> +  VERIFY( time.tm_mday == 5 );
> +
> +  iss.str("06.");
> +  format = "%e.";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::goodbit );
> +  VERIFY( ret == end );
> +  VERIFY( time.tm_mday == 6 );
> +
> +  iss.str("0");
> +  format = "%e";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::failbit|ios_base::eofbit );
> +  VERIFY( ret == end );
> +
> +  iss.str("35");
> +  format = "%e";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::failbit );
> +  VERIFY( ret != end && *ret == '5' );
> +
> +  iss.str(" \t\t 02");
> +  format = "%t%m";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::eofbit );
> +  VERIFY( ret == end );
> +  VERIFY( time.tm_mon == 1 );
> +
> +  iss.str(" \t \t 03");
> +  format = "%n%m";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::eofbit );
> +  VERIFY( ret == end );
> +  VERIFY( time.tm_mon == 2 );
> +
> +  iss.str(" \t \t 4");
> +  format = " %m";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::eofbit );
> +  VERIFY( ret == end );
> +  VERIFY( time.tm_mon == 3 );
> +
> +  iss.str(" \t \t 5");
> +  format = "\t%m";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::eofbit );
> +  VERIFY( ret == end );
> +  VERIFY( time.tm_mon == 4 );
> +
> +  iss.str("12:00AM");
> +  format = "%I:%M%p";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::eofbit );
> +  VERIFY( ret == end );
> +  VERIFY( time.tm_hour == 0 );
> +  VERIFY( time.tm_min == 0 );
> +
> +  iss.str("12:37AM");
> +  format = "%I:%M%p";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::eofbit );
> +  VERIFY( ret == end );
> +  VERIFY( time.tm_hour == 0 );
> +  VERIFY( time.tm_min == 37 );
> +
> +  iss.str("01:25AM");
> +  format = "%I:%M%p";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::eofbit );
> +  VERIFY( ret == end );
> +  VERIFY( time.tm_hour == 1 );
> +  VERIFY( time.tm_min == 25 );
> +
> +  iss.str("12:00PM");
> +  format = "%I:%M%p";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::eofbit );
> +  VERIFY( ret == end );
> +  VERIFY( time.tm_hour == 12 );
> +  VERIFY( time.tm_min == 0 );
> +
> +  iss.str("12:42PM");
> +  format = "%I:%M%p";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::eofbit );
> +  VERIFY( ret == end );
> +  VERIFY( time.tm_hour == 12 );
> +  VERIFY( time.tm_min == 42 );
> +
> +  iss.str("07:23PM");
> +  format = "%I:%M%p";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::eofbit );
> +  VERIFY( ret == end );
> +  VERIFY( time.tm_hour == 19 );
> +  VERIFY( time.tm_min == 23 );
> +
> +  iss.str("17%20");
> +  format = "%H%%%M";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::eofbit );
> +  VERIFY( ret == end );
> +  VERIFY( time.tm_hour == 17 );
> +  VERIFY( time.tm_min == 20 );
> +
> +  iss.str("24:30");
> +  format = "%H:%M";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::failbit );
> +  VERIFY( ret != end && *ret == '4' );
> +
> +  // This one behaves differently from strptime, in a single
> +  // pass scaning we can't go back.
> +  iss.str("Novembur");
> +  format = "%bembur";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::failbit );
> +  VERIFY( ret != end && *ret == 'u' );
> +}
> +
> +int
> +main()
> +{
> +  test01();
> +  return 0;
> +}
> --- libstdc++-v3/testsuite/22_locale/time_get/get/wchar_t/3.cc.jj       2021-12-09 13:08:34.785398279 +0100
> +++ libstdc++-v3/testsuite/22_locale/time_get/get/wchar_t/3.cc  2021-12-09 13:14:01.012746470 +0100
> @@ -0,0 +1,356 @@
> +// { dg-do run { target c++11 } }
> +
> +// Copyright (C) 2021 Free Software Foundation, Inc.
> +//
> +// This file is part of the GNU ISO C++ Library.  This library is free
> +// software; you can redistribute it and/or modify it under the
> +// terms of the GNU General Public License as published by the
> +// Free Software Foundation; either version 3, 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 General Public License for more details.
> +
> +// You should have received a copy of the GNU General Public License along
> +// with this library; see the file COPYING3.  If not see
> +// <http://www.gnu.org/licenses/>.
> +
> +#include <locale>
> +#include <sstream>
> +#include <iterator>
> +#include <testsuite_hooks.h>
> +
> +void
> +test01()
> +{
> +  using namespace std;
> +
> +  locale loc_c = locale::classic();
> +
> +  wistringstream iss;
> +  iss.imbue(loc_c);
> +  const time_get<wchar_t>& tget = use_facet<time_get<wchar_t>>(iss.getloc());
> +  typedef istreambuf_iterator<wchar_t> iter;
> +  const iter end;
> +
> +  tm time;
> +  ios_base::iostate err = ios_base::badbit;
> +
> +  // PR78714 tests
> +  iss.str(L"Mon");
> +  wstring format = L"%a";
> +  auto ret = tget.get(iter(iss), end, iss, err, &time,
> +                     format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::eofbit );
> +  VERIFY( ret == end );
> +  VERIFY( time.tm_wday == 1 );
> +
> +  iss.str(L"Tue L");
> +  format = L"%a";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::goodbit );
> +  VERIFY( ret != end && *ret == L' ' );
> +  VERIFY( time.tm_wday == 2 );
> +
> +  iss.str(L"Wednesday");
> +  format = L"%a";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::eofbit );
> +  VERIFY( ret == end );
> +  VERIFY( time.tm_wday == 3 );
> +
> +  iss.str(L"Thu");
> +  format = L"%A";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::eofbit );
> +  VERIFY( ret == end );
> +  VERIFY( time.tm_wday == 4 );
> +
> +  iss.str(L"Fri L");
> +  format = L"%A";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::goodbit );
> +  VERIFY( ret != end && *ret == L' ' );
> +  VERIFY( time.tm_wday == 5 );
> +
> +  iss.str(L"Saturday");
> +  format = L"%A";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::eofbit );
> +  VERIFY( ret == end );
> +  VERIFY( time.tm_wday == 6 );
> +
> +  iss.str(L"Feb");
> +  format = L"%b";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::eofbit );
> +  VERIFY( ret == end );
> +  VERIFY( time.tm_mon == 1 );
> +
> +  iss.str(L"Mar L");
> +  format = L"%b";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::goodbit );
> +  VERIFY( ret != end && *ret == L' ' );
> +  VERIFY( time.tm_mon == 2 );
> +
> +  iss.str(L"April");
> +  format = L"%b";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::eofbit );
> +  VERIFY( ret == end );
> +  VERIFY( time.tm_mon == 3 );
> +
> +  iss.str(L"May");
> +  format = L"%B";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::eofbit );
> +  VERIFY( ret == end );
> +  VERIFY( time.tm_mon == 4 );
> +
> +  iss.str(L"Jun L");
> +  format = L"%B";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::goodbit );
> +  VERIFY( ret != end && *ret == L' ' );
> +  VERIFY( time.tm_mon == 5 );
> +
> +  iss.str(L"July");
> +  format = L"%B";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::eofbit );
> +  VERIFY( ret == end );
> +  VERIFY( time.tm_mon == 6 );
> +
> +  iss.str(L"Aug");
> +  format = L"%h";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::eofbit );
> +  VERIFY( ret == end );
> +  VERIFY( time.tm_mon == 7 );
> +
> +  iss.str(L"May L");
> +  format = L"%h";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::goodbit );
> +  VERIFY( ret != end && *ret == L' ' );
> +  VERIFY( time.tm_mon == 4 );
> +
> +  iss.str(L"October");
> +  format = L"%h";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::eofbit );
> +  VERIFY( ret == end );
> +  VERIFY( time.tm_mon == 9 );
> +
> +  // Other tests.
> +  iss.str(L" 1.");
> +  format = L"%d.";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::goodbit );
> +  VERIFY( ret == end );
> +  VERIFY( time.tm_mday == 1 );
> +
> +  iss.str(L"2.");
> +  format = L"%d.";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::goodbit );
> +  VERIFY( ret == end );
> +  VERIFY( time.tm_mday == 2 );
> +
> +  iss.str(L"03.");
> +  format = L"%d.";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::goodbit );
> +  VERIFY( ret == end );
> +  VERIFY( time.tm_mday == 3 );
> +
> +  iss.str(L"0.");
> +  format = L"%d.";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::failbit );
> +  VERIFY( ret != end && *ret == L'.' );
> +
> +  iss.str(L"32.");
> +  format = L"%d.";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::failbit );
> +  VERIFY( ret != end && *ret == L'2' );
> +
> +  iss.str(L" 4.");
> +  format = L"%e.";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::goodbit );
> +  VERIFY( ret == end );
> +  VERIFY( time.tm_mday == 4 );
> +
> +  iss.str(L"5.");
> +  format = L"%e.";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::goodbit );
> +  VERIFY( ret == end );
> +  VERIFY( time.tm_mday == 5 );
> +
> +  iss.str(L"06.");
> +  format = L"%e.";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::goodbit );
> +  VERIFY( ret == end );
> +  VERIFY( time.tm_mday == 6 );
> +
> +  iss.str(L"0");
> +  format = L"%e";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::failbit|ios_base::eofbit );
> +  VERIFY( ret == end );
> +
> +  iss.str(L"35");
> +  format = L"%e";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::failbit );
> +  VERIFY( ret != end && *ret == L'5' );
> +
> +  iss.str(L" \t\t 02");
> +  format = L"%t%m";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::eofbit );
> +  VERIFY( ret == end );
> +  VERIFY( time.tm_mon == 1 );
> +
> +  iss.str(L" \t \t 03");
> +  format = L"%n%m";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::eofbit );
> +  VERIFY( ret == end );
> +  VERIFY( time.tm_mon == 2 );
> +
> +  iss.str(L" \t \t 4");
> +  format = L" %m";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::eofbit );
> +  VERIFY( ret == end );
> +  VERIFY( time.tm_mon == 3 );
> +
> +  iss.str(L" \t \t 5");
> +  format = L"\t%m";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::eofbit );
> +  VERIFY( ret == end );
> +  VERIFY( time.tm_mon == 4 );
> +
> +  iss.str(L"12:00AM");
> +  format = L"%I:%M%p";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::eofbit );
> +  VERIFY( ret == end );
> +  VERIFY( time.tm_hour == 0 );
> +  VERIFY( time.tm_min == 0 );
> +
> +  iss.str(L"12:37AM");
> +  format = L"%I:%M%p";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::eofbit );
> +  VERIFY( ret == end );
> +  VERIFY( time.tm_hour == 0 );
> +  VERIFY( time.tm_min == 37 );
> +
> +  iss.str(L"01:25AM");
> +  format = L"%I:%M%p";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::eofbit );
> +  VERIFY( ret == end );
> +  VERIFY( time.tm_hour == 1 );
> +  VERIFY( time.tm_min == 25 );
> +
> +  iss.str(L"12:00PM");
> +  format = L"%I:%M%p";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::eofbit );
> +  VERIFY( ret == end );
> +  VERIFY( time.tm_hour == 12 );
> +  VERIFY( time.tm_min == 0 );
> +
> +  iss.str(L"12:42PM");
> +  format = L"%I:%M%p";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::eofbit );
> +  VERIFY( ret == end );
> +  VERIFY( time.tm_hour == 12 );
> +  VERIFY( time.tm_min == 42 );
> +
> +  iss.str(L"07:23PM");
> +  format = L"%I:%M%p";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::eofbit );
> +  VERIFY( ret == end );
> +  VERIFY( time.tm_hour == 19 );
> +  VERIFY( time.tm_min == 23 );
> +
> +  iss.str(L"17%20");
> +  format = L"%H%%%M";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::eofbit );
> +  VERIFY( ret == end );
> +  VERIFY( time.tm_hour == 17 );
> +  VERIFY( time.tm_min == 20 );
> +
> +  iss.str(L"24:30");
> +  format = L"%H:%M";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::failbit );
> +  VERIFY( ret != end && *ret == L'4' );
> +
> +  // This one behaves differently from strptime, in a single
> +  // pass scaning we can't go back.
> +  iss.str(L"Novembur");
> +  format = L"%bembur";
> +  ret = tget.get(iter(iss), end, iss, err, &time,
> +                format.data(), format.data()+format.size());
> +  VERIFY( err == ios_base::failbit );
> +  VERIFY( ret != end && *ret == L'u' );
> +}
> +
> +int
> +main()
> +{
> +  test01();
> +  return 0;
> +}
> --- libstdc++-v3/testsuite/22_locale/time_get/get_date/char/12791.cc.jj 2021-01-05 00:13:58.426295719 +0100
> +++ libstdc++-v3/testsuite/22_locale/time_get/get_date/char/12791.cc    2021-12-09 13:02:02.498075474 +0100
> @@ -39,14 +39,14 @@ void test01()
>    const ios_base::iostate good = ios_base::goodbit;
>    ios_base::iostate errorstate = good;
>
> -  iss.str("60/04/71");
> +  iss.str("62/04/71");
>    iterator_type is_it01(iss);
>    tm time01;
>    errorstate = good;
>    iterator_type ret01 = tim_get.get_date(is_it01, end, iss, errorstate,
>                                          &time01);
>    VERIFY( errorstate == ios_base::failbit );
> -  VERIFY( *ret01 == '6' );
> +  VERIFY( *ret01 == '2' );
>
>    iss.str("04/38/71");
>    iterator_type is_it02(iss);
> --- libstdc++-v3/testsuite/22_locale/time_get/get_date/wchar_t/12791.cc.jj      2021-01-05 00:13:58.426295719 +0100
> +++ libstdc++-v3/testsuite/22_locale/time_get/get_date/wchar_t/12791.cc 2021-12-09 13:02:46.579437526 +0100
> @@ -40,14 +40,14 @@ void test01()
>    const ios_base::iostate good = ios_base::goodbit;
>    ios_base::iostate errorstate = good;
>
> -  iss.str(L"60/04/71");
> +  iss.str(L"62/04/71");
>    iterator_type is_it01(iss);
>    tm time01;
>    errorstate = good;
>    iterator_type ret01 = tim_get.get_date(is_it01, end, iss, errorstate,
>                                          &time01);
>    VERIFY( errorstate == ios_base::failbit );
> -  VERIFY( *ret01 == L'6' );
> +  VERIFY( *ret01 == L'2' );
>
>    iss.str(L"04/38/71");
>    iterator_type is_it02(iss);
> --- libstdc++-v3/testsuite/22_locale/time_get/get_time/char/2.cc.jj     2021-01-05 00:13:58.000000000 +0100
> +++ libstdc++-v3/testsuite/22_locale/time_get/get_time/char/2.cc        2021-12-08 15:18:42.586859294 +0100
> @@ -48,7 +48,7 @@ void test02()
>
>    // inspection of named locales, en_HK
>    iss.imbue(loc_hk);
> -  iss.str("12:00:00 PST");
> +  iss.str("12:00:00 PM PST");
>    // Hong Kong in California! Well, they have Paris in Vegas... this
>    // is all a little disney-esque anyway. Besides, you can get decent
>    // Dim Sum in San Francisco.
> --- libstdc++-v3/testsuite/22_locale/time_get/get_time/char/5.cc.jj     2021-01-05 00:13:58.000000000 +0100
> +++ libstdc++-v3/testsuite/22_locale/time_get/get_time/char/5.cc        2021-12-08 19:12:54.519198801 +0100
> @@ -52,7 +52,7 @@ void test01()
>    VERIFY( err == (failbit | eofbit) );
>    VERIFY( tm0.tm_sec == 0 );
>    VERIFY( tm0.tm_min == 0 );
> -  VERIFY( tm0.tm_hour == 0 );
> +  VERIFY( tm0.tm_hour == 1 );
>
>    const string str1 = "12:00:00 ";
>    iter_type end1 = tg.get_time(str1.begin(), str1.end(), iss, err, &tm1);
> --- libstdc++-v3/testsuite/22_locale/time_get/get_time/wchar_t/2.cc.jj  2021-01-05 00:13:58.427295707 +0100
> +++ libstdc++-v3/testsuite/22_locale/time_get/get_time/wchar_t/2.cc     2021-12-08 15:24:10.378202522 +0100
> @@ -48,7 +48,7 @@ void test02()
>
>    // inspection of named locales, en_HK
>    iss.imbue(loc_hk);
> -  iss.str(L"12:00:00 PST");
> +  iss.str(L"12:00:00 PM PST");
>    // Hong Kong in California! Well, they have Paris in Vegas... this
>    // is all a little disney-esque anyway. Besides, you can get decent
>    // Dim Sum in San Francisco.
> --- libstdc++-v3/testsuite/22_locale/time_get/get_time/wchar_t/5.cc.jj  2021-01-05 00:13:58.428295696 +0100
> +++ libstdc++-v3/testsuite/22_locale/time_get/get_time/wchar_t/5.cc     2021-12-08 19:14:32.826801803 +0100
> @@ -52,7 +52,7 @@ void test01()
>    VERIFY( err == (failbit | eofbit) );
>    VERIFY( tm0.tm_sec == 0 );
>    VERIFY( tm0.tm_min == 0 );
> -  VERIFY( tm0.tm_hour == 0 );
> +  VERIFY( tm0.tm_hour == 1 );
>
>    const wstring str1 = L"12:00:00 ";
>    iter_type end1 = tg.get_time(str1.begin(), str1.end(), iss, err, &tm1);
>
>         Jakub
  

Patch

--- libstdc++-v3/include/bits/locale_facets_nonio.tcc.jj	2021-01-05 00:13:58.292297226 +0100
+++ libstdc++-v3/include/bits/locale_facets_nonio.tcc	2021-12-09 12:41:53.117583447 +0100
@@ -684,7 +684,7 @@  _GLIBCXX_END_NAMESPACE_LDBL_OR_CXX11
     time_get<_CharT, _InIter>::do_date_order() const
     { return time_base::no_order; }
 
-  // Expand a strftime format string and parse it.  E.g., do_get_date() may
+  // Expand a strptime format string and parse it.  E.g., do_get_date() may
   // pass %m/%d/%Y => extracted characters.
   template<typename _CharT, typename _InIter>
     _InIter
@@ -714,41 +714,27 @@  _GLIBCXX_END_NAMESPACE_LDBL_OR_CXX11
 		  const char* __cs;
 		  _CharT __wcs[10];
 		case 'a':
-		  // Abbreviated weekday name [tm_wday]
-		  const char_type*  __days1[7];
-		  __tp._M_days_abbreviated(__days1);
-		  __beg = _M_extract_name(__beg, __end, __mem, __days1,
-					  7, __io, __tmperr);
-		  if (!__tmperr)
-		    __tm->tm_wday = __mem;
-		  break;
 		case 'A':
-		  // Weekday name [tm_wday].
-		  const char_type*  __days2[7];
-		  __tp._M_days(__days2);
-		  __beg = _M_extract_name(__beg, __end, __mem, __days2,
-					  7, __io, __tmperr);
+		  // Weekday name (possibly abbreviated) [tm_wday]
+		  const char_type*  __days[14];
+		  __tp._M_days(&__days[0]);
+		  __tp._M_days_abbreviated(&__days[7]);
+		  __beg = _M_extract_name(__beg, __end, __mem, __days,
+					  14, __io, __tmperr);
 		  if (!__tmperr)
-		    __tm->tm_wday = __mem;
+		    __tm->tm_wday = __mem % 7;
 		  break;
 		case 'h':
 		case 'b':
-		  // Abbreviated month name [tm_mon]
-		  const char_type*  __months1[12];
-		  __tp._M_months_abbreviated(__months1);
-		  __beg = _M_extract_name(__beg, __end, __mem,
-					  __months1, 12, __io, __tmperr);
-		  if (!__tmperr)
-		    __tm->tm_mon = __mem;
-		  break;
 		case 'B':
-		  // Month name [tm_mon].
-		  const char_type*  __months2[12];
-		  __tp._M_months(__months2);
+		  // Month name (possibly abbreviated) [tm_mon]
+		  const char_type*  __months[24];
+		  __tp._M_months(&__months[0]);
+		  __tp._M_months_abbreviated(&__months[12]);
 		  __beg = _M_extract_name(__beg, __end, __mem,
-					  __months2, 12, __io, __tmperr);
+					  __months, 24, __io, __tmperr);
 		  if (!__tmperr)
-		    __tm->tm_mon = __mem;
+		    __tm->tm_mon = __mem % 12;
 		  break;
 		case 'c':
 		  // Default time and date representation.
@@ -758,21 +744,12 @@  _GLIBCXX_END_NAMESPACE_LDBL_OR_CXX11
 						__tm, __dt[0]);
 		  break;
 		case 'd':
-		  // Day [01, 31]. [tm_mday]
-		  __beg = _M_extract_num(__beg, __end, __mem, 1, 31, 2,
-					 __io, __tmperr);
-		  if (!__tmperr)
-		    __tm->tm_mday = __mem;
-		  break;
 		case 'e':
-		  // Day [1, 31], with single digits preceded by
-		  // space. [tm_mday]
+		  // Day [1, 31]. [tm_mday]
 		  if (__ctype.is(ctype_base::space, *__beg))
-		    __beg = _M_extract_num(++__beg, __end, __mem, 1, 9,
-					   1, __io, __tmperr);
-		  else
-		    __beg = _M_extract_num(__beg, __end, __mem, 10, 31,
-					   2, __io, __tmperr);
+		    ++__beg;
+		  __beg = _M_extract_num(__beg, __end, __mem, 1, 31, 2,
+					 __io, __tmperr);
 		  if (!__tmperr)
 		    __tm->tm_mday = __mem;
 		  break;
@@ -795,7 +772,7 @@  _GLIBCXX_END_NAMESPACE_LDBL_OR_CXX11
 		  __beg = _M_extract_num(__beg, __end, __mem, 1, 12, 2,
 					 __io, __tmperr);
 		  if (!__tmperr)
-		    __tm->tm_hour = __mem;
+		    __tm->tm_hour = __mem % 12;
 		  break;
 		case 'm':
 		  // Month [01, 12]. [tm_mon]
@@ -812,10 +789,22 @@  _GLIBCXX_END_NAMESPACE_LDBL_OR_CXX11
 		    __tm->tm_min = __mem;
 		  break;
 		case 'n':
-		  if (__ctype.narrow(*__beg, 0) == '\n')
+		case 't':
+		  while (__beg != __end
+			 && __ctype.is(ctype_base::space, *__beg))
 		    ++__beg;
-		  else
-		    __tmperr |= ios_base::failbit;
+		  break;
+		case 'p':
+		  // Locale's a.m. or p.m.
+		  const char_type*  __ampm[2];
+		  __tp._M_am_pm(&__ampm[0]);
+		  if (!__ampm[0][0] || !__ampm[1][0])
+		    break;
+		  __beg = _M_extract_name(__beg, __end, __mem, __ampm,
+					  2, __io, __tmperr);
+		  // FIXME: This only works if %I comes before %p.
+		  if (!__tmperr && __mem)
+		    __tm->tm_hour += 12;
 		  break;
 		case 'R':
 		  // Equivalent to (%H:%M).
@@ -836,12 +825,6 @@  _GLIBCXX_END_NAMESPACE_LDBL_OR_CXX11
 		  if (!__tmperr)
 		  __tm->tm_sec = __mem;
 		  break;
-		case 't':
-		  if (__ctype.narrow(*__beg, 0) == '\t')
-		    ++__beg;
-		  else
-		    __tmperr |= ios_base::failbit;
-		  break;
 		case 'T':
 		  // Equivalent to (%H:%M:%S).
 		  __cs = "%H:%M:%S";
@@ -899,11 +882,24 @@  _GLIBCXX_END_NAMESPACE_LDBL_OR_CXX11
 		  else
 		    __tmperr |= ios_base::failbit;
 		  break;
+		case '%':
+		  if (*__beg == __ctype.widen('%'))
+		    ++__beg;
+		  else
+		    __tmperr |= ios_base::failbit;
+		  break;
 		default:
 		  // Not recognized.
 		  __tmperr |= ios_base::failbit;
 		}
 	    }
+	  else if (__ctype.is(ctype_base::space, __format[__i]))
+	    {
+	      // Skip any whitespace.
+	      while (__beg != __end
+		     && __ctype.is(ctype_base::space, *__beg))
+		++__beg;
+	    }
 	  else
 	    {
 	      // Verify format and input match, extract and discard.
@@ -930,10 +926,6 @@  _GLIBCXX_END_NAMESPACE_LDBL_OR_CXX11
       const locale& __loc = __io._M_getloc();
       const ctype<_CharT>& __ctype = use_facet<ctype<_CharT> >(__loc);
 
-      // As-is works for __len = 1, 2, 4, the values actually used.
-      int __mult = __len == 2 ? 10 : (__len == 4 ? 1000 : 1);
-
-      ++__min;
       size_t __i = 0;
       int __value = 0;
       for (; __beg != __end && __i < __len; ++__beg, (void)++__i)
@@ -942,19 +934,20 @@  _GLIBCXX_END_NAMESPACE_LDBL_OR_CXX11
 	  if (__c >= '0' && __c <= '9')
 	    {
 	      __value = __value * 10 + (__c - '0');
-	      const int __valuec = __value * __mult;
-	      if (__valuec > __max || __valuec + __mult < __min)
+	      if (__value > __max)
 		break;
-	      __mult /= 10;
 	    }
 	  else
 	    break;
 	}
-      if (__i == __len)
-	__member = __value;
       // Special encoding for do_get_year, 'y', and 'Y' above.
-      else if (__len == 4 && __i == 2)
+      if (__len == 4 && __i == 2)
 	__member = __value - 100;
+      else if (__len == 4 && __i == 4)
+	__member = __value;
+      else if (__len == 2 && __i && __i <= 2
+	       && __value >= __min && __value <= __max)
+	__member = __value;
       else
 	__err |= ios_base::failbit;
 
@@ -962,7 +955,10 @@  _GLIBCXX_END_NAMESPACE_LDBL_OR_CXX11
     }
 
   // Assumptions:
-  // All elements in __names are unique.
+  // All elements in __names are unique, except if __indexlen is
+  // even __names in the first half could be the same as corresponding
+  // __names in the second half (May is abbreviated as May).  Some __names
+  // elements could be prefixes of other __names elements.
   template<typename _CharT, typename _InIter>
     _InIter
     time_get<_CharT, _InIter>::
@@ -974,12 +970,15 @@  _GLIBCXX_END_NAMESPACE_LDBL_OR_CXX11
       const locale& __loc = __io._M_getloc();
       const ctype<_CharT>& __ctype = use_facet<ctype<_CharT> >(__loc);
 
-      int* __matches = static_cast<int*>(__builtin_alloca(sizeof(int)
-							  * __indexlen));
+      size_t* __matches
+	= static_cast<size_t*>(__builtin_alloca(2 * sizeof(size_t)
+						* __indexlen));
+      size_t* __lengths = __matches + __indexlen;
       size_t __nmatches = 0;
       size_t __pos = 0;
       bool __testvalid = true;
       const char_type* __name;
+      bool __begupdated = false;
 
       // Look for initial matches.
       // NB: Some of the locale data is in the form of all lowercase
@@ -991,26 +990,88 @@  _GLIBCXX_END_NAMESPACE_LDBL_OR_CXX11
 	  for (size_t __i1 = 0; __i1 < __indexlen; ++__i1)
 	    if (__c == __names[__i1][0]
 		|| __c == __ctype.toupper(__names[__i1][0]))
-	      __matches[__nmatches++] = __i1;
+	      {
+		__lengths[__nmatches]
+		  = __traits_type::length(__names[__i1]);
+		__matches[__nmatches++] = __i1;
+	      }
 	}
 
       while (__nmatches > 1)
 	{
 	  // Find smallest matching string.
-	  size_t __minlen = __traits_type::length(__names[__matches[0]]);
+	  size_t __minlen = __lengths[0];
 	  for (size_t __i2 = 1; __i2 < __nmatches; ++__i2)
-	    __minlen = std::min(__minlen,
-			      __traits_type::length(__names[__matches[__i2]]));
-	  ++__beg;
+	    __minlen = std::min(__minlen, __lengths[__i2]);
 	  ++__pos;
+	  ++__beg;
+	  if (__pos == __minlen)
+	    {
+	      // If some match has remaining length of 0,
+	      // need to decide if any match with remaining
+	      // length non-zero matches the next character.
+	      // If so, remove all matches with remaining length
+	      // 0 from consideration, otherwise keep only matches
+	      // with remaining length 0.
+	      bool __match_longer = false;
+
+	      if (__beg != __end)
+		for (size_t __i3 = 0; __i3 < __nmatches; ++__i3)
+		  {
+		    __name = __names[__matches[__i3]];
+		    if (__lengths[__i3] > __pos && (__name[__pos] == *__beg))
+		      {
+			__match_longer = true;
+			break;
+		      }
+		  }
+	      for (size_t __i4 = 0; __i4 < __nmatches;)
+		if (__match_longer == (__lengths[__i4] == __pos))
+		  {
+		    __matches[__i4] = __matches[--__nmatches];
+		    __lengths[__i4] = __lengths[__nmatches];
+		  }
+		else
+		  ++__i4;
+	      if (__match_longer)
+		{
+		  __minlen = __lengths[0];
+		  for (size_t __i5 = 1; __i5 < __nmatches; ++__i5)
+		    __minlen = std::min(__minlen, __lengths[__i5]);
+		}
+	      else
+		{
+		  // Deal with May being full as well as abbreviated month
+		  // name.  Pick the smaller index.
+		  if (__nmatches == 2 && (__indexlen & 1) == 0)
+		    {
+		      if (__matches[0] < __indexlen / 2)
+			{
+			  if (__matches[1] == __matches[0] + __indexlen / 2)
+			    __nmatches = 1;
+			}
+		      else if (__matches[1] == __matches[0] - __indexlen / 2)
+			{
+			  __matches[0] = __matches[1];
+			  __lengths[0] = __lengths[1];
+			  __nmatches = 1;
+			}
+		    }
+		  __begupdated = true;
+		  break;
+		}
+	    }
 	  if (__pos < __minlen && __beg != __end)
-	    for (size_t __i3 = 0; __i3 < __nmatches;)
+	    for (size_t __i6 = 0; __i6 < __nmatches;)
 	      {
-		__name = __names[__matches[__i3]];
+		__name = __names[__matches[__i6]];
 		if (!(__name[__pos] == *__beg))
-		  __matches[__i3] = __matches[--__nmatches];
+		  {
+		    __matches[__i6] = __matches[--__nmatches];
+		    __lengths[__i6] = __lengths[__nmatches];
+		  }
 		else
-		  ++__i3;
+		  ++__i6;
 	      }
 	  else
 	    break;
@@ -1019,10 +1080,13 @@  _GLIBCXX_END_NAMESPACE_LDBL_OR_CXX11
       if (__nmatches == 1)
 	{
 	  // Make sure found name is completely extracted.
-	  ++__beg;
-	  ++__pos;
+	  if (!__begupdated)
+	    {
+	      ++__beg;
+	      ++__pos;
+	    }
 	  __name = __names[__matches[0]];
-	  const size_t __len = __traits_type::length(__name);
+	  const size_t __len = __lengths[0];
 	  while (__pos < __len && __beg != __end && __name[__pos] == *__beg)
 	    ++__beg, (void)++__pos;
 
--- libstdc++-v3/testsuite/22_locale/time_get/get/char/3.cc.jj	2021-12-09 11:10:32.472129017 +0100
+++ libstdc++-v3/testsuite/22_locale/time_get/get/char/3.cc	2021-12-09 13:11:04.103251278 +0100
@@ -0,0 +1,356 @@ 
+// { dg-do run { target c++11 } }
+
+// Copyright (C) 2021 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library.  This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, 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 General Public License for more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+#include <locale>
+#include <sstream>
+#include <iterator>
+#include <testsuite_hooks.h>
+
+void
+test01()
+{
+  using namespace std;
+
+  locale loc_c = locale::classic();
+
+  istringstream iss;
+  iss.imbue(loc_c);
+  const time_get<char>& tget = use_facet<time_get<char>>(iss.getloc());
+  typedef istreambuf_iterator<char> iter;
+  const iter end;
+
+  tm time;
+  ios_base::iostate err = ios_base::badbit;
+
+  // PR78714 tests
+  iss.str("Mon");
+  string format = "%a";
+  auto ret = tget.get(iter(iss), end, iss, err, &time,
+		      format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::eofbit );
+  VERIFY( ret == end );
+  VERIFY( time.tm_wday == 1 );
+
+  iss.str("Tue ");
+  format = "%a";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::goodbit );
+  VERIFY( ret != end && *ret == ' ' );
+  VERIFY( time.tm_wday == 2 );
+
+  iss.str("Wednesday");
+  format = "%a";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::eofbit );
+  VERIFY( ret == end );
+  VERIFY( time.tm_wday == 3 );
+
+  iss.str("Thu");
+  format = "%A";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::eofbit );
+  VERIFY( ret == end );
+  VERIFY( time.tm_wday == 4 );
+
+  iss.str("Fri ");
+  format = "%A";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::goodbit );
+  VERIFY( ret != end && *ret == ' ' );
+  VERIFY( time.tm_wday == 5 );
+
+  iss.str("Saturday");
+  format = "%A";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::eofbit );
+  VERIFY( ret == end );
+  VERIFY( time.tm_wday == 6 );
+
+  iss.str("Feb");
+  format = "%b";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::eofbit );
+  VERIFY( ret == end );
+  VERIFY( time.tm_mon == 1 );
+
+  iss.str("Mar ");
+  format = "%b";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::goodbit );
+  VERIFY( ret != end && *ret == ' ' );
+  VERIFY( time.tm_mon == 2 );
+
+  iss.str("April");
+  format = "%b";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::eofbit );
+  VERIFY( ret == end );
+  VERIFY( time.tm_mon == 3 );
+
+  iss.str("May");
+  format = "%B";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::eofbit );
+  VERIFY( ret == end );
+  VERIFY( time.tm_mon == 4 );
+
+  iss.str("Jun ");
+  format = "%B";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::goodbit );
+  VERIFY( ret != end && *ret == ' ' );
+  VERIFY( time.tm_mon == 5 );
+
+  iss.str("July");
+  format = "%B";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::eofbit );
+  VERIFY( ret == end );
+  VERIFY( time.tm_mon == 6 );
+
+  iss.str("Aug");
+  format = "%h";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::eofbit );
+  VERIFY( ret == end );
+  VERIFY( time.tm_mon == 7 );
+
+  iss.str("May ");
+  format = "%h";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::goodbit );
+  VERIFY( ret != end && *ret == ' ' );
+  VERIFY( time.tm_mon == 4 );
+
+  iss.str("October");
+  format = "%h";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::eofbit );
+  VERIFY( ret == end );
+  VERIFY( time.tm_mon == 9 );
+
+  // Other tests.
+  iss.str(" 1.");
+  format = "%d.";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::goodbit );
+  VERIFY( ret == end );
+  VERIFY( time.tm_mday == 1 );
+
+  iss.str("2.");
+  format = "%d.";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::goodbit );
+  VERIFY( ret == end );
+  VERIFY( time.tm_mday == 2 );
+
+  iss.str("03.");
+  format = "%d.";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::goodbit );
+  VERIFY( ret == end );
+  VERIFY( time.tm_mday == 3 );
+
+  iss.str("0.");
+  format = "%d.";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::failbit );
+  VERIFY( ret != end && *ret == '.' );
+
+  iss.str("32.");
+  format = "%d.";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::failbit );
+  VERIFY( ret != end && *ret == '2' );
+
+  iss.str(" 4.");
+  format = "%e.";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::goodbit );
+  VERIFY( ret == end );
+  VERIFY( time.tm_mday == 4 );
+
+  iss.str("5.");
+  format = "%e.";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::goodbit );
+  VERIFY( ret == end );
+  VERIFY( time.tm_mday == 5 );
+
+  iss.str("06.");
+  format = "%e.";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::goodbit );
+  VERIFY( ret == end );
+  VERIFY( time.tm_mday == 6 );
+
+  iss.str("0");
+  format = "%e";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::failbit|ios_base::eofbit );
+  VERIFY( ret == end );
+
+  iss.str("35");
+  format = "%e";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::failbit );
+  VERIFY( ret != end && *ret == '5' );
+
+  iss.str(" \t\t 02");
+  format = "%t%m";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::eofbit );
+  VERIFY( ret == end );
+  VERIFY( time.tm_mon == 1 );
+
+  iss.str(" \t \t 03");
+  format = "%n%m";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::eofbit );
+  VERIFY( ret == end );
+  VERIFY( time.tm_mon == 2 );
+
+  iss.str(" \t \t 4");
+  format = " %m";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::eofbit );
+  VERIFY( ret == end );
+  VERIFY( time.tm_mon == 3 );
+
+  iss.str(" \t \t 5");
+  format = "\t%m";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::eofbit );
+  VERIFY( ret == end );
+  VERIFY( time.tm_mon == 4 );
+
+  iss.str("12:00AM");
+  format = "%I:%M%p";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::eofbit );
+  VERIFY( ret == end );
+  VERIFY( time.tm_hour == 0 );
+  VERIFY( time.tm_min == 0 );
+
+  iss.str("12:37AM");
+  format = "%I:%M%p";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::eofbit );
+  VERIFY( ret == end );
+  VERIFY( time.tm_hour == 0 );
+  VERIFY( time.tm_min == 37 );
+
+  iss.str("01:25AM");
+  format = "%I:%M%p";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::eofbit );
+  VERIFY( ret == end );
+  VERIFY( time.tm_hour == 1 );
+  VERIFY( time.tm_min == 25 );
+
+  iss.str("12:00PM");
+  format = "%I:%M%p";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::eofbit );
+  VERIFY( ret == end );
+  VERIFY( time.tm_hour == 12 );
+  VERIFY( time.tm_min == 0 );
+
+  iss.str("12:42PM");
+  format = "%I:%M%p";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::eofbit );
+  VERIFY( ret == end );
+  VERIFY( time.tm_hour == 12 );
+  VERIFY( time.tm_min == 42 );
+
+  iss.str("07:23PM");
+  format = "%I:%M%p";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::eofbit );
+  VERIFY( ret == end );
+  VERIFY( time.tm_hour == 19 );
+  VERIFY( time.tm_min == 23 );
+
+  iss.str("17%20");
+  format = "%H%%%M";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::eofbit );
+  VERIFY( ret == end );
+  VERIFY( time.tm_hour == 17 );
+  VERIFY( time.tm_min == 20 );
+
+  iss.str("24:30");
+  format = "%H:%M";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::failbit );
+  VERIFY( ret != end && *ret == '4' );
+
+  // This one behaves differently from strptime, in a single
+  // pass scaning we can't go back.
+  iss.str("Novembur");
+  format = "%bembur";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::failbit );
+  VERIFY( ret != end && *ret == 'u' );
+}
+
+int
+main()
+{
+  test01();
+  return 0;
+}
--- libstdc++-v3/testsuite/22_locale/time_get/get/wchar_t/3.cc.jj	2021-12-09 13:08:34.785398279 +0100
+++ libstdc++-v3/testsuite/22_locale/time_get/get/wchar_t/3.cc	2021-12-09 13:14:01.012746470 +0100
@@ -0,0 +1,356 @@ 
+// { dg-do run { target c++11 } }
+
+// Copyright (C) 2021 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library.  This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, 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 General Public License for more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+#include <locale>
+#include <sstream>
+#include <iterator>
+#include <testsuite_hooks.h>
+
+void
+test01()
+{
+  using namespace std;
+
+  locale loc_c = locale::classic();
+
+  wistringstream iss;
+  iss.imbue(loc_c);
+  const time_get<wchar_t>& tget = use_facet<time_get<wchar_t>>(iss.getloc());
+  typedef istreambuf_iterator<wchar_t> iter;
+  const iter end;
+
+  tm time;
+  ios_base::iostate err = ios_base::badbit;
+
+  // PR78714 tests
+  iss.str(L"Mon");
+  wstring format = L"%a";
+  auto ret = tget.get(iter(iss), end, iss, err, &time,
+		      format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::eofbit );
+  VERIFY( ret == end );
+  VERIFY( time.tm_wday == 1 );
+
+  iss.str(L"Tue L");
+  format = L"%a";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::goodbit );
+  VERIFY( ret != end && *ret == L' ' );
+  VERIFY( time.tm_wday == 2 );
+
+  iss.str(L"Wednesday");
+  format = L"%a";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::eofbit );
+  VERIFY( ret == end );
+  VERIFY( time.tm_wday == 3 );
+
+  iss.str(L"Thu");
+  format = L"%A";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::eofbit );
+  VERIFY( ret == end );
+  VERIFY( time.tm_wday == 4 );
+
+  iss.str(L"Fri L");
+  format = L"%A";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::goodbit );
+  VERIFY( ret != end && *ret == L' ' );
+  VERIFY( time.tm_wday == 5 );
+
+  iss.str(L"Saturday");
+  format = L"%A";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::eofbit );
+  VERIFY( ret == end );
+  VERIFY( time.tm_wday == 6 );
+
+  iss.str(L"Feb");
+  format = L"%b";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::eofbit );
+  VERIFY( ret == end );
+  VERIFY( time.tm_mon == 1 );
+
+  iss.str(L"Mar L");
+  format = L"%b";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::goodbit );
+  VERIFY( ret != end && *ret == L' ' );
+  VERIFY( time.tm_mon == 2 );
+
+  iss.str(L"April");
+  format = L"%b";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::eofbit );
+  VERIFY( ret == end );
+  VERIFY( time.tm_mon == 3 );
+
+  iss.str(L"May");
+  format = L"%B";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::eofbit );
+  VERIFY( ret == end );
+  VERIFY( time.tm_mon == 4 );
+
+  iss.str(L"Jun L");
+  format = L"%B";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::goodbit );
+  VERIFY( ret != end && *ret == L' ' );
+  VERIFY( time.tm_mon == 5 );
+
+  iss.str(L"July");
+  format = L"%B";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::eofbit );
+  VERIFY( ret == end );
+  VERIFY( time.tm_mon == 6 );
+
+  iss.str(L"Aug");
+  format = L"%h";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::eofbit );
+  VERIFY( ret == end );
+  VERIFY( time.tm_mon == 7 );
+
+  iss.str(L"May L");
+  format = L"%h";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::goodbit );
+  VERIFY( ret != end && *ret == L' ' );
+  VERIFY( time.tm_mon == 4 );
+
+  iss.str(L"October");
+  format = L"%h";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::eofbit );
+  VERIFY( ret == end );
+  VERIFY( time.tm_mon == 9 );
+
+  // Other tests.
+  iss.str(L" 1.");
+  format = L"%d.";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::goodbit );
+  VERIFY( ret == end );
+  VERIFY( time.tm_mday == 1 );
+
+  iss.str(L"2.");
+  format = L"%d.";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::goodbit );
+  VERIFY( ret == end );
+  VERIFY( time.tm_mday == 2 );
+
+  iss.str(L"03.");
+  format = L"%d.";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::goodbit );
+  VERIFY( ret == end );
+  VERIFY( time.tm_mday == 3 );
+
+  iss.str(L"0.");
+  format = L"%d.";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::failbit );
+  VERIFY( ret != end && *ret == L'.' );
+
+  iss.str(L"32.");
+  format = L"%d.";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::failbit );
+  VERIFY( ret != end && *ret == L'2' );
+
+  iss.str(L" 4.");
+  format = L"%e.";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::goodbit );
+  VERIFY( ret == end );
+  VERIFY( time.tm_mday == 4 );
+
+  iss.str(L"5.");
+  format = L"%e.";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::goodbit );
+  VERIFY( ret == end );
+  VERIFY( time.tm_mday == 5 );
+
+  iss.str(L"06.");
+  format = L"%e.";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::goodbit );
+  VERIFY( ret == end );
+  VERIFY( time.tm_mday == 6 );
+
+  iss.str(L"0");
+  format = L"%e";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::failbit|ios_base::eofbit );
+  VERIFY( ret == end );
+
+  iss.str(L"35");
+  format = L"%e";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::failbit );
+  VERIFY( ret != end && *ret == L'5' );
+
+  iss.str(L" \t\t 02");
+  format = L"%t%m";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::eofbit );
+  VERIFY( ret == end );
+  VERIFY( time.tm_mon == 1 );
+
+  iss.str(L" \t \t 03");
+  format = L"%n%m";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::eofbit );
+  VERIFY( ret == end );
+  VERIFY( time.tm_mon == 2 );
+
+  iss.str(L" \t \t 4");
+  format = L" %m";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::eofbit );
+  VERIFY( ret == end );
+  VERIFY( time.tm_mon == 3 );
+
+  iss.str(L" \t \t 5");
+  format = L"\t%m";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::eofbit );
+  VERIFY( ret == end );
+  VERIFY( time.tm_mon == 4 );
+
+  iss.str(L"12:00AM");
+  format = L"%I:%M%p";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::eofbit );
+  VERIFY( ret == end );
+  VERIFY( time.tm_hour == 0 );
+  VERIFY( time.tm_min == 0 );
+
+  iss.str(L"12:37AM");
+  format = L"%I:%M%p";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::eofbit );
+  VERIFY( ret == end );
+  VERIFY( time.tm_hour == 0 );
+  VERIFY( time.tm_min == 37 );
+
+  iss.str(L"01:25AM");
+  format = L"%I:%M%p";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::eofbit );
+  VERIFY( ret == end );
+  VERIFY( time.tm_hour == 1 );
+  VERIFY( time.tm_min == 25 );
+
+  iss.str(L"12:00PM");
+  format = L"%I:%M%p";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::eofbit );
+  VERIFY( ret == end );
+  VERIFY( time.tm_hour == 12 );
+  VERIFY( time.tm_min == 0 );
+
+  iss.str(L"12:42PM");
+  format = L"%I:%M%p";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::eofbit );
+  VERIFY( ret == end );
+  VERIFY( time.tm_hour == 12 );
+  VERIFY( time.tm_min == 42 );
+
+  iss.str(L"07:23PM");
+  format = L"%I:%M%p";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::eofbit );
+  VERIFY( ret == end );
+  VERIFY( time.tm_hour == 19 );
+  VERIFY( time.tm_min == 23 );
+
+  iss.str(L"17%20");
+  format = L"%H%%%M";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::eofbit );
+  VERIFY( ret == end );
+  VERIFY( time.tm_hour == 17 );
+  VERIFY( time.tm_min == 20 );
+
+  iss.str(L"24:30");
+  format = L"%H:%M";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::failbit );
+  VERIFY( ret != end && *ret == L'4' );
+
+  // This one behaves differently from strptime, in a single
+  // pass scaning we can't go back.
+  iss.str(L"Novembur");
+  format = L"%bembur";
+  ret = tget.get(iter(iss), end, iss, err, &time,
+		 format.data(), format.data()+format.size());
+  VERIFY( err == ios_base::failbit );
+  VERIFY( ret != end && *ret == L'u' );
+}
+
+int
+main()
+{
+  test01();
+  return 0;
+}
--- libstdc++-v3/testsuite/22_locale/time_get/get_date/char/12791.cc.jj	2021-01-05 00:13:58.426295719 +0100
+++ libstdc++-v3/testsuite/22_locale/time_get/get_date/char/12791.cc	2021-12-09 13:02:02.498075474 +0100
@@ -39,14 +39,14 @@  void test01()
   const ios_base::iostate good = ios_base::goodbit;
   ios_base::iostate errorstate = good;
 
-  iss.str("60/04/71");
+  iss.str("62/04/71");
   iterator_type is_it01(iss);
   tm time01;
   errorstate = good;
   iterator_type ret01 = tim_get.get_date(is_it01, end, iss, errorstate,
 					 &time01);
   VERIFY( errorstate == ios_base::failbit );
-  VERIFY( *ret01 == '6' );
+  VERIFY( *ret01 == '2' );
 
   iss.str("04/38/71");
   iterator_type is_it02(iss);
--- libstdc++-v3/testsuite/22_locale/time_get/get_date/wchar_t/12791.cc.jj	2021-01-05 00:13:58.426295719 +0100
+++ libstdc++-v3/testsuite/22_locale/time_get/get_date/wchar_t/12791.cc	2021-12-09 13:02:46.579437526 +0100
@@ -40,14 +40,14 @@  void test01()
   const ios_base::iostate good = ios_base::goodbit;
   ios_base::iostate errorstate = good;
 
-  iss.str(L"60/04/71");
+  iss.str(L"62/04/71");
   iterator_type is_it01(iss);
   tm time01;
   errorstate = good;
   iterator_type ret01 = tim_get.get_date(is_it01, end, iss, errorstate,
 					 &time01);
   VERIFY( errorstate == ios_base::failbit );
-  VERIFY( *ret01 == L'6' );
+  VERIFY( *ret01 == L'2' );
 
   iss.str(L"04/38/71");
   iterator_type is_it02(iss);
--- libstdc++-v3/testsuite/22_locale/time_get/get_time/char/2.cc.jj	2021-01-05 00:13:58.000000000 +0100
+++ libstdc++-v3/testsuite/22_locale/time_get/get_time/char/2.cc	2021-12-08 15:18:42.586859294 +0100
@@ -48,7 +48,7 @@  void test02()
 
   // inspection of named locales, en_HK
   iss.imbue(loc_hk);
-  iss.str("12:00:00 PST"); 
+  iss.str("12:00:00 PM PST"); 
   // Hong Kong in California! Well, they have Paris in Vegas... this
   // is all a little disney-esque anyway. Besides, you can get decent
   // Dim Sum in San Francisco.
--- libstdc++-v3/testsuite/22_locale/time_get/get_time/char/5.cc.jj	2021-01-05 00:13:58.000000000 +0100
+++ libstdc++-v3/testsuite/22_locale/time_get/get_time/char/5.cc	2021-12-08 19:12:54.519198801 +0100
@@ -52,7 +52,7 @@  void test01()
   VERIFY( err == (failbit | eofbit) );
   VERIFY( tm0.tm_sec == 0 );
   VERIFY( tm0.tm_min == 0 );
-  VERIFY( tm0.tm_hour == 0 );
+  VERIFY( tm0.tm_hour == 1 );
 
   const string str1 = "12:00:00 ";
   iter_type end1 = tg.get_time(str1.begin(), str1.end(), iss, err, &tm1);
--- libstdc++-v3/testsuite/22_locale/time_get/get_time/wchar_t/2.cc.jj	2021-01-05 00:13:58.427295707 +0100
+++ libstdc++-v3/testsuite/22_locale/time_get/get_time/wchar_t/2.cc	2021-12-08 15:24:10.378202522 +0100
@@ -48,7 +48,7 @@  void test02()
 
   // inspection of named locales, en_HK
   iss.imbue(loc_hk);
-  iss.str(L"12:00:00 PST"); 
+  iss.str(L"12:00:00 PM PST"); 
   // Hong Kong in California! Well, they have Paris in Vegas... this
   // is all a little disney-esque anyway. Besides, you can get decent
   // Dim Sum in San Francisco.
--- libstdc++-v3/testsuite/22_locale/time_get/get_time/wchar_t/5.cc.jj	2021-01-05 00:13:58.428295696 +0100
+++ libstdc++-v3/testsuite/22_locale/time_get/get_time/wchar_t/5.cc	2021-12-08 19:14:32.826801803 +0100
@@ -52,7 +52,7 @@  void test01()
   VERIFY( err == (failbit | eofbit) );
   VERIFY( tm0.tm_sec == 0 );
   VERIFY( tm0.tm_min == 0 );
-  VERIFY( tm0.tm_hour == 0 );
+  VERIFY( tm0.tm_hour == 1 );
 
   const wstring str1 = L"12:00:00 ";
   iter_type end1 = tg.get_time(str1.begin(), str1.end(), iss, err, &tm1);