Flesh out time_*() API.
diff --git a/Makefile.in b/Makefile.in
index c4f8cf9..e314a6f 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -116,7 +116,7 @@
 	$(srcroot)test/src/mtx.c $(srcroot)test/src/mq.c \
 	$(srcroot)test/src/SFMT.c $(srcroot)test/src/test.c \
 	$(srcroot)test/src/thd.c $(srcroot)test/src/timer.c
-C_UTIL_INTEGRATION_SRCS := $(srcroot)src/util.c
+C_UTIL_INTEGRATION_SRCS := $(srcroot)src/time.c $(srcroot)src/util.c
 TESTS_UNIT := $(srcroot)test/unit/atomic.c \
 	$(srcroot)test/unit/bitmap.c \
 	$(srcroot)test/unit/ckh.c \
diff --git a/include/jemalloc/internal/private_symbols.txt b/include/jemalloc/internal/private_symbols.txt
index 8b1fd45..4c40af6 100644
--- a/include/jemalloc/internal/private_symbols.txt
+++ b/include/jemalloc/internal/private_symbols.txt
@@ -460,6 +460,18 @@
 tcache_stats_merge
 thread_allocated_cleanup
 thread_deallocated_cleanup
+ticker_init
+ticker_tick
+time_add
+time_compare
+time_copy
+time_divide
+time_idivide
+time_imultiply
+time_init
+time_nsec
+time_sec
+time_subtract
 time_update
 tsd_arena_get
 tsd_arena_set
diff --git a/include/jemalloc/internal/time.h b/include/jemalloc/internal/time.h
index e3e6c5f..a290f38 100644
--- a/include/jemalloc/internal/time.h
+++ b/include/jemalloc/internal/time.h
@@ -1,8 +1,11 @@
+/******************************************************************************/
+#ifdef JEMALLOC_H_TYPES
+
 #define JEMALLOC_CLOCK_GETTIME defined(_POSIX_MONOTONIC_CLOCK) \
     && _POSIX_MONOTONIC_CLOCK >= 0
 
-/******************************************************************************/
-#ifdef JEMALLOC_H_TYPES
+/* Maximum supported number of seconds (~584 years). */
+#define	TIME_SEC_MAX	18446744072
 
 #endif /* JEMALLOC_H_TYPES */
 /******************************************************************************/
@@ -12,6 +15,17 @@
 /******************************************************************************/
 #ifdef JEMALLOC_H_EXTERNS
 
+void	time_init(struct timespec *time, time_t sec, long nsec);
+time_t	time_sec(const struct timespec *time);
+long	time_nsec(const struct timespec *time);
+void	time_copy(struct timespec *time, const struct timespec *source);
+int	time_compare(const struct timespec *a, const struct timespec *b);
+void	time_add(struct timespec *time, const struct timespec *addend);
+void	time_subtract(struct timespec *time, const struct timespec *subtrahend);
+void	time_imultiply(struct timespec *time, uint64_t multiplier);
+void	time_idivide(struct timespec *time, uint64_t divisor);
+uint64_t	time_divide(const struct timespec *time,
+    const struct timespec *divisor);
 bool	time_update(struct timespec *time);
 
 #endif /* JEMALLOC_H_EXTERNS */
diff --git a/src/time.c b/src/time.c
index 2147c52..3f93038 100644
--- a/src/time.c
+++ b/src/time.c
@@ -1,11 +1,160 @@
 #include "jemalloc/internal/jemalloc_internal.h"
 
