mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-05-06 21:52:54 +08:00
330 lines
8.4 KiB
Go
330 lines
8.4 KiB
Go
// 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
|
||
}
|