This is the mail archive of the newlib@sourceware.org mailing list for the newlib project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

[PATCH]: Enable LC_TIME handling


Hi,

the below patch enables LC_TIME handling.  The actual loading of the
localized LC_TIME data is restricted to Cygwin for now.  I was planning
to enable this for all targets, but I found that the __part_load_locale
function in ldpart.c is broken in terms of memory allocation and pointer
handling.  It's apparently a rather old implementation, which should
first be replaced by the latest from FreeBSD, for instance.  When we
have a new ldpart.c, we can easily enable this functionality for all
targets, if desired.

Although the actual localized data is only generated on Cygwin, the
generic functions strftime(3) and strptime(3) needed some work, since
they were using hardcoded formats as defined in the POSIX locale, rather
than accessing the locale specific data via the __get_current_time_locale()
function.

Cygwin itself needs also a patch, which depends on this one.  The new
Cygwin function __set_lc_time_from_win(), called from newlib's
__time_load_locale(), fetches the locale-specific time format strings
from the Windows function GetLocaleData and converts it from the Window
format to the POSIX format.  This allows to have locale-specific time
format strings without having to generate matching files.

Fortunately the nl_langinfo function already access the information
via the locale-specific data, so there was no change necessary.

I tested this functionality on Cygwin.

As I outlined in an earlier mail, I'm also planning to add matching
functionality for (at least) LC_NUMERIC and LC_MONETARY.  I'm not
yet sure this will work for LC_COLLATE, though.

Ok to apply?


Thanks,
Corinna


	* libc/locale/locale.c (loadlocale): Enable LC_TIME handling
	on Cygwin.
	* libc/locale/timelocal.c (_C_time_locale): Accommodate
	redefinition of am/pm members.
	(__time_load_locale): Take additional parameters for wide char
	to multibyte conversion.  Call __set_lc_time_from_win on Cygwin.
	* libc/locale/timelocal.h: Make C++-safe.
	(struct lc_time_T): Convert am and pm to a am_pm array for easier
	consumption by strftime and strptime.
	(__time_load_locale): Change declaration.
	* libc/time/strftime.c: Change documentation to reflect changes to
	strftime.  Remove locale constant strings in favor of access to
	locale-specifc data.
	(_ctloc): Define access method for locale-specifc data.
	(TOLOWER): Define for tolower conversion.
	(strftime): Throughout, convert locale-specific formats to use
	locale-specific data.  Add GNU-specific "%P" format.
	* libc/time/strptime.c: Remove locale constant strings in favor of
	access to locale-specifc data.
	(_ctloc): Define access method for locale-specifc data.
	(strptime): Throughout, convert locale-specific formats to use
	locale-specific data.


Index: libc/locale/locale.c
===================================================================
RCS file: /cvs/src/src/newlib/libc/locale/locale.c,v
retrieving revision 1.32
diff -u -p -r1.32 locale.c
--- libc/locale/locale.c	17 Jan 2010 14:57:32 -0000	1.32
+++ libc/locale/locale.c	18 Jan 2010 17:11:15 -0000
@@ -447,6 +447,9 @@ loadlocale(struct _reent *p, int categor
 #ifdef _MB_CAPABLE
   int cjknarrow = 0;
 #endif
+#ifdef __CYGWIN__
+  int ret = 0;
+#endif
   
   /* "POSIX" is translated to "C", as on Linux. */
   if (!strcmp (locale, "POSIX"))
@@ -729,6 +732,12 @@ loadlocale(struct _reent *p, int categor
     }
   else if (category == LC_MESSAGES)
     strcpy (lc_message_charset, charset);
+#ifdef __CYGWIN__
+  else if (category == LC_TIME)
+    ret = __time_load_locale (locale, (void *) l_wctomb, charset);
+  if (ret)
+    return NULL;
+#endif
   return strcpy(current_categories[category], new_categories[category]);
 }
 
Index: libc/locale/timelocal.c
===================================================================
RCS file: /cvs/src/src/newlib/libc/locale/timelocal.c,v
retrieving revision 1.1
diff -u -p -r1.1 timelocal.c
--- libc/locale/timelocal.c	23 Aug 2002 01:56:03 -0000	1.1
+++ libc/locale/timelocal.c	18 Jan 2010 17:11:15 -0000
@@ -70,11 +70,8 @@ static const struct lc_time_T	_C_time_lo
 	 */
 	"%a %b %e %H:%M:%S %Y",
 
