• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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