diff --git a/core/src/main/java/com/yahoo/ycsb/generator/RandomDiscreteTimestampGenerator.java b/core/src/main/java/com/yahoo/ycsb/generator/RandomDiscreteTimestampGenerator.java
new file mode 100644
index 0000000000000000000000000000000000000000..bb8bb07fe963e4e4af4e09f0d8b7f8e418e0584f
--- /dev/null
+++ b/core/src/main/java/com/yahoo/ycsb/generator/RandomDiscreteTimestampGenerator.java
@@ -0,0 +1,119 @@
+/**
+ * Copyright (c) 2017 YCSB contributors. All rights reserved.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); you
+ * may not use this file except in compliance with the License. You
+ * may obtain a copy of the License at
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * permissions and limitations under the License. See accompanying
+ * LICENSE file.
+ */
+package com.yahoo.ycsb.generator;
+
+import java.util.concurrent.TimeUnit;
+
+import com.yahoo.ycsb.Utils;
+
+/**
+ * A generator that picks from a discrete set of offsets from a base Unix Epoch
+ * timestamp that returns timestamps in a random order with the guarantee that
+ * each timestamp is only returned once.
+ * <p>
+ * TODO - It would be best to implement some kind of psuedo non-repeating random
+ * generator for this as it's likely OK that some small percentage of values are
+ * repeated. For now we just generate all of the offsets in an array, shuffle
+ * it and then iterate over the array.
+ * <p>
+ * Note that {@link #MAX_INTERVALS} defines a hard limit on the size of the 
+ * offset array so that we don't completely blow out the heap.
+ * <p>
+ * The constructor parameter {@code intervals} determines how many values will be
+ * returned by the generator. For example, if the {@code interval} is 60 and the
+ * {@code timeUnits} are set to {@link TimeUnit#SECONDS} and {@code intervals} 
+ * is set to 60, then the consumer can call {@link #nextValue()} 60 times for
+ * timestamps within an hour.
+ */
+public class RandomDiscreteTimestampGenerator extends UnixEpochTimestampGenerator {
+
+  /** A hard limit on the size of the offsets array to a void using too much heap. */
+  public static final int MAX_INTERVALS = 16777216;
+  
+  /** The total number of intervals for this generator. */
+  private final int intervals;
+  
+  // can't be primitives due to the generic params on the sort function :(
+  /** The array of generated offsets from the base time. */
+  private final Integer[] offsets;
+  
+  /** The current index into the offsets array. */
+  private int offsetIndex;
+  
+  /**
+   * Ctor that uses the current system time as current.
+   * @param interval The interval between timestamps.
+   * @param timeUnits The time units of the returned Unix Epoch timestamp (as well
+   * as the units for the interval).
+   * @param intervals The total number of intervals for the generator.
+   * @throws IllegalArgumentException if the intervals is larger than {@link #MAX_INTERVALS}
+   */
+  public RandomDiscreteTimestampGenerator(final long interval, final TimeUnit timeUnits, 
+                                          final int intervals) {
+    super(interval, timeUnits);
+    this.intervals = intervals;
+    offsets = new Integer[intervals];
+    setup();
+  }
+  
+  /**
+   * Ctor for supplying a starting timestamp.
+   * The interval between timestamps.
+   * @param timeUnits The time units of the returned Unix Epoch timestamp (as well
+   * as the units for the interval).
+   * @param startTimestamp The start timestamp to use. 
+   * NOTE that this must match the time units used for the interval. 
+   * If the units are in nanoseconds, provide a nanosecond timestamp {@code System.nanoTime()}
+   * or in microseconds, {@code System.nanoTime() / 1000}
+   * or in millis, {@code System.currentTimeMillis()}
+   * @param intervals The total number of intervals for the generator.
+   * @throws IllegalArgumentException if the intervals is larger than {@link #MAX_INTERVALS}
+   */
+  public RandomDiscreteTimestampGenerator(final long interval, final TimeUnit timeUnits,
+                                          final long startTimestamp, final int intervals) {
+    super(interval, timeUnits, startTimestamp);
+    this.intervals = intervals;
+    offsets = new Integer[intervals];
+    setup();
+  }
+  
+  /**
+   * Generates the offsets and shuffles the array.
+   */
+  private void setup() {
+    if (intervals > MAX_INTERVALS) {
+      throw new IllegalArgumentException("Too many intervals for the in-memory "
+          + "array. The limit is " + MAX_INTERVALS + ".");
+    }
+    offsetIndex = 0;
+    for (int i = 0; i < intervals; i++) {
+      offsets[i] = i;
+    }
+    Utils.shuffleArray(offsets);
+  }
+  
+  @Override
+  public Long nextValue() {
+    if (offsetIndex >= offsets.length) {
+      throw new IllegalStateException("Reached the end of the random timestamp "
+          + "intervals: " + offsetIndex);
+    }
+    lastTimestamp = currentTimestamp;
+    currentTimestamp = startTimestamp + (offsets[offsetIndex++] * getOffset(1));
+    return currentTimestamp;
+  }
+}
\ No newline at end of file
diff --git a/core/src/main/java/com/yahoo/ycsb/generator/UnixEpochTimestampGenerator.java b/core/src/main/java/com/yahoo/ycsb/generator/UnixEpochTimestampGenerator.java
index ce3c9e3d142b623a5dc9591cb26d5761a090ebe0..cb2e83f3599bccb9f2a725fec9b495b1438a50dd 100644
--- a/core/src/main/java/com/yahoo/ycsb/generator/UnixEpochTimestampGenerator.java
+++ b/core/src/main/java/com/yahoo/ycsb/generator/UnixEpochTimestampGenerator.java
@@ -43,17 +43,20 @@ import java.util.concurrent.TimeUnit;
  */
 public class UnixEpochTimestampGenerator extends Generator<Long> {
 
+  /** The base timestamp used as a starting reference. */
+  protected long startTimestamp;
+  
   /** The current timestamp that will be incremented. */
-  private long currentTimestamp;
+  protected long currentTimestamp;
 
   /** The last used timestamp. Should always be one interval behind current. */
-  private long lastTimestamp;
+  protected long lastTimestamp;
 
   /** The interval to increment by. Multiplied by {@link #timeUnits}. */
-  private long interval;
+  protected long interval;
 
   /** The units of time the interval represents. */
-  private TimeUnit timeUnits;
+  protected TimeUnit timeUnits;
 
   /**
    * Default ctor with the current system time and a 60 second interval.
@@ -94,7 +97,8 @@ public class UnixEpochTimestampGenerator extends Generator<Long> {
     this.timeUnits = timeUnits;
     // move the first timestamp by 1 interval so that the first call to nextValue
     // returns this timestamp
-    this.currentTimestamp = startTimestamp - getOffset(1);
+    currentTimestamp = startTimestamp - getOffset(1);
+    this.startTimestamp = currentTimestamp;
     lastTimestamp = currentTimestamp - getOffset(1);
   }
 
@@ -133,6 +137,7 @@ public class UnixEpochTimestampGenerator extends Generator<Long> {
     default:
       throw new IllegalArgumentException("Unhandled time unit type: " + timeUnits);
     }
+    startTimestamp = currentTimestamp;
   }
 
   @Override
@@ -175,4 +180,4 @@ public class UnixEpochTimestampGenerator extends Generator<Long> {
     return currentTimestamp;
   }
 
-}
+}
\ No newline at end of file
diff --git a/core/src/test/java/com/yahoo/ycsb/generator/TestRandomDiscreteTimestampGenerator.java b/core/src/test/java/com/yahoo/ycsb/generator/TestRandomDiscreteTimestampGenerator.java
new file mode 100644
index 0000000000000000000000000000000000000000..075005c693db2313b3920c781c977b77e3489712
--- /dev/null
+++ b/core/src/test/java/com/yahoo/ycsb/generator/TestRandomDiscreteTimestampGenerator.java
@@ -0,0 +1,75 @@
+/**
+ * Copyright (c) 2017 YCSB contributors. All rights reserved.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); you
+ * may not use this file except in compliance with the License. You
+ * may obtain a copy of the License at
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * permissions and limitations under the License. See accompanying
+ * LICENSE file.
+ */
+package com.yahoo.ycsb.generator;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.fail;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import org.testng.annotations.Test;
+import org.testng.collections.Lists;
+
+public class TestRandomDiscreteTimestampGenerator {
+
+  @Test
+  public void systemTime() throws Exception {
+    final RandomDiscreteTimestampGenerator generator = 
+        new RandomDiscreteTimestampGenerator(60, TimeUnit.SECONDS, 60);
+    List<Long> generated = Lists.newArrayList();
+    for (int i = 0; i < 60; i++) {
+      generated.add(generator.nextValue());
+    }
+    assertEquals(generated.size(), 60);
+    try {
+      generator.nextValue();
+      fail("Expected IllegalStateException");
+    } catch (IllegalStateException e) { }
+  }
+  
+  @Test
+  public void withStartTime() throws Exception {
+    final RandomDiscreteTimestampGenerator generator = 
+        new RandomDiscreteTimestampGenerator(60, TimeUnit.SECONDS, 1072915200L, 60);
+    List<Long> generated = Lists.newArrayList();
+    for (int i = 0; i < 60; i++) {
+      generated.add(generator.nextValue());
+    }
+    assertEquals(generated.size(), 60);
+    Collections.sort(generated);
+    long ts = 1072915200L - 60; // starts 1 interval in the past
+    for (final long t : generated) {
+      assertEquals(t, ts);
+      ts += 60;
+    }
+    try {
+      generator.nextValue();
+      fail("Expected IllegalStateException");
+    } catch (IllegalStateException e) { }
+  }
+  
+  @Test (expectedExceptions = IllegalArgumentException.class)
+  public void tooLarge() throws Exception {
+    new RandomDiscreteTimestampGenerator(60, TimeUnit.SECONDS, 
+        RandomDiscreteTimestampGenerator.MAX_INTERVALS + 1);
+  }
+  
+  //TODO - With PowerMockito we could UT the initializeTimestamp(long) call.
+  // Otherwise it would involve creating more functions and that would get ugly.
+}