Home > database >  Calendar`s getActualMinimum returns wrong value
Calendar`s getActualMinimum returns wrong value

Time:01-20

I need to compare a certain date to the current date/time to make sure that it comes before the first day/hour/min/sec of the current month. In order to implement this feature, a configure a Calendar instance using its getActualMinimum method, however, today (Thursday, 19/01/2023 - 10:40:18 BRT 2023), it presented a behavior that I have never faced before. Consider the following code:

    Calendar cal = Calendar.getInstance();
    System.out.println("After instantiation:                  "   cal.getTime());
    
    cal.set(Calendar.DAY_OF_MONTH, cal.getActualMinimum(Calendar.DAY_OF_MONTH));
    System.out.println("After configuring the Day of Month:   "   cal.getTime());
    
    cal.set(Calendar.HOUR_OF_DAY, cal.getActualMinimum(Calendar.HOUR_OF_DAY));
    System.out.println("After configuring the Hour of day:    "   cal.getTime());
    
    cal.set(Calendar.MINUTE, cal.getActualMinimum(Calendar.MINUTE));
    System.out.println("After configuring the Minutes:        "   cal.getTime());
    
    cal.set(Calendar.SECOND, cal.getActualMinimum(Calendar.SECOND));
    System.out.println("After configuring the Seconds:        "   cal.getTime());
    
    cal.set(Calendar.MILLISECOND, cal.getActualMinimum(Calendar.MILLISECOND));
    System.out.println("After configuring the Millis:         "   cal.getTime());

The code above, in the moment that this post is being created, would print to the console:

After instantiation:                  Thu Jan 19 10:40:18 BRT 2023
After configuring the Day of Month:   Sun Jan 01 10:40:18 BRT 2023
After configuring the Hour of day:    Sat Dec 31 23:40:18 BRT 2022
After configuring the Minutes:        Sat Dec 31 23:00:18 BRT 2022
After configuring the Seconds:        Sat Dec 31 23:00:00 BRT 2022
After configuring the Millis:         Sat Dec 31 23:00:00 BRT 2022

Could someone explain why, after configuring the Hour of day, the value was set to 23 and not 00?

CodePudding user response:

Probably this is caused by the default TimeZone of your PC , try adding this after you declare the Calnedar object

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

CodePudding user response:

java.time

The java.util Date-Time API and their formatting API, SimpleDateFormat are outdated and error-prone. It is recommended to stop using them completely and switch to the modern Date-Time API.

With java.time, the modern date-time API, you have specialized types for different purposes. A very common type is ZonedDateTime which contains the information of the timezone along with the date and time information.

Note: Unlike java.util date-time types, java.time types are immutable i.e. you always get a new instance on setting a new value; therefore, like a String, you need to assign the new value to the reference if you want the reference to point to the new value.

Demo:

import java.time.LocalDate;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoField;
import java.time.temporal.TemporalAdjusters;
import java.util.Locale;

class Main {
    public static void main(String[] args) {
        // Replace ZoneId.systemDefault() with the applicable ZoneId e.g.
        // ZoneId.of("America/New_York")
        ZonedDateTime zdt = ZonedDateTime.now(ZoneId.systemDefault());
        System.out.println(zdt);

        zdt = zdt.with(TemporalAdjusters.firstDayOfMonth());
        System.out.println(zdt);

        zdt = zdt.withHour(LocalTime.MIN.getHour());
        System.out.println(zdt);

        zdt = zdt.withMinute(LocalTime.MIN.getMinute());
        System.out.println(zdt);

        zdt = zdt.withSecond(LocalTime.MIN.getSecond());
        System.out.println(zdt);

        zdt = zdt.with(ChronoField.MILLI_OF_SECOND, LocalTime.MIN.getLong(ChronoField.MILLI_OF_SECOND));
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.SSSXXX", Locale.ENGLISH);
        System.out.println(zdt.format(formatter));

        // In a single statement
        String output = ZonedDateTime.now(ZoneId.systemDefault())
                .with(TemporalAdjusters.firstDayOfMonth())
                .withHour(LocalTime.MIN.getHour())
                .withMinute(LocalTime.MIN.getMinute())
                .withSecond(LocalTime.MIN.getSecond())
                .with(ChronoField.MILLI_OF_SECOND, LocalTime.MIN.getLong(ChronoField.MILLI_OF_SECOND))
                .format(formatter);
        System.out.println(output);

        // There is a better way if all you want is day-1 with minimum time
        zdt = LocalDate.now(ZoneId.systemDefault())
                .with(TemporalAdjusters.firstDayOfMonth())
                .atStartOfDay()
                .atZone(ZoneId.systemDefault());
        System.out.println(zdt.format(formatter));
    }
}

Output from a sample run:

2023-01-19T16:50:43.811714Z[GMT]
2023-01-01T16:50:43.811714Z[GMT]
2023-01-01T00:50:43.811714Z[GMT]
2023-01-01T00:00:43.811714Z[GMT]
2023-01-01T00:00:00.811714Z[GMT]
2023-01-01T00:00:00.000Z
2023-01-01T00:00:00.000Z
2023-01-01T00:00:00.000Z

ONLINE DEMO

Learn more about the modern Date-Time API from Trail: Date Time.

In case you need a solution using the legacy API:

Calendar#getTime returns an instance of java.util.Date which is not a real date-time object; rather, it just contains the number of milliseconds from January 1, 1970, 00:00:00 GMT. The Date#toString applies the system's timezone to calculate the date-time and returns the same.

The way to get the date-time string with the desired timezone is by applying the timezone to the SimpleDateFormat and using it to format the instance of java.util.Date.

Demo:

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

class Main {
    public static void main(String[] args) {
        SimpleDateFormat sdf = new SimpleDateFormat("EEE MMM dd HH:mm:ss.SSS z yyyy", Locale.ENGLISH);
        sdf.setTimeZone(TimeZone.getTimeZone("UTC"));

        Calendar cal = Calendar.getInstance();
        System.out.println("After instantiation:                  "   sdf.format(cal.getTime()));

        cal.set(Calendar.DAY_OF_MONTH, cal.getActualMinimum(Calendar.DAY_OF_MONTH));
        System.out.println("After configuring the Day of Month:   "   sdf.format(cal.getTime()));

        cal.set(Calendar.HOUR_OF_DAY, cal.getActualMinimum(Calendar.HOUR_OF_DAY));
        System.out.println("After configuring the Hour of day:    "   sdf.format(cal.getTime()));

        cal.set(Calendar.MINUTE, cal.getActualMinimum(Calendar.MINUTE));
        System.out.println("After configuring the Minutes:        "   sdf.format(cal.getTime()));

        cal.set(Calendar.SECOND, cal.getActualMinimum(Calendar.SECOND));
        System.out.println("After configuring the Seconds:        "   sdf.format(cal.getTime()));

        cal.set(Calendar.MILLISECOND, cal.getActualMinimum(Calendar.MILLISECOND));
        System.out.println("After configuring the Millis:         "   sdf.format(cal.getTime()));
    }
}

Output from a sample run:

After instantiation:                  Thu Jan 19 15:29:38.381 UTC 2023
After configuring the Day of Month:   Sun Jan 01 15:29:38.381 UTC 2023
After configuring the Hour of day:    Sun Jan 01 00:29:38.381 UTC 2023
After configuring the Minutes:        Sun Jan 01 00:00:38.381 UTC 2023
After configuring the Seconds:        Sun Jan 01 00:00:00.381 UTC 2023
After configuring the Millis:         Sun Jan 01 00:00:00.000 UTC 2023

ONLINE DEMO

  • Related