-	/* am */
-	"AM",
-
-	/* pm */
-	"PM",
+	/* am pm */
+	{ "AM", "PM" },
 
 	/* date_fmt */
 	"%a %b %e %H:%M:%S %Z %Y",
@@ -106,14 +103,22 @@ __get_current_time_locale(void) {
 }
 
 int
-__time_load_locale(const char *name) {
+__time_load_locale(const char *name, void *f_wctomb, const char *charset) {
 
 	int	ret;
 
+#ifdef __CYGWIN__
+	extern int __set_lc_time_from_win (const char *, struct lc_time_T *,
+					   void *, const char *);
+	int old_time_using_locale = _time_using_locale;
+	_time_using_locale = 0;
+	ret = __set_lc_time_from_win (name, &_time_locale, f_wctomb, charset);
+	_time_using_locale = ret ? old_time_using_locale : 1;
+#else
 	ret = __part_load_locale(name, &_time_using_locale,
 			time_locale_buf, "LC_TIME",
 			LCTIME_SIZE, LCTIME_SIZE,
 			(const char **)&_time_locale);
-
+#endif
 	return (ret);
 }
Index: libc/locale/timelocal.h
===================================================================
RCS file: /cvs/src/src/newlib/libc/locale/timelocal.h,v
retrieving revision 1.1
diff -u -p -r1.1 timelocal.h
--- libc/locale/timelocal.h	23 Aug 2002 01:56:03 -0000	1.1
+++ libc/locale/timelocal.h	18 Jan 2010 17:11:15 -0000
@@ -29,6 +29,8 @@
 #ifndef _TIMELOCAL_H_
 #define	_TIMELOCAL_H_
 
+__BEGIN_DECLS
+
 /*
  * Private header file for the strftime and strptime localization
  * stuff.
@@ -41,8 +43,7 @@ struct lc_time_T {
 	const char	*X_fmt;
 	const char	*x_fmt;
 	const char	*c_fmt;
-	const char	*am;
-	const char	*pm;
+	const char	*am_pm[2];
 	const char	*date_fmt;
 	const char	*alt_month[12];
 	const char	*md_order;
@@ -50,6 +51,8 @@ struct lc_time_T {
 };
 
 struct lc_time_T *__get_current_time_locale(void);
-int	__time_load_locale(const char *);
+int	__time_load_locale(const char *, void *, const char *);
+
+__END_DECLS
 
 #endif /* !_TIMELOCAL_H_ */
Index: libc/time/strftime.c
===================================================================
RCS file: /cvs/src/src/newlib/libc/time/strftime.c,v
retrieving revision 1.12
diff -u -p -r1.12 strftime.c
--- libc/time/strftime.c	12 Mar 2009 10:27:10 -0000	1.12
+++ libc/time/strftime.c	18 Jan 2010 17:11:16 -0000
@@ -52,26 +52,20 @@ following ways:
 
 o+
 o %a
-A three-letter abbreviation for the day of the week. [tm_wday]
+The abbreviated weekday name according to the current locale. [tm_wday]
 
 o %A
-The full name for the day of the week, one of `<<Sunday>>',
-`<<Monday>>', `<<Tuesday>>', `<<Wednesday>>', `<<Thursday>>',
-`<<Friday>>', or `<<Saturday>>'. [tm_wday]
+The full weekday name according to the current locale. [tm_wday]
 
 o %b
-A three-letter abbreviation for the month name. [tm_mon]
+The abbreviated month name according to the current locale. [tm_mon]
 
 o %B
-The full name of the month, one of `<<January>>', `<<February>>',
-`<<March>>', `<<April>>', `<<May>>', `<<June>>', `<<July>>',
-`<<August>>', `<<September>>', `<<October>>', `<<November>>',
-`<<December>>'. [tm_mon]
+The full month name according to the current locale. [tm_mon]
 
 o %c
-A string representing the complete date and time, in the form
-`<<"%a %b %e %H:%M:%S %Y">>' (example "Mon Apr 01 13:13:13
-1992"). [tm_sec, tm_min, tm_hour, tm_mday, tm_mon, tm_year, tm_wday]
+The preferred date and time representation for the current locale.
+[tm_sec, tm_min, tm_hour, tm_mday, tm_mon, tm_year, tm_wday]
 
 o %C
 The century, that is, the year divided by 100 then truncated.  For
@@ -115,8 +109,7 @@ Example: "%G" for Saturday 2nd January 1
 Tuesday 30th December 1997 gives "1998". [tm_year, tm_wday, tm_yday]
 
 o %h
-A three-letter abbreviation for the month name (synonym for
-"%b"). [tm_mon]
+Synonym for "%b". [tm_mon]
 
 o %H
 The hour (on a 24-hour clock), formatted with two digits (from
@@ -154,11 +147,16 @@ for certain modifiers <<x>>.  But in the
 is ignored, and treated as %<<x>>.
 
 o %p
-Either `<<AM>>' or `<<PM>>' as appropriate. [tm_hour]
+Either `<<AM>>' or `<<PM>>' as appropriate, or the corresponding strings for
+the current locale. [tm_hour]
+
+o %P
+Same as '<<%p>>', but in lowercase.  This is a GNU extension. [tm_hour]
 
 o %r
-The 12-hour time, to the second.  Equivalent to "%I:%M:%S %p". [tm_sec,
-tm_min, tm_hour]
+Replaced by the time in a.m. and p.m. notation.  In the POSIX locale this
+is equivalent to "%I:%M:%S %p".  In locales which don't define a.m./p.m.
+notations, the result is an empty string. [tm_sec, tm_min, tm_hour]
 
 o %R
 The 24-hour time, to the minute.  Equivalent to "%H:%M". [tm_min, tm_hour]
@@ -198,12 +196,13 @@ Monday in a year, and earlier days are i
 digits (from `<<00>>' to `<<53>>'). [tm_wday, tm_yday]
 
 o %x
-A string representing the complete date, equivalent to "%m/%d/%y".
+Replaced by the preferred date representation in the current locale.
+In the POSIX locale this is equivalent to "%m/%d/%y".
 [tm_mon, tm_mday, tm_year]
 
 o %X
-A string representing the full time of day (hours, minutes, and
-seconds), equivalent to "%H:%M:%S". [tm_sec, tm_min, tm_hour]
+Replaced by the preferred time representation in the current locale.
+In the POSIX locale this is equivalent to "%H:%M:%S". [tm_sec, tm_min, tm_hour]
 
 o %y
 The last two digits of the year (from `<<00>>' to `<<99>>'). [tm_year]
@@ -263,7 +262,10 @@ the "C" locale settings.
 #include <string.h>
 #include <stdlib.h>
 #include <limits.h>
+#include <ctype.h>
+#include <wctype.h>
 #include "local.h"
+#include "../locale/timelocal.h"
  
 /* Defines to make the file dual use for either strftime() or wcsftime().
  * To get wcsftime, define MAKE_WCSFTIME.
@@ -276,13 +278,26 @@ the "C" locale settings.
 #  define CHAR		char		/* string type basis */
 #  define CQ(a)		a		/* character constant qualifier */
 #  define SFLG				/* %s flag (null for normal char) */
+#  define _ctloc(x) (ctloclen = strlen (ctloc = _CurrentTimeLocale->x), ctloc)
+#  define TOLOWER(c)	tolower((int)(unsigned char)(c))
 # else
 #  define strftime	wcsftime	/* Alternate function name */
 #  define CHAR		wchar_t		/* string type basis */
 #  define CQ(a)		L##a		/* character constant qualifier */
 #  define snprintf	swprintf	/* wide-char equivalent function name */
 #  define strncmp	wcsncmp		/* wide-char equivalent function name */
+#  define TOLOWER(c)	towlower((wint_t)(c))
 #  define SFLG		"l"		/* %s flag (l for wide char) */
+   const wchar_t *
+   __ctloc (wchar_t *buf, const char *elem, size_t *len_ret)
+   {
+     *len_ret = mbstowcs (buf, elem, 256);
+     if (*len_ret == (size_t) -1 )
+       *len_ret = 0;
+     return buf;
+   }
+#  define _ctloc(x) (ctloc = __ctloc (ctlocbuf, _CurrentTimeLocale->x, \
+		     &ctloclen))
 #endif  /* MAKE_WCSFTIME */
 
 /* Enforce the coding assumptions that YEAR_BASE is positive.  (%C, %Y, etc.) */
@@ -293,18 +308,6 @@ the "C" locale settings.
 static _CONST int dname_len[7] =
 {6, 6, 7, 9, 8, 6, 8};
 
-static _CONST CHAR *_CONST dname[7] =
-{CQ("Sunday"), CQ("Monday"), CQ("Tuesday"), CQ("Wednesday"),
- CQ("Thursday"), CQ("Friday"), CQ("Saturday")};
-
-static _CONST int mname_len[12] =
-{7, 8, 5, 5, 3, 4, 4, 6, 9, 7, 8, 8};
-
-static _CONST CHAR *_CONST mname[12] =
-{CQ("January"), CQ("February"), CQ("March"), CQ("April"),
- CQ("May"), CQ("June"), CQ("July"), CQ("August"),
- CQ("September"), CQ("October"), CQ("November"), CQ("December")};
-
 /* Using the tm_year, tm_wday, and tm_yday components of TIM_P, return
    -1, 0, or 1 as the adjustment to add to the year for the ISO week
    numbering used in "%g%G%V", avoiding overflow.  */
@@ -361,7 +364,13 @@ _DEFUN (strftime, (s, maxsize, format, t
 {
   size_t count = 0;
   int i, len;
+  const CHAR *ctloc;
+#ifdef MAKE_WCSFTIME
+  CHAR ctlocbuf[256];
+#endif
+  size_t ctloclen;
 
+  struct lc_time_T *_CurrentTimeLocale = __get_current_time_locale ();
   for (;;)
     {
       while (*format && *format != CQ('%'))
@@ -382,56 +391,68 @@ _DEFUN (strftime, (s, maxsize, format, t
       switch (*format)
 	{
 	case CQ('a'):
-	  for (i = 0; i < 3; i++)
+	  _ctloc (wday[tim_p->tm_wday]);
+	  for (i = 0; i < ctloclen; i++)
 	    {
 	      if (count < maxsize - 1)
-		s[count++] =
-		  dname[tim_p->tm_wday][i];
+		s[count++] = ctloc[i];
 	      else
 		return 0;
 	    }
 	  break;
 	case CQ('A'):
-	  for (i = 0; i < dname_len[tim_p->tm_wday]; i++)
+	  _ctloc (weekday[tim_p->tm_wday]);
+	  for (i = 0; i < ctloclen; i++)
 	    {
 	      if (count < maxsize - 1)
-		s[count++] =
-		  dname[tim_p->tm_wday][i];
+		s[count++] = ctloc[i];
 	      else
 		return 0;
 	    }
 	  break;
 	case CQ('b'):
 	case CQ('h'):
-	  for (i = 0; i < 3; i++)
+	  _ctloc (mon[tim_p->tm_mon]);
+	  for (i = 0; i < ctloclen; i++)
 	    {
 	      if (count < maxsize - 1)
-		s[count++] =
-		  mname[tim_p->tm_mon][i];
+		s[count++] = ctloc[i];
 	      else
 		return 0;
 	    }
 	  break;
 	case CQ('B'):
-	  for (i = 0; i < mname_len[tim_p->tm_mon]; i++)
+	  _ctloc (month[tim_p->tm_mon]);
+	  for (i = 0; i < ctloclen; i++)
 	    {
 	      if (count < maxsize - 1)
-		s[count++] =
-		  mname[tim_p->tm_mon][i];
+		s[count++] = ctloc[i];
 	      else
 		return 0;
 	    }
 	  break;
 	case CQ('c'):
-	  {
-	    /* Recurse to avoid need to replicate %Y formation. */
-	    size_t adjust = strftime (&s[count], maxsize - count,
-				      CQ("%a %b %e %H:%M:%S %Y"), tim_p);
-	    if (adjust > 0)
-	      count += adjust;
-	    else
-	      return 0;
-	  }
+	  _ctloc (c_fmt);
+	  goto recurse;
+	case CQ('r'):
+	  _ctloc (ampm_fmt);
+	  goto recurse;
+	case CQ('x'):
+	  _ctloc (x_fmt);
+	  goto recurse;
+	case CQ('X'):
+	  _ctloc (X_fmt);
+recurse:
+	  if (*ctloc)
+	    {
+	      /* Recurse to avoid need to replicate %Y formation. */
+	      size_t adjust = strftime (&s[count], maxsize - count, ctloc,
+					tim_p);
+	      if (adjust > 0)
+		count += adjust;
+	      else
+		return 0;
+	    }
 	  break;
 	case CQ('C'):
 	  {
@@ -472,7 +493,6 @@ _DEFUN (strftime, (s, maxsize, format, t
 	  if (len < 0  ||  (count+=len) >= maxsize)  return 0;
 	  break;
 	case CQ('D'):
-	case CQ('x'):
 	  /* %m/%d/%y */
 	  len = snprintf (&s[count], maxsize - count,
 			CQ("%.2d/%.2d/%.2d"),
@@ -582,33 +602,16 @@ _DEFUN (strftime, (s, maxsize, format, t
 	    return 0;
 	  break;
 	case CQ('p'):
-	  if (count < maxsize - 1)
+	case CQ('P'):
+	  _ctloc (am_pm[tim_p->tm_hour < 12 ? 0 : 1]);
+	  for (i = 0; i < ctloclen; i++)
 	    {
-	      if (tim_p->tm_hour < 12)
-		s[count++] = CQ('A');
+	      if (count < maxsize - 1)
+		s[count++] = (*format == CQ('P') ? TOLOWER (ctloc[i])
+						 : ctloc[i]);
 	      else
-		s[count++] = CQ('P');
-	    }
-	  if (count < maxsize - 1)
-	    {
-	      s[count++] = CQ('M');
+		return 0;
 	    }
-	  else
-	    return 0;
-	  break;
-	case CQ('r'):
-	  {
-	    register int  h12;
-	    h12 = (tim_p->tm_hour == 0 || tim_p->tm_hour == 12)  ?
-						12  :  tim_p->tm_hour % 12;
-	    len = snprintf (&s[count], maxsize - count,
-			CQ("%.2d:%.2d:%.2d %cM"),
-			h12, 
-			tim_p->tm_min,
-			tim_p->tm_sec,
-			(tim_p->tm_hour < 12)  ?  CQ('A') :  CQ('P'));
-	    if (len < 0  ||  (count+=len) >= maxsize)  return 0;
-	  }
 	  break;
 	case CQ('R'):
           len = snprintf (&s[count], maxsize - count, CQ("%.2d:%.2d"),
@@ -627,7 +630,6 @@ _DEFUN (strftime, (s, maxsize, format, t
 	    return 0;
 	  break;
 	case CQ('T'):
-	case CQ('X'):
           len = snprintf (&s[count], maxsize - count, CQ("%.2d:%.2d:%.2d"),
 			tim_p->tm_hour, tim_p->tm_min, tim_p->tm_sec);
           if (len < 0  ||  (count+=len) >= maxsize)  return 0;
Index: libc/time/strptime.c
===================================================================
RCS file: /cvs/src/src/newlib/libc/time/strptime.c,v
retrieving revision 1.4
diff -u -p -r1.4 strptime.c
--- libc/time/strptime.c	18 Nov 2005 19:00:29 -0000	1.4
+++ libc/time/strptime.c	18 Jan 2010 17:11:16 -0000
@@ -36,66 +36,9 @@
 #include <string.h>
 #include <ctype.h>
 #include <stdlib.h>
+#include "../locale/timelocal.h"
 
-static const char *abb_weekdays[] = {
-    "Sun",
-    "Mon",
-    "Tue",
-    "Wed",
-    "Thu",
-    "Fri",
-    "Sat",
-    NULL
-};
-
-static const char *full_weekdays[] = {
-    "Sunday",
-    "Monday",
-    "Tuesday",
-    "Wednesday",
-    "Thursday",
-    "Friday",
-    "Saturday",
-    NULL
-};
-
-static const char *abb_month[] = {
-    "Jan",
-    "Feb",
-    "Mar",
-    "Apr",
-    "May",
-    "Jun",
-    "Jul",
-    "Aug",
-    "Sep",
-    "Oct",
-    "Nov",
-    "Dec",
-    NULL
-};
-
-static const char *full_month[] = {
-    "January",
-    "February",
-    "March",
-    "April",
-    "May",
-    "June",
-    "July",
-    "August",
-    "September",
-    "October",
-    "November",
-    "December",
-    NULL,
-};
-
-static const char *ampm[] = {
-    "am",
-    "pm",
-    NULL
-};
+#define _ctloc(x) (_CurrentTimeLocale->x)
 
 /*
  * tm_year is relative this year 
@@ -205,6 +148,7 @@ _DEFUN (strptime, (buf, format, timeptr)
 {
     char c;
 
+    struct lc_time_T *_CurrentTimeLocale = __get_current_time_locale ();
     for (; (c = *format) != '\0'; ++format) {
 	char *s;
 	int ret;
@@ -218,26 +162,26 @@ _DEFUN (strptime, (buf, format, timeptr)
 		c = *++format;
 	    switch (c) {
 	    case 'A' :
-		ret = match_string (&buf, full_weekdays);
+		ret = match_string (&buf, _ctloc (weekday));
 		if (ret < 0)
 		    return NULL;
 		timeptr->tm_wday = ret;
 		break;
 	    case 'a' :
-		ret = match_string (&buf, abb_weekdays);
+		ret = match_string (&buf, _ctloc (wday));
 		if (ret < 0)
 		    return NULL;
 		timeptr->tm_wday = ret;
 		break;
 	    case 'B' :
-		ret = match_string (&buf, full_month);
+		ret = match_string (&buf, _ctloc (month));
 		if (ret < 0)
 		    return NULL;
 		timeptr->tm_mon = ret;
 		break;
 	    case 'b' :
 	    case 'h' :
-		ret = match_string (&buf, abb_month);
+		ret = match_string (&buf, _ctloc (mon));
 		if (ret < 0)
 		    return NULL;
 		timeptr->tm_mon = ret;
@@ -250,7 +194,7 @@ _DEFUN (strptime, (buf, format, timeptr)
 		buf = s;
 		break;
 	    case 'c' :		/* %a %b %e %H:%M:%S %Y */
-		s = strptime (buf, "%a %b %e %H:%M:%S %Y", timeptr);
+		s = strptime (buf, _ctloc (c_fmt), timeptr);
 		if (s == NULL)
 		    return NULL;
 		buf = s;
@@ -316,7 +260,7 @@ _DEFUN (strptime, (buf, format, timeptr)
 		    return NULL;
 		break;
 	    case 'p' :
-		ret = match_string (&buf, ampm);
+		ret = match_string (&buf, _ctloc (am_pm));
 		if (ret < 0)
 		    return NULL;
 		if (timeptr->tm_hour == 0) {
@@ -326,7 +270,7 @@ _DEFUN (strptime, (buf, format, timeptr)
 		    timeptr->tm_hour += 12;
 		break;
 	    case 'r' :		/* %I:%M:%S %p */
-		s = strptime (buf, "%I:%M:%S %p", timeptr);
+		s = strptime (buf, _ctloc (ampm_fmt), timeptr);
 		if (s == NULL)
 		    return NULL;
 		buf = s;
@@ -351,7 +295,6 @@ _DEFUN (strptime, (buf, format, timeptr)
 		    return NULL;
 		break;
 	    case 'T' :		/* %H:%M:%S */
-	    case 'X' :
 		s = strptime (buf, "%H:%M:%S", timeptr);
 		if (s == NULL)
 		    return NULL;
@@ -393,11 +336,17 @@ _DEFUN (strptime, (buf, format, timeptr)
 		buf = s;
 		break;
 	    case 'x' :
-		s = strptime (buf, "%Y:%m:%d", timeptr);
+		s = strptime (buf, _ctloc (x_fmt), timeptr);
 		if (s == NULL)
 		    return NULL;
 		buf = s;
 		break;
+	    case 'X' :
+		s = strptime (buf, _ctloc (X_fmt), timeptr);
+		if (s == NULL)
+		    return NULL;
+		buf = s;
+	    	break;
 	    case 'y' :
 		ret = strtol (buf, &s, 10);
 		if (s == buf)


-- 
Corinna Vinschen
Cygwin Project Co-Leader
Red Hat


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]