[英]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. 我已经打破了我的大脑。
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. 好的解决方案是跳过Calendar
和GregorianCalendar
类,并使用java.time中的LocalDate
,即现代Java日期和时间API。 Calendar
and GregorianCalendar
are long outdated and poorly designed. Calendar
和GregorianCalendar
已经过时且设计不佳。 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上使用它,请进一步查看。
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: 它依赖于我无法验证的几个假设:
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: 由于我怀疑解释你的观察,我们需要更多的东西:
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)。
Yes, java.time works nicely on older and newer Android devices. 是的,java.time适用于较旧和较新的Android设备。 It just requires at least Java 6 . 它至少需要Java 6 。
org.threeten.bp
with subpackages. 并确保从子包中导入org.threeten.bp
的日期和时间类。 java.time
. Oracle教程:Date Time解释如何使用java.time
。 java.time
was first described. Java规范请求(JSR)310 ,其中首先描述了java.time
。 java.time
to Java 6 and 7 (ThreeTen for JSR-310). ThreeTen Backport项目 , java.time
到Java 6和7(用于JSR-310的ThreeTen)。 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.