exp2l: Work around a NetBSD 10.0/i386 bug.
[gnulib.git] / lib / posixtm.c
bloba072c7cad015527ba548766b3bc2d47f67ab984b
1 /* Parse dates for touch and date.
3 Copyright (C) 1989-1991, 1998, 2000-2024 Free Software Foundation, Inc.
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.
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.
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/>. */
18 /* Yacc-based version written by Jim Kingdon and David MacKenzie.
19 Rewritten by Jim Meyering. */
21 #include <config.h>
23 #include "posixtm.h"
25 #include "c-ctype.h"
26 #include "idx.h"
27 #include "verify.h"
29 #include <stdckdint.h>
30 #include <string.h>
33 POSIX requires:
35 touch -t [[CC]YY]mmddhhmm[.ss] FILE...
36 8, 10, or 12 digits, followed by optional .ss
37 (PDS_CENTURY | PDS_SECONDS)
39 touch mmddhhmm[YY] FILE... (obsoleted by POSIX 1003.1-2001)
40 8 or 10 digits, YY (if present) must be in the range 69-99
41 (PDS_TRAILING_YEAR | PDS_PRE_2000)
43 date mmddhhmm[[CC]YY]
44 8, 10, or 12 digits
45 (PDS_TRAILING_YEAR | PDS_CENTURY)
49 static bool
50 year (struct tm *tm, const int *digit_pair, idx_t n, unsigned int syntax_bits)
52 switch (n)
54 case 1:
55 tm->tm_year = *digit_pair;
56 /* Deduce the century based on the year.
57 POSIX requires that 00-68 be interpreted as 2000-2068,
58 and that 69-99 be interpreted as 1969-1999. */
59 if (digit_pair[0] <= 68)
61 if (syntax_bits & PDS_PRE_2000)
62 return false;
63 tm->tm_year += 100;
65 break;
67 case 2:
68 if (! (syntax_bits & PDS_CENTURY))
69 return false;
70 tm->tm_year = digit_pair[0] * 100 + digit_pair[1] - 1900;
71 break;
73 case 0:
75 /* Use current year. */
76 time_t now = time (NULL);
77 struct tm *tmp = localtime (&now);
78 if (! tmp)
79 return false;
80 tm->tm_year = tmp->tm_year;
82 break;
84 default:
85 assume (false);
88 return true;
91 static bool
92 posix_time_parse (struct tm *tm, const char *s, unsigned int syntax_bits)
94 const char *dot = NULL;
95 int pair[6];
97 idx_t s_len = strlen (s);
98 idx_t len = s_len;
100 if (syntax_bits & PDS_SECONDS)
102 dot = strchr (s, '.');
103 if (dot)
105 len = dot - s;
106 if (s_len - len != 3)
107 return false;
111 if (! (8 <= len && len <= 12 && len % 2 == 0))
112 return false;
114 for (idx_t i = 0; i < len; i++)
115 if (!c_isdigit (s[i]))
116 return false;
118 len /= 2;
119 for (idx_t i = 0; i < len; i++)
120 pair[i] = 10 * (s[2*i] - '0') + s[2*i + 1] - '0';
122 int *p = pair;
123 if (! (syntax_bits & PDS_TRAILING_YEAR))
125 if (! year (tm, p, len - 4, syntax_bits))
126 return false;
127 p += len - 4;
128 len = 4;
131 /* Handle 8 digits worth of 'MMDDhhmm'. */
132 tm->tm_mon = *p++ - 1;
133 tm->tm_mday = *p++;
134 tm->tm_hour = *p++;
135 tm->tm_min = *p++;
136 len -= 4;
138 /* Handle any trailing year. */
139 if (syntax_bits & PDS_TRAILING_YEAR)
141 if (! year (tm, p, len, syntax_bits))
142 return false;
145 /* Handle seconds. */
146 if (!dot)
147 tm->tm_sec = 0;
148 else if (c_isdigit (dot[1]) && c_isdigit (dot[2]))
149 tm->tm_sec = 10 * (dot[1] - '0') + dot[2] - '0';
150 else
151 return false;
153 return true;
156 /* Parse a POSIX-style date, returning true if successful. */
158 bool
159 posixtime (time_t *p, const char *s, unsigned int syntax_bits)
161 struct tm tm0;
162 bool leapsec = false;
164 if (! posix_time_parse (&tm0, s, syntax_bits))
165 return false;
167 while (true)
169 struct tm tm1;
170 tm1.tm_sec = tm0.tm_sec;
171 tm1.tm_min = tm0.tm_min;
172 tm1.tm_hour = tm0.tm_hour;
173 tm1.tm_mday = tm0.tm_mday;
174 tm1.tm_mon = tm0.tm_mon;
175 tm1.tm_year = tm0.tm_year;
176 tm1.tm_wday = -1;
177 tm1.tm_isdst = -1;
178 time_t t = mktime (&tm1);
180 if (tm1.tm_wday < 0)
181 return false;
183 /* Reject dates like "September 31" and times like "25:61".
184 However, allow a seconds count of 60 even in time zones that do
185 not support leap seconds, treating it as the following second;
186 POSIX requires this. */
187 if (! ((tm0.tm_year ^ tm1.tm_year)
188 | (tm0.tm_mon ^ tm1.tm_mon)
189 | (tm0.tm_mday ^ tm1.tm_mday)
190 | (tm0.tm_hour ^ tm1.tm_hour)
191 | (tm0.tm_min ^ tm1.tm_min)
192 | (tm0.tm_sec ^ tm1.tm_sec)))
194 if (ckd_add (&t, t, +leapsec))
195 return false;
196 *p = t;
197 return true;
200 /* Any mismatch without 60 in the tm_sec field is invalid. */
201 if (tm0.tm_sec != 60)
202 return false;
204 /* Allow times like 01:35:60 or 23:59:60. */
205 tm0.tm_sec = 59;
206 leapsec = true;