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