time: add .week_of_year() method for time.Time instances (#23838)

This commit is contained in:
kbkpbot 2025-03-02 16:04:59 +08:00 committed by GitHub
parent 05a6e557cf
commit 57a45bc353
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 65 additions and 10 deletions

View File

@ -416,14 +416,13 @@ pub fn (t Time) custom_format(s string) string {
sb.write_string(ordinal_suffix(t.day))
}
'DDD' {
sb.write_string((t.day + days_before[t.month - 1] + int(is_leap_year(t.year))).str())
sb.write_string((t.year_day()).str())
}
'DDDD' {
sb.write_string('${t.day + days_before[t.month - 1] + int(is_leap_year(t.year)):03}')
sb.write_string('${t.year_day():03}')
}
'DDDo' {
sb.write_string(ordinal_suffix(t.day + days_before[t.month - 1] +
int(is_leap_year(t.year))))
sb.write_string(ordinal_suffix(t.year_day()))
}
'd' {
sb.write_string('${t.day_of_week() % 7}')
@ -484,16 +483,13 @@ pub fn (t Time) custom_format(s string) string {
sb.write_string('${(t.hour + 1):02}')
}
'w' {
sb.write_string('${mceil((t.day + days_before[t.month - 1] +
int(is_leap_year(t.year))) / 7):.0}')
sb.write_string('${t.week_of_year():.0}')
}
'ww' {
sb.write_string('${mceil((t.day + days_before[t.month - 1] +
int(is_leap_year(t.year))) / 7):02.0}')
sb.write_string('${t.week_of_year():02.0}')
}
'wo' {
sb.write_string(ordinal_suffix(int(mceil((t.day + days_before[t.month - 1] +
int(is_leap_year(t.year))) / 7))))
sb.write_string(ordinal_suffix(t.week_of_year()))
}
'Q' {
sb.write_string('${(t.month % 4) + 1}')

View File

@ -296,6 +296,37 @@ pub fn (t Time) day_of_week() int {
return day_of_week(t.year, t.month, t.day)
}
// week_of_year returns the current week of year as an integer.
// follow ISO 8601 standard
pub fn (t Time) week_of_year() int {
// ISO 8601 Week of Year Rules:
// --------------------------------------------
// 1. Week Definition:
// - A week starts on **Monday** (Day 1) and ends on **Sunday** (Day 7).
// 2. First Week of the Year:
// - The first week is the one containing the year's **first Thursday**.
// - Equivalently, the week with January 4th always belongs to Week 1.
// 3. Year Assignment:
// - Dates in December/January may belong to the previous/next ISO year,
// depending on the week's Thursday.
// 4. Week Number Format:
// - Expressed as `YYYY-Www` (e.g., `2026-W01` for the first week of 2026).
// --------------------------------------------
// Algorithm Steps:
// 1. Find the Thursday of the current week:
// - If date is Monday-Wednesday, add days to reach Thursday.
// - If date is Thursday-Sunday, subtract days to reach Thursday.
// 2. The ISO year is the calendar year of this Thursday.
// 3. Compute the week number as:
// week_number = (thursday's day_of_year - 1) / 7 + 1
day_of_week := t.day_of_week()
days_to_thursday := 4 - day_of_week
thursday_date := t.add_days(days_to_thursday)
thursday_day_of_year := thursday_date.year_day()
week_number := (thursday_day_of_year - 1) / 7 + 1
return week_number
}
// year_day returns the current day of the year as an integer.
// See also #Time.custom_format .
pub fn (t Time) year_day() int {

View File

@ -201,6 +201,34 @@ fn test_day_of_week() {
}
}
fn test_week_of_year() {
// As windows use msvcrt.dll, which `strftime` does not support %V, so skip test
// TODO: newer version windows use ucrtbase.dll, which support %V
$if !windows {
for year in 2000 .. 2100 {
mut t := time.new(time.Time{
year: year
month: 12
day: 20
})
// check from year.12.20 to next_year.1.8
for _ in 0 .. 20 {
assert t.strftime('%V') == '${t.week_of_year():02}', '${t}'
t = t.add_days(1)
}
}
}
t1 := time.Time{
year: 2025
month: 3
day: 3
}
assert t1.week_of_year() == 10
assert t1.add_days(1).week_of_year() == 10
}
fn test_year_day() {
// testing if December 31st in a leap year is numbered as 366
assert time.parse('2024-12-31 20:00:00')!.year_day() == 366