/** * Copyright (C) 2015 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.strata.basics.date; import java.time.LocalDate; import java.util.Iterator; import java.util.Spliterator; import java.util.Spliterators; import java.util.stream.Stream; import java.util.stream.StreamSupport; /** * Utilities for working with {@code LocalDate}. */ final class LocalDateUtils { // First day-of-month minus one for a standard year // array length 13 with element zero ignored, so month 1 to 12 can be queried directly private static final int[] STANDARD = {0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}; // First day-of-month minus one for a leap year // array length 13 with element zero ignored, so month 1 to 12 can be queried directly private static final int[] LEAP = {0, 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335}; /** * Restricted constructor. */ private LocalDateUtils() { } //------------------------------------------------------------------------- /** * Finds the day-of-year of the date. * <p> * Faster than the JDK method. * * @param date the date to query * @return the day-of-year */ static int doy(LocalDate date) { int[] lookup = (date.isLeapYear() ? LEAP : STANDARD); return lookup[date.getMonthValue()] + date.getDayOfMonth(); } /** * Adds a number of days to the date. * <p> * Faster than the JDK method. * * @param date the date to add to * @param daysToAdd the days to add * @return the new date */ static LocalDate plusDays(LocalDate date, int daysToAdd) { if (daysToAdd == 0) { return date; } // add the days to the current day-of-month // if it is guaranteed to be in this month or the next month then fast path it // (59th Jan is 28th Feb, 59th Feb is 31st Mar) long dom = date.getDayOfMonth() + daysToAdd; if (dom > 0 && dom <= 59) { int monthLen = date.lengthOfMonth(); int month = date.getMonthValue(); int year = date.getYear(); if (dom <= monthLen) { return LocalDate.of(year, month, (int) dom); } else if (month < 12) { return LocalDate.of(year, month + 1, (int) (dom - monthLen)); } else { return LocalDate.of(year + 1, 1, (int) (dom - monthLen)); } } long mjDay = Math.addExact(date.toEpochDay(), daysToAdd); return LocalDate.ofEpochDay(mjDay); } /** * Returns the number of days between two dates. * <p> * Faster than the JDK method. * * @param firstDate the first date * @param secondDate the second date, after the first * @return the new date */ static long daysBetween(LocalDate firstDate, LocalDate secondDate) { int firstYear = firstDate.getYear(); int secondYear = secondDate.getYear(); if (firstYear == secondYear) { return doy(secondDate) - doy(firstDate); } if ((firstYear + 1) == secondYear) { return (firstDate.lengthOfYear() - doy(firstDate)) + doy(secondDate); } return secondDate.toEpochDay() - firstDate.toEpochDay(); } //------------------------------------------------------------------------- /** * Streams the set of dates included in the range. * <p> * This returns a stream consisting of each date in the range. * The stream is ordered. * * @param startInclusive the start date * @param endExclusive the end date * @return the stream of dates from the start to the end */ static Stream<LocalDate> stream(LocalDate startInclusive, LocalDate endExclusive) { Iterator<LocalDate> it = new Iterator<LocalDate>() { private LocalDate current = startInclusive; @Override public LocalDate next() { LocalDate result = current; current = plusDays(current, 1); return result; } @Override public boolean hasNext() { return current.isBefore(endExclusive); } }; long count = endExclusive.toEpochDay() - startInclusive.toEpochDay() + 1; Spliterator<LocalDate> spliterator = Spliterators.spliterator(it, count, Spliterator.IMMUTABLE | Spliterator.NONNULL | Spliterator.DISTINCT | Spliterator.ORDERED | Spliterator.SORTED | Spliterator.SIZED | Spliterator.SUBSIZED); return StreamSupport.stream(spliterator, false); } }