简体   繁体   English

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

[英]Strange behavior of the java.util.GregorianCalendar class in different android versions

I am creating a calendar in my Android application. 我正在Android应用程序中创建日历。 The first day of the calendar is Sunday or Monday. 日历的第一天是星期日或星期一。 It depends on the locale. 这取决于区域设置。 Strange behavior of the java.util. java.util的奇怪行为。 GregorianCalendar class in different android versions : GregorianCalendar在不同的Android版本中

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

Set the class constructor (new GregorianCalendar(1994, 3, 1), Calendar.SUNDAY). 设置类构造函数(new GregorianCalendar(1994,3,1),Calendar.SUNDAY)。 In android 4.4, 5.0 Logcat result: 在android 4.4,5.0 Logcat结果中:

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

In android 8.0 Logcat result: 在android 8.0 Logcat结果中:

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

As you can see the result - different days (26 and 27), which corresponds to different days of the week. 你可以看到结果 - 不同的日子(26和27),它对应于一周中的不同日期。 BUT IF YOU CHANGE THE INITIALIZATION of the calendar object: 如果您更改了日历对象的初始化

@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);

The RESULT WILL BE TRUE on all versions of android: 所有Android版本的结果都是正确的:

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

In junit tests the result is correct in all cases (27 and SUNDAY). 在junit测试中,结果在所有情况下都是正确的 (27和SUNDAY)。 Delete the logs from the code and check: 从代码中删除日志并检查:

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

Also behavior for April 1993, 1992. Why? 同样是1993年4月,1992年的行为。为什么? I already broke my brains. 我已经打破了我的大脑。

java.time java.time

The good solution is to skip the Calendar and GregorianCalendar classes and use LocalDate from java.time, the modern Java date and time API, instead. 好的解决方案是跳过CalendarGregorianCalendar类,并使用java.time中的LocalDate ,即现代Java日期和时间API。 Calendar and GregorianCalendar are long outdated and poorly designed. CalendarGregorianCalendar已经过时且设计不佳。 The modern API is so much nicer to work with. 现代API可以更好地使用。 And LocalDate is a date without time and without time zone, so if the suspicion that I am airing below is correct, it will guarantee to leave your time zone/summer time issue behind. 而且LocalDate是一个没有时间且没有时区的日期,所以如果怀疑我在下面播放是正确的,它将保证将您的时区/夏令时问题留在后面。 To use it on older Android, see further down. 要在较旧的Android上使用它,请进一步查看。

What went wrong? 什么地方出了错? Speculative explanation 推测性解释

The following explanation is purely theoretical, but the best I have been able to think of. 以下解释纯粹是理论上的,但我能够想到的最好。 It relies on a couple of assumptions that I have not been able to verify: 它依赖于我无法验证的几个假设:

  • You are (or one of your devices is) in a time zone where summer time (DST) began in the last days of March 1994. 您是(或您的某个设备之一)在1994年3月最后几天夏令时(DST)开始的时区。
  • There might be a bug in GregorianCalendar in Android 4.4 and 5.0 so that currentCalendar.add(Calendar.DAY_OF_WEEK, - (weekDay - 1)); Android 4.4和5.0中的GregorianCalendar可能存在错误,因此currentCalendar.add(Calendar.DAY_OF_WEEK, - (weekDay - 1)); just adds that many times 24 hours. 只是24小时多次添加。

It's pure speculation, but if there is such a bug, your GregorianCalendar will end up at 23:00 on the evening before the target date, which would explain your results. 这是纯粹的推测,但如果有这样的错误,你的GregorianCalendar将在目标日期前一天晚上23:00结束,这将解释你的结果。 Countries in EU, for example, begin summer time on the last Sunday of March. 例如,欧盟国家在3月的最后一个星期天开始夏令时。 This was also the case in 1994. This would fit your target date of Sunday, March 27, 1994 very nicely, and would also explain your wrong results for 1992 and 1993. I have made a brief Internet search for mention of such a bug in Android GregorianCalendar and didn't find anything to support it. 这也是1994年的情况。这将非常适合你1994年3月27日星期日的目标日期,并且也会解释你在1992年和1993年的错误结果。我已经做了一个简短的互联网搜索,提到了这样一个错误。 Android GregorianCalendar并没有找到任何支持它。

For my suspicion to explain your observations, we need a couple of pieces more: 由于我怀疑解释你的观察,我们需要更多的东西:

  1. The bug I am suspecting would be only in some Android versions (4.4, 5.0) and fixed in later versions (8.0) (alternatively you Android 8.0 device would be running a different time zone). 我怀疑的错误只会出现在某些Android版本(4.4,5.0)中并在更高版本(8.0)中修复(或者您的Android 8.0设备将运行不同的时区)。 Also the enviroment where you run your tests either doesn't have the bug or has a different default time zone (either would explain why the tests pass). 此外,您运行测试的环境要么没有错误,要么具有不同的默认时区(要么解释测试通过的原因)。
  2. The GregorianCalendar you get from getInstance has time of day in it. 您从getInstance获得的GregorianCalendar具有一天中的时间。 And keeps it after you set the date. 并在设置日期后保留它。 To spell out the difference between the two ways you set the date: Say you run your code at 9:05. 要说明设置日期的两种方式之间的区别:假设您在9:05运行代码。 new GregorianCalendar(1994, Calendar.APRIL, 1) will give you April 1, 1994 at 00:00. new GregorianCalendar(1994, Calendar.APRIL, 1)将于1994年4月1日00:00给你。 Calendar.getInstance() followed by currentCalendar.set(year, month, 1); Calendar.getInstance()后跟currentCalendar.set(year, month, 1); gives you April 1, 1994 at 09:05. 给你1994年4月1日09:05。 There's a little over 9 hours difference between the two. 这两者之间的差异有点超过9小时。 In the latter case the suspected bug will cause you to hit 8:05 on 27 March, which is still on the correct date, so you don't see the bug. 在后一种情况下,可疑的错误将导致你在3月27日8:05(仍然是正确的日期),所以你没有看到错误。 If you ran your code at, say, 0:35 in the night, you'd hit 23:35 on 26 March, so you'd see the bug at that case too. 如果您在晚上0:35运行代码,那么您将在3月26日23:35点击,所以您也会看到该案例中的错误。

