OLD | NEW |
| (Empty) |
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 static com.google.common.base.Preconditions.checkArgument; | |
34 import static com.google.common.math.IntMath.checkedAdd; | |
35 import static com.google.common.math.IntMath.checkedSubtract; | |
36 import static com.google.common.math.LongMath.checkedAdd; | |
37 import static com.google.common.math.LongMath.checkedMultiply; | |
38 import static com.google.common.math.LongMath.checkedSubtract; | |
39 | |
40 import com.google.protobuf.Duration; | |
41 import com.google.protobuf.Timestamp; | |
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.TimeZone; | |
48 | |
49 /** | |
50 * Utilities to help create/manipulate {@code protobuf/timestamp.proto}. All ope
rations throw an | |
51 * {@link IllegalArgumentException} if the input(s) are not {@linkplain #isValid
(Timestamp) valid}. | |
52 */ | |
53 public final class Timestamps { | |
54 | |
55 // Timestamp for "0001-01-01T00:00:00Z" | |
56 static final long TIMESTAMP_SECONDS_MIN = -62135596800L; | |
57 | |
58 // Timestamp for "9999-12-31T23:59:59Z" | |
59 static final long TIMESTAMP_SECONDS_MAX = 253402300799L; | |
60 | |
61 static final long NANOS_PER_SECOND = 1000000000; | |
62 static final long NANOS_PER_MILLISECOND = 1000000; | |
63 static final long NANOS_PER_MICROSECOND = 1000; | |
64 static final long MILLIS_PER_SECOND = 1000; | |
65 static final long MICROS_PER_SECOND = 1000000; | |
66 | |
67 /** A constant holding the minimum valid {@link Timestamp}, {@code 0001-01-01T
00:00:00Z}. */ | |
68 public static final Timestamp MIN_VALUE = | |
69 Timestamp.newBuilder().setSeconds(TIMESTAMP_SECONDS_MIN).setNanos(0).build
(); | |
70 | |
71 /** | |
72 * A constant holding the maximum valid {@link Timestamp}, {@code 9999-12-31T2
3:59:59.999999999Z}. | |
73 */ | |
74 public static final Timestamp MAX_VALUE = | |
75 Timestamp.newBuilder().setSeconds(TIMESTAMP_SECONDS_MAX).setNanos(99999999
9).build(); | |
76 | |
77 private static final ThreadLocal<SimpleDateFormat> timestampFormat = | |
78 new ThreadLocal<SimpleDateFormat>() { | |
79 @Override | |
80 protected SimpleDateFormat initialValue() { | |
81 return createTimestampFormat(); | |
82 } | |
83 }; | |
84 | |
85 private static SimpleDateFormat createTimestampFormat() { | |
86 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); | |
87 GregorianCalendar calendar = new GregorianCalendar(TimeZone.getTimeZone("UTC
")); | |
88 // We use Proleptic Gregorian Calendar (i.e., Gregorian calendar extends | |
89 // backwards to year one) for timestamp formating. | |
90 calendar.setGregorianChange(new Date(Long.MIN_VALUE)); | |
91 sdf.setCalendar(calendar); | |
92 return sdf; | |
93 } | |
94 | |
95 private Timestamps() {} | |
96 | |
97 private static final Comparator<Timestamp> COMPARATOR = | |
98 new Comparator<Timestamp>() { | |
99 @Override | |
100 public int compare(Timestamp t1, Timestamp t2) { | |
101 checkValid(t1); | |
102 checkValid(t2); | |
103 int secDiff = Long.compare(t1.getSeconds(), t2.getSeconds()); | |
104 return (secDiff != 0) ? secDiff : Integer.compare(t1.getNanos(), t2.ge
tNanos()); | |
105 } | |
106 }; | |
107 | |
108 /** | |
109 * Returns a {@link Comparator} for {@link Timestamp}s which sorts in increasi
ng chronological | |
110 * order. Nulls and invalid {@link Timestamp}s are not allowed (see {@link #is
Valid}). | |
111 */ | |
112 public static Comparator<Timestamp> comparator() { | |
113 return COMPARATOR; | |
114 } | |
115 | |
116 /** | |
117 * Returns true if the given {@link Timestamp} is valid. The {@code seconds} v
alue must be in the | |
118 * range [-62,135,596,800, +253,402,300,799] (i.e., between 0001-01-01T00:00:0
0Z and | |
119 * 9999-12-31T23:59:59Z). The {@code nanos} value must be in the range [0, +99
9,999,999]. | |
120 * | |
121 * <p><b>Note:</b> Negative second values with fractional seconds must still h
ave non-negative | |
122 * nanos values that count forward in time. | |
123 */ | |
124 public static boolean isValid(Timestamp timestamp) { | |
125 return isValid(timestamp.getSeconds(), timestamp.getNanos()); | |
126 } | |
127 | |
128 /** | |
129 * Returns true if the given number of seconds and nanos is a valid {@link Tim
estamp}. The {@code | |
130 * seconds} value must be in the range [-62,135,596,800, +253,402,300,799] (i.
e., between | |
131 * 0001-01-01T00:00:00Z and 9999-12-31T23:59:59Z). The {@code nanos} value mus
t be in the range | |
132 * [0, +999,999,999]. | |
133 * | |
134 * <p><b>Note:</b> Negative second values with fractional seconds must still h
ave non-negative | |
135 * nanos values that count forward in time. | |
136 */ | |
137 public static boolean isValid(long seconds, int nanos) { | |
138 if (seconds < TIMESTAMP_SECONDS_MIN || seconds > TIMESTAMP_SECONDS_MAX) { | |
139 return false; | |
140 } | |
141 if (nanos < 0 || nanos >= NANOS_PER_SECOND) { | |
142 return false; | |
143 } | |
144 return true; | |
145 } | |
146 | |
147 /** Throws an {@link IllegalArgumentException} if the given {@link Timestamp}
is not valid. */ | |
148 public static Timestamp checkValid(Timestamp timestamp) { | |
149 long seconds = timestamp.getSeconds(); | |
150 int nanos = timestamp.getNanos(); | |
151 checkArgument( | |
152 isValid(seconds, nanos), | |
153 "Timestamp is not valid. See proto definition for valid values. " | |
154 + "Seconds (%s) must be in range [-62,135,596,800, +253,402,300,799]
. " | |
155 + "Nanos (%s) must be in range [0, +999,999,999].", | |
156 seconds, | |
157 nanos); | |
158 return timestamp; | |
159 } | |
160 | |
161 /** | |
162 * Convert Timestamp to RFC 3339 date string format. The output will always be
Z-normalized and | |
163 * uses 3, 6 or 9 fractional digits as required to represent the exact value.
Note that Timestamp | |
164 * can only represent time from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.99
9999999Z. See | |
165 * https://www.ietf.org/rfc/rfc3339.txt | |
166 * | |
167 * <p>Example of generated format: "1972-01-01T10:00:20.021Z" | |
168 * | |
169 * @return The string representation of the given timestamp. | |
170 * @throws IllegalArgumentException if the given timestamp is not in the valid
range. | |
171 */ | |
172 public static String toString(Timestamp timestamp) { | |
173 checkValid(timestamp); | |
174 | |
175 long seconds = timestamp.getSeconds(); | |
176 int nanos = timestamp.getNanos(); | |
177 | |
178 StringBuilder result = new StringBuilder(); | |
179 // Format the seconds part. | |
180 Date date = new Date(seconds * MILLIS_PER_SECOND); | |
181 result.append(timestampFormat.get().format(date)); | |
182 // Format the nanos part. | |
183 if (nanos != 0) { | |
184 result.append("."); | |
185 result.append(formatNanos(nanos)); | |
186 } | |
187 result.append("Z"); | |
188 return result.toString(); | |
189 } | |
190 | |
191 /** | |
192 * Parse from RFC 3339 date string to Timestamp. This method accepts all outpu
ts of {@link | |
193 * #toString(Timestamp)} and it also accepts any fractional digits (or none) a
nd any offset as | |
194 * long as they fit into nano-seconds precision. | |
195 * | |
196 * <p>Example of accepted format: "1972-01-01T10:00:20.021-05:00" | |
197 * | |
198 * @return A Timestamp parsed from the string. | |
199 * @throws ParseException if parsing fails. | |
200 */ | |
201 public static Timestamp parse(String value) throws ParseException { | |
202 int dayOffset = value.indexOf('T'); | |
203 if (dayOffset == -1) { | |
204 throw new ParseException("Failed to parse timestamp: invalid timestamp \""
+ value + "\"", 0); | |
205 } | |
206 int timezoneOffsetPosition = value.indexOf('Z', dayOffset); | |
207 if (timezoneOffsetPosition == -1) { | |
208 timezoneOffsetPosition = value.indexOf('+', dayOffset); | |
209 } | |
210 if (timezoneOffsetPosition == -1) { | |
211 timezoneOffsetPosition = value.indexOf('-', dayOffset); | |
212 } | |
213 if (timezoneOffsetPosition == -1) { | |
214 throw new ParseException("Failed to parse timestamp: missing valid timezon
e offset.", 0); | |
215 } | |
216 // Parse seconds and nanos. | |
217 String timeValue = value.substring(0, timezoneOffsetPosition); | |
218 String secondValue = timeValue; | |
219 String nanoValue = ""; | |
220 int pointPosition = timeValue.indexOf('.'); | |
221 if (pointPosition != -1) { | |
222 secondValue = timeValue.substring(0, pointPosition); | |
223 nanoValue = timeValue.substring(pointPosition + 1); | |
224 } | |
225 Date date = timestampFormat.get().parse(secondValue); | |
226 long seconds = date.getTime() / MILLIS_PER_SECOND; | |
227 int nanos = nanoValue.isEmpty() ? 0 : parseNanos(nanoValue); | |
228 // Parse timezone offsets. | |
229 if (value.charAt(timezoneOffsetPosition) == 'Z') { | |
230 if (value.length() != timezoneOffsetPosition + 1) { | |
231 throw new ParseException( | |
232 "Failed to parse timestamp: invalid trailing data \"" | |
233 + value.substring(timezoneOffsetPosition) | |
234 + "\"", | |
235 0); | |
236 } | |
237 } else { | |
238 String offsetValue = value.substring(timezoneOffsetPosition + 1); | |
239 long offset = parseTimezoneOffset(offsetValue); | |
240 if (value.charAt(timezoneOffsetPosition) == '+') { | |
241 seconds -= offset; | |
242 } else { | |
243 seconds += offset; | |
244 } | |
245 } | |
246 try { | |
247 return normalizedTimestamp(seconds, nanos); | |
248 } catch (IllegalArgumentException e) { | |
249 throw new ParseException("Failed to parse timestamp: timestamp is out of r
ange.", 0); | |
250 } | |
251 } | |
252 | |
253 /** Create a Timestamp from the number of seconds elapsed from the epoch. */ | |
254 public static Timestamp fromSeconds(long seconds) { | |
255 return normalizedTimestamp(seconds, 0); | |
256 } | |
257 | |
258 /** | |
259 * Convert a Timestamp to the number of seconds elapsed from the epoch. | |
260 * | |
261 * <p>The result will be rounded down to the nearest second. E.g., if the time
stamp represents | |
262 * "1969-12-31T23:59:59.999999999Z", it will be rounded to -1 second. | |
263 */ | |
264 public static long toSeconds(Timestamp timestamp) { | |
265 return checkValid(timestamp).getSeconds(); | |
266 } | |
267 | |
268 /** Create a Timestamp from the number of milliseconds elapsed from the epoch.
*/ | |
269 public static Timestamp fromMillis(long milliseconds) { | |
270 return normalizedTimestamp( | |
271 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
timestamp | |
279 * represents "1969-12-31T23:59:59.999999999Z", it will be rounded to -1 milli
second. | |
280 */ | |
281 public static long toMillis(Timestamp timestamp) { | |
282 checkValid(timestamp); | |
283 return checkedAdd( | |
284 checkedMultiply(timestamp.getSeconds(), MILLIS_PER_SECOND), | |
285 timestamp.getNanos() / NANOS_PER_MILLISECOND); | |
286 } | |
287 | |
288 /** Create a Timestamp from the number of microseconds elapsed from the epoch.
*/ | |
289 public static Timestamp fromMicros(long microseconds) { | |
290 return normalizedTimestamp( | |
291 microseconds / MICROS_PER_SECOND, | |
292 (int) (microseconds % MICROS_PER_SECOND * NANOS_PER_MICROSECOND)); | |
293 } | |
294 | |
295 /** | |
296 * Convert a Timestamp to the number of microseconds elapsed from the epoch. | |
297 * | |
298 * <p>The result will be rounded down to the nearest microsecond. E.g., if the
timestamp | |
299 * represents "1969-12-31T23:59:59.999999999Z", it will be rounded to -1 milli
second. | |
300 */ | |
301 public static long toMicros(Timestamp timestamp) { | |
302 checkValid(timestamp); | |
303 return checkedAdd( | |
304 checkedMultiply(timestamp.getSeconds(), MICROS_PER_SECOND), | |
305 timestamp.getNanos() / NANOS_PER_MICROSECOND); | |
306 } | |
307 | |
308 /** Create a Timestamp from the number of nanoseconds elapsed from the epoch.
*/ | |
309 public static Timestamp fromNanos(long nanoseconds) { | |
310 return normalizedTimestamp( | |
311 nanoseconds / NANOS_PER_SECOND, (int) (nanoseconds % NANOS_PER_SECOND)); | |
312 } | |
313 | |
314 /** Convert a Timestamp to the number of nanoseconds elapsed from the epoch. *
/ | |
315 public static long toNanos(Timestamp timestamp) { | |
316 checkValid(timestamp); | |
317 return checkedAdd( | |
318 checkedMultiply(timestamp.getSeconds(), NANOS_PER_SECOND), timestamp.get
Nanos()); | |
319 } | |
320 | |
321 /** Calculate the difference between two timestamps. */ | |
322 public static Duration between(Timestamp from, Timestamp to) { | |
323 checkValid(from); | |
324 checkValid(to); | |
325 return Durations.normalizedDuration( | |
326 checkedSubtract(to.getSeconds(), from.getSeconds()), | |
327 checkedSubtract(to.getNanos(), from.getNanos())); | |
328 } | |
329 | |
330 /** Add a duration to a timestamp. */ | |
331 public static Timestamp add(Timestamp start, Duration length) { | |
332 checkValid(start); | |
333 Durations.checkValid(length); | |
334 return normalizedTimestamp( | |
335 checkedAdd(start.getSeconds(), length.getSeconds()), | |
336 checkedAdd(start.getNanos(), length.getNanos())); | |
337 } | |
338 | |
339 /** Subtract a duration from a timestamp. */ | |
340 public static Timestamp subtract(Timestamp start, Duration length) { | |
341 checkValid(start); | |
342 Durations.checkValid(length); | |
343 return normalizedTimestamp( | |
344 checkedSubtract(start.getSeconds(), length.getSeconds()), | |
345 checkedSubtract(start.getNanos(), length.getNanos())); | |
346 } | |
347 | |
348 static Timestamp normalizedTimestamp(long seconds, int nanos) { | |
349 if (nanos <= -NANOS_PER_SECOND || nanos >= NANOS_PER_SECOND) { | |
350 seconds = checkedAdd(seconds, nanos / NANOS_PER_SECOND); | |
351 nanos %= NANOS_PER_SECOND; | |
352 } | |
353 if (nanos < 0) { | |
354 nanos += NANOS_PER_SECOND; // no overflow since nanos is negative (and we'
re adding) | |
355 seconds = checkedSubtract(seconds, 1); | |
356 } | |
357 Timestamp timestamp = Timestamp.newBuilder().setSeconds(seconds).setNanos(na
nos).build(); | |
358 return checkValid(timestamp); | |
359 } | |
360 | |
361 private static long parseTimezoneOffset(String value) throws ParseException { | |
362 int pos = value.indexOf(':'); | |
363 if (pos == -1) { | |
364 throw new ParseException("Invalid offset value: " + value, 0); | |
365 } | |
366 String hours = value.substring(0, pos); | |
367 String minutes = value.substring(pos + 1); | |
368 return (Long.parseLong(hours) * 60 + Long.parseLong(minutes)) * 60; | |
369 } | |
370 | |
371 static int parseNanos(String value) throws ParseException { | |
372 int result = 0; | |
373 for (int i = 0; i < 9; ++i) { | |
374 result = result * 10; | |
375 if (i < value.length()) { | |
376 if (value.charAt(i) < '0' || value.charAt(i) > '9') { | |
377 throw new ParseException("Invalid nanoseconds.", 0); | |
378 } | |
379 result += value.charAt(i) - '0'; | |
380 } | |
381 } | |
382 return result; | |
383 } | |
384 | |
385 /** Format the nano part of a timestamp or a duration. */ | |
386 static String formatNanos(int nanos) { | |
387 // Determine whether to use 3, 6, or 9 digits for the nano part. | |
388 if (nanos % NANOS_PER_MILLISECOND == 0) { | |
389 return String.format("%1$03d", nanos / NANOS_PER_MILLISECOND); | |
390 } else if (nanos % NANOS_PER_MICROSECOND == 0) { | |
391 return String.format("%1$06d", nanos / NANOS_PER_MICROSECOND); | |
392 } else { | |
393 return String.format("%1$09d", nanos); | |
394 } | |
395 } | |
396 } | |
OLD | NEW |