如何计算某一天归属的周/月/年

246 阅读12分钟

一、代码示例

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

class TimeGrouping {
    private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");

    public static Map<String, List<String>> groupTimes(List<String> dates, String timeType) {
        if ("day".equals(timeType)) {
            return dates.stream().collect(Collectors.groupingBy(s -> s));
        } else if ("week".equals(timeType)) {
            return dates.stream()
                    .collect(Collectors.groupingBy(TimeGrouping::getWeek));
        } else if ("month".equals(timeType)) {
            return dates.stream()
                    .collect(Collectors.groupingBy(TimeGrouping::getMonth));
        } else if ("year".equals(timeType)) {
            return dates.stream()
                    .collect(Collectors.groupingBy(TimeGrouping::getYear));
        } else {
            throw new IllegalArgumentException("Unsupported time type: " + timeType);
        }
    }

    private static String getWeek(String dateStr) {
        LocalDate date = parseDate(dateStr);
        int year = date.getYear();
        int week = date.get(java.time.temporal.IsoFields.WEEK_OF_WEEK_BASED_YEAR);
        return year + "-W" + String.format("%02d", week);
    }

    private static String getMonth(String dateStr) {
        LocalDate date = parseDate(dateStr);
        return date.getYear() + "-" + String.format("%02d", date.getMonthValue());
    }

    private static String getYear(String dateStr) {
        LocalDate date = parseDate(dateStr);
        return String.valueOf(date.getYear());
    }

    private static LocalDate parseDate(String dateStr) {
        return LocalDate.parse(dateStr, FORMATTER);
    }

    // 将不同格式的日期字符串转换为LocalDate进行比较排序
    private static LocalDate parseDateForSort(String dateStr) {
        try {
            return LocalDate.parse(dateStr, FORMATTER);
        } catch (Exception e) {
            // 处理格式为年 - 周的情况
            if (dateStr.contains("-W")) {
                int year = Integer.parseInt(dateStr.substring(0, 4));
                int week = Integer.parseInt(dateStr.substring(6));
                return LocalDate.of(year, 1, 1).plusWeeks(week - 1);
            }
            // 处理格式为年 - 月的情况
            int year = Integer.parseInt(dateStr.substring(0, 4));
            int month = Integer.parseInt(dateStr.substring(5));
            return LocalDate.of(year, month, 1);
        }
    }

    // 新增的方法,用于对分组后的Map按照时间先后顺序排序
    public static Map<String, List<String>> sortResultByTime(Map<String, List<String>> result) {
        return result.entrySet().stream()
                .sorted((e1, e2) -> {
                    LocalDate date1 = parseDateForSort(e1.getKey());
                    LocalDate date2 = parseDateForSort(e2.getKey());
                    return date1.compareTo(date2);
                })
                .collect(Collectors.toMap(
                        Map.Entry::getKey,
                        Map.Entry::getValue,
                        (v1, v2) -> v1,
                        LinkedHashMap::new
                ));
    }

    public static void main(String[] args) {
        List<String> dateList = new ArrayList<>();
        LocalDate startDate = LocalDate.parse("2024-11-16");
        LocalDate endDate = LocalDate.parse("2024-12-20");
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");

        while (!startDate.isAfter(endDate)) {
            dateList.add(startDate.format(formatter));
            startDate = startDate.plusDays(1);
        }

        String timeType = "xxx"; // 这里传入:day、week、month、year
        Map<String, List<String>> result = groupTimes(dateList, timeType);
        // 调用排序方法对结果进行排序
        result = sortResultByTime(result);
        result.forEach((k, v) -> System.out.println(k + " : " + v));
    }
}

传入 year 时:

