diff --git a/CHANGES b/CHANGES
index 76e64c2f1a6c63fef494ae598c47579dbcb90c92..cdda85c350f41bbcb52c46c6f4a30235694eadbc 100644
--- a/CHANGES
+++ b/CHANGES
@@ -24,6 +24,10 @@ o SSL.File
 
   Support query_fd() and set_buffer_mode() methods.
 
+o mktime/System.TM
+
+  Considerable speedup and a reduction in codesize.
+
 Bug fixes
 ---------
 
@@ -85,6 +89,10 @@ o Sql.pgsql
 
   - Repair fetch_row_array().
 
+o mktime/System.TM
+
+  Make timezone management consistent (especially UTC handling).
+
 o Standards.IIM
 
   Disabled debug output for unknown segment markers.
diff --git a/src/builtin.cmod b/src/builtin.cmod
index b13ab4c83bf2de0c07c419df49991ef59ad29db1..f4ade9ac3bd690ca2a40eff291ec264b6cb9e3ce 100644
--- a/src/builtin.cmod
+++ b/src/builtin.cmod
@@ -43,8 +43,29 @@
 
 #define DEFAULT_CMOD_STORAGE
 
-DECLARATIONS
+#ifdef STRUCT_TM_HAS___TM_GMTOFF
+    struct tm_extra { };
+#define tm_zone			__tm_zone
+#define tm_gmtoff		__tm_gmtoff
+#define GET_GMTOFF(TM)		((TM)->tm_gmtoff)
+#define GET_ZONE(this)		((this)->t.tm_zone)
+#define SET_GMTOFF(TM, VAL)	(((TM)->tm_gmtoff) = (VAL))
+#define SET_ZONE(this, VAL)	((this)->t.tm_zone = (VAL))
+#elif defined(STRUCT_TM_HAS_GMTOFF)
+    struct tm_extra { };
+#define GET_GMTOFF(TM)		((TM)->tm_gmtoff)
+#define GET_ZONE(this)		((this)->t.tm_zone)
+#define SET_GMTOFF(TM, VAL)	(((TM)->tm_gmtoff) = (VAL))
+#define SET_ZONE(this, VAL)	((this)->t.tm_zone = (VAL))
+#else
+    struct tm_extra { const char*tm_zone; };
+#define GET_GMTOFF(TM)		0
+#define GET_ZONE(this)		((this)->extra.tm_zone)
+#define SET_GMTOFF(TM, VAL)	(VAL)
+#define SET_ZONE(this, VAL)	((this)->extra.tm_zone = (VAL))
+#endif
 
+DECLARATIONS
 
 /*! @module System
  */
@@ -59,28 +80,9 @@ PIKECLASS TM
     CVAR time_t unix_time;
     CVAR int modified;
     CVAR struct pike_string *set_zone;
-
-#ifdef STRUCT_TM_HAS___TM_GMTOFF
-#define tm_zone __tm_zone
-#define tm_gmtoff __tm_gmtoff
-#define GET_GMTOFF(TM)	((TM)->tm_gmtoff)
-#define GET_ZONE(TM)	((TM)->tm_zone)
-#define SET_GMTOFF(TM, VAL)	(((TM)->tm_gmtoff) = (VAL))
-#define SET_ZONE(TM, VAL)	(((TM)->tm_zone) = (VAL))
-#elif defined(STRUCT_TM_HAS_GMTOFF)
-#define GET_GMTOFF(TM)	((TM)->tm_gmtoff)
-#define GET_ZONE(TM)	((TM)->tm_zone)
-#define SET_GMTOFF(TM, VAL)	(((TM)->tm_gmtoff) = (VAL))
-#define SET_ZONE(TM, VAL)	(((TM)->tm_zone) = (VAL))
-#else
-#define GET_GMTOFF(TM)	0
-#define GET_ZONE(TM)	((char*)NULL)
-#define SET_GMTOFF(TM, VAL)	(VAL)
-#define SET_ZONE(TM, VAL)	(VAL)
-#endif
+    CVAR struct tm_extra extra;
 
 #define strftime_zone strftime
-#define mktime_zone mktime
 #define strptime_zone strptime
 #define asctime_zone asctime
 #define localtime_zone(X,Y) localtime(X)
@@ -89,13 +91,21 @@ PIKECLASS TM
 #endif
 
 #define MODIFY(X) do{ THIS->modified = 1;THIS->t.X; }while(0)
-#define FIX_THIS() do {                                               \
-        if(THIS->modified){                                           \
-            THIS->unix_time = mktime_zone( &THIS->t );                \
-            THIS->modified = 0;                                       \
-        }                                                             \
+#define FIX_THIS(fname) do {                                          \
+        if(THIS->modified)                                            \
+          fix_tm(fname, args, THIS);				      \
     }  while(0)
 
+static void fix_tm(const char*fname, int args, struct TM_struct*this)
+{
+  const char*tm_zone = GET_ZONE(this);
+  int is_utc_zone = tm_zone && !strcmp(tm_zone, "UTC");
+  if (is_utc_zone)
+    this->t.tm_isdst = 0;
+  this->unix_time = mktime_zone(fname, args, &this->t, is_utc_zone, 0);
+  this->modified = 0;
+}
+
 #ifdef HAVE_STRPTIME
     /*! @decl int(0..1) strptime( string(1..255) format, string(1..255) data )
      *!
@@ -356,13 +366,13 @@ PIKECLASS TM
      *! Unlike the system struct tm the 'year' field is not year-1900,
      *! instead it is the actual year.
      */
-    PIKEFUN int(0..60) `sec()       { FIX_THIS();RETURN THIS->t.tm_sec;  }
-    PIKEFUN int(0..59) `min()       { FIX_THIS();RETURN THIS->t.tm_min;  }
-    PIKEFUN int(0..23) `hour()      { FIX_THIS();RETURN THIS->t.tm_hour; }
-    PIKEFUN int(1..31) `mday()      { FIX_THIS();RETURN THIS->t.tm_mday; }
-    PIKEFUN int(0..11) `mon()       { FIX_THIS();RETURN THIS->t.tm_mon; }
+    PIKEFUN int(0..60) `sec()       { FIX_THIS("sec");RETURN THIS->t.tm_sec;  }
+    PIKEFUN int(0..59) `min()       { FIX_THIS("min");RETURN THIS->t.tm_min;  }
+    PIKEFUN int(0..23) `hour()      { FIX_THIS("hour");RETURN THIS->t.tm_hour; }
+    PIKEFUN int(1..31) `mday()      { FIX_THIS("mday");RETURN THIS->t.tm_mday; }
+    PIKEFUN int(0..11) `mon()       { FIX_THIS("mon");RETURN THIS->t.tm_mon; }
 
-    PIKEFUN int `year()             { FIX_THIS();RETURN THIS->t.tm_year+1900; }
+    PIKEFUN int `year()      { FIX_THIS("year");RETURN THIS->t.tm_year+1900; }
     PIKEFUN int `sec=(int a) { MODIFY(tm_sec=a); }
     PIKEFUN int `min=(int a) { MODIFY(tm_min=a); }
     PIKEFUN int `hour=(int a){ MODIFY(tm_hour=a); }
@@ -377,7 +387,7 @@ PIKECLASS TM
      *! automatically using the timezone rules.
      */
     PIKEFUN int(-1..1) `isdst()  {
-        FIX_THIS();
+        FIX_THIS("isdst");
         RETURN THIS->t.tm_isdst;
     }
 
@@ -385,13 +395,13 @@ PIKECLASS TM
      *! The day of the week, sunday is 0, saturday is 6.
      *! This is calculated from the other fields and can not be changed directly.
      */
