你知道这些跨年的周计算规则吗?Week Numbering System - Python & java & mysql跨年周的对应关系

1,602 阅读13分钟

你是不是经常跨年的时候遇到mysql、和语言(java、Python等)计算不一致的情况,看了这篇文章,你会豁然开朗~

Week Numbering System周计数系统

关于周计算系统主要区别就是2点:

  1. 一周的第一天是哪一天
  2. 一年的第一周是哪一周

1.1 周的第一天算法

每周的第一天是从周天算呢还是从周一算呢?

常见有3种标准:

ISO标准-星期一: 亚欧大陆

北美标准-星期天: 美洲大陆

伊斯兰标准-星期六: 中东

1.2 一年的第一周算法

1.2.1、ISO标准

  • 本年度 第一个星期四所在的星期;
  • 1 月4 日所在的星期;
  • 本年度第一个至少有4 天在 同一星期内的星期;
  • 星期一在去年12 月29 日至今年1 月4 日以内的星期;

这4个是等价的

1.2.2、北美、伊斯兰标准

1月1日所在的周就是第一周

1.3、总结

日历的周计算系统总共有三套:

  • 北美标准——星期天是第一天、星期六是最后一天;1月1日所在的周为一年的第一周

  • ISO8601标准——星期一为第一天,星期天是最后一天;新年超过4天所在的周为第一周(周四所在的周)

  • 伊斯兰标准——星期六为第一天,星期五为最后一天;1月1日所在的周为一年的第一周

2、MySQL的周计算规则

MySQL对周的计算有多个函数计算

dev.MySQL.com/doc/refman/…

2.1 week()

模式一周的第一天范围第1周的计算标准说明
0Sunday0-53with a Sunday in this year周日作为一周的第1天——第1个周日在的那周是第1周
1Monday0-53with 4 or more days this year周一中作为一周的第1天——一周大于等于4天在这一年是第1周
2Sunday1-53with a Sunday in this year周日作为一周的第1天——第1个周日在的那周是第1周
3Monday1-53with 4 or more days this year周一中作为一周的第1天——一周大于等于4天在这一年是第1周
4Sunday0-53with 4 or more days this year周日中作为一周的第1天——一周大于等于4天在这一年是第1周
5Monday0-53with a Monday in this year周一中作为一周的第1天——第1个周一在的那周是第1周
6Sunday1-53with 4 or more days this year周日中作为一周的第1天——一周大于等于4天在这一年是第1周
7Monday1-53with a Monday in this year周一中作为一周的第1天——第1个周一在的那周是第1周

下面以2021-01-01为例子解说一下这8种模式

image.png

2.1.1.模式0

2021-01-01:显示0,代表不是新年的第1周,模式0是要以周日作为一周的第一天(周日-周六为1周),且包含第一个周日,2021-01-01是周五,就是说是上一年的最后一周还没结束,新年开始的第一周是从2021-01-03开始的,2020-12-31应该是上一周的52或者53周,2021-01-03应该是新年的第一周,我们验证正确

2.1.2.模式1

2021-01-01:显示0,代表不是新年的第1周,模式1是要以周1作为一周的第一天(周一到周日为1周),且新年要超过4天在这个周,2021-01-01是周五,且2021-01-01-2021-03只有3天,不能算新的一年的第1周,就是说是上一年的最后一周还没结束,新年开始的第一周是从2021-01-04(周一)开始的,2020-12-31应该是上一周的52或者53周,2021-01-04应该是新年的第一周,我们验证正确

2.1.3.模式2

2021-01-01:显示52,代表是去年的第52周,模式2是要以周日作为一周的第一天(周日-周六为1周),且包含第一个周日,2021-01-01是周五,就是说是上一年的最后一周还没结束,新年开始的第一周是从2021-01-03(周日)开始的,2020-12-31应该是上一周的52或者53周,2021-01-03应该是新年的第一周,我们验证正确

2.1.4.模式3

2021-01-01:显示53,代表不是新年的第1周,模式3是要以周1作为一周的第一天(周一到周日为1周),且新年要超过4天在这个周,2021-01-01是周五,且2021-01-01-2021-03只有3天,不能算新的一年的第1周,就是说是上一年的最后一周还没结束,新年开始的第一周是从2021-01-04(周一)开始的,2020-12-31应该是上一周的53周,2021-01-04应该是新年的第一周,我们验证正确

2.1.5.模式4

2021-01-01:显示0,代表不是新年的第1周,模式4是要以周日作为一周的第一天(周日-周六为1周),且新年要超过4天在这个周,2021-01-01是周五,且2021-01-01-2021-02只有2天,不能算新的一年的第1周,就是说是上一年的最后一周还没结束,新年开始的第一周是从2021-01-03开始的,2020-12-31应该是上一周的52或者53周,2021-01-03应该是新年的第一周,我们验证正确

2.1.6.模式5

