首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >java.util.GregorianCalendar类在不同安卓版本中的奇怪行为

java.util.GregorianCalendar类在不同安卓版本中的奇怪行为
EN

Stack Overflow用户
提问于 2018-10-12 20:35:07
回答 2查看 732关注 0票数 1

我正在Android应用程序中创建日历。日历的第一天是星期天或星期一。这取决于区域设置。不同安卓版本中java.util.GregorianCalendar的奇怪行为:

代码语言:javascript
复制
public class CurrentMonth extends AbstractCurrentMonth implements InterfaceCurrentMonth {

    public CurrentMonth(GregorianCalendar calendar, int firstDayOfWeek) {
        super(calendar, firstDayOfWeek);
    }

    @Override
    public List<ContentAbstract> getListContent() {
        int year = calendar.get(Calendar.YEAR);
        int month = calendar.get(Calendar.MONTH);

        GregorianCalendar currentCalendar = new GregorianCalendar(year, month, 1);

        List<ContentAbstract> list = new ArrayList<>();
        int weekDay = getDayOfWeek(currentCalendar);
        currentCalendar.add(Calendar.DAY_OF_WEEK, - (weekDay - 1));

        while (currentCalendar.get(Calendar.MONTH) != month) {
            list.add(getContent(currentCalendar));
            currentCalendar.add(Calendar.DAY_OF_MONTH, 1);
        }

        while (currentCalendar.get(Calendar.MONTH) == month) {
            list.add(getContent(currentCalendar));
            currentCalendar.add(Calendar.DAY_OF_MONTH, 1);
        }
        currentCalendar.add(Calendar.DAY_OF_MONTH, - 1);

        while (getDayOfWeek(currentCalendar) != 7) {
            currentCalendar.add(Calendar.DAY_OF_MONTH, 1);
            list.add(getContent(currentCalendar));
        }

        Log.i("text", "yaer: " + list.get(0).getYear());
        Log.i("text", "month: " + list.get(0).getMonth());
        Log.i("text", "day of month: " + list.get(0).getDay());
        Log.i("text", "day of week: " + list.get(0).getDayOfWeek());

        return list;
    }

    private int getDayOfWeek(GregorianCalendar currentCalendar) {
        int weekDay;
        if (firstDayOfWeek == Calendar.MONDAY) {
            weekDay = 7 - (8 - currentCalendar.get(Calendar.DAY_OF_WEEK)) % 7;
        }
        else weekDay = currentCalendar.get(Calendar.DAY_OF_WEEK);
        return weekDay;
    }

    private GraphicContent getContent(GregorianCalendar cal) {
        GraphicContent content = new GraphicContent();
        content.setYear(cal.get(Calendar.YEAR));
        content.setMonth(cal.get(Calendar.MONTH));
        content.setDay(cal.get(Calendar.DAY_OF_MONTH));
        content.setDayOfWeek(cal.get(Calendar.DAY_OF_WEEK));
        return content;
    }
}

public class GraphicContent extends ContentAbstract {
    private int year;
    private int month;
    private int day;
    private int dayOfWeek;

    @Override
    public int getYear() {
        return year;
    }

    @Override
    public void setYear(int year) {
        this.year = year;
    }

    @Override
    public int getMonth() {
        return month;
    }

    @Override
    public void setMonth(int month) {
        this.month = month;
    }

    @Override
    public int getDay() {
        return day;
    }

    @Override
    public void setDay(int day) {
        this.day = day;
    }

    @Override
    public int getDayOfWeek() {
        return dayOfWeek;
    }

    @Override
    public void setDayOfWeek(int dayOfWeek) {
        this.dayOfWeek = dayOfWeek;
    }
}

设置类构造函数(new GregorianCalendar(1994,3,1),Calendar.SUNDAY)。android 4.4,5.0 Logcat中的结果:

