简体   繁体   中英

Java Calendar is corrupted by setting HOUR_OF_DAY

QUESTION: Would you consider this a bug in java.util.Calendar ?

A Calendar object in the local (PDT) time zone is instantiated and assigned the zero (epoch start) date. This value is maintained, as expected after setting to zero the calendar's millisecond, second, and minute. However, once the hour of day is set to zero, the time becomes non-zero. It acquires the value -57600000ms = -16 hours. This may be a timezone bug, but the value -16 hours does not correspond to the local -7 hours (PDT) offset, in effect at the time of execution. Without daylight savings, the time offset is PST (-8 hours) and also does not correspond to -16 hours.

If setting the hour should affect time zone offsets, you would expect that setting the minute should as well because: ( from https://en.wikipedia.org/wiki/Time_zone ) Several places "use half-hour deviations from standard time, and some" ... "use quarter-hour deviations."

The code:

Date epochStart = new Date(0L);
System.out.println("epochStart=" + epochStart.getTime());

Calendar calendar = Calendar.getInstance();
calendar.setTime(epochStart);
System.out.println("epochStart in calendar=" + calendar.getTime().getTime());

calendar.set(Calendar.MILLISECOND, 0);
System.out.println("ms cleared in calendar=" + calendar.getTime().getTime());

calendar.set(Calendar.SECOND, 0);
System.out.println("second cleared in calendar=" + calendar.getTime().getTime());

calendar.set(Calendar.MINUTE, 0);
System.out.println("minute cleared in calendar=" + calendar.getTime().getTime());

calendar.set(Calendar.HOUR_OF_DAY, 0);
System.out.println("hourOfDay cleared in calendar=" + calendar.getTime().getTime());

The output:

epochStart=0
epochStart in calendar=0
ms cleared in calendar=0
second cleared in calendar=0
minute cleared in calendar=0
hourOfDay cleared in calendar=-57600000

I have found descriptions of other bugs in java.util.Calendar, but I don't think this is known.

-- Thanks for your time.

Java is fine and working as expected.

A java.util.Date is a moment in time, internally represented as milliseconds since the start of the epoch (January 1, 1970 at 00:00:00 UTC).

java.util.Calender is for manipulating time information in a timezone dependent format. Normally, the timezone is implicitly defined through the current locale. Calling calendar.setTime(xxx) takes the supplied moment in time and converts it into the calendar fields (year, month, day, hour, minute, ...) according to its timezone.

In your case, the start of the epoch is split into Dec 31, 1969, 16:00:00.000 PDT . Clearing the milliseconds, seconds and minutes fields has no effect, since these fields are already zero.

However clearing the hours field changes the timestamp to In your case, the start of the epoch is split into Dec 31, 1969, 00:00:00.000 PDT .

The following code shows your manipulations, together with converting the calendars timestamp into UTC and PDT date strings.

import java.text.SimpleDateFormat;
import java.util.*;

public class CalendarTest {

    public static void main(String[] args) {
        TimeZone timeZone = TimeZone.getTimeZone("America/Los_Angeles");
        SimpleDateFormat udf = new SimpleDateFormat("'UTC: 'yyyy-MM-dd HH:mm:ss.SSS Z");
        udf.setTimeZone(TimeZone.getTimeZone("UTC"));
        SimpleDateFormat df = new SimpleDateFormat("'PDT: 'yyyy-MM-dd HH:mm:ss.SSS Z");
        df.setTimeZone(timeZone);

        Date epochStart = new Date(0L);
        System.out.println("epochStart=" + epochStart.getTime());
        System.out.println(udf.format(epochStart));
        System.out.println(df.format(epochStart));
        System.out.println();

        Calendar calendar = Calendar.getInstance(timeZone);
        calendar.setTime(epochStart);
        System.out.println("epochStart in calendar=" + calendar.getTime().getTime());
        System.out.println(udf.format(calendar.getTime()));
        System.out.println(df.format(calendar.getTime()));
        System.out.println();

        calendar.set(Calendar.MILLISECOND, 0);
        System.out.println("ms cleared in calendar=" + calendar.getTime().getTime());
        System.out.println(udf.format(calendar.getTime()));
        System.out.println(df.format(calendar.getTime()));
        System.out.println();

        calendar.set(Calendar.SECOND, 0);
        System.out.println("second cleared in calendar=" + calendar.getTime().getTime());
        System.out.println(udf.format(calendar.getTime()));
        System.out.println(df.format(calendar.getTime()));
        System.out.println();

        calendar.set(Calendar.MINUTE, 0);
        System.out.println("minute cleared in calendar=" + calendar.getTime().getTime());
        System.out.println(udf.format(calendar.getTime()));
        System.out.println(df.format(calendar.getTime()));
        System.out.println();

        calendar.set(Calendar.HOUR_OF_DAY, 0);
        System.out.println("hourOfDay cleared in calendar=" + calendar.getTime().getTime());
        System.out.println(udf.format(calendar.getTime()));
        System.out.println(df.format(calendar.getTime()));
        System.out.println();
    }
}

tl;dr

LocalDate.now( ZoneId.of( "America/Montreal" ) )
         .atStartOfDay( ZoneId.of( "America/Montreal" ) )

java.time

You are using troublesome old legacy date-time classes now supplanted by the java.time classes.

Seems that you are trying to get the first moment of today. Do not assume and hard-code the time as 00:00:00 . In some time zones, the day may start at a different time because of anomalies such as Daylight Saving Time (DST). Let java.time determine the first moment.

Get the current date and time. This requires a time zone. For any given moment both the date and the time-of-day vary by zone around the globe.

ZoneId z = ZoneId.of( "America/Montreal" );
ZonedDateTime zdt = ZonedDateTime.now( z );

Extract just the date, without time-of-day and without time zone.

LocalDate ld = zdt.toLocalDate();

Ask for the first moment of that day, agsin even specifying a time zone.

ZonedDateTime zdtStart = ld.atStartOfDay( z );

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM