Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(437)

Side by Side Diff: third_party/protobuf/java/util/src/main/java/com/google/protobuf/util/Timestamps.java

Issue 2599263002: third_party/protobuf: Update to HEAD (f52e188fe4) (Closed)
Patch Set: Address comments Created 3 years, 12 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698