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.text.ParseException; 37 import java.text.SimpleDateFormat; 38 import java.util.Date; 39 import java.util.GregorianCalendar; 40 import java.util.TimeZone; 41 42 /** 43 * Utilities to help create/manipulate {@code protobuf/timestamp.proto}. 44 */ 45 public final class Timestamps { 46 // Timestamp for "0001-01-01T00:00:00Z" 47 static final long TIMESTAMP_SECONDS_MIN = -62135596800L; 48 49 // Timestamp for "9999-12-31T23:59:59Z" 50 static final long TIMESTAMP_SECONDS_MAX = 253402300799L; 51 52 static final long NANOS_PER_SECOND = 1000000000; 53 static final long NANOS_PER_MILLISECOND = 1000000; 54 static final long NANOS_PER_MICROSECOND = 1000; 55 static final long MILLIS_PER_SECOND = 1000; 56 static final long MICROS_PER_SECOND = 1000000; 57 58 // TODO(kak): Do we want to expose Timestamp constants for MAX/MIN? 59 60 private static final ThreadLocal<SimpleDateFormat> timestampFormat = 61 new ThreadLocal<SimpleDateFormat>() { 62 protected SimpleDateFormat initialValue() { 63 return createTimestampFormat(); 64 } 65 }; 66 createTimestampFormat()67 private static SimpleDateFormat createTimestampFormat() { 68 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); 69 GregorianCalendar calendar = new GregorianCalendar(TimeZone.getTimeZone("UTC")); 70 // We use Proleptic Gregorian Calendar (i.e., Gregorian calendar extends 71 // backwards to year one) for timestamp formating. 72 calendar.setGregorianChange(new Date(Long.MIN_VALUE)); 73 sdf.setCalendar(calendar); 74 return sdf; 75 } 76 Timestamps()77 private Timestamps() {} 78 79 /** 80 * Returns true if the given {@link Timestamp} is valid. The {@code seconds} value must be in the 81 * range [-62,135,596,800, +253,402,300,799] (i.e., between 0001-01-01T00:00:00Z and 82 * 9999-12-31T23:59:59Z). The {@code nanos} value must be in the range [0, +999,999,999]. 83 * 84 * <p>Note: Negative second values with fractions must still have non-negative nanos value that 85 * counts forward in time. 86 */ isValid(Timestamp timestamp)87 public static boolean isValid(Timestamp timestamp) { 88 return isValid(timestamp.getSeconds(), timestamp.getNanos()); 89 } 90 91 /** 92 * Returns true if the given number of seconds and nanos is a valid {@link Timestamp}. The 93 * {@code seconds} value must be in the range [-62,135,596,800, +253,402,300,799] (i.e., between 94 * 0001-01-01T00:00:00Z and 9999-12-31T23:59:59Z). The {@code nanos} value must be in the range 95 * [0, +999,999,999]. 96 * 97 * <p>Note: Negative second values with fractions must still have non-negative nanos value that 98 * counts forward in time. 99 */ isValid(long seconds, long nanos)100 public static boolean isValid(long seconds, long nanos) { 101 if (seconds < TIMESTAMP_SECONDS_MIN || seconds > TIMESTAMP_SECONDS_MAX) { 102 return false; 103 } 104 if (nanos < 0 || nanos >= NANOS_PER_SECOND) { 105 return false; 106 } 107 return true; 108 } 109 110 /** 111 * Throws an {@link IllegalArgumentException} if the given seconds/nanos are not 112 * a valid {@link Timestamp}. 113 */ checkValid(long seconds, int nanos)114 private static void checkValid(long seconds, int nanos) { 115 if (!isValid(seconds, nanos)) { 116 throw new IllegalArgumentException(String.format( 117 "Timestamp is not valid. See proto definition for valid values. " 118 + "Seconds (%s) must be in range [-62,135,596,800, +253,402,300,799]." 119 + "Nanos (%s) must be in range [0, +999,999,999].", 120 seconds, nanos)); 121 } 122 } 123 124 /** 125 * Convert Timestamp to RFC 3339 date string format. The output will always 126 * be Z-normalized and uses 3, 6 or 9 fractional digits as required to 127 * represent the exact value. Note that Timestamp can only represent time 128 * from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. See 129 * https://www.ietf.org/rfc/rfc3339.txt 130 * 131 * <p>Example of generated format: "1972-01-01T10:00:20.021Z" 132 * 133 * @return The string representation of the given timestamp. 134 * @throws IllegalArgumentException if the given timestamp is not in the 135 * valid range. 136 */ toString(Timestamp timestamp)137 public static String toString(Timestamp timestamp) { 138 long seconds = timestamp.getSeconds(); 139 int nanos = timestamp.getNanos(); 140 checkValid(seconds, nanos); 141 StringBuilder result = new StringBuilder(); 142 // Format the seconds part. 143 Date date = new Date(seconds * MILLIS_PER_SECOND); 144 result.append(timestampFormat.get().format(date)); 145 // Format the nanos part. 146 if (nanos != 0) { 147 result.append("."); 148 result.append(formatNanos(nanos)); 149 } 150 result.append("Z"); 151 return result.toString(); 152 } 153 154 /** 155 * Parse from RFC 3339 date string to Timestamp. This method accepts all 156 * outputs of {@link #toString(Timestamp)} and it also accepts any fractional 157 * digits (or none) and any offset as long as they fit into nano-seconds 158 * precision. 159 * 160 * <p>Example of accepted format: "1972-01-01T10:00:20.021-05:00" 161 * 162 * @return A Timestamp parsed from the string. 163 * @throws ParseException if parsing fails. 164 */ parse(String value)165 public static Timestamp parse(String value) throws ParseException { 166 int dayOffset = value.indexOf('T'); 167 if (dayOffset == -1) { 168 throw new ParseException("Failed to parse timestamp: invalid timestamp \"" + value + "\"", 0); 169 } 170 int timezoneOffsetPosition = value.indexOf('Z', dayOffset); 171 if (timezoneOffsetPosition == -1) { 172 timezoneOffsetPosition = value.indexOf('+', dayOffset); 173 } 174 if (timezoneOffsetPosition == -1) { 175 timezoneOffsetPosition = value.indexOf('-', dayOffset); 176 } 177 if (timezoneOffsetPosition == -1) { 178 throw new ParseException("Failed to parse timestamp: missing valid timezone offset.", 0); 179 } 180 // Parse seconds and nanos. 181 String timeValue = value.substring(0, timezoneOffsetPosition); 182 String secondValue = timeValue; 183 String nanoValue = ""; 184 int pointPosition = timeValue.indexOf('.'); 185 if (pointPosition != -1) { 186 secondValue = timeValue.substring(0, pointPosition); 187 nanoValue = timeValue.substring(pointPosition + 1); 188 } 189 Date date = timestampFormat.get().parse(secondValue); 190 long seconds = date.getTime() / MILLIS_PER_SECOND; 191 int nanos = nanoValue.isEmpty() ? 0 : parseNanos(nanoValue); 192 // Parse timezone offsets. 193 if (value.charAt(timezoneOffsetPosition) == 'Z') { 194 if (value.length() != timezoneOffsetPosition + 1) { 195 throw new ParseException( 196 "Failed to parse timestamp: invalid trailing data \"" 197 + value.substring(timezoneOffsetPosition) 198 + "\"", 199 0); 200 } 201 } else { 202 String offsetValue = value.substring(timezoneOffsetPosition + 1); 203 long offset = parseTimezoneOffset(offsetValue); 204 if (value.charAt(timezoneOffsetPosition) == '+') { 205 seconds -= offset; 206 } else { 207 seconds += offset; 208 } 209 } 210 try { 211 return normalizedTimestamp(seconds, nanos); 212 } catch (IllegalArgumentException e) { 213 throw new ParseException("Failed to parse timestmap: timestamp is out of range.", 0); 214 } 215 } 216 217 /** 218 * Create a Timestamp from the number of milliseconds elapsed from the epoch. 219 */ fromMillis(long milliseconds)220 public static Timestamp fromMillis(long milliseconds) { 221 return normalizedTimestamp( 222 milliseconds / MILLIS_PER_SECOND, 223 (int) (milliseconds % MILLIS_PER_SECOND * NANOS_PER_MILLISECOND)); 224 } 225 226 /** 227 * Convert a Timestamp to the number of milliseconds elapsed from the epoch. 228 * 229 * <p>The result will be rounded down to the nearest millisecond. E.g., if the 230 * timestamp represents "1969-12-31T23:59:59.999999999Z", it will be rounded 231 * to -1 millisecond. 232 */ toMillis(Timestamp timestamp)233 public static long toMillis(Timestamp timestamp) { 234 return timestamp.getSeconds() * MILLIS_PER_SECOND 235 + timestamp.getNanos() / NANOS_PER_MILLISECOND; 236 } 237 238 /** 239 * Create a Timestamp from the number of microseconds elapsed from the epoch. 240 */ fromMicros(long microseconds)241 public static Timestamp fromMicros(long microseconds) { 242 return normalizedTimestamp( 243 microseconds / MICROS_PER_SECOND, 244 (int) (microseconds % MICROS_PER_SECOND * NANOS_PER_MICROSECOND)); 245 } 246 247 /** 248 * Convert a Timestamp to the number of microseconds elapsed from the epoch. 249 * 250 * <p>The result will be rounded down to the nearest microsecond. E.g., if the 251 * timestamp represents "1969-12-31T23:59:59.999999999Z", it will be rounded 252 * to -1 millisecond. 253 */ toMicros(Timestamp timestamp)254 public static long toMicros(Timestamp timestamp) { 255 return timestamp.getSeconds() * MICROS_PER_SECOND 256 + timestamp.getNanos() / NANOS_PER_MICROSECOND; 257 } 258 259 /** 260 * Create a Timestamp from the number of nanoseconds elapsed from the epoch. 261 */ fromNanos(long nanoseconds)262 public static Timestamp fromNanos(long nanoseconds) { 263 return normalizedTimestamp( 264 nanoseconds / NANOS_PER_SECOND, (int) (nanoseconds % NANOS_PER_SECOND)); 265 } 266 267 /** 268 * Convert a Timestamp to the number of nanoseconds elapsed from the epoch. 269 */ toNanos(Timestamp timestamp)270 public static long toNanos(Timestamp timestamp) { 271 return timestamp.getSeconds() * NANOS_PER_SECOND + timestamp.getNanos(); 272 } 273 274 /** 275 * Calculate the difference between two timestamps. 276 */ between(Timestamp from, Timestamp to)277 public static Duration between(Timestamp from, Timestamp to) { 278 return Durations.normalizedDuration( 279 to.getSeconds() - from.getSeconds(), to.getNanos() - from.getNanos()); 280 } 281 282 /** 283 * Add a duration to a timestamp. 284 */ add(Timestamp start, Duration length)285 public static Timestamp add(Timestamp start, Duration length) { 286 return normalizedTimestamp( 287 start.getSeconds() + length.getSeconds(), start.getNanos() + length.getNanos()); 288 } 289 290 /** 291 * Subtract a duration from a timestamp. 292 */ subtract(Timestamp start, Duration length)293 public static Timestamp subtract(Timestamp start, Duration length) { 294 return normalizedTimestamp( 295 start.getSeconds() - length.getSeconds(), start.getNanos() - length.getNanos()); 296 } 297 normalizedTimestamp(long seconds, int nanos)298 private static Timestamp normalizedTimestamp(long seconds, int nanos) { 299 if (nanos <= -NANOS_PER_SECOND || nanos >= NANOS_PER_SECOND) { 300 seconds += nanos / NANOS_PER_SECOND; 301 nanos %= NANOS_PER_SECOND; 302 } 303 if (nanos < 0) { 304 nanos += NANOS_PER_SECOND; 305 seconds -= 1; 306 } 307 checkValid(seconds, nanos); 308 return Timestamp.newBuilder().setSeconds(seconds).setNanos(nanos).build(); 309 } 310 parseTimezoneOffset(String value)311 private static long parseTimezoneOffset(String value) throws ParseException { 312 int pos = value.indexOf(':'); 313 if (pos == -1) { 314 throw new ParseException("Invalid offset value: " + value, 0); 315 } 316 String hours = value.substring(0, pos); 317 String minutes = value.substring(pos + 1); 318 return (Long.parseLong(hours) * 60 + Long.parseLong(minutes)) * 60; 319 } 320 parseNanos(String value)321 static int parseNanos(String value) throws ParseException { 322 int result = 0; 323 for (int i = 0; i < 9; ++i) { 324 result = result * 10; 325 if (i < value.length()) { 326 if (value.charAt(i) < '0' || value.charAt(i) > '9') { 327 throw new ParseException("Invalid nanosecnds.", 0); 328 } 329 result += value.charAt(i) - '0'; 330 } 331 } 332 return result; 333 } 334 335 /** 336 * Format the nano part of a timestamp or a duration. 337 */ formatNanos(int nanos)338 static String formatNanos(int nanos) { 339 assert nanos >= 1 && nanos <= 999999999; 340 // Determine whether to use 3, 6, or 9 digits for the nano part. 341 if (nanos % NANOS_PER_MILLISECOND == 0) { 342 return String.format("%1$03d", nanos / NANOS_PER_MILLISECOND); 343 } else if (nanos % NANOS_PER_MICROSECOND == 0) { 344 return String.format("%1$06d", nanos / NANOS_PER_MICROSECOND); 345 } else { 346 return String.format("%1$09d", nanos); 347 } 348 } 349 } 350