(root)/
glibc-2.38/
locale/
programs/
ld-time.c
       1  /* Copyright (C) 1995-2023 Free Software Foundation, Inc.
       2     This file is part of the GNU C Library.
       3  
       4     This program is free software; you can redistribute it and/or modify
       5     it under the terms of the GNU General Public License as published
       6     by the Free Software Foundation; version 2 of the License, or
       7     (at your option) any later version.
       8  
       9     This program is distributed in the hope that it will be useful,
      10     but WITHOUT ANY WARRANTY; without even the implied warranty of
      11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12     GNU General Public License for more details.
      13  
      14     You should have received a copy of the GNU General Public License
      15     along with this program; if not, see <https://www.gnu.org/licenses/>.  */
      16  
      17  #ifdef HAVE_CONFIG_H
      18  # include <config.h>
      19  #endif
      20  
      21  #include <byteswap.h>
      22  #include <langinfo.h>
      23  #include <stdlib.h>
      24  #include <string.h>
      25  #include <wchar.h>
      26  #include <stdint.h>
      27  #include <sys/uio.h>
      28  
      29  #include <assert.h>
      30  
      31  #include "localedef.h"
      32  #include "linereader.h"
      33  #include "localeinfo.h"
      34  #include "locfile.h"
      35  
      36  
      37  /* Entry describing an entry of the era specification.  */
      38  struct era_data
      39  {
      40    int32_t direction;
      41    int32_t offset;
      42    int32_t start_date[3];
      43    int32_t stop_date[3];
      44    const char *name;
      45    const char *format;
      46    uint32_t *wname;
      47    uint32_t *wformat;
      48  };
      49  
      50  
      51  /* The real definition of the struct for the LC_TIME locale.  */
      52  struct locale_time_t
      53  {
      54    const char *abday[7];
      55    const uint32_t *wabday[7];
      56    int abday_defined;
      57    const char *day[7];
      58    const uint32_t *wday[7];
      59    int day_defined;
      60    const char *abmon[12];
      61    const uint32_t *wabmon[12];
      62    int abmon_defined;
      63    const char *mon[12];
      64    const uint32_t *wmon[12];
      65    int mon_defined;
      66    const char *am_pm[2];
      67    const uint32_t *wam_pm[2];
      68    int am_pm_defined;
      69    const char *d_t_fmt;
      70    const uint32_t *wd_t_fmt;
      71    const char *d_fmt;
      72    const uint32_t *wd_fmt;
      73    const char *t_fmt;
      74    const uint32_t *wt_fmt;
      75    const char *t_fmt_ampm;
      76    const uint32_t *wt_fmt_ampm;
      77    const char **era;
      78    const uint32_t **wera;
      79    uint32_t num_era;
      80    const char *era_year;
      81    const uint32_t *wera_year;
      82    const char *era_d_t_fmt;
      83    const uint32_t *wera_d_t_fmt;
      84    const char *era_t_fmt;
      85    const uint32_t *wera_t_fmt;
      86    const char *era_d_fmt;
      87    const uint32_t *wera_d_fmt;
      88    const char *alt_digits[100];
      89    const uint32_t *walt_digits[100];
      90    const char *date_fmt;
      91    const uint32_t *wdate_fmt;
      92    int alt_digits_defined;
      93    const char *alt_mon[12];
      94    const uint32_t *walt_mon[12];
      95    int alt_mon_defined;
      96    const char *ab_alt_mon[12];
      97    const uint32_t *wab_alt_mon[12];
      98    int ab_alt_mon_defined;
      99    unsigned char week_ndays;
     100    uint32_t week_1stday;
     101    unsigned char week_1stweek;
     102    unsigned char first_weekday;
     103    unsigned char first_workday;
     104    unsigned char cal_direction;
     105    const char *timezone;
     106    const uint32_t *wtimezone;
     107  
     108    struct era_data *era_entries;
     109  };
     110  
     111  
     112  /* This constant is used to represent an empty wide character string.  */
     113  static const uint32_t empty_wstr[1] = { 0 };
     114  
     115  
     116  static void
     117  time_startup (struct linereader *lr, struct localedef_t *locale,
     118  	      int ignore_content)
     119  {
     120    if (!ignore_content)
     121      locale->categories[LC_TIME].time =
     122        (struct locale_time_t *) xcalloc (1, sizeof (struct locale_time_t));
     123  
     124    if (lr != NULL)
     125      {
     126        lr->translate_strings = 1;
     127        lr->return_widestr = 1;
     128      }
     129  }
     130  
     131  
     132  void
     133  time_finish (struct localedef_t *locale, const struct charmap_t *charmap)
     134  {
     135    struct locale_time_t *time = locale->categories[LC_TIME].time;
     136    int nothing = 0;
     137  
     138    /* Now resolve copying and also handle completely missing definitions.  */
     139    if (time == NULL)
     140      {
     141        /* First see whether we were supposed to copy.  If yes, find the
     142  	 actual definition.  */
     143        if (locale->copy_name[LC_TIME] != NULL)
     144  	{
     145  	  /* Find the copying locale.  This has to happen transitively since
     146  	     the locale we are copying from might also copying another one.  */
     147  	  struct localedef_t *from = locale;
     148  
     149  	  do
     150  	    from = find_locale (LC_TIME, from->copy_name[LC_TIME],
     151  				from->repertoire_name, charmap);
     152  	  while (from->categories[LC_TIME].time == NULL
     153  		 && from->copy_name[LC_TIME] != NULL);
     154  
     155  	  time = locale->categories[LC_TIME].time
     156  	    = from->categories[LC_TIME].time;
     157  	}
     158  
     159        /* If there is still no definition issue an warning and create an
     160  	 empty one.  */
     161        if (time == NULL)
     162  	{
     163  	  record_warning (_("\
     164  No definition for %s category found"), "LC_TIME");
     165  	  time_startup (NULL, locale, 0);
     166  	  time = locale->categories[LC_TIME].time;
     167  	  nothing = 1;
     168  	}
     169      }
     170  
     171  #define noparen(arg1, argn...) arg1, ##argn
     172  #define TESTARR_ELEM(cat, val) \
     173    if (!time->cat##_defined)						      \
     174      {									      \
     175        const char *initval[] = { noparen val };				      \
     176        unsigned int i;							      \
     177  									      \
     178        if (! nothing)					    		      \
     179  	record_error (0, 0, _("%s: field `%s' not defined"),	      	      \
     180  		      "LC_TIME", #cat);          			      \
     181  									      \
     182        for (i = 0; i < sizeof (initval) / sizeof (initval[0]); ++i)	      \
     183  	time->cat[i] = initval[i];					      \
     184      }
     185  
     186    TESTARR_ELEM (abday, ( "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" ));
     187    TESTARR_ELEM (day, ( "Sunday", "Monday", "Tuesday", "Wednesday",
     188  		        "Thursday", "Friday", "Saturday" ));
     189    TESTARR_ELEM (abmon, ( "Jan", "Feb", "Mar", "Apr", "May", "Jun",
     190  			  "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ));
     191    TESTARR_ELEM (mon, ( "January", "February", "March", "April",
     192  			"May", "June", "July", "August",
     193  			"September", "October", "November", "December" ));
     194    TESTARR_ELEM (am_pm, ( "AM", "PM" ));
     195  
     196  #define TEST_ELEM(cat, initval) \
     197    if (time->cat == NULL)						      \
     198      {									      \
     199        if (! nothing)							      \
     200  	record_error (0, 0, _("%s: field `%s' not defined"),		      \
     201  		      "LC_TIME", #cat);          			      \
     202  									      \
     203        time->cat = initval;						      \
     204      }
     205  
     206    TEST_ELEM (d_t_fmt, "%a %b %e %H:%M:%S %Y");
     207    TEST_ELEM (d_fmt, "%m/%d/%y");
     208    TEST_ELEM (t_fmt, "%H:%M:%S");
     209  
     210    /* According to C.Y.Alexis Cheng <alexis@vnet.ibm.com> the T_FMT_AMPM
     211       field is optional.  */
     212    if (time->t_fmt_ampm == NULL)
     213      {
     214        if (time->am_pm[0][0] == '\0' && time->am_pm[1][0] == '\0')
     215  	{
     216  	  /* No AM/PM strings defined, use the 24h format as default.  */
     217  	  time->t_fmt_ampm = time->t_fmt;
     218  	  time->wt_fmt_ampm = time->wt_fmt;
     219  	}
     220        else
     221  	{
     222  	  time->t_fmt_ampm = "%I:%M:%S %p";
     223  	  time->wt_fmt_ampm = (const uint32_t *) L"%I:%M:%S %p";
     224  	}
     225      }
     226  
     227    /* Now process the era entries.  */
     228    if (time->num_era != 0)
     229      {
     230        const int days_per_month[12] = { 31, 29, 31, 30, 31, 30,
     231  				       31, 31, 30, 31 ,30, 31 };
     232        size_t idx;
     233        wchar_t *wstr;
     234  
     235        time->era_entries =
     236  	(struct era_data *) xmalloc (time->num_era
     237  				     * sizeof (struct era_data));
     238  
     239        for (idx = 0; idx < time->num_era; ++idx)
     240  	{
     241  	  size_t era_len = strlen (time->era[idx]);
     242  	  char *str = xmalloc ((era_len + 1 + 3) & ~3);
     243  	  char *endp;
     244  
     245  	  memcpy (str, time->era[idx], era_len + 1);
     246  
     247  	  /* First character must be + or - for the direction.  */
     248  	  if (*str != '+' && *str != '-')
     249  	    {
     250  	      record_error (0, 0, _("\
     251  %s: direction flag in string %Zd in `era' field is not '+' nor '-'"),
     252  			    "LC_TIME", idx + 1);
     253  	      /* Default arbitrarily to '+'.  */
     254  	      time->era_entries[idx].direction = '+';
     255  	    }
     256  	  else
     257  	    time->era_entries[idx].direction = *str;
     258  	  if (*++str != ':')
     259  	    {
     260  	      record_error (0, 0, _("\
     261  %s: direction flag in string %Zd in `era' field is not a single character"),
     262  			    "LC_TIME", idx + 1);
     263  	      (void) strsep (&str, ":");
     264  	    }
     265  	  else
     266  	    ++str;
     267  
     268  	  /* Now the offset year.  */
     269  	  time->era_entries[idx].offset = strtol (str, &endp, 10);
     270  	  if (endp == str)
     271  	    {
     272  	      record_error (0, 0, _("\
     273  %s: invalid number for offset in string %Zd in `era' field"),
     274  			    "LC_TIME", idx + 1);
     275  	      (void) strsep (&str, ":");
     276  	    }
     277  	  else if (*endp != ':')
     278  	    {
     279  	      record_error (0, 0, _("\
     280  %s: garbage at end of offset value in string %Zd in `era' field"),
     281  			    "LC_TIME", idx + 1);
     282  	      (void) strsep (&str, ":");
     283  	    }
     284  	  else
     285  	    str = endp + 1;
     286  
     287  	  /* Next is the starting date in ISO format.  */
     288  	  if (strncmp (str, "-*", 2) == 0)
     289  	    {
     290  	      time->era_entries[idx].start_date[0] =
     291  		time->era_entries[idx].start_date[1] =
     292  		time->era_entries[idx].start_date[2] = 0x80000000;
     293  	      if (str[2] != ':')
     294  		goto garbage_start_date;
     295  	      str += 3;
     296  	    }
     297  	  else if (strncmp (str, "+*", 2) == 0)
     298  	    {
     299  	      time->era_entries[idx].start_date[0] =
     300  		time->era_entries[idx].start_date[1] =
     301  		time->era_entries[idx].start_date[2] = 0x7fffffff;
     302  	      if (str[2] != ':')
     303  		goto garbage_start_date;
     304  	      str += 3;
     305  	    }
     306  	  else
     307  	    {
     308  	      time->era_entries[idx].start_date[0] = strtol (str, &endp, 10);
     309  	      if (endp == str || *endp != '/')
     310  		goto invalid_start_date;
     311  	      else
     312  		str = endp + 1;
     313  	      time->era_entries[idx].start_date[0] -= 1900;
     314  	      /* year -1 represent 1 B.C. (not -1 A.D.) */
     315  	      if (time->era_entries[idx].start_date[0] < -1900)
     316  		++time->era_entries[idx].start_date[0];
     317  
     318  	      time->era_entries[idx].start_date[1] = strtol (str, &endp, 10);
     319  	      if (endp == str || *endp != '/')
     320  		goto invalid_start_date;
     321  	      else
     322  		str = endp + 1;
     323  	      time->era_entries[idx].start_date[1] -= 1;
     324  
     325  	      time->era_entries[idx].start_date[2] = strtol (str, &endp, 10);
     326  	      if (endp == str)
     327  		{
     328  		invalid_start_date:
     329  		  record_error (0, 0, _("\
     330  %s: invalid starting date in string %Zd in `era' field"),
     331  				"LC_TIME", idx + 1);
     332  		  (void) strsep (&str, ":");
     333  		}
     334  	      else if (*endp != ':')
     335  		{
     336  		garbage_start_date:
     337  		  record_error (0, 0, _("\
     338  %s: garbage at end of starting date in string %Zd in `era' field "),
     339  				"LC_TIME", idx + 1);
     340  		  (void) strsep (&str, ":");
     341  		}
     342  	      else
     343  		{
     344  		  str = endp + 1;
     345  
     346  		  /* Check for valid value.  */
     347  		  if ((time->era_entries[idx].start_date[1] < 0
     348  		       || time->era_entries[idx].start_date[1] >= 12
     349  		       || time->era_entries[idx].start_date[2] < 0
     350  		       || (time->era_entries[idx].start_date[2]
     351  			   > days_per_month[time->era_entries[idx].start_date[1]])
     352  		       || (time->era_entries[idx].start_date[1] == 2
     353  			   && time->era_entries[idx].start_date[2] == 29
     354  			   && !__isleap (time->era_entries[idx].start_date[0]))))
     355  		    record_error (0, 0, _("\
     356  %s: starting date is invalid in string %Zd in `era' field"),
     357  				  "LC_TIME", idx + 1);
     358  		}
     359  	    }
     360  
     361  	  /* Next is the stopping date in ISO format.  */
     362  	  if (strncmp (str, "-*", 2) == 0)
     363  	    {
     364  	      time->era_entries[idx].stop_date[0] =
     365  		time->era_entries[idx].stop_date[1] =
     366  		time->era_entries[idx].stop_date[2] = 0x80000000;
     367  	      if (str[2] != ':')
     368  		goto garbage_stop_date;
     369  	      str += 3;
     370  	    }
     371  	  else if (strncmp (str, "+*", 2) == 0)
     372  	    {
     373  	      time->era_entries[idx].stop_date[0] =
     374  		time->era_entries[idx].stop_date[1] =
     375  		time->era_entries[idx].stop_date[2] = 0x7fffffff;
     376  	      if (str[2] != ':')
     377  		goto garbage_stop_date;
     378  	      str += 3;
     379  	    }
     380  	  else
     381  	    {
     382  	      time->era_entries[idx].stop_date[0] = strtol (str, &endp, 10);
     383  	      if (endp == str || *endp != '/')
     384  		goto invalid_stop_date;
     385  	      else
     386  		str = endp + 1;
     387  	      time->era_entries[idx].stop_date[0] -= 1900;
     388  	      /* year -1 represent 1 B.C. (not -1 A.D.) */
     389  	      if (time->era_entries[idx].stop_date[0] < -1900)
     390  		++time->era_entries[idx].stop_date[0];
     391  
     392  	      time->era_entries[idx].stop_date[1] = strtol (str, &endp, 10);
     393  	      if (endp == str || *endp != '/')
     394  		goto invalid_stop_date;
     395  	      else
     396  		str = endp + 1;
     397  	      time->era_entries[idx].stop_date[1] -= 1;
     398  
     399  	      time->era_entries[idx].stop_date[2] = strtol (str, &endp, 10);
     400  	      if (endp == str)
     401  		{
     402  		invalid_stop_date:
     403  		  record_error (0, 0, _("\
     404  %s: invalid stopping date in string %Zd in `era' field"),
     405  				"LC_TIME", idx + 1);
     406  		  (void) strsep (&str, ":");
     407  		}
     408  	      else if (*endp != ':')
     409  		{
     410  		garbage_stop_date:
     411  		  record_error (0, 0, _("\
     412  %s: garbage at end of stopping date in string %Zd in `era' field"),
     413  				"LC_TIME", idx + 1);
     414  		  (void) strsep (&str, ":");
     415  		}
     416  	      else
     417  		{
     418  		  str = endp + 1;
     419  
     420  		  /* Check for valid value.  */
     421  		  if ((time->era_entries[idx].stop_date[1] < 0
     422  		       || time->era_entries[idx].stop_date[1] >= 12
     423  		       || time->era_entries[idx].stop_date[2] < 0
     424  		       || (time->era_entries[idx].stop_date[2]
     425  			   > days_per_month[time->era_entries[idx].stop_date[1]])
     426  		       || (time->era_entries[idx].stop_date[1] == 2
     427  			   && time->era_entries[idx].stop_date[2] == 29
     428  			   && !__isleap (time->era_entries[idx].stop_date[0]))))
     429  		    record_error (0, 0, _("\
     430  %s: invalid stopping date in string %Zd in `era' field"),
     431  				  "LC_TIME", idx + 1);
     432  		}
     433  	    }
     434  
     435  	  if (str == NULL || *str == '\0')
     436  	    {
     437  	      record_error (0, 0, _("\
     438  %s: missing era name in string %Zd in `era' field"), "LC_TIME", idx + 1);
     439  	      time->era_entries[idx].name =
     440  		time->era_entries[idx].format = "";
     441  	    }
     442  	  else
     443  	    {
     444  	      time->era_entries[idx].name = strsep (&str, ":");
     445  
     446  	      if (str == NULL || *str == '\0')
     447  		{
     448  		  record_error (0, 0, _("\
     449  %s: missing era format in string %Zd in `era' field"),
     450  				"LC_TIME", idx + 1);
     451  		  time->era_entries[idx].name =
     452  		    time->era_entries[idx].format = "";
     453  		}
     454  	      else
     455  		time->era_entries[idx].format = str;
     456  	    }
     457  
     458  	  /* Now generate the wide character name and format.  */
     459  	  wstr = wcschr ((wchar_t *) time->wera[idx], L':');/* end direction */
     460  	  wstr = wstr ? wcschr (wstr + 1, L':') : NULL;	/* end offset */
     461  	  wstr = wstr ? wcschr (wstr + 1, L':') : NULL;	/* end start */
     462  	  wstr = wstr ? wcschr (wstr + 1, L':') : NULL;	/* end end */
     463  	  if (wstr != NULL)
     464  	    {
     465  	      time->era_entries[idx].wname = (uint32_t *) wstr + 1;
     466  	      wstr = wcschr (wstr + 1, L':');	/* end name */
     467  	      if (wstr != NULL)
     468  		{
     469  		  *wstr = L'\0';
     470  		  time->era_entries[idx].wformat = (uint32_t *) wstr + 1;
     471  		}
     472  	      else
     473  		time->era_entries[idx].wname =
     474  		  time->era_entries[idx].wformat = (uint32_t *) L"";
     475  	    }
     476  	  else
     477  	    time->era_entries[idx].wname =
     478  	      time->era_entries[idx].wformat = (uint32_t *) L"";
     479  	}
     480      }
     481  
     482    /* Set up defaults based on ISO 30112 WD10 [2014].  */
     483    if (time->week_ndays == 0)
     484      time->week_ndays = 7;
     485  
     486    if (time->week_1stday == 0)
     487      time->week_1stday = 19971130;
     488  
     489    if (time->week_1stweek == 0)
     490      time->week_1stweek = 7;
     491  
     492    if (time->week_1stweek > time->week_ndays)
     493      record_error (0, 0, _("\
     494  %s: third operand for value of field `%s' must not be larger than %d"),
     495  		  "LC_TIME", "week", 7);
     496  
     497    if (time->first_weekday == '\0')
     498      /* The definition does not specify this so the default is used.  */
     499      time->first_weekday = 1;
     500    else if (time->first_weekday > time->week_ndays)
     501      record_error (0, 0, _("\
     502  %s: values for field `%s' must not be larger than %d"),
     503  		  "LC_TIME", "first_weekday", 7);
     504  
     505    if (time->first_workday == '\0')
     506      /* The definition does not specify this so the default is used.  */
     507      time->first_workday = 2;
     508    else if (time->first_workday > time->week_ndays)
     509      record_error (0, 0, _("\
     510  %s: values for field `%s' must not be larger than %d"),
     511  		  "LC_TIME", "first_workday", 7);
     512  
     513    if (time->cal_direction == '\0')
     514      /* The definition does not specify this so the default is used.  */
     515      time->cal_direction = 1;
     516    else if (time->cal_direction > 3)
     517      record_error (0, 0, _("\
     518  %s: values for field `%s' must not be larger than %d"),
     519  		  "LC_TIME", "cal_direction", 3);
     520  
     521    /* XXX We don't perform any tests on the timezone value since this is
     522       simply useless, stupid $&$!@...  */
     523    if (time->timezone == NULL)
     524      time->timezone = "";
     525  
     526    if (time->date_fmt == NULL)
     527      time->date_fmt = "%a %b %e %H:%M:%S %Z %Y";
     528    if (time->wdate_fmt == NULL)
     529      time->wdate_fmt = (const uint32_t *) L"%a %b %e %H:%M:%S %Z %Y";
     530  }
     531  
     532  
     533  void
     534  time_output (struct localedef_t *locale, const struct charmap_t *charmap,
     535  	     const char *output_path)
     536  {
     537    struct locale_time_t *time = locale->categories[LC_TIME].time;
     538    struct locale_file file;
     539    size_t num, n;
     540  
     541    init_locale_data (&file, _NL_ITEM_INDEX (_NL_NUM_LC_TIME));
     542  
     543    /* The ab'days.  */
     544    for (n = 0; n < 7; ++n)
     545      add_locale_string (&file, time->abday[n] ?: "");
     546  
     547    /* The days.  */
     548    for (n = 0; n < 7; ++n)
     549      add_locale_string (&file, time->day[n] ?: "");
     550  
     551    /* The ab'mons.  */
     552    for (n = 0; n < 12; ++n)
     553      add_locale_string (&file, time->abmon[n] ?: "");
     554  
     555    /* The mons.  */
     556    for (n = 0; n < 12; ++n)
     557      add_locale_string (&file, time->mon[n] ?: "");
     558  
     559    /* AM/PM.  */
     560    for (n = 0; n < 2; ++n)
     561      add_locale_string (&file, time->am_pm[n]);
     562  
     563    add_locale_string (&file, time->d_t_fmt ?: "");
     564    add_locale_string (&file, time->d_fmt ?: "");
     565    add_locale_string (&file, time->t_fmt ?: "");
     566    add_locale_string (&file, time->t_fmt_ampm ?: "");
     567  
     568    start_locale_structure (&file);
     569    for (num = 0; num < time->num_era; ++num)
     570      add_locale_string (&file, time->era[num]);
     571    end_locale_structure (&file);
     572  
     573    add_locale_string (&file, time->era_year ?: "");
     574    add_locale_string (&file, time->era_d_fmt ?: "");
     575  
     576    start_locale_structure (&file);
     577    for (num = 0; num < 100; ++num)
     578      add_locale_string (&file, time->alt_digits[num] ?: "");
     579    end_locale_structure (&file);
     580  
     581    add_locale_string (&file, time->era_d_t_fmt ?: "");
     582    add_locale_string (&file, time->era_t_fmt ?: "");
     583    add_locale_uint32 (&file, time->num_era);
     584  
     585    start_locale_structure (&file);
     586    for (num = 0; num < time->num_era; ++num)
     587      {
     588        add_locale_uint32 (&file, time->era_entries[num].direction);
     589        add_locale_uint32 (&file, time->era_entries[num].offset);
     590        add_locale_uint32 (&file, time->era_entries[num].start_date[0]);
     591        add_locale_uint32 (&file, time->era_entries[num].start_date[1]);
     592        add_locale_uint32 (&file, time->era_entries[num].start_date[2]);
     593        add_locale_uint32 (&file, time->era_entries[num].stop_date[0]);
     594        add_locale_uint32 (&file, time->era_entries[num].stop_date[1]);
     595        add_locale_uint32 (&file, time->era_entries[num].stop_date[2]);
     596        add_locale_string (&file, time->era_entries[num].name);
     597        add_locale_string (&file, time->era_entries[num].format);
     598        add_locale_wstring (&file, time->era_entries[num].wname);
     599        add_locale_wstring (&file, time->era_entries[num].wformat);
     600      }
     601    end_locale_structure (&file);
     602  
     603    /* The wide character ab'days.  */
     604    for (n = 0; n < 7; ++n)
     605      add_locale_wstring (&file, time->wabday[n] ?: empty_wstr);
     606  
     607    /* The wide character days.  */
     608    for (n = 0; n < 7; ++n)
     609      add_locale_wstring (&file, time->wday[n] ?: empty_wstr);
     610  
     611    /* The wide character ab'mons.  */
     612    for (n = 0; n < 12; ++n)
     613      add_locale_wstring (&file, time->wabmon[n] ?: empty_wstr);
     614  
     615    /* The wide character mons.  */
     616    for (n = 0; n < 12; ++n)
     617      add_locale_wstring (&file, time->wmon[n] ?: empty_wstr);
     618  
     619    /* Wide character AM/PM.  */
     620    for (n = 0; n < 2; ++n)
     621      add_locale_wstring (&file, time->wam_pm[n] ?: empty_wstr);
     622  
     623    add_locale_wstring (&file, time->wd_t_fmt ?: empty_wstr);
     624    add_locale_wstring (&file, time->wd_fmt ?: empty_wstr);
     625    add_locale_wstring (&file, time->wt_fmt ?: empty_wstr);
     626    add_locale_wstring (&file, time->wt_fmt_ampm ?: empty_wstr);
     627    add_locale_wstring (&file, time->wera_year ?: empty_wstr);
     628    add_locale_wstring (&file, time->wera_d_fmt ?: empty_wstr);
     629  
     630    start_locale_structure (&file);
     631    for (num = 0; num < 100; ++num)
     632      add_locale_wstring (&file, time->walt_digits[num] ?: empty_wstr);
     633    end_locale_structure (&file);
     634  
     635    add_locale_wstring (&file, time->wera_d_t_fmt ?: empty_wstr);
     636    add_locale_wstring (&file, time->wera_t_fmt ?: empty_wstr);
     637    add_locale_char (&file, time->week_ndays);
     638    add_locale_uint32 (&file, time->week_1stday);
     639    add_locale_char (&file, time->week_1stweek);
     640    add_locale_char (&file, time->first_weekday);
     641    add_locale_char (&file, time->first_workday);
     642    add_locale_char (&file, time->cal_direction);
     643    add_locale_string (&file, time->timezone);
     644    add_locale_string (&file, time->date_fmt);
     645    add_locale_wstring (&file, time->wdate_fmt);
     646    add_locale_string (&file, charmap->code_set_name);
     647  
     648    /* The alt'mons.  */
     649    for (n = 0; n < 12; ++n)
     650      add_locale_string (&file, time->alt_mon[n] ?: "");
     651  
     652    /* The wide character alt'mons.  */
     653    for (n = 0; n < 12; ++n)
     654      add_locale_wstring (&file, time->walt_mon[n] ?: empty_wstr);
     655  
     656    /* The ab'alt'mons.  */
     657    for (n = 0; n < 12; ++n)
     658      add_locale_string (&file, time->ab_alt_mon[n] ?: "");
     659  
     660    /* The wide character ab'alt'mons.  */
     661    for (n = 0; n < 12; ++n)
     662      add_locale_wstring (&file, time->wab_alt_mon[n] ?: empty_wstr);
     663  
     664    write_locale_data (output_path, LC_TIME, "LC_TIME", &file);
     665  }
     666  
     667  
     668  /* The parser for the LC_TIME section of the locale definition.  */
     669  void
     670  time_read (struct linereader *ldfile, struct localedef_t *result,
     671  	   const struct charmap_t *charmap, const char *repertoire_name,
     672  	   int ignore_content)
     673  {
     674    struct repertoire_t *repertoire = NULL;
     675    struct locale_time_t *time;
     676    struct token *now;
     677    enum token_t nowtok;
     678    size_t cnt;
     679  
     680    /* Get the repertoire we have to use.  */
     681    if (repertoire_name != NULL)
     682      repertoire = repertoire_read (repertoire_name);
     683  
     684    /* The rest of the line containing `LC_TIME' must be free.  */
     685    lr_ignore_rest (ldfile, 1);
     686  
     687  
     688    do
     689      {
     690        now = lr_token (ldfile, charmap, result, repertoire, verbose);
     691        nowtok = now->tok;
     692      }
     693    while (nowtok == tok_eol);
     694  
     695    /* If we see `copy' now we are almost done.  */
     696    if (nowtok == tok_copy)
     697      {
     698        handle_copy (ldfile, charmap, repertoire_name, result, tok_lc_time,
     699  		   LC_TIME, "LC_TIME", ignore_content);
     700        return;
     701      }
     702  
     703    /* Prepare the data structures.  */
     704    time_startup (ldfile, result, ignore_content);
     705    time = result->categories[LC_TIME].time;
     706  
     707    while (1)
     708      {
     709        /* Of course we don't proceed beyond the end of file.  */
     710        if (nowtok == tok_eof)
     711  	break;
     712  
     713        /* Ingore empty lines.  */
     714        if (nowtok == tok_eol)
     715  	{
     716  	  now = lr_token (ldfile, charmap, result, repertoire, verbose);
     717  	  nowtok = now->tok;
     718  	  continue;
     719  	}
     720  
     721        switch (nowtok)
     722  	{
     723  #define STRARR_ELEM(cat, min, max) \
     724  	case tok_##cat:							      \
     725  	  /* Ignore the rest of the line if we don't need the input of	      \
     726  	     this line.  */						      \
     727  	  if (ignore_content)						      \
     728  	    {								      \
     729  	      lr_ignore_rest (ldfile, 0);				      \
     730  	      break;							      \
     731  	    }								      \
     732  									      \
     733  	  for (cnt = 0; cnt < max; ++cnt)				      \
     734  	    {								      \
     735  	      now = lr_token (ldfile, charmap, result, repertoire, verbose);  \
     736  	      if (now->tok == tok_eol)					      \
     737  		{							      \
     738  		  if (cnt < min)					      \
     739  		    lr_error (ldfile, _("%s: too few values for field `%s'"), \
     740  			      "LC_TIME", #cat);				      \
     741  		  if (!ignore_content)					      \
     742  		    do							      \
     743  		      {							      \
     744  			time->cat[cnt] = "";				      \
     745  			time->w##cat[cnt] = empty_wstr;			      \
     746  		      }							      \
     747  		    while (++cnt < max);				      \
     748  		  break;						      \
     749  		}							      \
     750  	      else if (now->tok != tok_string)				      \
     751  		goto err_label;						      \
     752  	      else if (!ignore_content && (now->val.str.startmb == NULL	      \
     753  					   || now->val.str.startwc == NULL))  \
     754  		{							      \
     755  		  lr_error (ldfile, _("%s: unknown character in field `%s'"), \
     756  			    "LC_TIME", #cat);				      \
     757  		  time->cat[cnt] = "";					      \
     758  		  time->w##cat[cnt] = empty_wstr;			      \
     759  		}							      \
     760  	      else if (!ignore_content)					      \
     761  		{							      \
     762  		  time->cat[cnt] = now->val.str.startmb;		      \
     763  		  time->w##cat[cnt] = now->val.str.startwc;		      \
     764  		}							      \
     765  									      \
     766  	      /* Match the semicolon.  */				      \
     767  	      now = lr_token (ldfile, charmap, result, repertoire, verbose);  \
     768  	      if (now->tok != tok_semicolon && now->tok != tok_eol)	      \
     769  		break;							      \
     770  	    }								      \
     771  	  if (now->tok != tok_eol)					      \
     772  	    {								      \
     773  	      while (!ignore_content && cnt < min)			      \
     774  		{							      \
     775  		  time->cat[cnt] = "";					      \
     776  		  time->w##cat[cnt++] = empty_wstr;			      \
     777  		}							      \
     778  									      \
     779  	      if (now->tok == tok_semicolon)				      \
     780  		{							      \
     781  		  now = lr_token (ldfile, charmap, result, repertoire,	      \
     782  				  verbose);				      \
     783  		  if (now->tok == tok_eol)				      \
     784  		    lr_error (ldfile, _("extra trailing semicolon"));	      \
     785  		  else if (now->tok == tok_string)			      \
     786  		    {							      \
     787  		      lr_error (ldfile, _("\
     788  %s: too many values for field `%s'"),					      \
     789  				"LC_TIME", #cat);			      \
     790  		      lr_ignore_rest (ldfile, 0);			      \
     791  		    }							      \
     792  		  else							      \
     793  		    goto err_label;					      \
     794  		}							      \
     795  	      else							      \
     796  		goto err_label;						      \
     797  	    }								      \
     798  	  time->cat##_defined = 1;					      \
     799  	  break
     800  
     801  	  STRARR_ELEM (abday, 7, 7);
     802  	  STRARR_ELEM (day, 7, 7);
     803  	  STRARR_ELEM (abmon, 12, 12);
     804  	  STRARR_ELEM (mon, 12, 12);
     805  	  STRARR_ELEM (am_pm, 2, 2);
     806  	  STRARR_ELEM (alt_digits, 0, 100);
     807  	  STRARR_ELEM (alt_mon, 12, 12);
     808  	  STRARR_ELEM (ab_alt_mon, 12, 12);
     809  
     810  	case tok_era:
     811  	  /* Ignore the rest of the line if we don't need the input of
     812  	     this line.  */
     813  	  if (ignore_content)
     814  	    {
     815  	      lr_ignore_rest (ldfile, 0);
     816  	      break;
     817  	    }
     818  	  do
     819  	    {
     820  	      now = lr_token (ldfile, charmap, result, repertoire, verbose);
     821  	      if (now->tok != tok_string)
     822  		goto err_label;
     823  	      if (!ignore_content && (now->val.str.startmb == NULL
     824  				      || now->val.str.startwc == NULL))
     825  		{
     826  		  lr_error (ldfile, _("%s: unknown character in field `%s'"),
     827  			    "LC_TIME", "era");
     828  		  lr_ignore_rest (ldfile, 0);
     829  		  break;
     830  		}
     831  	      if (!ignore_content)
     832  		{
     833  		  time->era = xrealloc (time->era,
     834  					(time->num_era + 1) * sizeof (char *));
     835  		  time->era[time->num_era] = now->val.str.startmb;
     836  
     837  		  time->wera = xrealloc (time->wera,
     838  					 (time->num_era + 1)
     839  					 * sizeof (char *));
     840  		  time->wera[time->num_era++] = now->val.str.startwc;
     841  		}
     842  	      now = lr_token (ldfile, charmap, result, repertoire, verbose);
     843  	      if (now->tok != tok_eol && now->tok != tok_semicolon)
     844  		goto err_label;
     845  	    }
     846  	  while (now->tok == tok_semicolon);
     847  	  break;
     848  
     849  #define STR_ELEM(cat) \
     850  	case tok_##cat:							      \
     851  	  /* Ignore the rest of the line if we don't need the input of	      \
     852  	     this line.  */						      \
     853  	  if (ignore_content)						      \
     854  	    {								      \
     855  	      lr_ignore_rest (ldfile, 0);				      \
     856  	      break;							      \
     857  	    }								      \
     858  									      \
     859  	  now = lr_token (ldfile, charmap, result, repertoire, verbose);      \
     860  	  if (now->tok != tok_string)					      \
     861  	    goto err_label;						      \
     862  	  else if (time->cat != NULL)					      \
     863  	    lr_error (ldfile, _("\
     864  %s: field `%s' declared more than once"), "LC_TIME", #cat);		      \
     865  	  else if (!ignore_content && (now->val.str.startmb == NULL	      \
     866  				       || now->val.str.startwc == NULL))      \
     867  	    {								      \
     868  	      lr_error (ldfile, _("%s: unknown character in field `%s'"),     \
     869  			"LC_TIME", #cat);				      \
     870  	      time->cat = "";						      \
     871  	      time->w##cat = empty_wstr;				      \
     872  	    }								      \
     873  	  else if (!ignore_content)					      \
     874  	    {								      \
     875  	      time->cat = now->val.str.startmb;				      \
     876  	      time->w##cat = now->val.str.startwc;			      \
     877  	    }								      \
     878  	  break
     879  
     880  	  STR_ELEM (d_t_fmt);
     881  	  STR_ELEM (d_fmt);
     882  	  STR_ELEM (t_fmt);
     883  	  STR_ELEM (t_fmt_ampm);
     884  	  STR_ELEM (era_year);
     885  	  STR_ELEM (era_d_t_fmt);
     886  	  STR_ELEM (era_d_fmt);
     887  	  STR_ELEM (era_t_fmt);
     888  	  STR_ELEM (timezone);
     889  	  STR_ELEM (date_fmt);
     890  
     891  #define INT_ELEM(cat) \
     892  	case tok_##cat:							      \
     893  	  /* Ignore the rest of the line if we don't need the input of	      \
     894  	     this line.  */						      \
     895  	  if (ignore_content)						      \
     896  	    {								      \
     897  	      lr_ignore_rest (ldfile, 0);				      \
     898  	      break;							      \
     899  	    }								      \
     900  									      \
     901  	  now = lr_token (ldfile, charmap, result, repertoire, verbose);      \
     902  	  if (now->tok != tok_number)					      \
     903  	    goto err_label;						      \
     904  	  else if (time->cat != 0)					      \
     905  	    lr_error (ldfile, _("%s: field `%s' declared more than once"),    \
     906  		      "LC_TIME", #cat);					      \
     907  	  else if (!ignore_content)					      \
     908  	    time->cat = now->val.num;					      \
     909  	  break
     910  
     911  	  INT_ELEM (first_weekday);
     912  	  INT_ELEM (first_workday);
     913  	  INT_ELEM (cal_direction);
     914  
     915  	case tok_week:
     916  	  /* Ignore the rest of the line if we don't need the input of
     917  	     this line.  */
     918  	  if (ignore_content)
     919  	    {
     920  	      lr_ignore_rest (ldfile, 0);
     921  	      break;
     922  	    }
     923  
     924  	  now = lr_token (ldfile, charmap, result, repertoire, verbose);
     925  	  if (now->tok != tok_number)
     926  	    goto err_label;
     927  	  time->week_ndays = now->val.num;
     928  
     929  	  now = lr_token (ldfile, charmap, result, repertoire, verbose);
     930  	  if (now->tok != tok_semicolon)
     931  	    goto err_label;
     932  
     933  	  now = lr_token (ldfile, charmap, result, repertoire, verbose);
     934  	  if (now->tok != tok_number)
     935  	    goto err_label;
     936  	  time->week_1stday = now->val.num;
     937  
     938  	  now = lr_token (ldfile, charmap, result, repertoire, verbose);
     939  	  if (now->tok != tok_semicolon)
     940  	    goto err_label;
     941  
     942  	  now = lr_token (ldfile, charmap, result, repertoire, verbose);
     943  	  if (now->tok != tok_number)
     944  	    goto err_label;
     945  	  time->week_1stweek = now->val.num;
     946  
     947  	  lr_ignore_rest (ldfile,  1);
     948  	  break;
     949  
     950  	case tok_end:
     951  	  /* Next we assume `LC_TIME'.  */
     952  	  now = lr_token (ldfile, charmap, result, repertoire, verbose);
     953  	  if (now->tok == tok_eof)
     954  	    break;
     955  	  if (now->tok == tok_eol)
     956  	    lr_error (ldfile, _("%s: incomplete `END' line"), "LC_TIME");
     957  	  else if (now->tok != tok_lc_time)
     958  	    lr_error (ldfile, _("\
     959  %1$s: definition does not end with `END %1$s'"), "LC_TIME");
     960  	  lr_ignore_rest (ldfile, now->tok == tok_lc_time);
     961  
     962  	  /* If alt_mon was not specified, make it a copy of mon.  */
     963  	  if (!ignore_content && !time->alt_mon_defined)
     964  	    {
     965  	      memcpy (time->alt_mon, time->mon, sizeof (time->mon));
     966  	      memcpy (time->walt_mon, time->wmon, sizeof (time->wmon));
     967  	      time->alt_mon_defined = 1;
     968  	    }
     969  	  /* The same for abbreviated versions.  */
     970  	  if (!ignore_content && !time->ab_alt_mon_defined)
     971  	    {
     972  	      memcpy (time->ab_alt_mon, time->abmon, sizeof (time->abmon));
     973  	      memcpy (time->wab_alt_mon, time->wabmon, sizeof (time->wabmon));
     974  	      time->ab_alt_mon_defined = 1;
     975  	    }
     976  	  return;
     977  
     978  	default:
     979  	err_label:
     980  	  SYNTAX_ERROR (_("%s: syntax error"), "LC_TIME");
     981  	}
     982  
     983        /* Prepare for the next round.  */
     984        now = lr_token (ldfile, charmap, result, repertoire, verbose);
     985        nowtok = now->tok;
     986      }
     987  
     988    /* When we come here we reached the end of the file.  */
     989    lr_error (ldfile, _("%s: premature end of file"), "LC_TIME");
     990  }