繁体   English   中英

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

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

我正在Android应用程序中创建日历。 日历的第一天是星期日或星期一。 这取决于区域设置。 java.util的奇怪行为。 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;
    }
}

设置类构造函数(new GregorianCalendar(1994,3,1),Calendar.SUNDAY)。 在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

在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

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

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

所有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

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

 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年的行为。为什么? 我已经打破了我的大脑。

java.time

好的解决方案是跳过CalendarGregorianCalendar类,并使用java.time中的LocalDate ,即现代Java日期和时间API。 CalendarGregorianCalendar已经过时且设计不佳。 现代API可以更好地使用。 而且LocalDate是一个没有时间且没有时区的日期,所以如果怀疑我在下面播放是正确的,它将保证将您的时区/夏令时问题留在后面。 要在较旧的Android上使用它,请进一步查看。

什么地方出了错? 推测性解释

以下解释纯粹是理论上的,但我能够想到的最好。 它依赖于我无法验证的几个假设:

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

这是纯粹的推测,但如果有这样的错误,你的GregorianCalendar将在目标日期前一天晚上23:00结束,这将解释你的结果。 例如,欧盟国家在3月的最后一个星期天开始夏令时。 这也是1994年的情况。这将非常适合你1994年3月27日星期日的目标日期,并且也会解释你在1992年和1993年的错误结果。我已经做了一个简短的互联网搜索,提到了这样一个错误。 Android GregorianCalendar并没有找到任何支持它。

由于我怀疑解释你的观察,我们需要更多的东西:

  1. 我怀疑的错误只会出现在某些Android版本(4.4,5.0)中并在更高版本(8.0)中修复(或者您的Android 8.0设备将运行不同的时区)。 此外,您运行测试的环境要么没有错误,要么具有不同的默认时区(要么解释测试通过的原因)。
  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小时。 在后一种情况下,可疑的错误将导致你在3月27日8:05(仍然是正确的日期),所以你没有看到错误。 如果您在晚上0:35运行代码,那么您将在3月26日23:35点击,所以您也会看到该案例中的错误。

正如我已经说过的, LocalDate ,java.time和ThreeTenABP将形成良好的解决方案。 如果你选择不依赖外部图书馆,而是选择过时的课程,我相信以下内容会有所帮助:

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

TimeZone是一个又一个旧的和设计不佳的类,特别是我使用的getTimeZone方法有一些令人讨厌的惊喜,但我相信上面的工作(手指交叉)。 这个想法是告诉Calendar使用UTC时间。 UTC没有夏令时,这避免了问题。

你可能会尝试的另一个更糟糕的事情是:

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

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

问题:我可以在Android上使用java.time吗?

是的,java.time适用于较旧和较新的Android设备。 它至少需要Java 6

  • 在Java 8及更高版本和更新的Android设备上(来自API级别26),现代API内置。
  • 在Java 6和7中获取ThreeTen Backport,新类的后端端口(适用于JSR 310的ThreeTen;请参见底部的链接)。
  • 在(较旧的)Android上使用Android版的ThreeTen Backport。 它被称为ThreeTenABP。 并确保从子包中导入org.threeten.bp的日期和时间类。

链接

这里没什么奇怪的。 与conststructor不同, getInstance将根据您的语言环境和时区返回数据。 不确定更新的Android版本,可能在这里发生了变化,或者您使用不同的时区/区域设置进行了测试?

暂无
暂无

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

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM