Files
cloudpods/pkg/compute/models/timer.go
2023-10-23 10:20:15 +08:00

330 lines
8.4 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Copyright 2019 Yunion
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package models
import (
"context"
"fmt"
"sort"
"strconv"
"strings"
"time"
"yunion.io/x/log"
api "yunion.io/x/onecloud/pkg/apis/compute"
"yunion.io/x/onecloud/pkg/i18n"
"yunion.io/x/onecloud/pkg/util/bitmap"
)
type STimer struct {
// Cycle type
Type string `width:"8" charset:"ascii"`
// 0-59
Minute int `nullable:"false"`
// 0-23
Hour int `nullable:"false"`
// 0-7 1 is Monday 0 is unlimited
WeekDays uint8 `nullable:"false"`
// 0-31 0 is unlimited
MonthDays uint32 `nullable:"false"`
// StartTime represent the start time of this timer
StartTime time.Time
// EndTime represent deadline of this timer
EndTime time.Time
// NextTime represent the time timer should bell
NextTime time.Time `index:"true"`
IsExpired bool
}
// Update will update the SScalingTimer
func (st *STimer) Update(now time.Time) {
if now.IsZero() {
now = time.Now()
}
if !now.Before(st.EndTime) {
st.IsExpired = true
return
}
if now.Before(st.StartTime) {
now = st.StartTime
}
if !st.NextTime.Before(now) {
return
}
newNextTime := time.Date(now.Year(), now.Month(), now.Day(), st.Hour, st.Minute, 0, 0, time.UTC).In(now.Location())
if now.After(newNextTime) {
newNextTime = newNextTime.AddDate(0, 0, 1)
}
switch {
case st.WeekDays != 0:
// week
nowDay, weekdays := int(newNextTime.Weekday()), st.GetWeekDays()
if nowDay == 0 {
nowDay = 7
}
// weekdays[0]+7 is for the case that all time nodes has been missed in this week
weekdays = append(weekdays, weekdays[0]+7)
index := sort.SearchInts(weekdays, nowDay)
newNextTime = newNextTime.AddDate(0, 0, weekdays[index]-nowDay)
case st.MonthDays != 0:
// month
monthdays := st.GetMonthDays()
suitTime := newNextTime
for {
day := suitTime.Day()
index := sort.SearchInts(monthdays, day)
if index == len(monthdays) || monthdays[index] > st.MonthDaySum(suitTime) {
// set suitTime as the first day of next month
suitTime = suitTime.AddDate(0, 1, -suitTime.Day()+1)
continue
}
newNextTime = time.Date(suitTime.Year(), suitTime.Month(), monthdays[index], suitTime.Hour(),
suitTime.Minute(), 0, 0, suitTime.Location())
break
}
default:
// day
}
log.Debugf("The final NextTime: %s", newNextTime)
st.NextTime = newNextTime
if st.NextTime.After(st.EndTime) {
st.IsExpired = true
}
}
// MonthDaySum calculate the number of month's days
func (st *STimer) MonthDaySum(t time.Time) int {
year, month := t.Year(), t.Month()
monthDays := []int{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
if month != 2 {
return monthDays[2]
}
if year%4 != 0 || (year%100 == 0 && year%400 != 0) {
return 28
}
return 29
}
func (st *STimer) GetWeekDays() []int {
return bitmap.Uint2IntArray(uint32(st.WeekDays))
}
func (st *STimer) GetMonthDays() []int {
return bitmap.Uint2IntArray(st.MonthDays)
}
func (st *STimer) SetWeekDays(days []int) {
st.WeekDays = uint8(bitmap.IntArray2Uint(days))
}
func (st *STimer) SetMonthDays(days []int) {
st.MonthDays = bitmap.IntArray2Uint(days)
}
func (st *STimer) TimerDetails() api.TimerDetails {
return api.TimerDetails{ExecTime: st.EndTime}
}
func (st *STimer) CycleTimerDetails() api.CycleTimerDetails {
out := api.CycleTimerDetails{
Minute: st.Minute,
Hour: st.Hour,
WeekDays: st.GetWeekDays(),
MonthDays: st.GetMonthDays(),
StartTime: st.StartTime,
EndTime: st.EndTime,
CycleType: st.Type,
}
return out
}
func checkTimerCreateInput(in api.TimerCreateInput) (api.TimerCreateInput, error) {
now := time.Now()
if now.After(in.ExecTime) {
return in, fmt.Errorf("exec_time is earlier than now")
}
return in, nil
}
var (
timerDescTable = i18n.Table{}
TIMERLANG = "timerLang"
)
func init() {
timerDescTable.Set("timerLang", i18n.NewTableEntry().EN("en").CN("cn"))
}
func (st *STimer) Description(ctx context.Context) string {
lang := timerDescTable.Lookup(ctx, TIMERLANG)
switch lang {
case "en":
return st.descEnglish()
case "cn":
return st.descChinese()
}
return ""
}
var (
wdsCN = []string{"", "一", "二", "三", "四", "五", "六", "日"}
wdsEN = []string{"", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"}
zone = time.Now().Local().Location()
//zone = time.FixedZone("GMT", 8*3600)
)
func (st *STimer) descChinese() string {
format := "2006-01-02 15:04:05"
var prefix string
switch st.Type {
case api.TIMER_TYPE_ONCE:
return fmt.Sprintf("单次 %s触发", st.StartTime.In(zone).Format(format))
case api.TIMER_TYPE_DAY:
prefix = "每天"
case api.TIMER_TYPE_WEEK:
wds := st.GetWeekDays()
weekDays := make([]string, len(wds))
for i := range wds {
weekDays[i] = fmt.Sprintf("星期%s", wdsCN[wds[i]])
}
prefix = fmt.Sprintf("每周 【%s】", strings.Join(weekDays, ""))
case api.TIMER_TYPE_MONTH:
mns := st.GetMonthDays()
monthDays := make([]string, len(mns))
for i := range mns {
monthDays[i] = fmt.Sprintf("%d号", mns[i])
}
prefix = fmt.Sprintf("每月 【%s】", strings.Join(monthDays, ""))
}
return fmt.Sprintf("%s %02d:%02d触发 有效时间为%s至%s", prefix, st.Hour, st.Minute, st.StartTime.In(zone).Format(format), st.EndTime.In(zone).Format(format))
}
func (st *STimer) descEnglish() string {
var detail string
format := "2006-01-02 15:04:05"
switch st.Type {
case api.TIMER_TYPE_ONCE:
return st.EndTime.In(zone).Format(format)
case api.TIMER_TYPE_DAY:
detail = fmt.Sprintf("%d:%d every day", st.Hour, st.Minute)
case api.TIMER_TYPE_WEEK:
detail = st.weekDaysDesc()
case api.TIMER_TYPE_MONTH:
detail = st.monthDaysDesc()
}
if st.EndTime.IsZero() {
return detail
}
return fmt.Sprintf("%s, from %s to %s", detail, st.StartTime.In(zone).Format(format), st.EndTime.In(zone).Format(format))
}
func (st *STimer) weekDaysDesc() string {
if st.WeekDays == 0 {
return ""
}
var desc strings.Builder
wds := st.GetWeekDays()
i := 0
desc.WriteString(fmt.Sprintf("%d:%d every %s", st.Hour, st.Minute, wdsEN[wds[i]]))
for i++; i < len(wds)-1; i++ {
desc.WriteString(", ")
desc.WriteString(wdsEN[wds[i]])
}
if i == len(wds)-1 {
desc.WriteString(" and ")
desc.WriteString(wdsEN[wds[i]])
}
return desc.String()
}
func (st *STimer) monthDaysDesc() string {
if st.MonthDays == 0 {
return ""
}
var desc strings.Builder
mds := st.GetMonthDays()
i := 0
desc.WriteString(fmt.Sprintf("%d:%d on the %d%s", st.Hour, st.Minute, mds[i], st.dateSuffix(mds[i])))
for i++; i < len(mds)-1; i++ {
desc.WriteString(", ")
desc.WriteString(strconv.Itoa(mds[i]))
desc.WriteString(st.dateSuffix(mds[i]))
}
if i == len(mds)-1 {
desc.WriteString(" and ")
desc.WriteString(strconv.Itoa(mds[i]))
desc.WriteString(st.dateSuffix(mds[i]))
}
desc.WriteString(" of each month")
return desc.String()
}
func (st *STimer) dateSuffix(date int) string {
var ret string
switch date {
case 1:
ret = "st"
case 2:
ret = "nd"
case 3:
ret = "rd"
default:
ret = "th"
}
return ret
}
func checkCycleTimerCreateInput(in api.CycleTimerCreateInput) (api.CycleTimerCreateInput, error) {
now := time.Now()
if in.Minute < 0 || in.Minute > 59 {
return in, fmt.Errorf("minute should between 0 and 59")
}
if in.Hour < 0 || in.Hour > 23 {
return in, fmt.Errorf("hour should between 0 and 23")
}
switch in.CycleType {
case api.TIMER_TYPE_HOUR:
if in.CycleHour <= 0 || in.CycleHour >= 24 {
return in, fmt.Errorf("cycle_hour should between 0 and 23")
}
in.WeekDays = []int{}
in.MonthDays = []int{}
case api.TIMER_TYPE_DAY:
in.WeekDays = []int{}
in.MonthDays = []int{}
case api.TIMER_TYPE_WEEK:
if len(in.WeekDays) == 0 {
return in, fmt.Errorf("week_days should not be empty")
}
in.MonthDays = []int{}
case api.TIMER_TYPE_MONTH:
if len(in.MonthDays) == 0 {
return in, fmt.Errorf("month_days should not be empty")
}
in.WeekDays = []int{}
default:
return in, fmt.Errorf("unkown cycle type %s", in.CycleType)
}
if now.After(in.EndTime) {
return in, fmt.Errorf("end_time is earlier than now")
}
return in, nil
}