(ISDIGIT): Comment fix.
[gnulib.git] / lib / posixtm.c
1 /* Parse dates for touch and date.
2    Copyright (C) 1989, 1990, 1991, 1998, 2000-2002 Free Software Foundation Inc.
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 by
6    the Free Software Foundation; either version 2, or (at your option)
7    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, write to the Free Software Foundation,
16    Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
17
18 /* Yacc-based version written by Jim Kingdon and David MacKenzie.
19    Rewritten by Jim Meyering.  */
20
21 #ifdef HAVE_CONFIG_H
22 # include <config.h>
23 #endif
24
25 #include <stdio.h>
26 #if HAVE_STDLIB_H
27 # include <stdlib.h>
28 #endif
29 #include <sys/types.h>
30 #if HAVE_STRING_H
31 # include <string.h>
32 #else
33 # include <strings.h>
34 #endif
35
36 #ifdef TM_IN_SYS_TIME
37 # include <sys/time.h>
38 #else
39 # include <time.h>
40 #endif
41
42 #include "posixtm.h"
43 #include "unlocked-io.h"
44
45 /* ISDIGIT differs from isdigit, as follows:
46    - Its arg may be any int or unsigned int; it need not be an unsigned char.
47    - It's guaranteed to evaluate its argument exactly once.
48    - It's typically faster.
49    POSIX says that only '0' through '9' are digits.  Prefer ISDIGIT to
50    ISDIGIT_LOCALE unless it's important to use the locale's definition
51    of `digit' even when the host does not conform to POSIX.  */
52 #define ISDIGIT(c) ((unsigned) (c) - '0' <= 9)
53
54 /* The return value. */
55 static struct tm t;
56
57 time_t mktime ();
58
59 /*
60   POSIX requires:
61
62   touch -t [[CC]YY]mmddhhmm[.ss] FILE...
63     8, 10, or 12 digits, followed by optional .ss
64     (PDS_LEADING_YEAR | PDS_CENTURY | PDS_SECONDS)
65
66   touch mmddhhmm[YY] FILE... (obsoleted by POSIX 1003.1-2001)
67     8 or 10 digits
68     (PDS_TRAILING_YEAR)
69
70   date mmddhhmm[[CC]YY]
71     8, 10, or 12 digits
72     (PDS_TRAILING_YEAR | PDS_CENTURY)
73
74 */
75
76 static int
77 year (const int *digit_pair, size_t n, int allow_century)
78 {
79   switch (n)
80     {
81     case 1:
82       t.tm_year = *digit_pair;
83       /* Deduce the century based on the year.
84          POSIX requires that 00-68 be interpreted as 2000-2068,
85          and that 69-99 be interpreted as 1969-1999.  */
86       if (digit_pair[0] <= 68)
87         t.tm_year += 100;
88       break;
89
90     case 2:
91       if (!allow_century)
92         return 1;
93       t.tm_year = digit_pair[0] * 100 + digit_pair[1];
94       if (t.tm_year < 1900)
95         return 1;
96       t.tm_year -= 1900;
97       break;
98
99     case 0:
100       {
101         time_t now;
102         struct tm *tmp;
103
104         /* Use current year.  */
105         time (&now);
106         tmp = localtime (&now);
107         t.tm_year = tmp->tm_year;
108       }
109       break;
110
111     default:
112       abort ();
113     }
114
115   return 0;
116 }
117
118 static int
119 posix_time_parse (const char *s, unsigned int syntax_bits)
120 {
121   const char *dot = NULL;
122   int pair[6];
123   int *p;
124   unsigned int i;
125
126   size_t s_len = strlen (s);
127   size_t len = (((syntax_bits & PDS_SECONDS) && (dot = strchr (s, '.')))
128                 ? (size_t) (dot - s)
129                 : s_len);
130
131   if (len != 8 && len != 10 && len != 12)
132     return 1;
133
134   if (dot)
135     {
136       if (!(syntax_bits & PDS_SECONDS))
137         return 1;
138
139       if (s_len - len != 3)
140         return 1;
141     }
142
143   for (i = 0; i < len; i++)
144     if (!ISDIGIT (s[i]))
145       return 1;
146
147   len /= 2;
148   for (i = 0; i < len; i++)
149     pair[i] = 10 * (s[2*i] - '0') + s[2*i + 1] - '0';
150
151   p = pair;
152   if (syntax_bits & PDS_LEADING_YEAR)
153     {
154       if (year (p, len - 4, syntax_bits & PDS_CENTURY))
155         return 1;
156       p += len - 4;
157       len = 4;
158     }
159
160   /* Handle 8 digits worth of `MMDDhhmm'.  */
161   if (*p < 1 || *p > 12)
162     return 1;
163   t.tm_mon = *p - 1;
164   ++p; --len;
165
166   if (*p < 1 || *p > 31)
167     return 1;
168   t.tm_mday = *p;
169   ++p; --len;
170
171   if (*p < 0 || *p > 23)
172     return 1;
173   t.tm_hour = *p;
174   ++p; --len;
175
176   if (*p < 0 || *p > 59)
177     return 1;
178   t.tm_min = *p;
179   ++p; --len;
180
181   /* Handle any trailing year.  */
182   if (syntax_bits & PDS_TRAILING_YEAR)
183     {
184       if (year (p, len, syntax_bits & PDS_CENTURY))
185         return 1;
186     }
187
188   /* Handle seconds.  */
189   if (!dot)
190     {
191       t.tm_sec = 0;
192     }
193   else
194     {
195       int seconds;
196
197       ++dot;
198       if (!ISDIGIT (dot[0]) || !ISDIGIT (dot[1]))
199         return 1;
200       seconds = 10 * (dot[0] - '0') + dot[1] - '0';
201
202       if (seconds < 0 || seconds > 61)
203         return 1;
204       t.tm_sec = seconds;
205     }
206
207   return 0;
208 }
209
210 /* Parse a POSIX-style date and return it, or (time_t)-1 for an error.  */
211
212 time_t
213 posixtime (const char *s, unsigned int syntax_bits)
214 {
215   t.tm_isdst = -1;
216   if (posix_time_parse (s, syntax_bits))
217     return (time_t)-1;
218   else
219     return mktime (&t);
220 }
221
222 /* Parse a POSIX-style date and return it, or NULL for an error.  */
223
224 struct tm *
225 posixtm (const char *s, unsigned int syntax_bits)
226 {
227   if (posixtime (s, syntax_bits) == -1)
228     return NULL;
229   return &t;
230 }
231
232 #ifdef TEST_POSIXTIME
233 /*
234     Test mainly with syntax_bits == 13
235     (aka: (PDS_LEADING_YEAR | PDS_CENTURY | PDS_SECONDS))
236
237 BEGIN-DATA
238 1112131415      13 1323807300 Tue Dec 13 14:15:00 2011
239 1112131415.16   13 1323807316 Tue Dec 13 14:15:16 2011
240 201112131415.16 13 1323807316 Tue Dec 13 14:15:16 2011
241 191112131415.16 13 -1 ***
242 203712131415.16 13 2144348116 Sun Dec 13 14:15:16 2037
243 3712131415.16   13 2144348116 Sun Dec 13 14:15:16 2037
244 6812131415.16   13 -1 ***
245 6912131415.16   13 -1 ***
246 7012131415.16   13 29967316 Sun Dec 13 14:15:16 1970
247 12131415.16     13 913580116 Sun Dec 13 14:15:16 1998
248
249 1213141599       2 945116100 Mon Dec 13 14:15:00 1999
250 1213141500       2 976738500 Wed Dec 13 14:15:00 2000
251 END-DATA
252
253 */
254
255 # define MAX_BUFF_LEN 1024
256
257 int
258 main ()
259 {
260   char buff[MAX_BUFF_LEN + 1];
261
262   buff[MAX_BUFF_LEN] = 0;
263   while (fgets (buff, MAX_BUFF_LEN, stdin) && buff[0])
264     {
265       char time_str[MAX_BUFF_LEN];
266       unsigned int syntax_bits;
267       time_t t;
268       char *result;
269       sscanf (buff, "%s %u", time_str, &syntax_bits);
270       t = posixtime (time_str, syntax_bits);
271       result = (t == (time_t) -1 ? "***" : ctime (&t));
272       printf ("%d %s\n", (int) t, result);
273     }
274   exit (0);
275
276 }
277 #endif