diff --git a/CHANGELOG.md b/CHANGELOG.md index e9c787f48e794cf1220403f93280b30d530c5767..f1387e0e1639e8af6d88b334a6f6797aaa345022 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +* Make time zone configurable via server properties during runtime, but only in + debug mode. + ### Changed ### Deprecated diff --git a/src/main/java/org/caosdb/datetime/UTCTimeZoneShift.java b/src/main/java/org/caosdb/datetime/UTCTimeZoneShift.java index e4b9c3c1e29c0092b24d2b88fac4f06991e666e5..5cd9de48a183f18dd0ca39adaae0c7671cf8efdb 100644 --- a/src/main/java/org/caosdb/datetime/UTCTimeZoneShift.java +++ b/src/main/java/org/caosdb/datetime/UTCTimeZoneShift.java @@ -25,6 +25,14 @@ package org.caosdb.datetime; import java.util.Date; import java.util.TimeZone; +/** + * Represents the shift, i.e. the time difference between a time zone 'X' and the UTC+0 time zone. + * + * <p>This is used for (de-)serialization of ISO8601-compliant representations of UTC-relative time + * zones (e.g. +0100 for CEST) + * + * @author tf + */ public class UTCTimeZoneShift extends TimeZone { private static final long serialVersionUID = 6962096078347188058L; public static final int POSITIVE = 1; @@ -35,10 +43,10 @@ public class UTCTimeZoneShift extends TimeZone { public UTCTimeZoneShift(final int sign, final int h, final int m) { if (59 < m || m < 0) { - throw new IllegalArgumentException("59<m<0 is not allowed."); + throw new IllegalArgumentException("59<m<0 is not allowed. m was " + Integer.toString(m)); } - if (12 < h || h < 0) { - throw new IllegalArgumentException("12<h<0 is not allowed."); + if (14 < h || h < 0) { + throw new IllegalArgumentException("14<h<0 is not allowed. h was " + Integer.toString(h)); } rawOffset = sign * (3600 * h + 60 * m) * 1000; @@ -48,6 +56,12 @@ public class UTCTimeZoneShift extends TimeZone { private static String generateString(final int sign, final int h, final int m) { final StringBuilder sb = new StringBuilder(); sb.append(sign == NEGATIVE ? '-' : '+'); + if (59 < m || m < 0) { + throw new IllegalArgumentException("59<m<0 is not allowed. m was " + Integer.toString(m)); + } + if (14 < h || h < 0) { + throw new IllegalArgumentException("14<h<0 is not allowed. h was " + Integer.toString(h)); + } if (h < 10) { sb.append('0'); } @@ -113,15 +127,24 @@ public class UTCTimeZoneShift extends TimeZone { } /** - * Generate an ISO 8601 time zone offset string (e.g. +0200) from the given raw offset in - * milliseconds. + * Generate an ISO 8601 time zone offset string (e.g. +0200) for the given time zone at the given + * date. + * + * <p>The date is necessary to decide whether the time zone (e.g. Europe/Berlin) is in the + * daylight saving regime or not. In winter Europe/Berlin has a +0100 offset, in summer it is + * +0200. * - * @param rawOffset - offset in ms. + * @param timezone - the time zone in question. + * @param date - the date for which the offset is to be calculated (unix time stamp). * @return ISO 8601 time zone offset. */ - public static String getISO8601Offset(int rawOffset) { - int h = Integer.divideUnsigned(rawOffset, 1000 * 60 * 60); - int m = Integer.remainderUnsigned(rawOffset, 1000 * 60 * 60); - return generateString(Integer.signum(h), h, m); + public static String getISO8601Offset(TimeZone timezone, long date) { + int divisor = 1000 * 60 * 60; + int offset = timezone.getOffset(date); + // convert the ms to the ISO format + int sign = Integer.signum(offset); + int h = Integer.divideUnsigned(sign * offset, divisor); + int m = Integer.remainderUnsigned(sign * offset, divisor); + return generateString(sign, h, m); } } diff --git a/src/main/java/org/caosdb/server/CaosDBServer.java b/src/main/java/org/caosdb/server/CaosDBServer.java index fab783b765cb8af3c5ffdd1dda8ab2eb5dd0086b..9daabaed65aa39a9be868567581588625aba7a53 100644 --- a/src/main/java/org/caosdb/server/CaosDBServer.java +++ b/src/main/java/org/caosdb/server/CaosDBServer.java @@ -87,6 +87,8 @@ import org.caosdb.server.scripting.ScriptingPermissions; import org.caosdb.server.transaction.ChecksumUpdater; import org.caosdb.server.utils.FileUtils; import org.caosdb.server.utils.Initialization; +import org.caosdb.server.utils.Observable; +import org.caosdb.server.utils.Observer; import org.quartz.JobDetail; import org.quartz.Scheduler; import org.quartz.SchedulerException; @@ -118,7 +120,7 @@ import org.slf4j.LoggerFactory; public class CaosDBServer extends Application { private static Logger logger = LoggerFactory.getLogger(CaosDBServer.class); - private static Properties SERVER_PROPERTIES = null; + private static ServerProperties SERVER_PROPERTIES = null; private static Component component = null; private static ArrayList<Runnable> postShutdownHooks = new ArrayList<Runnable>(); private static ArrayList<Runnable> preShutdownHooks = new ArrayList<Runnable>(); @@ -210,6 +212,27 @@ public class CaosDBServer extends Application { */ public static void initTimeZone() throws InterruptedException, IOException { final String serverProperty = getServerProperty(ServerProperties.KEY_TIMEZONE); + + // Setup an observer which updates the server's time zone when the relevant ServerProperty + // changes (this can only happen when in debug mode). + if (isDebugMode()) { + SERVER_PROPERTIES.acceptObserver( + new Observer() { + + @Override + public boolean notifyObserver(String e, Observable sender) { + if (e.equals(ServerProperties.KEY_TIMEZONE)) { + + String[] availableIDs = TimeZone.getAvailableIDs(); + TimeZone newZoneId = + TimeZone.getTimeZone(getServerProperty(ServerProperties.KEY_TIMEZONE)); + TimeZone.setDefault(newZoneId); + } + return true; // true means, keep this observer. + } + }); + } + if (serverProperty != null && !serverProperty.isEmpty()) { logger.info( "SET TIMEZONE = " diff --git a/src/main/java/org/caosdb/server/ServerProperties.java b/src/main/java/org/caosdb/server/ServerProperties.java index aa8fc43ead96dad56e739950f12ad7638e550bef..87f5b22d55dfc11a8ae0158df85fa69d2b6cdc9d 100644 --- a/src/main/java/org/caosdb/server/ServerProperties.java +++ b/src/main/java/org/caosdb/server/ServerProperties.java @@ -31,11 +31,15 @@ import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.Properties; +import org.caosdb.server.utils.AbstractObservable; +import org.caosdb.server.utils.Observable; +import org.caosdb.server.utils.Observer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class ServerProperties extends Properties { +public class ServerProperties extends Properties implements Observable { + private Observable delegateObservable = new AbstractObservable() {}; private static final long serialVersionUID = -6178774548807398071L; private static Logger logger = LoggerFactory.getLogger(ServerProperties.class.getName()); @@ -150,8 +154,8 @@ public class ServerProperties extends Properties { * * @throws IOException */ - public static Properties initServerProperties() throws IOException { - final Properties serverProperties = new Properties(); + public static ServerProperties initServerProperties() throws IOException { + final ServerProperties serverProperties = new ServerProperties(); final String basepath = System.getProperty("user.dir"); final URL url = CaosDBServer.class.getResource("/build.properties"); @@ -207,4 +211,21 @@ public class ServerProperties extends Properties { sp_in.close(); } } + + @Override + public synchronized Object setProperty(String key, String value) { + Object ret = super.setProperty(key, value); + delegateObservable.notifyObservers(key); + return ret; + } + + @Override + public boolean acceptObserver(Observer o) { + return delegateObservable.acceptObserver(o); + } + + @Override + public void notifyObservers(String e) { + delegateObservable.notifyObservers(e); + } } diff --git a/src/main/java/org/caosdb/server/resource/InfoResource.java b/src/main/java/org/caosdb/server/resource/InfoResource.java index 25e8842b40df2d64c3dd628116ad958eb45bdb11..452ea7956d75ff8ea4d9a5f3cf8e24df1980caf3 100644 --- a/src/main/java/org/caosdb/server/resource/InfoResource.java +++ b/src/main/java/org/caosdb/server/resource/InfoResource.java @@ -55,8 +55,9 @@ public class InfoResource extends AbstractCaosDBServerResource { public Element getTimeZone() { TimeZone d = TimeZone.getDefault(); + long currentDate = System.currentTimeMillis(); // show time zone offset for the current date. return new Element("TimeZone") - .setAttribute("offset", UTCTimeZoneShift.getISO8601Offset(d.getRawOffset())) + .setAttribute("offset", UTCTimeZoneShift.getISO8601Offset(d, currentDate)) .setAttribute("id", d.getID()) .addContent(d.getDisplayName()); } diff --git a/src/test/java/org/caosdb/datetime/UTCTimeZoneShiftTest.java b/src/test/java/org/caosdb/datetime/UTCTimeZoneShiftTest.java new file mode 100644 index 0000000000000000000000000000000000000000..9cfa98b144d5f3a4c66a008722d0f03f8d626840 --- /dev/null +++ b/src/test/java/org/caosdb/datetime/UTCTimeZoneShiftTest.java @@ -0,0 +1,15 @@ +package org.caosdb.datetime; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.TimeZone; +import org.junit.jupiter.api.Test; + +class UTCTimeZoneShiftTest { + + @Test + void test() { + TimeZone timeZone = TimeZone.getTimeZone("CST"); + assertEquals("-0500", UTCTimeZoneShift.getISO8601Offset(timeZone, 1665133324000L)); + } +}