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