+#define	BILLION		1000000000
+
+UNUSED static bool
+time_valid(const struct timespec *time)
+{
+
+	if (time->tv_sec > TIME_SEC_MAX)
+		return (false);
+	if (time->tv_nsec >= BILLION)
+		return (false);
+
+	return (true);
+}
+
+void
+time_init(struct timespec *time, time_t sec, long nsec)
+{
+
+	time->tv_sec = sec;
+	time->tv_nsec = nsec;
+
+	assert(time_valid(time));
+}
+
+time_t
+time_sec(const struct timespec *time)
+{
+
+	assert(time_valid(time));
+
+	return (time->tv_sec);
+}
+
+long
+time_nsec(const struct timespec *time)
+{
+
+	assert(time_valid(time));
+
+	return (time->tv_nsec);
+}
+
+void
+time_copy(struct timespec *time, const struct timespec *source)
+{
+
+	assert(time_valid(source));
+
+	*time = *source;
+}
+
+int
+time_compare(const struct timespec *a, const struct timespec *b)
+{
+	int ret;
+
+	assert(time_valid(a));
+	assert(time_valid(b));
+
+	ret = (a->tv_sec > b->tv_sec) - (a->tv_sec < b->tv_sec);
+	if (ret == 0)
+		ret = (a->tv_nsec > b->tv_nsec) - (a->tv_nsec < b->tv_nsec);
+
+	return (ret);
+}
+
+void
+time_add(struct timespec *time, const struct timespec *addend)
+{
+
+	assert(time_valid(time));
+	assert(time_valid(addend));
+
+	time->tv_sec += addend->tv_sec;
+	time->tv_nsec += addend->tv_nsec;
+	if (time->tv_nsec >= BILLION) {
+		time->tv_sec++;
+		time->tv_nsec -= BILLION;
+	}
+
+	assert(time_valid(time));
+}
+
+void
+time_subtract(struct timespec *time, const struct timespec *subtrahend)
+{
+
+	assert(time_valid(time));
+	assert(time_valid(subtrahend));
+	assert(time_compare(time, subtrahend) >= 0);
+
+	time->tv_sec -= subtrahend->tv_sec;
+	if (time->tv_nsec < subtrahend->tv_nsec) {
+		time->tv_sec--;
+		time->tv_nsec += BILLION;
+	}
+	time->tv_nsec -= subtrahend->tv_nsec;
+}
+
+void
+time_imultiply(struct timespec *time, uint64_t multiplier)
+{
+	time_t sec;
+	uint64_t nsec;
+
+	assert(time_valid(time));
+
+	sec = time->tv_sec * multiplier;
+	nsec = time->tv_nsec * multiplier;
+	sec += nsec / BILLION;
+	nsec %= BILLION;
+	time_init(time, sec, (long)nsec);
+
+	assert(time_valid(time));
+}
+
+void
+time_idivide(struct timespec *time, uint64_t divisor)
+{
+	time_t sec;
+	uint64_t nsec;
+
+	assert(time_valid(time));
+
+	sec = time->tv_sec / divisor;
+	nsec = ((time->tv_sec % divisor) * BILLION + time->tv_nsec) / divisor;
+	sec += nsec / BILLION;
+	nsec %= BILLION;
+	time_init(time, sec, (long)nsec);
+
+	assert(time_valid(time));
+}
+
+uint64_t
+time_divide(const struct timespec *time, const struct timespec *divisor)
+{
+	uint64_t t, d;
+
+	assert(time_valid(time));
+	assert(time_valid(divisor));
+
+	t = time_sec(time) * BILLION + time_nsec(time);
+	d = time_sec(divisor) * BILLION + time_nsec(divisor);
+	assert(d != 0);
+	return (t / d);
+}
+
 bool
 time_update(struct timespec *time)
 {
 	struct timespec old_time;
 
-	memcpy(&old_time, time, sizeof(struct timespec));
+	assert(time_valid(time));
+
+	time_copy(&old_time, time);
 
 #ifdef _WIN32
 	FILETIME ft;
@@ -27,10 +176,11 @@
 #endif
 
 	/* Handle non-monotonic clocks. */
-	if (unlikely(old_time.tv_sec > time->tv_sec))
+	if (unlikely(time_compare(&old_time, time) > 0)) {
+		time_copy(time, &old_time);
 		return (true);
-	if (unlikely(old_time.tv_sec == time->tv_sec))
-		return old_time.tv_nsec > time->tv_nsec;
+	}
 
+	assert(time_valid(time));
 	return (false);
 }
diff --git a/test/include/test/jemalloc_test.h.in b/test/include/test/jemalloc_test.h.in
index 455569d..223162e 100644
--- a/test/include/test/jemalloc_test.h.in
+++ b/test/include/test/jemalloc_test.h.in
@@ -94,6 +94,7 @@
 #  define JEMALLOC_H_STRUCTS
 #  define JEMALLOC_H_EXTERNS
 #  define JEMALLOC_H_INLINES
+#  include "jemalloc/internal/time.h"
 #  include "jemalloc/internal/util.h"
 #  include "jemalloc/internal/qr.h"
 #  include "jemalloc/internal/ql.h"
diff --git a/test/include/test/timer.h b/test/include/test/timer.h
index a7fefdf..a791f9c 100644
--- a/test/include/test/timer.h
+++ b/test/include/test/timer.h
@@ -3,21 +3,9 @@
 #include <unistd.h>
 #include <sys/time.h>
 
-#define JEMALLOC_CLOCK_GETTIME defined(_POSIX_MONOTONIC_CLOCK) \
-    && _POSIX_MONOTONIC_CLOCK >= 0
-
 typedef struct {
-#ifdef _WIN32
-	FILETIME ft0;
-	FILETIME ft1;
-#elif JEMALLOC_CLOCK_GETTIME
-	struct timespec ts0;
-	struct timespec ts1;
-	int clock_id;
-#else
-	struct timeval tv0;
-	struct timeval tv1;
-#endif
+	struct timespec t0;
+	struct timespec t1;
 } timedelta_t;
 
 void	timer_start(timedelta_t *timer);
diff --git a/test/src/timer.c b/test/src/timer.c
index 0c93aba..15306cf 100644
--- a/test/src/timer.c
+++ b/test/src/timer.c
@@ -4,50 +4,26 @@
 timer_start(timedelta_t *timer)
 {
 
-#ifdef _WIN32
-	GetSystemTimeAsFileTime(&timer->ft0);
-#elif JEMALLOC_CLOCK_GETTIME
-	if (sysconf(_SC_MONOTONIC_CLOCK) <= 0)
-		timer->clock_id = CLOCK_REALTIME;
-	else
-		timer->clock_id = CLOCK_MONOTONIC;
-	clock_gettime(timer->clock_id, &timer->ts0);
-#else
-	gettimeofday(&timer->tv0, NULL);
-#endif
+	time_init(&timer->t0, 0, 0);
+	time_update(&timer->t0);
 }
 
 void
 timer_stop(timedelta_t *timer)
 {
 
-#ifdef _WIN32
-	GetSystemTimeAsFileTime(&timer->ft0);
-#elif JEMALLOC_CLOCK_GETTIME
-	clock_gettime(timer->clock_id, &timer->ts1);
-#else
-	gettimeofday(&timer->tv1, NULL);
-#endif
+	time_copy(&timer->t1, &timer->t0);
+	time_update(&timer->t1);
 }
 
 uint64_t
 timer_usec(const timedelta_t *timer)
 {
+	struct timespec delta;
 
-#ifdef _WIN32
-	uint64_t t0, t1;
-	t0 = (((uint64_t)timer->ft0.dwHighDateTime) << 32) |
-	    timer->ft0.dwLowDateTime;
-	t1 = (((uint64_t)timer->ft1.dwHighDateTime) << 32) |
-	    timer->ft1.dwLowDateTime;
-	return ((t1 - t0) / 10);
-#elif JEMALLOC_CLOCK_GETTIME
-	return (((timer->ts1.tv_sec - timer->ts0.tv_sec) * 1000000) +
-	    (timer->ts1.tv_nsec - timer->ts0.tv_nsec) / 1000);
-#else
-	return (((timer->tv1.tv_sec - timer->tv0.tv_sec) * 1000000) +
-	    timer->tv1.tv_usec - timer->tv0.tv_usec);
-#endif
+	time_copy(&delta, &timer->t1);
+	time_subtract(&delta, &timer->t0);
+	return (time_sec(&delta) * 1000000 + time_nsec(&delta) / 1000);
 }
 
 void
diff --git a/test/unit/time.c b/test/unit/time.c
index 80460f9..941e6f1 100644
--- a/test/unit/time.c
+++ b/test/unit/time.c
@@ -1,16 +1,206 @@
 #include "test/jemalloc_test.h"
 
+#define	BILLION	1000000000
+
+TEST_BEGIN(test_time_init)
+{
+	struct timespec ts;
+
+	time_init(&ts, 42, 43);
+	assert_ld_eq(ts.tv_sec, 42, "tv_sec incorrectly initialized");
+	assert_ld_eq(ts.tv_nsec, 43, "tv_nsec incorrectly initialized");
+}
+TEST_END
+
+TEST_BEGIN(test_time_sec)
+{
+	struct timespec ts;
+
+	time_init(&ts, 42, 43);
+	assert_ld_eq(time_sec(&ts), 42, "tv_sec incorrectly read");
+}
+TEST_END
+
+TEST_BEGIN(test_time_nsec)
+{
+	struct timespec ts;
+
+	time_init(&ts, 42, 43);
+	assert_ld_eq(time_nsec(&ts), 43, "tv_nsec incorrectly read");
+}
+TEST_END
+
+TEST_BEGIN(test_time_copy)
+{
+	struct timespec tsa, tsb;
+
+	time_init(&tsa, 42, 43);
+	time_init(&tsb, 0, 0);
+	time_copy(&tsb, &tsa);
+	assert_ld_eq(time_sec(&tsb), 42, "tv_sec incorrectly copied");
+	assert_ld_eq(time_nsec(&tsb), 43, "tv_nsec incorrectly copied");
+}
+TEST_END
+
+TEST_BEGIN(test_time_compare)
+{
+	struct timespec tsa, tsb;
+
+	time_init(&tsa, 42, 43);
+	time_copy(&tsb, &tsa);
+	assert_d_eq(time_compare(&tsa, &tsb), 0, "Times should be equal");
+	assert_d_eq(time_compare(&tsb, &tsa), 0, "Times should be equal");
+
+	time_init(&tsb, 42, 42);
+	assert_d_eq(time_compare(&tsa, &tsb), 1,
+	    "tsa should be greater than tsb");
+	assert_d_eq(time_compare(&tsb, &tsa), -1,
+	    "tsb should be less than tsa");
+
+	time_init(&tsb, 42, 44);
+	assert_d_eq(time_compare(&tsa, &tsb), -1,
+	    "tsa should be less than tsb");
+	assert_d_eq(time_compare(&tsb, &tsa), 1,
+	    "tsb should be greater than tsa");
+
+	time_init(&tsb, 41, BILLION - 1);
+	assert_d_eq(time_compare(&tsa, &tsb), 1,
+	    "tsa should be greater than tsb");
+	assert_d_eq(time_compare(&tsb, &tsa), -1,
+	    "tsb should be less than tsa");
+
+	time_init(&tsb, 43, 0);
+	assert_d_eq(time_compare(&tsa, &tsb), -1,
+	    "tsa should be less than tsb");
+	assert_d_eq(time_compare(&tsb, &tsa), 1,
+	    "tsb should be greater than tsa");
+}
+TEST_END
+
+TEST_BEGIN(test_time_add)
+{
+	struct timespec tsa, tsb;
+
+	time_init(&tsa, 42, 43);
+	time_copy(&tsb, &tsa);
+	time_add(&tsa, &tsb);
+	time_init(&tsb, 84, 86);
+	assert_d_eq(time_compare(&tsa, &tsb), 0, "Incorrect addition result");
+
+	time_init(&tsa, 42, BILLION - 1);
+	time_copy(&tsb, &tsa);
+	time_add(&tsa, &tsb);
+	time_init(&tsb, 85, BILLION - 2);
+	assert_d_eq(time_compare(&tsa, &tsb), 0, "Incorrect addition result");
+}
+TEST_END
+
+TEST_BEGIN(test_time_subtract)
+{
+	struct timespec tsa, tsb;
+
+	time_init(&tsa, 42, 43);
+	time_copy(&tsb, &tsa);
+	time_subtract(&tsa, &tsb);
+	time_init(&tsb, 0, 0);
+	assert_d_eq(time_compare(&tsa, &tsb), 0,
+	    "Incorrect subtraction result");
+
+	time_init(&tsa, 42, 43);
+	time_init(&tsb, 41, 44);
+	time_subtract(&tsa, &tsb);
+	time_init(&tsb, 0, BILLION - 1);
+	assert_d_eq(time_compare(&tsa, &tsb), 0,
+	    "Incorrect subtraction result");
+}
+TEST_END
+
+TEST_BEGIN(test_time_imultiply)
+{
+	struct timespec tsa, tsb;
+
+	time_init(&tsa, 42, 43);
+	time_imultiply(&tsa, 10);
+	time_init(&tsb, 420, 430);
+	assert_d_eq(time_compare(&tsa, &tsb), 0,
+	    "Incorrect multiplication result");
+
+	time_init(&tsa, 42, 666666666);
+	time_imultiply(&tsa, 3);
+	time_init(&tsb, 127, 999999998);
+	assert_d_eq(time_compare(&tsa, &tsb), 0,
+	    "Incorrect multiplication result");
+}
+TEST_END
+
+TEST_BEGIN(test_time_idivide)
+{
+	struct timespec tsa, tsb;
+
+	time_init(&tsa, 42, 43);
+	time_copy(&tsb, &tsa);
+	time_imultiply(&tsa, 10);
+	time_idivide(&tsa, 10);
+	assert_d_eq(time_compare(&tsa, &tsb), 0, "Incorrect division result");
+
+	time_init(&tsa, 42, 666666666);
+	time_copy(&tsb, &tsa);
+	time_imultiply(&tsa, 3);
+	time_idivide(&tsa, 3);
+	assert_d_eq(time_compare(&tsa, &tsb), 0, "Incorrect division result");
+}
+TEST_END
+
+TEST_BEGIN(test_time_divide)
+{
+	struct timespec tsa, tsb, tsc;
+
+	time_init(&tsa, 42, 43);
+	time_copy(&tsb, &tsa);
+	time_imultiply(&tsa, 10);
+	assert_u64_eq(time_divide(&tsa, &tsb), 10,
+	    "Incorrect division result");
+
+	time_init(&tsa, 42, 43);
+	time_copy(&tsb, &tsa);
+	time_imultiply(&tsa, 10);
+	time_init(&tsc, 0, 1);
+	time_add(&tsa, &tsc);
+	assert_u64_eq(time_divide(&tsa, &tsb), 10,
+	    "Incorrect division result");
+
+	time_init(&tsa, 42, 43);
+	time_copy(&tsb, &tsa);
+	time_imultiply(&tsa, 10);
+	time_init(&tsc, 0, 1);
+	time_subtract(&tsa, &tsc);
+	assert_u64_eq(time_divide(&tsa, &tsb), 9, "Incorrect division result");
+}
+TEST_END
+
 TEST_BEGIN(test_time_update)
 {
 	struct timespec ts;
 
-	memset(&ts, 0, sizeof(struct timespec));
+	time_init(&ts, 0, 0);
 
 	assert_false(time_update(&ts), "Basic time update failed.");
 
 	/* Only Rip Van Winkle sleeps this long. */
-	ts.tv_sec += 631152000;
-	assert_true(time_update(&ts), "Update should detect time roll-back.");
+	{
+		struct timespec addend;
+		time_init(&addend, 631152000, 0);
+		time_add(&ts, &addend);
+	}
+	{
+		struct timespec ts0;
+		time_copy(&ts0, &ts);
+		assert_true(time_update(&ts),
+		    "Update should detect time roll-back.");
+		assert_d_eq(time_compare(&ts, &ts0), 0,
+		    "Time should not have been modified");
+	}
+
 }
 TEST_END
 
@@ -19,5 +209,15 @@
 {
 
 	return (test(
+	    test_time_init,
+	    test_time_sec,
+	    test_time_nsec,
+	    test_time_copy,
+	    test_time_compare,
+	    test_time_add,
+	    test_time_subtract,
+	    test_time_imultiply,
+	    test_time_idivide,
+	    test_time_divide,
 	    test_time_update));
 }