2021-01-01:显示0,代表不是新年的第1周,模式5是要以周一作为一周的第一天(周一-周日为1周),且包含第一个周一,2021-01-01是周五,不能算新的一年的第1周,就是说是上一年的最后一周还没结束,新年开始的第一周是从2021-01-04开始的,2020-12-31应该是上一周的52或者53周,2021-01-04应该是新年的第一周,我们验证正确

2.1.7.模式6

2021-01-01:显示53,代表不是新年的第1周,模式6是要以周日作为一周的第一天(周日-周六为1周),且新年要超过4天在这个周,2021-01-01是周五,且2021-01-01-2021-02只有2天,不能算新的一年的第1周,就是说是上一年的最后一周还没结束,新年开始的第一周是从2021-01-03开始的,2020-12-31应该是上一年的53周,2021-01-03应该是新年的第一周,我们验证正确

2.1.8.模式7

2021-01-01:显示52,代表不是新年的第1周,模式7是要以周一作为一周的第一天(周一-周日为1周),且包含第一个周一,2021-01-01是周五,不能算新的一年的第1周,就是说是上一年的最后一周还没结束,新年开始的第一周是从2021-01-04开始的,2020-12-31应该是上一周的52周,2021-01-04应该是新年的第一周,我们验证正确

image.png

  • 模式2和模式0的区别在于:模式0(有0)跟随年份来计算首周数,模式2(无0)不跟随年份来算首周数
  • 模式3和模式1的区别在于:模式1(有0)是跟随年份来计算首周数,模式3(无0)不跟随年份来算首周数
  • 模式6和模式4的区别在于:模式4(有0)是跟随年份来计算首周数,模式6(无0)不跟随年份来算首周数
  • 模式7和模式5的区别在于:模式5(有0)是跟随年份来计算首周数,模式7(无0)不跟随年份来算首周数

2.2. weekofday()

  • 相当于week(day,3),范围是1-53**

2.3. yearweek(day,mode)

  • 这个模式和week的mode完全等效唯一的区别在于yearweek没有0周,0周算上一年的周

  • 模式0==模式2

  • 模式1==模式3

  • 模式4==模式6

  • 模式5==模式7

image.png

2.4. date_format()

和week的区别在于,date_fomat()补全2位

字符说明和week对应关系
%UWeek (00..53), where Sunday is the first day of the week; WEEK() mode 0week(day,0)
%uWeek (00..53), where Monday is the first day of the week; WEEK() mode 1week(day,1)
%VWeek (01..53), where Sunday is the first day of the week; WEEK() mode 2; used with %Xweek(day,2)
%vWeek (01..53), where Monday is the first day of the week; WEEK() mode 3; used with %xweek(day,3)

image.png

2.5.小结

模式一周的第一天范围第一周的计算方法week(day,mode)(颜色相同代表算法一致,只是有0和无0的区别)weekofyear(day)yearweek(day,mode)date_format(day,s)
0周日0-53第1个周日在的那周是第1周week(day,0)————date_format(day,'%U')
1周一0-53一周大于等于4天在这一年是第1周week(day,1)————date_format(day,'%u')
2周日1-53第1个周日在的那周是第1周week(day,2)——yearweek(day,0)yearweek(day,2)date_format(day,'%V')
3周一1-53一周大于等于4天在这一年是第1周week(day,3)weekofyear(day)yearweek(day,1)yearweek(day,3)date_format(day,'%v')
4周日0-53一周大于等于4天在这一年是第1周week(day,4)——————
5周一0-53第1个周一在的那周是第1周week(day,5)——————
6周日1-53一周大于等于4天在这一年是第1周week(day,6)——yearweek(day,4)yearweek(day,6)——
7周一1-53第1个周一在的那周是第1周week(day,7)——yearweek(day,5)yearweek(day,7)——

3、Python & Java中的对应情况

案例一:Java和MySQL的对应周计算不一致问题

现象:程序员小A用Java计算2021-10-18~2021-10-24的周数是43,然后用43周来获取MySQL的数据,发现取出来的数据不对。

原因:Java取的2021-10-18~2021-10-24的时间范围是:43周,但是这个对应的MySQL是42周

public static void main(String[] args) {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
        Calendar c = Calendar.getInstance();
        try {
//            指定日期计算所在的周
            Date time = simpleDateFormat.parse("2021-10-24");
            c.setTime(time);
        } catch (Exception e) {
            return;
        }
        c.setFirstDayOfWeek(Calendar.MONDAY);
        System.out.println(c.get(Calendar.WEEK_OF_YEAR)); // "2021-10-18"~"2021-10-24"打印出43
    }

MySQL使用weekofyear()对应的week(day,3)

案例二:Java的跨年和MySQL跨年不一致问题

现象:程序员小B想取2021年底跨年那2周的数据,也就是:2021-12-27~2022-01-09期间的周对应的MySQL的数据,用Java算成了2022w52和2022w1,实际上对应的MySQL是2021w52和2022w01

