// Package isoweek calculates a starting date and time of [ISO 8601] week. // // ISO 8601 standard defines the common [week number] system used in Europe // and many other countries. Monday is the first day of a week. // // The Go standard library [time] package has [time.Time.ISOWeek] function // for getting ISO 8601 week number of a given [time.Time], but there is no // reverse functionality for getting a date from a week number. This package // implements that. // // Invalid input is silently accepted. There is a separate [Validate] // function if week number validation is needed. // // There are also functions for working with [Julian day numbers]. Using Julian // day numbers is often the easiest and fastest way to do date calculations. // // This package does not work with the "traditional" week system used in // US/Canada/Japan/etc. (weeks starting on Sundays). However the Julian day // number functions may be still useful. // // [ISO 8601]: https://en.wikipedia.org/wiki/ISO_8601 // [week number]: https://en.wikipedia.org/wiki/ISO_week_date // [Julian day numbers]: https://en.wikipedia.org/wiki/Julian_day package isoweek import "time" // ISOWeekday returns the ISO 8601 weekday number of given day. // (1 = Mon, 2 = Tue,.. 7 = Sun) // // This is different from Go's standard [time.Weekday]. func ISOWeekday(year int, month time.Month, day int) (weekday int) { // Richards, E. G. (2013) pp. 592, 618 return DateToJulian(year, month, day)%7 + 1 } // startOffset returns the offset (in days) from the start of a year to // Monday of the given week. Offset may be negative. func startOffset(y, week int) (offset int) { // This is optimized version of the following: // // return week*7 - ISOWeekday(y, 1, 4) - 3 // // Uses Tomohiko Sakamoto's algorithm for calculating the weekday. y = y - 1 return week*7 - (y+y/4-y/100+y/400+3)%7 - 4 } // StartTime returns the starting time (Monday 00:00) of the given // ISO 8601 week. func StartTime(wyear, week int, loc *time.Location) (start time.Time) { y, m, d := StartDate(wyear, week) return time.Date(y, m, d, 0, 0, 0, 0, loc) } // StartDate returns the starting date (Monday) of the given ISO 8601 week. func StartDate(wyear, week int) (year int, month time.Month, day int) { return JulianToDate( DateToJulian(wyear, 1, 1) + startOffset(wyear, week)) } // ordinalInYear returns the ordinal (within a year) day number. func ordinalInYear(year int, month time.Month, day int) (dayNo int) { return DateToJulian(year, month, day) - DateToJulian(year, 1, 1) + 1 } // FromDate returns ISO 8601 week number of a date. func FromDate(year int, month time.Month, day int) (wyear, week int) { week = (ordinalInYear(year, month, day) - ISOWeekday(year, month, day) + 10) / 7 if week < 1 { return FromDate(year-1, 12, 31) // last week of preceding year } if week == 53 && DateToJulian(StartDate(year+1, 1)) <= DateToJulian(year, month, day) { return year + 1, 1 // first week of following year } return year, week } // Validate checks if a week number is valid. Returns true if it is valid. func Validate(wyear, week int) (ok bool) { if week < 1 || week > 53 { return false } wyear2, week2 := FromDate(StartDate(wyear, week)) if wyear == wyear2 && week == week2 { return true } return false }