首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 日期區間切分工具:按月/日/時/分精確拆分大時間範圍

     編輯:Java 相關知識

業務場景

報表導出、定時任務分片、數據歸檔常需將一個大時間範圍 [start, end] 切分為等長的小區間:

  • 按月:2023-01-01 ~ 2023-12-31 → 12 個月度區間
  • 按日:2023-05-01 ~ 2023-05-10 → 10 個日區間
  • 按時/分:高頻監控數據按小時/分鐘切片

通用切分工具類(Java 8+ 推薦 java.time)

public final class DateRangeSplitter {
    private DateRangeSplitter() {}

    public enum Unit { MONTH, DAY, HOUR, MINUTE }

    /** 
     * 切分區間,返回所有「分割點」時間戳(包含 start,不包含 end 末尾點)
     * 例如 DAY: 2023-05-01 00:00:00, 2023-05-02 00:00:00 ... 2023-05-10 00:00:00
     */
    public static List split(LocalDateTime start, LocalDateTime end, Unit unit) {
        if (start.isAfter(end)) throw new IllegalArgumentException("start 必須 <= end");
        List points = new ArrayList<>();
        LocalDateTime cur = start;
        points.add(cur);
        while (cur.isBefore(end)) {
            cur = switch (unit) {
                case MONTH   -> cur.plusMonths(1).withDayOfMonth(1).withHour(0).withMinute(0).withSecond(0).withNano(0);
                case DAY     -> cur.plusDays(1).withHour(0).withMinute(0).withSecond(0).withNano(0);
                case HOUR    -> cur.plusHours(1).withMinute(0).withSecond(0).withNano(0);
                case MINUTE  -> cur.plusMinutes(1).withSecond(0).withNano(0);
            };
            if (cur.isBefore(end) || cur.isEqual(end)) points.add(cur);
        }
        return points;
    }

    /** 返回區間對象列表,每個區間為 [start, end) 左閉右開 */
    public static List splitToRanges(LocalDateTime start, LocalDateTime end, Unit unit) {
        List points = split(start, end, unit);
        List ranges = new ArrayList<>();
        for (int i = 0; i < points.size() - 1; i++) {
            ranges.add(new Range(points.get(i), points.get(i + 1)));
        }
        return ranges;
    }

    public record Range(LocalDateTime startInclusive, LocalDateTime endExclusive) {}
}

使用示例

public class Demo {
    public static void main(String[] args) {
        LocalDateTime start = LocalDateTime.of(2023, 2, 1, 0, 0);
        LocalDateTime end   = LocalDateTime.of(2023, 3, 2, 0, 0);

        // 按天切分
        List days = DateRangeSplitter.splitToRanges(start, end, DateRangeSplitter.Unit.DAY);
        days.forEach(r -> System.out.println(r.startInclusive() + " ~ " + r.endExclusive()));
        // 輸出 2023-02-01T00:00 ~ 2023-02-02T00:00 ... 2023-03-01T00:00 ~ 2023-03-02T00:00

        // 按小時切分(跨天自動處理)
        List hours = DateRangeSplitter.splitToRanges(
            LocalDateTime.of(2023, 5, 1, 10, 30),
            LocalDateTime.of(2023, 5, 1, 14, 45),
            DateRangeSplitter.Unit.HOUR);
        hours.forEach(System.out::println);
        // 2023-05-01T10:30 ~ 2023-05-01T11:00
        // 2023-05-01T11:00 ~ 2023-05-01T12:00
        // 2023-05-01T12:00 ~ 2023-05-01T13:00
        // 2023-05-01T13:00 ~ 2023-05-01T14:00
        // 2023-05-01T14:00 ~ 2023-05-01T14:45  (最後一段不足 1 小時)
    }
}

舊版本兼容(Java 7 Calendar 寫法)

public static List splitLegacy(Date start, Date end, int calendarField) {
    List points = new ArrayList<>();
    Calendar cal = Calendar.getInstance();
    cal.setTime(start);
    // 對齊到字段起始
    alignToFieldStart(cal, calendarField);
    points.add(cal.getTime());
    while (cal.getTime().before(end)) {
        cal.add(calendarField, 1);
        if (!cal.getTime().after(end)) points.add(cal.getTime());
    }
    return points;
}

private static void alignToFieldStart(Calendar cal, int field) {
    switch (field) {
        case Calendar.MONTH -> { cal.set(Calendar.DAY_OF_MONTH, 1); }
        case Calendar.DAY_OF_MONTH, Calendar.DAY_OF_YEAR -> {}
        case Calendar.HOUR_OF_DAY -> { cal.set(Calendar.MINUTE, 0); }
        case Calendar.MINUTE -> { cal.set(Calendar.SECOND, 0); }
    }
    cal.set(Calendar.SECOND, 0);
    cal.set(Calendar.MILLISECOND, 0);
}

注意事項

  1. 左閉右開:返回區間均為 [start, end),避免邊界重疊/遺漏,適合 SQL WHERE ts >= ? AND ts < ?。
  2. 時區一致性:所有時間對象建議使用同一 ZoneId(如 UTC),跨夏令時切分時 java.time 自動處理。
  3. 性能:切分點數量 = 區間數 + 1,百萬級區間建議流式處理而非全量列表。

將 DateRangeSplitter 放入公共工具包,即可用一行代碼解決所有「大範圍切小片」需求,告別手寫 while (cal.before(end)) cal.add(...) 的重複造輪子。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved