我正在Android应用程序中创建日历。日历的第一天是星期日或星期一。这取决于语言环境。 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;
}
}
设置类构造函数(新的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和周日)。从代码中删除日志并检查:
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年也有行为。为什么?我已经伤脑筋了。
答案 0 :(得分:3)
一个好的解决方案是跳过Calendar
和GregorianCalendar
类,而改用Java.time(现代Java日期和时间API)中的LocalDate
。 Calendar
和GregorianCalendar
早已过时且设计欠佳。现代的API更好用。并且LocalDate
是一个没有时间,没有时区的日期,因此,如果我在下面播出的怀疑是正确的,它将保证不留下您的时区/夏令时问题。要在较旧的Android上使用它,请参阅下文。
以下解释纯粹是理论上的,但是我已经想到了最好的解释。它基于一些我无法验证的假设:
GregorianCalendar
中可能存在一个错误,因此currentCalendar.add(Calendar.DAY_OF_WEEK, - (weekDay - 1));
仅在24小时内添加了很多次。纯粹是猜测,但如果存在此类错误,您的GregorianCalendar
将会在目标日期前一天的晚上23:00结束,这可以解释您的结果。例如,欧盟国家/地区在3月的最后一个星期日开始夏季时间。 1994年的情况也是如此。这非常适合您的目标日期1994年3月27日,星期日,也可以解释您在1992年和1993年的错误结果。 Android GregorianCalendar
,没有找到任何支持它的东西。
我怀疑是要解释您的观察结果,我们还需要几段内容:
GregorianCalendar
获得的getInstance
中有一天中的时间。并在设置日期后保留它。要说明两种设置日期的方式之间的区别:假设您在9:05运行代码。 new GregorianCalendar(1994, Calendar.APRIL, 1)
将在1994年4月1日00:00给您。 Calendar.getInstance()
后跟currentCalendar.set(year, month, 1);
会给您1994年4月1日上午9: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:00播放,这仍然是正确的日期(上述调用不会设置秒和毫秒;一次运行,我得到的时间是1994年4月1日世界标准时间06:00:40.213。
是的,java.time在较新和较旧的Android设备上均可正常运行。它只需要至少 Java 6 。
org.threeten.bp
导入日期和时间类。java.time
。java.time
。java.time
向Java 6和7(JSR-310的ThreeTen)的反向端口。答案 1 :(得分:0)
这里没什么奇怪的。与构造函数不同,getInstance
将根据您的语言环境和时区返回数据。不确定较新的Android版本,也许这里有所更改,或者您使用不同的时区/语言环境进行了测试?