-    PIKEFUN int(0..6) `wday()      { FIX_THIS(); RETURN THIS->t.tm_wday; }
+    PIKEFUN int(0..6) `wday()      { FIX_THIS("wday"); RETURN THIS->t.tm_wday; }
 
     /*! @decl int yday
      *! The day of the year, from 0 (the first day) to 365
      *! This is calculated from the other fields and can not be changed directly.
      */
-    PIKEFUN int(0..365) `yday()      { FIX_THIS(); RETURN THIS->t.tm_yday; }
+    PIKEFUN int(0..365) `yday()    { FIX_THIS("yday"); RETURN THIS->t.tm_yday; }
 
     /*! @decl int unix_time()
      *! Return the unix time corresponding to this time_t. If no time
@@ -399,7 +409,7 @@ PIKECLASS TM
      */
     PIKEFUN int unix_time()
     {
-        FIX_THIS();
+        FIX_THIS("unix_time");
         RETURN THIS->unix_time;
     }
 
@@ -410,19 +420,20 @@ PIKECLASS TM
      */
     PIKEFUN string asctime()
     {
-        FIX_THIS();
+        FIX_THIS("asctime");
         {
-            char *tval = asctime_zone( &THIS->t );
-            if( tval )
-                push_text( tval );
-            else
-                push_undefined();
+#define STRFTIME_MAXSIZE 26
+          char s[STRFTIME_MAXSIZE];
+          if( !strftime(s, STRFTIME_MAXSIZE, "%c\n", &THIS->t) )
+            push_undefined();
+          else
+            push_text(s);
         }
     }
 
     PIKEFUN void _sprintf( int flag, mapping options )
     {
-        int post_sum = 1;
+        int post_sum = 0;
         switch( flag )
         {
           case 'O':
@@ -432,10 +443,10 @@ PIKECLASS TM
           case 's':
             f_TM_asctime(0);
             push_text("\n");
-            if( GET_ZONE(&(THIS->t)) )
+            if( GET_ZONE(THIS) )
             {
                 push_text(" ");
-                push_text( GET_ZONE(&(THIS->t)) );
+                push_text( GET_ZONE(THIS) );
                 f_add( 2 );
             }
             else
@@ -484,9 +495,9 @@ PIKECLASS TM
      *! The timezone of this structure
      */
     PIKEFUN string `zone() {
-        FIX_THIS();
-        if( GET_ZONE(&(THIS->t)) )
-            push_text( GET_ZONE(&(THIS->t)) );
+        FIX_THIS("zone");
+        if( GET_ZONE(THIS) )
+            push_text( GET_ZONE(THIS) );
         else
             push_undefined();
     }
@@ -495,7 +506,7 @@ PIKECLASS TM
      *! The offset from GMT for the time in this tm-struct
      */
     PIKEFUN int `gmtoff() {
-        FIX_THIS();
+        FIX_THIS("gmtoff");
         push_int( GET_GMTOFF(&(THIS->t)) );
     }
 
@@ -520,10 +531,6 @@ PIKECLASS TM
         if( !res )
             RETURN 0;
 
-        /* These are supposedly correctly by localtime_zone. */
-        SET_GMTOFF(res, GET_GMTOFF(&(THIS->t)));
-        SET_ZONE(res, GET_ZONE(&(THIS->t)));
-
         THIS->t = *res;
         THIS->modified = 1;
         RETURN 1;
@@ -543,6 +550,7 @@ PIKECLASS TM
             RETURN 0;
 
         THIS->t = *res;
+	SET_ZONE(THIS, "UTC");	/* Override timezone */
         THIS->modified = 1;
         RETURN 1;
     }
@@ -581,26 +589,30 @@ PIKECLASS TM
                          string|void timezone )
     {
         struct tm *t = &THIS->t;
-        t->tm_isdst = -1;
+        int use_utc;
+        t->tm_isdst = use_utc ? 0 : -1;
         t->tm_year = year - 1900;
         t->tm_mon = mon;
         t->tm_mday = mday;
         t->tm_hour = hour;
         t->tm_min = min;
         t->tm_sec = sec;
+        use_utc = 0;
+        if (timezone) {
+          if (strcmp(timezone->str, "UTC"))
+            Pike_error("Timezone must either be UTC or omitted.\n");
+          use_utc = 1;
+        }
 	if (THIS->set_zone) {
 	    free_string(THIS->set_zone);
 	    THIS->set_zone = NULL;
 	}
-        if( !timezone ) /* gmtime. */
-	    SET_ZONE(t, "UTC");
-        else
-        {
-	    add_ref(timezone);
-            THIS->set_zone = timezone;
-            SET_ZONE(t, timezone->str);
-        }
-        THIS->unix_time = mktime_zone( t );
+        if (use_utc)
+          t->tm_isdst = 0;
+        THIS->unix_time = mktime_zone("TM", args, &THIS->t, use_utc, 0);
+        /* Setting it to other timezones than UTC is not supported (yet) */
+        if (use_utc)
+          SET_ZONE(THIS, "UTC");
     }
 
     INIT {
diff --git a/src/builtin_functions.c b/src/builtin_functions.c
index 621a347433fb9e688e034550b43cbe0e741830ef..dc86b6a8c07254bea99283433fa4e646e99d19e9 100644
--- a/src/builtin_functions.c
+++ b/src/builtin_functions.c
@@ -5733,224 +5733,52 @@ PMOD_EXPORT void f_localtime(INT32 args)
   f_aggregate_mapping(20);
 }
 
