+int
+isotime_p (const char *string)
+{
+ const char *s;
+ int i;
+
+ if (!*string)
+ return 0;
+ for (s=string, i=0; i < 8; i++, s++)
+ if (!digitp (s))
+ return 0;
+ if (*s != 'T')
+ return 0;
+ for (s++, i=9; i < 15; i++, s++)
+ if (!digitp (s))
+ return 0;
+ if (*s == 'Z')
+ s++;
+ if ( !(!*s || (isascii (*s) && isspace(*s)) || *s == ':' || *s == ','))
+ return 0; /* Wrong delimiter. */
+
+ return 1;
+}
+
+
+/* Scan a string and return true if the string represents the human
+ readable format of an ISO time. This format is:
+ yyyy-mm-dd[ hh[:mm[:ss]]]
+ Scanning stops at the second space or at a comma. If DATE_ONLY is
+ true the time part is not expected and the scanning stops at the
+ first space or at a comma. */
+int
+isotime_human_p (const char *string, int date_only)
+{
+ const char *s;
+ int i;
+
+ if (!*string)
+ return 0;
+ for (s=string, i=0; i < 4; i++, s++)
+ if (!digitp (s))
+ return 0;
+ if (*s != '-')
+ return 0;
+ s++;
+ if (!digitp (s) || !digitp (s+1) || s[2] != '-')
+ return 0;
+ i = atoi_2 (s);
+ if (i < 1 || i > 12)
+ return 0;
+ s += 3;
+ if (!digitp (s) || !digitp (s+1))
+ return 0;
+ i = atoi_2 (s);
+ if (i < 1 || i > 31)
+ return 0;
+ s += 2;
+ if (!*s || *s == ',')
+ return 1; /* Okay; only date given. */
+ if (!spacep (s))
+ return 0;
+ if (date_only)
+ return 1; /* Okay; only date was requested. */
+ s++;
+ if (spacep (s))
+ return 1; /* Okay, second space stops scanning. */
+ if (!digitp (s) || !digitp (s+1))
+ return 0;
+ i = atoi_2 (s);
+ if (i < 0 || i > 23)
+ return 0;
+ s += 2;
+ if (!*s || *s == ',')
+ return 1; /* Okay; only date and hour given. */
+ if (*s != ':')
+ return 0;
+ s++;
+ if (!digitp (s) || !digitp (s+1))
+ return 0;
+ i = atoi_2 (s);
+ if (i < 0 || i > 59)
+ return 0;
+ s += 2;
+ if (!*s || *s == ',')
+ return 1; /* Okay; only date, hour and minute given. */
+ if (*s != ':')
+ return 0;
+ s++;
+ if (!digitp (s) || !digitp (s+1))
+ return 0;
+ i = atoi_2 (s);
+ if (i < 0 || i > 60)
+ return 0;
+ s += 2;
+ if (!*s || *s == ',' || spacep (s))
+ return 1; /* Okay; date, hour and minute and second given. */
+
+ return 0; /* Unexpected delimiter. */
+}
+
+/* Convert a standard isotime or a human readable variant into an
+ isotime structure. The allowed formats are those described by
+ isotime_p and isotime_human_p. The function returns 0 on failure
+ or the length of the scanned string on success. */
+size_t
+string2isotime (gnupg_isotime_t atime, const char *string)
+{
+ gnupg_isotime_t dummyatime;
+
+ if (!atime)
+ atime = dummyatime;
+
+ atime[0] = 0;
+ if (isotime_p (string))
+ {
+ memcpy (atime, string, 15);
+ atime[15] = 0;
+ return 15;
+ }
+ if (!isotime_human_p (string, 0))
+ return 0;
+ atime[0] = string[0];
+ atime[1] = string[1];
+ atime[2] = string[2];
+ atime[3] = string[3];
+ atime[4] = string[5];
+ atime[5] = string[6];
+ atime[6] = string[8];
+ atime[7] = string[9];
+ atime[8] = 'T';
+ memset (atime+9, '0', 6);
+ atime[15] = 0;
+ if (!spacep (string+10))
+ return 10;
+ if (spacep (string+11))
+ return 11; /* As per def, second space stops scanning. */
+ atime[9] = string[11];
+ atime[10] = string[12];
+ if (string[13] != ':')
+ return 13;
+ atime[11] = string[14];
+ atime[12] = string[15];
+ if (string[16] != ':')
+ return 16;
+ atime[13] = string[17];
+ atime[14] = string[18];
+ return 19;
+}
+
+
+/* Scan an ISO timestamp and return an Epoch based timestamp. The
+ only supported format is "yyyymmddThhmmss[Z]" delimited by white
+ space, nul, a colon or a comma. Returns (time_t)(-1) for an
+ invalid string. */
+time_t
+isotime2epoch (const char *string)
+{
+ int year, month, day, hour, minu, sec;
+ struct tm tmbuf;
+
+ if (!isotime_p (string))
+ return (time_t)(-1);
+
+ year = atoi_4 (string);
+ month = atoi_2 (string + 4);
+ day = atoi_2 (string + 6);
+ hour = atoi_2 (string + 9);
+ minu = atoi_2 (string + 11);
+ sec = atoi_2 (string + 13);
+
+ /* Basic checks. */
+ if (year < 1970 || month < 1 || month > 12 || day < 1 || day > 31
+ || hour > 23 || minu > 59 || sec > 61 )
+ return (time_t)(-1);
+
+ memset (&tmbuf, 0, sizeof tmbuf);
+ tmbuf.tm_sec = sec;
+ tmbuf.tm_min = minu;
+ tmbuf.tm_hour = hour;
+ tmbuf.tm_mday = day;
+ tmbuf.tm_mon = month-1;
+ tmbuf.tm_year = year - 1900;
+ tmbuf.tm_isdst = -1;
+ return timegm (&tmbuf);
+}
+
+
+/* Convert an Epoch time to an iso time stamp. */
+void
+epoch2isotime (gnupg_isotime_t timebuf, time_t atime)
+{
+ if (atime == (time_t)(-1))
+ *timebuf = 0;
+ else
+ {
+ struct tm *tp;
+#ifdef HAVE_GMTIME_R
+ struct tm tmbuf;
+
+ tp = gmtime_r (&atime, &tmbuf);
+#else
+ tp = gmtime (&atime);
+#endif
+ snprintf (timebuf, 16, "%04d%02d%02dT%02d%02d%02d",
+ 1900 + tp->tm_year, tp->tm_mon+1, tp->tm_mday,
+ tp->tm_hour, tp->tm_min, tp->tm_sec);
+ }
+}
+
+
+/* Parse a short ISO date string (YYYY-MM-DD) into a TM structure.
+ Returns 0 on success. */
+int
+isodate_human_to_tm (const char *string, struct tm *t)
+{
+ int year, month, day;
+
+ if (!isotime_human_p (string, 1))
+ return -1;
+
+ year = atoi_4 (string);
+ month = atoi_2 (string + 5);
+ day = atoi_2 (string + 8);
+
+ /* Basic checks. */
+ if (year < 1970 || month < 1 || month > 12 || day < 1 || day > 31)
+ return -1;
+
+ memset (t, 0, sizeof *t);
+ t->tm_sec = 0;
+ t->tm_min = 0;
+ t->tm_hour = 0;
+ t->tm_mday = day;
+ t->tm_mon = month-1;
+ t->tm_year = year - 1900;
+ t->tm_isdst = -1;
+ return 0;
+}
+
+
+/* This function is a copy of gpgme/src/conversion.c:_gpgme_timegm.
+ If you change it, then update the other one too. */
+#ifdef HAVE_W32_SYSTEM
+static time_t
+_win32_timegm (struct tm *tm)
+{
+ /* This one is thread safe. */
+ SYSTEMTIME st;
+ FILETIME ft;
+ unsigned long long cnsecs;
+
+ st.wYear = tm->tm_year + 1900;
+ st.wMonth = tm->tm_mon + 1;
+ st.wDay = tm->tm_mday;
+ st.wHour = tm->tm_hour;
+ st.wMinute = tm->tm_min;
+ st.wSecond = tm->tm_sec;
+ st.wMilliseconds = 0; /* Not available. */
+ st.wDayOfWeek = 0; /* Ignored. */
+
+ /* System time is UTC thus the conversion is pretty easy. */
+ if (!SystemTimeToFileTime (&st, &ft))
+ {
+ gpg_err_set_errno (EINVAL);
+ return (time_t)(-1);
+ }
+
+ cnsecs = (((unsigned long long)ft.dwHighDateTime << 32)
+ | ft.dwLowDateTime);
+ cnsecs -= 116444736000000000ULL; /* The filetime epoch is 1601-01-01. */
+ return (time_t)(cnsecs / 10000000ULL);
+}
+#endif
+
+
+/* Parse the string TIMESTAMP into a time_t. The string may either be
+ seconds since Epoch or in the ISO 8601 format like
+ "20390815T143012". Returns 0 for an empty string or seconds since
+ Epoch. Leading spaces are skipped. If ENDP is not NULL, it will
+ point to the next non-parsed character in TIMESTRING.
+
+ This function is a copy of
+ gpgme/src/conversion.c:_gpgme_parse_timestamp. If you change it,
+ then update the other one too. */
+time_t
+parse_timestamp (const char *timestamp, char **endp)
+{
+ /* Need to skip leading spaces, because that is what strtoul does
+ but not our ISO 8601 checking code. */
+ while (*timestamp && *timestamp== ' ')
+ timestamp++;
+ if (!*timestamp)
+ return 0;
+
+ if (strlen (timestamp) >= 15 && timestamp[8] == 'T')
+ {
+ struct tm buf;
+ int year;
+
+ year = atoi_4 (timestamp);
+ if (year < 1900)
+ return (time_t)(-1);
+
+ if (endp)
+ *endp = (char*)(timestamp + 15);
+
+ /* Fixme: We would better use a configure test to see whether
+ mktime can handle dates beyond 2038. */
+ if (sizeof (time_t) <= 4 && year >= 2038)
+ return (time_t)2145914603; /* 2037-12-31 23:23:23 */
+
+ memset (&buf, 0, sizeof buf);
+ buf.tm_year = year - 1900;
+ buf.tm_mon = atoi_2 (timestamp+4) - 1;
+ buf.tm_mday = atoi_2 (timestamp+6);
+ buf.tm_hour = atoi_2 (timestamp+9);
+ buf.tm_min = atoi_2 (timestamp+11);
+ buf.tm_sec = atoi_2 (timestamp+13);
+
+#ifdef HAVE_W32_SYSTEM
+ return _win32_timegm (&buf);
+#else
+#ifdef HAVE_TIMEGM
+ return timegm (&buf);
+#else
+ {
+ time_t tim;
+
+ putenv ("TZ=UTC");
+ tim = mktime (&buf);
+#ifdef __GNUC__
+#warning fixme: we must somehow reset TZ here. It is not threadsafe anyway.
+#endif
+ return tim;
+ }
+#endif /* !HAVE_TIMEGM */
+#endif /* !HAVE_W32_SYSTEM */
+ }
+ else
+ return (time_t)strtoul (timestamp, endp, 10);
+}
+
+
+