From fa8957d81af625dd45d459661ce5564b0b701a8c Mon Sep 17 00:00:00 2001 From: Nico Piel Date: Thu, 20 Nov 2025 01:17:58 +0100 Subject: [PATCH] Replace SimpleDateFormat with DateTimeFormatter Replace usages of the non-thread-safe date formatter with java.time's DateTimeFormatter and related APIs to improve thread-safety and correctness when parsing/formatting dates and time zones. Update calendar parameter conversion to use ZonedDateTime/Instant and adapt log timestamp formatting to use DateTimeFormatter. Removes unused imports and modernizes date handling to avoid concurrency issues introduced by SimpleDateFormat. Collectively decided to use `DateTimeFormatter` instead of `FastDateTime`. Signed-off-by: Nico Piel --- .../CalendarParamConverterProvider.java | 41 +++++++++++++------ .../plugins/serverlog/ServerLogItem.java | 8 ++-- 2 files changed, 33 insertions(+), 16 deletions(-) diff --git a/server/src/com/mirth/connect/client/core/api/providers/CalendarParamConverterProvider.java b/server/src/com/mirth/connect/client/core/api/providers/CalendarParamConverterProvider.java index 9436bbcb62..f194439702 100644 --- a/server/src/com/mirth/connect/client/core/api/providers/CalendarParamConverterProvider.java +++ b/server/src/com/mirth/connect/client/core/api/providers/CalendarParamConverterProvider.java @@ -9,23 +9,24 @@ package com.mirth.connect.client.core.api.providers; -import java.lang.annotation.Annotation; -import java.lang.reflect.Type; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Calendar; - import javax.inject.Singleton; import javax.ws.rs.ProcessingException; import javax.ws.rs.ext.ParamConverter; import javax.ws.rs.ext.ParamConverterProvider; import javax.ws.rs.ext.Provider; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.Calendar; @Provider @Singleton public class CalendarParamConverterProvider implements ParamConverterProvider { - - private static final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); + private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); @Override public ParamConverter getConverter(Class rawType, Type genericType, Annotation[] annotations) { @@ -38,10 +39,16 @@ public T fromString(String value) { } try { - Calendar date = Calendar.getInstance(); - date.setTime(format.parse(value)); - return (T) date; - } catch (ParseException e) { + // Parse the incoming string as a ZonedDateTime using the given pattern + ZonedDateTime zdt = ZonedDateTime.parse(value, FORMATTER); + + Calendar calendar = Calendar.getInstance(java.util.TimeZone.getTimeZone(zdt.getZone())); + calendar.setTimeInMillis(zdt.toInstant().toEpochMilli()); + + @SuppressWarnings("unchecked") + T result = (T) calendar; + return result; + } catch (DateTimeParseException e) { throw new ProcessingException(e); } } @@ -51,7 +58,15 @@ public String toString(T value) { if (value == null) { return null; } - return format.format(((Calendar) value).getTime()); + + Calendar calendar = (Calendar) value; + + // Build a ZonedDateTime from the Calendar (date/time + time zone) + Instant instant = calendar.toInstant(); + ZoneId zoneId = calendar.getTimeZone().toZoneId(); + ZonedDateTime zdt = ZonedDateTime.ofInstant(instant, zoneId); + + return FORMATTER.format(zdt); } }; } diff --git a/server/src/com/mirth/connect/plugins/serverlog/ServerLogItem.java b/server/src/com/mirth/connect/plugins/serverlog/ServerLogItem.java index a88bdf3f9a..0008fa8a26 100644 --- a/server/src/com/mirth/connect/plugins/serverlog/ServerLogItem.java +++ b/server/src/com/mirth/connect/plugins/serverlog/ServerLogItem.java @@ -10,14 +10,16 @@ package com.mirth.connect.plugins.serverlog; import java.io.Serializable; -import java.text.SimpleDateFormat; + +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; import java.util.Date; import org.apache.commons.lang3.StringUtils; public class ServerLogItem implements Serializable { - public static SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); + public static DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS").withZone(ZoneId.systemDefault()); private String serverId; private Long id; @@ -123,7 +125,7 @@ public void setThrowableInformation(String throwableInformation) { public String toString() { if (id != null) { StringBuilder builder = new StringBuilder(); - builder.append('[').append(DATE_FORMAT.format(date)).append("] "); + builder.append('[').append(DATE_FORMAT.format(date.toInstant())).append("] "); builder.append(level); builder.append(" (").append(category); if (StringUtils.isNotBlank(lineNumber)) {