1 /*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License, Version 1.0 only
6 * (the "License"). You may not use this file except in compliance
7 * with the License.
8 *
9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10 * or http://www.opensolaris.org/os/licensing.
11 * See the License for the specific language governing permissions
12 * and limitations under the License.
13 *
14 * When distributing Covered Code, include this CDDL HEADER in each
15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16 * If applicable, add the following below this CDDL HEADER, with the
17 * fields enclosed by brackets "[]" replaced with your own identifying
18 * information: Portions Copyright [yyyy] [name of copyright owner]
19 *
20 * CDDL HEADER END
21 */
22 /*
23 * Copyright 1997 Sun Microsystems, Inc. All rights reserved.
24 * Use is subject to license terms.
25 */
26
27 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
28 /* All Rights Reserved */
29
30 #pragma ident "%Z%%M% %I% %E% SMI"
31
32 #if !defined(lint) && defined(SCCSIDS)
33 static char *sccsid = "%Z%%M% %I% %E% SMI";
34 #endif
35
36 #include <ctype.h>
37 #include <locale.h>
38 #include <time.h>
39
40 static char *strmatch(/*char *cp, char *string*/);
41 static char *yearmatch(/*char *cp, char *format, struct tm *tm,
42 int *hadyearp*/);
43 static char *cvtnum(/*char *cp, int *nump*/);
44 static char *skipnws(/*char *format*/);
45
46 extern char *getlocale_time();
47 #define NULL 0
48
49 char *
strptime(buf,format,tm)50 strptime(buf, format, tm)
51 char *buf;
52 char *format;
53 struct tm *tm;
54 {
55 register char *cp, *p;
56 register int c, ch;
57 register int i;
58 register struct dtconv *dtcp;
59 int hadyear;
60
61 (void) getlocale_time();
62 dtcp = localdtconv(); /* get locale's strings */
63
64 cp = buf;
65 while ((c = *format++) != '\0') {
66 if (c == '%') {
67 switch (*format++) {
68
69 case '%': /* Percent sign */
70 if (*cp++ != '%')
71 return (NULL);
72 break;
73
74 case 'a': /* Abbreviated weekday name */
75 case 'A': /* Weekday name */
76 for (i = 0; i < 7; i++) {
77 if ((p = strmatch(cp,
78 dtcp->weekday_names[i],
79 *format)) != NULL
80 || (p = strmatch(cp,
81 dtcp->abbrev_weekday_names[i],
82 *format)) != NULL)
83 goto match_wday;
84 }
85 return (NULL); /* no match */
86
87 match_wday:
88 tm->tm_wday = i;
89 cp = p;
90 break;
91
92 case 'h':
93 case 'b': /* Abbreviated month name */
94 case 'B': /* Month name */
95 for (i = 0; i < 12; i++) {
96 if ((p = strmatch(cp,
97 dtcp->month_names[i],
98 *format)) != NULL
99 || (p = strmatch(cp,
100 dtcp->abbrev_month_names[i],
101 *format)) != NULL)
102 goto match_month;
103 }
104 return (NULL); /* no match */
105
106 match_month:
107 tm->tm_mon = i;
108 cp = p;
109 break;
110
111 case 'c': /* date and time representation */
112 cp = strptime(cp, "%x %X", tm);
113 if (cp == NULL)
114 return (NULL);
115 break;
116
117 case 'C': /* long date and time representation */
118 cp = strptime(cp, dtcp->ldate_format, tm);
119 if (cp == NULL)
120 return (NULL);
121 break;
122
123 case 'd': /* Day of month, with leading zero */
124 case 'e': /* Day of month without leading zero */
125 cp = cvtnum(cp, &tm->tm_mday);
126 if (cp == NULL)
127 return (NULL); /* no digits */
128 if (tm->tm_mday > 31)
129 return (NULL);
130 if ((c = *cp) == '\0'
131 || isspace((unsigned char)c))
132 format = skipnws(format);
133 break;
134
135 case 'D': /* Shorthand for %m/%d/%y */
136 cp = strptime(cp, "%m/%d/%y", tm);
137 if (cp == NULL)
138 return (NULL);
139 break;
140
141 case 'H': /* Hour (24 hour version) */
142 case 'k': /* Hour (24 hour version) */
143 cp = cvtnum(cp, &tm->tm_hour);
144 if (cp == NULL)
145 return (NULL); /* no digits */
146 if (tm->tm_hour > 23)
147 return (NULL);
148 if ((c = *cp) == '\0'
149 || isspace((unsigned char)c))
150 format = skipnws(format);
151 break;
152
153 case 'I': /* Hour (12 hour version) */
154 case 'l': /* Hour (12 hour version) */
155 cp = cvtnum(cp, &tm->tm_hour);
156 if (cp == NULL)
157 return (NULL); /* no digits */
158 if (tm->tm_hour == 12)
159 tm->tm_hour = 0;
160 else if (tm->tm_hour > 11)
161 return (NULL);
162 if ((c = *cp) == '\0'
163 || isspace((unsigned char)c))
164 format = skipnws(format);
165 break;
166
167 case 'j': /* Julian date */
168 cp = cvtnum(cp, &tm->tm_yday);
169 if (cp == NULL)
170 return (NULL); /* no digits */
171 if (tm->tm_yday > 365)
172 return (NULL);
173 break;
174
175 case 'm': /* Month number */
176 cp = cvtnum(cp, &tm->tm_mon);
177 if (cp == NULL)
178 return (NULL); /* no digits */
179 tm->tm_mon--;
180 if (tm->tm_mon < 0 || tm->tm_mon > 11)
181 return (NULL);
182 if ((c = *cp) == '\0'
183 || isspace((unsigned char)c))
184 format = skipnws(format);
185 break;
186
187 case 'M': /* Minute */
188 /*
189 * This is optional; if we're at the end of the
190 * string, or the next character is white
191 * space, don't try to match it.
192 */
193 if ((c = *cp) != '\0'
194 && !isspace((unsigned char)c)) {
195 cp = cvtnum(cp, &tm->tm_min);
196 if (cp == NULL)
197 return (NULL); /* no digits */
198 if (tm->tm_min > 59)
199 return (NULL);
200 }
201 if ((c = *cp) == '\0'
202 || isspace((unsigned char)c))
203 format = skipnws(format);
204 break;
205
206 case 'p': /* AM or PM */
207 if ((p = strmatch(cp, dtcp->am_string,
208 *format)) != NULL) {
209 /*
210 * AM.
211 */
212 if (tm->tm_hour == 12)
213 tm->tm_hour = 0;
214 cp = p;
215 } else if ((p = strmatch(cp, dtcp->pm_string,
216 *format)) != NULL) {
217 /*
218 * PM.
219 */
220 if (tm->tm_hour > 12)
221 return (NULL); /* error */
222 else if (tm->tm_hour != 12)
223 tm->tm_hour += 12;
224 cp = p;
225 }
226 break;
227
228 case 'r': /* Shorthand for %I:%M:%S AM or PM */
229 cp = strptime(cp, "%I:%M:%S %p", tm);
230 if (cp == NULL)
231 return (NULL);
232 break;
233
234 case 'R': /* Time as %H:%M */
235 cp = strptime(cp, "%H:%M", tm);
236 if (cp == NULL)
237 return (NULL);
238 break;
239
240 case 'S': /* Seconds */
241 /*
242 * This is optional; if we're at the end of the
243 * string, or the next character is white
244 * space, don't try to match it.
245 */
246 if ((c = *cp) != '\0'
247 && !isspace((unsigned char)c)) {
248 cp = cvtnum(cp, &tm->tm_sec);
249 if (cp == NULL)
250 return (NULL); /* no digits */
251 if (tm->tm_sec > 59)
252 return (NULL);
253 }
254 if ((c = *cp) == '\0'
255 || isspace((unsigned char)c))
256 format = skipnws(format);
257 break;
258
259 case 'T': /* Shorthand for %H:%M:%S */
260 cp = strptime(cp, "%H:%M:%S", tm);
261 if (cp == NULL)
262 return (NULL);
263 break;
264
265 case 'x': /* Localized date format */
266 cp = strptime(cp, dtcp->sdate_format, tm);
267 if (cp == NULL)
268 return (NULL);
269 break;
270
271 case 'X': /* Localized time format */
272 cp = strptime(cp, dtcp->time_format, tm);
273 if (cp == NULL)
274 return (NULL);
275 break;
276
277 case 'y': /* Year in the form yy */
278 cp = yearmatch(cp, format, tm, &hadyear);
279 if (cp == NULL)
280 return (NULL);
281 if (hadyear) {
282 if (tm->tm_year < 69)
283 tm->tm_year += 100;
284 }
285 return (cp); /* match is complete */
286
287 case 'Y': /* Year in the form ccyy */
288 cp = yearmatch(cp, format, tm, &hadyear);
289 if (cp == NULL)
290 return (NULL);
291 if (hadyear) {
292 tm->tm_year -= 1900;
293 if (tm->tm_year < 0)
294 return (NULL);
295 }
296 return (cp); /* match is complete */
297
298 default:
299 return (NULL); /* unknown conversion */
300 }
301 } else {
302 if (isspace((unsigned char)c)) {
303 while ((ch = *cp++) != '\0'
304 && isspace((unsigned char)ch))
305 ;
306 cp--;
307 } else {
308 if (*cp++ != c)
309 return (NULL);
310 }
311 }
312 }
313 return (cp);
314 }
315
316 /*
317 * Try to match the beginning of the string pointed to by "cp" with the string
318 * pointed to by "string". The match is independent of the case of either
319 * string.
320 *
321 * "termc" is the next character in the format string following the one for
322 * which this match is being done. If the match succeeds, make sure the next
323 * character after the match is either '\0', or that it would match "termc".
324 *
325 * If both matches succeed, return a pointer to the next character after the
326 * first match. Otherwise, return NULL.
327 */
328 static char *
strmatch(cp,string,termc)329 strmatch(cp, string, termc)
330 register char *cp;
331 register char *string;
332 char termc;
333 {
334 register unsigned char c, strc;
335
336 /*
337 * Match the beginning portion of "cp" with "string".
338 */
339 while ((strc = *string++) != '\0') {
340 c = *cp++;
341 if (isupper(c))
342 c = tolower(c);
343 if (isupper(strc))
344 strc = tolower(strc);
345 if (c != strc)
346 return (NULL);
347 }
348
349 if ((c = *cp) != '\0') {
350 if (isspace((unsigned char)termc)) {
351 if (!isspace(c))
352 return (NULL);
353 } else {
354 if (c != (unsigned char)termc)
355 return (NULL);
356 }
357 }
358 return (cp);
359 }
360
361 /*
362 * Try to match a %y or %Y specification.
363 * If it matches, try matching the rest of the format. If it succeeds, just
364 * return. Otherwise, try backing the scan up, ignoring the %y/%Y and any
365 * following non-white-space string. If that succeeds, just return. (This
366 * permits a missing year to be detected if it's at the beginning of a date, as
367 * well as if it's at the end of a date, so that formats such as "%Y/%m/%d" can
368 * match "3/14" and default the year.)
369 *
370 * Set "*hadyearp" to indicate whether a year was specified or not.
371 */
372 static char *
yearmatch(cp,format,tm,hadyearp)373 yearmatch(cp, format, tm, hadyearp)
374 register char *cp;
375 char *format;
376 struct tm *tm;
377 int *hadyearp;
378 {
379 register int c;
380 char *savecp;
381 int saveyear;
382
383 /*
384 * This is optional; if we're at the end of the
385 * string, or the next character is white
386 * space, don't try to match it.
387 */
388 if ((c = *cp) != '\0' && !isspace((unsigned char)c)) {
389 savecp = cp;
390 saveyear = tm->tm_year;
391 cp = cvtnum(cp, &tm->tm_year);
392 if (cp == NULL)
393 return (NULL); /* no digits */
394 if ((c = *cp) == '\0'
395 || isspace((unsigned char)c))
396 format = skipnws(format);
397
398 /*
399 * Year can also be optional if it's at
400 * the *beginning* of a date. We check
401 * this by trying to parse the rest of
402 * the date here. If we succeed, OK;
403 * otherwise, we skip over the %y and
404 * try again.
405 */
406 cp = strptime(cp, format, tm);
407 if (cp != NULL)
408 *hadyearp = 1;
409 else {
410 *hadyearp = 0;
411 cp = savecp;
412 format = skipnws(format);
413 tm->tm_year = saveyear;
414 cp = strptime(cp, format, tm);
415 }
416 } else {
417 *hadyearp = 0;
418 if ((c = *cp) == '\0'
419 || isspace((unsigned char)c))
420 format = skipnws(format);
421 cp = strptime(cp, format, tm);
422 }
423
424 return (cp);
425 }
426
427 /*
428 * Try to match a (decimal) number in the string pointed to by "cp".
429 * If the match succeeds, store the result in the "int" pointed to by "nump"
430 * and return a pointer to the character following the number in the string.
431 * If it fails, return NULL.
432 */
433 static char *
cvtnum(cp,nump)434 cvtnum(cp, nump)
435 register char *cp;
436 int *nump;
437 {
438 register int c;
439 register int i;
440
441 c = (unsigned char)*cp++;
442 if (!isdigit(c))
443 return (NULL); /* no digits */
444 i = 0;
445 do {
446 i = i*10 + c - '0';
447 c = (unsigned char)*cp++;
448 } while (isdigit(c));
449 *nump = i;
450 return (cp - 1);
451 }
452
453 /*
454 * If a format item (such as %H, hours) is followed by a non-white-space
455 * character other than "%", and the part of the string that matched the format
456 * item is followed by white space, the string of non-white-space,
457 * non-format-item characters following that format item may be omitted.
458 */
459 static char *
skipnws(format)460 skipnws(format)
461 register char *format;
462 {
463 register char c;
464
465 /*
466 * Skip over non-white-space, non-digit characters. "%" is special.
467 */
468 while ((c = *format) != '\0' && !isspace((unsigned char)c)) {
469 if (c == '%') {
470 /*
471 * This is a format item. If it's %%, skip it as
472 * that's a non-white space, non-digit character.
473 */
474 if (*(format + 1) == '%')
475 format++; /* skip % */
476 else
477 break;
478 }
479 format++;
480 }
481
482 return (format);
483 }
484