Skip to content

Instantly share code, notes, and snippets.

@spatialtime
Created May 23, 2020 01:48
Show Gist options
  • Save spatialtime/f212f6370907b995ca092de17273991a to your computer and use it in GitHub Desktop.
Save spatialtime/f212f6370907b995ca092de17273991a to your computer and use it in GitHub Desktop.
Demonstrates Golang formatting and parsing of ISO 8601 weeks
// This snippet demonstrates Golang formatting and parsing of ISO 8601 weeks.
import (
"errors"
"fmt"
"regexp"
"strconv"
"time"
)
const (
MinWeek = 1
MinYear = 1
MaxYear = 9999
)
// ErrYearRange is returned when a week is not within our permitted range.
var ErrYearRange = fmt.Errorf("year is out of range (valid range: %d–%d inclusive)", MinYear, MaxYear)
// ErrWeekRange is returned when a week is not within our permitted range.
var ErrWeekRange = fmt.Errorf("week is out of range (valid range: %d–number of iso weeks in the given year inclusive)", MinWeek)
// FormatWeek returns an ISO 8601 week string.
func FormatWeek(date time.Time, shortForm bool) string {
year, week := date.ISOWeek()
if shortForm {
return fmt.Sprintf("%d-W%02d", year, week)
}
//date.Weekday() returns a Sunday-started week, with Sunday=0.
// have to adjust to ISO Monday-started week, with Monday=1.
dow := ((7 + date.Weekday() - 1) % 7) + 1
return fmt.Sprintf("%d-W%02d-%d", year, week, dow)
}
func calcP(y int) int {
return y + (y / 4) - (y / 100) + (y / 400)
}
// ISOYearWeeks returns the number of ISO weeks contained
// in a given Gregorian calendar year.
func ISOYearWeeks(gregYear int) int {
if (calcP(gregYear)%7 == 4) ||
(calcP(gregYear-1)%7 == 3) {
return 53
}
return 52
}
// ParseWeek parses an ISO 8601 string representing an ISO week,
// and returns the resultant golang time.Time instance.
// Note: if the ISO week is of the short form (doesn't include day of week),
// this function will return a time.Time instance with day of week of Monday.
func ParseWeek(isoWeek string) (time.Time, error) {
re := regexp.MustCompile(`^(\d{4})-W([0-5]\d)(?:-([1-7]))?$`)
matches := re.FindStringSubmatch(isoWeek)
if matches == nil {
return time.Time{}, errors.New("isoWeek string is of incorrect format")
}
year, err := strconv.Atoi(matches[1])
if err != nil {
return time.Time{}, err
}
if year < MinYear || year > MaxYear {
return time.Time{}, ErrYearRange
}
week, err := strconv.Atoi(matches[2])
if week < MinWeek || week > ISOYearWeeks(year) {
return time.Time{}, ErrWeekRange
}
if err != nil {
return time.Time{}, err
}
week--
daysToAdd := week * 7
if matches[3] != "" {
day, err := strconv.Atoi(matches[3])
if err != nil {
return time.Time{}, err
}
daysToAdd += day - 1
}
daysToAdd -= Weekday(year, 1, 4)
return time.Date(year, time.January, 4+daysToAdd, 0, 0, 0, 0, time.UTC), nil
}
// Weekday returns day of week with Monday=0...Sunday=6.
// Utilizes Zeller's Congruence. see: https://en.wikipedia.org/wiki/Zeller%27s_congruence
func Weekday(year, month, day int) int {
if month == 1 {
month = 13
year--
} else if month == 2 {
month = 14
year--
}
dow := (day + (13 * (month + 1) / 5) + year +
(year / 4) - (year / 100) +
(year / 400)) % 7
return (7 + (dow - 2)) % 7
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment