你是不是经常跨年的时候遇到mysql、和语言(java、Python等)计算不一致的情况,看了这篇文章,你会豁然开朗~
Week Numbering System周计数系统
关于周计算系统主要区别就是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对周的计算有多个函数计算
2.1 week()
模式 | 一周的第一天 | 范围 | 第1周的计算标准 | 说明 |
---|---|---|---|---|
0 | Sunday | 0-53 | with a Sunday in this year | 周日作为一周的第1天——第1个周日在的那周是第1周 |
1 | Monday | 0-53 | with 4 or more days this year | 周一中作为一周的第1天——一周大于等于4天在这一年是第1周 |
2 | Sunday | 1-53 | with a Sunday in this year | 周日作为一周的第1天——第1个周日在的那周是第1周 |
3 | Monday | 1-53 | with 4 or more days this year | 周一中作为一周的第1天——一周大于等于4天在这一年是第1周 |
4 | Sunday | 0-53 | with 4 or more days this year | 周日中作为一周的第1天——一周大于等于4天在这一年是第1周 |
5 | Monday | 0-53 | with a Monday in this year | 周一中作为一周的第1天——第1个周一在的那周是第1周 |
6 | Sunday | 1-53 | with 4 or more days this year | 周日中作为一周的第1天——一周大于等于4天在这一年是第1周 |
7 | Monday | 1-53 | with a Monday in this year | 周一中作为一周的第1天——第1个周一在的那周是第1周 |
下面以2021-01-01为例子解说一下这8种模式
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应该是新年的第一周,我们验证正确
- 模式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
2.4. date_format()
和week的区别在于,date_fomat()补全2位
字符 | 说明 | 和week对应关系 |
---|---|---|
%U | Week (00 ..53 ), where Sunday is the first day of the week; WEEK() mode 0 | week(day,0) |
%u | Week (00 ..53 ), where Monday is the first day of the week; WEEK() mode 1 | week(day,1) |
%V | Week (01 ..53 ), where Sunday is the first day of the week; WEEK() mode 2; used with %X | week(day,2) |
%v | Week (01 ..53 ), where Monday is the first day of the week; WEEK() mode 3; used with %x | week(day,3) |
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除了周计算规则对齐之外,是否补全的规则也要对齐呀~
小结
Mode | First day of week | Range | Week 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) | Python | Java |
---|---|---|---|---|---|---|---|---|---|
0 | Sunday | 0-53 | with a Sunday in this year | week(day,0) | —— | —— | date_format(day,'%U ') | datetime.date(year, month, day).strftime(‘%U’) | |
1 | Monday | 0-53 | with 4 or more days this year | week(day,1) | —— | —— | date_format(day,'%u ') | ||
2 | Sunday | 1-53 | with a Sunday in this year | week(day,2) | —— | yearweek(day,0)yearweek(day,2) | date_format(day,'%V ') | ||
3***(常用)*** | Monday | 1-53 | with 4 or more days this year | week(day,3) | weekofyear(day) | yearweek(day,1)yearweek(day,3) | date_format(day,'%v ') | datetime.date(year, month, day).isocalendar()[1] isoweek | Calendar.setFirstDayOfWeek(Calendar.MONDAY) + Calendar.setMinimalDaysInFirstWeek(4) |
4 | Sunday | 0-53 | with 4 or more days this year | week(day,4) | —— | —— | —— | ||
5 | Monday | 0-53 | with a Monday in this year | week(day,5) | —— | —— | —— | datetime.date(year, month, day).strftime(‘%W’) | |
6 | Sunday | 1-53 | with 4 or more days this year | week(day,6) | —— | yearweek(day,4)yearweek(day,6) | —— | Calendar.setFirstDayOfWeek(Calendar.SUNDAY) + Calendar.setMinimalDaysInFirstWeek(4) | |
7 | Monday | 1-53 | with a Monday in this year | week(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
4.1小结
1.y:year-of-era;正正经经的年,即元旦过后;
2.Y:week-based-year;只要本周跨年,那么这周就算入下一年;一周从周日开始,周六结束。
5. outlook周计算规则
5.1 有2种设置日历的方式:
- 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天的周是第一周)
参考文献:
- mysql的官网:dev.mysql.com/doc/refman/…
- outlook的周计数系统:www.msoutlook.info/question/we…
- 关于周计数系统的描述:www.calendar-12.com/week_number
- ISO 8601标准解读:baike.baidu.com/item/ISO%20…
- what is current week numbering system?: www.epochconverter.com/weeknumbers…