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