【问题标题】:Java - create group of dates by Month from 2 datesJava - 从 2 个日期按月创建日期组
【发布时间】:2018-10-25 08:40:42
【问题描述】:

使用 Java 8

目标

从两个日期(例如:firstDay 2018-09-01 和 lastDay 2018-11-10),我想创建两个按月创建的 firstDay 和 lastDay 数组。例如:

List<LocalDate> firstDays = [2018-09-01,2018-10-01,2018-11-01]
List<LocalDate> lastDays = [2018-09-30, 2018-10-31,2018-11-10]

最终,我希望这种方法也适用多年(例如:firstDay 2018-12-10 和 lastDay 2019-01-06)。

问题

我现在不知道用什么来实现这个目标。我还在寻找。请问你能帮帮我吗?

【问题讨论】:

  • 你有没有尝试过?这基本上只是一个问题陈述。
  • 最后几天你是指2018-11-10还是2018-11-30
  • 最后几天我的意思是 2018-11-10

标签: java date java-time localdate


【解决方案1】:

你可以像这样使用plusMonth

LocalDate firstDay = LocalDate.parse("2018-09-01");
LocalDate lastDay = LocalDate.parse("2018-11-10");

Long timeBetween = ChronoUnit.MONTHS.between(firstDay, lastDay);

// First day of firstDays is not changeable so add it like it is
List<LocalDate> firstDays = new ArrayList<>(Arrays.asList(firstDay));

// Generate the dates between the start and end date
firstDays.addAll(LongStream.rangeClosed(1, timeBetween)
        .mapToObj(f -> firstDay.withDayOfMonth(1).plusMonths(f))
        .collect(Collectors.toList()));

// For the lastDays, generate the dates
List<LocalDate> lastDays = LongStream.range(0, timeBetween)
        .mapToObj(f -> {
            LocalDate newDate = firstDay.plusMonths(f);
            return newDate.withDayOfMonth(newDate.lengthOfMonth());
        }).collect(Collectors.toList());
// Last day of lastDays is not changeable so add it like it is
lastDays.add(lastDay);

输出

[2018-09-01, 2018-10-01, 2018-11-01]
[2018-09-30, 2018-10-31, 2018-11-10]

【讨论】:

  • 很好地使用流,但我认为lastDays 对最后一个元素是错误的:它应该是2018-11-10 而不是2018-11-30
  • 谢谢@AlexM 哎呀我没有看到这个,我会修复它,但我认为这是一个错字?
  • 我认为用户希望看到firstDay 而不是第一个月的第一天,以及lastDay 而不是上个月的最后一天。问题中没有说清楚,但有点道理。
  • 是的,Alex M 和 YCF_L。我正在尝试两个版本。你的和下面的迭代。我首先测试了迭代的,因为第一个 firstDay 和最后一个 lastDay 对应于实际输入的两个日期。非常感谢你的帮助。为了我的目的,我需要在上面做一些代码。我告诉你它是否有效。
  • YCF_L 感谢它的工作原理。我使用 AlexM 的一个,因为我更了解它。但两者都在工作。感谢您和@AlexM。
【解决方案2】:

以迭代的方式处理边缘情况:

LocalDate startDate = LocalDate.of(2018, 9, 1);
LocalDate endDate = LocalDate.of(2018, 11, 10);

List<LocalDate> firstDays = new ArrayList<>();
List<LocalDate> lastDays = new ArrayList<>();

LocalDate firstOfMonth = startDate.withDayOfMonth(1);
LocalDate lastOfMonth = startDate.withDayOfMonth(startDate.lengthOfMonth());

while (firstOfMonth.isBefore(endDate)) {
    firstDays.add(firstOfMonth.isBefore(startDate) ? startDate : firstOfMonth);
    lastDays.add(endDate.isBefore(lastOfMonth) ? endDate : lastOfMonth);

    firstOfMonth = firstOfMonth.plus(1, ChronoUnit.MONTHS);
    lastOfMonth = firstOfMonth.withDayOfMonth(firstOfMonth.lengthOfMonth());
}

