/*! * Copyright 2013 Raymond Hill * * Modifications 2020 - HashiCorp * * Project: github.com/gorhill/cronexpr * File: cronexpr_next.go * Version: 1.0 * License: pick the one which suits you : * GPL v3 see * APL v2 see * */ package cronexpr /******************************************************************************/ import ( "sort" "time" ) /******************************************************************************/ var dowNormalizedOffsets = [][]int{ {1, 8, 15, 22, 29}, {2, 9, 16, 23, 30}, {3, 10, 17, 24, 31}, {4, 11, 18, 25}, {5, 12, 19, 26}, {6, 13, 20, 27}, {7, 14, 21, 28}, } /******************************************************************************/ func (expr *Expression) calculateActualDaysOfMonth(year, month int) []int { actualDaysOfMonthMap := make(map[int]bool) firstDayOfMonth := time.Date(year, time.Month(month), 1, 0, 0, 0, 0, time.UTC) lastDayOfMonth := firstDayOfMonth.AddDate(0, 1, -1) // As per crontab man page (http://linux.die.net/man/5/crontab#): // "The day of a command's execution can be specified by two // "fields - day of month, and day of week. If both fields are // "restricted (ie, aren't *), the command will be run when // "either field matches the current time" // If both fields are not restricted, all days of the month are a hit if expr.daysOfMonthRestricted == false && expr.daysOfWeekRestricted == false { return genericDefaultList[1 : lastDayOfMonth.Day()+1] } // day-of-month != `*` if expr.daysOfMonthRestricted { // Last day of month if expr.lastDayOfMonth { actualDaysOfMonthMap[lastDayOfMonth.Day()] = true } // Last work day of month if expr.lastWorkdayOfMonth { actualDaysOfMonthMap[workdayOfMonth(lastDayOfMonth, lastDayOfMonth)] = true } // Days of month for v := range expr.daysOfMonth { // Ignore days beyond end of month if v <= lastDayOfMonth.Day() { actualDaysOfMonthMap[v] = true } } // Work days of month // As per Wikipedia: month boundaries are not crossed. for v := range expr.workdaysOfMonth { // Ignore days beyond end of month if v <= lastDayOfMonth.Day() { actualDaysOfMonthMap[workdayOfMonth(firstDayOfMonth.AddDate(0, 0, v-1), lastDayOfMonth)] = true } } } // day-of-week != `*` if expr.daysOfWeekRestricted { // How far first sunday is from first day of month offset := 7 - int(firstDayOfMonth.Weekday()) // days of week // offset : (7 - day_of_week_of_1st_day_of_month) // target : 1 + (7 * week_of_month) + (offset + day_of_week) % 7 for v := range expr.daysOfWeek { w := dowNormalizedOffsets[(offset+v)%7] actualDaysOfMonthMap[w[0]] = true actualDaysOfMonthMap[w[1]] = true actualDaysOfMonthMap[w[2]] = true actualDaysOfMonthMap[w[3]] = true if len(w) > 4 && w[4] <= lastDayOfMonth.Day() { actualDaysOfMonthMap[w[4]] = true } } // days of week of specific week in the month // offset : (7 - day_of_week_of_1st_day_of_month) // target : 1 + (7 * week_of_month) + (offset + day_of_week) % 7 for v := range expr.specificWeekDaysOfWeek { v = 1 + 7*(v/7) + (offset+v)%7 if v <= lastDayOfMonth.Day() { actualDaysOfMonthMap[v] = true } } // Last days of week of the month lastWeekOrigin := firstDayOfMonth.AddDate(0, 1, -7) offset = 7 - int(lastWeekOrigin.Weekday()) for v := range expr.lastWeekDaysOfWeek { v = lastWeekOrigin.Day() + (offset+v)%7 if v <= lastDayOfMonth.Day() { actualDaysOfMonthMap[v] = true } } } return toList(actualDaysOfMonthMap) } func workdayOfMonth(targetDom, lastDom time.Time) int { // If saturday, then friday // If sunday, then monday dom := targetDom.Day() dow := targetDom.Weekday() if dow == time.Saturday { if dom > 1 { dom -= 1 } else { dom += 2 } } else if dow == time.Sunday { if dom < lastDom.Day() { dom += 1 } else { dom -= 2 } } return dom } func sortContains(a []int, x int) bool { i := sort.SearchInts(a, x) return i < len(a) && a[i] == x } func timeZoneInDay(t time.Time) bool { if t.Location() == time.UTC { return false } _, off := t.AddDate(0, 0, -1).Zone() _, ndoff := t.AddDate(0, 0, 1).Zone() return off != ndoff }