Skip to content

Commit 1912d57

Browse files
author
kasemir
committed
RDB archive: Fix Oracle time zone problem.
The sample.smpl_time is handled differently for MySQL, Postgres, Oracle. In MySQL, it's stored as UTC. The `Timestamp` passed to `PreparedStatement.setTimestamp(Timestamp)` is converted to UTC and stored as UTC. On retrieval, `ResultSet.getTimestamp` converts back to the local time zone with appropriate GMT offset. In Postgres, we need to use a `TIMESTAMPTZ` datatype that stores the stamp with timezone info. Both MySQL and Postgres handle the fall transition from daylight savings time back to standard time just fine. In Oracle, even if we use `TIMESTAMP WITH TIMEZONE` for smpl_time, Oracle JDBC will second-guess the Timestamp passed to `setTimestamp(Timestamp)` and change the time offset. During the DST change in the fall, time stamps will be written with the wrong GMT offset. The workaround is to use `setObject(OffsetDateTime)` because OffsetDateTime is passed through and written as received.
1 parent e7c8343 commit 1912d57

File tree

3 files changed

+30
-30
lines changed

3 files changed

+30
-30
lines changed

services/archive-engine/dbd/postgres_schema.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ DROP TABLE IF EXISTS sample;
199199
CREATE TABLE sample
200200
(
201201
channel_id BIGINT NOT NULL,
202-
smpl_time TIMESTAMP NOT NULL,
202+
smpl_time TIMESTAMPTZ NOT NULL,
203203
nanosecs BIGINT NOT NULL,
204204
severity_id BIGINT NOT NULL,
205205
status_id BIGINT NOT NULL,

services/archive-engine/src/main/java/org/csstudio/archive/writer/rdb/RDBArchiveWriter.java

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2011-2024 Oak Ridge National Laboratory.
2+
* Copyright (c) 2011-2025 Oak Ridge National Laboratory.
33
* All rights reserved. This program and the accompanying materials
44
* are made available under the terms of the Eclipse Public License v1.0
55
* which accompanies this distribution, and is available at
@@ -51,7 +51,6 @@
5151
* @author Lana Abadie - PostgreSQL for original RDBArchive code. Disable autocommit as needed.
5252
* @author Laurent Philippe (Use read-only connection when possible for MySQL load balancing)
5353
*/
54-
@SuppressWarnings("nls")
5554
public class RDBArchiveWriter implements ArchiveWriter
5655
{
5756
/** Status string for <code>Double.NaN</code> samples */
@@ -430,9 +429,8 @@ private void oldBatchDoubleSamples(final RDBWriteChannel channel,
430429
final int N = additional.size();
431430
for (int i = 1; i < N; i++)
432431
{
433-
if (dialect == Dialect.Oracle){
434-
insert_array_sample.setTimestamp(2, stamp);
435-
}
432+
if (dialect == Dialect.Oracle)
433+
insert_array_sample.setObject(2, TimestampHelper.toOffsetDateTime(stamp));
436434
else
437435
{
438436
// Truncate the time stamp
@@ -498,9 +496,8 @@ private void completeAndBatchInsert(
498496
final Timestamp stamp, final int severity,
499497
final Status status) throws Exception
500498
{
501-
if (dialect == Dialect.Oracle){
502-
insert_xx.setTimestamp(2, stamp);
503-
}
499+
if (dialect == Dialect.Oracle)
500+
insert_xx.setObject(2, TimestampHelper.toOffsetDateTime(stamp));
504501
else
505502
{
506503
// Truncate the time stamp

services/archive-engine/src/main/java/org/csstudio/archive/writer/rdb/TimestampHelper.java

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2012-2018 Oak Ridge National Laboratory.
2+
* Copyright (c) 2012-2025 Oak Ridge National Laboratory.
33
* All rights reserved. This program and the accompanying materials
44
* are made available under the terms of the Eclipse Public License v1.0
55
* which accompanies this distribution, and is available at
@@ -9,17 +9,16 @@
99

1010
import java.time.Duration;
1111
import java.time.Instant;
12+
import java.time.OffsetDateTime;
1213
import java.time.ZoneId;
1314
import java.time.ZonedDateTime;
14-
import java.util.Calendar;
1515
import java.util.concurrent.TimeUnit;
1616

1717
import org.phoebus.util.time.TimestampFormats;
1818

1919
/** Time stamp gymnastics
2020
* @author Kay Kasemir
2121
*/
22-
@SuppressWarnings("nls")
2322
public class TimestampHelper
2423
{
2524
/** @param timestamp {@link Instant}, may be <code>null</code>
@@ -32,17 +31,6 @@ public static String format(final Instant timestamp)
3231
return TimestampFormats.FULL_FORMAT.format(timestamp);
3332
}
3433

35-
// May look like just time_format.format(Instant) works,
36-
// but results in runtime error " java.time.temporal.UnsupportedTemporalTypeException: Unsupported field: YearOfEra"
37-
// because time for printing needs to be in local time
38-
// public static void main(String[] args)
39-
// {
40-
// final Instant now = Instant.now();
41-
// System.out.println(format(now));
42-
// System.out.println(time_format.format(now));
43-
// }
44-
45-
4634
/** @param timestamp EPICS Timestamp
4735
* @return SQL Timestamp
4836
*/
@@ -77,14 +65,29 @@ public static Instant fromMillisecs(final long millisecs)
7765
}
7866
return Instant.ofEpochSecond(seconds, nanoseconds);
7967
}
80-
81-
/** @param calendar Calendar
82-
* @return EPICS Timestamp
68+
69+
/** Zone ID is something like "America/New_York".
70+
* Within that zone, time might change between
71+
* EDT (daylight saving) and EST (standard),
72+
* but the Zone ID remains, so we can keep it final.
8373
*/
84-
public static Instant fromCalendar(final Calendar calendar)
85-
{
86-
return fromMillisecs(calendar.getTimeInMillis());
87-
}
74+
final private static ZoneId zone = ZoneId.systemDefault();
75+
76+
/** Turn SQL {@link java.sql.Timestamp} into {@link OffsetDateTime}
77+
*
78+
* Oracle JDBC PreparedStatement.setTimestamp(int, Timestamp)
79+
* will change the zone info in unexpected ways.
80+
* Using PreparedStatement.setObject(int, OffsetDateTime)
81+
* is the suggested workaround, so this morphs a Timestamp
82+
* into OffsetDateTime
83+
*
84+
* @param sql_time SQL {@link java.sql.Timestamp}
85+
* @return {@link OffsetDateTime}
86+
*/
87+
public static OffsetDateTime toOffsetDateTime(final java.sql.Timestamp sql_time)
88+
{
89+
return OffsetDateTime.ofInstant(sql_time.toInstant(), zone);
90+
}
8891

8992
/** Round time to next multiple of given duration
9093
* @param time Original time stamp

0 commit comments

Comments
 (0)