【问题标题】:Adding days with java.util.Calendar gives strange results用 java.util.Calendar 添加天数会产生奇怪的结果
【发布时间】:2011-10-07 19:14:55
【问题描述】:

使用 java.util.Calendar 将一天添加到 Date,并使用 SimpleDateFormat 显示结果,有时似乎会丢失一天(通常在 3 月),有时会跳过一天(在 11 月)。

下面的程序,带有输出,说明了这个问题。请注意,我只是一次添加一天,然后跳过几个月并再添加几天。你会看到 2008-03-09 被打印了两次,但是 2008-11-02 被跳过了。同样的事情发生在其他年份,但发生在不同的日子。我不得不尝试找出导致问题的日期。

如果我没有在 SimpleDateFormat 中将时区设置为 UTC,那么问题就不会发生。我在美国中部时区的一台机器上运行了这个。

这看起来确实像 Calendar 或 SimpleDateFormat 中的错误,但我无法在任何地方找到它的文档。有人对这里发生的事情有解释吗?

程序:

package mab;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;

public class CalendarHiccup2 {

    public static void main(String[] args) {
        addDays("2008-03-08");
        addDays("2009-03-07");
        addDays("2010-03-13");
    }

    public static void addDays(String dateString) {
        System.out.println("Got dateString: " + dateString);

        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        sdf.setTimeZone(TimeZone.getTimeZone("UTC"));

        Calendar calendar = Calendar.getInstance();
        try {
            calendar.setTime(sdf.parse(dateString));
            Date day1 = calendar.getTime();
            System.out.println("  day1 = " + sdf.format(day1));

            calendar.add(java.util.Calendar.DAY_OF_MONTH, 1);
            Date day2 = calendar.getTime();
            System.out.println("  day2 = " + sdf.format(day2));

            calendar.add(java.util.Calendar.DAY_OF_MONTH, 1);
            Date day3 = calendar.getTime();
            System.out.println("  day3 = " + sdf.format(day3));

            calendar.add(java.util.Calendar.DAY_OF_MONTH, 1);
            Date day4 = calendar.getTime();
            System.out.println("  day4 = " + sdf.format(day4));

            // Skipping a few days ahead:
            calendar.add(java.util.Calendar.DAY_OF_MONTH, 235);
            Date day5 = calendar.getTime();
            System.out.println("  day5 = " + sdf.format(day5));

            calendar.add(java.util.Calendar.DAY_OF_MONTH, 1);
            Date day6 = calendar.getTime();
            System.out.println("  day6 = " + sdf.format(day6));

            calendar.add(java.util.Calendar.DAY_OF_MONTH, 1);
            Date day7 = calendar.getTime();
            System.out.println("  day7 = " + sdf.format(day7));

            calendar.add(java.util.Calendar.DAY_OF_MONTH, 1);
            Date day8 = calendar.getTime();
            System.out.println("  day8 = " + sdf.format(day8));

        } catch (Exception e) {
        }
    }

}

输出:

Got dateString: 2008-03-08
  day1 = 2008-03-08
  day2 = 2008-03-09
  day3 = 2008-03-09
  day4 = 2008-03-10
  day5 = 2008-10-31
  day6 = 2008-11-01
  day7 = 2008-11-03
  day8 = 2008-11-04
Got dateString: 2009-03-07
  day1 = 2009-03-07
  day2 = 2009-03-08
  day3 = 2009-03-08
  day4 = 2009-03-09
  day5 = 2009-10-30
  day6 = 2009-10-31
  day7 = 2009-11-02
  day8 = 2009-11-03
Got dateString: 2010-03-13
  day1 = 2010-03-13
  day2 = 2010-03-14
  day3 = 2010-03-14
  day4 = 2010-03-15
  day5 = 2010-11-05
  day6 = 2010-11-06
  day7 = 2010-11-08
  day8 = 2010-11-09

【问题讨论】:

  • 发生这种情况时的诀窍是检查您的假设:我只是打印了没有 SDF 的日历本身,问题很明显,因为日历的 TZ 发生了变化。
  • 谢谢。通过“打印日历”,我想您的意思是调用 toString()。我又做了几个测试。添加一个日历日时,我的时区 (CST) 中的日历将在“提前”日增加 23 小时,在“后退”日增加 25 小时。如果 SimpleDateFormat 也是 CST,它将正确打印出当天,但如果是 UTC,则添加的 23 小时并不能完全让您到达第二天。而在秋季,增加的 25 小时会超过 UTC 一天。如果日历为 UTC,则始终添加 24 小时。关键是 Calendar 和 SimpleDateFormat 应该在时区上达成一致。
  • 如果你真的告诉他们使用相同的 TZ,他们只会在时区上达成一致。 (欢迎来到 Java 时间/日期处理的世界。)

标签: java simpledateformat


【解决方案1】:

这是daylight saving time引起的,完全正确。

时间(在北半球)通常在 3 月提前一小时,并在 11 月向后移动。

【讨论】:

【解决方案2】:

看起来更像是夏令时问题,在 3 月和 11 月会发生变化。您可以尝试将时间元素设置为 00:00:00 吗?如果你这样做,

addDays("2008-03-08 00:00:00");
addDays("2009-03-07 00:00:00");
addDays("2010-03-13 00:00:00");

并将格式更改为,

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

你会看到它在时间元素上的不同。

【讨论】:

    【解决方案3】:

    当您遇到此类问题时,您必须打印 Calendar 和 Date 实例以了解发生了什么。

    以下内容:

    sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
    

    根据您的 JVM 本地时间(和时区)更改日历的时间。 Calendar.getInstance(); 在您的 JVM 本地时间创建一个日历,当您执行 calendar.setTime(sdf.parse(".....")) 时,它将时间设置为 UTC(鉴于您创建 sdf 的方式!)。取决于可以让您度过午夜的 DoY(以及年份!),当您使用 yyyy-MM-dd 格式打印日期时,您会看到一天的差异。

    打印完整的日历和完整的日期,您就会知道发生了什么!

    【讨论】:

    • Nit pick:sdf.setTimeZone 不会更改日历的时间,它会更改 SDF 的时间,然后用于设置日历的时间。
    【解决方案4】:

    夏令时

    answer by Tomasz Nurkiewicz 是正确的,问题是Daylight Saving Time (DST)。

    当地时间在 3 月的第二个星期日从 02:00 本地标准时间更改为 03:00 本地夏令时,并在 11 月的第一个星期日从 02:00 本地标准时间返回到 01:00 本地标准时间。

    参见维基百科Central Time Zone

    乔达时间

    Joda-Time 库使此类工作变得更加容易。

    Joda-Time 中的DateTime 知道自己的time zone。要使用UTC/GMT(无时间偏移),请传递内置常量DateTimeZone.UTC

    要仅打印日期时间的日期部分,请使用DateTimeFormatter

    // © 2013 Basil Bourque. This source code may be used freely forever by anyone taking full responsibility for doing so.
    // import org.joda.time.*;
    // import org.joda.time.format.*;
    
    String dateString = "2008-03-08";
    
    // withTimeAtStartOfDay() is probably superfluous in this example, but I like that it self-documents our focus on the day rather than a particular time of day.
    DateTime dateTime = new DateTime( dateString, DateTimeZone.UTC ).withTimeAtStartOfDay();
    DateTime dateTimePlus1 = dateTime.plusDays( 1 ).withTimeAtStartOfDay();
    DateTime dateTimePlus2 = dateTime.plusDays( 2 ).withTimeAtStartOfDay();
    DateTime dateTimePlus3 = dateTime.plusDays( 3 ).withTimeAtStartOfDay();
    

    转储到控制台...

    System.out.println("dateTime: " + dateTime );
    System.out.println("dateTime (date portion): " + ISODateTimeFormat.date().withZone( DateTimeZone.UTC ).print( dateTime ) );
    System.out.println("dateTimePlus1: " + dateTimePlus1 );
    System.out.println("dateTimePlus2: " + dateTimePlus2 );
    System.out.println("dateTimePlus3: " + dateTimePlus3 );
    

    运行时……

    dateTime: 2008-03-08T00:00:00.000Z
    dateTime (date portion): 2008-03-08
    dateTimePlus1: 2008-03-09T00:00:00.000Z
    dateTimePlus2: 2008-03-10T00:00:00.000Z
    dateTimePlus3: 2008-03-11T00:00:00.000Z
    

    【讨论】:

      猜你喜欢
      • 2022-01-08
      • 1970-01-01
      • 2015-10-04
      • 1970-01-01
      • 2013-03-15
      • 1970-01-01
      • 2019-01-11
      • 2013-09-28
      • 1970-01-01
      相关资源
      最近更新 更多