common: Add new function gnupg_gmtime.
[gnupg.git] / common / gettime.c
index 6bdf3c5..115f725 100644 (file)
@@ -71,6 +71,29 @@ gnupg_get_time ()
 }
 
 
+/* Wrapper around gmtime_r.
+
+   On systems without gmtime_r this implementation works within gnupg
+   because we use only one thread a time.  FIXME: An independent
+   library may use gmtime in one of its own thread (or via
+   npth_enter/npth_leave) - in this case we run into a problem.  The
+   solution would be to use a mutex here.  */
+struct tm *
+gnupg_gmtime (const time_t *timep, struct tm *result)
+{
+#ifdef HAVE_GMTIME_R
+  return gmtime_r (timep, result);
+#else
+  struct tm *tp;
+
+  tp = gmtime (timep);
+  if (tp)
+    memcpy (result, tp, sizeof *result);
+  return tp;
+#endif
+}
+
+
 /* Return the current time (possibly faked) in ISO format. */
 void
 gnupg_get_isotime (gnupg_isotime_t timebuf)
@@ -82,16 +105,15 @@ gnupg_get_isotime (gnupg_isotime_t timebuf)
   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);
+      tp = gnupg_gmtime (&atime, &tmbuf);
+      if (!tp)
+        *timebuf = 0;
+      else
+        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);
     }
 }
 
@@ -216,9 +238,11 @@ isotime_p (const char *string)
 /* 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.  */
+   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)
+isotime_human_p (const char *string, int date_only)
 {
   const char *s;
   int i;
@@ -247,6 +271,8 @@ isotime_human_p (const char *string)
     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.  */
@@ -303,7 +329,7 @@ string2isotime (gnupg_isotime_t atime, const char *string)
       atime[15] = 0;
       return 15;
     }
-  if (!isotime_human_p (string))
+  if (!isotime_human_p (string, 0))
     return 0;
   atime[0] = string[0];
   atime[1] = string[1];
@@ -393,6 +419,138 @@ epoch2isotime (gnupg_isotime_t timebuf, time_t atime)
 }
 
 
+/* 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);
+}
+
 
 
 u32
@@ -430,6 +588,45 @@ strtimevalue( u32 value )
 }
 
 
+
+/* Return a malloced string with the time elapsed between NOW and
+   SINCE.  May return NULL on error. */
+char *
+elapsed_time_string (time_t since, time_t now)
+{
+  char *result;
+  double diff;
+  unsigned long value;
+  unsigned int days, hours, minutes, seconds;
+
+  if (!now)
+    now = gnupg_get_time ();
+
+  diff = difftime (now, since);
+  if (diff < 0)
+    return xtrystrdup ("time-warp");
+
+  seconds = (unsigned long)diff % 60;
+  value = (unsigned long)(diff / 60);
+  minutes = value % 60;
+  value /= 60;
+  hours = value % 24;
+  value /= 24;
+  days = value % 365;
+
+  if (days)
+    result = xtryasprintf ("%ud%uh%um%us", days, hours, minutes, seconds);
+  else if (hours)
+    result = xtryasprintf ("%uh%um%us", hours, minutes, seconds);
+  else if (minutes)
+    result = xtryasprintf ("%um%us", minutes, seconds);
+  else
+    result = xtryasprintf ("%us", seconds);
+
+  return result;
+}
+
+
 /*
  * Note: this function returns GMT
  */