Skip to content

Commit f98dd62

Browse files
committed
Align java datetime types with the best practices and move away from local date time assumption to UTC
1 parent b97a7ef commit f98dd62

15 files changed

Lines changed: 62 additions & 44 deletions

File tree

memex/src/main/java/com/johnlpage/memex/VehicleInspection/controller/VehicleInspectionController.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
import java.io.BufferedOutputStream;
1717
import java.io.IOException;
1818
import java.io.OutputStream;
19-
import java.util.Date;
19+
import java.time.Instant;
20+
import java.time.LocalDateTime;
21+
import java.time.ZoneOffset;
2022
import java.util.Iterator;
2123
import java.util.List;
2224
import java.util.stream.Stream;
@@ -251,8 +253,9 @@ public ResponseEntity<StreamingResponseBody> streamJsonNative() {
251253

252254
@GetMapping(value = "/inspections/asOf", produces = MediaType.APPLICATION_JSON_VALUE)
253255
public ResponseEntity<StreamingResponseBody> dataAtDate(
254-
@RequestParam(name = "asOfDate") @DateTimeFormat(pattern = "yyyyMMddHHmmss") Date asOfDate,
256+
@RequestParam(name = "asOfDate") @DateTimeFormat(pattern = "yyyyMMddHHmmss") LocalDateTime asOfDateParam,
255257
@RequestParam(name = "id") Long id) {
258+
Instant asOfDate = asOfDateParam.atZone(ZoneOffset.UTC).toInstant();
256259
return ResponseEntity.ok()
257260
.contentType(MediaType.APPLICATION_JSON)
258261
.body(

memex/src/main/java/com/johnlpage/memex/VehicleInspection/model/VehicleInspection.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
import org.springframework.data.mongodb.core.mapping.Document;
1717
import org.springframework.data.mongodb.core.mapping.Field;
1818

19-
import java.util.Date;
19+
import java.time.LocalDate;
2020
import java.util.HashMap;
2121
import java.util.Map;
2222

@@ -45,7 +45,7 @@ public class VehicleInspection {
4545
@Version
4646
Long lockVersion;
4747

48-
Date testdate;
48+
LocalDate testdate;
4949
String testclass;
5050
String testtype;
5151
String testresult;
@@ -58,7 +58,7 @@ public class VehicleInspection {
5858

5959
Long capacity;
6060

61-
Date firstusedate;
61+
LocalDate firstusedate;
6262
/* Use this to flag from the JSON we want to remove the record */
6363
@Transient
6464
@DeleteFlag

memex/src/main/java/com/johnlpage/memex/VehicleInspection/service/VehicleInspectionHistoryService.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import com.johnlpage.memex.VehicleInspection.model.VehicleInspection;
44
import com.johnlpage.memex.VehicleInspection.repository.VehicleInspectionRepository;
55

6-
import java.util.Date;
6+
import java.time.Instant;
77
import java.util.stream.Stream;
88

99
import org.springframework.stereotype.Service;
@@ -19,7 +19,7 @@ public VehicleInspectionHistoryService(VehicleInspectionRepository repository) {
1919
this.repository = repository;
2020
}
2121

22-
public Stream<VehicleInspection> asOfDate(Long id, Date asOfDate) {
22+
public Stream<VehicleInspection> asOfDate(Long id, Instant asOfDate) {
2323
return repository.GetRecordByIdAsOfDate(id, asOfDate, VehicleInspection.class);
2424
}
2525
}
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
package com.johnlpage.memex.generics.repository;
22

3-
import java.util.Date;
3+
import java.time.Instant;
44
import java.util.stream.Stream;
55

66
import org.springframework.data.mongodb.core.query.Criteria;
77

88
public interface MongoHistoryRepository<T, I> {
99

10-
Stream<T> GetRecordByIdAsOfDate(I recordId, Date asOf, Class<T> clazz);
10+
Stream<T> GetRecordByIdAsOfDate(I recordId, Instant asOf, Class<T> clazz);
1111

12-
Stream<T> GetRecordsAsOfDate(Criteria criteria, Date asOf, Class<T> clazz);
12+
Stream<T> GetRecordsAsOfDate(Criteria criteria, Instant asOf, Class<T> clazz);
1313
}

memex/src/main/java/com/johnlpage/memex/generics/repository/MongoHistoryRepositoryImpl.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import org.springframework.data.mongodb.core.aggregation.*;
1414
import org.springframework.data.mongodb.core.query.Criteria;
1515

16+
import java.time.Instant;
1617
import java.util.*;
1718
import java.util.stream.Stream;
1819

@@ -29,12 +30,12 @@ public MongoHistoryRepositoryImpl(MongoTemplate mongoTemplate, MongoVersionBean
2930
this.mongoVersion = mongoVersion;
3031
}
3132

32-
public Stream<T> GetRecordByIdAsOfDate(I recordId, Date asOf, Class<T> clazz) {
33+
public Stream<T> GetRecordByIdAsOfDate(I recordId, Instant asOf, Class<T> clazz) {
3334
Criteria criteria = Criteria.where("_id").is(recordId);
3435
return GetRecordsAsOfDate(criteria, asOf, clazz);
3536
}
3637

37-
public Stream<T> GetRecordsAsOfDate(Criteria criteria, Date asOf, Class<T> clazz) {
38+
public Stream<T> GetRecordsAsOfDate(Criteria criteria, Instant asOf, Class<T> clazz) {
3839

3940
List<AggregationOperation> stages = new ArrayList<>();
4041
String collectionName = AnnotationExtractor.getCollectionName(clazz);

memex/src/main/java/com/johnlpage/memex/generics/service/DocumentHistory.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import com.fasterxml.jackson.annotation.*;
44

5-
import java.util.Date;
5+
import java.time.Instant;
66
import java.util.Map;
77

88
import lombok.EqualsAndHashCode;
@@ -33,7 +33,7 @@ public class DocumentHistory {
3333
@EqualsAndHashCode.Include
3434
ObjectId historyId;
3535
Object recordId;
36-
Date timestamp;
36+
Instant timestamp;
3737
String type; // TOOO enum?
3838
Map<String, Object> changes;
3939
}

memex/src/main/java/com/johnlpage/memex/generics/service/HistoryTriggerService.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import com.mongodb.bulk.BulkWriteUpsert;
1212
import com.mongodb.client.ClientSession;
1313

14+
import java.time.Instant;
1415
import java.util.*;
1516

1617
import org.bson.Document;
@@ -60,7 +61,7 @@ public void postWriteTrigger(
6061
DocumentHistory vih = new DocumentHistory();
6162
vih.setRecordId(v.getId());
6263
vih.setType("insert");
63-
vih.setTimestamp(new Date());
64+
vih.setTimestamp(Instant.now());
6465
history.add(vih); // Add this history records to the history list
6566
}
6667
}
@@ -103,7 +104,7 @@ public void postWriteTrigger(
103104
previousValues.remove(OptimizedMongoLoadRepositoryImpl.LAST_UPDATE_DATE);
104105

105106
vih.setChanges(previousValues);
106-
vih.setTimestamp(new Date());
107+
vih.setTimestamp(Instant.now());
107108
history.add(vih); // Add this history records to the history list
108109
}
109110
}
@@ -123,7 +124,7 @@ public void postWriteTrigger(
123124
});
124125
vih.setChanges(finalState);
125126
vih.setType("delete");
126-
vih.setTimestamp(new Date());
127+
vih.setTimestamp(Instant.now());
127128
history.add(vih); // Add this history records to the history list
128129
}
129130
}

memex/src/main/java/com/johnlpage/memex/util/MongoSchemaGenerator.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,13 @@ private static String mapJavaToBson(Class<?> type) {
143143
if (type == Boolean.class || type == boolean.class) return "bool";
144144
if (Date.class.isAssignableFrom(type)) return "date";
145145
if (type == java.time.Instant.class)
146-
return "date"; // Works as logn as we have a typemapper
146+
return "date"; // Works as long as we have a typemapper
147+
if (type == java.time.LocalDate.class)
148+
return "date"; // Stored as BSON Date via native java.time codecs
149+
if (type == java.time.LocalTime.class)
150+
return "date"; // Stored as BSON Date via native java.time codecs
151+
if (type == java.time.LocalDateTime.class)
152+
return "date"; // Stored as BSON Date via native java.time codecs
147153
if (UUID.class.isAssignableFrom(type)) return "string";
148154

149155
// Native BSON types (preferred for performance)
@@ -208,7 +214,8 @@ private static String createErrorMessage(Class<?> type) {
208214
if (pkg.startsWith("java.time")) {
209215
return "Unsupported type: "
210216
+ type.getName()
211-
+ ". Convert to java.util.Date or store as ISO-8601 String or epoch Long.";
217+
+ ". Use Instant for timestamps, LocalDate for date-only values, LocalTime for time-only values,"
218+
+ " LocalDateTime for combined date/time, or store as ISO-8601 String or epoch Long.";
212219
}
213220

214221
if (pkg.startsWith("java.sql")) {

memex/src/main/java/com/johnlpage/memex/util/ObjectConverter.java

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,14 @@
22

33
import java.time.LocalDate;
44
import java.time.LocalDateTime;
5+
import java.time.ZoneOffset;
56
import java.time.ZonedDateTime;
67
import java.time.format.DateTimeFormatter;
78
import java.time.format.DateTimeParseException;
8-
import java.util.*;
9+
import java.util.ArrayList;
10+
import java.util.HashMap;
11+
import java.util.List;
12+
import java.util.Map;
913

1014
public class ObjectConverter {
1115

@@ -32,18 +36,13 @@ public static Object convertObject(Object input) {
3236
// Try parsing as different date/time types
3337
if (str.contains("T")) {
3438
if (str.contains("Z") || str.contains("+") || str.contains("-")) {
35-
return Date.from(ZonedDateTime.parse(str, formatter).toInstant());
39+
return ZonedDateTime.parse(str, formatter).toInstant();
3640
} else {
37-
return Date.from(
38-
LocalDateTime.parse(str, formatter)
39-
.atZone(java.time.ZoneOffset.UTC)
40-
.toInstant());
41+
return LocalDateTime.parse(str, formatter)
42+
.toInstant(ZoneOffset.UTC);
4143
}
4244
} else {
43-
return Date.from(
44-
LocalDate.parse(str, formatter)
45-
.atStartOfDay(java.time.ZoneOffset.UTC)
46-
.toInstant());
45+
return LocalDate.parse(str, formatter);
4746
}
4847
} catch (DateTimeParseException e) {
4948
// Continue to next formatter

memex/src/test/java/com/johnlpage/memex/cucumber/steps/TimeManagementSteps.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import io.cucumber.java.en.Given;
55
import org.springframework.beans.factory.annotation.Autowired;
66

7+
import java.time.ZoneOffset;
78
import java.time.ZonedDateTime;
89
import java.time.format.DateTimeFormatter;
910
import java.util.concurrent.TimeUnit;
@@ -15,7 +16,7 @@ public class TimeManagementSteps {
1516

1617
@Given("I capture the current timestamp to {string} with {string} pattern")
1718
public void iCaptureTheCurrentTimestamp(String macroName, String datePattern) {
18-
ZonedDateTime capturedTimestamp = ZonedDateTime.now();
19+
ZonedDateTime capturedTimestamp = ZonedDateTime.now(ZoneOffset.UTC);
1920
macroRegister.registerMacro(macroName, capturedTimestamp.format(DateTimeFormatter.ofPattern(datePattern)));
2021
}
2122

0 commit comments

Comments
 (0)