As I already said, LocalDate , java.time and the ThreeTenABP would form the good solution. 正如我已经说过的, LocalDate ,java.time和ThreeTenABP将形成良好的解决方案。 If you choose not to rely on an external library but rather fight your way through with the outdated classes, I believe that the following would help: 如果你选择不依赖外部图书馆,而是选择过时的课程,我相信以下内容会有所帮助:

    GregorianCalendar currentCalendar = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
    currentCalendar.set(year, month, 1);

TimeZone is yet one more old and poorly designed classes, in particular the getTimeZone method that I am using has some nasty surprises to it, but I believe the above works (fingers crossed). TimeZone是一个又一个旧的和设计不佳的类,特别是我使用的getTimeZone方法有一些令人讨厌的惊喜,但我相信上面的工作(手指交叉)。 The idea is to tell the Calendar to use UTC time. 这个想法是告诉Calendar使用UTC时间。 UTC does not have summer time, which evades the problem. UTC没有夏令时,这避免了问题。

Another and more hacky thing you might try, would be: 你可能会尝试的另一个更糟糕的事情是:

    currentCalendar.set(year, month, 1, 6, 0);

This sets the hour of day to 6, meaning that when you go back across the summer time transition, you will hit 5:00 in the morning, which will still be on the correct date (the call above does not set the seconds and milliseconds; in one run I got April 1, 1994 at 06:00:40.213 UTC). 这会将一天中的小时数设置为6,这意味着当您回到夏季时间过渡时,您将在早上5点到达,这仍然​​是正确的日期(上面的调用没有设置秒和毫秒;在一次运行中,我得到了1994年4月1日06:00:40.213 UTC)。

Question: Can I use java.time on Android? 问题:我可以在Android上使用java.time吗?

Yes, java.time works nicely on older and newer Android devices. 是的,java.time适用于较旧和较新的Android设备。 It just requires at least Java 6 . 它至少需要Java 6

  • In Java 8 and later and on newer Android devices (from API level 26) the modern API comes built-in. 在Java 8及更高版本和更新的Android设备上(来自API级别26),现代API内置。
  • In Java 6 and 7 get the ThreeTen Backport, the backport of the new classes (ThreeTen for JSR 310; see the links at the bottom). 在Java 6和7中获取ThreeTen Backport,新类的后端端口(适用于JSR 310的ThreeTen;请参见底部的链接)。
  • On (older) Android use the Android edition of ThreeTen Backport. 在(较旧的)Android上使用Android版的ThreeTen Backport。 It's called ThreeTenABP. 它被称为ThreeTenABP。 And make sure you import the date and time classes from org.threeten.bp with subpackages. 并确保从子包中导入org.threeten.bp的日期和时间类。

Links 链接

Nothing strange here. 这里没什么奇怪的。 getInstance will return data based on your locale and time zone, unlike conststructor. 与conststructor不同, getInstance将根据您的语言环境和时区返回数据。 Not sure about newer Android versions, maybe something changed here or you tested with different time zones/locales? 不确定更新的Android版本,可能在这里发生了变化,或者您使用不同的时区/区域设置进行了测试?

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 JAVA - 从 JDatePicker 获取日期 - 无法将类 java.util.GregorianCalendar 转换为类 java.util.Date - JAVA - Getting a Date from JDatePicker - class java.util.GregorianCalendar cannot be cast to class java.util.Date 应用DST更改时add()的java.util.GregorianCalendar问题 - java.util.GregorianCalendar issue with add() when applying DST changes GregorianCalendar输出:日期为java.util.GregorianCalendar [time = 1141556400000,areFieldsSet = true,areAllFieldsSet = true - GregorianCalendar outputs: The date is java.util.GregorianCalendar[time=1141556400000,areFieldsSet=true,areAllFieldsSet=true JasperReports Server:在scriptlet上使用java.util.Calendar时出现“未配置java.util.GregorianCalendar”错误 - JasperReports Server: Getting “java.util.GregorianCalendar not configured” error when using java.util.Calendar at scriptlet 1976年3月28日到3月29日期间发生了什么与java.util.GregorianCalendar? - What happened between March 28th and March 29th, 1976 with the java.util.GregorianCalendar? Android(Java)GregorianCalendar异常行为 - Android (Java) GregorianCalendar odd behavior java GregorianCalendar添加小时奇怪的行为 - java GregorianCalendar adding hour strange behavior 使用不同Java版本的Strange Default Method行为 - Strange Default Method behavior with different Java versions Java 中的 GregorianCalendar 类 - GregorianCalendar Class in Java 将java.util.GregorianCalendar.toString()重新转换回GregorianCalendar - Reconvert java.util.GregorianCalendar.toString() back to GregorianCalendar
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM