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.