1 /* Parse dates for touch and date.
2 
3    Copyright (C) 1989-1991, 1998, 2000-2020 Free Software Foundation, Inc.
4 
5    This program is free software: you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation; either version 3 of the License, or
8    (at your option) any later version.
9 
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14 
15    You should have received a copy of the GNU General Public License
16    along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
17 
18 /* Yacc-based version written by Jim Kingdon and David MacKenzie.
19    Rewritten by Jim Meyering.  */
20 
21 #include <config.h>
22 
23 #include "posixtm.h"
24 
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <sys/types.h>
28 #include <string.h>
29 
30 #if USE_UNLOCKED_IO
31 # include "unlocked-io.h"
32 #endif
33 
34 /* ISDIGIT differs from isdigit, as follows:
35    - Its arg may be any int or unsigned int; it need not be an unsigned char
36      or EOF.
37    - It's typically faster.
38    POSIX says that only '0' through '9' are digits.  Prefer ISDIGIT to
39    isdigit unless it's important to use the locale's definition
40    of "digit" even when the host does not conform to POSIX.  */
41 #define ISDIGIT(c) ((unsigned int) (c) - '0' <= 9)
42 
43 /*
44   POSIX requires:
45 
46   touch -t [[CC]YY]mmddhhmm[.ss] FILE...
47     8, 10, or 12 digits, followed by optional .ss
48     (PDS_CENTURY | PDS_SECONDS)
49 
50   touch mmddhhmm[YY] FILE... (obsoleted by POSIX 1003.1-2001)
51     8 or 10 digits, YY (if present) must be in the range 69-99
52     (PDS_TRAILING_YEAR | PDS_PRE_2000)
53 
54   date mmddhhmm[[CC]YY]
55     8, 10, or 12 digits
56     (PDS_TRAILING_YEAR | PDS_CENTURY)
57 
58 */
59 
60 static bool
year(struct tm * tm,const int * digit_pair,size_t n,unsigned int syntax_bits)61 year (struct tm *tm, const int *digit_pair, size_t n, unsigned int syntax_bits)
62 {
63   switch (n)
64     {
65     case 1:
66       tm->tm_year = *digit_pair;
67       /* Deduce the century based on the year.
68          POSIX requires that 00-68 be interpreted as 2000-2068,
69          and that 69-99 be interpreted as 1969-1999.  */
70       if (digit_pair[0] <= 68)
71         {
72           if (syntax_bits & PDS_PRE_2000)
73             return false;
74           tm->tm_year += 100;
75         }
76       break;
77 
78     case 2:
79       if (! (syntax_bits & PDS_CENTURY))
80         return false;
81       tm->tm_year = digit_pair[0] * 100 + digit_pair[1] - 1900;
82       break;
83 
84     case 0:
85       {
86         time_t now;
87         struct tm *tmp;
88 
89         /* Use current year.  */
90         time (&now);
91         tmp = localtime (&now);
92         if (! tmp)
93           return false;
94         tm->tm_year = tmp->tm_year;
95       }
96       break;
97 
98     default:
99       abort ();
100     }
101 
102   return true;
103 }
104 
105 static bool
posix_time_parse(struct tm * tm,const char * s,unsigned int syntax_bits)106 posix_time_parse (struct tm *tm, const char *s, unsigned int syntax_bits)
107 {
108   const char *dot = NULL;
109   int pair[6];
110   int *p;
111   size_t i;
112 
113   size_t s_len = strlen (s);
114   size_t len = s_len;
115 
116   if (syntax_bits & PDS_SECONDS)
117     {
118       dot = strchr (s, '.');
119       if (dot)
120         {
121           len = dot - s;
122           if (s_len - len != 3)
123             return false;
124         }
125     }
126 
127   if (! (8 <= len && len <= 12 && len % 2 == 0))
128     return false;
129 
130   for (i = 0; i < len; i++)
131     if (!ISDIGIT (s[i]))
132       return false;
133 
134   len /= 2;
135   for (i = 0; i < len; i++)
136     pair[i] = 10 * (s[2*i] - '0') + s[2*i + 1] - '0';
137 
138   p = pair;
139   if (! (syntax_bits & PDS_TRAILING_YEAR))
140     {
141       if (! year (tm, p, len - 4, syntax_bits))
142         return false;
143       p += len - 4;
144       len = 4;
145     }
146 
147   /* Handle 8 digits worth of 'MMDDhhmm'.  */
148   tm->tm_mon = *p++ - 1;
149   tm->tm_mday = *p++;
150   tm->tm_hour = *p++;
151   tm->tm_min = *p++;
152   len -= 4;
153 
154   /* Handle any trailing year.  */
155   if (syntax_bits & PDS_TRAILING_YEAR)
156     {
157       if (! year (tm, p, len, syntax_bits))
158         return false;
159     }
160 
161   /* Handle seconds.  */
162   if (!dot)
163     tm->tm_sec = 0;
164   else if (ISDIGIT (dot[1]) && ISDIGIT (dot[2]))
165     tm->tm_sec = 10 * (dot[1] - '0') + dot[2] - '0';
166   else
167     return false;
168 
169   return true;
170 }
171 
172 /* Parse a POSIX-style date, returning true if successful.  */
173 
174 bool
posixtime(time_t * p,const char * s,unsigned int syntax_bits)175 posixtime (time_t *p, const char *s, unsigned int syntax_bits)
176 {
177   struct tm tm0;
178   struct tm tm1;
179   time_t t;
180 
181   if (! posix_time_parse (&tm0, s, syntax_bits))
182     return false;
183 
184   tm1.tm_sec = tm0.tm_sec;
185   tm1.tm_min = tm0.tm_min;
186   tm1.tm_hour = tm0.tm_hour;
187   tm1.tm_mday = tm0.tm_mday;
188   tm1.tm_mon = tm0.tm_mon;
189   tm1.tm_year = tm0.tm_year;
190   tm1.tm_wday = -1;
191   tm1.tm_isdst = -1;
192   t = mktime (&tm1);
193 
194   if (tm1.tm_wday < 0)
195     return false;
196 
197   /* Reject dates like "September 31" and times like "25:61".
198      However, allow a seconds count of 60 even in time zones that do
199      not support leap seconds, treating it as the following second;
200      POSIX requires this.  */
201   if ((tm0.tm_year ^ tm1.tm_year)
202       | (tm0.tm_mon ^ tm1.tm_mon)
203       | (tm0.tm_mday ^ tm1.tm_mday)
204       | (tm0.tm_hour ^ tm1.tm_hour)
205       | (tm0.tm_min ^ tm1.tm_min)
206       | (tm0.tm_sec ^ tm1.tm_sec))
207     {
208       /* Any mismatch without 60 in the tm_sec field is invalid.  */
209       if (tm0.tm_sec != 60)
210         return false;
211 
212       {
213         /* Allow times like 01:35:60 or 23:59:60.  */
214         time_t dummy;
215         char buf[16];
216         char *b = stpcpy (buf, s);
217         strcpy (b - 2, "59");
218         if (!posixtime (&dummy, buf, syntax_bits))
219           return false;
220       }
221     }
222 
223   *p = t;
224   return true;
225 }
226