1

My embedded Java application running on Linux should allow the user to change the timezone through a GUI. My application is running in an OSGI container (see below why I believe this is relevant) and should not need to restart before using the new timezone.

What is the recommended way of persistently setting the timezone from my Java/OSGi application?

I can think of the following approaches, for which I list some Pros and Cons. Am I missing something? What's recommended?:

  1. From the app, change the underlying OS timezone, additionally use TimeZone.setDefault(...) for the currently running JVM and renew all Clock instances which hold the old TZ (so some kind of event is necessary). Con: this method is OS dependent and quite low level, also I would like to keep the OS clock UTC. Pros: OS takes care of storing the TZ, TZ is immediately correct upon next startup of app.
  2. From the app, change the -Duser.timezone=... parameter used for launching. Con: very ugly, even lower level, but allows to leave the OS clock in UTC, while having the app start with the correct TZ. Also need to renew Clock instances on change.
  3. Don't touch the OS, and only use TimeZone.setDefault(...) and call it early on startup. This will need a separate persistence (preferences) to save it. Here also, in the currently running JVM, all Clock instances which reference the old TZ, need to be renewed upon change (needs event). When running in an OSGi container, the startup order of bundles is not guaranteed, so I cannot be sure that the default TZ gets set before it is being used. How can I guarantee this? Also, JSR310 explicitely advises against using the "default TZ" in Clock.
  4. Not use the "default" TimeZone at all, use a separate global variable and upon each conversion between Instant and LocalXXX values, pass the timezone explicitly. This gets rid of needing an event to update Clock instance. But we need to pay attention not to use LocalDate.now(clock), as this uses the clock's TZ (which is then no longer correct). How to have this global variable in OSGi? using ConfigAdmin? How to make code behave correctly which I have no control over (eg. logging time stamps)?

Edit: To get rid of the need to update the Clock, I could use a clock which always checks the default TimeZone, but this seems suboptimal from a performance POV:

public class DefaultZoneClock extends Clock {
  private final Clock ref = Clock.systemUTC();

  @Override
  public ZoneId getZone() {
    return ZoneId.systemDefault(); // probed on each request
  }

  @Override
  public Clock withZone(ZoneId zone) {
    return ref.withZone(zone);
  }

  @Override
  public Instant instant() {
    return ref.instant();
  }
}

Is that a good idea?

Edit 2:

About my performance concerns above: they are obviously not justified. When you call LocalDate.now(), internally a new SytemClock is built which sets the current ZoneID by searching it in a Map - this is exactly the same as using my DefaultZoneClock above, the difference being that using my code I can inject any other Clock for testing. (all client code would use LocalDate.now(clock) )

Answers below suggest not to change the JVM TimeZone but to do the conversion whenever it is necessary based on a user-defined TimeZone, this means that I have to take care not to use the java.time methods which call the TimeZone from Clock, eg. if I need a LocaTime use

// OK, TimeZone is set explicitely from user data
LocalTime t = clock.instant().atZone(myUserZoneID).toLocalTime();

