Adds `Timestamps.now()`.

PiperOrigin-RevId: 526160914
pull/12519/head
Kurt Alfred Kluever 2 years ago committed by Copybara-Service
parent a010e26505
commit 295f1125ce
  1. 49
      java/util/src/main/java/com/google/protobuf/util/Timestamps.java
  2. 33
      java/util/src/test/java/com/google/protobuf/util/TimestampsTest.java

@ -42,6 +42,7 @@ import com.google.j2objc.annotations.J2ObjCIncompatible;
import com.google.protobuf.Duration;
import com.google.protobuf.Timestamp;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Comparator;
@ -49,6 +50,7 @@ import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.TimeZone;
import javax.annotation.Nullable;
/**
* Utilities to help create/manipulate {@code protobuf/timestamp.proto}. All operations throw an
@ -117,8 +119,8 @@ public final class Timestamps {
/**
* Returns a {@link Comparator} for {@link Timestamp Timestamps} which sorts in increasing
* chronological order. Nulls and invalid {@link Timestamp Timestamps} are not allowed (see
* {@link #isValid}). The returned comparator is serializable.
* chronological order. Nulls and invalid {@link Timestamp Timestamps} are not allowed (see {@link
* #isValid}). The returned comparator is serializable.
*/
public static Comparator<Timestamp> comparator() {
return TimestampComparator.INSTANCE;
@ -281,7 +283,8 @@ public final class Timestamps {
try {
return normalizedTimestamp(seconds, nanos);
} catch (IllegalArgumentException e) {
ParseException ex = new ParseException(
ParseException ex =
new ParseException(
"Failed to parse timestamp " + value + " Timestamp is out of range.", 0);
ex.initCause(e);
throw ex;
@ -307,6 +310,39 @@ public final class Timestamps {
}
}
// the following 3 constants contain references to java.time.Instant methods (if that class is
// available at runtime); otherwise, they are null.
@Nullable private static final Method INSTANT_NOW = instantMethod("now");
@Nullable private static final Method INSTANT_GET_EPOCH_SECOND = instantMethod("getEpochSecond");
@Nullable private static final Method INSTANT_GET_NANO = instantMethod("getNano");
@Nullable
private static Method instantMethod(String methodName) {
try {
return Class.forName("java.time.Instant").getMethod(methodName);
} catch (Exception e) {
return null;
}
}
/** Create a Timestamp using the best-available system clock. */
public static Timestamp now() {
if (INSTANT_NOW != null) {
try {
Object now = INSTANT_NOW.invoke(null);
long epochSecond = (long) INSTANT_GET_EPOCH_SECOND.invoke(now);
int nanoAdjustment = (int) INSTANT_GET_NANO.invoke(now);
return normalizedTimestamp(epochSecond, nanoAdjustment);
} catch (Throwable fallThrough) {
throw new AssertionError(fallThrough);
}
}
// otherwise, fall back on millisecond precision
return fromMillis(System.currentTimeMillis());
}
/** Create a Timestamp from the number of seconds elapsed from the epoch. */
@SuppressWarnings("GoodTime") // this is a legacy conversion API
public static Timestamp fromSeconds(long seconds) {
@ -333,7 +369,7 @@ public final class Timestamps {
}
/**
* Create a Timestamp from a java.util.Date. If the java.util.Date is a java.sql.Timestamp,
* Create a Timestamp from a {@link Date}. If the {@link Date} is a {@link java.sql.Timestamp},
* full nanonsecond precision is retained.
*
* @throws IllegalArgumentException if the year is before 1 CE or after 9999 CE
@ -344,7 +380,10 @@ public final class Timestamps {
if (date instanceof java.sql.Timestamp) {
java.sql.Timestamp sqlTimestamp = (java.sql.Timestamp) date;
long time = sqlTimestamp.getTime();
long integralSeconds = (time < 0 && time % 1000 != 0) ? time / 1000L - 1 : time / 1000L ; // truncate the fractional seconds
long integralSeconds =
(time < 0 && time % 1000 != 0)
? time / 1000L - 1
: time / 1000L; // truncate the fractional seconds
return Timestamp.newBuilder()
.setSeconds(integralSeconds)
.setNanos(sqlTimestamp.getNanos())

@ -73,13 +73,41 @@ public class TimestampsTest {
private static final Timestamp INVALID_MIN =
Timestamp.newBuilder().setSeconds(Long.MIN_VALUE).setNanos(Integer.MIN_VALUE).build();
@Test
public void testNow() {
Timestamp now = Timestamps.now();
long epochSeconds = System.currentTimeMillis() / 1000;
assertThat(now.getSeconds()).isAtLeast(epochSeconds - 1);
assertThat(now.getSeconds()).isAtMost(epochSeconds + 1);
}
@Test
public void testNowWithSubMillisecondPrecision() {
try {
// throws if we're not on Java9+
Class.forName("java.lang.Runtime$Version");
} catch (ClassNotFoundException e) {
// ignored; we're not on Java 9+
return;
}
// grab 100 timestamps, and ensure that at least 1 of them has sub-millisecond precision
for (int i = 0; i < 100; i++) {
Timestamp now = Timestamps.now();
Timestamp nowWithMilliPrecision = Timestamps.fromMillis(Timestamps.toMillis(now));
if (!now.equals(nowWithMilliPrecision)) {
return;
}
}
assertWithMessage("no timestamp had sub-millisecond precision").fail();
}
@Test
public void testMinMaxAreValid() {
assertThat(Timestamps.isValid(Timestamps.MAX_VALUE)).isTrue();
assertThat(Timestamps.isValid(Timestamps.MIN_VALUE)).isTrue();
}
@Test
public void testIsValid_false() {
assertThat(Timestamps.isValid(0L, -1)).isFalse();
@ -615,6 +643,7 @@ public class TimestampsTest {
} catch (IllegalArgumentException expected) {
}
}
@Test
public void testInvalidMaxMicrosecondsOverflow() {
try {
@ -693,7 +722,6 @@ public class TimestampsTest {
}
}
@Test
public void testIllegalArgumentExceptionForMaxMilliseconds() {
try {
@ -718,7 +746,6 @@ public class TimestampsTest {
}
}
@Test
public void testIllegalArgumentExceptionForMinMilliseconds() {
try {

Loading…
Cancel
Save