【问题标题】:SimpleDateFormat hour string off-by-one on certain *dates*. How? Why?SimpleDateFormat 小时字符串在某些 * 日期 * 上相差一个。如何?为什么?
【发布时间】:2015-03-19 21:54:53
【问题描述】:

我正在使用SimpleDateFormat 将我的GregorianCalendar 实例转换为漂亮的字符串。我遇到了一个奇怪的问题,在某些 dates 中,time 字符串会减一,但并非总是如此。

通常,如果我的 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

然后我将月份从 3 月更改为 4 月(年和日都一样),现在:

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.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 发现了问题,但由于代码更改隐藏在 cmets 中,我也将其复制到这里。

问题是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,它就可以工作了!

【问题讨论】:

  • 我对此表示怀疑,但也许它与夏令时有关?
  • 几乎可以肯定是夏令时。如果打印时将日期/时间转换为您的区域设置,并且您在英国,则从 11 月到 3 月(或多或少)您的本地时间与 GMT 相同。但从 4 月到 10 月(或多或少),您的当地时间比 GMT 早一小时。
  • @jas 和@Felk - 我不在英国,我在意大利。但是,是的!我不知道我必须在SimpleDateFormat 中设置时区。我因为现在没有看到它而感到愚蠢(一年中令人讨厌的两个月,美国做了夏令时,但意大利没有,它影响了我整个月的日历)。我想我只是想到我正在使用 GMT 和时区/DST 不是我的问题。而且我非常确定 my 问题不是另一个时区问题!那好吧!至少你们都很好:)

标签: java android calendar simpledateformat gregorian-calendar


【解决方案1】:

您正在设置您正在使用的 日历 的时区,但这不是您传递给 SimpleDateFormat 的内容 - 您只是在格式化时使用默认系统时区这里:

SimpleDateFormat.getTimeInstance(SimpleDateFormat.SHORT).format(datetime.getTime())

我建议:

  • 理想情况下,从 Java 8 转到 Joda Time 或 java.timejava.util糟糕。我知道您正在使用 Android,所以 java.time 已退出,但您可以选择 Joda Time 吗?
  • 将日期选择器排除在外:努力想出一个简短但完整的程序来演示该问题 - 只是一个控制台应用程序,它(例如)尝试从 2010 年初开始在每个月的 1 日凌晨 5 点格式化到 2019 年底。(为简单起见,请尝试在 Android 之外复制它。)
  • 无论何时格式化或解析(或做任何事情,基本上),准确计算出您感兴趣的时区,并明确指定它。 (我建议使用UTC 而不是GMT 来表示“与UTC 没有偏移”,否则有些人会认为您指的是英国时区。)

【讨论】:

  • 它们并没有那么糟糕,尽管它们确实需要比你想要的更多的知识。但首先这是一个很难掌握的领域。最后,错误很常见但很简单:使用带有默认时区的 SimpleDateFormat。
  • @SebastiaanvandenBroek:当 API 合适时,这是一个更容易掌握的领域。例如,在 Noda Time 中,我们从不隐式使用默认时区来做任何事情——当你想要系统默认时区时,你必须,因为那样即使你确实想要它,任何阅读代码的人都清楚。 CalendarDate 糟糕的方式有很多......我可以在这上面咆哮非常很长时间......
  • 这实际上是一个好主意,从不隐式使用默认时区。运行应用程序的机器的时区几乎从不相关。
【解决方案2】:

试试这个方法

Date date = new Date();    
System.out.println(new SimpleDateFormat("yyyy.MM.dd  HH:mm").format(date));

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-04-20
    • 1970-01-01
    • 1970-01-01
    • 2012-04-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多