2024 : [2024-11-16, 2024-11-17, 2024-11-18, 2024-11-19, 2024-11-20, 2024-11-21, 2024-11-22, 2024-11-23, 2024-11-24, 2024-11-25, 2024-11-26, 2024-11-27, 2024-11-28, 2024-11-29, 2024-11-30, 2024-12-01, 2024-12-02, 2024-12-03, 2024-12-04, 2024-12-05, 2024-12-06, 2024-12-07, 2024-12-08, 2024-12-09, 2024-12-10, 2024-12-11, 2024-12-12, 2024-12-13, 2024-12-14, 2024-12-15, 2024-12-16, 2024-12-17, 2024-12-18, 2024-12-19, 2024-12-20]

传入 month 时:

2024-11 : [2024-11-16, 2024-11-17, 2024-11-18, 2024-11-19, 2024-11-20, 2024-11-21, 2024-11-22, 2024-11-23, 2024-11-24, 2024-11-25, 2024-11-26, 2024-11-27, 2024-11-28, 2024-11-29, 2024-11-30]
2024-12 : [2024-12-01, 2024-12-02, 2024-12-03, 2024-12-04, 2024-12-05, 2024-12-06, 2024-12-07, 2024-12-08, 2024-12-09, 2024-12-10, 2024-12-11, 2024-12-12, 2024-12-13, 2024-12-14, 2024-12-15, 2024-12-16, 2024-12-17, 2024-12-18, 2024-12-19, 2024-12-20]

传入 week 时:

2024-W46 : [2024-11-16, 2024-11-17]
2024-W47 : [2024-11-18, 2024-11-19, 2024-11-20, 2024-11-21, 2024-11-22, 2024-11-23, 2024-11-24]
2024-W48 : [2024-11-25, 2024-11-26, 2024-11-27, 2024-11-28, 2024-11-29, 2024-11-30, 2024-12-01]
2024-W49 : [2024-12-02, 2024-12-03, 2024-12-04, 2024-12-05, 2024-12-06, 2024-12-07, 2024-12-08]
2024-W50 : [2024-12-09, 2024-12-10, 2024-12-11, 2024-12-12, 2024-12-13, 2024-12-14, 2024-12-15]
2024-W51 : [2024-12-16, 2024-12-17, 2024-12-18, 2024-12-19, 2024-12-20]

传入 day 时:

2024-11-16 : [2024-11-16]
2024-11-17 : [2024-11-17]
2024-11-18 : [2024-11-18]
2024-11-19 : [2024-11-19]
2024-11-20 : [2024-11-20]
2024-11-21 : [2024-11-21]
2024-11-22 : [2024-11-22]
2024-11-23 : [2024-11-23]
2024-11-24 : [2024-11-24]
2024-11-25 : [2024-11-25]
2024-11-26 : [2024-11-26]
2024-11-27 : [2024-11-27]
2024-11-28 : [2024-11-28]
2024-11-29 : [2024-11-29]
2024-11-30 : [2024-11-30]
2024-12-01 : [2024-12-01]
2024-12-02 : [2024-12-02]
2024-12-03 : [2024-12-03]
2024-12-04 : [2024-12-04]
2024-12-05 : [2024-12-05]
2024-12-06 : [2024-12-06]
2024-12-07 : [2024-12-07]
2024-12-08 : [2024-12-08]
2024-12-09 : [2024-12-09]
2024-12-10 : [2024-12-10]
2024-12-11 : [2024-12-11]
2024-12-12 : [2024-12-12]
2024-12-13 : [2024-12-13]
2024-12-14 : [2024-12-14]
2024-12-15 : [2024-12-15]
2024-12-16 : [2024-12-16]
2024-12-17 : [2024-12-17]
2024-12-18 : [2024-12-18]
2024-12-19 : [2024-12-19]
2024-12-20 : [2024-12-20]

二、详细解读

以下是对上述Java代码的详细解读:

1. 整体功能概述

定义了一个名为TimeGrouping的类,主要功能是对给定的日期字符串列表按照不同的时间类型(天、周、月、年)进行分组,并提供了对分组结果按照时间先后顺序进行排序的功能。它使用了Java 8引入的java.time包来处理日期相关操作,以及利用Stream API进行便捷的集合处理。

