xref: /freebsd/crypto/heimdal/lib/roken/strptime.c (revision 6a068746777241722b2b32c5d0bc443a2a64d80b)
1b528cefcSMark Murray /*
2*ae771770SStanislav Sedov  * Copyright (c) 1999, 2003, 2005 Kungliga Tekniska Högskolan
3b528cefcSMark Murray  * (Royal Institute of Technology, Stockholm, Sweden).
4b528cefcSMark Murray  * All rights reserved.
5b528cefcSMark Murray  *
6b528cefcSMark Murray  * Redistribution and use in source and binary forms, with or without
7b528cefcSMark Murray  * modification, are permitted provided that the following conditions
8b528cefcSMark Murray  * are met:
9b528cefcSMark Murray  *
10b528cefcSMark Murray  * 1. Redistributions of source code must retain the above copyright
11b528cefcSMark Murray  *    notice, this list of conditions and the following disclaimer.
12b528cefcSMark Murray  *
13b528cefcSMark Murray  * 2. Redistributions in binary form must reproduce the above copyright
14b528cefcSMark Murray  *    notice, this list of conditions and the following disclaimer in the
15b528cefcSMark Murray  *    documentation and/or other materials provided with the distribution.
16b528cefcSMark Murray  *
17b528cefcSMark Murray  * 3. Neither the name of KTH nor the names of its contributors may be
18b528cefcSMark Murray  *    used to endorse or promote products derived from this software without
19b528cefcSMark Murray  *    specific prior written permission.
20b528cefcSMark Murray  *
21b528cefcSMark Murray  * THIS SOFTWARE IS PROVIDED BY KTH AND ITS CONTRIBUTORS ``AS IS'' AND ANY
22b528cefcSMark Murray  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23b528cefcSMark Murray  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
24b528cefcSMark Murray  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL KTH OR ITS CONTRIBUTORS BE
25b528cefcSMark Murray  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26b528cefcSMark Murray  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27b528cefcSMark Murray  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
28b528cefcSMark Murray  * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
29b528cefcSMark Murray  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
30b528cefcSMark Murray  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
31b528cefcSMark Murray  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
32b528cefcSMark Murray 
33b528cefcSMark Murray #include <config.h>
34*ae771770SStanislav Sedov #include "roken.h"
35c19800e8SDoug Rabson #ifdef TEST_STRPFTIME
36c19800e8SDoug Rabson #include "strpftime-test.h"
37c19800e8SDoug Rabson #endif
38b528cefcSMark Murray #include <ctype.h>
39b528cefcSMark Murray 
40b528cefcSMark Murray static const char *abb_weekdays[] = {
41b528cefcSMark Murray     "Sun",
42b528cefcSMark Murray     "Mon",
43b528cefcSMark Murray     "Tue",
44b528cefcSMark Murray     "Wed",
45b528cefcSMark Murray     "Thu",
46b528cefcSMark Murray     "Fri",
47b528cefcSMark Murray     "Sat",
48b528cefcSMark Murray     NULL
49b528cefcSMark Murray };
50b528cefcSMark Murray 
51b528cefcSMark Murray static const char *full_weekdays[] = {
52b528cefcSMark Murray     "Sunday",
53b528cefcSMark Murray     "Monday",
54b528cefcSMark Murray     "Tuesday",
55b528cefcSMark Murray     "Wednesday",
56b528cefcSMark Murray     "Thursday",
57b528cefcSMark Murray     "Friday",
58b528cefcSMark Murray     "Saturday",
59b528cefcSMark Murray     NULL
60b528cefcSMark Murray };
61b528cefcSMark Murray 
62b528cefcSMark Murray static const char *abb_month[] = {
63b528cefcSMark Murray     "Jan",
64b528cefcSMark Murray     "Feb",
65b528cefcSMark Murray     "Mar",
66b528cefcSMark Murray     "Apr",
67b528cefcSMark Murray     "May",
68b528cefcSMark Murray     "Jun",
69b528cefcSMark Murray     "Jul",
70b528cefcSMark Murray     "Aug",
71b528cefcSMark Murray     "Sep",
72b528cefcSMark Murray     "Oct",
73b528cefcSMark Murray     "Nov",
74b528cefcSMark Murray     "Dec",
75b528cefcSMark Murray     NULL
76b528cefcSMark Murray };
77b528cefcSMark Murray 
78b528cefcSMark Murray static const char *full_month[] = {
79b528cefcSMark Murray     "January",
80b528cefcSMark Murray     "February",
81c19800e8SDoug Rabson     "March",
82b528cefcSMark Murray     "April",
83b528cefcSMark Murray     "May",
84b528cefcSMark Murray     "June",
85b528cefcSMark Murray     "July",
86b528cefcSMark Murray     "August",
87b528cefcSMark Murray     "September",
88b528cefcSMark Murray     "October",
89b528cefcSMark Murray     "November",
90b528cefcSMark Murray     "December",
91b528cefcSMark Murray     NULL,
92b528cefcSMark Murray };
93b528cefcSMark Murray 
94b528cefcSMark Murray static const char *ampm[] = {
95b528cefcSMark Murray     "am",
96b528cefcSMark Murray     "pm",
97b528cefcSMark Murray     NULL
98b528cefcSMark Murray };
99b528cefcSMark Murray 
100b528cefcSMark Murray /*
101b528cefcSMark Murray  * Try to match `*buf' to one of the strings in `strs'.  Return the
102b528cefcSMark Murray  * index of the matching string (or -1 if none).  Also advance buf.
103b528cefcSMark Murray  */
104b528cefcSMark Murray 
105b528cefcSMark Murray static int
match_string(const char ** buf,const char ** strs)106b528cefcSMark Murray match_string (const char **buf, const char **strs)
107b528cefcSMark Murray {
108b528cefcSMark Murray     int i = 0;
109b528cefcSMark Murray 
110b528cefcSMark Murray     for (i = 0; strs[i] != NULL; ++i) {
111b528cefcSMark Murray 	int len = strlen (strs[i]);
112b528cefcSMark Murray 
113b528cefcSMark Murray 	if (strncasecmp (*buf, strs[i], len) == 0) {
114b528cefcSMark Murray 	    *buf += len;
115b528cefcSMark Murray 	    return i;
116b528cefcSMark Murray 	}
117b528cefcSMark Murray     }
118b528cefcSMark Murray     return -1;
119b528cefcSMark Murray }
120b528cefcSMark Murray 
121b528cefcSMark Murray /*
122c19800e8SDoug Rabson  * Try to match `*buf' to at the most `n' characters and return the
123c19800e8SDoug Rabson  * resulting number in `num'. Returns 0 or an error.  Also advance
124c19800e8SDoug Rabson  * buf.
125c19800e8SDoug Rabson  */
126c19800e8SDoug Rabson 
127c19800e8SDoug Rabson static int
parse_number(const char ** buf,int n,int * num)128c19800e8SDoug Rabson parse_number (const char **buf, int n, int *num)
129c19800e8SDoug Rabson {
130c19800e8SDoug Rabson     char *s, *str;
131c19800e8SDoug Rabson     int i;
132c19800e8SDoug Rabson 
133c19800e8SDoug Rabson     str = malloc(n + 1);
134c19800e8SDoug Rabson     if (str == NULL)
135c19800e8SDoug Rabson 	return -1;
136c19800e8SDoug Rabson 
137c19800e8SDoug Rabson     /* skip whitespace */
138c19800e8SDoug Rabson     for (; **buf != '\0' && isspace((unsigned char)(**buf)); (*buf)++)
139c19800e8SDoug Rabson 	;
140c19800e8SDoug Rabson 
141c19800e8SDoug Rabson     /* parse at least n characters */
142c19800e8SDoug Rabson     for (i = 0; **buf != '\0' && i < n && isdigit((unsigned char)(**buf)); i++, (*buf)++)
143c19800e8SDoug Rabson 	str[i] = **buf;
144c19800e8SDoug Rabson     str[i] = '\0';
145c19800e8SDoug Rabson 
146c19800e8SDoug Rabson     *num = strtol (str, &s, 10);
147c19800e8SDoug Rabson     free(str);
148c19800e8SDoug Rabson     if (s == str)
149c19800e8SDoug Rabson 	return -1;
150c19800e8SDoug Rabson 
151c19800e8SDoug Rabson     return 0;
152c19800e8SDoug Rabson }
153c19800e8SDoug Rabson 
154c19800e8SDoug Rabson /*
155c19800e8SDoug Rabson  * tm_year is relative this year
156c19800e8SDoug Rabson  */
157b528cefcSMark Murray 
158b528cefcSMark Murray const int tm_year_base = 1900;
159b528cefcSMark Murray 
160b528cefcSMark Murray /*
161b528cefcSMark Murray  * Return TRUE iff `year' was a leap year.
162b528cefcSMark Murray  */
163b528cefcSMark Murray 
164b528cefcSMark Murray static int
is_leap_year(int year)165b528cefcSMark Murray is_leap_year (int year)
166b528cefcSMark Murray {
167b528cefcSMark Murray     return (year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0);
168b528cefcSMark Murray }
169b528cefcSMark Murray 
170b528cefcSMark Murray /*
171b528cefcSMark Murray  * Return the weekday [0,6] (0 = Sunday) of the first day of `year'
172b528cefcSMark Murray  */
173b528cefcSMark Murray 
174b528cefcSMark Murray static int
first_day(int year)175b528cefcSMark Murray first_day (int year)
176b528cefcSMark Murray {
177b528cefcSMark Murray     int ret = 4;
178b528cefcSMark Murray 
179b528cefcSMark Murray     for (; year > 1970; --year)
180*ae771770SStanislav Sedov 	ret = (ret + (is_leap_year (year) ? 366 : 365)) % 7;
181b528cefcSMark Murray     return ret;
182b528cefcSMark Murray }
183b528cefcSMark Murray 
184b528cefcSMark Murray /*
185b528cefcSMark Murray  * Set `timeptr' given `wnum' (week number [0, 53])
186b528cefcSMark Murray  */
187b528cefcSMark Murray 
188b528cefcSMark Murray static void
set_week_number_sun(struct tm * timeptr,int wnum)189b528cefcSMark Murray set_week_number_sun (struct tm *timeptr, int wnum)
190b528cefcSMark Murray {
191b528cefcSMark Murray     int fday = first_day (timeptr->tm_year + tm_year_base);
192b528cefcSMark Murray 
193b528cefcSMark Murray     timeptr->tm_yday = wnum * 7 + timeptr->tm_wday - fday;
194b528cefcSMark Murray     if (timeptr->tm_yday < 0) {
195b528cefcSMark Murray 	timeptr->tm_wday = fday;
196b528cefcSMark Murray 	timeptr->tm_yday = 0;
197b528cefcSMark Murray     }
198b528cefcSMark Murray }
199b528cefcSMark Murray 
200b528cefcSMark Murray /*
201b528cefcSMark Murray  * Set `timeptr' given `wnum' (week number [0, 53])
202b528cefcSMark Murray  */
203b528cefcSMark Murray 
204b528cefcSMark Murray static void
set_week_number_mon(struct tm * timeptr,int wnum)205b528cefcSMark Murray set_week_number_mon (struct tm *timeptr, int wnum)
206b528cefcSMark Murray {
207b528cefcSMark Murray     int fday = (first_day (timeptr->tm_year + tm_year_base) + 6) % 7;
208b528cefcSMark Murray 
209b528cefcSMark Murray     timeptr->tm_yday = wnum * 7 + (timeptr->tm_wday + 6) % 7 - fday;
210b528cefcSMark Murray     if (timeptr->tm_yday < 0) {
211b528cefcSMark Murray 	timeptr->tm_wday = (fday + 1) % 7;
212b528cefcSMark Murray 	timeptr->tm_yday = 0;
213b528cefcSMark Murray     }
214b528cefcSMark Murray }
215b528cefcSMark Murray 
216b528cefcSMark Murray /*
217b528cefcSMark Murray  * Set `timeptr' given `wnum' (week number [0, 53])
218b528cefcSMark Murray  */
219b528cefcSMark Murray 
220b528cefcSMark Murray static void
set_week_number_mon4(struct tm * timeptr,int wnum)221b528cefcSMark Murray set_week_number_mon4 (struct tm *timeptr, int wnum)
222b528cefcSMark Murray {
223b528cefcSMark Murray     int fday = (first_day (timeptr->tm_year + tm_year_base) + 6) % 7;
224b528cefcSMark Murray     int offset = 0;
225b528cefcSMark Murray 
226b528cefcSMark Murray     if (fday < 4)
227b528cefcSMark Murray 	offset += 7;
228b528cefcSMark Murray 
229b528cefcSMark Murray     timeptr->tm_yday = offset + (wnum - 1) * 7 + timeptr->tm_wday - fday;
230b528cefcSMark Murray     if (timeptr->tm_yday < 0) {
231b528cefcSMark Murray 	timeptr->tm_wday = fday;
232b528cefcSMark Murray 	timeptr->tm_yday = 0;
233b528cefcSMark Murray     }
234b528cefcSMark Murray }
235b528cefcSMark Murray 
236b528cefcSMark Murray /*
237b528cefcSMark Murray  *
238b528cefcSMark Murray  */
239b528cefcSMark Murray 
240*ae771770SStanislav Sedov ROKEN_LIB_FUNCTION char * ROKEN_LIB_CALL
strptime(const char * buf,const char * format,struct tm * timeptr)241b528cefcSMark Murray strptime (const char *buf, const char *format, struct tm *timeptr)
242b528cefcSMark Murray {
243b528cefcSMark Murray     char c;
244b528cefcSMark Murray 
245b528cefcSMark Murray     for (; (c = *format) != '\0'; ++format) {
246b528cefcSMark Murray 	char *s;
247b528cefcSMark Murray 	int ret;
248b528cefcSMark Murray 
249c19800e8SDoug Rabson 	if (isspace ((unsigned char)c)) {
250c19800e8SDoug Rabson 	    while (isspace ((unsigned char)*buf))
251b528cefcSMark Murray 		++buf;
252b528cefcSMark Murray 	} else if (c == '%' && format[1] != '\0') {
253b528cefcSMark Murray 	    c = *++format;
254b528cefcSMark Murray 	    if (c == 'E' || c == 'O')
255b528cefcSMark Murray 		c = *++format;
256b528cefcSMark Murray 	    switch (c) {
257b528cefcSMark Murray 	    case 'A' :
258b528cefcSMark Murray 		ret = match_string (&buf, full_weekdays);
259b528cefcSMark Murray 		if (ret < 0)
260b528cefcSMark Murray 		    return NULL;
261b528cefcSMark Murray 		timeptr->tm_wday = ret;
262b528cefcSMark Murray 		break;
263b528cefcSMark Murray 	    case 'a' :
264b528cefcSMark Murray 		ret = match_string (&buf, abb_weekdays);
265b528cefcSMark Murray 		if (ret < 0)
266b528cefcSMark Murray 		    return NULL;
267b528cefcSMark Murray 		timeptr->tm_wday = ret;
268b528cefcSMark Murray 		break;
269b528cefcSMark Murray 	    case 'B' :
270b528cefcSMark Murray 		ret = match_string (&buf, full_month);
271b528cefcSMark Murray 		if (ret < 0)
272b528cefcSMark Murray 		    return NULL;
273b528cefcSMark Murray 		timeptr->tm_mon = ret;
274b528cefcSMark Murray 		break;
275b528cefcSMark Murray 	    case 'b' :
276b528cefcSMark Murray 	    case 'h' :
277b528cefcSMark Murray 		ret = match_string (&buf, abb_month);
278b528cefcSMark Murray 		if (ret < 0)
279b528cefcSMark Murray 		    return NULL;
280b528cefcSMark Murray 		timeptr->tm_mon = ret;
281b528cefcSMark Murray 		break;
282b528cefcSMark Murray 	    case 'C' :
283c19800e8SDoug Rabson 		if (parse_number(&buf, 2, &ret))
284b528cefcSMark Murray 		    return NULL;
285b528cefcSMark Murray 		timeptr->tm_year = (ret * 100) - tm_year_base;
286b528cefcSMark Murray 		break;
287b528cefcSMark Murray 	    case 'c' :
288b528cefcSMark Murray 		abort ();
289b528cefcSMark Murray 	    case 'D' :		/* %m/%d/%y */
290b528cefcSMark Murray 		s = strptime (buf, "%m/%d/%y", timeptr);
291b528cefcSMark Murray 		if (s == NULL)
292b528cefcSMark Murray 		    return NULL;
293b528cefcSMark Murray 		buf = s;
294b528cefcSMark Murray 		break;
295b528cefcSMark Murray 	    case 'd' :
296b528cefcSMark Murray 	    case 'e' :
297c19800e8SDoug Rabson 		if (parse_number(&buf, 2, &ret))
298b528cefcSMark Murray 		    return NULL;
299b528cefcSMark Murray 		timeptr->tm_mday = ret;
300b528cefcSMark Murray 		break;
301b528cefcSMark Murray 	    case 'H' :
302b528cefcSMark Murray 	    case 'k' :
303c19800e8SDoug Rabson 		if (parse_number(&buf, 2, &ret))
304b528cefcSMark Murray 		    return NULL;
305b528cefcSMark Murray 		timeptr->tm_hour = ret;
306b528cefcSMark Murray 		break;
307b528cefcSMark Murray 	    case 'I' :
308b528cefcSMark Murray 	    case 'l' :
309c19800e8SDoug Rabson 		if (parse_number(&buf, 2, &ret))
310b528cefcSMark Murray 		    return NULL;
311b528cefcSMark Murray 		if (ret == 12)
312b528cefcSMark Murray 		    timeptr->tm_hour = 0;
313b528cefcSMark Murray 		else
314b528cefcSMark Murray 		    timeptr->tm_hour = ret;
315b528cefcSMark Murray 		break;
316b528cefcSMark Murray 	    case 'j' :
317c19800e8SDoug Rabson 		if (parse_number(&buf, 3, &ret))
318c19800e8SDoug Rabson 		    return NULL;
319c19800e8SDoug Rabson 		if (ret == 0)
320b528cefcSMark Murray 		    return NULL;
321b528cefcSMark Murray 		timeptr->tm_yday = ret - 1;
322b528cefcSMark Murray 		break;
323b528cefcSMark Murray 	    case 'm' :
324c19800e8SDoug Rabson 		if (parse_number(&buf, 2, &ret))
325c19800e8SDoug Rabson 		    return NULL;
326c19800e8SDoug Rabson 		if (ret == 0)
327b528cefcSMark Murray 		    return NULL;
328b528cefcSMark Murray 		timeptr->tm_mon = ret - 1;
329b528cefcSMark Murray 		break;
330b528cefcSMark Murray 	    case 'M' :
331c19800e8SDoug Rabson 		if (parse_number(&buf, 2, &ret))
332b528cefcSMark Murray 		    return NULL;
333b528cefcSMark Murray 		timeptr->tm_min = ret;
334b528cefcSMark Murray 		break;
335b528cefcSMark Murray 	    case 'n' :
336c19800e8SDoug Rabson 		while (isspace ((unsigned char)*buf))
337c19800e8SDoug Rabson 		    buf++;
338b528cefcSMark Murray 		break;
339b528cefcSMark Murray 	    case 'p' :
340b528cefcSMark Murray 		ret = match_string (&buf, ampm);
341b528cefcSMark Murray 		if (ret < 0)
342b528cefcSMark Murray 		    return NULL;
343b528cefcSMark Murray 		if (timeptr->tm_hour == 0) {
344b528cefcSMark Murray 		    if (ret == 1)
345b528cefcSMark Murray 			timeptr->tm_hour = 12;
346b528cefcSMark Murray 		} else
347b528cefcSMark Murray 		    timeptr->tm_hour += 12;
348b528cefcSMark Murray 		break;
349b528cefcSMark Murray 	    case 'r' :		/* %I:%M:%S %p */
350b528cefcSMark Murray 		s = strptime (buf, "%I:%M:%S %p", timeptr);
351b528cefcSMark Murray 		if (s == NULL)
352b528cefcSMark Murray 		    return NULL;
353b528cefcSMark Murray 		buf = s;
354b528cefcSMark Murray 		break;
355b528cefcSMark Murray 	    case 'R' :		/* %H:%M */
356b528cefcSMark Murray 		s = strptime (buf, "%H:%M", timeptr);
357b528cefcSMark Murray 		if (s == NULL)
358b528cefcSMark Murray 		    return NULL;
359b528cefcSMark Murray 		buf = s;
360b528cefcSMark Murray 		break;
361b528cefcSMark Murray 	    case 'S' :
362c19800e8SDoug Rabson 		if (parse_number(&buf, 2, &ret))
363b528cefcSMark Murray 		    return NULL;
364b528cefcSMark Murray 		timeptr->tm_sec = ret;
365b528cefcSMark Murray 		break;
366b528cefcSMark Murray 	    case 't' :
367c19800e8SDoug Rabson 		while (isspace ((unsigned char)*buf))
368c19800e8SDoug Rabson 		    buf++;
369b528cefcSMark Murray 		break;
370b528cefcSMark Murray 	    case 'T' :		/* %H:%M:%S */
371b528cefcSMark Murray 	    case 'X' :
372b528cefcSMark Murray 		s = strptime (buf, "%H:%M:%S", timeptr);
373b528cefcSMark Murray 		if (s == NULL)
374b528cefcSMark Murray 		    return NULL;
375b528cefcSMark Murray 		buf = s;
376b528cefcSMark Murray 		break;
377b528cefcSMark Murray 	    case 'u' :
378c19800e8SDoug Rabson 		if (parse_number(&buf, 1, &ret))
379c19800e8SDoug Rabson 		    return NULL;
380c19800e8SDoug Rabson 		if (ret <= 0)
381b528cefcSMark Murray 		    return NULL;
382b528cefcSMark Murray 		timeptr->tm_wday = ret - 1;
383b528cefcSMark Murray 		break;
384b528cefcSMark Murray 	    case 'w' :
385c19800e8SDoug Rabson 		if (parse_number(&buf, 1, &ret))
386b528cefcSMark Murray 		    return NULL;
387b528cefcSMark Murray 		timeptr->tm_wday = ret;
388b528cefcSMark Murray 		break;
389b528cefcSMark Murray 	    case 'U' :
390c19800e8SDoug Rabson 		if (parse_number(&buf, 2, &ret))
391b528cefcSMark Murray 		    return NULL;
392b528cefcSMark Murray 		set_week_number_sun (timeptr, ret);
393b528cefcSMark Murray 		break;
394b528cefcSMark Murray 	    case 'V' :
395c19800e8SDoug Rabson 		if (parse_number(&buf, 2, &ret))
396b528cefcSMark Murray 		    return NULL;
397b528cefcSMark Murray 		set_week_number_mon4 (timeptr, ret);
398b528cefcSMark Murray 		break;
399b528cefcSMark Murray 	    case 'W' :
400c19800e8SDoug Rabson 		if (parse_number(&buf, 2, &ret))
401b528cefcSMark Murray 		    return NULL;
402b528cefcSMark Murray 		set_week_number_mon (timeptr, ret);
403b528cefcSMark Murray 		break;
404b528cefcSMark Murray 	    case 'x' :
405b528cefcSMark Murray 		s = strptime (buf, "%Y:%m:%d", timeptr);
406b528cefcSMark Murray 		if (s == NULL)
407b528cefcSMark Murray 		    return NULL;
408b528cefcSMark Murray 		buf = s;
409b528cefcSMark Murray 		break;
410b528cefcSMark Murray 	    case 'y' :
411c19800e8SDoug Rabson 		if (parse_number(&buf, 2, &ret))
412b528cefcSMark Murray 		    return NULL;
413b528cefcSMark Murray 		if (ret < 70)
414b528cefcSMark Murray 		    timeptr->tm_year = 100 + ret;
415b528cefcSMark Murray 		else
416b528cefcSMark Murray 		    timeptr->tm_year = ret;
417b528cefcSMark Murray 		break;
418b528cefcSMark Murray 	    case 'Y' :
419c19800e8SDoug Rabson 		if (parse_number(&buf, 4, &ret))
420b528cefcSMark Murray 		    return NULL;
421b528cefcSMark Murray 		timeptr->tm_year = ret - tm_year_base;
422b528cefcSMark Murray 		break;
423b528cefcSMark Murray 	    case 'Z' :
424b528cefcSMark Murray 		abort ();
425b528cefcSMark Murray 	    case '\0' :
426b528cefcSMark Murray 		--format;
427b528cefcSMark Murray 		/* FALLTHROUGH */
428b528cefcSMark Murray 	    case '%' :
429b528cefcSMark Murray 		if (*buf == '%')
430b528cefcSMark Murray 		    ++buf;
431b528cefcSMark Murray 		else
432b528cefcSMark Murray 		    return NULL;
433b528cefcSMark Murray 		break;
434b528cefcSMark Murray 	    default :
435b528cefcSMark Murray 		if (*buf == '%' || *++buf == c)
436b528cefcSMark Murray 		    ++buf;
437b528cefcSMark Murray 		else
438b528cefcSMark Murray 		    return NULL;
439b528cefcSMark Murray 		break;
440b528cefcSMark Murray 	    }
441b528cefcSMark Murray 	} else {
442b528cefcSMark Murray 	    if (*buf == c)
443b528cefcSMark Murray 		++buf;
444b528cefcSMark Murray 	    else
445b528cefcSMark Murray 		return NULL;
446b528cefcSMark Murray 	}
447b528cefcSMark Murray     }
448c19800e8SDoug Rabson     return rk_UNCONST(buf);
449b528cefcSMark Murray }
450