-#define isleap(y) ((((y) % 4) == 0 && ((y) % 100) != 0) || ((y) % 400) == 0)
-
-static const int mon_lengths[2][12] = {
-  {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 void normalize_date (struct tm *t)
-/* Normalizes t->tm_mday and t->tm_mon. */
+time_t mktime_zone(const char*fname, int args,
+                   struct tm*date, int other_timezone, int tz)
 {
-  int q, year, mon, mday, leap;
-
-  q = t->tm_mon / 12;
-  if (t->tm_mon < 0) q--;
-  t->tm_mon -= q * 12;
-  t->tm_year += q;
-
-  year = t->tm_year + 1900;
-  leap = isleap (year);
-  mon = t->tm_mon;
-  mday = t->tm_mday;
-
-  if (mday > 0) {
-    int mon_len = mon_lengths[leap][mon];
-    if (mday <= mon_len) return;
-    do {
-      mday -= mon_len;
-      if (++mon == 12) mon = 0, year++, leap = isleap (year);
-    } while (mday > (mon_len = mon_lengths[leap][mon]));
-  }
-
-  else
-    do {
-      if (mon == 0) mon = 11, year--, leap = isleap (year);
-      else mon--;
-      mday += mon_lengths[leap][mon];
-    } while (mday < 1);
-
-  t->tm_year = year - 1900;
-  t->tm_mon = mon;
-  t->tm_mday = mday;
-}
-
-#define CHECKED_DIFF_MULT(RES, A, B, MULT, OVERFLOW) do {		\
-    RES = (A - B) * (MULT);						\
-    if ((A > B) != (RES > 0)) {OVERFLOW;}				\
-  } while (0)
-
-#define CHECKED_ADD(ACC, DIFF, OVERFLOW) do {				\
-    time_t res_ = ACC + DIFF;						\
-    if ((ACC > 0) == (DIFF > 0) && (ACC > 0) != (res_ > 0))		\
-      {OVERFLOW;}							\
-    else								\
-      ACC = res_;							\
-  } while (0)
-
-/* Returns the approximate difference in seconds between the
- * two struct tm's.
- */
-static time_t my_tm_diff(const struct tm *t1, const struct tm *t2)
-{
-  time_t base, diff;
-
-  /* Win32 localtime() returns NULL for all dates before Jan 01, 1970. */
-  if (!t2) return -1;
-
-  CHECKED_DIFF_MULT (base, t1->tm_year, t2->tm_year, 60*60*24*31*12,
-		     return base < 0 ? MAX_TIME_T : MIN_TIME_T);
-
-  /* Overflow detection not necessary on these fields since we can
-   * assume they're all in the valid ranges here. */
-  diff =
-    (t1->tm_mon - t2->tm_mon) * (60*60*24*31) +
-    (t1->tm_mday - t2->tm_mday) * (60*60*24) +
-    (t1->tm_hour - t2->tm_hour) * (60*60) +
-    (t1->tm_min - t2->tm_min) * 60 +
-    (t1->tm_sec - t2->tm_sec);
-
-  CHECKED_ADD (base, diff,
-	       return diff < 0 ? MIN_TIME_T : MAX_TIME_T);
-
-  return base;
-}
-
-typedef struct tm *time_fn (const time_t *);
-
-/* Inverse operation of gmtime or localtime. Unlike mktime(3), this
- * doesn't fill in a normalized time in target_tm.
- */
-static int my_time_inverse (struct tm *target_tm, time_t *result, time_fn timefn)
-{
-  struct tm norm_tm = *target_tm;
-  time_t current_ts = 0;
-  time_t displacement;
-  time_t diff_ts, old_diff_ts = 0;
-  int loop_cnt, tried_dst_displacement = 0;
-
-#ifdef DEBUG_MY_TIME_INVERSE
-  fprintf (stderr, "target: y %d m %d d %d h %d m %d isdst %d\n",
-	   target_tm->tm_year, target_tm->tm_mon, target_tm->tm_mday,
-	   target_tm->tm_hour, target_tm->tm_min, target_tm->tm_isdst);
-#endif
-
-  /* An hour, minute or second value outside the valid range is
-   * treated as a displacement rather than an absolute time spec. We
-   * therefore zero them in the target time spec and add the
-   * displacement seconds back to the time_t afterwards. This way we
-   * don't need to worry about them in the date normalization. */
-
-  /* It's quicker to always move the seconds to the displacement. It
-   * works just as well and we don't need to consider leap seconds. */
-  displacement = norm_tm.tm_sec;
-  norm_tm.tm_sec = 0;
-
-  /* Bug: The following conversions to seconds ought to compensate for
-   * leap seconds. That should only happen if timefn takes leap
-   * seconds into account however, which it might not do. */
-  if (norm_tm.tm_min < 0 || norm_tm.tm_min >= 60) {
-    time_t d;
-    CHECKED_DIFF_MULT (d, norm_tm.tm_min, 0, 60, return 0);
-    CHECKED_ADD (displacement, d, return 0);
-    norm_tm.tm_min = 0;
-  }
-  if (norm_tm.tm_hour < 0 || norm_tm.tm_hour >= 60) {
-    time_t d;
-    CHECKED_DIFF_MULT (d, norm_tm.tm_hour, 0, 60*60, return 0);
-    CHECKED_ADD (displacement, d, return 0);
-    norm_tm.tm_hour = 0;
-  }
-
-  /* Normalize the date. This is necessary since the simplistic diff
-   * calculation in my_tm_diff doesn't work on invalid dates like
-   * November 100th or March -10th. (Can't use the displacement
-   * variable for an invalid tm_mday since the number of seconds per
-   * day isn't constant.) */
-  normalize_date (&norm_tm);
-#ifdef DEBUG_MY_TIME_INVERSE
-  fprintf (stderr, "normalized: y %d m %d d %d h %d m %d isdst %d\n"
-	   "displacement: %ld\n",
-	   norm_tm.tm_year, norm_tm.tm_mon, norm_tm.tm_mday,
-	   norm_tm.tm_hour, norm_tm.tm_min, norm_tm.tm_isdst,
-	   (long) displacement);
-#endif
-
-  /* This loop seems stable, and usually converges in two passes.
-   * The loop counter is for paranoia reasons.
-   */
-  for (loop_cnt = 0; loop_cnt < 20; loop_cnt++, old_diff_ts = diff_ts) {
-    struct tm *current_tm = timefn(&current_ts);
-#ifdef DEBUG_MY_TIME_INVERSE
-    fprintf (stderr, "curr: y %d m %d d %d h %d m %d isdst %d\n",
-	     current_tm->tm_year, current_tm->tm_mon, current_tm->tm_mday,
-	     current_tm->tm_hour, current_tm->tm_min, current_tm->tm_isdst);
-#endif
+  time_t retval;
+  int normalised_time;
 
-    diff_ts = my_tm_diff (&norm_tm, current_tm);
-#ifdef DEBUG_MY_TIME_INVERSE
-    fprintf (stderr, "diff: %ld\n", (long) diff_ts);
-#endif
+  date->tm_wday = -1;		/* flag to determine failure */
 
-    if (!current_tm) {
-#ifdef DEBUG_MY_TIME_INVERSE
-      fprintf (stderr, "outside range for timefn().\n");
-#endif
-      return 0;
-    }
-
-    if (!diff_ts) {
-      /* Got a satisfactory time, but if norm_tm has an opinion on
-       * DST we should check if we can return an alternative in the
-       * same DST zone, to cope with the overlapping DST adjustment at
-       * fall. */
-      if (norm_tm.tm_isdst >= 0 &&
-	  norm_tm.tm_isdst != current_tm->tm_isdst &&
-	  !tried_dst_displacement) {
-	/* Offset the time a day and iterate some more (only once
-	 * more, really), so that we approach the target time from the
-	 * right direction. */
-	if (norm_tm.tm_isdst)
-	  current_ts -= 24 * 3600;
-	else
-	  current_ts += 24 * 3600;
-	tried_dst_displacement = 1;
-#ifdef DEBUG_MY_TIME_INVERSE
-	fprintf (stderr, "dst displacement\n");
-#endif
-	continue;
-      }
-      break;
-    }
-
-    if (diff_ts == -old_diff_ts) {
-      /* We're oscillating. Shouldn't happen since norm_tm ought to be
-       * valid. */
-#ifdef DEBUG_MY_TIME_INVERSE
-      fprintf (stderr, "oscillation detected: %ld <-> %ld\n",
-	       (long) old_diff_ts, (long) diff_ts);
+  {
+    int sec, min, hour;
+    sec = date->tm_sec;
+    min = date->tm_min;
+    hour = date->tm_hour;
+
+    min += sec / 60;
+    if ((sec %= 60) < 0)
+      min--, sec += 60;
+    hour += min / 60;
+    if ((min %= 60) < 0)
+      hour--, min += 60;
+    if ((hour %= 24) < 0)
+      hour += 24;
+    normalised_time = ((hour * 60) + min) * 60 + sec;
+  }
+
+  retval = mktime(date);
+  if (date->tm_wday < 0)
+    PIKE_ERROR("mktime", "Time conversion unsuccessful.\n", Pike_sp, args);
+
+  if(other_timezone)
+  {
+    normalised_time -= ((date->tm_hour * 60) + date->tm_min) * 60 + date->tm_sec;
+    if (normalised_time < -12*60*60)
+      normalised_time += 24*60*60;
+    else if (normalised_time > 12*60*60)
+      normalised_time -= 24*60*60;
+#ifdef STRUCT_TM_HAS___TM_GMTOFF
+    retval += date->__tm_gmtoff;
+#elif defined(STRUCT_TM_HAS_GMTOFF)
+    retval += date->tm_gmtoff;
+#else
+    normalised_time = retval - mktime(gmtime(&retval));
 #endif
-      return 0;
-    }
-
-    /* It's ok to not add the full diff here since we're looping. Do
-     * this since the diff calculation can overshoot the target
-     * time. */
-    CHECKED_ADD (current_ts, diff_ts, {
-	if (diff_ts > 0 && current_ts < MAX_TIME_T)
-	  current_ts = MAX_TIME_T;
-	else if (diff_ts < 0 && current_ts > MIN_TIME_T)
-	  current_ts = MIN_TIME_T;
-	else
-	  return 0;
-      });
+    retval += normalised_time + tz;
   }
-
-  CHECKED_ADD (current_ts, displacement, return 0);
-
-  *result = current_ts;
-  return 1;
+  return retval;
 }
 
 /*! @decl int mktime(mapping(string:int) tm)
@@ -6000,6 +5828,7 @@ PMOD_EXPORT void f_mktime (INT32 args)
   INT_TYPE isdst = -1, tz = 0;
   struct tm date;
   time_t retval;
+  int normalised_time;
 
   if (args<1)
     SIMPLE_TOO_FEW_ARGS_ERROR("mktime", 1);
@@ -6034,30 +5863,11 @@ PMOD_EXPORT void f_mktime (INT32 args)
   date.tm_mon=mon;
   date.tm_year=year;
   date.tm_isdst=isdst;
-
   /* date.tm_zone = NULL; */
 
-  if((args > 7) && (SUBTYPEOF(Pike_sp[7-args]) == NUMBER_NUMBER))
-  {
-    /* UTC-relative time. Use gmtime. */
-    if (!my_time_inverse (&date, &retval, gmtime))
-      PIKE_ERROR("mktime", "Time conversion failed.\n", Pike_sp, args);
-    retval += tz;
-  } else
-
-  {
-    retval = mktime(&date);
-    if (retval == -1)
-    {
-      /* mktime might fail on dates before 1970 (e.g. GNU libc 2.3.2),
-       * so try our own inverse function with localtime.
-       *
-       * Note that localtime on Win32 will also fail for dates before 1970.
-       */
-      if (!my_time_inverse (&date, &retval, localtime))
-	PIKE_ERROR("mktime", "Time conversion unsuccessful.\n", Pike_sp, args);
-    }
-  }
+  retval = mktime_zone("mktime", args, &date,
+                       args > 7 && SUBTYPEOF(Pike_sp[7-args]) == NUMBER_NUMBER,
+	               tz);
 
   pop_n_elems(args);
 #if SIZEOF_TIME_T > SIZEOF_INT_TYPE
diff --git a/src/builtin_functions.h b/src/builtin_functions.h
index e8a84aff9c3e8695887c7f90bdf62d19da0d4d86..decb02ade0de976ffc285a8d8e00319da9f6b065 100644
--- a/src/builtin_functions.h
+++ b/src/builtin_functions.h
@@ -124,6 +124,8 @@ PMOD_EXPORT void f__assembler_debug(INT32 args);
 PMOD_EXPORT void f__compiler_trace(INT32 args);
 PMOD_EXPORT void f_gmtime(INT32 args);
 PMOD_EXPORT void f_localtime(INT32 args);
+time_t mktime_zone(const char*fname, int args,
+                   struct tm*date, int other_timezone, int tz);
 PMOD_EXPORT void f_mktime (INT32 args);
 PMOD_EXPORT void f_glob(INT32 args);
 PMOD_EXPORT void f_permute(INT32 args);
diff --git a/src/modules/system/testsuite.in b/src/modules/system/testsuite.in
index b76189260a6092f3a3fce9b60f4b50241fc7ecdc..7d8ee57074dd2f4be42bf81ddc06d48d84687424 100644
--- a/src/modules/system/testsuite.in
+++ b/src/modules/system/testsuite.in
@@ -278,4 +278,39 @@ cond_begin([[ System["__MMAP__"] ]])
 
 cond_end // System["__MMAP__"]
 
+test_equal(sort(indices(System.Time())), ({ "sec","usec","usec_full" }))
+test_eq(abs(time()-System.Time()->sec)<2, 1)
+test_eq(System.Time()->usec-System.Time()->usec<=0, 1)
+
+test_do(System.TM())
+test_do((int)System.TM(0), 0)
+test_do((int)System.TM(1<<31), 1<<31)
+test_do((int)System.TM(1<<32), 1<<32)
+test_do((int)System.TM(1<<33), 1<<33)
+test_do(System.TM(2017,11,21,16,15,01,"UTC"))
+test_do(System.TM(2017,6,21,16,15,01,"UTC"))
+test_do(System.TM(2017,6,21,16,15,01))
+
+test_eq(System.TM(1513871300)->year, 2017)
+test_eq(System.TM(1513871300)->mon, 11)
+test_eq(System.TM(1513871300)->mday, 21)
+test_eq(System.TM(1513871300)->wday, 4)
+test_eq(System.TM(1513871300)->yday, 354)
+test_eq(System.TM(1513871300)->hour, 15)
+test_eq(System.TM(1513871300)->min, 48)
+test_eq(System.TM(1513871300)->sec, 20)
+test_eq(System.TM(1513871300)->isdst, 0)
+test_eq(intp(System.TM(1513871300)->gmtoff), 1)
+test_eq(stringp(System.TM(1513871300)->zone), 1)
+test_eq(stringp(System.TM(1513871300)->asctime()), 1)
+test_eq(System.TM(1513871300)->unix_time(), 1513871300)
+test_equal(map("%d%D%e%F%G%g%H%I%k%l%M%m%p%r%R%T%u%U%V%w"/2,
+               System.TM(1513871300)->strftime),
+  ({ "21", "12/21/17", "21", "2017-12-21", "2017", "17",
+     "15", "03", "15", " 3", "48", "12", "PM",
+     "03:48:20 PM", "15:48", "15:48:20", "4", "51", "51", "4" }))
+
+test_eq(stringp((string)System.TM(1513871300)), 1)
+test_eq((int)System.TM(1513871300), 1513871300)
+
 END_MARKER
diff --git a/src/testsuite.in b/src/testsuite.in
index 39953b7ee29a43d147c8a05e395ce9bf503aa577..367809deb44b89393651811ec548245fbb68c2aa 100644
--- a/src/testsuite.in
+++ b/src/testsuite.in
@@ -11298,6 +11298,19 @@ test_equal(mkmultiset(indices(class{constant a="a"; constant b="b";}())),
 // - localtime
 cond([[all_constants()->localtime]],[[
   test_true(mappingp(localtime(0)))
+  test_eq(localtime((1<<31)-1)->year, 138)
+  test_equal(sort(indices(localtime(0))),({
+     "hour",
+     "isdst",
+     "mday",
+     "min",
+     "mon",
+     "sec",
+     "timezone",
+     "wday",
+     "yday",
+     "year"
+  }))
   test_do([[int t = -1; catch(localtime(t));]])
 ]])
 cond([[all_constants()->localtime && all_constants()->mktime]],
@@ -11492,7 +11505,8 @@ cond([[all_constants()->mktime]],
   test_any([[foreach(({1075550400,94691300,220921700,347152100,473382500,
 		       599612900,725843300,852073700,978304100,1104534500,
 		       1230764900,1356995300,1483225700,1609456100,1735686500,
-		       1861916900,1988147300,2114377700,1500033813
+                       1861916900,1988147300,2114377700,1500033813,
+                       0,1,1<<31-1
 		      }),int t) {
 	       int res = mktime (gmtime (t));
 	       if(res!=t) return ({t, res});
@@ -11506,7 +11520,7 @@ cond([[all_constants()->mktime]],
   test_eq(mktime (-200, -200, -200, 200, 1, 107, 0, 0), 1186749400);
   test_eq(mktime (33, 3, 12, 14, 6, 117, 0, 0), 1500033813);
   test_eq(mktime ((["sec":33, "min":3, "hour":12,
-        "mday":14, "mon":6, "year":117, "timezone":0])), 1500033813);
+   "mday":14, "mon":6, "year":117, "isdst":0, "timezone":0])), 1500033813);
 
   test_any( [[
   // bug [2861] ------------------------------------------------------------