SimpleDateFormat 小时字符串在某些 *dates* 上减一。如何?为什么?
SimpleDateFormat hour string off-by-one on certain *dates*. How? Why?
我正在使用 SimpleDateFormat
将我的 GregorianCalendar
实例变成漂亮的字符串。我遇到一个奇怪的问题,在某些 dates 中,time 字符串会减一,但并非总是如此。
通常,如果我的 GregorianCalendar
的值类似于 HOUR_OF_DAY=0,MINUTE=11
,那么我会得到一个类似于“1:11 AM”的字符串。但是后来我更改了 YEAR
、MONTH
或 DAY
,现在 HOUR_OF_DAY=0,MINUTE=11
给我字符串“2:11 AM” .
这是我的时间字符串格式化函数(datetime是GregorianCalendar
):
public String toTimeString() {
Log.i(TAG, "Datetime: "+ datetime.toString() + "Formatted as: " + SimpleDateFormat.getTimeInstance(SimpleDateFormat.SHORT).format(datetime.getTime()));
return SimpleDateFormat.getTimeInstance(SimpleDateFormat.SHORT).format(datetime.getTime());
}
这给了我这些日志:
一开始工作不错:
Datetime: java.util.GregorianCalendar[time=1426637512725,areFieldsSet=true,lenient=true,zone=GMT,firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2015,MONTH=2,WEEK_OF_YEAR=12,WEEK_OF_MONTH=3,DAY_OF_MONTH=18,DAY_OF_YEAR=77,DAY_OF_WEEK=4,DAY_OF_WEEK_IN_MONTH=3,AM_PM=0,HOUR=0,HOUR_OF_DAY=0,MINUTE=11,SECOND=52,MILLISECOND=725,ZONE_OFFSET=0,DST_OFFSET=0]
Formatted as: 1:11 AM
然后我将月份从三月更改为四月(年和日做同样的事情)现在这个:
Datetime: java.util.GregorianCalendar[time=1429315912725,areFieldsSet=true,lenient=true,zone=GMT,firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2015,MONTH=3,WEEK_OF_YEAR=16,WEEK_OF_MONTH=3,DAY_OF_MONTH=18,DAY_OF_YEAR=108,DAY_OF_WEEK=7,DAY_OF_WEEK_IN_MONTH=3,AM_PM=0,HOUR=0,HOUR_OF_DAY=0,MINUTE=11,SECOND=52,MILLISECOND=725,ZONE_OFFSET=0,DST_OFFSET=0]
Formatted as: 2:11 AM
这怎么可能?
这种相差一的行为似乎只发生在未来几个月,而不是本月或上个月。 2015 年和 2016 年还好,但 2017 年也出现了问题。实际日期似乎在未来一周或更长时间(无论年份如何)时触发它。
我可以在不同的 UI 小部件中更改年、月和日期,将时间字符串格式从正常切换到逐一关闭再返回。我使用 TimePicker
来控制小时,该 TimePicker
调用 TimePicker.OnTimeChangedListener()
上的字符串格式化程序。我不认为它是相关的,因为它在月份和日期设置为某些值时有效,但这里是代码以防万一。如您所见,有一些逐一处理,因为 GregorianCalendar
将 HOUR_OF_DAY
存储为 0-23 值,而 TimePicker
使用与小部件匹配的值 1-24实际上显示。我想我做对了,但很高兴发现这里的问题是错误的。
private void setUpTimePicker() {
timePicker.setCurrentHour(schedule.getHourOfDay() + 1);
timePicker.setCurrentMinute(schedule.getMinute());
timePicker.setOnTimeChangedListener(new TimePicker.OnTimeChangedListener() {
public void onTimeChanged(TimePicker view, int hourOfDay, int minute) {
schedule.setHourOfDay(hourOfDay - 1);
schedule.setMinute(minute);
timeText.setText(schedule.toTimeString());
}
});
}
格式化为字符串的时间总是正确的(0 = 凌晨 1 点、17 = 下午 5 点等一天中的每个小时)或总是错误的(0 = 凌晨 2 点、17 = 下午 6 点等一天中的每个小时)日期)基于其他日历字段。 一致性让我认为这不是线程问题。
会不会像其他类似问题一样是 时区问题?我无法想象如何。我的 GregorianCalandar
实例总是用这个函数创建的:
public static TimeZone TIMEZONE = TimeZone.getTimeZone("GMT");
private static GregorianCalendar makeCalendar(){
GregorianCalendar now = (GregorianCalendar) GregorianCalendar.getInstance(TIMEZONE);
now.setFirstDayOfWeek(Calendar.SUNDAY);
return now;
}
任何想法将不胜感激!我已经用尽了我能想到的一切,包括在上面睡了几天。 提前感谢任何回答的人!!
更新
Jon 发现了问题,但由于代码更改隐藏在注释中,我也将其复制到这里。
问题是 SimpleDateFormat
使用的是系统时区,而不是我假设的给定 GregorianCalendar
的时区。我通过更改此行来解决此问题:
return SimpleDateFormat.getTimeInstance(SimpleDateFormat.SHORT).format(datetime.getTime());
对此:
SimpleDateFormat sdf = new SimpleDateFormat("hh:mm a", LOCALE);
sdf.setTimeZone(TIMEZONE);
return sdf.format(datetime.getTime());
然后我从 TimePicker
中删除了 +1
和 -1
,它起作用了!
您正在设置您正在使用的 日历 的时区,但这不是您传递给 SimpleDateFormat
的内容 - 您只是在使用此处格式化时的默认系统时区:
SimpleDateFormat.getTimeInstance(SimpleDateFormat.SHORT).format(datetime.getTime())
我建议:
- 理想情况下,从 Java 转到 Joda Time 或
java.time
8。java.util
类 是 糟糕的 。我知道你正在研究 Android,所以 java.time
已经结束,但是 Joda Time 是你的选择吗?
- 将日期选择器排除在外:努力想出一个简短但完整的程序来演示问题 - 只是一个控制台应用程序,它(比如)尝试从一开始就在每个月的 1 号早上 5 点格式化2010 年至 2019 年底。(为简单起见,请尝试在 Android 之外重现。)
- 每当您格式化或解析(或基本上做任何事情)时,准确计算出您感兴趣的时区,并明确指定它。 (我建议使用
UTC
而不是 GMT
来表示 "no offset from UTC",否则有些人会认为您指的是英国时区。)
试试这个方法
Date date = new Date();
System.out.println(new SimpleDateFormat("yyyy.MM.dd HH:mm").format(date));
我正在使用 SimpleDateFormat
将我的 GregorianCalendar
实例变成漂亮的字符串。我遇到一个奇怪的问题,在某些 dates 中,time 字符串会减一,但并非总是如此。
通常,如果我的 GregorianCalendar
的值类似于 HOUR_OF_DAY=0,MINUTE=11
,那么我会得到一个类似于“1:11 AM”的字符串。但是后来我更改了 YEAR
、MONTH
或 DAY
,现在 HOUR_OF_DAY=0,MINUTE=11
给我字符串“2:11 AM” .
这是我的时间字符串格式化函数(datetime是GregorianCalendar
):
public String toTimeString() {
Log.i(TAG, "Datetime: "+ datetime.toString() + "Formatted as: " + SimpleDateFormat.getTimeInstance(SimpleDateFormat.SHORT).format(datetime.getTime()));
return SimpleDateFormat.getTimeInstance(SimpleDateFormat.SHORT).format(datetime.getTime());
}
这给了我这些日志:
一开始工作不错:
Datetime: java.util.GregorianCalendar[time=1426637512725,areFieldsSet=true,lenient=true,zone=GMT,firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2015,MONTH=2,WEEK_OF_YEAR=12,WEEK_OF_MONTH=3,DAY_OF_MONTH=18,DAY_OF_YEAR=77,DAY_OF_WEEK=4,DAY_OF_WEEK_IN_MONTH=3,AM_PM=0,HOUR=0,HOUR_OF_DAY=0,MINUTE=11,SECOND=52,MILLISECOND=725,ZONE_OFFSET=0,DST_OFFSET=0]
Formatted as: 1:11 AM
然后我将月份从三月更改为四月(年和日做同样的事情)现在这个:
Datetime: java.util.GregorianCalendar[time=1429315912725,areFieldsSet=true,lenient=true,zone=GMT,firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2015,MONTH=3,WEEK_OF_YEAR=16,WEEK_OF_MONTH=3,DAY_OF_MONTH=18,DAY_OF_YEAR=108,DAY_OF_WEEK=7,DAY_OF_WEEK_IN_MONTH=3,AM_PM=0,HOUR=0,HOUR_OF_DAY=0,MINUTE=11,SECOND=52,MILLISECOND=725,ZONE_OFFSET=0,DST_OFFSET=0]
Formatted as: 2:11 AM
这怎么可能?
这种相差一的行为似乎只发生在未来几个月,而不是本月或上个月。 2015 年和 2016 年还好,但 2017 年也出现了问题。实际日期似乎在未来一周或更长时间(无论年份如何)时触发它。
我可以在不同的 UI 小部件中更改年、月和日期,将时间字符串格式从正常切换到逐一关闭再返回。我使用 TimePicker
来控制小时,该 TimePicker
调用 TimePicker.OnTimeChangedListener()
上的字符串格式化程序。我不认为它是相关的,因为它在月份和日期设置为某些值时有效,但这里是代码以防万一。如您所见,有一些逐一处理,因为 GregorianCalendar
将 HOUR_OF_DAY
存储为 0-23 值,而 TimePicker
使用与小部件匹配的值 1-24实际上显示。我想我做对了,但很高兴发现这里的问题是错误的。
private void setUpTimePicker() {
timePicker.setCurrentHour(schedule.getHourOfDay() + 1);
timePicker.setCurrentMinute(schedule.getMinute());
timePicker.setOnTimeChangedListener(new TimePicker.OnTimeChangedListener() {
public void onTimeChanged(TimePicker view, int hourOfDay, int minute) {
schedule.setHourOfDay(hourOfDay - 1);
schedule.setMinute(minute);
timeText.setText(schedule.toTimeString());
}
});
}
格式化为字符串的时间总是正确的(0 = 凌晨 1 点、17 = 下午 5 点等一天中的每个小时)或总是错误的(0 = 凌晨 2 点、17 = 下午 6 点等一天中的每个小时)日期)基于其他日历字段。 一致性让我认为这不是线程问题。
会不会像其他类似问题一样是 时区问题?我无法想象如何。我的 GregorianCalandar
实例总是用这个函数创建的:
public static TimeZone TIMEZONE = TimeZone.getTimeZone("GMT");
private static GregorianCalendar makeCalendar(){
GregorianCalendar now = (GregorianCalendar) GregorianCalendar.getInstance(TIMEZONE);
now.setFirstDayOfWeek(Calendar.SUNDAY);
return now;
}
任何想法将不胜感激!我已经用尽了我能想到的一切,包括在上面睡了几天。 提前感谢任何回答的人!!
更新
Jon 发现了问题,但由于代码更改隐藏在注释中,我也将其复制到这里。
问题是 SimpleDateFormat
使用的是系统时区,而不是我假设的给定 GregorianCalendar
的时区。我通过更改此行来解决此问题:
return SimpleDateFormat.getTimeInstance(SimpleDateFormat.SHORT).format(datetime.getTime());
对此:
SimpleDateFormat sdf = new SimpleDateFormat("hh:mm a", LOCALE);
sdf.setTimeZone(TIMEZONE);
return sdf.format(datetime.getTime());
然后我从 TimePicker
中删除了 +1
和 -1
,它起作用了!
您正在设置您正在使用的 日历 的时区,但这不是您传递给 SimpleDateFormat
的内容 - 您只是在使用此处格式化时的默认系统时区:
SimpleDateFormat.getTimeInstance(SimpleDateFormat.SHORT).format(datetime.getTime())
我建议:
- 理想情况下,从 Java 转到 Joda Time 或
java.time
8。java.util
类 是 糟糕的 。我知道你正在研究 Android,所以java.time
已经结束,但是 Joda Time 是你的选择吗? - 将日期选择器排除在外:努力想出一个简短但完整的程序来演示问题 - 只是一个控制台应用程序,它(比如)尝试从一开始就在每个月的 1 号早上 5 点格式化2010 年至 2019 年底。(为简单起见,请尝试在 Android 之外重现。)
- 每当您格式化或解析(或基本上做任何事情)时,准确计算出您感兴趣的时区,并明确指定它。 (我建议使用
UTC
而不是GMT
来表示 "no offset from UTC",否则有些人会认为您指的是英国时区。)
试试这个方法
Date date = new Date();
System.out.println(new SimpleDateFormat("yyyy.MM.dd HH:mm").format(date));