2. 包导入与常量定义

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

class TimeGrouping {
    private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
  • 开头导入了一系列必要的Java类,包括用于处理日期的LocalDate、日期格式化的DateTimeFormatter以及用于操作集合的相关类。
  • 定义了一个private static finalDateTimeFormatter常量FORMATTER,用于将日期字符串按照yyyy-MM-dd的格式进行解析和格式化。

3. groupTimes方法

public static Map<String, List<String>> groupTimes(List<String> dates, String timeType) {
    if ("day".equals(timeType)) {
        return dates.stream().collect(Collectors.groupingBy(s -> s));
    } else if ("week".equals(timeType)) {
        return dates.stream()
               .collect(Collectors.groupingBy(TimeGrouping::getWeek));
    } else if ("month".equals(timeType)) {
        return dates.stream()
               .collect(Collectors.groupingBy(TimeGrouping::getMonth));
    } else if ("year".equals(timeType)) {
        return dates.stream()
               .collect(Collectors.groupingBy(TimeGrouping::getYear));
    } else {
        throw new IllegalArgumentException("Unsupported time type: " + timeType);
    }
}
  • 这是一个静态方法,接受一个日期字符串列表dates和一个表示时间类型的字符串timeType作为参数,并返回一个按照指定时间类型分组后的Map

  • 根据timeType的值不同,使用Stream APIcollect方法结合Collectors.groupingBy来对日期进行分组:

    • timeType"day"时,直接按照日期字符串本身进行分组,即每个不同的日期字符串是一个分组键,对应的值是包含该日期字符串的列表(实际上列表中只有这一个元素本身)。
    • timeType"week"时,调用getWeek方法来提取每个日期所属的周信息(格式为年-W周数)作为分组键进行分组。
    • 类似地,"month"调用getMonth方法(提取年-月信息作为分组键),"year"调用getYear方法(提取年份作为分组键)来分别进行相应的分组操作。
    • 如果传入的timeType不支持,则抛出IllegalArgumentException异常。

4. 日期信息提取相关私有方法

private static String getWeek(String dateStr) {
    LocalDate date = parseDate(dateStr);
    int year = date.getYear();
    int week = date.get(java.time.temporal.IsoFields.WEEK_OF_WEEK_BASED_YEAR);
    return year + "-W" + String.format("%02d", week);
}

private static String getMonth(String dateStr) {
    LocalDate date = parseDate(dateStr);
    return date.getYear() + "-" + String.format("%02d", date.getMonthValue());
}

private static String getYear(String dateStr) {
    LocalDate date = parseDate(dateStr);
    return String.valueOf(date.getYear());
}

private static LocalDate parseDate(String dateStr) {
    return LocalDate.parse(dateStr, FORMATTER);
}
  • getWeek方法:先通过parseDate方法将日期字符串解析为LocalDate对象,然后获取该日期对应的年份以及按照ISO标准的周数(一年中的第几周),最后将其格式化为年-W周数(周数占两位,不足两位前面补0)的字符串形式返回,用于按周分组时作为分组键。
  • getMonth方法:同样先解析日期字符串为LocalDate,接着获取年份和月份,并格式化为年-月(月份占两位,不足两位前面补0)的字符串,用于按月分组的分组键。
  • getYear方法:解析日期字符串得到LocalDate后,直接返回年份的字符串表示,用于按年分组的分组键。
  • parseDate方法:利用前面定义的FORMATTER将输入的日期字符串按照yyyy-MM-dd格式解析成LocalDate对象,方便后续日期相关操作的使用。

5. 处理不同格式日期字符串解析用于排序的方法

private static LocalDate parseDateForSort(String dateStr) {
    try {
        return LocalDate.parse(dateStr, FORMATTER);
    } catch (Exception e) {
        // 处理格式为年 - 周的情况
        if (dateStr.contains("-W")) {
            int year = Integer.parseInt(dateStr.substring(0, 4));
            int week = Integer.parseInt(dateStr.substring(6));
            return LocalDate.of(year, 1, 1).plusWeeks(week - 1);
        }
        // 处理格式为年 - 月的情况
        int year = Integer.parseInt(dateStr.substring(0, 4));
        int month = Integer.parseInt(dateStr.substring(5));
        return LocalDate.of(year, month, 1);
    }
}
  • 这个私有静态方法用于将不同格式的日期字符串转换为LocalDate对象,以便进行后续的比较排序操作。
  • 首先尝试按照标准的yyyy-MM-dd格式解析日期字符串,如果解析成功则直接返回对应的LocalDate对象。
  • 如果解析失败(可能是其他格式的日期表示),则进一步判断:
    • 如果字符串包含-W,则认为是年-W周数的格式,先提取年份和周数,然后以当年的1月1日为基础,通过plusWeeks方法往后推移相应周数得到对应的LocalDate对象。
    • 如果不包含-W,则认为是年-月的格式,提取年份和月份,通过LocalDate.of方法创建对应月份第一天的LocalDate对象。

6. sortResultByTime方法

public static Map<String, List<String>> sortResultByTime(Map<String, List<String>> result) {
    return result.entrySet().stream()
           .sorted((e1, e2) -> {
                LocalDate date1 = parseDateForSort(e1.getKey());
                LocalDate date2 = parseDateForSort(e2.getKey());
                return date1.compareTo(date2);
            })
           .collect(Collectors.toMap(
                    Map.Entry::getKey,
                    Map.Entry::getValue,
                    (v1, v2) -> v1,
                    LinkedHashMap::new
            ));
}
  • 这是一个静态方法,用于对已经分组后的结果Map(键为表示时间的字符串,值为对应时间下的日期字符串列表)按照时间先后顺序进行排序。
  • 通过对resultentrySet转换为Stream,在排序时,调用parseDateForSort方法将每个分组键(时间字符串)转换为LocalDate对象,然后根据LocalDate对象的比较结果(compareTo方法)来确定顺序。
  • 最后使用Collectors.toMap将排序后的Entry重新收集为一个Map,这里使用LinkedHashMap::new构造函数保证收集后的Map能保留排序后的顺序。

7. main方法

public static void main(String[] args) {
    List<String> dateList = new ArrayList<>();
    LocalDate startDate = LocalDate.parse("2024-12-16");
    LocalDate endDate = LocalDate.parse("2024-12-20");
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");

    while (!startDate.isAfter(endDate)) {
        dateList.add(startDate.format(formatter));
        startDate = startDate.plusDays(1);
    }

    String timeType = "xxx"; // 这里传入:day、week、month、year
    Map<String, List<String>> result = groupTimes(dateList, timeType);
    // 调用排序方法对结果进行排序
    result = sortResultByTime(result);
    result.forEach((k, v) -> System.out.println(k + " : " + v));
}
  • main方法是程序的入口点,用于演示TimeGrouping类中方法的使用:

    • 首先创建一个空的ArrayList用于存放日期字符串。
    • 定义了起始日期startDate和结束日期endDate,并设置日期格式为yyyy-MM-dd
    • 通过循环,从起始日期开始,每天将日期格式化为字符串添加到dateList中,直到达到结束日期。
    • 定义了一个timeType变量(示例中初始值为"xxx",实际使用时应传入"day""week""month""year"等合法的时间类型),调用groupTimes方法按照指定时间类型对dateList中的日期进行分组,得到分组结果result
    • 接着调用sortResultByTime方法对分组结果result按照时间先后顺序进行排序。
    • 最后通过forEach方法遍历排序后的result,并打印出每个分组键(时间信息)及其对应的日期字符串列表内容。

在实际使用时传入正确的timeType参数即可。