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 |