Java Calendar, Date, and Time Management for a Multi-Timezone Application

java Calendar, Date, and Time management for a multi-timezone application

In general, scheduling future events is a complex subject. You have to make a distinction in the context of what is going to be scheduled:

  • Does the event occur at a specific universal instant in time? If so, you should record the event time in terms of UTC.

    For example, a task that runs every 24 hours would be scheduled by UTC time and not by the local time. It might start off at some local midnight, but as daylight saving time changes take effect it could be running at 23:00 or 01:00 by the local clock.

  • However, if the event is scheduled by human beings, it is likely to be in terms of a local time, so you should record it that way.

    For example, a meeting that occurs at 08:00 Eastern Time will always occur at that local time. In the winter, that would be 13:00 UTC, and in the summer it would be at 12:00 UTC.

    So in this context, you cannot record the scheduled start time in terms of UTC. This is a very common mistake, as there is a ton of advice on the Internet that says "always store using UTC", which would be wrong in this scenario.

    Instead, you should store two values - the local time such as 08:00 and its IANA time zone identifier, such as America/New_York. You may also need to store a recurrence pattern or specific date depending on how the event is scheduled.

  • Consider using Joda Time instead of Java's Calendar or Date classess. It will save you from many headaches. Make sure you read the Joda Time documentation and understand how it works.

    Joda Time has all of the functions you will need for converting between one time zone and another - which I believe was the primary concern of your question.

  • Be sure to have a procedure in place for updating the time zone data regularly. Updates are pushed out multiple times a year, as the governments of the world make changes to the legal definitions of their time zones. You cannot just deploy it once and forget about it.

  • Also be sure you understand that conversion from local time to a specific UTC moment is not a perfect function due to daylight saving time. If an event is scheduled during an invalid or ambiguous local time, you should have a strategy for detecting and dealing with that in your application. You might just apply some assumptions, or you might want to go out of your way to ask the user what to do.

    For example, if I schedule an event at 2:00 AM Eastern Time every day, then on March 10th 2013, that time does not exist. Should the event occur at 3:00 AM? Or should it not occur at all?

    Another example, if I schedule an event at 1:00 AM Eastern Time every day, then on November 3rd, 2013, that time occurs twice. Should the event happen at the first (daylight time) instance? Or at the second (standard time) instance? Or both? Should I assume one or the other, or should I ask the user which they mean?

    Only you can decide what to do, as it is your application. But ignoring the problem will likely lead to errors.

  • Once an event has passed, you can record it in UTC if you wish, or record it with the full local date time and offset. Either are acceptable. This works just fine for singular past events, just not for recurring future ones.

Java Date timezone printing different timezones for different years, Workaround needed

This is likely the expected behaviour from Java (and not from JavaScript).

As implied by the comment by RobG above, programming languages may or may not support historical time rules (such as DST and timezone offsets). In your case, it appears that your Java runtime supports it, whereas your JavaScript runtime does not.

A list of historical timezones and DST rules for India can be found at timeanddate.com. The list confirms the timezone offsets of your Java dates:

  • Until 1941: UTC+5:53:20
  • 1941: UTC+6:30
  • 1942: UTC+5:30
  • 1943-44: UTC+6:30
  • From 1945: UTC+5:30

Checking your dates against Wolfram|Alpha further confirms your Java date UTC offsets: 1915, 1943, 1946

Wikipedia provides more information about time in India:

Calcutta time was officially maintained as a separate time zone until 1948

Calcutta time could either be specified as UTC+5:54 or UTC+5:53:20. The latter is consistent with your code example.

The Wikipedia entry further informs that the current IST timezone with an offset of UTC+5:30 was not in full effect in all of India until 1955.

As pointed out by Elliott Frisch and confirmed by the link to timeanddate.com above, DST was in effect during WWII. In your comment to his answer, you state:

is this the way we are supposed to save in database and use it in applications, or we use some workaround for it

I guess it depends. If you really need to distinguish dates as points in time accurately, you would need a timezone-independent representation such as UTC or unix time (or milliseconds since the unix epoch). If you just work with local dates from the same timezone, a simple string representation (e.g. YYYY-MM-DD hh:mm:ss) could suffice.

How to deal with timezones in an event scheduling application with recurring events?

When it comes to future scheduling, especially for recurring events, you should not store values in terms of UTC. Instead, you need to store values in terms of the time zone related to that event, and you also need to store the identifier of that time zone (such as "America/Los_Angeles")

In order to conveniently order these events, you might additionally compute the UTC time for an occurrence of the event, but you should be prepared to recalculate it - as rules for time zones often change. The UTC time of the next event occurrence should be either in another column, or another table entirely.

Keep in mind that scheduling future events is a difficult problem. There are multiple pieces involved to do it properly. You should not be looking for a trivial solution. Please read some of the other posts I've written about this subject: here, and here.

How should I store data for events in different timezones?

If you mean that you are recording the time of events as they happen, or recording the times of events in the past, then paxdiablo's answer is correct. Usually, UTC will suffice for that. In a few cases, you might want to store a local datetime + offset (a "DateTimeOffset" in some platforms), but that depends on exactly what you're using the data for.