System.out.println(firstDays);
System.out.println(lastDays);

输出:

[2018-09-01, 2018-10-01, 2018-11-01]
[2018-09-30, 2018-10-31, 2018-11-10]

【讨论】:

    【解决方案3】:

    已经有几个很好的答案了。请允许我看看我是否还能设计出一些优雅的东西。

    只创建一个对象列表

    首先,我建议你不要两个列表。这是此处描述的反模式:Anti-pattern: parallel collections。由于每个第一天都属于另一个列表中相应的最后一天,因此当您将它们放在一起时,一切都会更方便且不易出错。为此,您可以使用一些库类或设计自己的:

    public class DateInterval {
    
        LocalDate firstDay;
        LocalDate lastDay;
    
        public DateInterval(LocalDate firstDay, LocalDate lastDay) {
            if (lastDay.isBefore(firstDay)) {
                throw new IllegalArgumentException("Dates in wrong order");
            }
            this.firstDay = firstDay;
            this.lastDay = lastDay;
        }
    
        // getters and other stuff
    
        @Override
        public String toString() {
            return "" + firstDay + " - " + lastDay;
        }
    
    }
    

    有了这个类,我们只需要一个列表:

        LocalDate firstDayOverall = LocalDate.of(2018, Month.DECEMBER, 10);
        LocalDate lastDayOverall = LocalDate.of(2019, Month.JANUARY, 6);
    
        if (lastDayOverall.isBefore(firstDayOverall)) {
            throw new IllegalStateException("Overall dates in wrong order");
        }
        LocalDate firstDayOfLastMonth = lastDayOverall.withDayOfMonth(1);
        LocalDate currentFirstDay = firstDayOverall;
        List<DateInterval> intervals = new ArrayList<>();
        while (currentFirstDay.isBefore(firstDayOfLastMonth)) {
            intervals.add(new DateInterval(currentFirstDay, currentFirstDay.with(TemporalAdjusters.lastDayOfMonth())));
            currentFirstDay = currentFirstDay.withDayOfMonth(1).plusMonths(1);
        }
        intervals.add(new DateInterval(currentFirstDay, lastDayOverall));
    
        System.out.println("Intervals: " + intervals);
    

    上述代码sn-p的输出为:

    间隔:[2018-12-10 - 2018-12-31, 2019-01-01 - 2019-01-06]

    第一天和最后一天在同一个月的特殊情况由根本不循环的循环和循环后的add 处理,将单个间隔添加到列表中。

    如果需要两个日期列表

    如果您确实坚持使用两个列表,则两个参数 datesUntil 方法很方便:

        List<LocalDate> firstDays = new ArrayList<>();
        firstDays.add(firstDayOverall);
        // first day not to be included in first days
        LocalDate endExclusive = lastDayOverall.withDayOfMonth(1).plusMonths(1);
        List<LocalDate> remainingFirstDays = firstDayOverall.withDayOfMonth(1)
                .plusMonths(1)
                .datesUntil(endExclusive, Period.ofMonths(1))
                .collect(Collectors.toList());
        firstDays.addAll(remainingFirstDays);
        System.out.println("First days: " + firstDays);
    
        // Calculate last days as the day before each first day except the first
        List<LocalDate> lastDays = remainingFirstDays.stream()
                .map(day -> day.minusDays(1))
                .collect(Collectors.toCollection(ArrayList::new));
        lastDays.add(lastDayOverall);
        System.out.println("Last days:  " + lastDays);
    

    输出:

    First days: [2018-12-10, 2019-01-01]
    Last days:  [2018-12-31, 2019-01-06]
    

    一边

    起初我觉得YearMonth 类会是一个优雅的解决方案。然而,我意识到它需要对第一个月和上个月进行特殊处理,所以我认为它不会产生比我们上面的代码更优雅的代码。喜欢的可以试试。

    【讨论】:

      猜你喜欢
      • 2013-05-06
      • 2012-03-19
      • 2015-10-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多