如何处理JodaTime和Android的时区数据库差异? [英] How to handle JodaTime's and Android's timezone database differences?

查看:185
本文介绍了如何处理JodaTime和Android的时区数据库差异?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想在Reddit Android开发社区开始讨论。)。即使一些网站仍然显示这个时区为GMT + 3:00,因为这个变化相对较近。但是,正确的是,由JodaTime显示的GMT + 2:00。



有缺陷的可能解决方案



这是一个问题,因为无论我如何设法规避它,最后我必须格式化时间,以便在时区显示给用户。而当我使用JodaTime做的时候,时间将格式错误,因为它将与系统显示的预期时间不一致。



或者,假设我处理所有的UTC。当用户在日历中添加事件并选择提醒的时间时,我可以将其设置为UTC,将其存储在数据库中,并将其完成。



然而,我需要在Android的时候将Android的 AlarmManager 设置为提醒,而不是在UTC时间转换用户设置的时间,而是相对于他们想要的时间提醒触发。这需要时区信息才能发挥作用。



例如,如果用户在UTC + 1:00的某个地方,并且他或她设置了一个提醒9: 00am,我可以:




  • 创建一个新的 DateTime 实例设置为09:00 am在用户的时区,并将其毫秒数据存储在数据库中。我也可以直接使用相同的毫秒与 AlarmManager ;

  • 创建一个新的 DateTime 实例设置为UTC的09:00 am,并将其毫秒存储在数据库中。这更好地解决了与这个问题不完全相关的其他一些问题。但是当使用 AlarmManager 设置时间时,我需要在用户时区上午09:00计算其毫秒值;

  • 忽略完整的Joda DateTime ,并使用Java的 Calendar 处理提醒的设置。这将使我的应用程序在显示时间时依赖于过时的时区信息,但至少在使用 AlarmManager 或显示日期和时间进行调度时,不会有不一致的情况。 / li>


我缺少什么?



我可能会想到这一点,恐怕我可能会缺少一些明显的东西。我是吗有没有什么办法可以继续在Android上使用JodaTime,不需要将自己的时区管理添加到应用程序,完全忽略所有内置的Android格式化功能?

解决方案

我认为其他答案都没有提到。是的,当坚持时间信息时,你应该仔细考虑你的用例来决定如何最好地这样做。但是即使你这样做,这个问题也会持续下去。



考虑Android的闹钟应用程序,其源代码免费提供。如果您看到它的 AlarmInstance 类,那么它是如何在数据库中建模的:

 code> private static final String [] QUERY_COLUMNS = {
_ID,
YEAR,
MONTH,
DAY,
HOUR,
MINUTES,
LABEL,
VIBRATE,
RINGTONE,
ALARM_ID,
ALARM_STATE
};要知道一个警报实例何时触发,您可以调用 getAlarmTime()来获取消息,然后在



<

  / ** 
*返回闹钟应该触发的时间。
*
* @返回时间
* /
public Calendar getAlarmTime(){
日历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);
返回日历;
}

注意一个 AlarmInstance 存储确切时间,它应该触发,不管时区。这确保每次您调用 getAlarmTime()时,您可以在用户的​​时区触发正确的时间。这里的问题是如果时区不更新, getAlarmTime()无法获取正确的时间更改,例如,当DST启动时。



JodaTime 在这种情况下派上用场,因为它拥有自己的时间区域数据库。您可以考虑使用其他日期时间库,例如 date4j ,以方便更好地处理日期计算,但这些通常不会不需要处理自己的时区数据。



但是,拥有自己的时区数据会为您的应用程序引入约束:您无法依赖Android时区。这意味着您不能使用其 Calendar 类或其格式化函数。 JodaTime也提供格式化功能,使用它们。如果您必须转换为日历,而不是使用 toCalendar()方法,请创建一个类似于 getAlarmTime()以上,您通过所需的确切时间。



或者,您可以检查是否存在时区不匹配并警告用户像Matt Johnson在他的评论中建议的。如果您决定继续使用Android和Joda的功能,我同意他的观点:


是 - 有两个真相来源,重新失去同步,将会有
的不匹配。检查版本,显示警告,要求更新,
等。可能没有更多的可以做到这一点。




除了还有一件事您可以做:你可以自己更改Android的时区。你应该在这样做之前警告用户,但是您可以强制Android使用与Joda相同的时区偏移量:

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

检查后,如果不一样,您可以更改Android的时区与您从Joda正确时区信息的偏移创建的假区域:

  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());
}