However, if you are asking about scheduling an event that will happen in the future, especially if it's a recurring event, then UTC is not entirely sufficient. I've already written about this several times, so I suggest you read these articles for details:

  • https://stackoverflow.com/a/20828804
  • https://stackoverflow.com/a/19627330
  • https://stackoverflow.com/a/19170823
  • https://serverfault.com/a/554761

How to handle JodaTime's and Android's timezone database differences?

I think the other answers are missing the point. Yes, when persisting time information, you should consider carefully your use cases to decide how best to do so. But even if you had done it, the problem this question poses would still persist.

Consider Android's alarm clock app, which has its source code freely available. If you look at its AlarmInstance class, this is how it is modeled in the database:

private static final String[] QUERY_COLUMNS = {
_ID,
YEAR,
MONTH,
DAY,
HOUR,
MINUTES,
LABEL,
VIBRATE,
RINGTONE,
ALARM_ID,
ALARM_STATE
};

And to know when an alarm instance should fire, you call getAlarmTime():

/**
* Return the time when a alarm should fire.
*
* @return the time
*/
public Calendar getAlarmTime() {
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.YEAR, mYear);
calendar.set(Calendar.MONTH, mMonth);
calendar.set(Calendar.DAY_OF_MONTH, mDay);
calendar.set(Calendar.HOUR_OF_DAY, mHour);
calendar.set(Calendar.MINUTE, mMinute);
calendar.set(Calendar.SECOND, 0);
calendar.set(Calendar.MILLISECOND, 0);
return calendar;
}

Note how an AlarmInstance stores the exact time it should fire, regardless of time zone. This ensures that every time you call getAlarmTime() you get the correct time to fire on the user's time zone. The problem here is if the time zone is not updated, getAlarmTime() cannot get correct time changes, for example, when DST starts.

JodaTime comes in handy in this scenario because it ships with its own time zone database. You could consider other date time libraries such as date4j for the convenience of better handling date calculations, but these typically don't handle their own time zone data.

But having your own time zone data introduces a constraint to your app: you cannot rely anymore on Android's time zone. That means you cannot use its Calendar class or its formatting functions. JodaTime provides formatting functions as well, use them. If you must convert to Calendar, instead of using the toCalendar() method, create one similar to the getAlarmTime() above where you pass the exact time you want.

Alternatively, you could check whether there is a time zone mismatch and warn the user like Matt Johnson suggested in his comment. If you decide to keep using both Android's and Joda's functions, I agree with him:

Yes - with two sources of truth, if they're out of sync, there will be
mismatches. Check the versions, show a warning, ask to be updated,
etc. There's probably not much more you can do than that.

Except there is one more thing you can do: You can change Android's time zone yourself. You should probably warn the user before doing so but then you could force Android to use the same time zone offset as Joda's:

public static boolean isSameOffset() {
long now = System.currentTimeMillis();
return DateTimeZone.getDefault().getOffset(now) == TimeZone.getDefault().getOffset(now);
}

After checking, if it is not the same, you can change Android's time zone with a "fake" zone you create from the offset of Joda's correct time zone information:

public static void updateTimeZone(Context c) {
TimeZone tz = DateTimeZone.forOffsetMillis(DateTimeZone.getDefault().getOffset(System.currentTimeMillis())).toTimeZone();
AlarmManager mgr = (AlarmManager) c.getSystemService(Context.ALARM_SERVICE);
mgr.setTimeZone(tz.getID());
}

Remember you will need the <uses-permission android:name="android.permission.SET_TIME_ZONE"/> permission for that.

Finally, changing the time zone will change the system current time. Unfortunately only system apps can set the time so the best you can do is open the date time settings for the user and prompt him/her to change it manually to the correct one:

startActivity(new Intent(android.provider.Settings.ACTION_DATE_SETTINGS));

You will also have to add some more controls to make sure the time zone gets updated when DST starts and ends. Like you said, you will be adding your own time zone management but it's the only way to ensure the consistency between the two time zone databases.

How to make event trigger at the same time on all devices regardless of timezone

You always store dates as a UTC time (with a time zone stored separately). Then all alarm devices can easily convert that into their own local time, and make the alarm at the appropriate time.

Alarms that should be at 9am no matter what time zone then is stored without a time zone.

Storing event times, UTC issues when DST

If you want to represent a repeated event, you definitely need to store the local time and the time zone, and either store or decide on the policy for what to do if that time becomes invalid or ambiguous. (Suppose the user stores 02:30 - in spring that value doesn't occur at all when the clocks go forward, and in autumn it occurs twice when the clocks go back.)

But if you've got a repeated event, you possibly shouldn't store a DateTime anyway - I'd store something like a "start date" (the first date on which it should occur; a LocalDate), a time of day at which it should occur each day (a LocalTime) and a time zone ID (e.g. Europe/Paris).

Storing "local date/time and time zone" can also be important if you're storing a future event, bearing in mind that the time zone rules can change between now and then.

Storing a UTC instant is useful if:

  • The original time zone is no longer relevant (if it even existed)
  • The event isn't repeated
  • You want to be able to compare events really easily

This is particularly relevant for timestamps.

But yes, like most things in date/time handling, a simple "one size fits all" answer doesn't exist - you need to think about your context.



Related Topics



Leave a reply



Submit