Skip to content

Commit 855708d

Browse files
authored
Allow to use BigInteger and BigDecimal for timestamps (#657)
* Allow to use BigInteger and BigDecimal for timestamps in the Point class to allow arbitrary timestamps with nanosecond precision. Fixes #267 * Add test for conversion of BitInteger/BigDecimal in Unit with less resolution * updated comments * Add method to add time as Long for binary compatibility with previous releases
1 parent 8c602ef commit 855708d

File tree

2 files changed

+189
-12
lines changed

2 files changed

+189
-12
lines changed

src/main/java/org/influxdb/dto/Point.java

100644100755
Lines changed: 59 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import java.lang.reflect.Field;
55
import java.math.BigDecimal;
66
import java.math.BigInteger;
7+
import java.math.RoundingMode;
78
import java.text.NumberFormat;
89
import java.time.Instant;
910
import java.util.Locale;
@@ -28,7 +29,7 @@
2829
public class Point {
2930
private String measurement;
3031
private Map<String, String> tags;
31-
private Long time;
32+
private Number time;
3233
private TimeUnit precision = TimeUnit.NANOSECONDS;
3334
private Map<String, Object> fields;
3435
private static final int MAX_FRACTION_DIGITS = 340;
@@ -89,9 +90,10 @@ private static void throwExceptionIfMissingAnnotation(final Class<?> clazz,
8990
*
9091
*/
9192
public static final class Builder {
93+
private static final BigInteger NANOSECONDS_PER_SECOND = BigInteger.valueOf(1000000000L);
9294
private final String measurement;
9395
private final Map<String, String> tags = new TreeMap<>();
94-
private Long time;
96+
private Number time;
9597
private TimeUnit precision;
9698
private final Map<String, Object> fields = new TreeMap<>();
9799

@@ -220,17 +222,30 @@ public Builder fields(final Map<String, Object> fieldsToAdd) {
220222
/**
221223
* Add a time to this point.
222224
*
223-
* @param timeToSet the time for this point
225+
* @param timeToSet the time for this point
224226
* @param precisionToSet the TimeUnit
225227
* @return the Builder instance.
226228
*/
227-
public Builder time(final long timeToSet, final TimeUnit precisionToSet) {
229+
public Builder time(final Number timeToSet, final TimeUnit precisionToSet) {
230+
Objects.requireNonNull(timeToSet, "timeToSet");
228231
Objects.requireNonNull(precisionToSet, "precisionToSet");
229232
this.time = timeToSet;
230233
this.precision = precisionToSet;
231234
return this;
232235
}
233236

237+
/**
238+
* Add a time to this point as Long.
239+
* only kept for binary compatibility with previous releases.
240+
*
241+
* @param timeToSet the time for this point as Long
242+
* @param precisionToSet the TimeUnit
243+
* @return the Builder instance.
244+
*/
245+
public Builder time(final Long timeToSet, final TimeUnit precisionToSet) {
246+
return time((Number) timeToSet, precisionToSet);
247+
}
248+
234249
/**
235250
* Does this builder contain any fields?
236251
*
@@ -283,9 +298,17 @@ private void addFieldByAttribute(final Object pojo, final Field field, final Col
283298
TimeColumn tc = field.getAnnotation(TimeColumn.class);
284299
if (tc != null && Instant.class.isAssignableFrom(field.getType())) {
285300
Optional.ofNullable((Instant) fieldValue).ifPresent(instant -> {
286-
TimeUnit timeUint = tc.timeUnit();
287-
this.time = TimeUnit.MILLISECONDS.convert(instant.toEpochMilli(), timeUint);
288-
this.precision = timeUint;
301+
TimeUnit timeUnit = tc.timeUnit();
302+
if (timeUnit == TimeUnit.NANOSECONDS || timeUnit == TimeUnit.MICROSECONDS) {
303+
this.time = BigInteger.valueOf(instant.getEpochSecond())
304+
.multiply(NANOSECONDS_PER_SECOND)
305+
.add(BigInteger.valueOf(instant.getNano()))
306+
.divide(BigInteger.valueOf(TimeUnit.NANOSECONDS.convert(1, timeUnit)));
307+
} else {
308+
this.time = TimeUnit.MILLISECONDS.convert(instant.toEpochMilli(), timeUnit);
309+
this.precision = timeUnit;
310+
}
311+
this.precision = timeUnit;
289312
});
290313
return;
291314
}
@@ -339,7 +362,7 @@ void setMeasurement(final String measurement) {
339362
* @param time
340363
* the time to set
341364
*/
342-
void setTime(final Long time) {
365+
void setTime(final Number time) {
343366
this.time = time;
344367
}
345368

@@ -553,13 +576,37 @@ private void formatedTime(final StringBuilder sb, final TimeUnit precision) {
553576
if (this.time == null) {
554577
return;
555578
}
556-
if (precision == null) {
557-
sb.append(" ").append(TimeUnit.NANOSECONDS.convert(this.time, this.precision));
558-
return;
579+
TimeUnit converterPrecision = precision;
580+
581+
if (converterPrecision == null) {
582+
converterPrecision = TimeUnit.NANOSECONDS;
583+
}
584+
if (this.time instanceof BigInteger) {
585+
BigInteger time = (BigInteger) this.time;
586+
long conversionFactor = converterPrecision.convert(1, this.precision);
587+
if (conversionFactor >= 1) {
588+
time = time.multiply(BigInteger.valueOf(conversionFactor));
589+
} else {
590+
conversionFactor = this.precision.convert(1, converterPrecision);
591+
time = time.divide(BigInteger.valueOf(conversionFactor));
592+
}
593+
sb.append(" ").append(time);
594+
} else if (this.time instanceof BigDecimal) {
595+
BigDecimal time = (BigDecimal) this.time;
596+
long conversionFactor = converterPrecision.convert(1, this.precision);
597+
if (conversionFactor >= 1) {
598+
time = time.multiply(BigDecimal.valueOf(conversionFactor));
599+
} else {
600+
conversionFactor = this.precision.convert(1, converterPrecision);
601+
time = time.divide(BigDecimal.valueOf(conversionFactor), RoundingMode.HALF_UP);
602+
}
603+
sb.append(" ").append(time.toBigInteger());
604+
} else {
605+
sb.append(" ").append(converterPrecision.convert(this.time.longValue(), this.precision));
559606
}
560-
sb.append(" ").append(precision.convert(this.time, this.precision));
561607
}
562608

609+
563610
private static String findMeasurementName(final Class<?> clazz) {
564611
return clazz.getAnnotation(Measurement.class).name();
565612
}

src/test/java/org/influxdb/dto/PointTest.java

100644100755
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import java.math.BigDecimal;
88
import java.math.BigInteger;
99
import java.time.Instant;
10+
import java.time.temporal.ChronoUnit;
1011
import java.util.Collections;
1112
import java.util.Date;
1213
import java.util.HashMap;
@@ -351,6 +352,104 @@ public void testBuilderHasFields() {
351352
assertThat(pointBuilder.hasFields()).isTrue();
352353
}
353354

355+
/**
356+
* Tests for #267
357+
*
358+
* @throws Exception
359+
*/
360+
@Test
361+
public void testLineProtocolBigInteger() throws Exception {
362+
// GIVEN a point with nanosecond precision farther in the future than a long can hold
363+
Instant instant = Instant.EPOCH.plus(600L * 365, ChronoUnit.DAYS);
364+
Point p = Point
365+
.measurement("measurement")
366+
.addField("foo", "bar")
367+
.time(BigInteger.valueOf(instant.getEpochSecond())
368+
.multiply(BigInteger.valueOf(1000000000L))
369+
.add(BigInteger.valueOf(instant.getNano())), TimeUnit.NANOSECONDS)
370+
.build();
371+
372+
// WHEN i call lineProtocol(TimeUnit.NANOSECONDS)
373+
String nanosTime = p.lineProtocol(TimeUnit.NANOSECONDS).replace("measurement foo=\"bar\" ", "");
374+
375+
// THEN the timestamp is in nanoseconds
376+
assertThat(nanosTime).isEqualTo(BigInteger.valueOf(instant.getEpochSecond())
377+
.multiply(BigInteger.valueOf(1000000000L))
378+
.add(BigInteger.valueOf(instant.getNano())).toString());
379+
}
380+
381+
/**
382+
* Tests for #267
383+
*
384+
* @throws Exception
385+
*/
386+
@Test
387+
public void testLineProtocolBigIntegerSeconds() throws Exception {
388+
// GIVEN a point with nanosecond precision farther in the future than a long can hold
389+
Instant instant = Instant.EPOCH.plus(600L * 365, ChronoUnit.DAYS);
390+
Point p = Point
391+
.measurement("measurement")
392+
.addField("foo", "bar")
393+
.time(BigInteger.valueOf(instant.getEpochSecond())
394+
.multiply(BigInteger.valueOf(1000000000L))
395+
.add(BigInteger.valueOf(instant.getNano())), TimeUnit.NANOSECONDS)
396+
.build();
397+
398+
// WHEN i call lineProtocol(TimeUnit.SECONDS)
399+
String secondTime = p.lineProtocol(TimeUnit.SECONDS).replace("measurement foo=\"bar\" ", "");
400+
401+
// THEN the timestamp is the seconds part of the Instant
402+
assertThat(secondTime).isEqualTo(Long.toString(instant.getEpochSecond()));
403+
}
404+
405+
/**
406+
* Tests for #267
407+
*
408+
* @throws Exception
409+
*/
410+
@Test
411+
public void testLineProtocolBigDecimal() throws Exception {
412+
// GIVEN a point with nanosecond precision farther in the future than a long can hold
413+
Instant instant = Instant.EPOCH.plus(600L * 365, ChronoUnit.DAYS);
414+
Point p = Point
415+
.measurement("measurement")
416+
.addField("foo", "bar")
417+
.time(BigDecimal.valueOf(instant.getEpochSecond())
418+
.multiply(BigDecimal.valueOf(1000000000L))
419+
.add(BigDecimal.valueOf(instant.getNano())).add(BigDecimal.valueOf(1.9123456)), TimeUnit.NANOSECONDS)
420+
.build();
421+
422+
// WHEN i call lineProtocol(TimeUnit.NANOSECONDS)
423+
String nanosTime = p.lineProtocol(TimeUnit.NANOSECONDS).replace("measurement foo=\"bar\" ", "");
424+
425+
// THEN the timestamp is the integer part of the BigDecimal
426+
assertThat(nanosTime).isEqualTo("18921600000000000001");
427+
}
428+
429+
/**
430+
* Tests for #267
431+
*
432+
* @throws Exception
433+
*/
434+
@Test
435+
public void testLineProtocolBigDecimalSeconds() throws Exception {
436+
// GIVEN a point with nanosecond precision farther in the future than a long can hold
437+
Instant instant = Instant.EPOCH.plus(600L * 365, ChronoUnit.DAYS);
438+
Point p = Point
439+
.measurement("measurement")
440+
.addField("foo", "bar")
441+
.time(BigDecimal.valueOf(instant.getEpochSecond())
442+
.multiply(BigDecimal.valueOf(1000000000L))
443+
.add(BigDecimal.valueOf(instant.getNano())).add(BigDecimal.valueOf(1.9123456)), TimeUnit.NANOSECONDS)
444+
.build();
445+
446+
// WHEN i call lineProtocol(TimeUnit.SECONDS)
447+
String secondTime = p.lineProtocol(TimeUnit.SECONDS).replace("measurement foo=\"bar\" ", "");
448+
449+
// THEN the timestamp is the seconds part of the Instant
450+
assertThat(secondTime).isEqualTo(Long.toString(instant.getEpochSecond()));
451+
}
452+
354453
/**
355454
* Tests for #182
356455
*
@@ -575,6 +674,27 @@ public void testAddFieldsFromPOJOWithTimeColumn() throws NoSuchFieldException, I
575674
pojo.time = null;
576675
}
577676

677+
@Test
678+
public void testAddFieldsFromPOJOWithTimeColumnNanoseconds() throws NoSuchFieldException, IllegalAccessException {
679+
TimeColumnPojoNano pojo = new TimeColumnPojoNano();
680+
pojo.time = Instant.now().plusNanos(13213L).plus(365L * 12000, ChronoUnit.DAYS);
681+
pojo.booleanPrimitive = true;
682+
683+
Point p = Point.measurementByPOJO(pojo.getClass()).addFieldsFromPOJO(pojo).build();
684+
Field timeField = p.getClass().getDeclaredField("time");
685+
Field precisionField = p.getClass().getDeclaredField("precision");
686+
timeField.setAccessible(true);
687+
precisionField.setAccessible(true);
688+
689+
Assertions.assertEquals(pojo.booleanPrimitive, p.getFields().get("booleanPrimitive"));
690+
Assertions.assertEquals(TimeUnit.NANOSECONDS, precisionField.get(p));
691+
Assertions.assertEquals(BigInteger.valueOf(pojo.time.getEpochSecond())
692+
.multiply(BigInteger.valueOf(1000000000L)).add(
693+
BigInteger.valueOf(pojo.time.getNano())), timeField.get(p));
694+
695+
pojo.time = null;
696+
}
697+
578698
@Test
579699
public void testAddFieldsFromPOJOWithTimeColumnNull() throws NoSuchFieldException, IllegalAccessException {
580700
TimeColumnPojo pojo = new TimeColumnPojo();
@@ -727,6 +847,16 @@ static class TimeColumnPojo {
727847
private Instant time;
728848
}
729849

850+
@Measurement(name = "tcmeasurement")
851+
static class TimeColumnPojoNano {
852+
@Column(name = "booleanPrimitive")
853+
private boolean booleanPrimitive;
854+
855+
@TimeColumn(timeUnit = TimeUnit.NANOSECONDS)
856+
@Column(name = "time")
857+
private Instant time;
858+
}
859+
730860
@Measurement(name = "mymeasurement")
731861
static class Pojo {
732862

0 commit comments

Comments
 (0)