代码语言:javascript
复制
10-12 14:32:28.332 27739-27739/*** I/text: yaer: 1994
10-12 14:32:28.332 27739-27739/*** I/text: month: 2
10-12 14:32:28.332 27739-27739/*** I/text: day of month: 26
10-12 14:32:28.332 27739-27739/*** I/text: day of week: 7

android 8.0 Logcat中的结果:

代码语言:javascript
复制
2018-10-12 11:50:59.549 6565-6565/*** I/text: yaer: 1994
2018-10-12 11:50:59.549 6565-6565/*** I/text: month: 2
2018-10-12 11:50:59.549 6565-6565/*** I/text: day of month: 27
2018-10-12 11:50:59.549 6565-6565/*** I/text: day of week: 1

正如您可以看到的结果-不同的日期(26和27),这对应于一周中的不同日期。,但如果您更改日历对象的初始化:

代码语言:javascript
复制
@Override
    public List<ContentAbstract> getListContent() {
        int year = calendar.get(Calendar.YEAR);
        int month = calendar.get(Calendar.MONTH);

        GregorianCalendar currentCalendar = (GregorianCalendar) Calendar.getInstance();
        currentCalendar.set(year, month, 1);

在所有版本的安卓系统上,的结果都是真的:

代码语言:javascript
复制
10-12 15:12:56.400 28914-28914/*** I/text: yaer: 1994
10-12 15:12:56.400 28914-28914/*** I/text: month: 2
10-12 15:12:56.400 28914-28914/*** I/text: day of month: 27
10-12 15:12:56.400 28914-28914/*** I/text: week day: 1

junit中的测试结果在所有情况下都是正确的,(27日和周日)。从代码中删除日志并检查:

代码语言:javascript
复制
 public class TestCurrentMonth {

    @Test
    public void testGetListContent() {
        GregorianCalendar calendar = new GregorianCalendar(1994, 3, 1);
        int firstDay = Calendar.SUNDAY;
        CurrentMonth currentMonth = new CurrentMonth(calendar, firstDay);
        List<ContentAbstract> list = currentMonth.getListContent();
        Assert.assertEquals(27, list.get(0).getDay());
        Assert.assertEquals(Calendar.SUNDAY, list.get(0).getDayOfWeek());
    }
}

另请参阅1993年4月、1992年4月的行为。为什么?我已经伤透脑筋了。

EN

回答 2

Stack Overflow用户

发布于 2018-10-14 12:17:52

java.time

好的解决方案是跳过CalendarGregorianCalendar类,使用java.time中的LocalDate,即现代的Java date and time API。CalendarGregorianCalendar早就过时了,设计也很糟糕。现代的API使用起来要好得多。LocalDate是一个没有时间和时区的日期,所以如果我在下面广播的怀疑是正确的,它将保证把你的时区/夏令时问题抛在脑后。要在较旧的Android上使用它,请参阅下面的内容。

哪里出了问题?推测性解释

下面的解释纯粹是理论上的,但这是我能想到的最好的解释。它依赖于一些我无法验证的假设:

  • 您所在的时区(或者您的某个设备所在的时区)的夏令时(DST)是从1994年3月的最后几天开始的。
  • Android 4.4和5.0的GregorianCalendar中可能存在错误,因此currentCalendar.add(Calendar.DAY_OF_WEEK, - (weekDay - 1));只是将24小时的时间增加了很多倍。

这纯粹是猜测,但如果有这样的bug,你的GregorianCalendar会在目标日期的前一天晚上23:00结束,这可以解释你的结果。例如,欧盟国家的夏季时间从3月的最后一个星期天开始。1994年的情况也是如此。这将非常好地符合您的目标日期为1994年3月27日星期日,也可以解释1992和1993年的错误结果。我在网上搜索了一下,想知道安卓GregorianCalendar中有这样一个bug,但没有找到任何支持它的东西。

为了让我怀疑你的观察结果,我们还需要一些东西:

  1. 我怀疑这个bug只会在一些安卓版本(4.4,5.0)中出现,并在以后的版本( 8.0 )中得到修复(或者你的安卓8.0设备将运行不同的时区)。此外,您运行测试的环境要么没有bug,要么有不同的默认时区(两者都可以解释测试通过的原因)。
  2. 您从getInstance获得的GregorianCalendar中有一天的时间。并在你设定日期后保留下来。为了阐明设置日期的两种方式之间的区别:假设您在9:05运行代码。new GregorianCalendar(1994, Calendar.APRIL, 1)会在1994年4月1日00:00给你。Calendar.getInstance()currentCalendar.set(year, month, 1);在1994年4月1日的09:05给出。两者之间有9个多小时的差异。在后一种情况下,疑似bug将导致您在3月27日到达8:05,这仍然是正确的日期,所以您看不到bug。比方说,如果你在晚上0:35运行你的代码,你会在3月26日达到23:35,所以你也会在这种情况下看到错误。

正如我已经说过的,LocalDate、java.time和ThreeTenABP将形成很好的解决方案。如果您选择不依赖外部库,而是通过使用过时的类来拼搏,我相信以下内容将有所帮助:

代码语言:javascript
复制
    GregorianCalendar currentCalendar = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
    currentCalendar.set(year, month, 1);

TimeZone仍然是一个古老且设计糟糕的类,特别是我使用的getTimeZone方法有一些令人不快的惊喜,但我相信上面的方法是有效的(祈祷)。这个想法是告诉Calendar使用UTC时间。UTC没有夏季时间,这就回避了这个问题。

你可以尝试的另一件更棘手的事情是:

代码语言:javascript
复制
    currentCalendar.set(year, month, 1, 6, 0);

这将一天中的小时设置为6,这意味着当您回到夏季时间转换时,您将到达早上5:00,这仍然是正确的日期(上面的调用没有设置秒和毫秒;在一次运行中,我在1994年4月1日06:00:40.213 UTC)。

问:我可以在安卓系统上使用java.time吗?

是的,java.time在旧的和新的安卓设备上都能很好地工作。它只需要至少Java6即可。

Java8及更高版本的

  • 和更新的安卓设备(从API26开始)内置了现代的API。Java6和7中的
  • 获得了ThreeTen后端口,这是新类的后端口(JSR310的ThreeTen;请参阅底部的链接)。(较老的)安卓系统上的
  • 使用的是ThreeTen后端口的安卓版本。它被称为ThreeTenABP。并确保使用subpackages.

org.threeten.bp导入date和time类

链接

票数 3
EN

Stack Overflow用户

发布于 2018-10-12 20:47:42

这里没什么奇怪的。与构造器不同,getInstance将根据您的语言环境和时区返回数据。不确定较新的Android版本,可能这里发生了一些变化,或者您使用不同的时区/地区进行了测试?

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/52779671

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档