公历错时

Gregorian Calendar Wrong Hour

因此,我开始使用 Java-GregorianCalendar Class 进行一些测试,并注意到在用毫秒初始化对象时发生了奇怪的行为。困扰我的是,虽然我将毫秒设置为0,但时间显示为1点。

稍微浏览一下 Whosebug 后,我注意到 Java 有时会与夏令时和冬令时混淆。所以我的问题是,如果虽然今年已经完成时间更改并且我们再次生活在冬季,但这种奇怪的行为来自冬季和夏季。

这是我用来测试的代码:

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.GregorianCalendar;

public class TestCalendar {

    public static void main(String[] args) {
        GregorianCalendar c = new GregorianCalendar();
        c.setTimeInMillis(-3600000);
        System.out.println(c.getTimeInMillis());
        System.out.println(c.get(Calendar.HOUR));
        String format = "mm:ss";
        if (c.get(Calendar.HOUR_OF_DAY) > 0) format = "HH:mm:ss";
        System.out.println(new SimpleDateFormat(format).format(c.getTime()));
    }

}

这给了我输出

-3600000
0
0

最好的办法是找到一个独立于减去 -3600000 的解决方案,就好像在其他计算机上这个 "bug" 不存在我不想 23:00:00 :)

编辑:

经过更多尝试并感谢反馈,我能够通过在初始化日历后添加此行来解决我的小问题:

c.setTimeZone(TimeZone.getTimeZone("GMT"));

设置c.setTimeInMillis(0);将时间设置为 1970 年 1 月 1 日 00:00:00 GMT(1970-01-01 00:00:00 GMT),称为纪元

https://docs.oracle.com/javase/7/docs/api/java/util/Calendar.html#setTimeInMillis(long)

public void setTimeInMillis(long millis)

Sets this Calendar's current time from the given long value.

Parameters:
    millis - the new time in UTC milliseconds from the epoch.
See Also:
    setTime(Date), getTimeInMillis()

如果你想将时间设置为午夜,我想你想做。

    c.set(Calendar.HOUR_OF_DAY, 0);
    c.set(Calendar.MINUTE, 0);
    c.set(Calendar.SECOND, 0);
    c.set(Calendar.MILLISECOND, 0);

tl;博士

问题:您错误地更改了日期,而不仅仅是时间。最重要的是,隐含地应用了时区。

解决方法:改用现代的java.time类.

LocalDate
.now()                 // Better to explicitly pass the desired/expected time zone as a `ZoneId` object.
.atStartOfDay()        // Again, better to explicitly pass the desired/expected time zone as a `ZoneId` object.

Returns一个LocalDateTime注意:片刻,时间轴上的一个点)。

2018-11-01T00:00

最好指定时区。

LocalDate
.now( 
    ZoneId.of( "Pacific/Auckland"  ) 
)                
.atStartOfDay(
    ZoneId.of( "Pacific/Auckland"  ) 
)   

Returns一个ZonedDateTime。这一个时刻,时间轴上的一个点。

2018-11-02T00:00+13:00[Pacific/Auckland]

GregorianCalendar::setTimeInMillis不是设置时间

显然您误以为 GregorianCalendar::setTimeInMillis 会设置时间而不影响日期。在这些遗留日期时间 类 的众多缺陷中,命名 类 和方法的选择非常糟糕。

但是,不,该方法将时刻重新定义为自纪元参考日期 1970-01-01T00:00Z 以来的毫秒数。

添加隐式分配给 GregorianCalendar 的时区,您会得到意想不到的结果。

I started to test around a bit with the Java-GregorianCalendar

不要。

与 Java 的最早版本捆绑在一起的旧日期时间 类 很糟糕 。它们在多年前被 JSR 310java.time 类 所取代,定义在 JSR 310.

具体来说,要跟踪 UTC 时刻,请使用 Instant

initializing the Object with milliseconds

不要。

跟踪时间作为从纪元开始计数的参考很容易出错。业内有 many different epoch reference dates 个在使用。业界使用不同的粒度(整秒、毫秒、微秒、纳秒)。

所以从纪元开始计数是不明确的。也容易混淆和遗漏错误,因为人类无法阅读值的含义。

交换日期时间值时,请改用标准 ISO 8601 格式的字符串。

当从 1970 年第一时刻的 Unix 纪元开始计算 UTC 的毫秒数时,解析为 Instant

Instant instant = Instant.ofEpochMilli( … ) ;

java.time

现代解决方案使用 java.time 类。

获取你的约会对象。

LocalDate

LocalDate class represents a date-only value without time-of-day and without time zone or offset-from-UTC.

时区对于确定日期至关重要。对于任何给定时刻,日期在全球范围内因地区而异。例如,Paris France is a new day while still “yesterday” in Montréal Québec.

午夜后几分钟

如果未指定时区,JVM 将隐式应用其当前默认时区。该默认值可以 change at any moment during runtime(!), so your results may vary. Better to specify your desired/expected time zone 明确作为参数。

指定 proper time zone name in the format of continent/region, such as America/Montreal, Africa/CasablancaPacific/Auckland。切勿使用 ESTIST 等 2-4 字母缩写,因为它们 不是 真正的时区,没有标准化,甚至不是唯一的(!)。

ZoneId z = ZoneId.of( "America/Montreal" ) ;  
LocalDate today = LocalDate.now( z ) ;

如果你想使用 JVM 当前的默认时区,请求它并作为参数传递。如果省略,则隐式应用 JVM 的当前默认值。最好是明确的,因为默认值可能会在任何时候 在运行时 中被 JVM 中任何应用程序的任何线程中的任何代码更改。

ZoneId z = ZoneId.systemDefault() ;  // Get JVM’s current default time zone.

或指定日期。您可以通过数字设置月份,1 月至 12 月的编号为 1-12。

LocalDate ld = LocalDate.of( 1986 , 2 , 23 ) ;  // Years use sane direct numbering (1986 means year 1986). Months use sane numbering, 1-12 for January-December.

或者,更好的是,使用 Month enum objects pre-defined, one for each month of the year. Tip: Use these Month objects throughout your codebase rather than a mere integer number to make your code more self-documenting, ensure valid values, and provide type-safety

LocalDate ld = LocalDate.of( 1986 , Month.FEBRUARY , 23 ) ;

ZonedDateTime

显然您想要当天的第一时间。顺便说一句,不要把这当成“午夜”,因为这个词含糊不清。

一天的第一个时刻可能不是00:00。夏令时 (DST) 等异常意味着某些地区某些日期的第一时刻可能是另一个时间,例如 01:00。让java.time确定第一个时刻

指定时区。

ZoneId z = ZoneId.of( "Africa/Tunis" ) ;
ZonedDateTime zdt = localDate.atStartOfDay( z ) ;

如果您想以 UTC 格式查看同一时刻,请提取 Instant

Instant instant = zdt.toInstant() ;

关于java.time

java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.

Joda-Time project, now in maintenance mode, advises migration to the java.time 类.

要了解更多信息,请参阅 Oracle 教程。并在 Stack Overflow 中搜索许多示例和解释。规格为 JSR 310.

您可以直接与数据库交换 java.time 对象。使用 JDBC driver compliant with JDBC 4.2 或更高版本。不需要字符串,不需要 java.sql.* 类.

在哪里获取java.time类?

ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.