计算日期在当月是第几周-自然周(每月第一个周一为该月第一周)做法以及1号为第一周做法


Java计算日期在当月是第几周

    • 背景
    • Java LocalDate API
      • 每月第1个周一为该月第一周的做法
      • 每月1号为第一周做法

背景

公司项目需求是统计周报,一开始我的做法是按每月1号开始为第一周统计周报,比如2020-05-01到2020-05-03属于5月第一周,2020-05-04到2020-05-10为5月第二周。

2020年5月日历
产品小姐姐看了觉得不对,账单周报统计,是以一周7天为维度,如果按照上面的做法,统计出来5月第一周的周报只有3天,4月最后一周只有4天,这种不符合账单统计型产品的需求。
后面又拿出支付宝的周报账单给我看

图片名称
看了看支付宝的做法,发现这是以每月第一个星期一作为这个月一周的开始。(好吧,原来这就是所谓的自然周,又得重写打标了)

第一步当然先去Google百度,但是找了一段时间都没有想要的,不是这个抄那个,就是太杂太乱。总之最后决定还是自己写这个逻辑了。

Java LocalDate API

每月第1个周一为该月第一周的做法

其实知道了规则之后,做法和思路很简单。

  • 获得日期的所在周的周一
  • 获得日期这个月的第一个周一
  • 根据两个周一判断是这个月的第几周
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/**
     * 传入日期判断属于哪一年哪一月第几周
     *
     * @apiNote 以每月的第一个周一所在的周作为每月第一周
     * @param date  时间格式(yyyyMMdd) 20200701
     * @return java.lang.String 返回字符串(2020-06-W5) 2020年6月第5周
     * @author kiring
     */
    public String getWeekOfMonthByDay(int date){
        DateTimeFormatter dfDay = DateTimeFormatter.ofPattern("yyyyMMdd");
        LocalDate localDate = LocalDate.of(date / 10000, (date / 100) % 100, date % 100);
        // 获得当前日期的所在周的周一(previousOrSame:如果当前日期是周一,就返回当前日期)
        LocalDate localDateMondy = LocalDate.of(date / 10000, (date / 100) % 100, date % 100).with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY));
        LocalDate firstMonday = null;
        // 获得这个月的第一个周一(这个月的第一周)
        for(int day=1; day<=7; day++){
            DayOfWeek dayOfWeek = LocalDate.of(date / 10000, (date / 100) % 100, day).getDayOfWeek();
            if(DayOfWeek.MONDAY == dayOfWeek){
                firstMonday = LocalDate.of(date / 10000, (date / 100) % 100, day);
                break;
            }
        }
        String outYear = null;
        String outMonth = null;
        String outWeek = null;
        // 根据两个周一判断是这个月的第几周
        if(firstMonday.isBefore(localDateMondy) || firstMonday.isEqual(localDateMondy)){
            //a. 如果当月第一个周一小于等于当前日期所在的周一
            outYear = localDateMondy.format(dfDay).substring(0,4);
            outMonth = localDateMondy.format(dfDay).substring(4,6);
            outWeek = String.valueOf((localDateMondy.toEpochDay() - firstMonday.toEpochDay())/7 + 1);
            return outYear + "-" + outMonth + "-" + "W" + outWeek;
        } else {
            //b. 如果当月第一个周一比当前日期所在的周一还要大
            // 计算上一个月最后一天所在周
            LocalDate lastMonthDate = localDate.minusMonths(1).with(TemporalAdjusters.lastDayOfMonth());
            Integer lastMonthDay = Integer.valueOf(lastMonthDate.format(dfDay));
            return getWeekOfMonthByDay(lastMonthDay);
        }
    }

这里注意考虑两个点,
一是传进来的日期如果刚好是周一的情况,
二是传来的日期如果是上一周的最后一周,也就是当月第一个周一大于当前日期所在的周一的情况,这里我直接使用的是递归来计算

每次传值进来来都会循环计算当月的第一个周一,读者可以自行做缓存处理。

每月1号为第一周做法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
     * 计算日期在月中的周
     * @param day   时间格式如:20200606
     * @return java.lang.Integer 日期在月中的周数
     * @author kiring
     */
    public Integer calculateWeekInMonth(Integer day) {
        if(day < 19700101 || day > 99999999){
            throw new RuntimeException("时间不正确");
        }
        // ISO算法:每个月的第一周至少4天,如果小于4天,算出来是第0周
        // WeekFields weekFields = WeekFields.ISO;
        // 以周一作为一周的开始,每周至少一天
        WeekFields weekFields = WeekFields.of(DayOfWeek.MONDAY,1);
        int monthInWeek = LocalDateTime.of(day / 10000, (day / 100) % 100, day % 100, 0, 0, 0).atZone(ZoneOffset.ofHours(8)).get(weekFields.weekOfMonth());
        return monthInWeek;
    }