原因:小编猜测:Java可能用了2022年的年份2022,用的2022-01-01所在的周数52周,或者用了YYYY-MM-dd,这个也会导致用到2022年的年份,这个后文会提及跨年的问题;

其次,MySQL的weekofyear(day)是计算出来的2022w1,但是MySQL用lpad(weekofyear(publish_time), 2, 0)补全了2位,但是Java没有设置补齐。所以语言和MySQL除了周计算规则对齐之外,是否补全的规则也要对齐呀~

小结

ModeFirst day of weekRangeWeek 1 is the first week …week(day,mode)(0&2、1&3、4&6、5&7算法一致)weekofyear(day)yearweek(day,mode)date_format(day,s)PythonJava
0Sunday0-53with a Sunday in this yearweek(day,0)————date_format(day,'%U')datetime.date(year, month, day).strftime(‘%U’)
1Monday0-53with 4 or more days this yearweek(day,1)————date_format(day,'%u')
2Sunday1-53with a Sunday in this yearweek(day,2)——yearweek(day,0)yearweek(day,2)date_format(day,'%V')
3***(常用)***Monday1-53with 4 or more days this yearweek(day,3)weekofyear(day)yearweek(day,1)yearweek(day,3)date_format(day,'%v')datetime.date(year, month, day).isocalendar()[1]  isoweekCalendar.setFirstDayOfWeek(Calendar.MONDAY) + Calendar.setMinimalDaysInFirstWeek(4)
4Sunday0-53with 4 or more days this yearweek(day,4)——————
5Monday0-53with a Monday in this yearweek(day,5)——————datetime.date(year, month, day).strftime(‘%W’)
6Sunday1-53with 4 or more days this yearweek(day,6)——yearweek(day,4)yearweek(day,6)——Calendar.setFirstDayOfWeek(Calendar.SUNDAY) + Calendar.setMinimalDaysInFirstWeek(4)
7Monday1-53with a Monday in this yearweek(day,7)——yearweek(day,5)yearweek(day,7)——

Java计算日期所在函数


package com.wzm.example;
 
 
import Java.text.SimpleDateFormat;
import Java.util.Calendar;
import Java.util.Date;
 
public class Test {
    public static void main(String[] args) {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
        Calendar c = Calendar.getInstance();
        try {
//            指定日期计算所在的周
            Date time = simpleDateFormat.parse("2021-01-01");
            c.setTime(time);
        } catch (Exception e) {
            return;
        }
 
//        设置一周的第一天
        c.setFirstDayOfWeek(Calendar.MONDAY);
//        设置一年的第一周至少包含新年的多少天,设置为4是对照ISO标准
        c.setMinimalDaysInFirstWeek(4);
//        c.add();
        System.out.println(c.get(Calendar.WEEK_OF_YEAR));
    }
}

Python计算日期所在函数

>>> day = datetime.strptime('2021-01-01', '%Y-%m-%d')
>>> week = datetime.date(day).strftime('%U')
>>> week
'00'
>>> week = datetime.date(day).isocalendar()
>>> week
(2020, 53, 5)
>>> week[1]
53
>>> week = datetime.date(day).strftime('%W')
>>> week
'00'

4.YYYY-MM-dd和yyyy-MM-dd的跨年问题

举例:以2021-12-31为例

public static void getTime() {
        Calendar calendar = Calendar.getInstance();
        calendar.set(2021, Calendar.DECEMBER, 31);
        Date strDate = calendar.getTime();
        SimpleDateFormat formatUpperCase = new SimpleDateFormat("yyyy-MM-dd");
//        打印出2021-12-31 to yyyy-MM-dd: 2021-12-31
        System.out.println("2021-12-31 to yyyy-MM-dd: " + formatUpperCase.format(strDate));
        formatUpperCase = new SimpleDateFormat("YYYY-MM-dd");
//        打印出2021-12-31 to YYYY/MM/dd: 2022-12-31
        System.out.println("2021-12-31 to YYYY/MM/dd: " + formatUpperCase.format(strDate));
 
    }
  • yyyy-MM-dd模式打印出来的是2021-12-31

  • YYYY-MM-dd模式打印出来的是2022-12-31

image.png

4.1小结

1.y:year-of-era;正正经经的年,即元旦过后;

2.Y:week-based-year;只要本周跨年,那么这周就算入下一年;一周从周日开始,周六结束。

5. outlook周计算规则

5.1 有2种设置日历的方式:

image.png

  • First day of week (一周的第一天)
  • First week of year(一年的第一周)设置为first4-day week就是ISO8601的标准

5.2 举例:

例1:澳大利亚、新西兰、英国一般设置:

        按照北美的周计数系统

First day of week: Sunday (一周的第一天是周日)

First week of year: Starts on Jan 1 (一年的第一周是以1.1开始)

例2:欧洲

按照ISO标准

First day of week: Monday (一周的第一天是周一)

First week of year: First 4-day week (新年超过4天的周是第一周)

参考文献:

image.png