├── go.mod ├── task_test.go ├── LICENSE ├── go.sum ├── .github └── workflows │ └── ci.yml ├── task.go ├── README.md ├── .gitignore ├── scheduler.go └── scheduler_test.go /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/iflamed/schedule 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/golang-module/carbon/v2 v2.1.9 7 | github.com/stretchr/testify v1.7.0 8 | ) 9 | 10 | require ( 11 | github.com/davecgh/go-spew v1.1.0 // indirect 12 | github.com/pmezard/go-difflib v1.0.0 // indirect 13 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect 14 | ) 15 | -------------------------------------------------------------------------------- /task_test.go: -------------------------------------------------------------------------------- 1 | // Package schedule 2 | package schedule 3 | 4 | import ( 5 | "context" 6 | "github.com/stretchr/testify/assert" 7 | "testing" 8 | ) 9 | 10 | func TestDefaultTask_Run(t *testing.T) { 11 | var mark bool 12 | d := NewDefaultTask(func(ctx context.Context) { 13 | mark = true 14 | }) 15 | d.Run(context.Background()) 16 | assert.True(t, mark) 17 | } 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 刘兵 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/golang-module/carbon/v2 v2.1.9 h1:OWkhYzTTPe+jPOUEL2JkvGwf6bKNQJoh4LVT1LUay80= 4 | github.com/golang-module/carbon/v2 v2.1.9/go.mod h1:NF5unWf838+pyRY0o+qZdIwBMkFf7w0hmLIguLiEpzU= 5 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 6 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 7 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 8 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 9 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 10 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 11 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 12 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 13 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 14 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | jobs: 10 | 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | 16 | - name: Set up Go 17 | uses: actions/setup-go@v3 18 | with: 19 | go-version: 1.18 20 | 21 | - name: Build 22 | run: go build -v ./... 23 | 24 | - name: Test 25 | run: | 26 | go test -v ./... -covermode=count -coverprofile=coverage.out 27 | go tool cover -func=coverage.out -o=coverage.out 28 | - name: Go Coverage Badge 29 | uses: tj-actions/coverage-badge-go@v2 30 | with: 31 | filename: coverage.out 32 | - name: Verify Changed files 33 | uses: tj-actions/verify-changed-files@v9.1 34 | id: verify-changed-files 35 | with: 36 | files: README.md 37 | - name: Commit changes 38 | if: steps.verify-changed-files.outputs.files_changed == 'true' 39 | run: | 40 | git config --local user.email "action@github.com" 41 | git config --local user.name "GitHub Action" 42 | git add README.md 43 | git commit -m "chore: Updated coverage badge." 44 | - name: Push changes 45 | if: steps.verify-changed-files.outputs.files_changed == 'true' 46 | uses: ad-m/github-push-action@master 47 | with: 48 | github_token: ${{ github.token }} 49 | branch: ${{ github.head_ref }} 50 | -------------------------------------------------------------------------------- /task.go: -------------------------------------------------------------------------------- 1 | // Package schedule 2 | // file contains all struct and interface define. 3 | package schedule 4 | 5 | import ( 6 | "context" 7 | "log" 8 | "time" 9 | ) 10 | 11 | // Task task interface for scheduler task 12 | type Task interface { 13 | Run(ctx context.Context) 14 | } 15 | 16 | // Logger logger interface for scheduler logger 17 | type Logger interface { 18 | Error(msg string, e any) 19 | Debugf(msg string, n int32) 20 | Debug(msg string) 21 | } 22 | 23 | // TaskFunc the function of task 24 | type TaskFunc func(ctx context.Context) 25 | 26 | // WhenFunc the function define of task constraint 27 | type WhenFunc func(ctx context.Context) bool 28 | 29 | type DefaultTask struct { 30 | fn TaskFunc 31 | } 32 | 33 | func NewDefaultTask(fn TaskFunc) *DefaultTask { 34 | return &DefaultTask{fn: fn} 35 | } 36 | 37 | func (d *DefaultTask) Run(ctx context.Context) { 38 | d.fn(ctx) 39 | } 40 | 41 | type NextTick struct { 42 | Year int 43 | Month int 44 | Day int 45 | Hour int 46 | Minute int 47 | Omit bool 48 | } 49 | 50 | type Limit struct { 51 | DaysOfWeek []time.Weekday 52 | StartTime string 53 | EndTime string 54 | IsBetween bool 55 | When WhenFunc 56 | } 57 | 58 | type DefaultLogger struct { 59 | } 60 | 61 | func (d *DefaultLogger) Error(msg string, r any) { 62 | log.Println(msg, r) 63 | } 64 | 65 | func (d *DefaultLogger) Debug(msg string) { 66 | log.Println(msg) 67 | } 68 | 69 | func (d *DefaultLogger) Debugf(msg string, i int32) { 70 | log.Printf(msg, i) 71 | } 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Schedule inspired laravel 2 | A simple scheduler for golang, inspired with laravel's task scheduling, use it with `crontab`. 3 | 4 | [![CI](https://github.com/iflamed/schedule/actions/workflows/ci.yml/badge.svg)](https://github.com/iflamed/schedule/actions/workflows/ci.yml) 5 | ![Coverage](https://img.shields.io/badge/Coverage-100.0%25-brightgreen) 6 | [![Go Report Card](https://goreportcard.com/badge/github.com/iflamed/schedule)](https://goreportcard.com/report/github.com/iflamed/schedule) 7 | [![license](http://img.shields.io/badge/license-MIT-red.svg?style=flat)](https://raw.githubusercontent.com/iflamed/schedule/master/LICENSE) 8 | [![PkgGoDev](https://pkg.go.dev/badge/github.com/iflamed/schedule)](https://pkg.go.dev/github.com/iflamed/schedule) 9 | 10 | ### Run with crontab 11 | After you build your project, you can use it with crontab like below. 12 | ```shell 13 | * * * * * /path/to/your/schedule >> /dev/null 2>&1 14 | ``` 15 | 16 | ### Features 17 | - You can integrate to your self project; 18 | - Multiple frequency options; 19 | - Day and time constraints; 20 | - Custom logger; 21 | - `context.Context` integration; 22 | - Panic recover; 23 | - Run task in go routine; 24 | - Custom timezone; 25 | - 100% code coverage; 26 | 27 | ### Installation 28 | ```shell 29 | go get -u github.com/iflamed/schedule 30 | ``` 31 | 32 | ## Getting Started 33 | ### Custom logger 34 | **Logger interface** 35 | ```go 36 | type Logger interface { 37 | Error(msg string, e any) 38 | Debugf(msg string, n int32) 39 | Debug(msg string) 40 | } 41 | ``` 42 | **The default logger** 43 | ```go 44 | type DefaultLogger struct { 45 | } 46 | 47 | func (d *DefaultLogger) Error(msg string, r any) { 48 | log.Println(msg, r) 49 | } 50 | 51 | func (d *DefaultLogger) Debug(msg string) { 52 | log.Println(msg) 53 | } 54 | 55 | func (d *DefaultLogger) Debugf(msg string, i int32) { 56 | log.Printf(msg, i) 57 | } 58 | ``` 59 | 60 | ### Create scheduler instance 61 | ```go 62 | s := NewScheduler(context.Background(), time.UTC) 63 | s.Daily().CallFunc(func(ctx context.Context) { 64 | log.Println("Task finished.") 65 | return 66 | }) 67 | s.DailyAt("09:00").Call(NewDefaultTask(func(ctx context.Context) { 68 | log.Println("Task finished at 09:00") 69 | })) 70 | s.Start() 71 | ``` 72 | ⚠️You should set frequency first, then call the `Call` and `CallFunc` method to run task. 73 | 74 | ⚠️`s.Start()` method must call at last, it will wait all task finished when process exit. 75 | 76 | ### Schedule Frequency Options 77 | There are many more task schedule frequencies that you may assign to a task: 78 | 79 | Method | Description 80 | ------------- | ------------- 81 | `EveryMinute()` | Run the task every minute 82 | `EveryTwoMinutes()` | Run the task every two minutes 83 | `EveryThreeMinutes()` | Run the task every three minutes 84 | `EveryFourMinutes()` | Run the task every four minutes 85 | `EveryFiveMinutes()` | Run the task every five minutes 86 | `EveryTenMinutes()` | Run the task every ten minutes 87 | `EveryFifteenMinutes()` | Run the task every fifteen minutes 88 | `EveryThirtyMinutes()` | Run the task every thirty minutes 89 | `Hourly()` | Run the task every hour 90 | `HourlyAt(17)` | Run the task every hour at 17 minutes past the hour 91 | `EveryOddHour()` | Run the task every odd hour 92 | `EveryTwoHours()` | Run the task every two hours 93 | `EveryThreeHours()` | Run the task every three hours 94 | `EveryFourHours()` | Run the task every four hours 95 | `EverySixHours()` | Run the task every six hours 96 | `Daily()` | Run the task every day at midnight 97 | `DailyAt("13:00")` | Run the task every day at 13:00 98 | `At("13:00")` | Run the task every day at 13:00, method alias of `dailyAt` 99 | `TwiceDaily(1, 13)` | Run the task daily at 1:00 & 13:00 100 | `TwiceDailyAt(1, 13, 15)` | Run the task daily at 1:15 & 13:15 101 | `Weekly()` | Run the task every Sunday at 00:00 102 | `WeeklyOn(1, "8:00")` | Run the task every week on Monday at 8:00 103 | `Monthly()` | Run the task on the first day of every month at 00:00 104 | `MonthlyOn(4, "15:00")` | Run the task every month on the 4th at 15:00 105 | `TwiceMonthly(1, 16, "13:00")` | Run the task monthly on the 1st and 16th at 13:00 106 | `LastDayOfMonth("15:00")` | Run the task on the last day of the month at 15:00 107 | `Quarterly()` | Run the task on the first day of every quarter at 00:00 108 | `Yearly()` | Run the task on the first day of every year at 00:00 109 | `YearlyOn(6, 1, "17:00")` | Run the task every year on June 1st at 17:00 110 | `Timezone(time.UTC)` | Set the timezone for the task 111 | 112 | ### Schedule constraints 113 | Method | Description 114 | ------------- | ------------- 115 | `Weekdays()` | Limit the task to weekdays 116 | `Weekends()` | Limit the task to weekends 117 | `Sundays()` | Limit the task to Sunday 118 | `Mondays()` | Limit the task to Monday 119 | `Tuesdays()` | Limit the task to Tuesday 120 | `Wednesdays()` | Limit the task to Wednesday 121 | `Thursdays()` | Limit the task to Thursday 122 | `Fridays()` | Limit the task to Friday 123 | `Saturdays()` | Limit the task to Saturday 124 | `Days(d ...time.Weekday)` | Limit the task to specific days 125 | `Between(start, end string)` | Limit the task to run between start and end time 126 | `UnlessBetween(start, end string)` | Limit the task to not run between start and end time 127 | `When(when WhenFunc)` | Limit the task based on a truth test 128 | 129 | ### Schedule example 130 | ```go 131 | package main 132 | 133 | import ( 134 | "context" 135 | "github.com/iflamed/schedule" 136 | "log" 137 | "time" 138 | ) 139 | 140 | func main() { 141 | s := schedule.NewScheduler(context.Background(), time.UTC) 142 | s.Daily().CallFunc(func(ctx context.Context) { 143 | log.Println("Task finished.") 144 | }) 145 | s.DailyAt("09:00").Call(schedule.NewDefaultTask(func(ctx context.Context) { 146 | log.Println("Task finished at 09:00") 147 | })) 148 | s.EveryMinute().Sundays().Call(schedule.NewDefaultTask(func(ctx context.Context) { 149 | log.Println("Task finished at 09:00") 150 | })) 151 | s.EveryMinute().Sundays().Between("12:00", "20:00").Call(schedule.NewDefaultTask(func(ctx context.Context) { 152 | log.Println("Task finished at 09:00") 153 | })) 154 | s.Start() 155 | } 156 | 157 | ``` 158 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/visualstudiocode,sublimetext,go,goland+all,macos,windows,linux 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode,sublimetext,go,goland+all,macos,windows,linux 3 | 4 | ### Go ### 5 | # If you prefer the allow list template instead of the deny list, see community template: 6 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 7 | # 8 | # Binaries for programs and plugins 9 | *.exe 10 | *.exe~ 11 | *.dll 12 | *.so 13 | *.dylib 14 | 15 | # Test binary, built with `go test -c` 16 | *.test 17 | 18 | # Output of the go coverage tool, specifically when used with LiteIDE 19 | *.out 20 | 21 | # Dependency directories (remove the comment below to include it) 22 | # vendor/ 23 | 24 | # Go workspace file 25 | go.work 26 | 27 | ### Go Patch ### 28 | /vendor/ 29 | /Godeps/ 30 | 31 | ### GoLand+all ### 32 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 33 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 34 | 35 | # User-specific stuff 36 | .idea/**/workspace.xml 37 | .idea/**/tasks.xml 38 | .idea/**/usage.statistics.xml 39 | .idea/**/dictionaries 40 | .idea/**/shelf 41 | 42 | # AWS User-specific 43 | .idea/**/aws.xml 44 | 45 | # Generated files 46 | .idea/**/contentModel.xml 47 | 48 | # Sensitive or high-churn files 49 | .idea/**/dataSources/ 50 | .idea/**/dataSources.ids 51 | .idea/**/dataSources.local.xml 52 | .idea/**/sqlDataSources.xml 53 | .idea/**/dynamic.xml 54 | .idea/**/uiDesigner.xml 55 | .idea/**/dbnavigator.xml 56 | 57 | # Gradle 58 | .idea/**/gradle.xml 59 | .idea/**/libraries 60 | 61 | # Gradle and Maven with auto-import 62 | # When using Gradle or Maven with auto-import, you should exclude module files, 63 | # since they will be recreated, and may cause churn. Uncomment if using 64 | # auto-import. 65 | # .idea/artifacts 66 | # .idea/compiler.xml 67 | # .idea/jarRepositories.xml 68 | # .idea/modules.xml 69 | # .idea/*.iml 70 | # .idea/modules 71 | # *.iml 72 | # *.ipr 73 | 74 | # CMake 75 | cmake-build-*/ 76 | 77 | # Mongo Explorer plugin 78 | .idea/**/mongoSettings.xml 79 | 80 | # File-based project format 81 | *.iws 82 | 83 | # IntelliJ 84 | out/ 85 | 86 | # mpeltonen/sbt-idea plugin 87 | .idea_modules/ 88 | 89 | # JIRA plugin 90 | atlassian-ide-plugin.xml 91 | 92 | # Cursive Clojure plugin 93 | .idea/replstate.xml 94 | 95 | # SonarLint plugin 96 | .idea/sonarlint/ 97 | 98 | # Crashlytics plugin (for Android Studio and IntelliJ) 99 | com_crashlytics_export_strings.xml 100 | crashlytics.properties 101 | crashlytics-build.properties 102 | fabric.properties 103 | 104 | # Editor-based Rest Client 105 | .idea/httpRequests 106 | 107 | # Android studio 3.1+ serialized cache file 108 | .idea/caches/build_file_checksums.ser 109 | 110 | ### GoLand+all Patch ### 111 | # Ignore everything but code style settings and run configurations 112 | # that are supposed to be shared within teams. 113 | 114 | .idea/* 115 | 116 | !.idea/codeStyles 117 | !.idea/runConfigurations 118 | 119 | ### Linux ### 120 | *~ 121 | 122 | # temporary files which can be created if a process still has a handle open of a deleted file 123 | .fuse_hidden* 124 | 125 | # KDE directory preferences 126 | .directory 127 | 128 | # Linux trash folder which might appear on any partition or disk 129 | .Trash-* 130 | 131 | # .nfs files are created when an open file is removed but is still being accessed 132 | .nfs* 133 | 134 | ### macOS ### 135 | # General 136 | .DS_Store 137 | .AppleDouble 138 | .LSOverride 139 | 140 | # Icon must end with two \r 141 | Icon 142 | 143 | 144 | # Thumbnails 145 | ._* 146 | 147 | # Files that might appear in the root of a volume 148 | .DocumentRevisions-V100 149 | .fseventsd 150 | .Spotlight-V100 151 | .TemporaryItems 152 | .Trashes 153 | .VolumeIcon.icns 154 | .com.apple.timemachine.donotpresent 155 | 156 | # Directories potentially created on remote AFP share 157 | .AppleDB 158 | .AppleDesktop 159 | Network Trash Folder 160 | Temporary Items 161 | .apdisk 162 | 163 | ### macOS Patch ### 164 | # iCloud generated files 165 | *.icloud 166 | 167 | ### SublimeText ### 168 | # Cache files for Sublime Text 169 | *.tmlanguage.cache 170 | *.tmPreferences.cache 171 | *.stTheme.cache 172 | 173 | # Workspace files are user-specific 174 | *.sublime-workspace 175 | 176 | # Project files should be checked into the repository, unless a significant 177 | # proportion of contributors will probably not be using Sublime Text 178 | # *.sublime-project 179 | 180 | # SFTP configuration file 181 | sftp-config.json 182 | sftp-config-alt*.json 183 | 184 | # Package control specific files 185 | Package Control.last-run 186 | Package Control.ca-list 187 | Package Control.ca-bundle 188 | Package Control.system-ca-bundle 189 | Package Control.cache/ 190 | Package Control.ca-certs/ 191 | Package Control.merged-ca-bundle 192 | Package Control.user-ca-bundle 193 | oscrypto-ca-bundle.crt 194 | bh_unicode_properties.cache 195 | 196 | # Sublime-github package stores a github token in this file 197 | # https://packagecontrol.io/packages/sublime-github 198 | GitHub.sublime-settings 199 | 200 | ### VisualStudioCode ### 201 | .vscode/* 202 | !.vscode/settings.json 203 | !.vscode/tasks.json 204 | !.vscode/launch.json 205 | !.vscode/extensions.json 206 | !.vscode/*.code-snippets 207 | 208 | # Local History for Visual Studio Code 209 | .history/ 210 | 211 | # Built Visual Studio Code Extensions 212 | *.vsix 213 | 214 | ### VisualStudioCode Patch ### 215 | # Ignore all local history of files 216 | .history 217 | .ionide 218 | 219 | # Support for Project snippet scope 220 | .vscode/*.code-snippets 221 | 222 | # Ignore code-workspaces 223 | *.code-workspace 224 | 225 | ### Windows ### 226 | # Windows thumbnail cache files 227 | Thumbs.db 228 | Thumbs.db:encryptable 229 | ehthumbs.db 230 | ehthumbs_vista.db 231 | 232 | # Dump file 233 | *.stackdump 234 | 235 | # Folder config file 236 | [Dd]esktop.ini 237 | 238 | # Recycle Bin used on file shares 239 | $RECYCLE.BIN/ 240 | 241 | # Windows Installer files 242 | *.cab 243 | *.msi 244 | *.msix 245 | *.msm 246 | *.msp 247 | 248 | # Windows shortcuts 249 | *.lnk 250 | 251 | # End of https://www.toptal.com/developers/gitignore/api/visualstudiocode,sublimetext,go,goland+all,macos,windows,linux 252 | -------------------------------------------------------------------------------- /scheduler.go: -------------------------------------------------------------------------------- 1 | // Package schedule 2 | // The core code of scheduler, contain frequency options and constraints. 3 | package schedule 4 | 5 | import ( 6 | "context" 7 | "github.com/golang-module/carbon/v2" 8 | "strconv" 9 | "strings" 10 | "sync" 11 | "sync/atomic" 12 | "time" 13 | ) 14 | 15 | // Scheduler The core scheduler struct 16 | type Scheduler struct { 17 | location *time.Location 18 | now time.Time 19 | wg sync.WaitGroup 20 | ctx context.Context 21 | Next *NextTick 22 | limit *Limit 23 | count int32 24 | log Logger 25 | } 26 | 27 | // NewScheduler create instance of scheduler with context and default time.location 28 | func NewScheduler(ctx context.Context, loc *time.Location) *Scheduler { 29 | return &Scheduler{ 30 | ctx: ctx, 31 | location: loc, 32 | now: time.Now().In(loc), 33 | Next: &NextTick{}, 34 | limit: &Limit{}, 35 | count: 0, 36 | log: &DefaultLogger{}, 37 | } 38 | } 39 | 40 | // Timezone set timezone with a new time.Location instance 41 | // after `Call` and `CallFunc` method called, the current time will roll back to default location. 42 | func (s *Scheduler) Timezone(loc *time.Location) *Scheduler { 43 | s.now = s.now.In(loc) 44 | return s 45 | } 46 | 47 | // SetLogger set a new logger 48 | func (s *Scheduler) SetLogger(l Logger) *Scheduler { 49 | if l == nil { 50 | return s 51 | } 52 | s.log = l 53 | return s 54 | } 55 | 56 | // Start wait all task to be finished 57 | func (s *Scheduler) Start() { 58 | if atomic.LoadInt32(&s.count) > 0 { 59 | s.log.Debugf("Wait for %d tasks finish... \n", s.count) 60 | } 61 | s.wg.Wait() 62 | s.log.Debug("All tasks have been finished.") 63 | } 64 | 65 | // Call call a task 66 | func (s *Scheduler) Call(t Task) { 67 | defer s.Timezone(s.location) 68 | if !s.isTimeMatched() { 69 | return 70 | } 71 | if !s.checkLimit() { 72 | return 73 | } 74 | atomic.AddInt32(&s.count, 1) 75 | s.wg.Add(1) 76 | go func() { 77 | defer func() { 78 | s.wg.Done() 79 | atomic.AddInt32(&s.count, -1) 80 | if r := recover(); r != nil { 81 | s.log.Error("Recovering schedule task from panic:", r) 82 | } 83 | }() 84 | t.Run(s.ctx) 85 | }() 86 | } 87 | 88 | // CallFunc call a task function 89 | func (s *Scheduler) CallFunc(fn TaskFunc) { 90 | s.Call(NewDefaultTask(fn)) 91 | } 92 | 93 | func (s *Scheduler) isTimeMatched() bool { 94 | if s.Next.Omit { 95 | return false 96 | } 97 | if s.Next.Year == s.now.Year() && 98 | s.Next.Month == int(s.now.Month()) && 99 | s.Next.Day == s.now.Day() && 100 | s.Next.Hour == s.now.Hour() && 101 | s.Next.Minute == s.now.Minute() { 102 | return true 103 | } 104 | return false 105 | } 106 | 107 | func (s *Scheduler) timeToMinutes(t string) (hour, minute int) { 108 | var err error 109 | hm := strings.Split(t, ":") 110 | if len(hm) == 2 { 111 | hour, err = strconv.Atoi(hm[0]) 112 | if err == nil { 113 | minute, err = strconv.Atoi(hm[1]) 114 | } 115 | } 116 | if err != nil { 117 | hour = 0 118 | minute = 0 119 | } 120 | return 121 | } 122 | 123 | func (s *Scheduler) checkLimit() bool { 124 | if len(s.limit.DaysOfWeek) > 0 { 125 | var inDays bool 126 | for _, day := range s.limit.DaysOfWeek { 127 | if day == s.now.Weekday() { 128 | inDays = true 129 | } 130 | } 131 | if !inDays { 132 | return false 133 | } 134 | } 135 | var startMinute, endMinute int 136 | var hour, minute int 137 | if s.limit.StartTime != "" { 138 | hour, minute = s.timeToMinutes(s.limit.StartTime) 139 | startMinute = hour*60 + minute 140 | } 141 | if s.limit.EndTime != "" { 142 | hour, minute = s.timeToMinutes(s.limit.EndTime) 143 | endMinute = hour*60 + minute 144 | } 145 | if startMinute > endMinute { 146 | temp := startMinute 147 | startMinute = endMinute 148 | endMinute = temp 149 | } 150 | minuteOffset := s.now.Hour()*60 + s.now.Minute() 151 | if s.limit.IsBetween && (minuteOffset < startMinute || minuteOffset > endMinute) { 152 | return false 153 | } else if !s.limit.IsBetween && minuteOffset > startMinute && minuteOffset < endMinute { 154 | return false 155 | } 156 | 157 | if s.limit.When != nil { 158 | return s.limit.When(s.ctx) 159 | } 160 | return true 161 | } 162 | 163 | func (s *Scheduler) initNextTick() { 164 | s.Next = &NextTick{ 165 | Year: s.now.Year(), 166 | Month: int(s.now.Month()), 167 | Day: s.now.Day(), 168 | Hour: s.now.Hour(), 169 | Minute: 0, 170 | } 171 | } 172 | 173 | // EveryMinute run task every minutes 174 | func (s *Scheduler) EveryMinute() *Scheduler { 175 | s.initNextTick() 176 | s.Next.Minute = s.now.Minute() 177 | return s 178 | } 179 | 180 | // EveryTwoMinutes run task every two minutes 181 | func (s *Scheduler) EveryTwoMinutes() *Scheduler { 182 | s.initNextTick() 183 | minute := s.now.Minute() 184 | if minute%2 == 0 { 185 | s.Next.Minute = minute 186 | } 187 | return s 188 | } 189 | 190 | // EveryThreeMinutes run task every three minutes 191 | func (s *Scheduler) EveryThreeMinutes() *Scheduler { 192 | s.initNextTick() 193 | minute := s.now.Minute() 194 | if minute%3 == 0 { 195 | s.Next.Minute = minute 196 | } 197 | return s 198 | } 199 | 200 | // EveryFourMinutes run task every four minutes 201 | func (s *Scheduler) EveryFourMinutes() *Scheduler { 202 | s.initNextTick() 203 | minute := s.now.Minute() 204 | if minute%4 == 0 { 205 | s.Next.Minute = minute 206 | } 207 | return s 208 | } 209 | 210 | // EveryFiveMinutes run task every five minutes 211 | func (s *Scheduler) EveryFiveMinutes() *Scheduler { 212 | s.initNextTick() 213 | minute := s.now.Minute() 214 | if minute%5 == 0 { 215 | s.Next.Minute = minute 216 | } 217 | return s 218 | } 219 | 220 | // EveryTenMinutes run the task every ten minutes 221 | func (s *Scheduler) EveryTenMinutes() *Scheduler { 222 | s.initNextTick() 223 | minute := s.now.Minute() 224 | if minute%10 == 0 { 225 | s.Next.Minute = minute 226 | } 227 | return s 228 | } 229 | 230 | // EveryFifteenMinutes run the task every fifteen minutes 231 | func (s *Scheduler) EveryFifteenMinutes() *Scheduler { 232 | s.initNextTick() 233 | minute := s.now.Minute() 234 | if minute%15 == 0 { 235 | s.Next.Minute = minute 236 | } 237 | return s 238 | } 239 | 240 | // EveryThirtyMinutes run the task every thirty minutes 241 | func (s *Scheduler) EveryThirtyMinutes() *Scheduler { 242 | s.initNextTick() 243 | minute := s.now.Minute() 244 | if minute%30 == 0 { 245 | s.Next.Minute = minute 246 | } 247 | return s 248 | } 249 | 250 | // Hourly run the task every hour 251 | func (s *Scheduler) Hourly() *Scheduler { 252 | s.initNextTick() 253 | return s 254 | } 255 | 256 | // HourlyAt run the task every hour at some minutes past the hour 257 | func (s *Scheduler) HourlyAt(t ...int) *Scheduler { 258 | s.initNextTick() 259 | s.Next.Omit = true 260 | minute := s.now.Minute() 261 | for _, v := range t { 262 | if v >= 0 && v == minute { 263 | s.Next.Minute = v 264 | s.Next.Omit = false 265 | break 266 | } 267 | } 268 | return s 269 | } 270 | 271 | // EveryOddHour run the task every odd hour 272 | func (s *Scheduler) EveryOddHour() *Scheduler { 273 | s.initNextTick() 274 | s.Next.Omit = true 275 | hour := s.now.Hour() 276 | if hour >= 1 && hour <= 23 && hour%2 != 0 { 277 | s.Next.Hour = hour 278 | s.Next.Omit = false 279 | } 280 | return s 281 | } 282 | 283 | func (s *Scheduler) setHourlyInterval(n int) { 284 | s.Next.Omit = true 285 | hour := s.now.Hour() 286 | if hour%n == 0 { 287 | s.Next.Hour = hour 288 | s.Next.Omit = false 289 | } 290 | } 291 | 292 | // EveryTwoHours run the task every two hours 293 | func (s *Scheduler) EveryTwoHours() *Scheduler { 294 | s.initNextTick() 295 | s.setHourlyInterval(2) 296 | return s 297 | } 298 | 299 | // EveryThreeHours run the task every three hours 300 | func (s *Scheduler) EveryThreeHours() *Scheduler { 301 | s.initNextTick() 302 | s.setHourlyInterval(3) 303 | return s 304 | } 305 | 306 | // EveryFourHours run the task every four hours 307 | func (s *Scheduler) EveryFourHours() *Scheduler { 308 | s.initNextTick() 309 | s.setHourlyInterval(4) 310 | return s 311 | } 312 | 313 | // EveryFiveHours run the task every five hours 314 | func (s *Scheduler) EveryFiveHours() *Scheduler { 315 | s.initNextTick() 316 | s.setHourlyInterval(5) 317 | return s 318 | } 319 | 320 | // EverySixHours run the task every six hours 321 | func (s *Scheduler) EverySixHours() *Scheduler { 322 | s.initNextTick() 323 | s.setHourlyInterval(6) 324 | return s 325 | } 326 | 327 | // Daily run the task every day at midnight 328 | func (s *Scheduler) Daily() *Scheduler { 329 | s.initNextTick() 330 | s.Next.Hour = 0 331 | return s 332 | } 333 | 334 | func (s *Scheduler) setNextTime(t []string) { 335 | currentHour := s.now.Hour() 336 | currentMinute := s.now.Minute() 337 | var hour, minute int 338 | var err error 339 | for _, v := range t { 340 | hm := strings.Split(v, ":") 341 | if len(hm) == 2 { 342 | hour, err = strconv.Atoi(hm[0]) 343 | if err == nil { 344 | minute, err = strconv.Atoi(hm[1]) 345 | if err == nil { 346 | if currentHour == hour && currentMinute == minute { 347 | s.Next.Hour = currentHour 348 | s.Next.Minute = currentMinute 349 | s.Next.Omit = false 350 | break 351 | } 352 | } 353 | } 354 | } 355 | } 356 | } 357 | 358 | // At run the task every day at some time (03:00 format), method alias of dailyAt 359 | func (s *Scheduler) At(t ...string) *Scheduler { 360 | return s.DailyAt(t...) 361 | } 362 | 363 | // DailyAt run the task every day at some time (03:00 format) 364 | func (s *Scheduler) DailyAt(t ...string) *Scheduler { 365 | s.initNextTick() 366 | s.Next.Hour = 0 367 | s.Next.Minute = 0 368 | s.Next.Omit = true 369 | s.setNextTime(t) 370 | return s 371 | } 372 | 373 | // TwiceDaily run the task daily at first and second hour 374 | func (s *Scheduler) TwiceDaily(first, second int) *Scheduler { 375 | timeList := make([]string, 0, 2) 376 | timeList = append(timeList, strconv.Itoa(first)+":00") 377 | timeList = append(timeList, strconv.Itoa(second)+":00") 378 | s.DailyAt(timeList...) 379 | return s 380 | } 381 | 382 | // TwiceDailyAt run the task daily at some time 383 | // TwiceDailyAt(1, 13, 15) run the task daily at 1:15 & 13:15 384 | func (s *Scheduler) TwiceDailyAt(first, second, offset int) *Scheduler { 385 | timeList := make([]string, 0, 2) 386 | timeList = append(timeList, strconv.Itoa(first)+":"+strconv.Itoa(offset)) 387 | timeList = append(timeList, strconv.Itoa(second)+":"+strconv.Itoa(offset)) 388 | s.DailyAt(timeList...) 389 | return s 390 | } 391 | 392 | // Weekly run the task every Sunday at 00:00 393 | func (s *Scheduler) Weekly() *Scheduler { 394 | now := carbon.Time2Carbon(s.now) 395 | now = now.StartOfWeek() 396 | s.Next = &NextTick{ 397 | Year: now.Year(), 398 | Month: now.Month(), 399 | Day: now.Day(), 400 | Hour: 0, 401 | Minute: 0, 402 | } 403 | return s 404 | } 405 | 406 | // WeeklyOn run the task every week on a time 407 | // WeeklyOn(1, "8:00") run the task every week on Monday at 8:00 408 | func (s *Scheduler) WeeklyOn(d time.Weekday, t string) *Scheduler { 409 | s.Next = &NextTick{ 410 | Year: s.now.Year(), 411 | Month: int(s.now.Month()), 412 | Day: 0, 413 | Hour: 0, 414 | Minute: 0, 415 | Omit: true, 416 | } 417 | if s.now.Weekday() == d { 418 | s.Next.Day = s.now.Day() 419 | s.setNextTime([]string{t}) 420 | } 421 | return s 422 | } 423 | 424 | // Monthly run the task on the first day of every month at 00:00 425 | func (s *Scheduler) Monthly() *Scheduler { 426 | now := carbon.Time2Carbon(s.now) 427 | now = now.StartOfMonth() 428 | s.Next = &NextTick{ 429 | Year: now.Year(), 430 | Month: now.Month(), 431 | Day: now.Day(), 432 | Hour: 0, 433 | Minute: 0, 434 | } 435 | return s 436 | } 437 | 438 | // MonthlyOn run the task every month on a time 439 | // MonthlyOn(4, "15:00") run the task every month on the 4th at 15:00 440 | func (s *Scheduler) MonthlyOn(d int, t string) *Scheduler { 441 | now := carbon.Time2Carbon(s.now) 442 | s.Next = &NextTick{ 443 | Year: now.Year(), 444 | Month: now.Month(), 445 | Day: 0, 446 | Hour: 0, 447 | Minute: 0, 448 | Omit: true, 449 | } 450 | if now.DayOfMonth() == d { 451 | s.Next.Day = now.Day() 452 | s.setNextTime([]string{t}) 453 | } 454 | return s 455 | } 456 | 457 | // TwiceMonthly run the task monthly on some time 458 | // TwiceMonthly(1, 16, "13:00") run the task monthly on the 1st and 16th at 13:00 459 | func (s *Scheduler) TwiceMonthly(b, e int, t string) *Scheduler { 460 | now := carbon.Time2Carbon(s.now) 461 | s.Next = &NextTick{ 462 | Year: now.Year(), 463 | Month: now.Month(), 464 | Day: 0, 465 | Hour: 0, 466 | Minute: 0, 467 | Omit: true, 468 | } 469 | day := now.DayOfMonth() 470 | if day == b || day == e { 471 | s.Next.Day = day 472 | s.setNextTime([]string{t}) 473 | } 474 | return s 475 | } 476 | 477 | // LastDayOfMonth run the task on the last day of the month at a time 478 | // LastDayOfMonth("15:00") run the task on the last day of the month at 15:00 479 | func (s *Scheduler) LastDayOfMonth(t string) *Scheduler { 480 | now := carbon.Time2Carbon(s.now) 481 | s.Next = &NextTick{ 482 | Year: now.Year(), 483 | Month: now.Month(), 484 | Day: now.EndOfMonth().Day(), 485 | Hour: 0, 486 | Minute: 0, 487 | Omit: true, 488 | } 489 | if t != "" { 490 | s.setNextTime([]string{t}) 491 | } 492 | return s 493 | } 494 | 495 | // Quarterly Run the task on the first day of every quarter at 00:00 496 | func (s *Scheduler) Quarterly() *Scheduler { 497 | now := carbon.Time2Carbon(s.now) 498 | qs := now.StartOfQuarter() 499 | s.Next = &NextTick{ 500 | Year: now.Year(), 501 | Month: qs.Month(), 502 | Day: qs.Day(), 503 | Hour: 0, 504 | Minute: 0, 505 | } 506 | return s 507 | } 508 | 509 | // Yearly run the task on the first day of every year at 00:00 510 | func (s *Scheduler) Yearly() *Scheduler { 511 | s.Next = &NextTick{ 512 | Year: s.now.Year(), 513 | Month: 1, 514 | Day: 1, 515 | Hour: 0, 516 | Minute: 0, 517 | } 518 | return s 519 | } 520 | 521 | // YearlyOn Run the task every year on a time 522 | // YearlyOn(6, 1, "17:00") run the task every year on June 1st at 17:00 523 | func (s *Scheduler) YearlyOn(m, d int, t string) *Scheduler { 524 | now := carbon.Time2Carbon(s.now) 525 | s.Next = &NextTick{ 526 | Year: now.Year(), 527 | Month: 0, 528 | Day: 0, 529 | Hour: 0, 530 | Minute: 0, 531 | Omit: true, 532 | } 533 | month := now.Month() 534 | day := now.Day() 535 | if month == m && day == d { 536 | s.Next.Month = month 537 | s.Next.Day = d 538 | } 539 | if t != "" { 540 | s.setNextTime([]string{t}) 541 | } 542 | return s 543 | } 544 | 545 | // Weekdays limit the task to weekdays 546 | func (s *Scheduler) Weekdays() *Scheduler { 547 | s.limit.DaysOfWeek = append( 548 | s.limit.DaysOfWeek, 549 | time.Monday, 550 | time.Tuesday, 551 | time.Wednesday, 552 | time.Thursday, 553 | time.Friday, 554 | ) 555 | return s 556 | } 557 | 558 | // Weekends limit the task to weekends 559 | func (s *Scheduler) Weekends() *Scheduler { 560 | s.limit.DaysOfWeek = append( 561 | s.limit.DaysOfWeek, 562 | time.Saturday, 563 | time.Sunday, 564 | ) 565 | return s 566 | } 567 | 568 | // Mondays limit the task to Monday 569 | func (s *Scheduler) Mondays() *Scheduler { 570 | s.limit.DaysOfWeek = append( 571 | s.limit.DaysOfWeek, 572 | time.Monday, 573 | ) 574 | return s 575 | } 576 | 577 | // Tuesdays limit the task to Tuesday 578 | func (s *Scheduler) Tuesdays() *Scheduler { 579 | s.limit.DaysOfWeek = append( 580 | s.limit.DaysOfWeek, 581 | time.Tuesday, 582 | ) 583 | return s 584 | } 585 | 586 | // Wednesdays limit the task to Wednesday 587 | func (s *Scheduler) Wednesdays() *Scheduler { 588 | s.limit.DaysOfWeek = append( 589 | s.limit.DaysOfWeek, 590 | time.Wednesday, 591 | ) 592 | return s 593 | } 594 | 595 | // Thursdays limit the task to Thursday 596 | func (s *Scheduler) Thursdays() *Scheduler { 597 | s.limit.DaysOfWeek = append( 598 | s.limit.DaysOfWeek, 599 | time.Thursday, 600 | ) 601 | return s 602 | } 603 | 604 | // Fridays limit the task to Friday 605 | func (s *Scheduler) Fridays() *Scheduler { 606 | s.limit.DaysOfWeek = append( 607 | s.limit.DaysOfWeek, 608 | time.Friday, 609 | ) 610 | return s 611 | } 612 | 613 | // Saturdays limit the task to Saturday 614 | func (s *Scheduler) Saturdays() *Scheduler { 615 | s.limit.DaysOfWeek = append( 616 | s.limit.DaysOfWeek, 617 | time.Saturday, 618 | ) 619 | return s 620 | } 621 | 622 | // Sundays limit the task to Sunday 623 | func (s *Scheduler) Sundays() *Scheduler { 624 | s.limit.DaysOfWeek = append( 625 | s.limit.DaysOfWeek, 626 | time.Sunday, 627 | ) 628 | return s 629 | } 630 | 631 | // Days limit the task to specific days 632 | func (s *Scheduler) Days(d ...time.Weekday) *Scheduler { 633 | s.limit.DaysOfWeek = append(s.limit.DaysOfWeek, d...) 634 | return s 635 | } 636 | 637 | // Between limit the task to run between start and end time 638 | func (s *Scheduler) Between(start, end string) *Scheduler { 639 | s.limit.StartTime = start 640 | s.limit.EndTime = end 641 | s.limit.IsBetween = true 642 | return s 643 | } 644 | 645 | // UnlessBetween limit the task to not run between start and end time 646 | func (s *Scheduler) UnlessBetween(start, end string) *Scheduler { 647 | s.limit.StartTime = start 648 | s.limit.EndTime = end 649 | s.limit.IsBetween = false 650 | return s 651 | } 652 | 653 | // When limit the task based on a truth test 654 | func (s *Scheduler) When(when WhenFunc) *Scheduler { 655 | s.limit.When = when 656 | return s 657 | } 658 | -------------------------------------------------------------------------------- /scheduler_test.go: -------------------------------------------------------------------------------- 1 | // Package schedule 2 | package schedule 3 | 4 | import ( 5 | "context" 6 | "github.com/stretchr/testify/assert" 7 | "testing" 8 | "time" 9 | ) 10 | 11 | func TestScheduler_Timezone(t *testing.T) { 12 | s := NewScheduler(context.Background(), time.UTC) 13 | prc, err := time.LoadLocation("Asia/Shanghai") 14 | assert.NoError(t, err) 15 | hour := s.now.Hour() 16 | s.Timezone(prc) 17 | assert.NotEqual(t, hour, s.now.Hour()) 18 | } 19 | 20 | func TestScheduler_isTimeMatched(t *testing.T) { 21 | s := NewScheduler(context.Background(), time.UTC) 22 | s.Next = &NextTick{ 23 | Year: s.now.Year(), 24 | Month: int(s.now.Month()), 25 | Day: s.now.Day(), 26 | Hour: s.now.Hour(), 27 | Minute: s.now.Minute(), 28 | Omit: true, 29 | } 30 | assert.False(t, s.isTimeMatched()) 31 | s.Next.Omit = false 32 | assert.True(t, s.isTimeMatched()) 33 | s.Next.Minute = 60 34 | assert.False(t, s.isTimeMatched()) 35 | } 36 | 37 | func TestScheduler_timeToMinutes(t *testing.T) { 38 | s := NewScheduler(context.Background(), time.UTC) 39 | var hour, minute int 40 | hour, minute = s.timeToMinutes("a:b") 41 | assert.Zero(t, hour) 42 | assert.Zero(t, minute) 43 | hour, minute = s.timeToMinutes("a:1") 44 | assert.Zero(t, hour) 45 | assert.Zero(t, minute) 46 | hour, minute = s.timeToMinutes("1:b") 47 | assert.Zero(t, hour) 48 | assert.Zero(t, minute) 49 | hour, minute = s.timeToMinutes("1:1") 50 | assert.Equal(t, 1, hour) 51 | assert.Equal(t, 1, minute) 52 | } 53 | 54 | func TestScheduler_checkLimit(t *testing.T) { 55 | type fields struct { 56 | now time.Time 57 | limit *Limit 58 | } 59 | now, _ := time.Parse("2006-01-02 15:04:05", "2022-10-05 15:30:01") 60 | tests := []struct { 61 | name string 62 | fields fields 63 | want bool 64 | }{ 65 | { 66 | name: "DaysOfWeek in time", 67 | fields: fields{ 68 | now: now, 69 | limit: &Limit{ 70 | DaysOfWeek: []time.Weekday{time.Wednesday}, 71 | StartTime: "00:00", 72 | EndTime: "23:59", 73 | IsBetween: true, 74 | When: nil, 75 | }, 76 | }, 77 | want: true, 78 | }, 79 | { 80 | name: "DaysOfWeek out time", 81 | fields: fields{ 82 | now: now, 83 | limit: &Limit{ 84 | DaysOfWeek: []time.Weekday{time.Wednesday}, 85 | StartTime: "00:00", 86 | EndTime: "02:59", 87 | IsBetween: true, 88 | When: nil, 89 | }, 90 | }, 91 | want: false, 92 | }, 93 | { 94 | name: "DaysOfWeek out time", 95 | fields: fields{ 96 | now: now, 97 | limit: &Limit{ 98 | DaysOfWeek: []time.Weekday{time.Wednesday}, 99 | StartTime: "00:00", 100 | EndTime: "02:59", 101 | IsBetween: false, 102 | When: nil, 103 | }, 104 | }, 105 | want: true, 106 | }, 107 | { 108 | name: "DaysOfWeek out of time", 109 | fields: fields{ 110 | now: now, 111 | limit: &Limit{ 112 | DaysOfWeek: []time.Weekday{time.Sunday}, 113 | StartTime: "00:00", 114 | EndTime: "02:59", 115 | IsBetween: false, 116 | When: nil, 117 | }, 118 | }, 119 | want: false, 120 | }, 121 | { 122 | name: "in time limit", 123 | fields: fields{ 124 | now: now, 125 | limit: &Limit{ 126 | DaysOfWeek: []time.Weekday{}, 127 | StartTime: "00:00", 128 | EndTime: "23:59", 129 | IsBetween: true, 130 | When: nil, 131 | }, 132 | }, 133 | want: true, 134 | }, 135 | { 136 | name: "out of day", 137 | fields: fields{ 138 | now: now, 139 | limit: &Limit{ 140 | DaysOfWeek: []time.Weekday{time.Friday}, 141 | StartTime: "00:00", 142 | EndTime: "23:59", 143 | IsBetween: true, 144 | When: nil, 145 | }, 146 | }, 147 | want: false, 148 | }, 149 | { 150 | name: "in day in time", 151 | fields: fields{ 152 | now: now, 153 | limit: &Limit{ 154 | DaysOfWeek: []time.Weekday{time.Friday, time.Monday, time.Wednesday}, 155 | StartTime: "00:00", 156 | EndTime: "23:59", 157 | IsBetween: true, 158 | When: nil, 159 | }, 160 | }, 161 | want: true, 162 | }, 163 | { 164 | name: "in time limit", 165 | fields: fields{ 166 | now: now, 167 | limit: &Limit{ 168 | DaysOfWeek: []time.Weekday{}, 169 | StartTime: "00:00", 170 | EndTime: "23:59", 171 | IsBetween: true, 172 | When: func(ctx context.Context) bool { 173 | return false 174 | }, 175 | }, 176 | }, 177 | want: false, 178 | }, 179 | { 180 | name: "in time limit", 181 | fields: fields{ 182 | now: now, 183 | limit: &Limit{ 184 | DaysOfWeek: []time.Weekday{}, 185 | StartTime: "00:00", 186 | EndTime: "23:59", 187 | IsBetween: true, 188 | When: func(ctx context.Context) bool { 189 | return true 190 | }, 191 | }, 192 | }, 193 | want: true, 194 | }, 195 | { 196 | name: "in time limit", 197 | fields: fields{ 198 | now: now, 199 | limit: &Limit{ 200 | DaysOfWeek: []time.Weekday{}, 201 | StartTime: "23:59", 202 | EndTime: "00:00", 203 | IsBetween: false, 204 | When: func(ctx context.Context) bool { 205 | return true 206 | }, 207 | }, 208 | }, 209 | want: false, 210 | }, 211 | } 212 | for _, tt := range tests { 213 | t.Run(tt.name, func(t *testing.T) { 214 | s := &Scheduler{ 215 | now: tt.fields.now, 216 | limit: tt.fields.limit, 217 | } 218 | assert.Equalf(t, tt.want, s.checkLimit(), "checkLimit()") 219 | }) 220 | } 221 | } 222 | 223 | func TestScheduler_EveryMinute(t *testing.T) { 224 | s := NewScheduler(context.Background(), time.UTC) 225 | var marked bool 226 | ch := make(chan bool, 1) 227 | s.EveryMinute().CallFunc(func(ctx context.Context) { 228 | marked = true 229 | ch <- true 230 | }) 231 | <-ch 232 | assert.True(t, marked) 233 | } 234 | 235 | func TestScheduler_EveryTwoMinutes(t *testing.T) { 236 | s := NewScheduler(context.Background(), time.UTC) 237 | s.now, _ = time.Parse("2006-01-02 15:04:05", "2022-10-05 15:30:01") 238 | s.EveryTwoMinutes() 239 | assert.Equal(t, s.Next.Minute, s.now.Minute()) 240 | s.now, _ = time.Parse("2006-01-02 15:04:05", "2022-10-05 15:31:01") 241 | s.EveryTwoMinutes() 242 | assert.NotEqual(t, s.Next.Minute, s.now.Minute()) 243 | } 244 | 245 | func TestScheduler_EveryThreeMinutes(t *testing.T) { 246 | s := NewScheduler(context.Background(), time.UTC) 247 | s.now, _ = time.Parse("2006-01-02 15:04:05", "2022-10-05 15:30:01") 248 | s.EveryThreeMinutes() 249 | assert.Equal(t, s.Next.Minute, s.now.Minute()) 250 | s.now, _ = time.Parse("2006-01-02 15:04:05", "2022-10-05 15:31:01") 251 | s.EveryThreeMinutes() 252 | assert.NotEqual(t, s.Next.Minute, s.now.Minute()) 253 | } 254 | 255 | func TestScheduler_EveryFourMinutes(t *testing.T) { 256 | s := NewScheduler(context.Background(), time.UTC) 257 | s.now, _ = time.Parse("2006-01-02 15:04:05", "2022-10-05 15:28:01") 258 | s.EveryFourMinutes() 259 | assert.Equal(t, s.Next.Minute, s.now.Minute()) 260 | s.now, _ = time.Parse("2006-01-02 15:04:05", "2022-10-05 15:31:01") 261 | s.EveryFourMinutes() 262 | assert.NotEqual(t, s.Next.Minute, s.now.Minute()) 263 | } 264 | 265 | func TestScheduler_EveryFiveMinutes(t *testing.T) { 266 | s := NewScheduler(context.Background(), time.UTC) 267 | s.now, _ = time.Parse("2006-01-02 15:04:05", "2022-10-05 15:30:01") 268 | s.EveryFiveMinutes() 269 | assert.Equal(t, s.Next.Minute, s.now.Minute()) 270 | s.now, _ = time.Parse("2006-01-02 15:04:05", "2022-10-05 15:31:01") 271 | s.EveryFiveMinutes() 272 | assert.NotEqual(t, s.Next.Minute, s.now.Minute()) 273 | } 274 | 275 | func TestScheduler_EveryTenMinutes(t *testing.T) { 276 | s := NewScheduler(context.Background(), time.UTC) 277 | s.now, _ = time.Parse("2006-01-02 15:04:05", "2022-10-05 15:30:01") 278 | s.EveryTenMinutes() 279 | assert.Equal(t, s.Next.Minute, s.now.Minute()) 280 | s.now, _ = time.Parse("2006-01-02 15:04:05", "2022-10-05 15:31:01") 281 | s.EveryTenMinutes() 282 | assert.NotEqual(t, s.Next.Minute, s.now.Minute()) 283 | } 284 | 285 | func TestScheduler_EveryFifteenMinutes(t *testing.T) { 286 | s := NewScheduler(context.Background(), time.UTC) 287 | s.now, _ = time.Parse("2006-01-02 15:04:05", "2022-10-05 15:30:01") 288 | s.EveryFifteenMinutes() 289 | assert.Equal(t, s.Next.Minute, s.now.Minute()) 290 | s.now, _ = time.Parse("2006-01-02 15:04:05", "2022-10-05 15:31:01") 291 | s.EveryFifteenMinutes() 292 | assert.NotEqual(t, s.Next.Minute, s.now.Minute()) 293 | } 294 | 295 | func TestScheduler_EveryThirtyMinutes(t *testing.T) { 296 | s := NewScheduler(context.Background(), time.UTC) 297 | s.now, _ = time.Parse("2006-01-02 15:04:05", "2022-10-05 15:30:01") 298 | s.EveryThirtyMinutes() 299 | assert.Equal(t, s.Next.Minute, s.now.Minute()) 300 | s.now, _ = time.Parse("2006-01-02 15:04:05", "2022-10-05 15:31:01") 301 | s.EveryThirtyMinutes() 302 | assert.NotEqual(t, s.Next.Minute, s.now.Minute()) 303 | } 304 | 305 | func TestScheduler_Hourly(t *testing.T) { 306 | s := NewScheduler(context.Background(), time.UTC) 307 | s.now, _ = time.Parse("2006-01-02 15:04:05", "2022-10-05 15:00:00") 308 | s.Hourly() 309 | assert.Equal(t, s.Next.Minute, 0) 310 | assert.Equal(t, s.Next.Hour, 15) 311 | } 312 | 313 | func TestScheduler_HourlyAt(t *testing.T) { 314 | s := NewScheduler(context.Background(), time.UTC) 315 | s.now, _ = time.Parse("2006-01-02 15:04:05", "2022-10-05 15:00:00") 316 | s.HourlyAt(0, 1) 317 | assert.Equal(t, s.Next.Minute, 0) 318 | s.now, _ = time.Parse("2006-01-02 15:04:05", "2022-10-05 15:03:00") 319 | s.HourlyAt(0, 1, 2, 3) 320 | assert.Equal(t, s.Next.Hour, 15) 321 | assert.Equal(t, s.Next.Minute, 3) 322 | } 323 | 324 | func TestScheduler_EveryOddHour(t *testing.T) { 325 | s := NewScheduler(context.Background(), time.UTC) 326 | s.now, _ = time.Parse("2006-01-02 15:04:05", "2022-10-05 15:00:00") 327 | s.EveryOddHour() 328 | assert.Equal(t, s.Next.Hour, 15) 329 | assert.Equal(t, s.Next.Minute, 0) 330 | assert.False(t, s.Next.Omit) 331 | s.now, _ = time.Parse("2006-01-02 15:04:05", "2022-10-05 00:00:00") 332 | s.EveryOddHour() 333 | assert.Equal(t, s.Next.Hour, 0) 334 | assert.Equal(t, s.Next.Minute, 0) 335 | assert.True(t, s.Next.Omit) 336 | s.now, _ = time.Parse("2006-01-02 15:04:05", "2022-10-05 02:00:00") 337 | s.EveryOddHour() 338 | assert.Equal(t, s.Next.Minute, 0) 339 | assert.True(t, s.Next.Omit) 340 | } 341 | 342 | func TestScheduler_EveryTwoHours(t *testing.T) { 343 | s := NewScheduler(context.Background(), time.UTC) 344 | s.now, _ = time.Parse("2006-01-02 15:04:05", "2022-10-05 15:00:00") 345 | s.EveryTwoHours() 346 | assert.Equal(t, s.Next.Minute, 0) 347 | assert.True(t, s.Next.Omit) 348 | s.now, _ = time.Parse("2006-01-02 15:04:05", "2022-10-05 00:00:00") 349 | s.EveryTwoHours() 350 | assert.Equal(t, s.Next.Hour, 0) 351 | assert.Equal(t, s.Next.Minute, 0) 352 | assert.False(t, s.Next.Omit) 353 | s.now, _ = time.Parse("2006-01-02 15:04:05", "2022-10-05 02:00:00") 354 | s.EveryTwoHours() 355 | assert.Equal(t, s.Next.Hour, 2) 356 | assert.Equal(t, s.Next.Minute, 0) 357 | assert.False(t, s.Next.Omit) 358 | } 359 | 360 | func TestScheduler_EveryThreeHours(t *testing.T) { 361 | s := NewScheduler(context.Background(), time.UTC) 362 | s.now, _ = time.Parse("2006-01-02 15:04:05", "2022-10-05 14:00:00") 363 | s.EveryThreeHours() 364 | assert.Equal(t, s.Next.Minute, 0) 365 | assert.True(t, s.Next.Omit) 366 | s.now, _ = time.Parse("2006-01-02 15:04:05", "2022-10-05 00:00:00") 367 | s.EveryThreeHours() 368 | assert.Equal(t, s.Next.Hour, 0) 369 | assert.Equal(t, s.Next.Minute, 0) 370 | assert.False(t, s.Next.Omit) 371 | s.now, _ = time.Parse("2006-01-02 15:04:05", "2022-10-05 03:00:00") 372 | s.EveryThreeHours() 373 | assert.Equal(t, s.Next.Hour, 3) 374 | assert.Equal(t, s.Next.Minute, 0) 375 | assert.False(t, s.Next.Omit) 376 | } 377 | 378 | func TestScheduler_EveryFourHours(t *testing.T) { 379 | s := NewScheduler(context.Background(), time.UTC) 380 | s.now, _ = time.Parse("2006-01-02 15:04:05", "2022-10-05 14:00:00") 381 | s.EveryFourHours() 382 | assert.Equal(t, s.Next.Minute, 0) 383 | assert.True(t, s.Next.Omit) 384 | s.now, _ = time.Parse("2006-01-02 15:04:05", "2022-10-05 00:00:00") 385 | s.EveryFourHours() 386 | assert.Equal(t, s.Next.Hour, 0) 387 | assert.Equal(t, s.Next.Minute, 0) 388 | assert.False(t, s.Next.Omit) 389 | s.now, _ = time.Parse("2006-01-02 15:04:05", "2022-10-05 04:00:00") 390 | s.EveryFourHours() 391 | assert.Equal(t, s.Next.Hour, 4) 392 | assert.Equal(t, s.Next.Minute, 0) 393 | assert.False(t, s.Next.Omit) 394 | } 395 | 396 | func TestScheduler_EveryFiveHours(t *testing.T) { 397 | s := NewScheduler(context.Background(), time.UTC) 398 | s.now, _ = time.Parse("2006-01-02 15:04:05", "2022-10-05 16:00:00") 399 | s.EveryFiveHours() 400 | assert.Equal(t, s.Next.Minute, 0) 401 | assert.True(t, s.Next.Omit) 402 | s.now, _ = time.Parse("2006-01-02 15:04:05", "2022-10-05 00:00:00") 403 | s.EveryFiveHours() 404 | assert.Equal(t, s.Next.Hour, 0) 405 | assert.Equal(t, s.Next.Minute, 0) 406 | assert.False(t, s.Next.Omit) 407 | s.now, _ = time.Parse("2006-01-02 15:04:05", "2022-10-05 05:00:00") 408 | s.EveryFiveHours() 409 | assert.Equal(t, s.Next.Hour, 5) 410 | assert.Equal(t, s.Next.Minute, 0) 411 | assert.False(t, s.Next.Omit) 412 | } 413 | 414 | func TestScheduler_EverySixHours(t *testing.T) { 415 | s := NewScheduler(context.Background(), time.UTC) 416 | s.now, _ = time.Parse("2006-01-02 15:04:05", "2022-10-05 17:00:00") 417 | s.EverySixHours() 418 | assert.Equal(t, s.Next.Minute, 0) 419 | assert.True(t, s.Next.Omit) 420 | s.now, _ = time.Parse("2006-01-02 15:04:05", "2022-10-05 00:00:00") 421 | s.EverySixHours() 422 | assert.Equal(t, s.Next.Hour, 0) 423 | assert.Equal(t, s.Next.Minute, 0) 424 | assert.False(t, s.Next.Omit) 425 | s.now, _ = time.Parse("2006-01-02 15:04:05", "2022-10-05 06:00:00") 426 | s.EverySixHours() 427 | assert.Equal(t, s.Next.Hour, 6) 428 | assert.Equal(t, s.Next.Minute, 0) 429 | assert.False(t, s.Next.Omit) 430 | } 431 | 432 | func TestScheduler_Daily(t *testing.T) { 433 | s := NewScheduler(context.Background(), time.UTC) 434 | s.now, _ = time.Parse("2006-01-02 15:04:05", "2022-10-05 17:00:00") 435 | s.Daily() 436 | assert.Equal(t, s.Next.Hour, 0) 437 | assert.Equal(t, s.Next.Minute, 0) 438 | assert.False(t, s.Next.Omit) 439 | } 440 | 441 | func TestScheduler_DailyAt(t *testing.T) { 442 | s := NewScheduler(context.Background(), time.UTC) 443 | s.now, _ = time.Parse("2006-01-02 15:04:05", "2022-10-05 17:01:00") 444 | s.DailyAt("00:30", "17:01") 445 | assert.Equal(t, s.Next.Hour, 17) 446 | assert.Equal(t, s.Next.Minute, 1) 447 | assert.False(t, s.Next.Omit) 448 | s.DailyAt("00:30", "16:01") 449 | assert.Equal(t, s.Next.Hour, 0) 450 | assert.Equal(t, s.Next.Minute, 0) 451 | assert.True(t, s.Next.Omit) 452 | } 453 | 454 | func TestScheduler_At(t *testing.T) { 455 | s := NewScheduler(context.Background(), time.UTC) 456 | s.now, _ = time.Parse("2006-01-02 15:04:05", "2022-10-05 17:01:00") 457 | s.At("00:30", "17:01") 458 | assert.Equal(t, s.Next.Hour, 17) 459 | assert.Equal(t, s.Next.Minute, 1) 460 | assert.False(t, s.Next.Omit) 461 | s.At("00:30", "16:01") 462 | assert.Equal(t, s.Next.Hour, 0) 463 | assert.Equal(t, s.Next.Minute, 0) 464 | assert.True(t, s.Next.Omit) 465 | } 466 | 467 | func TestScheduler_TwiceDaily(t *testing.T) { 468 | s := NewScheduler(context.Background(), time.UTC) 469 | s.now, _ = time.Parse("2006-01-02 15:04:05", "2022-10-05 17:00:00") 470 | s.TwiceDaily(1, 17) 471 | assert.Equal(t, s.Next.Hour, 17) 472 | assert.Equal(t, s.Next.Minute, 0) 473 | assert.False(t, s.Next.Omit) 474 | s.now, _ = time.Parse("2006-01-02 15:04:05", "2022-10-05 01:00:00") 475 | s.TwiceDaily(1, 17) 476 | assert.Equal(t, s.Next.Hour, 1) 477 | assert.Equal(t, s.Next.Minute, 0) 478 | assert.False(t, s.Next.Omit) 479 | s.now, _ = time.Parse("2006-01-02 15:04:05", "2022-10-05 03:00:00") 480 | s.TwiceDaily(1, 17) 481 | assert.Equal(t, s.Next.Hour, 0) 482 | assert.Equal(t, s.Next.Minute, 0) 483 | assert.True(t, s.Next.Omit) 484 | } 485 | 486 | func TestScheduler_TwiceDailyAt(t *testing.T) { 487 | s := NewScheduler(context.Background(), time.UTC) 488 | s.now, _ = time.Parse("2006-01-02 15:04:05", "2022-10-05 17:05:00") 489 | s.TwiceDailyAt(1, 17, 5) 490 | assert.Equal(t, s.Next.Hour, 17) 491 | assert.Equal(t, s.Next.Minute, 5) 492 | assert.False(t, s.Next.Omit) 493 | s.now, _ = time.Parse("2006-01-02 15:04:05", "2022-10-05 01:04:00") 494 | s.TwiceDailyAt(1, 17, 4) 495 | assert.Equal(t, s.Next.Hour, 1) 496 | assert.Equal(t, s.Next.Minute, 4) 497 | assert.False(t, s.Next.Omit) 498 | s.now, _ = time.Parse("2006-01-02 15:04:05", "2022-10-05 03:02:00") 499 | s.TwiceDailyAt(1, 17, 2) 500 | assert.Equal(t, s.Next.Hour, 0) 501 | assert.Equal(t, s.Next.Minute, 0) 502 | assert.True(t, s.Next.Omit) 503 | } 504 | 505 | func TestScheduler_Weekly(t *testing.T) { 506 | s := NewScheduler(context.Background(), time.UTC) 507 | s.now, _ = time.Parse("2006-01-02 15:04:05", "2022-10-05 17:00:00") 508 | s.Weekly() 509 | assert.Equal(t, s.Next.Day, 2) 510 | assert.Equal(t, s.Next.Hour, 0) 511 | assert.Equal(t, s.Next.Minute, 0) 512 | assert.False(t, s.Next.Omit) 513 | } 514 | 515 | func TestScheduler_WeeklyOn(t *testing.T) { 516 | s := NewScheduler(context.Background(), time.UTC) 517 | s.now, _ = time.Parse("2006-01-02 15:04:05", "2022-10-04 17:00:00") 518 | s.WeeklyOn(time.Tuesday, "16:10") 519 | assert.Equal(t, s.Next.Day, 4) 520 | assert.Equal(t, s.Next.Hour, 0) 521 | assert.Equal(t, s.Next.Minute, 0) 522 | assert.True(t, s.Next.Omit) 523 | s.WeeklyOn(time.Tuesday, "17:00") 524 | assert.Equal(t, s.Next.Day, 4) 525 | assert.Equal(t, s.Next.Hour, 17) 526 | assert.Equal(t, s.Next.Minute, 0) 527 | assert.False(t, s.Next.Omit) 528 | } 529 | 530 | func TestScheduler_Monthly(t *testing.T) { 531 | s := NewScheduler(context.Background(), time.UTC) 532 | s.now, _ = time.Parse("2006-01-02 15:04:05", "2022-10-01 00:00:00") 533 | s.Monthly() 534 | assert.Equal(t, s.Next.Month, 10) 535 | assert.Equal(t, s.Next.Day, 1) 536 | assert.Equal(t, s.Next.Hour, 0) 537 | assert.Equal(t, s.Next.Minute, 0) 538 | assert.False(t, s.Next.Omit) 539 | 540 | s.now, _ = time.Parse("2006-01-02 15:04:05", "2022-10-02 00:00:00") 541 | s.Monthly() 542 | assert.Equal(t, s.Next.Month, 10) 543 | assert.Equal(t, s.Next.Day, 1) 544 | assert.Equal(t, s.Next.Hour, 0) 545 | assert.Equal(t, s.Next.Minute, 0) 546 | assert.False(t, s.Next.Omit) 547 | } 548 | 549 | func TestScheduler_MonthlyOn(t *testing.T) { 550 | s := NewScheduler(context.Background(), time.UTC) 551 | s.now, _ = time.Parse("2006-01-02 15:04:05", "2022-10-02 03:00:00") 552 | s.MonthlyOn(2, "03:00") 553 | assert.Equal(t, s.Next.Month, 10) 554 | assert.Equal(t, s.Next.Day, 2) 555 | assert.Equal(t, s.Next.Hour, 3) 556 | assert.Equal(t, s.Next.Minute, 0) 557 | assert.False(t, s.Next.Omit) 558 | s.MonthlyOn(3, "00:00") 559 | assert.Equal(t, s.Next.Month, 10) 560 | assert.Equal(t, s.Next.Day, 0) 561 | assert.Equal(t, s.Next.Hour, 0) 562 | assert.Equal(t, s.Next.Minute, 0) 563 | assert.True(t, s.Next.Omit) 564 | } 565 | 566 | func TestScheduler_TwiceMonthly(t *testing.T) { 567 | s := NewScheduler(context.Background(), time.UTC) 568 | s.now, _ = time.Parse("2006-01-02 15:04:05", "2022-10-02 03:00:00") 569 | s.TwiceMonthly(2, 3, "03:00") 570 | assert.Equal(t, s.Next.Month, 10) 571 | assert.Equal(t, s.Next.Day, 2) 572 | assert.Equal(t, s.Next.Hour, 3) 573 | assert.Equal(t, s.Next.Minute, 0) 574 | assert.False(t, s.Next.Omit) 575 | s.now, _ = time.Parse("2006-01-02 15:04:05", "2022-10-03 03:00:00") 576 | s.TwiceMonthly(2, 3, "03:00") 577 | assert.Equal(t, s.Next.Month, 10) 578 | assert.Equal(t, s.Next.Day, 3) 579 | assert.Equal(t, s.Next.Hour, 3) 580 | assert.Equal(t, s.Next.Minute, 0) 581 | assert.False(t, s.Next.Omit) 582 | s.now, _ = time.Parse("2006-01-02 15:04:05", "2022-10-03 04:00:00") 583 | s.TwiceMonthly(2, 3, "03:00") 584 | assert.Equal(t, s.Next.Month, 10) 585 | assert.Equal(t, s.Next.Day, 3) 586 | assert.Equal(t, s.Next.Hour, 0) 587 | assert.Equal(t, s.Next.Minute, 0) 588 | assert.True(t, s.Next.Omit) 589 | } 590 | 591 | func TestScheduler_LastDayOfMonth(t *testing.T) { 592 | s := NewScheduler(context.Background(), time.UTC) 593 | s.now, _ = time.Parse("2006-01-02 15:04:05", "2022-10-02 03:00:00") 594 | s.LastDayOfMonth("03:00") 595 | assert.Equal(t, s.Next.Month, 10) 596 | assert.Equal(t, s.Next.Day, 31) 597 | assert.Equal(t, s.Next.Hour, 3) 598 | assert.Equal(t, s.Next.Minute, 0) 599 | assert.False(t, s.Next.Omit) 600 | 601 | s.now, _ = time.Parse("2006-01-02 15:04:05", "2022-10-02 03:00:00") 602 | s.LastDayOfMonth("02:00") 603 | assert.Equal(t, s.Next.Month, 10) 604 | assert.Equal(t, s.Next.Day, 31) 605 | assert.Equal(t, s.Next.Hour, 0) 606 | assert.Equal(t, s.Next.Minute, 0) 607 | assert.True(t, s.Next.Omit) 608 | } 609 | 610 | func TestScheduler_Quarterly(t *testing.T) { 611 | s := NewScheduler(context.Background(), time.UTC) 612 | s.now, _ = time.Parse("2006-01-02 15:04:05", "2022-10-10 03:00:00") 613 | s.Quarterly() 614 | assert.Equal(t, s.Next.Month, 10) 615 | assert.Equal(t, s.Next.Day, 1) 616 | assert.Equal(t, s.Next.Hour, 0) 617 | assert.Equal(t, s.Next.Minute, 0) 618 | assert.False(t, s.Next.Omit) 619 | } 620 | 621 | func TestScheduler_Yearly(t *testing.T) { 622 | s := NewScheduler(context.Background(), time.UTC) 623 | s.now, _ = time.Parse("2006-01-02 15:04:05", "2022-10-10 03:00:00") 624 | s.Yearly() 625 | assert.Equal(t, s.Next.Month, 1) 626 | assert.Equal(t, s.Next.Day, 1) 627 | assert.Equal(t, s.Next.Hour, 0) 628 | assert.Equal(t, s.Next.Minute, 0) 629 | assert.False(t, s.Next.Omit) 630 | } 631 | 632 | func TestScheduler_YearlyOn(t *testing.T) { 633 | s := NewScheduler(context.Background(), time.UTC) 634 | s.now, _ = time.Parse("2006-01-02 15:04:05", "2022-10-10 03:00:00") 635 | s.YearlyOn(10, 10, "03:00") 636 | assert.Equal(t, s.Next.Month, 10) 637 | assert.Equal(t, s.Next.Day, 10) 638 | assert.Equal(t, s.Next.Hour, 3) 639 | assert.Equal(t, s.Next.Minute, 0) 640 | assert.False(t, s.Next.Omit) 641 | s.now, _ = time.Parse("2006-01-02 15:04:05", "2022-10-10 03:00:00") 642 | s.YearlyOn(8, 9, "02:00") 643 | assert.Equal(t, s.Next.Month, 0) 644 | assert.Equal(t, s.Next.Day, 0) 645 | assert.Equal(t, s.Next.Hour, 0) 646 | assert.Equal(t, s.Next.Minute, 0) 647 | assert.True(t, s.Next.Omit) 648 | } 649 | 650 | func TestScheduler_Weekdays(t *testing.T) { 651 | s := NewScheduler(context.Background(), time.UTC) 652 | s.Weekdays() 653 | assert.Len(t, s.limit.DaysOfWeek, 5) 654 | assert.Contains(t, s.limit.DaysOfWeek, time.Monday) 655 | assert.Contains(t, s.limit.DaysOfWeek, time.Tuesday) 656 | assert.Contains(t, s.limit.DaysOfWeek, time.Wednesday) 657 | assert.Contains(t, s.limit.DaysOfWeek, time.Thursday) 658 | assert.Contains(t, s.limit.DaysOfWeek, time.Friday) 659 | assert.NotContains(t, s.limit.DaysOfWeek, time.Saturday) 660 | assert.NotContains(t, s.limit.DaysOfWeek, time.Sunday) 661 | } 662 | 663 | func TestScheduler_Weekends(t *testing.T) { 664 | s := NewScheduler(context.Background(), time.UTC) 665 | s.Weekends() 666 | assert.Len(t, s.limit.DaysOfWeek, 2) 667 | assert.Contains(t, s.limit.DaysOfWeek, time.Saturday) 668 | assert.Contains(t, s.limit.DaysOfWeek, time.Sunday) 669 | } 670 | 671 | func TestScheduler_Mondays(t *testing.T) { 672 | s := NewScheduler(context.Background(), time.UTC) 673 | s.Mondays() 674 | assert.Len(t, s.limit.DaysOfWeek, 1) 675 | assert.Contains(t, s.limit.DaysOfWeek, time.Monday) 676 | } 677 | 678 | func TestScheduler_Tuesdays(t *testing.T) { 679 | s := NewScheduler(context.Background(), time.UTC) 680 | s.Tuesdays() 681 | assert.Len(t, s.limit.DaysOfWeek, 1) 682 | assert.Contains(t, s.limit.DaysOfWeek, time.Tuesday) 683 | } 684 | 685 | func TestScheduler_Wednesdays(t *testing.T) { 686 | s := NewScheduler(context.Background(), time.UTC) 687 | s.Wednesdays() 688 | assert.Len(t, s.limit.DaysOfWeek, 1) 689 | assert.Contains(t, s.limit.DaysOfWeek, time.Wednesday) 690 | } 691 | 692 | func TestScheduler_Thursdays(t *testing.T) { 693 | s := NewScheduler(context.Background(), time.UTC) 694 | s.Thursdays() 695 | assert.Len(t, s.limit.DaysOfWeek, 1) 696 | assert.Contains(t, s.limit.DaysOfWeek, time.Thursday) 697 | } 698 | 699 | func TestScheduler_Fridays(t *testing.T) { 700 | s := NewScheduler(context.Background(), time.UTC) 701 | s.Fridays() 702 | assert.Len(t, s.limit.DaysOfWeek, 1) 703 | assert.Contains(t, s.limit.DaysOfWeek, time.Friday) 704 | } 705 | 706 | func TestScheduler_Saturdays(t *testing.T) { 707 | s := NewScheduler(context.Background(), time.UTC) 708 | s.Saturdays() 709 | assert.Len(t, s.limit.DaysOfWeek, 1) 710 | assert.Contains(t, s.limit.DaysOfWeek, time.Saturday) 711 | } 712 | 713 | func TestScheduler_Sundays(t *testing.T) { 714 | s := NewScheduler(context.Background(), time.UTC) 715 | s.Sundays() 716 | assert.Len(t, s.limit.DaysOfWeek, 1) 717 | assert.Contains(t, s.limit.DaysOfWeek, time.Sunday) 718 | } 719 | 720 | func TestScheduler_Days(t *testing.T) { 721 | s := NewScheduler(context.Background(), time.UTC) 722 | s.Days(time.Monday, time.Friday) 723 | assert.Len(t, s.limit.DaysOfWeek, 2) 724 | assert.Contains(t, s.limit.DaysOfWeek, time.Monday) 725 | assert.Contains(t, s.limit.DaysOfWeek, time.Friday) 726 | assert.NotContains(t, s.limit.DaysOfWeek, time.Wednesday) 727 | } 728 | 729 | func TestScheduler_Between(t *testing.T) { 730 | s := NewScheduler(context.Background(), time.UTC) 731 | s.Between("09:00", "15:00") 732 | assert.Equal(t, "09:00", s.limit.StartTime) 733 | assert.Equal(t, "15:00", s.limit.EndTime) 734 | assert.True(t, s.limit.IsBetween) 735 | } 736 | 737 | func TestScheduler_UnlessBetween(t *testing.T) { 738 | s := NewScheduler(context.Background(), time.UTC) 739 | s.UnlessBetween("09:00", "15:00") 740 | assert.Equal(t, "09:00", s.limit.StartTime) 741 | assert.Equal(t, "15:00", s.limit.EndTime) 742 | assert.False(t, s.limit.IsBetween) 743 | } 744 | 745 | func TestScheduler_When(t *testing.T) { 746 | s := NewScheduler(context.Background(), time.UTC) 747 | s.When(func(ctx context.Context) bool { 748 | return false 749 | }) 750 | assert.False(t, s.limit.When(s.ctx)) 751 | s.When(func(ctx context.Context) bool { 752 | return true 753 | }) 754 | assert.True(t, s.limit.When(s.ctx)) 755 | } 756 | 757 | func TestScheduler_Call(t *testing.T) { 758 | s := NewScheduler(context.Background(), time.UTC) 759 | s.SetLogger(nil).SetLogger(&DefaultLogger{}) 760 | dayTime := s.now.Format("15:04") 761 | ch := make(chan bool, 1) 762 | s.DailyAt(dayTime).When(func(ctx context.Context) bool { 763 | return true 764 | }).Call(NewDefaultTask(func(ctx context.Context) { 765 | ch <- true 766 | })) 767 | assert.True(t, <-ch) 768 | var mark bool 769 | s.DailyAt(dayTime).When(func(ctx context.Context) bool { 770 | return false 771 | }).CallFunc(func(ctx context.Context) { 772 | mark = true 773 | return 774 | }) 775 | assert.False(t, mark) 776 | s.YearlyOn(12, 31, dayTime).CallFunc(func(ctx context.Context) { 777 | mark = true 778 | return 779 | }) 780 | assert.False(t, mark) 781 | s.DailyAt(dayTime).When(func(ctx context.Context) bool { 782 | return true 783 | }).Call(NewDefaultTask(func(ctx context.Context) { 784 | mark = true 785 | panic("task panic") 786 | })) 787 | s.Start() 788 | assert.True(t, mark) 789 | } 790 | --------------------------------------------------------------------------------