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 import java.text.ParseException; 36 import java.util.ArrayList; 37 import java.util.List; 38 import junit.framework.TestCase; 39 import org.junit.Assert; 40 41 /** Unit tests for {@link TimeUtil}. */ 42 public class TimeUtilTest extends TestCase { testTimestampStringFormat()43 public void testTimestampStringFormat() throws Exception { 44 Timestamp start = TimeUtil.parseTimestamp("0001-01-01T00:00:00Z"); 45 Timestamp end = TimeUtil.parseTimestamp("9999-12-31T23:59:59.999999999Z"); 46 assertEquals(TimeUtil.TIMESTAMP_SECONDS_MIN, start.getSeconds()); 47 assertEquals(0, start.getNanos()); 48 assertEquals(TimeUtil.TIMESTAMP_SECONDS_MAX, end.getSeconds()); 49 assertEquals(999999999, end.getNanos()); 50 assertEquals("0001-01-01T00:00:00Z", TimeUtil.toString(start)); 51 assertEquals("9999-12-31T23:59:59.999999999Z", TimeUtil.toString(end)); 52 53 Timestamp value = TimeUtil.parseTimestamp("1970-01-01T00:00:00Z"); 54 assertEquals(0, value.getSeconds()); 55 assertEquals(0, value.getNanos()); 56 57 // Test negative timestamps. 58 value = TimeUtil.parseTimestamp("1969-12-31T23:59:59.999Z"); 59 assertEquals(-1, value.getSeconds()); 60 // Nano part is in the range of [0, 999999999] for Timestamp. 61 assertEquals(999000000, value.getNanos()); 62 63 // Test that 3, 6, or 9 digits are used for the fractional part. 64 value = Timestamp.newBuilder().setNanos(10).build(); 65 assertEquals("1970-01-01T00:00:00.000000010Z", TimeUtil.toString(value)); 66 value = Timestamp.newBuilder().setNanos(10000).build(); 67 assertEquals("1970-01-01T00:00:00.000010Z", TimeUtil.toString(value)); 68 value = Timestamp.newBuilder().setNanos(10000000).build(); 69 assertEquals("1970-01-01T00:00:00.010Z", TimeUtil.toString(value)); 70 71 // Test that parsing accepts timezone offsets. 72 value = TimeUtil.parseTimestamp("1970-01-01T00:00:00.010+08:00"); 73 assertEquals("1969-12-31T16:00:00.010Z", TimeUtil.toString(value)); 74 value = TimeUtil.parseTimestamp("1970-01-01T00:00:00.010-08:00"); 75 assertEquals("1970-01-01T08:00:00.010Z", TimeUtil.toString(value)); 76 } 77 78 private volatile boolean stopParsingThreads = false; 79 private volatile String errorMessage = ""; 80 81 private class ParseTimestampThread extends Thread { 82 private final String[] strings; 83 private final Timestamp[] values; 84 ParseTimestampThread(String[] strings, Timestamp[] values)85 public ParseTimestampThread(String[] strings, Timestamp[] values) { 86 this.strings = strings; 87 this.values = values; 88 } 89 90 @Override run()91 public void run() { 92 int index = 0; 93 while (!stopParsingThreads) { 94 Timestamp result; 95 try { 96 result = TimeUtil.parseTimestamp(strings[index]); 97 } catch (ParseException e) { 98 errorMessage = "Failed to parse timestamp: " + strings[index]; 99 break; 100 } 101 if (result.getSeconds() != values[index].getSeconds() 102 || result.getNanos() != values[index].getNanos()) { 103 errorMessage = 104 "Actual result: " + result.toString() + ", expected: " + values[index].toString(); 105 break; 106 } 107 index = (index + 1) % strings.length; 108 } 109 } 110 } 111 testTimestampConcurrentParsing()112 public void testTimestampConcurrentParsing() throws Exception { 113 String[] timestampStrings = 114 new String[] { 115 "0001-01-01T00:00:00Z", 116 "9999-12-31T23:59:59.999999999Z", 117 "1970-01-01T00:00:00Z", 118 "1969-12-31T23:59:59.999Z", 119 }; 120 Timestamp[] timestampValues = new Timestamp[timestampStrings.length]; 121 for (int i = 0; i < timestampStrings.length; i++) { 122 timestampValues[i] = TimeUtil.parseTimestamp(timestampStrings[i]); 123 } 124 125 final int THREAD_COUNT = 16; 126 final int RUNNING_TIME = 5000; // in milliseconds. 127 final List<Thread> threads = new ArrayList<Thread>(); 128 129 stopParsingThreads = false; 130 errorMessage = ""; 131 for (int i = 0; i < THREAD_COUNT; i++) { 132 Thread thread = new ParseTimestampThread(timestampStrings, timestampValues); 133 thread.start(); 134 threads.add(thread); 135 } 136 Thread.sleep(RUNNING_TIME); 137 stopParsingThreads = true; 138 for (Thread thread : threads) { 139 thread.join(); 140 } 141 Assert.assertEquals("", errorMessage); 142 } 143 testTimetampInvalidFormat()144 public void testTimetampInvalidFormat() throws Exception { 145 try { 146 // Value too small. 147 Timestamp value = 148 Timestamp.newBuilder().setSeconds(TimeUtil.TIMESTAMP_SECONDS_MIN - 1).build(); 149 TimeUtil.toString(value); 150 Assert.fail("Exception is expected."); 151 } catch (IllegalArgumentException e) { 152 // Expected. 153 } 154 155 try { 156 // Value too large. 157 Timestamp value = 158 Timestamp.newBuilder().setSeconds(TimeUtil.TIMESTAMP_SECONDS_MAX + 1).build(); 159 TimeUtil.toString(value); 160 Assert.fail("Exception is expected."); 161 } catch (IllegalArgumentException e) { 162 // Expected. 163 } 164 165 try { 166 // Invalid nanos value. 167 Timestamp value = Timestamp.newBuilder().setNanos(-1).build(); 168 TimeUtil.toString(value); 169 Assert.fail("Exception is expected."); 170 } catch (IllegalArgumentException e) { 171 // Expected. 172 } 173 174 try { 175 // Invalid nanos value. 176 Timestamp value = Timestamp.newBuilder().setNanos(1000000000).build(); 177 TimeUtil.toString(value); 178 Assert.fail("Exception is expected."); 179 } catch (IllegalArgumentException e) { 180 // Expected. 181 } 182 183 try { 184 // Value to small. 185 TimeUtil.parseTimestamp("0000-01-01T00:00:00Z"); 186 Assert.fail("Exception is expected."); 187 } catch (ParseException e) { 188 // Expected. 189 } 190 191 try { 192 // Value to large. 193 TimeUtil.parseTimestamp("10000-01-01T00:00:00Z"); 194 Assert.fail("Exception is expected."); 195 } catch (ParseException e) { 196 // Expected. 197 } 198 199 try { 200 // Missing 'T'. 201 TimeUtil.parseTimestamp("1970-01-01 00:00:00Z"); 202 Assert.fail("Exception is expected."); 203 } catch (ParseException e) { 204 // Expected. 205 } 206 207 try { 208 // Missing 'Z'. 209 TimeUtil.parseTimestamp("1970-01-01T00:00:00"); 210 Assert.fail("Exception is expected."); 211 } catch (ParseException e) { 212 // Expected. 213 } 214 215 try { 216 // Invalid offset. 217 TimeUtil.parseTimestamp("1970-01-01T00:00:00+0000"); 218 Assert.fail("Exception is expected."); 219 } catch (ParseException e) { 220 // Expected. 221 } 222 223 try { 224 // Trailing text. 225 TimeUtil.parseTimestamp("1970-01-01T00:00:00Z0"); 226 Assert.fail("Exception is expected."); 227 } catch (ParseException e) { 228 // Expected. 229 } 230 231 try { 232 // Invalid nanosecond value. 233 TimeUtil.parseTimestamp("1970-01-01T00:00:00.ABCZ"); 234 Assert.fail("Exception is expected."); 235 } catch (ParseException e) { 236 // Expected. 237 } 238 } 239 testDurationStringFormat()240 public void testDurationStringFormat() throws Exception { 241 Timestamp start = TimeUtil.parseTimestamp("0001-01-01T00:00:00Z"); 242 Timestamp end = TimeUtil.parseTimestamp("9999-12-31T23:59:59.999999999Z"); 243 Duration duration = TimeUtil.distance(start, end); 244 assertEquals("315537897599.999999999s", TimeUtil.toString(duration)); 245 duration = TimeUtil.distance(end, start); 246 assertEquals("-315537897599.999999999s", TimeUtil.toString(duration)); 247 248 // Generated output should contain 3, 6, or 9 fractional digits. 249 duration = Duration.newBuilder().setSeconds(1).build(); 250 assertEquals("1s", TimeUtil.toString(duration)); 251 duration = Duration.newBuilder().setNanos(10000000).build(); 252 assertEquals("0.010s", TimeUtil.toString(duration)); 253 duration = Duration.newBuilder().setNanos(10000).build(); 254 assertEquals("0.000010s", TimeUtil.toString(duration)); 255 duration = Duration.newBuilder().setNanos(10).build(); 256 assertEquals("0.000000010s", TimeUtil.toString(duration)); 257 258 // Parsing accepts an fractional digits as long as they fit into nano 259 // precision. 260 duration = TimeUtil.parseDuration("0.1s"); 261 assertEquals(100000000, duration.getNanos()); 262 duration = TimeUtil.parseDuration("0.0001s"); 263 assertEquals(100000, duration.getNanos()); 264 duration = TimeUtil.parseDuration("0.0000001s"); 265 assertEquals(100, duration.getNanos()); 266 267 // Duration must support range from -315,576,000,000s to +315576000000s 268 // which includes negative values. 269 duration = TimeUtil.parseDuration("315576000000.999999999s"); 270 assertEquals(315576000000L, duration.getSeconds()); 271 assertEquals(999999999, duration.getNanos()); 272 duration = TimeUtil.parseDuration("-315576000000.999999999s"); 273 assertEquals(-315576000000L, duration.getSeconds()); 274 assertEquals(-999999999, duration.getNanos()); 275 } 276 testDurationInvalidFormat()277 public void testDurationInvalidFormat() throws Exception { 278 try { 279 // Value too small. 280 Duration value = Duration.newBuilder().setSeconds(TimeUtil.DURATION_SECONDS_MIN - 1).build(); 281 TimeUtil.toString(value); 282 Assert.fail("Exception is expected."); 283 } catch (IllegalArgumentException e) { 284 // Expected. 285 } 286 287 try { 288 // Value too large. 289 Duration value = Duration.newBuilder().setSeconds(TimeUtil.DURATION_SECONDS_MAX + 1).build(); 290 TimeUtil.toString(value); 291 Assert.fail("Exception is expected."); 292 } catch (IllegalArgumentException e) { 293 // Expected. 294 } 295 296 try { 297 // Invalid nanos value. 298 Duration value = Duration.newBuilder().setSeconds(1).setNanos(-1).build(); 299 TimeUtil.toString(value); 300 Assert.fail("Exception is expected."); 301 } catch (IllegalArgumentException e) { 302 // Expected. 303 } 304 305 try { 306 // Invalid nanos value. 307 Duration value = Duration.newBuilder().setSeconds(-1).setNanos(1).build(); 308 TimeUtil.toString(value); 309 Assert.fail("Exception is expected."); 310 } catch (IllegalArgumentException e) { 311 // Expected. 312 } 313 314 try { 315 // Value too small. 316 TimeUtil.parseDuration("-315576000001s"); 317 Assert.fail("Exception is expected."); 318 } catch (ParseException e) { 319 // Expected. 320 } 321 322 try { 323 // Value too large. 324 TimeUtil.parseDuration("315576000001s"); 325 Assert.fail("Exception is expected."); 326 } catch (ParseException e) { 327 // Expected. 328 } 329 330 try { 331 // Empty. 332 TimeUtil.parseDuration(""); 333 Assert.fail("Exception is expected."); 334 } catch (ParseException e) { 335 // Expected. 336 } 337 338 try { 339 // Missing "s". 340 TimeUtil.parseDuration("0"); 341 Assert.fail("Exception is expected."); 342 } catch (ParseException e) { 343 // Expected. 344 } 345 346 try { 347 // Invalid trailing data. 348 TimeUtil.parseDuration("0s0"); 349 Assert.fail("Exception is expected."); 350 } catch (ParseException e) { 351 // Expected. 352 } 353 354 try { 355 // Invalid prefix. 356 TimeUtil.parseDuration("--1s"); 357 Assert.fail("Exception is expected."); 358 } catch (ParseException e) { 359 // Expected. 360 } 361 } 362 testTimestampConversion()363 public void testTimestampConversion() throws Exception { 364 Timestamp timestamp = TimeUtil.parseTimestamp("1970-01-01T00:00:01.111111111Z"); 365 assertEquals(1111111111, TimeUtil.toNanos(timestamp)); 366 assertEquals(1111111, TimeUtil.toMicros(timestamp)); 367 assertEquals(1111, TimeUtil.toMillis(timestamp)); 368 timestamp = TimeUtil.createTimestampFromNanos(1111111111); 369 assertEquals("1970-01-01T00:00:01.111111111Z", TimeUtil.toString(timestamp)); 370 timestamp = TimeUtil.createTimestampFromMicros(1111111); 371 assertEquals("1970-01-01T00:00:01.111111Z", TimeUtil.toString(timestamp)); 372 timestamp = TimeUtil.createTimestampFromMillis(1111); 373 assertEquals("1970-01-01T00:00:01.111Z", TimeUtil.toString(timestamp)); 374 375 timestamp = TimeUtil.parseTimestamp("1969-12-31T23:59:59.111111111Z"); 376 assertEquals(-888888889, TimeUtil.toNanos(timestamp)); 377 assertEquals(-888889, TimeUtil.toMicros(timestamp)); 378 assertEquals(-889, TimeUtil.toMillis(timestamp)); 379 timestamp = TimeUtil.createTimestampFromNanos(-888888889); 380 assertEquals("1969-12-31T23:59:59.111111111Z", TimeUtil.toString(timestamp)); 381 timestamp = TimeUtil.createTimestampFromMicros(-888889); 382 assertEquals("1969-12-31T23:59:59.111111Z", TimeUtil.toString(timestamp)); 383 timestamp = TimeUtil.createTimestampFromMillis(-889); 384 assertEquals("1969-12-31T23:59:59.111Z", TimeUtil.toString(timestamp)); 385 } 386 testDurationConversion()387 public void testDurationConversion() throws Exception { 388 Duration duration = TimeUtil.parseDuration("1.111111111s"); 389 assertEquals(1111111111, TimeUtil.toNanos(duration)); 390 assertEquals(1111111, TimeUtil.toMicros(duration)); 391 assertEquals(1111, TimeUtil.toMillis(duration)); 392 duration = TimeUtil.createDurationFromNanos(1111111111); 393 assertEquals("1.111111111s", TimeUtil.toString(duration)); 394 duration = TimeUtil.createDurationFromMicros(1111111); 395 assertEquals("1.111111s", TimeUtil.toString(duration)); 396 duration = TimeUtil.createDurationFromMillis(1111); 397 assertEquals("1.111s", TimeUtil.toString(duration)); 398 399 duration = TimeUtil.parseDuration("-1.111111111s"); 400 assertEquals(-1111111111, TimeUtil.toNanos(duration)); 401 assertEquals(-1111111, TimeUtil.toMicros(duration)); 402 assertEquals(-1111, TimeUtil.toMillis(duration)); 403 duration = TimeUtil.createDurationFromNanos(-1111111111); 404 assertEquals("-1.111111111s", TimeUtil.toString(duration)); 405 duration = TimeUtil.createDurationFromMicros(-1111111); 406 assertEquals("-1.111111s", TimeUtil.toString(duration)); 407 duration = TimeUtil.createDurationFromMillis(-1111); 408 assertEquals("-1.111s", TimeUtil.toString(duration)); 409 } 410 testTimeOperations()411 public void testTimeOperations() throws Exception { 412 Timestamp start = TimeUtil.parseTimestamp("0001-01-01T00:00:00Z"); 413 Timestamp end = TimeUtil.parseTimestamp("9999-12-31T23:59:59.999999999Z"); 414 415 Duration duration = TimeUtil.distance(start, end); 416 assertEquals("315537897599.999999999s", TimeUtil.toString(duration)); 417 Timestamp value = TimeUtil.add(start, duration); 418 assertEquals(end, value); 419 value = TimeUtil.subtract(end, duration); 420 assertEquals(start, value); 421 422 duration = TimeUtil.distance(end, start); 423 assertEquals("-315537897599.999999999s", TimeUtil.toString(duration)); 424 value = TimeUtil.add(end, duration); 425 assertEquals(start, value); 426 value = TimeUtil.subtract(start, duration); 427 assertEquals(end, value); 428 429 // Result is larger than Long.MAX_VALUE. 430 try { 431 duration = TimeUtil.parseDuration("315537897599.999999999s"); 432 duration = TimeUtil.multiply(duration, 315537897599.999999999); 433 Assert.fail("Exception is expected."); 434 } catch (IllegalArgumentException e) { 435 // Expected. 436 } 437 438 // Result is lesser than Long.MIN_VALUE. 439 try { 440 duration = TimeUtil.parseDuration("315537897599.999999999s"); 441 duration = TimeUtil.multiply(duration, -315537897599.999999999); 442 Assert.fail("Exception is expected."); 443 } catch (IllegalArgumentException e) { 444 // Expected. 445 } 446 447 duration = TimeUtil.parseDuration("-1.125s"); 448 duration = TimeUtil.divide(duration, 2.0); 449 assertEquals("-0.562500s", TimeUtil.toString(duration)); 450 duration = TimeUtil.multiply(duration, 2.0); 451 assertEquals("-1.125s", TimeUtil.toString(duration)); 452 453 duration = TimeUtil.add(duration, duration); 454 assertEquals("-2.250s", TimeUtil.toString(duration)); 455 456 duration = TimeUtil.subtract(duration, TimeUtil.parseDuration("-1s")); 457 assertEquals("-1.250s", TimeUtil.toString(duration)); 458 459 // Multiplications (with results larger than Long.MAX_VALUE in nanoseconds). 460 duration = TimeUtil.parseDuration("0.999999999s"); 461 assertEquals( 462 "315575999684.424s", TimeUtil.toString(TimeUtil.multiply(duration, 315576000000L))); 463 duration = TimeUtil.parseDuration("-0.999999999s"); 464 assertEquals( 465 "-315575999684.424s", TimeUtil.toString(TimeUtil.multiply(duration, 315576000000L))); 466 assertEquals( 467 "315575999684.424s", TimeUtil.toString(TimeUtil.multiply(duration, -315576000000L))); 468 469 // Divisions (with values larger than Long.MAX_VALUE in nanoseconds). 470 Duration d1 = TimeUtil.parseDuration("315576000000s"); 471 Duration d2 = TimeUtil.subtract(d1, TimeUtil.createDurationFromNanos(1)); 472 assertEquals(1, TimeUtil.divide(d1, d2)); 473 assertEquals(0, TimeUtil.divide(d2, d1)); 474 assertEquals("0.000000001s", TimeUtil.toString(TimeUtil.remainder(d1, d2))); 475 assertEquals("315575999999.999999999s", TimeUtil.toString(TimeUtil.remainder(d2, d1))); 476 477 // Divisions involving negative values. 478 // 479 // (-5) / 2 = -2, remainder = -1 480 d1 = TimeUtil.parseDuration("-5s"); 481 d2 = TimeUtil.parseDuration("2s"); 482 assertEquals(-2, TimeUtil.divide(d1, d2)); 483 assertEquals(-2, TimeUtil.divide(d1, 2).getSeconds()); 484 assertEquals(-1, TimeUtil.remainder(d1, d2).getSeconds()); 485 // (-5) / (-2) = 2, remainder = -1 486 d1 = TimeUtil.parseDuration("-5s"); 487 d2 = TimeUtil.parseDuration("-2s"); 488 assertEquals(2, TimeUtil.divide(d1, d2)); 489 assertEquals(2, TimeUtil.divide(d1, -2).getSeconds()); 490 assertEquals(-1, TimeUtil.remainder(d1, d2).getSeconds()); 491 // 5 / (-2) = -2, remainder = 1 492 d1 = TimeUtil.parseDuration("5s"); 493 d2 = TimeUtil.parseDuration("-2s"); 494 assertEquals(-2, TimeUtil.divide(d1, d2)); 495 assertEquals(-2, TimeUtil.divide(d1, -2).getSeconds()); 496 assertEquals(1, TimeUtil.remainder(d1, d2).getSeconds()); 497 } 498 } 499