Что является лучшим решением для разделения на куски ежемесячно, учитывая две даты startTime и EndTime в Java? - PullRequest
1 голос
/ 27 февраля 2020

Мне нужно создать алгоритм для вычисления чанка ежемесячно с учетом двух дат.

Пример (формат даты: гггг-мм-дд чч: мм: сс) с учетом двух дат:

  • startTime:: 2020-01-10 13: 00: 25
  • endTime: 2020-03-19 15:00:30

Я должен делить на куски ежемесячно период выше. В двух словах, если я вычислю куски, то будет:

  • chunk_1 -> с: 2020-01-10 13:00:25 до: 2020-01-31 23: 59: 59
  • chunk_2 -> с: 2020-02-01 00:00:00 до: 2020-02-29 23: 59: 59
  • chunk_3 -> с: 2020-03-01 00 : 00: 00 до: 2020-03-19 15: 00: 30

мое первое решение следующее:

public static List<ExportDateSegment> datesBetweenWithCalendar(Date d1, Date d2) {

    List<ExportDateSegment> dateSegments = new ArrayList<ExportDateSegment>();
    Calendar c1 = Calendar.getInstance();
    c1.setTime(d1);

    int monthsDiff = mounthsDiffbetween(d1, d2);
    LOGGER.debug("months between two dates: {} ",monthsDiff);

    int i = 1;

    while (c1.getTimeInMillis() < d2.getTime()) {

        Calendar calendar;

        ExportDateSegment exportDateSegment = new ExportDateSegment();

        LOGGER.debug("last day of the month: " + c1.getActualMaximum(Calendar.DATE) + " last hour of the month: "
                + c1.getActualMaximum(Calendar.HOUR_OF_DAY) + " first day of the month: "
                + c1.getActualMinimum(Calendar.DAY_OF_MONTH) + " month: " + (c1.get(Calendar.MONTH) + 1));
        // the logic is to separate the three cases: the start period, intermediate period and the end period   

        if (i == 1) {

            calendar = new GregorianCalendar(c1.get(Calendar.YEAR), c1.get(Calendar.MONTH),
                    c1.getActualMaximum(Calendar.DATE), 23, 59, 59);

            exportDateSegment.setStartDate(c1.getTime());
            exportDateSegment.setEndDate(calendar.getTime());

        } else if (i == monthsDiff) {

            calendar = new GregorianCalendar(c1.get(Calendar.YEAR), c1.get(Calendar.MONTH),
                    c1.getActualMinimum(Calendar.DATE), 00, 00, 00);

            exportDateSegment.setStartDate(calendar.getTime());
            exportDateSegment.setEndDate(d2);

        } else {

            Calendar startCalendar = new GregorianCalendar(c1.get(Calendar.YEAR), c1.get(Calendar.MONTH),
                    c1.getActualMinimum(Calendar.DATE), 00, 00, 00);
            Calendar endCalendar = new GregorianCalendar(c1.get(Calendar.YEAR), c1.get(Calendar.MONTH),
                    c1.getActualMaximum(Calendar.DATE), 23, 59, 59);

            exportDateSegment.setStartDate(startCalendar.getTime());
            exportDateSegment.setEndDate(endCalendar.getTime());
        }

        c1.add(Calendar.MONTH, 1);
        dateSegments.add(exportDateSegment);

        i = i + 1;

    }

    return dateSegments;
}


public static int mounthsDiffbetween(Date d1, Date d2) {

    int monthsDiff = 0;

    Calendar c1 = Calendar.getInstance();
    Calendar c2 = Calendar.getInstance();

    c1.setTime(d1);
    c2.setTime(d2);

    monthsDiff = (c2.get(Calendar.MONTH) - c1.get(Calendar.MONTH)) + 1;

    return monthsDiff;
}

ExportDateSegment - это bean-компонент, содержащий startDate и endDate в качестве атрибутов, другими словами, это кусок.

Есть ли более разумное решение?

Ответы [ 3 ]

3 голосов
/ 27 февраля 2020

Вы должны использовать Java 8 Time API, например, вот так:

static List<TemporalRange<LocalDateTime>> chunkMonthly(LocalDateTime start, LocalDateTime end) {
    List<TemporalRange<LocalDateTime>> list = new ArrayList<>();
    for (LocalDateTime chunkEnd = end, chunkStart; ! chunkEnd.isBefore(start); chunkEnd = chunkStart.minusSeconds(1)) {
        chunkStart = chunkEnd.toLocalDate().withDayOfMonth(1).atStartOfDay();
        if (chunkStart.isBefore(start))
            chunkStart = start;
        list.add(new TemporalRange<>(chunkStart, chunkEnd));
    }
    Collections.reverse(list);
    return list;
}
class TemporalRange<T extends TemporalAccessor> {
    private final T start;
    private final T end;

