给定 Java 中的两个日期 startTime 和 EndTime,每月拆分块的最佳解决方案是什么?
what is the best solution to split in chunk monthly given two dates startTime and EndTime in Java?
我必须创建算法来每月计算给定两个日期的组块。
示例(格式日期:yyyy-MM-dd hh:mm:ss)给定两个日期:
- 开始时间: : 2020-01-10 13:00:25
- 结束时间: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 是包含
startDate 和 endDate 作为属性,换句话说就是块。
有没有更聪明的解决方案?
您绝对应该使用 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;
}
您应该使用 Java 8 次 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
这是另一个使用流的方法:
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;
}
}
我必须创建算法来每月计算给定两个日期的组块。
示例(格式日期:yyyy-MM-dd hh:mm:ss)给定两个日期:
- 开始时间: : 2020-01-10 13:00:25
- 结束时间: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 是包含 startDate 和 endDate 作为属性,换句话说就是块。
有没有更聪明的解决方案?
您绝对应该使用 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;
}
您应该使用 Java 8 次 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
这是另一个使用流的方法:
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;
}
}