]> Cypherpunks repositories - gostls13.git/commitdiff
time: add ISOWeek method to Time
authorVolker Dobler <dr.volker.dobler@gmail.com>
Thu, 10 Nov 2011 20:40:50 +0000 (12:40 -0800)
committerRob Pike <r@golang.org>
Thu, 10 Nov 2011 20:40:50 +0000 (12:40 -0800)
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
src/pkg/time/time_test.go

index 859b31672780ff17f4168670c73d4a50fdd20ea5..e11d17731b4fe3dd5c6b400e1ff14c840fd141b1 100644 (file)
@@ -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
+}
index 8b373a13bc29a3d4f53e89bb655b0f021c0c0549..01b8bea4aad495a6e933a5f9f62fc377f29896b8 100644 (file)
@@ -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()