// Not OK, uses the Clock's internal TimeZone which may not have been set or updated
LocalTime t2 = LocalTime.now(clock);
Philipp
  • 4,659
  • 9
  • 48
  • 69
  • What is `Clock`? Is it a type from your own application, or from an external library? – Neil Bartlett Jan 12 '15 at 14:29
  • 1
    `Clock` is the Java 8 Time API / threetenbp provider of `Instant`s – Philipp Jan 12 '15 at 14:31
  • @Philipp As I say in [my answer](http://stackoverflow.com/a/27908809/642706), you seem to be making a mountain out of a molehill. Is there something special or different about "embedded" or OSGi that is complicating the situation as compared to a desktop or web app? Can't you create and lookup a collection of objects that represent a user's profile such as their localization language (French or Italian) and preferred time zone? Perhaps I'm too ignorant about OSGi to comment. – Basil Bourque Jan 12 '15 at 19:04

2 Answers2

3

In my experience, dates/times should always be represented internally using UTC. Only ever convert to a local time when showing it to a user. At that point you also have the requirement to format it according to the user's locale.

So the question becomes, how do you know the user's timezone and locale? It depends on the nature of the application. If it's a single-user desktop app then you should either look these up in the OS or use configuration stored with the Config Admin service. If it's a multi-user web application then you will probably have some user-related information in the web session; or use the Accept-Language header or even geo-location.

UPDATE

Poster clarified that the application is single-user on an embedded device. In this case isn't it easier just to query the current system timezone each time there is a requirement to display the time? This avoids nasty global variables, and it also allows your application to respond dynamically to changes in the timezone, for example if the user carries the device from one place to another.

I don't believe you should call TimeZone.setDefault from your Java application, because where are you going to get this information from? Direct user entry perhaps, but wouldn't the user prefer to set it in the OS, so that all applications can get it?

Neil Bartlett
  • 23,743
  • 4
  • 44
  • 77
  • If I use ConfigAdmin, all bundles which use time will have a dependency on ConfigAdmin and the specific PID. Also, each one must then call `TZ.setDefault()` so that anyone which starts first, sets the default (eg. for logging). Is this the recommended way? – Philipp Jan 12 '15 at 14:43
  • @Philipp The answer is reasonable. You could also ask the (web)-user for its timezone and store it in the session and database. By the way, never change the default timezone of the server using `TimeZone.setDefault()`. This would only change the default-tz for **ALL** users (meaningless and even harmful on the server). The clock will primarily yield instants at UTC-offset. If you want the 'local' date then just use the user's preferred timezone for conversion from `Instant` to `LocalDate`, not the default tz. – Meno Hochschild Jan 12 '15 at 17:47
  • To emphasize: Calling `TimeZone.setDefault` affects all the code in all the threads of all the apps running in that JVM. Not a good thing, generally. – Basil Bourque Jan 12 '15 at 18:12
  • Actually my code runs on an embedded device, and uses the localtime and thus the timezone to take control decisions. So setting the JVM timezone globally is really what I need. – Philipp Jan 12 '15 at 18:19
0

You seem to be making a mountain out of a molehill, working hard to make a simple problem into a complicated one.

Treat Date-Time Values Like Localizing Text

Localizing date-time is, well, a localization problem. Approach it in a manner similar to what you would do if some users speak French, some Italian, and some German.

Do all your internal work, such as lookups and key values, in one language, say English, even if none of your users speak English.

For date-time, the equivalent is keeping internal values in UTC time zone. Your business logic, file storage, and database records should all use UTC. If it is critical to know the original time zone and/or original input, store that as an additional piece of information in addition to the UTC-adjusted value.

When it comes time to display to the user, or export data for the user, you would use those English strings and keys to look up French or Italian or German translation. Which language? Three possibilities:

  • The user’s computing environment’s default language (JVM defaults, http headers, JavaScript query, whatever)
  • The user’s explicitly chosen language (our app asked the user)
  • The language appropriate to the data’s context.

For the first two, somewhere somehow you keep a profile for each user. In a web app, we use a session object. In a single-user "desktop" app, we use a Singleton. For a user’s chosen language, persist to a database or file storage so as to remember for future work sessions by this user.

Same goes for date-time. When presenting or exporting data, if the context does not dictate a time zone then detect their current time zone from their computing environment. If time zone is critical, or users are highly mobile and crossing time zones, then ask the user for their choice of time zone. Provide a list of proper time zone names. Remember this choice for future work sessions by this user.

Specify Time Zone On Date-Time Objects

Both the Joda-Time library and the java.time package in Java 8 make it easy to adjust a date-time object’s assigned time zone to and from UTC. You should be changing the time zone here, on the data value objects, rather than changing the JVM’s default or the OS locale settings.

Logging In UTC

As for logging and other system matters, that should be done in UTC. You can always later convert that UTC value to a local time zone later when investigating an issue. Or include the user’s localized date-time in your logging event’s message if relevant.

Do Not Mess With Clock

I would not mess with the java.time Clock when deployed in production. The main intention for this class is testing. You can plug in a bogus Clock to lie about the current date-time to create testing scenarios. You could plug in a Clock in production, but it makes much more sense to me to specify the desired time zone on your date-time objects.

Example

For example, say the user wants an alarm to fire at 7:30 AM. You either ask the user or otherwise determine their desired time zone is in Montréal. Here is unrealistic code in Joda-Time (java.time would be similar).

DateTimeZone zone = DateTimeZone.forID( "America/Montreal" );
DateTime alarmMontréal = DateTime.now( zone ).plusDays( 1 ).withTime( 7 , 30 , 0 , 0 );
DateTime alarmUtc = alarmMontréal.withZone( DateTimeZone.UTC );

We display alarmMontréal to the user, but store alarmUtc in the database. Both represent the same moment in the timeline of the history of the Universe.

Say the user flies to Portland Oregon US in the meantime. If we want the alarm to fire at the same moment unchanged, no change needed. To display when that alarm will fire in Portland time, we create a new immutable object adjusting to yet another time zone.

DateTime alarmPortland = alarmUtc.withZone( DateTimeZone.forID( "America/Los_Angeles" ) );  // 3 hours behind Montréal, 04:30:00.000.

If we are going to call our Uncle in India at that time, we send him an appointment confirmation email using the result of this code:

DateTime alarmKolkata = alarmUtc.withZone( DateTimeZone.forID(  "Asia/Kolkata" ) );

We have four DateTime objects, all representing the moment in the timeline of the Universe:

zone            America/Montreal
alarmMontréal   2015-01-14T07:30:00.000-05:00
alarmUtc        2015-01-14T12:30:00.000Z
alarmPortland   2015-01-14T04:30:00.000-08:00
alarmKolkata    2015-01-14T18:00:00.000+05:30

LocalTime

If in a different scenario you had wanted 7:30 in whatever local time zone the user happened to be in, then you would use a LocalTime, which is merely the idea of seven-thirty in the morning in any time zone. A LocalTime is not tied to the timeline of the history of the Universe.

Both Joda-Time and java.time offer a LocalTime class. Search StackOverflow for many examples and discussions.

Here is an overly-simplistic example using LocalTime if the business rule is "Fire the alarm if the current time is after 07:30:00.000 in whatever locality the user/computer/device happens to find itself now":

Boolean alarmFired = Boolean.FALSE;
LocalTime alarm = new LocalTime( 7 , 30 );
// …
DateTimeZone zoneDefault = DateTimeZone.getDefault();  // Use the computer’s/device’s JVM’s current default time zone. May change at any moment.
if ( LocalTime.now( zoneDefault ).isAfter( alarm ) ) {
    DateTime whenAlarmFired = DateTime.now( DateTimeZone.UTC );
    alarmFired = Boolean.TRUE;
    // fire the alarm.
}

Use UTC For History

Notice that if we are tracking a history of when the alarm fires, we should do so in UTC as a DateTime. We may also want to know at what local time that alarm fired. If we know or record the time zone, we can always re-create that. Or we may choose to also record the current local date-time. But that local value is secondary information. Primary tracking should be by UTC.

Explicitly Specify Time Zone

Note how we specify the time zone explicitly as the default. It would be shorter to use LocalTime.now() as that does indeed use the JVM’s current default time zone.

We are talking about the difference between this…

LocalTime now = LocalTime.now();  // Implicit reliance on JVM’s current default time zone.

…and this…

LocalTime now = LocalTime.now( DateTimeZone.getDefault() ); // Explicitly asking for JVM’s current default time zone.

Implicit reliance on the default time zone is a bad practice. For one thing such reliance encourages the programmer to be thinking in a local date-time frame of reference when she should instead develop a habit of thinking in UTC. Another problem is that implicitly relying on the default time zone makes your code ambiguous in the sense that the reader is not sure if you meant to use the default or just didn’t know better (which is all too often a cause of problems in date-time handling).

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
  • Thanks for your answer. My business logic depends on the TimeZone (eg. alarm at 7:30), so it's not just a display/localization problem. Nonetheless I take away your recommendation not to touch the JVM TimeZone and use a user specified TZ instead on each need. – Philipp Jan 13 '15 at 08:57
  • @Philipp No, I think it is indeed the same kind of problem. I added a pair of examples at the bottom of my Answer. Does either of those apply to your app? If not, please edit your question to explain what you mean by "my business logic depends on the time zone". – Basil Bourque Jan 13 '15 at 17:59
  • A typical business rule decision would be "is it currently after 7:30". I would implement this as `LocalTime.now(clock).isAfter(LocalTime.of(7,30))`. The call to now() uses the timezone. Maybe I could convert all this back to UTC, but it then becomes really unreadable IMO. – Philipp Jan 13 '15 at 19:42
  • @Philipp Tracking is not a matter of all-or-nothing. Use of `LocalTime` is correct when you mean the idea of a time-of-day for any (or all) locality. That means what ever time zone the user/computer/device happens to find itself located. Such a local time is not tied to the timeline, so it has no meaning in UTC. Note that you can apply a `LocalTime` to a `DateTime` to create a point on the timeline with methods such as [`LocalTime::toDateTimeToday( DateTimeZone zone )`](http://www.joda.org/joda-time/apidocs/org/joda/time/LocalTime.html#toDateTimeToday(org.joda.time.DateTimeZone)) (Joda-Time). – Basil Bourque Jan 13 '15 at 22:33
  • @Philipp P.S. Sorry about all the Joda-Time code instead of java.time. I'm just not as familiar with java.time. I'm pretty sure you'll find nearly direct translations from Joda-Time to java.time for the material we’ve discussed. P.P.S. See additional example code and discussion at bottom of my Answer. [Comment above was meant to begin "Tracking by UTC …".] – Basil Bourque Jan 13 '15 at 22:35