Created
May 23, 2020 01:48
-
-
Save spatialtime/f212f6370907b995ca092de17273991a to your computer and use it in GitHub Desktop.
Demonstrates Golang formatting and parsing of ISO 8601 weeks
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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