Daniel Ponte
03ebf74abe
Closes #13 Reviewed-on: #60 Co-authored-by: Daniel Ponte <amigan@gmail.com> Co-committed-by: Daniel Ponte <amigan@gmail.com>
93 lines
3.2 KiB
Go
93 lines
3.2 KiB
Go
// 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
|
|
}
|