#720 周数问题:今天是今年的第几周

2021-11-16

2021-01-01

TLDR:

  1. ISO 8601 标准:国际标准规定周一为一周的开始,包含 1 月 4 日的那一周为第一周
  2. Java: 使用 WeekFields.ISO 获取 ISO 标准周数,或使用本地化设置
  3. Python: 使用 strftime%W (本地化) 或 %V (ISO) 参数获取周数
  4. Go: 使用 ISOWeek() 方法获取 ISO 标准周数
  5. MySQL: 使用 WEEK() 函数的 mode 3 或 7 获取 ISO 标准周数

Java

import java.time.LocalDate;
import java.time.temporal.WeekFields;
import java.util.Locale;

public class WeekNumberExample {
    public static void main(String[] args) {
        LocalDate date = LocalDate.of(2021, 1, 1);
        // 使用 ISO 标准计算周数(周一为一周开始)
        int weekNumber = date.get(WeekFields.ISO.weekOfYear());
        System.out.println("ISO week number: " + weekNumber);

        // 使用本地化周数计算
        WeekFields weekFields = WeekFields.of(Locale.getDefault());
        int localizedWeek = date.get(weekFields.weekOfYear());
        System.out.println("Localized week number: " + localizedWeek);
    }
}

Python

import datetime

# 说明不同格式化参数的含义:
# %Y - 年份 (基于日历年度)
# %W - 周数 (周一作为一周的开始,第一周是包含第一个周一的周)
# %G - 年份 (基于ISO周数)
# %V - 周数 (ISO周数,周一作为一周开始,第一周是包含1月4日的那一周)

for i in range(10):
    d = datetime.date(2021 - i, 1, 1)
    print((str(d), d.weekday(), d.strftime('%Y %W / %G %V')))
('2021-01-01', 4, '2021 00 / 2020 53')
('2020-01-01', 2, '2020 00 / 2020 01')
('2019-01-01', 1, '2019 00 / 2019 01')
('2018-01-01', 0, '2018 01 / 2018 01')
('2017-01-01', 6, '2017 00 / 2016 52')
('2016-01-01', 4, '2016 00 / 2015 53')
('2015-01-01', 3, '2015 00 / 2015 01')
('2014-01-01', 2, '2014 00 / 2014 01')
('2013-01-01', 1, '2013 00 / 2013 01')
('2012-01-01', 6, '2012 00 / 2011 52')

Go

package main

import (
    "fmt"
    "time"
)

func main() {
    // Go 中计算周数
    t := time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC)

    // 使用 ISO 标准计算周数
    _, week := t.ISOWeek()
    fmt.Printf("ISO week number: %d\n", week)

    // 使用 YearDay 计算基于年的周数
    yearDay := t.YearDay()
    weekFromYearDay := (yearDay + 6) / 7
    fmt.Printf("Week from year day: %d\n", weekFromYearDay)
}

MySQL 中的实现

WEEK(date[, mode]) 函数:

Mode First day of week Range Week 1 is the first week …
0 Sunday 0-53 with a Sunday in this year
1 Monday 0-53 with 4 or more days this year
2 Sunday 1-53 with a Sunday in this year
3 Monday 1-53 with 4 or more days this year
4 Sunday 0-53 with 4 or more days this year
5 Monday 0-53 with a Monday in this year
6 Sunday 1-53 with 4 or more days this year
7 Monday 1-53 with a Monday in this year

总结一下,就是三个维度:

  1. 周数从 0 开始还是从 1 开始
  2. 以周一算每周的第一天还是周日
  3. 按每周第一天开始算,还是按每周四天开始算
0 周日 每周第一天 0
2 周日 每周第一天 1
4 周日 每周四天 0
6 周日 每周四天 1

1 周一 每周四天 0
3 周一 每周四天 1
5 周一 每周第一天 0
7 周一 每周第一天 1
SELECT NULL AS Mode, WEEK('2021-01-01'), WEEK('2021-01-04')
UNION SELECT 0 AS Mode, WEEK('2021-01-01', 0), WEEK('2021-01-04', 0)
UNION SELECT 1 AS Mode, WEEK('2021-01-01', 1), WEEK('2021-01-04', 1)
UNION SELECT 2 AS Mode, WEEK('2021-01-01', 2), WEEK('2021-01-04', 2)
UNION SELECT 3 AS Mode, WEEK('2021-01-01', 3), WEEK('2021-01-04', 3)
UNION SELECT 4 AS Mode, WEEK('2021-01-01', 4), WEEK('2021-01-04', 4)
UNION SELECT 5 AS Mode, WEEK('2021-01-01', 5), WEEK('2021-01-04', 5)
UNION SELECT 6 AS Mode, WEEK('2021-01-01', 6), WEEK('2021-01-04', 6)
UNION SELECT 7 AS Mode, WEEK('2021-01-01', 7), WEEK('2021-01-04', 7);
Mode WEEK('2021-01-01') WEEK('2021-01-04')
NULL 0 1
0 0 1
1 0 1
2 52 1
3 53 1
4 0 1
5 0 1
6 53 1
7 52 1

