From d98970963081585c3c2e85fa68740cc854d08f92 Mon Sep 17 00:00:00 2001 From: Volker Dobler Date: Thu, 10 Nov 2011 12:40:50 -0800 Subject: [PATCH] time: add ISOWeek method to Time As the ISO 8601 week number is untrivial to compute a new method on *Time provides year and number of week. R=golang-dev, rsc, r, r CC=golang-dev https://golang.org/cl/5316074 --- src/pkg/time/time.go | 60 +++++++++++++++++++++++++++++++++++++ src/pkg/time/time_test.go | 62 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+) diff --git a/src/pkg/time/time.go b/src/pkg/time/time.go index 859b3167278..e11d17731b4 100644 --- a/src/pkg/time/time.go +++ b/src/pkg/time/time.go @@ -237,3 +237,63 @@ func (t *Time) Weekday() int { } return weekday } + +// julianDayNumber returns the time's Julian Day Number +// relative to the epoch 12:00 January 1, 4713 BC, Monday. +func julianDayNumber(year int64, month, day int) int64 { + a := int64(14-month) / 12 + y := year + 4800 - a + m := int64(month) + 12*a - 3 + return int64(day) + (153*m+2)/5 + 365*y + y/4 - y/100 + y/400 - 32045 +} + +// startOfFirstWeek returns the julian day number of the first day +// of the first week of the given year. +func startOfFirstWeek(year int64) (d int64) { + jan01 := julianDayNumber(year, 1, 1) + weekday := (jan01 % 7) + 1 + if weekday <= 4 { + d = jan01 - weekday + 1 + } else { + d = jan01 + 8 - weekday + } + return +} + +// dayOfWeek returns the weekday of the given date. +func dayOfWeek(year int64, month, day int) int { + t := Time{Year: year, Month: month, Day: day} + return t.Weekday() +} + +// ISOWeek returns the time's year and week number according to ISO 8601. +// Week ranges from 1 to 53. Jan 01 to Jan 03 of year n might belong to +// week 52 or 53 of year n-1, and Dec 29 to Dec 31 might belong to week 1 +// of year n+1. +func (t *Time) ISOWeek() (year int64, week int) { + d := julianDayNumber(t.Year, t.Month, t.Day) + week1Start := startOfFirstWeek(t.Year) + + if d < week1Start { + // Previous year, week 52 or 53 + year = t.Year - 1 + if dayOfWeek(t.Year-1, 1, 1) == 4 || dayOfWeek(t.Year-1, 12, 31) == 4 { + week = 53 + } else { + week = 52 + } + return + } + + if d < startOfFirstWeek(t.Year+1) { + // Current year, week 01..52(,53) + year = t.Year + week = int((d-week1Start)/7 + 1) + return + } + + // Next year, week 1 + year = t.Year + 1 + week = 1 + return +} diff --git a/src/pkg/time/time_test.go b/src/pkg/time/time_test.go index 8b373a13bc2..01b8bea4aad 100644 --- a/src/pkg/time/time_test.go +++ b/src/pkg/time/time_test.go @@ -478,6 +478,68 @@ func TestMinutesInTimeZone(t *testing.T) { } } +type ISOWeekTest struct { + year int64 // year + month, day int // month and day + yex int64 // expected year + wex int // expected week +} + +var isoWeekTests = []ISOWeekTest{ + {1981, 1, 1, 1981, 1}, {1982, 1, 1, 1981, 53}, {1983, 1, 1, 1982, 52}, + {1984, 1, 1, 1983, 52}, {1985, 1, 1, 1985, 1}, {1986, 1, 1, 1986, 1}, + {1987, 1, 1, 1987, 1}, {1988, 1, 1, 1987, 53}, {1989, 1, 1, 1988, 52}, + {1990, 1, 1, 1990, 1}, {1991, 1, 1, 1991, 1}, {1992, 1, 1, 1992, 1}, + {1993, 1, 1, 1992, 53}, {1994, 1, 1, 1993, 52}, {1995, 1, 2, 1995, 1}, + {1996, 1, 1, 1996, 1}, {1996, 1, 7, 1996, 1}, {1996, 1, 8, 1996, 2}, + {1997, 1, 1, 1997, 1}, {1998, 1, 1, 1998, 1}, {1999, 1, 1, 1998, 53}, + {2000, 1, 1, 1999, 52}, {2001, 1, 1, 2001, 1}, {2002, 1, 1, 2002, 1}, + {2003, 1, 1, 2003, 1}, {2004, 1, 1, 2004, 1}, {2005, 1, 1, 2004, 53}, + {2006, 1, 1, 2005, 52}, {2007, 1, 1, 2007, 1}, {2008, 1, 1, 2008, 1}, + {2009, 1, 1, 2009, 1}, {2010, 1, 1, 2009, 53}, {2010, 1, 1, 2009, 53}, + {2011, 1, 1, 2010, 52}, {2011, 1, 2, 2010, 52}, {2011, 1, 3, 2011, 1}, + {2011, 1, 4, 2011, 1}, {2011, 1, 5, 2011, 1}, {2011, 1, 6, 2011, 1}, + {2011, 1, 7, 2011, 1}, {2011, 1, 8, 2011, 1}, {2011, 1, 9, 2011, 1}, + {2011, 1, 10, 2011, 2}, {2011, 1, 11, 2011, 2}, {2011, 6, 12, 2011, 23}, + {2011, 6, 13, 2011, 24}, {2011, 12, 25, 2011, 51}, {2011, 12, 26, 2011, 52}, + {2011, 12, 27, 2011, 52}, {2011, 12, 28, 2011, 52}, {2011, 12, 29, 2011, 52}, + {2011, 12, 30, 2011, 52}, {2011, 12, 31, 2011, 52}, {1995, 1, 1, 1994, 52}, + {2012, 1, 1, 2011, 52}, {2012, 1, 2, 2012, 1}, {2012, 1, 8, 2012, 1}, + {2012, 1, 9, 2012, 2}, {2012, 12, 23, 2012, 51}, {2012, 12, 24, 2012, 52}, + {2012, 12, 30, 2012, 52}, {2012, 12, 31, 2013, 1}, {2013, 1, 1, 2013, 1}, + {2013, 1, 6, 2013, 1}, {2013, 1, 7, 2013, 2}, {2013, 12, 22, 2013, 51}, + {2013, 12, 23, 2013, 52}, {2013, 12, 29, 2013, 52}, {2013, 12, 30, 2014, 1}, + {2014, 1, 1, 2014, 1}, {2014, 1, 5, 2014, 1}, {2014, 1, 6, 2014, 2}, + {2015, 1, 1, 2015, 1}, {2016, 1, 1, 2015, 53}, {2017, 1, 1, 2016, 52}, + {2018, 1, 1, 2018, 1}, {2019, 1, 1, 2019, 1}, {2020, 1, 1, 2020, 1}, + {2021, 1, 1, 2020, 53}, {2022, 1, 1, 2021, 52}, {2023, 1, 1, 2022, 52}, + {2024, 1, 1, 2024, 1}, {2025, 1, 1, 2025, 1}, {2026, 1, 1, 2026, 1}, + {2027, 1, 1, 2026, 53}, {2028, 1, 1, 2027, 52}, {2029, 1, 1, 2029, 1}, + {2030, 1, 1, 2030, 1}, {2031, 1, 1, 2031, 1}, {2032, 1, 1, 2032, 1}, + {2033, 1, 1, 2032, 53}, {2034, 1, 1, 2033, 52}, {2035, 1, 1, 2035, 1}, + {2036, 1, 1, 2036, 1}, {2037, 1, 1, 2037, 1}, {2038, 1, 1, 2037, 53}, + {2039, 1, 1, 2038, 52}, {2040, 1, 1, 2039, 52}, +} + +func TestISOWeek(t *testing.T) { + // Selected dates and corner cases + for _, wt := range isoWeekTests { + dt := &Time{Year: wt.year, Month: wt.month, Day: wt.day} + y, w := dt.ISOWeek() + if w != wt.wex || y != wt.yex { + t.Errorf("got %d/%d; expected %d/%d for %d-%02d-%02d", + y, w, wt.yex, wt.wex, wt.year, wt.month, wt.day) + } + } + + // The only real invariant: Jan 04 is in week 1 + for year := int64(1950); year < 2100; year++ { + if y, w := (&Time{Year: year, Month: 1, Day: 4}).ISOWeek(); y != year || w != 1 { + t.Errorf("got %d/%d; expected %d/1 for Jan 04", y, w, year) + } + } +} + func BenchmarkSeconds(b *testing.B) { for i := 0; i < b.N; i++ { Seconds()