记住,您将需要< uses-permission android:name =android.permission.SET_TIME_ZONE/> 的权限。



最后,更改时区将会更改系统当前时间。不幸的是,只有系统应用可以设置时间,所以最好的方法是打开用户的日期时间设置,并提示他/她将其手动更改为正确的一个:

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

您还必须添加更多控件,以确保在DST启动时更新时区,结束。就像你说的,你将会添加自己的时区管理,但这是保证两个时区数据库之间一致性的唯一方法。


I want to extend a discussion I started on the Reddit Android Dev community yesterday with a new question: How do you manage an up-to-date timezone database shipped with your app using the JodaTime library on a device that has outdated timezone information?

The problem

The specific issue at hand relates to a particular timezone, "Europe/Kaliningrad". And I can reproduce the problem: On an Android 4.4 device, if I manually set its time zone to the above, calling new DateTime() will set this DateTime instance to a time one hour before the actual time displayed on the phone's status bar.

I created a sample Activity to illustrate the problem. On its onCreate() I call the following:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    ResourceZoneInfoProvider.init(getApplicationContext());

    ViewGroup v = (ViewGroup) findViewById(R.id.root);
    addTimeZoneInfo("America/New_York", v);
    addTimeZoneInfo("Europe/Paris", v);
    addTimeZoneInfo("Europe/Kaliningrad", v);
}

private void addTimeZoneInfo(String id, ViewGroup root) {
    AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
    am.setTimeZone(id);
    //Joda does not update its time zone automatically when there is a system change
    DateTimeZone.setDefault(DateTimeZone.forID(id));

    View v = getLayoutInflater().inflate(R.layout.info, root, false);

    TextView idInfo = (TextView) v.findViewById(R.id.id);
    idInfo.setText(id);

    TextView timezone = (TextView) v.findViewById(android.R.id.text1);
    timezone.setText("Time zone: " + TimeZone.getDefault().getDisplayName());

    TextView jodaTime = (TextView) v.findViewById(android.R.id.text2);
    //Using the same pattern as Date()
    jodaTime.setText("Time now (Joda): " + new DateTime().toString("EEE MMM dd HH:mm:ss zzz yyyy"));

    TextView javaTime = (TextView) v.findViewById(R.id.time_java);
    javaTime.setText("Time now (Java): " + new Date().toString());


    root.addView(v);
}

ResourceZoneInfoProvider.init() is part of the joda-time-android library and it is meant to initialize Joda's time zone database. addTimeZoneInfo overwrites the device's time zone and inflates a new view where the updated time zone information is displayed. Here is an example of result:

Note how for "Kaliningrad", Android maps it to "GMT+3:00" because that was the case until 26 October 2014 (see Wikipedia article). Even some web sites still show this time zone as GMT+3:00 because of how relatively recent this change is. The correct, however, is "GMT+2:00" as displayed by JodaTime.

Flawed possible solutions?

This is a problem because no matter how I try to circumvent it, in the end, I have to format the time to display it to the user in their time zone. And when I do that using JodaTime, the time will be incorrectly formatted because it will mismatch the expected time the system is displaying.

Alternatively, suppose I handle everything in UTC. When the user is adding an event in the calendar and picks a time for the reminder, I can set it to UTC, store it in the db like that and be done with it.

However, I need to set that reminder with Android's AlarmManager not at the UTC time I converted the time set by the user but at the one relative to the time they want the reminder to trigger. This requires the time zone info to come into play.

For example, if the user is somewhere at UTC+1:00 and he or she sets a reminder for 9:00am, I can:

  • Create a new DateTime instance set for 09:00am at the user's timezone and store its milliseconds in the db. I can also directly use the same milliseconds with the AlarmManager;
  • Create a new DateTime instance set for 09:00am at UTC and store its milliseconds in the db. This better addresses a few other issues not exactly related to this question. But when setting the time with the AlarmManager, I need to compute its millisecond value for 09:00am at the user's timezone;
  • Ignore completely Joda DateTime and handle the setting of the reminder using Java's Calendar. This will make my app rely on outdated time zone information when displaying the time but at least there won't be inconsistencies when scheduling with the AlarmManager or displaying the dates and times.

What am I missing?

I may be over thinking this and I'm afraid I might be missing something obvious. Am I? Is there any way I could keep using JodaTime on Android short of adding my own time zone management to the app and completely disregard all built-in Android formatting functions?

解决方案

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.

这篇关于如何处理JodaTime和Android的时区数据库差异?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