我定义的每周迭代名称(例如:2020-01A)

  1. 每周第一天为周一。
  2. 按照周一属于哪个月计算。
import datetime
from datetime import timedelta

def get_week_iteration_name(date):
    """
    根据周一所属月份计算周迭代名称
    格式:YYYY-MM{A-Z}
    返回:str, date, date (迭代名称,周一日期,周日日期)
    """
    weekday = date.weekday()
    monday = date - timedelta(days=weekday)
    sunday = monday + timedelta(days=6)

    year = monday.year
    month = monday.month

    # 计算该月第几个周一
    # 找到该月第一个周一
    first_day = datetime.date(year, month, 1)
    first_monday_offset = (7 - first_day.weekday()) % 7
    first_monday = first_day + timedelta(days=first_monday_offset)

    # 如果第一个周一不在本月,说明该月第一天就是周一
    if first_monday.month != month:
        first_monday = first_day

    # 计算是第几个周一
    week_index = ((monday - first_monday).days // 7) + 1
    # 转换为字母标识 (A=1, B=2, ...)
    week_letter = chr(ord('A') + week_index - 1)

    return f"{year}-{month:02d}{week_letter}", monday, sunday

test_dates = [
    datetime.date(2021, 1, 1),   # Friday
    datetime.date(2021, 1, 4),   # Monday
    datetime.date(2021, 1, 8),   # Friday
    datetime.date(2020, 12, 28), # Monday
]
for d in test_dates:
    weekday = d.weekday()
    name, monday, sunday = get_week_iteration_name(d)
    print(f"{d} -> {name} {weekday + 1} (Mon: {monday}, Sun: {sunday})")

#718 元宇宙与人类的未来

2021-11-15

对于近日大火的“元宇宙”概念,被称为“中国第一位元宇宙架构师”刘慈欣曾表示,元宇宙将是整个人类文明的一次内卷,而内卷的封闭系统的熵值总归是要趋于最大的。所以元宇宙最后就是引导人类走向死路一条。

#714 最近工作的一次总结

2021-11-13

最近两周开发了一个功能,其实质就是做个数据统计,没啥好说的。
我是在没有产品设计的前提下开始工作,产品的设计反倒有一点依赖我所能提供的数据。
而且,和以往自己写功能逻辑不通的是,我这次只管提供数据和方案,业务逻辑的实现由别人来做。
工作过程中遇到了一些问题,这里做个总结(复盘)。

#713 Golang 历史回顾

2021-11-09
版本 日期 备注
go1.17 2021-08-16
go1.16 2021-02-16
go1.15 2020-08-11
go1.14 2020-02-25
go1.13 2019-09-03 Go modules 成为默认
go1.12 2019-02-25
go1.11 2018-08-24 Go modules 引入
go1.10 2018-02-16
go1.9 2017-08-24
go1.8 2017-02-16
go1.7 2016-08-15 context
go1.6 2016-02-17 net/http 支持 HTTP/2
go1.5 2015-08-19 并发 GC
go1.4 2014-12-10
go1.3 2014-06-18
go1.2 2013-12-01
go1.1 2013-05-13
go1 2012-03-28

参考资料与拓展阅读

#712 穿越必备

2021-11-04

知乎上别人安利的几本奇书, 据说穿越的时候如果带了这几本书可以直接准备年号了。

#711 个人物资储备清单

2021-11-02

考虑到我们的消耗速度和物质的保质期,尽可能充足地存储一批物资。
这个双十一准备大采购一波。

食物

  1. 面粉
  2. 龙口粉丝
  3. 调料
  4. 食用油
  5. 酱油(生抽)
  6. 陈醋
  7. 白糖
  8. 牛奶
  9. 奶粉
  10. 零食
  11. 棒棒糖
  12. 饼干

日用品

  1. 卫生纸
  2. 厨房用纸
  3. 洗发水
  4. 沐浴露
  5. 洗手液
  6. 卫生巾

药品

  1. 创口贴
  2. 小儿退热贴
  3. 999 感冒灵颗粒

参考资料与拓展阅读