1/* Parse dates for touch and date.
2
3   Copyright (C) 1989-1991, 1998, 2000-2010 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 <http://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_LEADING_YEAR | 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
60static int
61year (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 1;
74          tm->tm_year += 100;
75        }
76      break;
77
78    case 2:
79      if (! (syntax_bits & PDS_CENTURY))
80        return 1;
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 1;
94        tm->tm_year = tmp->tm_year;
95      }
96      break;
97
98    default:
99      abort ();
100    }
101
102  return 0;
103}
104
105static int
106posix_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 = (((syntax_bits & PDS_SECONDS) && (dot = strchr (s, '.')))
115                ? (size_t) (dot - s)
116                : s_len);
117
118  if (len != 8 && len != 10 && len != 12)
119    return 1;
120
121  if (dot)
122    {
123      if (!(syntax_bits & PDS_SECONDS))
124        return 1;
125
126      if (s_len - len != 3)
127        return 1;
128    }
129
130  for (i = 0; i < len; i++)
131    if (!ISDIGIT (s[i]))
132      return 1;
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_LEADING_YEAR)
140    {
141      if (year (tm, p, len - 4, syntax_bits))
142        return 1;
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 1;
159    }
160
161  /* Handle seconds.  */
162  if (!dot)
163    {
164      tm->tm_sec = 0;
165    }
166  else
167    {
168      int seconds;
169
170      ++dot;
171      if (!ISDIGIT (dot[0]) || !ISDIGIT (dot[1]))
172        return 1;
173      seconds = 10 * (dot[0] - '0') + dot[1] - '0';
174
175      tm->tm_sec = seconds;
176    }
177
178  return 0;
179}
180
181/* Parse a POSIX-style date, returning true if successful.  */
182
183bool
184posixtime (time_t *p, const char *s, unsigned int syntax_bits)
185{
186  struct tm tm0;
187  struct tm tm1;
188  struct tm const *tm;
189  time_t t;
190
191  if (posix_time_parse (&tm0, s, syntax_bits))
192    return false;
193
194  tm1 = tm0;
195  tm1.tm_isdst = -1;
196  t = mktime (&tm1);
197
198  if (t != (time_t) -1)
199    tm = &tm1;
200  else
201    {
202      /* mktime returns -1 for errors, but -1 is also a valid time_t
203         value.  Check whether an error really occurred.  */
204      tm = localtime (&t);
205      if (! tm)
206        return false;
207    }
208
209  /* Reject dates like "September 31" and times like "25:61".
210     Do not reject times that specify "60" as the number of seconds.  */
211  if ((tm0.tm_year ^ tm->tm_year)
212      | (tm0.tm_mon ^ tm->tm_mon)
213      | (tm0.tm_mday ^ tm->tm_mday)
214      | (tm0.tm_hour ^ tm->tm_hour)
215      | (tm0.tm_min ^ tm->tm_min)
216      | (tm0.tm_sec ^ tm->tm_sec))
217    {
218      /* Any mismatch without 60 in the tm_sec field is invalid.  */
219      if (tm0.tm_sec != 60)
220        return false;
221
222      {
223        /* Allow times like 01:35:60 or 23:59:60.  */
224        time_t dummy;
225        char buf[16];
226        char *b = stpcpy (buf, s);
227        strcpy (b - 2, "59");
228        if (!posixtime (&dummy, buf, syntax_bits))
229          return false;
230      }
231    }
232
233  *p = t;
234  return true;
235}
236