Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Getting the first and last time in milliseconds with Java 8 Time API

I am converting my time calculations from self implemented code to Java 8 Time API.

I need to have the start and end time in milliseconds from a java.time.Year or java.time.Month class, which I plan to use later in another layer for JFreeChart.

I need functions like getFirstMillisecond() & getLastMilliSecond() from org.jfree.data.time.RegularTimePeriod class of JFreeChart.

I have already implemented code something like-

public static long getStartTimeInMillis(java.time.Year year, java.time.Month month) {       
    if (year != null && month != null) {
        return LocalDate.of(year.getValue(), month, 1).with(TemporalAdjusters.firstDayOfMonth()).
                atStartOfDay().atZone(TimeZone.getDefault().toZoneId()).toInstant().toEpochMilli();
    } else if (year != null) {
        return LocalDate.of(year.getValue(), java.time.Month.JANUARY, 1).with(TemporalAdjusters.firstDayOfMonth()).
                atStartOfDay().atZone(TimeZone.getDefault().toZoneId()).toInstant().toEpochMilli();
    }       
    return 0;
}

public static long getEndTimeInMillis(java.time.Year year, java.time.Month month) {
    if (year != null && month != null) {
        return LocalDate.of(year.getValue(), month, 1).with(TemporalAdjusters.lastDayOfMonth()).
                atTime(OffsetTime.MAX).toLocalDateTime().atZone(TimeZone.getDefault().toZoneId()).toInstant().toEpochMilli();
    } else if (year != null) {
        return  LocalDate.of(year.getValue(), java.time.Month.DECEMBER, 1).with(TemporalAdjusters.lastDayOfMonth()).
                atTime(OffsetTime.MAX).toLocalDateTime().atZone(TimeZone.getDefault().toZoneId()).toInstant().toEpochMilli();
    }       
    return 0;
}

But it looks really complicated to me. Is there any better/shorter way to get these values?

like image 466
Endery Avatar asked Dec 23 '15 09:12

Endery


2 Answers

YearMonth

Yes, there is a slightly better way. Use the YearMonth class included with java.time.

Also, break up that call-chain into separate statements to make it more readable and easier to trace/debug. Trust the JVM to optimize on your behalf; only use call-chaining where it makes your code more readable and easier to understand.

Going through TimeZone to get the JVM’s current default time zone is unnecessary. Instead, call ZoneId.systemDefault().

Set up some input values.

// Inputs
Year year = Year.of ( 2015 );
Month month = Month.DECEMBER;

The core part of your method.

// Code for your method.
YearMonth yearMonth = year.atMonth ( month ); // Instantiate a YearMonth from a Year and a Month.
LocalDate localDate = yearMonth.atDay ( 1 ); // First day of month.
ZoneId zoneId = ZoneId.systemDefault (); // Or… ZoneId.of("America/Montreal");
ZonedDateTime zdt = localDate.atStartOfDay ( zoneId );
long millis = zdt.toInstant ().toEpochMilli ();

Dump to console.

System.out.println ( "year: " + year + " | month: " + month + " | yearMonth: " + yearMonth + " | zoneId:" + zoneId + " | zdt: " + zdt + " | millis: " + millis );

year: 2015 | month: DECEMBER | yearMonth: 2015-12 | zoneId:America/Los_Angeles | zdt: 2015-12-01T00:00-08:00[America/Los_Angeles] | millis: 1448956800000

Even better, pass the YearMonth instance to your method rather than the pair of Year and Month objects. If your other business logic is using the Year + Month pair, use YearMonth instead – that’s what it’s for.

like image 135
Basil Bourque Avatar answered Sep 19 '22 02:09

Basil Bourque


Aside from the questionable practices of returning 0 when given a null year and trusting system default timezone, you can rewrite your methods as follows:

public static long getStartTimeInMillis(java.time.Year year, java.time.Month month) {
    if (year == null) {
        return 0;
    }

    if (month == null) {
        month = Month.JANUARY;
    }

    return year.atMonth(month)
            .atDay(1)
            .atStartOfDay()
            .atZone(ZoneId.systemDefault())
            .toInstant()
            .toEpochMilli();

}

public static long getEndTimeInMillis(java.time.Year year, java.time.Month month) {
    if (year == null) {
        return 0;
    }

    if (month == null) {
        month = Month.JANUARY;
    }

    return year.atMonth(month)
            .atEndOfMonth()
            .atTime(LocalTime.MAX)
            .atZone(ZoneId.systemDefault())
            .toInstant()
            .toEpochMilli();
};

To expand, it is likely better to just throw a NullPointerException if year is null. If you are passed a null year, there's probably a bug in upstream code. Returning a meaningless 0 only pushes the bug further down the road and makes it harder to track down. This principle is called "fail fast".

Relying on system default timezone is a bad idea for serious production code because it tends to cause problems as servers might be configured unpredictably (e.g. GMT) or you can have issues when geographically distributed servers are in different timezones. It prevents headaches to give a careful consideration to the question of "what timezone am I computing all these times against?"

like image 37
Misha Avatar answered Sep 20 '22 02:09

Misha