• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Protocol Buffers - Google's data interchange format
2 // Copyright 2008 Google Inc.  All rights reserved.
3 // https://developers.google.com/protocol-buffers/
4 //
5 // Redistribution and use in source and binary forms, with or without
6 // modification, are permitted provided that the following conditions are
7 // met:
8 //
9 //     * Redistributions of source code must retain the above copyright
10 // notice, this list of conditions and the following disclaimer.
11 //     * Redistributions in binary form must reproduce the above
12 // copyright notice, this list of conditions and the following disclaimer
13 // in the documentation and/or other materials provided with the
14 // distribution.
15 //     * Neither the name of Google Inc. nor the names of its
16 // contributors may be used to endorse or promote products derived from
17 // this software without specific prior written permission.
18 //
19 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 
31 package com.google.protobuf.util;
32 
33 import com.google.protobuf.Duration;
34 import com.google.protobuf.Timestamp;
35 
36 import java.math.BigInteger;
37 import java.text.ParseException;
38 import java.text.SimpleDateFormat;
39 import java.util.Date;
40 import java.util.GregorianCalendar;
41 import java.util.TimeZone;
42 
43 /**
44  * Utilities to help create/manipulate Timestamp/Duration
45  */
46 public class TimeUtil {
47   // Timestamp for "0001-01-01T00:00:00Z"
48   public static final long TIMESTAMP_SECONDS_MIN = -62135596800L;
49 
50   // Timestamp for "9999-12-31T23:59:59Z"
51   public static final long TIMESTAMP_SECONDS_MAX = 253402300799L;
52   public static final long DURATION_SECONDS_MIN = -315576000000L;
53   public static final long DURATION_SECONDS_MAX = 315576000000L;
54 
55   private static final long NANOS_PER_SECOND = 1000000000;
56   private static final long NANOS_PER_MILLISECOND = 1000000;
57   private static final long NANOS_PER_MICROSECOND = 1000;
58   private static final long MILLIS_PER_SECOND = 1000;
59   private static final long MICROS_PER_SECOND = 1000000;
60 
61   private static final ThreadLocal<SimpleDateFormat> timestampFormat =
62       new ThreadLocal<SimpleDateFormat>() {
63         protected SimpleDateFormat initialValue() {
64           return createTimestampFormat();
65         }
66       };
67 
createTimestampFormat()68   private static SimpleDateFormat createTimestampFormat() {
69     SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
70     GregorianCalendar calendar =
71       new GregorianCalendar(TimeZone.getTimeZone("UTC"));
72     // We use Proleptic Gregorian Calendar (i.e., Gregorian calendar extends
73     // backwards to year one) for timestamp formating.
74     calendar.setGregorianChange(new Date(Long.MIN_VALUE));
75     sdf.setCalendar(calendar);
76     return sdf;
77   }
78 
TimeUtil()79   private TimeUtil() {}
80 
81   /**
82    * Convert Timestamp to RFC 3339 date string format. The output will always
83    * be Z-normalized and uses 3, 6 or 9 fractional digits as required to
84    * represent the exact value. Note that Timestamp can only represent time
85    * from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. See
86    * https://www.ietf.org/rfc/rfc3339.txt
87    *
88    * <p>Example of generated format: "1972-01-01T10:00:20.021Z"
89    *
90    * @return The string representation of the given timestamp.
91    * @throws IllegalArgumentException if the given timestamp is not in the
92    *         valid range.
93    */
toString(Timestamp timestamp)94   public static String toString(Timestamp timestamp)
95     throws IllegalArgumentException {
96     StringBuilder result = new StringBuilder();
97     // Format the seconds part.
98     if (timestamp.getSeconds() < TIMESTAMP_SECONDS_MIN
99         || timestamp.getSeconds() > TIMESTAMP_SECONDS_MAX) {
100       throw new IllegalArgumentException("Timestamp is out of range.");
101     }
102     Date date = new Date(timestamp.getSeconds() * MILLIS_PER_SECOND);
103     result.append(timestampFormat.get().format(date));
104     // Format the nanos part.
105     if (timestamp.getNanos() < 0 || timestamp.getNanos() >= NANOS_PER_SECOND) {
106       throw new IllegalArgumentException("Timestamp has invalid nanos value.");
107     }
108     if (timestamp.getNanos() != 0) {
109       result.append(".");
110       result.append(formatNanos(timestamp.getNanos()));
111     }
112     result.append("Z");
113     return result.toString();
114   }
115 
116   /**
117    * Parse from RFC 3339 date string to Timestamp. This method accepts all
118    * outputs of {@link #toString(Timestamp)} and it also accepts any fractional
119    * digits (or none) and any offset as long as they fit into nano-seconds
120    * precision.
121    *
122    * <p>Example of accepted format: "1972-01-01T10:00:20.021-05:00"
123    *
124    * @return A Timestamp parsed from the string.
125    * @throws ParseException if parsing fails.
126    */
127 
parseTimestamp(String value)128   public static Timestamp parseTimestamp(String value) throws ParseException {
129     int dayOffset = value.indexOf('T');
130     if (dayOffset == -1) {
131       throw new ParseException(
132         "Failed to parse timestamp: invalid timestamp \"" + value + "\"", 0);
133     }
134     int timezoneOffsetPosition = value.indexOf('Z', dayOffset);
135     if (timezoneOffsetPosition == -1) {
136       timezoneOffsetPosition = value.indexOf('+', dayOffset);
137     }
138     if (timezoneOffsetPosition == -1) {
139       timezoneOffsetPosition = value.indexOf('-', dayOffset);
140     }
141     if (timezoneOffsetPosition == -1) {
142       throw new ParseException(
143         "Failed to parse timestamp: missing valid timezone offset.", 0);
144     }
145     // Parse seconds and nanos.
146     String timeValue = value.substring(0, timezoneOffsetPosition);
147     String secondValue = timeValue;
148     String nanoValue = "";
149     int pointPosition = timeValue.indexOf('.');
150     if (pointPosition != -1) {
151       secondValue = timeValue.substring(0, pointPosition);
152       nanoValue = timeValue.substring(pointPosition + 1);
153     }
154     Date date = timestampFormat.get().parse(secondValue);
155     long seconds = date.getTime() / MILLIS_PER_SECOND;
156     int nanos = nanoValue.isEmpty() ? 0 : parseNanos(nanoValue);
157     // Parse timezone offsets.
158     if (value.charAt(timezoneOffsetPosition) == 'Z') {
159       if (value.length() != timezoneOffsetPosition + 1) {
160         throw new ParseException(
161           "Failed to parse timestamp: invalid trailing data \""
162           + value.substring(timezoneOffsetPosition) + "\"", 0);
163       }
164     } else {
165       String offsetValue = value.substring(timezoneOffsetPosition + 1);
166       long offset = parseTimezoneOffset(offsetValue);
167       if (value.charAt(timezoneOffsetPosition) == '+') {
168         seconds -= offset;
169       } else {
170         seconds += offset;
171       }
172     }
173     try {
174       return normalizedTimestamp(seconds, nanos);
175     } catch (IllegalArgumentException e) {
176       throw new ParseException(
177         "Failed to parse timestmap: timestamp is out of range.", 0);
178     }
179   }
180 
181   /**
182    * Convert Duration to string format. The string format will contains 3, 6,
183    * or 9 fractional digits depending on the precision required to represent
184    * the exact Duration value. For example: "1s", "1.010s", "1.000000100s",
185    * "-3.100s" The range that can be represented by Duration is from
186    * -315,576,000,000 to +315,576,000,000 inclusive (in seconds).
187    *
188    * @return The string representation of the given duration.
189    * @throws IllegalArgumentException if the given duration is not in the valid
190    *         range.
191    */
toString(Duration duration)192   public static String toString(Duration duration)
193     throws IllegalArgumentException {
194     if (duration.getSeconds() < DURATION_SECONDS_MIN
195       || duration.getSeconds() > DURATION_SECONDS_MAX) {
196       throw new IllegalArgumentException("Duration is out of valid range.");
197     }
198     StringBuilder result = new StringBuilder();
199     long seconds = duration.getSeconds();
200     int nanos = duration.getNanos();
201     if (seconds < 0 || nanos < 0) {
202       if (seconds > 0 || nanos > 0) {
203         throw new IllegalArgumentException(
204             "Invalid duration: seconds value and nanos value must have the same"
205             + "sign.");
206       }
207       result.append("-");
208       seconds = -seconds;
209       nanos = -nanos;
210     }
211     result.append(seconds);
212     if (nanos != 0) {
213       result.append(".");
214       result.append(formatNanos(nanos));
215     }
216     result.append("s");
217     return result.toString();
218   }
219 
220   /**
221    * Parse from a string to produce a duration.
222    *
223    * @return A Duration parsed from the string.
224    * @throws ParseException if parsing fails.
225    */
parseDuration(String value)226   public static Duration parseDuration(String value) throws ParseException {
227     // Must ended with "s".
228     if (value.isEmpty() || value.charAt(value.length() - 1) != 's') {
229       throw new ParseException("Invalid duration string: " + value, 0);
230     }
231     boolean negative = false;
232     if (value.charAt(0) == '-') {
233       negative = true;
234       value = value.substring(1);
235     }
236     String secondValue = value.substring(0, value.length() - 1);
237     String nanoValue = "";
238     int pointPosition = secondValue.indexOf('.');
239     if (pointPosition != -1) {
240       nanoValue = secondValue.substring(pointPosition + 1);
241       secondValue = secondValue.substring(0, pointPosition);
242     }
243     long seconds = Long.parseLong(secondValue);
244     int nanos = nanoValue.isEmpty() ? 0 : parseNanos(nanoValue);
245     if (seconds < 0) {
246       throw new ParseException("Invalid duration string: " + value, 0);
247     }
248     if (negative) {
249       seconds = -seconds;
250       nanos = -nanos;
251     }
252     try {
253       return normalizedDuration(seconds, nanos);
254     } catch (IllegalArgumentException e) {
255       throw new ParseException("Duration value is out of range.", 0);
256     }
257   }
258 
259   /**
260    * Create a Timestamp from the number of milliseconds elapsed from the epoch.
261    */
createTimestampFromMillis(long milliseconds)262   public static Timestamp createTimestampFromMillis(long milliseconds) {
263     return normalizedTimestamp(milliseconds / MILLIS_PER_SECOND,
264       (int) (milliseconds % MILLIS_PER_SECOND * NANOS_PER_MILLISECOND));
265   }
266 
267   /**
268    * Create a Duration from the number of milliseconds.
269    */
createDurationFromMillis(long milliseconds)270   public static Duration createDurationFromMillis(long milliseconds) {
271     return normalizedDuration(milliseconds / MILLIS_PER_SECOND,
272       (int) (milliseconds % MILLIS_PER_SECOND * NANOS_PER_MILLISECOND));
273   }
274 
275   /**
276    * Convert a Timestamp to the number of milliseconds elapsed from the epoch.
277    *
278    * <p>The result will be rounded down to the nearest millisecond. E.g., if the
279    * timestamp represents "1969-12-31T23:59:59.999999999Z", it will be rounded
280    * to -1 millisecond.
281    */
toMillis(Timestamp timestamp)282   public static long toMillis(Timestamp timestamp) {
283     return timestamp.getSeconds() * MILLIS_PER_SECOND + timestamp.getNanos()
284       / NANOS_PER_MILLISECOND;
285   }
286 
287   /**
288    * Convert a Duration to the number of milliseconds.The result will be
289    * rounded towards 0 to the nearest millisecond. E.g., if the duration
290    * represents -1 nanosecond, it will be rounded to 0.
291    */
toMillis(Duration duration)292   public static long toMillis(Duration duration) {
293     return duration.getSeconds() * MILLIS_PER_SECOND + duration.getNanos()
294       / NANOS_PER_MILLISECOND;
295   }
296 
297   /**
298    * Create a Timestamp from the number of microseconds elapsed from the epoch.
299    */
createTimestampFromMicros(long microseconds)300   public static Timestamp createTimestampFromMicros(long microseconds) {
301     return normalizedTimestamp(microseconds / MICROS_PER_SECOND,
302       (int) (microseconds % MICROS_PER_SECOND * NANOS_PER_MICROSECOND));
303   }
304 
305   /**
306    * Create a Duration from the number of microseconds.
307    */
createDurationFromMicros(long microseconds)308   public static Duration createDurationFromMicros(long microseconds) {
309     return normalizedDuration(microseconds / MICROS_PER_SECOND,
310       (int) (microseconds % MICROS_PER_SECOND * NANOS_PER_MICROSECOND));
311   }
312 
313   /**
314    * Convert a Timestamp to the number of microseconds elapsed from the epoch.
315    *
316    * <p>The result will be rounded down to the nearest microsecond. E.g., if the
317    * timestamp represents "1969-12-31T23:59:59.999999999Z", it will be rounded
318    * to -1 millisecond.
319    */
toMicros(Timestamp timestamp)320   public static long toMicros(Timestamp timestamp) {
321     return timestamp.getSeconds() * MICROS_PER_SECOND + timestamp.getNanos()
322       / NANOS_PER_MICROSECOND;
323   }
324 
325   /**
326    * Convert a Duration to the number of microseconds.The result will be
327    * rounded towards 0 to the nearest microseconds. E.g., if the duration
328    * represents -1 nanosecond, it will be rounded to 0.
329    */
toMicros(Duration duration)330   public static long toMicros(Duration duration) {
331     return duration.getSeconds() * MICROS_PER_SECOND + duration.getNanos()
332       / NANOS_PER_MICROSECOND;
333   }
334 
335   /**
336    * Create a Timestamp from the number of nanoseconds elapsed from the epoch.
337    */
createTimestampFromNanos(long nanoseconds)338   public static Timestamp createTimestampFromNanos(long nanoseconds) {
339     return normalizedTimestamp(nanoseconds / NANOS_PER_SECOND,
340       (int) (nanoseconds % NANOS_PER_SECOND));
341   }
342 
343   /**
344    * Create a Duration from the number of nanoseconds.
345    */
createDurationFromNanos(long nanoseconds)346   public static Duration createDurationFromNanos(long nanoseconds) {
347     return normalizedDuration(nanoseconds / NANOS_PER_SECOND,
348       (int) (nanoseconds % NANOS_PER_SECOND));
349   }
350 
351   /**
352    * Convert a Timestamp to the number of nanoseconds elapsed from the epoch.
353    */
toNanos(Timestamp timestamp)354   public static long toNanos(Timestamp timestamp) {
355     return timestamp.getSeconds() * NANOS_PER_SECOND + timestamp.getNanos();
356   }
357 
358   /**
359    * Convert a Duration to the number of nanoseconds.
360    */
toNanos(Duration duration)361   public static long toNanos(Duration duration) {
362     return duration.getSeconds() * NANOS_PER_SECOND + duration.getNanos();
363   }
364 
365   /**
366    * Get the current time.
367    */
getCurrentTime()368   public static Timestamp getCurrentTime() {
369     return createTimestampFromMillis(System.currentTimeMillis());
370   }
371 
372   /**
373    * Get the epoch.
374    */
getEpoch()375   public static Timestamp getEpoch() {
376     return Timestamp.getDefaultInstance();
377   }
378 
379   /**
380    * Calculate the difference between two timestamps.
381    */
distance(Timestamp from, Timestamp to)382   public static Duration distance(Timestamp from, Timestamp to) {
383     return normalizedDuration(to.getSeconds() - from.getSeconds(),
384       to.getNanos() - from.getNanos());
385   }
386 
387   /**
388    * Add a duration to a timestamp.
389    */
add(Timestamp start, Duration length)390   public static Timestamp add(Timestamp start, Duration length) {
391     return normalizedTimestamp(start.getSeconds() + length.getSeconds(),
392       start.getNanos() + length.getNanos());
393   }
394 
395   /**
396    * Subtract a duration from a timestamp.
397    */
subtract(Timestamp start, Duration length)398   public static Timestamp subtract(Timestamp start, Duration length) {
399     return normalizedTimestamp(start.getSeconds() - length.getSeconds(),
400       start.getNanos() - length.getNanos());
401   }
402 
403   /**
404    * Add two durations.
405    */
add(Duration d1, Duration d2)406   public static Duration add(Duration d1, Duration d2) {
407     return normalizedDuration(d1.getSeconds() + d2.getSeconds(),
408       d1.getNanos() + d2.getNanos());
409   }
410 
411   /**
412    * Subtract a duration from another.
413    */
subtract(Duration d1, Duration d2)414   public static Duration subtract(Duration d1, Duration d2) {
415     return normalizedDuration(d1.getSeconds() - d2.getSeconds(),
416       d1.getNanos() - d2.getNanos());
417   }
418 
419   // Multiplications and divisions.
420 
multiply(Duration duration, double times)421   public static Duration multiply(Duration duration, double times) {
422     double result = duration.getSeconds() * times + duration.getNanos() * times
423       / 1000000000.0;
424     if (result < Long.MIN_VALUE || result > Long.MAX_VALUE) {
425       throw new IllegalArgumentException("Result is out of valid range.");
426     }
427     long seconds = (long) result;
428     int nanos = (int) ((result - seconds) * 1000000000);
429     return normalizedDuration(seconds, nanos);
430   }
431 
divide(Duration duration, double value)432   public static Duration divide(Duration duration, double value) {
433     return multiply(duration, 1.0 / value);
434   }
435 
multiply(Duration duration, long times)436   public static Duration multiply(Duration duration, long times) {
437     return createDurationFromBigInteger(
438       toBigInteger(duration).multiply(toBigInteger(times)));
439   }
440 
divide(Duration duration, long times)441   public static Duration divide(Duration duration, long times) {
442     return createDurationFromBigInteger(
443       toBigInteger(duration).divide(toBigInteger(times)));
444   }
445 
divide(Duration d1, Duration d2)446   public static long divide(Duration d1, Duration d2) {
447     return toBigInteger(d1).divide(toBigInteger(d2)).longValue();
448   }
449 
remainder(Duration d1, Duration d2)450   public static Duration remainder(Duration d1, Duration d2) {
451     return createDurationFromBigInteger(
452       toBigInteger(d1).remainder(toBigInteger(d2)));
453   }
454 
455   private static final BigInteger NANOS_PER_SECOND_BIG_INTEGER =
456       new BigInteger(String.valueOf(NANOS_PER_SECOND));
457 
toBigInteger(Duration duration)458   private static BigInteger toBigInteger(Duration duration) {
459     return toBigInteger(duration.getSeconds())
460       .multiply(NANOS_PER_SECOND_BIG_INTEGER)
461       .add(toBigInteger(duration.getNanos()));
462   }
463 
toBigInteger(long value)464   private static BigInteger toBigInteger(long value) {
465     return new BigInteger(String.valueOf(value));
466   }
467 
createDurationFromBigInteger(BigInteger value)468   private static Duration createDurationFromBigInteger(BigInteger value) {
469     long seconds = value.divide(
470       new BigInteger(String.valueOf(NANOS_PER_SECOND))).longValue();
471     int nanos = value.remainder(
472       new BigInteger(String.valueOf(NANOS_PER_SECOND))).intValue();
473     return normalizedDuration(seconds, nanos);
474 
475   }
476 
normalizedDuration(long seconds, int nanos)477   private static Duration normalizedDuration(long seconds, int nanos) {
478     if (nanos <= -NANOS_PER_SECOND || nanos >= NANOS_PER_SECOND) {
479       seconds += nanos / NANOS_PER_SECOND;
480       nanos %= NANOS_PER_SECOND;
481     }
482     if (seconds > 0 && nanos < 0) {
483       nanos += NANOS_PER_SECOND;
484       seconds -= 1;
485     }
486     if (seconds < 0 && nanos > 0) {
487       nanos -= NANOS_PER_SECOND;
488       seconds += 1;
489     }
490     if (seconds < DURATION_SECONDS_MIN || seconds > DURATION_SECONDS_MAX) {
491       throw new IllegalArgumentException("Duration is out of valid range.");
492     }
493     return Duration.newBuilder().setSeconds(seconds).setNanos(nanos).build();
494   }
495 
normalizedTimestamp(long seconds, int nanos)496   private static Timestamp normalizedTimestamp(long seconds, int nanos) {
497     if (nanos <= -NANOS_PER_SECOND || nanos >= NANOS_PER_SECOND) {
498       seconds += nanos / NANOS_PER_SECOND;
499       nanos %= NANOS_PER_SECOND;
500     }
501     if (nanos < 0) {
502       nanos += NANOS_PER_SECOND;
503       seconds -= 1;
504     }
505     if (seconds < TIMESTAMP_SECONDS_MIN || seconds > TIMESTAMP_SECONDS_MAX) {
506       throw new IllegalArgumentException("Timestamp is out of valid range.");
507     }
508     return Timestamp.newBuilder().setSeconds(seconds).setNanos(nanos).build();
509   }
510 
511   /**
512    * Format the nano part of a timestamp or a duration.
513    */
formatNanos(int nanos)514   private static String formatNanos(int nanos) {
515     assert nanos >= 1 && nanos <= 999999999;
516     // Determine whether to use 3, 6, or 9 digits for the nano part.
517     if (nanos % NANOS_PER_MILLISECOND == 0) {
518       return String.format("%1$03d", nanos / NANOS_PER_MILLISECOND);
519     } else if (nanos % NANOS_PER_MICROSECOND == 0) {
520       return String.format("%1$06d", nanos / NANOS_PER_MICROSECOND);
521     } else {
522       return String.format("%1$09d", nanos);
523     }
524   }
525 
parseNanos(String value)526   private static int parseNanos(String value) throws ParseException {
527     int result = 0;
528     for (int i = 0; i < 9; ++i) {
529       result = result * 10;
530       if (i < value.length()) {
531         if (value.charAt(i) < '0' || value.charAt(i) > '9') {
532           throw new ParseException("Invalid nanosecnds.", 0);
533         }
534         result += value.charAt(i) - '0';
535       }
536     }
537     return result;
538   }
539 
parseTimezoneOffset(String value)540   private static long parseTimezoneOffset(String value) throws ParseException {
541     int pos = value.indexOf(':');
542     if (pos == -1) {
543       throw new ParseException("Invalid offset value: " + value, 0);
544     }
545     String hours = value.substring(0, pos);
546     String minutes = value.substring(pos + 1);
547     return (Long.parseLong(hours) * 60 + Long.parseLong(minutes)) * 60;
548   }
549 }
550