1// This file contains time-related utilities. 2 3package timex 4 5import ( 6 "fmt" 7 "time" 8) 9 10// Diff calculates the absolute difference between 2 time instances in 11// years, months, days, hours, minutes and seconds. 12// 13// For details, see https://stackoverflow.com/a/36531443/1705598 14func Diff(a, b time.Time) (year, month, day, hour, min, sec int) { 15 if a.Location() != b.Location() { 16 b = b.In(a.Location()) 17 } 18 if a.After(b) { 19 a, b = b, a 20 } 21 y1, M1, d1 := a.Date() 22 y2, M2, d2 := b.Date() 23 24 h1, m1, s1 := a.Clock() 25 h2, m2, s2 := b.Clock() 26 27 year = int(y2 - y1) 28 month = int(M2 - M1) 29 day = int(d2 - d1) 30 hour = int(h2 - h1) 31 min = int(m2 - m1) 32 sec = int(s2 - s1) 33 34 // Normalize negative values 35 if sec < 0 { 36 sec += 60 37 min-- 38 } 39 if min < 0 { 40 min += 60 41 hour-- 42 } 43 if hour < 0 { 44 hour += 24 45 day-- 46 } 47 if day < 0 { 48 // days in month: 49 t := time.Date(y1, M1, 32, 0, 0, 0, 0, time.UTC) 50 day += 32 - t.Day() 51 month-- 52 } 53 if month < 0 { 54 month += 12 55 year-- 56 } 57 58 return 59} 60 61// WeekStart returns the time instant pointing to the start of the week given 62// by its year and ISO Week. Weeks are interpreted starting on Monday, 63// so the returned instant will be 00:00 of Monday of the designated week. 64// 65// One nice property of this function is that it handles out-of-range weeks nicely. 66// That is, if you pass 0 for the week, it will be interpreted as the last week 67// of the previous year. If you pass -1 for the week, it will designate 68// the second to last week of the previous year. Similarly, if you pass max week 69// of the year plus 1, it will be interpreted as the first week of the next year etc. 70// 71// This function only returns the given week's first day (Monday), because the 72// last day of the week is always its first day + 6 days. 73// 74// For details, see https://stackoverflow.com/a/52303730/1705598 75func WeekStart(year, week int) time.Time { 76 // Start from the middle of the year: 77 t := time.Date(year, 7, 1, 0, 0, 0, 0, time.UTC) 78 79 // Roll back to Monday: 80 if wd := t.Weekday(); wd == time.Sunday { 81 t = t.AddDate(0, 0, -6) 82 } else { 83 t = t.AddDate(0, 0, -int(wd)+1) 84 } 85 86 // Difference in weeks: 87 _, w := t.ISOWeek() 88 t = t.AddDate(0, 0, (week-w)*7) 89 90 return t 91} 92 93var months = map[string]time.Month{} 94 95func init() { 96 for i := time.January; i <= time.December; i++ { 97 name := i.String() 98 months[name] = i 99 months[name[:3]] = i 100 } 101} 102 103// ParseMonth parses a month given by its name. 104// Both long names such as "January", "February" and short names such as 105// "Jan", "Feb" are recognized. 106// 107// For details, see https://stackoverflow.com/a/59681275/1705598 108func ParseMonth(s string) (time.Month, error) { 109 if m, ok := months[s]; ok { 110 return m, nil 111 } 112 113 return time.January, fmt.Errorf("invalid month '%s'", s) 114} 115 116var weekdays = map[string]time.Weekday{} 117 118func init() { 119 for d := time.Sunday; d <= time.Saturday; d++ { 120 name := d.String() 121 weekdays[name] = d 122 weekdays[name[:3]] = d 123 } 124} 125 126// ParseWeekday parses a weekday given by its name. 127// Both long names such as "Monday", "Tuesday" and short names such as 128// "Mon", "Tue" are recognized. 129// 130// For details, see https://stackoverflow.com/a/52456320/1705598 131func ParseWeekday(s string) (time.Weekday, error) { 132 if d, ok := weekdays[s]; ok { 133 return d, nil 134 } 135 136 return time.Sunday, fmt.Errorf("invalid weekday '%s'", s) 137} 138