java - SimpleDateFormat 小时字符串在某些*日期*上相差一。如何?为什么?

标签 java android calendar simpledateformat gregorian-calendar

我正在使用 SimpleDateFormat 将我的 GregorianCalendar 实例转换为漂亮的字符串。我遇到一个奇怪的问题,在某些日期中,时间字符串会相差一位,但并非总是如此。

通常,如果我的 GregorianCalendar 具有类似于 HOUR_OF_DAY=0,MINUTE=11 的值,那么我会得到类似于“1:11 AM”的字符串。但后来我更改了 YEARMONTHDAY,现在 HOUR_OF_DAY=0,MINUTE=11 给了我字符串“2:11 AM”。

这是我的时间字符串格式化函数(日期时间是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() 上的字符串格式化程序。我认为这不相关,因为当月份和日期设置为某些值时它会起作用,但这里是代码以防万一。正如您所看到的,存在一些差一处理,因为 GregorianCalendarHOUR_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 8 转向 Joda Time 或 java.timejava.util糟糕。我知道您正在使用 Android,因此 java.time 已经退出,但是 Joda Time 适合您吗?
  • 将日期选择器从等式中剔除:努力想出一个简短但完整的程序来演示问题 - 只是一个控制台应用程序,它(比如说)尝试从 2010 年初开始格式化每个月的第一天早上 5 点到 2019 年底。(为简单起见,请尝试在 Android 之外重现它。)
  • 每当您格式化或解析(或者基本上做任何事情)时,请准确计算出您感兴趣的时区,并明确指定它。 (我建议使用 UTC 而不是 GMT 来表示“与 UTC 没有偏移”,否则有些人会认为您指的是英国时区。)

关于java - SimpleDateFormat 小时字符串在某些*日期*上相差一。如何?为什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29155702/

相关文章:

java - 致命异常主要 - 应用程序在启动时崩溃

android - 即使设备已连接,获取连接也会返回 false

Javascript NoGray 日历如果今天被阻止,则在选择的阻止日期后查找下一个可用日期

Android 日历 DTSTART 和 DTEND 问题

java - 如何将 java.util.Date 对象转换为 Calendar 对象?

java - Spark 采样 - 比使用完整的 RDD/DataFrame 快多少

java - SonarQube 警告 "Insufficient branch coverage by unit tests"

java - 调用目标异常 : Plugin (Bundle) "org.datanucleus" is already registered

android 切换按钮状态始终为真

java - 如何为泛型对实现 equals?