diff --git a/services/archive-engine/dbd/postgres_schema.txt b/services/archive-engine/dbd/postgres_schema.txt
index 1649820a49..4947bd3b6c 100644
--- a/services/archive-engine/dbd/postgres_schema.txt
+++ b/services/archive-engine/dbd/postgres_schema.txt
@@ -199,7 +199,7 @@ DROP TABLE IF EXISTS sample;
CREATE TABLE sample
(
channel_id BIGINT NOT NULL,
- smpl_time TIMESTAMP NOT NULL,
+ smpl_time TIMESTAMPTZ NOT NULL,
nanosecs BIGINT NOT NULL,
severity_id BIGINT NOT NULL,
status_id BIGINT NOT NULL,
diff --git a/services/archive-engine/src/main/java/org/csstudio/archive/writer/rdb/RDBArchiveWriter.java b/services/archive-engine/src/main/java/org/csstudio/archive/writer/rdb/RDBArchiveWriter.java
index 38c54216ee..516a3f47fa 100644
--- a/services/archive-engine/src/main/java/org/csstudio/archive/writer/rdb/RDBArchiveWriter.java
+++ b/services/archive-engine/src/main/java/org/csstudio/archive/writer/rdb/RDBArchiveWriter.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2011-2024 Oak Ridge National Laboratory.
+ * Copyright (c) 2011-2025 Oak Ridge National Laboratory.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -51,7 +51,6 @@
* @author Lana Abadie - PostgreSQL for original RDBArchive code. Disable autocommit as needed.
* @author Laurent Philippe (Use read-only connection when possible for MySQL load balancing)
*/
-@SuppressWarnings("nls")
public class RDBArchiveWriter implements ArchiveWriter
{
/** Status string for Double.NaN samples */
@@ -430,9 +429,8 @@ private void oldBatchDoubleSamples(final RDBWriteChannel channel,
final int N = additional.size();
for (int i = 1; i < N; i++)
{
- if (dialect == Dialect.Oracle){
- insert_array_sample.setTimestamp(2, stamp);
- }
+ if (dialect == Dialect.Oracle)
+ insert_array_sample.setObject(2, TimestampHelper.toOffsetDateTime(stamp));
else
{
// Truncate the time stamp
@@ -498,9 +496,8 @@ private void completeAndBatchInsert(
final Timestamp stamp, final int severity,
final Status status) throws Exception
{
- if (dialect == Dialect.Oracle){
- insert_xx.setTimestamp(2, stamp);
- }
+ if (dialect == Dialect.Oracle)
+ insert_xx.setObject(2, TimestampHelper.toOffsetDateTime(stamp));
else
{
// Truncate the time stamp
diff --git a/services/archive-engine/src/main/java/org/csstudio/archive/writer/rdb/TimestampHelper.java b/services/archive-engine/src/main/java/org/csstudio/archive/writer/rdb/TimestampHelper.java
index 7c0b865ca9..909d74afbb 100644
--- a/services/archive-engine/src/main/java/org/csstudio/archive/writer/rdb/TimestampHelper.java
+++ b/services/archive-engine/src/main/java/org/csstudio/archive/writer/rdb/TimestampHelper.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2012-2018 Oak Ridge National Laboratory.
+ * Copyright (c) 2012-2025 Oak Ridge National Laboratory.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -9,9 +9,9 @@
import java.time.Duration;
import java.time.Instant;
+import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
-import java.util.Calendar;
import java.util.concurrent.TimeUnit;
import org.phoebus.util.time.TimestampFormats;
@@ -19,7 +19,6 @@
/** Time stamp gymnastics
* @author Kay Kasemir
*/
-@SuppressWarnings("nls")
public class TimestampHelper
{
/** @param timestamp {@link Instant}, may be null
@@ -32,17 +31,6 @@ public static String format(final Instant timestamp)
return TimestampFormats.FULL_FORMAT.format(timestamp);
}
- // May look like just time_format.format(Instant) works,
- // but results in runtime error " java.time.temporal.UnsupportedTemporalTypeException: Unsupported field: YearOfEra"
- // because time for printing needs to be in local time
-// public static void main(String[] args)
-// {
-// final Instant now = Instant.now();
-// System.out.println(format(now));
-// System.out.println(time_format.format(now));
-// }
-
-
/** @param timestamp EPICS Timestamp
* @return SQL Timestamp
*/
@@ -77,14 +65,29 @@ public static Instant fromMillisecs(final long millisecs)
}
return Instant.ofEpochSecond(seconds, nanoseconds);
}
-
- /** @param calendar Calendar
- * @return EPICS Timestamp
+
+ /** Zone ID is something like "America/New_York".
+ * Within that zone, time might change between
+ * EDT (daylight saving) and EST (standard),
+ * but the Zone ID remains, so we can keep it final.
*/
- public static Instant fromCalendar(final Calendar calendar)
- {
- return fromMillisecs(calendar.getTimeInMillis());
- }
+ final private static ZoneId zone = ZoneId.systemDefault();
+
+ /** Turn SQL {@link java.sql.Timestamp} into {@link OffsetDateTime}
+ *
+ * Oracle JDBC PreparedStatement.setTimestamp(int, Timestamp)
+ * will change the zone info in unexpected ways.
+ * Using PreparedStatement.setObject(int, OffsetDateTime)
+ * is the suggested workaround, so this morphs a Timestamp
+ * into OffsetDateTime
+ *
+ * @param sql_time SQL {@link java.sql.Timestamp}
+ * @return {@link OffsetDateTime}
+ */
+ public static OffsetDateTime toOffsetDateTime(final java.sql.Timestamp sql_time)
+ {
+ return OffsetDateTime.ofInstant(sql_time.toInstant(), zone);
+ }
/** Round time to next multiple of given duration
* @param time Original time stamp