summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorTodd C. Miller <millert@cvs.openbsd.org>2012-01-16 17:42:46 +0000
committerTodd C. Miller <millert@cvs.openbsd.org>2012-01-16 17:42:46 +0000
commit259019888a5790818d4076aa3ef276769b07e04a (patch)
tree5aff08bc21aa0ce15a2666d2dd06cb2571bcc010 /lib
parentf13b98e11b551f366555dd66f8a787e0fa3f9056 (diff)
POSIX indicates that some fields should be computed even if not
explicitly set. We can compute tm_yday, tm_wday, tm_mon and tm_mday based on the values that were specified if possible. Some logic borrowed from localtime.c. OK espie@ deraadt@
Diffstat (limited to 'lib')
-rw-r--r--lib/libc/time/strptime.c70
1 files changed, 68 insertions, 2 deletions
diff --git a/lib/libc/time/strptime.c b/lib/libc/time/strptime.c
index d3398d12aa8..4e7eb4ebf26 100644
--- a/lib/libc/time/strptime.c
+++ b/lib/libc/time/strptime.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: strptime.c,v 1.14 2011/01/19 16:50:14 landry Exp $ */
+/* $OpenBSD: strptime.c,v 1.15 2012/01/16 17:42:45 millert Exp $ */
/* $NetBSD: strptime.c,v 1.12 1998/01/20 21:39:40 mycroft Exp $ */
/*-
@@ -46,6 +46,15 @@
#define _ALT_O 0x02
#define _LEGAL_ALT(x) { if (alt_format & ~(x)) return (0); }
+/*
+ * We keep track of some of the fields we set in order to compute missing ones.
+ */
+#define FIELD_TM_MON (1 << 0)
+#define FIELD_TM_MDAY (1 << 1)
+#define FIELD_TM_WDAY (1 << 2)
+#define FIELD_TM_YDAY (1 << 3)
+#define FIELD_TM_YEAR (1 << 4)
+
static char gmt[] = { "GMT" };
#ifdef TM_ZONE
static char utc[] = { "UTC" };
@@ -58,7 +67,13 @@ static const char * const nadt[5] = {
"EDT", "CDT", "MDT", "PDT", "\0\0\0"
};
+static const int mon_lengths[2][MONSPERYEAR] = {
+ { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
+ { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
+};
+
static int _conv_num(const unsigned char **, int *, int, int);
+static int leaps_thru_end_of(const int y);
static char *_strptime(const char *, const char *, struct tm *, int);
static const u_char *_find_string(const u_char *, int *, const char * const *,
const char * const *, int);
@@ -78,11 +93,12 @@ _strptime(const char *buf, const char *fmt, struct tm *tm, int initialize)
size_t len;
int alt_format, i, offs;
int neg = 0;
- static int century, relyear;
+ static int century, relyear, fields;
if (initialize) {
century = TM_YEAR_BASE;
relyear = -1;
+ fields = 0;
}
bp = (unsigned char *)buf;
@@ -200,6 +216,7 @@ literal:
tm->tm_wday = i;
bp += len;
+ fields |= FIELD_TM_WDAY;
break;
case 'B': /* The month, using the locale's form. */
@@ -224,6 +241,7 @@ literal:
tm->tm_mon = i;
bp += len;
+ fields |= FIELD_TM_MON;
break;
case 'C': /* The century number. */
@@ -239,6 +257,7 @@ literal:
_LEGAL_ALT(_ALT_O);
if (!(_conv_num(&bp, &tm->tm_mday, 1, 31)))
return (NULL);
+ fields |= FIELD_TM_MDAY;
break;
case 'k': /* The hour (24-hour clock representation). */
@@ -264,6 +283,7 @@ literal:
if (!(_conv_num(&bp, &tm->tm_yday, 1, 366)))
return (NULL);
tm->tm_yday--;
+ fields |= FIELD_TM_YDAY;
break;
case 'M': /* The minute. */
@@ -277,6 +297,7 @@ literal:
if (!(_conv_num(&bp, &tm->tm_mon, 1, 12)))
return (NULL);
tm->tm_mon--;
+ fields |= FIELD_TM_MON;
break;
case 'p': /* The locale's equivalent of AM/PM. */
@@ -330,6 +351,7 @@ literal:
_LEGAL_ALT(_ALT_O);
if (!(_conv_num(&bp, &tm->tm_wday, 0, 6)))
return (NULL);
+ fields |= FIELD_TM_WDAY;
break;
case 'u': /* The day of week, monday = 1. */
@@ -337,6 +359,7 @@ literal:
if (!(_conv_num(&bp, &i, 1, 7)))
return (NULL);
tm->tm_wday = i % 7;
+ fields |= FIELD_TM_WDAY;
continue;
case 'g': /* The year corresponding to the ISO week
@@ -366,6 +389,7 @@ literal:
relyear = -1;
tm->tm_year = i - TM_YEAR_BASE;
+ fields |= FIELD_TM_YEAR;
break;
case 'y': /* The year within the century (2 digits). */
@@ -562,6 +586,41 @@ literal:
} else {
tm->tm_year = relyear + century - TM_YEAR_BASE;
}
+ fields |= FIELD_TM_YEAR;
+ }
+
+ /* Compute some missing values when possible. */
+ if (fields & FIELD_TM_YEAR) {
+ const int year = tm->tm_year + TM_YEAR_BASE;
+ const int *mon_lens = mon_lengths[isleap(year)];
+ if (!(fields & FIELD_TM_YDAY) &&
+ (fields & (FIELD_TM_MON|FIELD_TM_MDAY))) {
+ tm->tm_yday = tm->tm_mday - 1;
+ for (i = 0; i < tm->tm_mon; i++)
+ tm->tm_yday += mon_lens[i];
+ fields |= FIELD_TM_YDAY;
+ }
+ if (fields & FIELD_TM_YDAY) {
+ int days = tm->tm_yday;
+ if (!(fields & FIELD_TM_WDAY)) {
+ tm->tm_wday = EPOCH_WDAY +
+ ((year - EPOCH_YEAR) % DAYSPERWEEK) *
+ (DAYSPERNYEAR % DAYSPERWEEK) +
+ leaps_thru_end_of(year - 1) -
+ leaps_thru_end_of(EPOCH_YEAR - 1) +
+ tm->tm_yday;
+ tm->tm_wday %= DAYSPERWEEK;
+ if (tm->tm_wday < 0)
+ tm->tm_wday += DAYSPERWEEK;
+ }
+ if (!(fields & FIELD_TM_MON)) {
+ tm->tm_mon = 0;
+ while (tm->tm_mon < MONSPERYEAR && days >= mon_lens[tm->tm_mon])
+ days -= mon_lens[tm->tm_mon++];
+ }
+ if (!(fields & FIELD_TM_MDAY))
+ tm->tm_mday = days + 1;
+ }
}
return ((char *)bp);
@@ -612,3 +671,10 @@ _find_string(const u_char *bp, int *tgt, const char * const *n1,
/* Nothing matched */
return NULL;
}
+
+static int
+leaps_thru_end_of(const int y)
+{
+ return (y >= 0) ? (y / 4 - y / 100 + y / 400) :
+ -(leaps_thru_end_of(-(y + 1)) + 1);
+}