31 package com.google.protobuf.util;
33 import static com.
google.common.math.IntMath.checkedAdd;
34 import static com.
google.common.math.IntMath.checkedSubtract;
35 import static com.
google.common.math.LongMath.checkedAdd;
36 import static com.
google.common.math.LongMath.checkedMultiply;
37 import static com.
google.common.math.LongMath.checkedSubtract;
39 import com.
google.errorprone.annotations.CanIgnoreReturnValue;
42 import java.text.ParseException;
43 import java.text.SimpleDateFormat;
44 import java.util.Comparator;
45 import java.util.Date;
46 import java.util.GregorianCalendar;
47 import java.util.Locale;
48 import java.util.TimeZone;
57 static final long TIMESTAMP_SECONDS_MIN = -62135596800L;
60 static final long TIMESTAMP_SECONDS_MAX = 253402300799L;
62 static final long NANOS_PER_SECOND = 1000000000;
63 static final long NANOS_PER_MILLISECOND = 1000000;
64 static final long NANOS_PER_MICROSECOND = 1000;
65 static final long MILLIS_PER_SECOND = 1000;
66 static final long MICROS_PER_SECOND = 1000000;
70 Timestamp.newBuilder().setSeconds(TIMESTAMP_SECONDS_MIN).setNanos(0).build();
76 Timestamp.newBuilder().setSeconds(TIMESTAMP_SECONDS_MAX).setNanos(999999999).build();
84 new ThreadLocal<SimpleDateFormat>() {
86 protected SimpleDateFormat initialValue() {
92 SimpleDateFormat sdf =
new SimpleDateFormat(
"yyyy-MM-dd'T'HH:mm:ss", Locale.ENGLISH);
93 GregorianCalendar calendar =
new GregorianCalendar(TimeZone.getTimeZone(
"UTC"));
96 calendar.setGregorianChange(
new Date(Long.MIN_VALUE));
97 sdf.setCalendar(calendar);
104 new Comparator<Timestamp>() {
109 int secDiff = Long.compare(t1.getSeconds(), t2.getSeconds());
110 return (secDiff != 0) ? secDiff : Integer.compare(t1.getNanos(), t2.getNanos());
143 return isValid(timestamp.getSeconds(), timestamp.getNanos());
155 @SuppressWarnings(
"GoodTime")
156 public static
boolean isValid(
long seconds,
int nanos) {
157 if (seconds < TIMESTAMP_SECONDS_MIN || seconds > TIMESTAMP_SECONDS_MAX) {
160 if (nanos < 0 || nanos >= NANOS_PER_SECOND) {
167 @CanIgnoreReturnValue
169 long seconds = timestamp.getSeconds();
170 int nanos = timestamp.getNanos();
171 if (!
isValid(seconds, nanos)) {
172 throw new IllegalArgumentException(
174 "Timestamp is not valid. See proto definition for valid values. "
175 +
"Seconds (%s) must be in range [-62,135,596,800, +253,402,300,799]. "
176 +
"Nanos (%s) must be in range [0, +999,999,999].",
206 long seconds = timestamp.getSeconds();
207 int nanos = timestamp.getNanos();
209 StringBuilder result =
new StringBuilder();
211 Date date =
new Date(seconds * MILLIS_PER_SECOND);
216 result.append(formatNanos(nanos));
219 return result.toString();
233 int dayOffset =
value.indexOf(
'T');
234 if (dayOffset == -1) {
235 throw new ParseException(
"Failed to parse timestamp: invalid timestamp \"" +
value +
"\"", 0);
237 int timezoneOffsetPosition =
value.indexOf(
'Z', dayOffset);
238 if (timezoneOffsetPosition == -1) {
239 timezoneOffsetPosition =
value.indexOf(
'+', dayOffset);
241 if (timezoneOffsetPosition == -1) {
242 timezoneOffsetPosition =
value.indexOf(
'-', dayOffset);
244 if (timezoneOffsetPosition == -1) {
245 throw new ParseException(
"Failed to parse timestamp: missing valid timezone offset.", 0);
248 String timeValue =
value.substring(0, timezoneOffsetPosition);
249 String secondValue = timeValue;
250 String nanoValue =
"";
251 int pointPosition = timeValue.indexOf(
'.');
252 if (pointPosition != -1) {
253 secondValue = timeValue.substring(0, pointPosition);
254 nanoValue = timeValue.substring(pointPosition + 1);
257 long seconds = date.getTime() / MILLIS_PER_SECOND;
258 int nanos = nanoValue.isEmpty() ? 0 : parseNanos(nanoValue);
260 if (
value.charAt(timezoneOffsetPosition) ==
'Z') {
261 if (
value.length() != timezoneOffsetPosition + 1) {
262 throw new ParseException(
263 "Failed to parse timestamp: invalid trailing data \""
264 +
value.substring(timezoneOffsetPosition)
269 String offsetValue =
value.substring(timezoneOffsetPosition + 1);
271 if (
value.charAt(timezoneOffsetPosition) ==
'+') {
278 return normalizedTimestamp(seconds, nanos);
279 }
catch (IllegalArgumentException e) {
280 throw new ParseException(
"Failed to parse timestamp: timestamp is out of range.", 0);
285 @SuppressWarnings(
"GoodTime")
287 return normalizedTimestamp(seconds, 0);
296 @SuppressWarnings(
"GoodTime")
302 @SuppressWarnings(
"GoodTime")
304 return normalizedTimestamp(
305 milliseconds / MILLIS_PER_SECOND,
306 (
int) (milliseconds % MILLIS_PER_SECOND * NANOS_PER_MILLISECOND));
315 @SuppressWarnings(
"GoodTime")
319 checkedMultiply(timestamp.getSeconds(), MILLIS_PER_SECOND),
320 timestamp.getNanos() / NANOS_PER_MILLISECOND);
324 @SuppressWarnings(
"GoodTime")
326 return normalizedTimestamp(
327 microseconds / MICROS_PER_SECOND,
328 (
int) (microseconds % MICROS_PER_SECOND * NANOS_PER_MICROSECOND));
337 @SuppressWarnings(
"GoodTime")
341 checkedMultiply(timestamp.getSeconds(), MICROS_PER_SECOND),
342 timestamp.getNanos() / NANOS_PER_MICROSECOND);
346 @SuppressWarnings(
"GoodTime")
348 return normalizedTimestamp(
349 nanoseconds / NANOS_PER_SECOND, (
int) (nanoseconds % NANOS_PER_SECOND));
353 @SuppressWarnings(
"GoodTime")
357 checkedMultiply(timestamp.getSeconds(), NANOS_PER_SECOND), timestamp.getNanos());
365 checkedSubtract(to.getSeconds(), from.getSeconds()),
366 checkedSubtract(to.getNanos(), from.getNanos()));
373 return normalizedTimestamp(
374 checkedAdd(
start.getSeconds(),
length.getSeconds()),
382 return normalizedTimestamp(
383 checkedSubtract(
start.getSeconds(),
length.getSeconds()),
384 checkedSubtract(
start.getNanos(),
length.getNanos()));
387 static Timestamp normalizedTimestamp(
long seconds,
int nanos) {
388 if (nanos <= -NANOS_PER_SECOND || nanos >= NANOS_PER_SECOND) {
389 seconds = checkedAdd(seconds, nanos / NANOS_PER_SECOND);
390 nanos = (int) (nanos % NANOS_PER_SECOND);
395 (nanos + NANOS_PER_SECOND);
396 seconds = checkedSubtract(seconds, 1);
398 Timestamp timestamp =
Timestamp.newBuilder().setSeconds(seconds).setNanos(nanos).build();
403 int pos =
value.indexOf(
':');
405 throw new ParseException(
"Invalid offset value: " +
value, 0);
407 String hours =
value.substring(0, pos);
408 String minutes =
value.substring(pos + 1);
409 return (Long.parseLong(hours) * 60 + Long.parseLong(minutes)) * 60;
412 static int parseNanos(String
value)
throws ParseException {
414 for (
int i = 0;
i < 9; ++
i) {
415 result = result * 10;
418 throw new ParseException(
"Invalid nanoseconds.", 0);
420 result +=
value.charAt(
i) -
'0';
427 static String formatNanos(
int nanos) {
429 if (nanos % NANOS_PER_MILLISECOND == 0) {
430 return String.format(Locale.ENGLISH,
"%1$03d", nanos / NANOS_PER_MILLISECOND);
431 }
else if (nanos % NANOS_PER_MICROSECOND == 0) {
432 return String.format(Locale.ENGLISH,
"%1$06d", nanos / NANOS_PER_MICROSECOND);
434 return String.format(Locale.ENGLISH,
"%1$09d", nanos);