    public TemporalRange(T start, T end) {
        this.start = start;
        this.end = end;
    }

    public T getStart() {
        return this.start;
    }

    public T getEnd() {
        return this.end;
    }

    @Override
    public String toString() {
        return this.start + " to " + this.end;
    }

    public String toString(DateTimeFormatter fmt) {
        return fmt.format(this.start) + " to " + fmt.format(this.end);
    }
}

Тест

DateTimeFormatter fmt = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss");
List<TemporalRange<LocalDateTime>> list = chunkMonthly(
        LocalDateTime.parse("2020-01-10 13:00:25", fmt),
        LocalDateTime.parse("2020-03-19 15:00:30", fmt));
list.forEach(r -> System.out.println(r.toString(fmt)));

Выход

2020-01-10 13:00:25 to 2020-01-31 23:59:59
2020-02-01 00:00:00 to 2020-02-29 23:59:59
2020-03-01 00:00:00 to 2020-03-19 15:00:30
2 голосов
/ 27 февраля 2020

Вы должны определенно использовать классы java.time, чтобы сделать это. Есть встроенные TemporalAdjusters, которые помогут вам найти первый и последний день месяца.

public static List<ExportDateSegment> splitIntoMonths(LocalDateTime start, LocalDateTime end) {
    LocalDate segmentEndDate =
            start.with(TemporalAdjusters.lastDayOfMonth()).toLocalDate();
    LocalTime segmentEndTime = LocalTime.of(23, 59, 59);
    LocalDate lastSegmentStartDate = end.with(TemporalAdjusters.firstDayOfMonth()).toLocalDate();
    LocalTime segmentStartTime = LocalTime.of(0, 0, 0);

    if (lastSegmentStartDate.isBefore(segmentEndDate)) { // start & end are in the same month
        return Collections.singletonList(new ExportDateSegment(start, end));
    }

    ArrayList<ExportDateSegment> list = new ArrayList<>();

    // adds the first segment, which is not a whole month
    list.add(new ExportDateSegment(start, LocalDateTime.of(segmentEndDate, segmentEndTime)));

    // just like a typical for loop, but with LocalDate
    for (LocalDate segmentStartDate = segmentEndDate.plusDays(1) ; segmentStartDate.isBefore(lastSegmentStartDate) ; segmentStartDate = segmentStartDate.plusMonths(1)) {
        list.add(new ExportDateSegment(
                LocalDateTime.of(segmentStartDate, segmentStartTime),
                LocalDateTime.of(segmentStartDate.with(TemporalAdjusters.lastDayOfMonth()), segmentEndTime)
        ));
    }

    // adds the last segment, which is also not a whole month
    list.add(new ExportDateSegment(LocalDateTime.of(lastSegmentStartDate, segmentStartTime), end));
    return list;
}
1 голос
/ 27 февраля 2020

Вот еще один, использующий потоки:

public class SplitDateRange {

public static class Range {
    private final LocalDateTime start;
    private final LocalDateTime end;

    public Range(LocalDateTime start, LocalDateTime end) {
        this.start = start;
        this.end = end;
    }

    @Override
    public String toString() {
        return "Range{" + "start=" + start + ", end=" + end + '}';
    }
}

public static void main(String[] args) {
    LocalDateTime start = LocalDateTime.parse("2020-01-10T13:00:25", DateTimeFormatter.ISO_LOCAL_DATE_TIME);
    LocalDateTime end = LocalDateTime.parse("2020-03-19T15:00:30", DateTimeFormatter.ISO_LOCAL_DATE_TIME);

    Stream.iterate(start, date -> date.isBefore(end), SplitDateRange::firstDateTimeOfNextMonth)
            .map(date -> new Range(date, min(end, firstDateTimeOfNextMonth(date).minusSeconds(1))))
            .collect(Collectors.toList())
            .forEach(System.out::println);

}

public static LocalDateTime firstDateTimeOfNextMonth(LocalDateTime current) {
    return current.plusMonths(1).with(TemporalAdjusters.firstDayOfMonth()).with(LocalTime.MIN);
}

public static LocalDateTime min(LocalDateTime a, LocalDateTime b) {
    return a.isBefore(b) ? a : b;
}

}

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...