4

I am new to the Java 8 time package, and am trying to better understand it and make sure that I am making good use of it.

Is there a specific reason that LocalDateTime's truncatedTo(TemporalUnit) does not support ChronoUnit values past Days.

I think I put together a successful implementation of such a method, but had concerns with integer division and floating point arithmetic anomalies. Is this something I should be concerned with?

Also, I understand them making LocalDateTime and LocalDate both immutable Objects meaning that LocalDateTime cannot extend LocalDate, but I don't see them sharing a common interface for all the date components of LocalDateTime, making it hard to understand why they did this. The only common interfaces I can see is Temporal, TemporalAccessor, and TemporalAdjuster.

If they shared a common interface for the date and time components, then you could easily write code to the interface that can use both LocalDate and LocalDateTime instances for things that modify the date without caring about the time part of the instance during runtime. Is there a good reason for them not doing this, or am I missing something here?

Here is my implementation of the aforementioned way to truncate LocalDateTime beyond DAYS:

public static LocalDateTime truncateDate(LocalDateTime date, ChronoUnit unit) {

    LocalDateTime truncatedDate = null;

    // LocalDateTime only supports truncatedTo(TemporalUnit) up to ChronoUnit.DAYS.
    switch (unit) {
        case NANOS:
        case MICROS:
        case MILLIS:
        case SECONDS:
        case MINUTES:
        case HOURS:
        case HALF_DAYS:
        case DAYS:
            truncatedDate = date.truncatedTo(unit);
            System.out.println("date = '" + String.valueOf(date) + "', unit = '" + String.valueOf(unit) + "', truncatedDate = '" + String.valueOf(truncatedDate) + "'.");
            return truncatedDate; // break;
        default: // else; we can't use LocalDateTime.truncatedTo(TemporalUnit) past ChronoUnit.DAYS, so lets truncate up to DAYS and continue from there.
            truncatedDate = date.truncatedTo(ChronoUnit.DAYS);
            break;
    }

    int year = 0;

    switch (unit) {
        case WEEKS:
            truncatedDate = truncatedDate.plus(DayOfWeek.MONDAY.getValue()-truncatedDate.getDayOfWeek().getValue(), ChronoUnit.DAYS); // subtract days to the last Monday.
            System.out.println("date = '" + String.valueOf(date) + "', unit = '" + String.valueOf(unit) + "', truncatedDate = '" + String.valueOf(truncatedDate) + "'.");
            return truncatedDate; // break;
        case MONTHS:
            truncatedDate = truncatedDate.with(TemporalAdjusters.firstDayOfMonth());
            System.out.println("date = '" + String.valueOf(date) + "', unit = '" + String.valueOf(unit) + "', truncatedDate = '" + String.valueOf(truncatedDate) + "'.");
            return truncatedDate; // break;
        case YEARS:
            truncatedDate = truncatedDate.with(TemporalAdjusters.firstDayOfYear());
            System.out.println("date = '" + String.valueOf(date) + "', unit = '" + String.valueOf(unit) + "', truncatedDate = '" + String.valueOf(truncatedDate) + "'.");
            return truncatedDate; // break;
        case DECADES:
            truncatedDate = truncatedDate.with(TemporalAdjusters.firstDayOfYear());
            year = truncatedDate.getYear();
            int decadeYear = (year/10)*10; // int division rounds down, same as trunc(year/10)*10.
            truncatedDate = truncatedDate.plus(decadeYear-year, ChronoUnit.YEARS);
            System.out.println("date = '" + String.valueOf(date) + "', unit = '" + String.valueOf(unit) + "', truncatedDate = '" + String.valueOf(truncatedDate) + "'.");
            return truncatedDate; // break;
        case CENTURIES:
            truncatedDate = truncatedDate.with(TemporalAdjusters.firstDayOfYear());
            year = truncatedDate.getYear();
            int centuryYear = (year/100)*100; // int division rounds down, same as trunc(year/100)*100.
            truncatedDate = truncatedDate.plus(centuryYear-year, ChronoUnit.YEARS);
            System.out.println("date = '" + String.valueOf(date) + "', unit = '" + String.valueOf(unit) + "', truncatedDate = '" + String.valueOf(truncatedDate) + "'.");
            return truncatedDate; // break;
        case MILLENNIA:
            truncatedDate = truncatedDate.with(TemporalAdjusters.firstDayOfYear());
            year = truncatedDate.getYear();
            int millenniumYear = (year/1000)*1000; // int division rounds down, same as trunc(year/1000)*1000.
            truncatedDate = truncatedDate.plus(millenniumYear-year, ChronoUnit.YEARS);
            System.out.println("date = '" + String.valueOf(date) + "', unit = '" + String.valueOf(unit) + "', truncatedDate = '" + String.valueOf(truncatedDate) + "'.");
            return truncatedDate; // break;
        default: // ChronoUnit.ERA || ChronoUnit.FOREVER:
            throw new UnsupportedTemporalTypeException("Unable to truncate to unit = '" + String.valueOf(unit) + "', not well supported!");
    } // all switch paths return or throw an exception.

}

With the following output from a main test program:

$ java Main
date = '2016-08-26T10:51:58.828'.
Truncating date = '2016-08-26T10:51:58.828' by unit = 'Nanos'.
date = '2016-08-26T10:51:58.828', unit = 'Nanos', truncatedDate = '2016-08-26T10:51:58.828'.
  => result = '2016-08-26T10:51:58.828'.
Truncating date = '2016-08-26T10:51:58.828' by unit = 'Micros'.
date = '2016-08-26T10:51:58.828', unit = 'Micros', truncatedDate = '2016-08-26T10:51:58.828'.
  => result = '2016-08-26T10:51:58.828'.
Truncating date = '2016-08-26T10:51:58.828' by unit = 'Millis'.
date = '2016-08-26T10:51:58.828', unit = 'Millis', truncatedDate = '2016-08-26T10:51:58.828'.
  => result = '2016-08-26T10:51:58.828'.
Truncating date = '2016-08-26T10:51:58.828' by unit = 'Seconds'.
date = '2016-08-26T10:51:58.828', unit = 'Seconds', truncatedDate = '2016-08-26T10:51:58'.
  => result = '2016-08-26T10:51:58'.
Truncating date = '2016-08-26T10:51:58.828' by unit = 'Minutes'.
date = '2016-08-26T10:51:58.828', unit = 'Minutes', truncatedDate = '2016-08-26T10:51'.
  => result = '2016-08-26T10:51'.
Truncating date = '2016-08-26T10:51:58.828' by unit = 'Hours'.
date = '2016-08-26T10:51:58.828', unit = 'Hours', truncatedDate = '2016-08-26T10:00'.
  => result = '2016-08-26T10:00'.
Truncating date = '2016-08-26T10:51:58.828' by unit = 'HalfDays'.
date = '2016-08-26T10:51:58.828', unit = 'HalfDays', truncatedDate = '2016-08-26T00:00'.
  => result = '2016-08-26T00:00'.
Truncating date = '2016-08-26T10:51:58.828' by unit = 'Days'.
date = '2016-08-26T10:51:58.828', unit = 'Days', truncatedDate = '2016-08-26T00:00'.
  => result = '2016-08-26T00:00'.
Truncating date = '2016-08-26T10:51:58.828' by unit = 'Weeks'.
date = '2016-08-26T10:51:58.828', unit = 'Weeks', truncatedDate = '2016-08-22T00:00'.
  => result = '2016-08-22T00:00'.
Truncating date = '2016-08-26T10:51:58.828' by unit = 'Months'.
date = '2016-08-26T10:51:58.828', unit = 'Months', truncatedDate = '2016-08-01T00:00'.
  => result = '2016-08-01T00:00'.
Truncating date = '2016-08-26T10:51:58.828' by unit = 'Years'.
date = '2016-08-26T10:51:58.828', unit = 'Years', truncatedDate = '2016-01-01T00:00'.
  => result = '2016-01-01T00:00'.
Truncating date = '2016-08-26T10:51:58.828' by unit = 'Decades'.
date = '2016-08-26T10:51:58.828', unit = 'Decades', truncatedDate = '2010-01-01T00:00'.
  => result = '2010-01-01T00:00'.
Truncating date = '2016-08-26T10:51:58.828' by unit = 'Centuries'.
date = '2016-08-26T10:51:58.828', unit = 'Centuries', truncatedDate = '2000-01-01T00:00'.
  => result = '2000-01-01T00:00'.
Truncating date = '2016-08-26T10:51:58.828' by unit = 'Millennia'.
date = '2016-08-26T10:51:58.828', unit = 'Millennia', truncatedDate = '2000-01-01T00:00'.
  => result = '2000-01-01T00:00'.
Truncating date = '2016-08-26T10:51:58.828' by unit = 'Eras'.
  => exception = 'UnsupportedTemporalTypeException' => {Unable to truncate to unit = 'Eras', not well supported!}.
Truncating date = '2016-08-26T10:51:58.828' by unit = 'Forever'.
  => exception = 'UnsupportedTemporalTypeException' => {Unable to truncate to unit = 'Forever', not well supported!}.

It appears to all be working as expected, I was just wondering the implementation isn't recommended and why the default library doesn't support it if it is?

Thanks!

  • 1
    The universal answer to all questions in the form of "why didn't the designers of a particular library or language add this feature" is "because they decided that the cost of doing so exceeded the benefits." In this particular case, you've already demonstrated that users can easily write their own code to add this functionality if it suits them. – Robert Harvey Aug 26 '16 at 15:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.