├── .gitignore ├── LICENSE ├── NuGet.Config ├── README.md ├── crontab.sln ├── crontab.sln.DotSettings ├── sample ├── CrontabSample.ClassLibrary │ ├── CrontabSample.ClassLibrary.csproj │ └── Schedulers │ │ └── TestScheduler.cs └── CrontabSample.ConsoleApp │ ├── CrontabSample.ConsoleApp.csproj │ └── Program.cs ├── src └── Yisoft.Crontab │ ├── Constants.cs │ ├── CronAttribute.cs │ ├── CronStringFormat.cs │ ├── CrontabException.cs │ ├── CrontabFieldKind.cs │ ├── CrontabSchedule.cs │ ├── CrontabTask.cs │ ├── CrontabTaskExecutor.cs │ ├── CrontabTaskScaner.cs │ ├── CrontabTaskStatus.cs │ ├── Extensions │ └── DayOfWeekExtensions.cs │ ├── Filters │ ├── AnyFilter.cs │ ├── BlankDayOfMonthOrWeekFilter.cs │ ├── LastDayOfMonthFilter.cs │ ├── LastDayOfWeekInMonthFilter.cs │ ├── LastWeekdayOfMonthFilter.cs │ ├── NearestWeekdayFilter.cs │ ├── RangeFilter.cs │ ├── SpecificDayOfWeekInMonthFilter.cs │ ├── SpecificFilter.cs │ ├── SpecificYearFilter.cs │ └── StepFilter.cs │ ├── ICronFilter.cs │ ├── ITimeFilter.cs │ ├── Properties │ └── AssemblyInfo.cs │ └── Yisoft.Crontab.csproj └── test └── Yisoft.Crontab.UnitTests ├── ConstantTests.cs ├── CronInstanceTests.cs ├── Extensions └── AssertExtensions.cs ├── FilterTest.cs └── Yisoft.Crontab.UnitTests.csproj /.gitignore: -------------------------------------------------------------------------------- 1 | .vs/ 2 | bin/ 3 | obj/ 4 | 5 | *.user 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /NuGet.Config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Yisoft.Crontab # 2 | 3 | [![Build status](https://ci.appveyor.com/api/projects/status/mgms413qy8s0y181?svg=true)](https://ci.appveyor.com/project/yiteam/crontab) 4 | [![NuGet](https://img.shields.io/nuget/v/Yisoft.Crontab.svg?style=flat&label=nuget)](https://www.nuget.org/packages/Yisoft.Crontab/) 5 | 6 | cron expression parser and executor for dotnet core. 7 | 8 | > this project based on [NCrontab-Advanced](https://github.com/jcoutch/NCrontab-Advanced). 9 | 10 | If you have any problems, make sure to file an issue here on Github. 11 | 12 | # Crontab task executor # 13 | 14 | ## CronAttribute ## 15 | This library support annotation method only. if you want create a crontab task, simply add the `CronAttribute` on some method. 16 | 17 | We also provide some advanced features that you can get by adding some parameters to the method to get the information associated with the current task. 18 | 19 | Here are some [samples](sample): 20 | ```csharp 21 | public class TestScheduler 22 | { 23 | [Cron("18/1 * * * * ? *", CronStringFormat.WithSecondsAndYears)] 24 | public static void Task1() 25 | { 26 | Debug.WriteLine($"Task..............1111_{DateTime.Now}"); 27 | } 28 | 29 | [Cron("28/1 * * * * ? *", CronStringFormat.WithSecondsAndYears)] 30 | public static void Task2(DateTime time, CrontabTask task) 31 | { 32 | Debug.WriteLine($"Task..............2222_{time}_{task.Method.Name}"); 33 | } 34 | 35 | [Cron("28/1 * * * * ? *", CronStringFormat.WithSecondsAndYears)] 36 | public static void Task3(DateTime time, CrontabTask task) 37 | { 38 | Debug.WriteLine($"Task..............3333_{time}_{task.Method.Name}"); 39 | } 40 | 41 | [Cron("1-8 * * * * ? *", CronStringFormat.WithSecondsAndYears)] 42 | [Cron("48/1 * * * * ? *", CronStringFormat.WithSecondsAndYears)] 43 | public static void Task4(DateTime time, CrontabTask task, CrontabTaskExecutor taskExecutor) 44 | { 45 | Debug.WriteLine($"Task..............Cron_{time}_{task.Method.Name}_{taskExecutor.Tasks.Count}"); 46 | } 47 | 48 | // this task will begin execution after 100 seconds of startup 49 | [Cron("0/1 * * * * *", 100, CronStringFormat.WithSeconds)] 50 | public static void DeferTask1() 51 | { 52 | Debug.WriteLine($"Task..............5555_{DateTime.Now}"); 53 | } 54 | } 55 | ``` 56 | 57 | ## Constructor ## 58 | 59 | The `CrontabTaskExecutor` class contains a constructor that with one parameter, the parameter is `Func typeInstanceCreator`. `typeInstanceCreator` used to create an object instance where the task method is definded. this will be very useful! 60 | 61 | In console application, you can initialize an instance of an object with the `new` keyword or reflection. and in web application, you can use `DI`(`dependency injection`) directly. 62 | 63 | For better use this library in your Web application, see the [Yisoft.AspNetCore.Crontab](https://github.com/yisoft-aspnet/crontab) project. 64 | 65 | # Support for the following cron expressions # 66 | 67 | ``` 68 | Field name | Allowed values | Allowed special characters 69 | ------------------------------------------------------------ 70 | Minutes | 0-59 | * , - / 71 | Hours | 0-23 | * , - / 72 | Day of month | 1-31 | * , - / ? L W 73 | Month | 1-12 or JAN-DEC | * , - / 74 | Day of week | 0-6 or SUN-SAT | * , - / ? L # 75 | Year | 0001–9999 | * , - / 76 | ``` 77 | 78 | ## Related community projects 79 | * [Yisoft.AspNetCore.Crontab](https://github.com/yisoft-aspnet/crontab) 80 | 81 | # License 82 | Released under the [Apache License](License.txt). -------------------------------------------------------------------------------- /crontab.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26403.3 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{B7A6F3B1-5A1F-481F-AF8D-E27BF73D5A49}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{506B9CDF-EED7-4D70-9B61-F07DDDB55D2C}" 9 | ProjectSection(SolutionItems) = preProject 10 | .gitignore = .gitignore 11 | LICENSE = LICENSE 12 | NuGet.Config = NuGet.Config 13 | README.md = README.md 14 | EndProjectSection 15 | EndProject 16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yisoft.Crontab", "src\Yisoft.Crontab\Yisoft.Crontab.csproj", "{476A1529-5AF8-4D12-BBDF-396BDD2D0651}" 17 | EndProject 18 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{C698C263-3412-4F65-8CF9-98CF9B31F0F1}" 19 | EndProject 20 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yisoft.Crontab.UnitTests", "test\Yisoft.Crontab.UnitTests\Yisoft.Crontab.UnitTests.csproj", "{3BEA7ED9-CB28-4428-99DE-FE31F7A4383F}" 21 | EndProject 22 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "sample", "sample", "{E031B909-90C6-4C57-9090-DC23571A6E9B}" 23 | EndProject 24 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrontabSample.ConsoleApp", "sample\CrontabSample.ConsoleApp\CrontabSample.ConsoleApp.csproj", "{276C9513-E53C-49B2-B6C5-FFD5DEEFB3FC}" 25 | EndProject 26 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrontabSample.ClassLibrary", "sample\CrontabSample.ClassLibrary\CrontabSample.ClassLibrary.csproj", "{45D97113-6306-40B9-8E24-B576F87D9C41}" 27 | EndProject 28 | Global 29 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 30 | Debug|Any CPU = Debug|Any CPU 31 | Release|Any CPU = Release|Any CPU 32 | EndGlobalSection 33 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 34 | {476A1529-5AF8-4D12-BBDF-396BDD2D0651}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {476A1529-5AF8-4D12-BBDF-396BDD2D0651}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {476A1529-5AF8-4D12-BBDF-396BDD2D0651}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {476A1529-5AF8-4D12-BBDF-396BDD2D0651}.Release|Any CPU.Build.0 = Release|Any CPU 38 | {3BEA7ED9-CB28-4428-99DE-FE31F7A4383F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {3BEA7ED9-CB28-4428-99DE-FE31F7A4383F}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {3BEA7ED9-CB28-4428-99DE-FE31F7A4383F}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {3BEA7ED9-CB28-4428-99DE-FE31F7A4383F}.Release|Any CPU.Build.0 = Release|Any CPU 42 | {276C9513-E53C-49B2-B6C5-FFD5DEEFB3FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 43 | {276C9513-E53C-49B2-B6C5-FFD5DEEFB3FC}.Debug|Any CPU.Build.0 = Debug|Any CPU 44 | {276C9513-E53C-49B2-B6C5-FFD5DEEFB3FC}.Release|Any CPU.ActiveCfg = Release|Any CPU 45 | {276C9513-E53C-49B2-B6C5-FFD5DEEFB3FC}.Release|Any CPU.Build.0 = Release|Any CPU 46 | {45D97113-6306-40B9-8E24-B576F87D9C41}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 47 | {45D97113-6306-40B9-8E24-B576F87D9C41}.Debug|Any CPU.Build.0 = Debug|Any CPU 48 | {45D97113-6306-40B9-8E24-B576F87D9C41}.Release|Any CPU.ActiveCfg = Release|Any CPU 49 | {45D97113-6306-40B9-8E24-B576F87D9C41}.Release|Any CPU.Build.0 = Release|Any CPU 50 | EndGlobalSection 51 | GlobalSection(SolutionProperties) = preSolution 52 | HideSolutionNode = FALSE 53 | EndGlobalSection 54 | GlobalSection(NestedProjects) = preSolution 55 | {476A1529-5AF8-4D12-BBDF-396BDD2D0651} = {B7A6F3B1-5A1F-481F-AF8D-E27BF73D5A49} 56 | {3BEA7ED9-CB28-4428-99DE-FE31F7A4383F} = {C698C263-3412-4F65-8CF9-98CF9B31F0F1} 57 | {276C9513-E53C-49B2-B6C5-FFD5DEEFB3FC} = {E031B909-90C6-4C57-9090-DC23571A6E9B} 58 | {45D97113-6306-40B9-8E24-B576F87D9C41} = {E031B909-90C6-4C57-9090-DC23571A6E9B} 59 | EndGlobalSection 60 | GlobalSection(ExtensibilityGlobals) = postSolution 61 | RESX_AutoCreateNewLanguageFiles = True 62 | RESX_ResXSortingComparison = InvariantCultureIgnoreCase 63 | RESX_SortFileContentOnSave = True 64 | EndGlobalSection 65 | EndGlobal 66 | -------------------------------------------------------------------------------- /crontab.sln.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | False 3 | False 4 | True 5 | True 6 | True 7 | True 8 | 20 9 | True 10 | True 11 | False 12 | AE795BEC-867B-4658-BAD3-D853C1A850DB/d:wwwroot/d:jscripts/d:lib 13 | AE795BEC-867B-4658-BAD3-D853C1A850DB/d:wwwroot/d:jscripts/d:local 14 | True 15 | DO_NOT_SHOW 16 | HINT 17 | True 18 | <?xml version="1.0" encoding="utf-16"?><Profile name="yisoft"><CSReorderTypeMembers>True</CSReorderTypeMembers><CSUpdateFileHeader>True</CSUpdateFileHeader><CSCodeStyleAttributes ArrangeTypeAccessModifier="True" ArrangeTypeMemberAccessModifier="True" SortModifiers="True" RemoveRedundantParentheses="False" AddMissingParentheses="True" ArrangeAttributes="True" /><CSEnforceVarKeywordUsageSettings>True</CSEnforceVarKeywordUsageSettings><RemoveCodeRedundancies>True</RemoveCodeRedundancies><CSUseAutoProperty>True</CSUseAutoProperty><CSMakeFieldReadonly>True</CSMakeFieldReadonly><CSMakeAutoPropertyGetOnly>True</CSMakeAutoPropertyGetOnly><CSArrangeQualifiers>True</CSArrangeQualifiers><CSFixBuiltinTypeReferences>True</CSFixBuiltinTypeReferences><CSShortenReferences>True</CSShortenReferences><CSReformatCode>True</CSReformatCode><AspOptimizeRegisterDirectives>True</AspOptimizeRegisterDirectives><FormatAttributeQuoteDescriptor>True</FormatAttributeQuoteDescriptor><HtmlReformatCode>True</HtmlReformatCode><JsReformatCode>True</JsReformatCode><StringToTemplatesDescriptor>True</StringToTemplatesDescriptor><VariablesToInnerScopesDescriptor>True</VariablesToInnerScopesDescriptor><CorrectVariableKindsDescriptor>True</CorrectVariableKindsDescriptor><JsInsertSemicolon>True</JsInsertSemicolon><RemoveRedundantQualifiersTs>True</RemoveRedundantQualifiersTs><OptimizeImportsTs>True</OptimizeImportsTs><OptimizeReferenceCommentsTs>True</OptimizeReferenceCommentsTs><PublicModifierStyleTs>True</PublicModifierStyleTs><ExplicitAnyTs>True</ExplicitAnyTs><TypeAnnotationStyleTs>True</TypeAnnotationStyleTs><RelativePathStyleTs>True</RelativePathStyleTs><AsInsteadOfCastTs>True</AsInsteadOfCastTs><XMLReformatCode>True</XMLReformatCode><CppReformatCode>True</CppReformatCode><CssReformatCode>True</CssReformatCode><CssAlphabetizeProperties>True</CssAlphabetizeProperties><VBReformatCode>True</VBReformatCode><VBShortenReferences>True</VBShortenReferences><VBOptimizeImports>True</VBOptimizeImports><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings></Profile> 19 | lcsoft 20 | ALIGN_ALL 21 | NEXT_LINE 22 | TOGETHER_SAME_LINE 23 | ON_SINGLE_LINE 24 | ON_SINGLE_LINE 25 | True 26 | ON_SINGLE_LINE 27 | True 28 | True 29 | ALWAYS_ADD 30 | ONLY_FOR_MULTILINE 31 | ONLY_FOR_MULTILINE 32 | ONLY_FOR_MULTILINE 33 | ALWAYS_ADD 34 | ALWAYS_ADD 35 | True 36 | False 37 | True 38 | ON_SINGLE_LINE 39 | True 40 | CHOP_IF_LONG 41 | 160 42 | html,head,body,thead,tbody,tfoot 43 | ALIGN_ALL 44 | True 45 | 160 46 | 160 47 | 160 48 | 160 49 | 160 50 | False 51 | ) * 52 | ( /( * ) ( ( ` 53 | )\()) ( ` ) /( ( )\ )\))( 54 | ((_)\ )\ ( )(_)))\ ((((_)( ((_)()\ 55 | __ ((_)((_) (_(_())((_) )\ _ )\ (_()((_) 56 | \ \ / / (_) |_ _|| __|(_)_\(_)| \/ | 57 | \ V / | | _ | | | _| / _ \ | |\/| | 58 | |_| |_|(_)|_| |___|/_/ \_\ |_| |_| 59 | 60 | This file is subject to the terms and conditions defined in 61 | file 'License.txt', which is part of this source code package. 62 | 63 | Copyright © Yi.TEAM. All rights reserved. 64 | ------------------------------------------------------------------------------- 65 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 66 | <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> 67 | <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> 68 | <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> 69 | <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> 70 | <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> 71 | <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> 72 | <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> 73 | <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> 74 | <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> 75 | <Policy Inspect="True" Prefix="set_" Suffix="" Style="aa_bb" /> 76 | <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> 77 | <Policy Inspect="True" Prefix="" Suffix="_" Style="aa_bb" /> 78 | <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> 79 | <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> 80 | <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> 81 | <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> 82 | <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> 83 | AB 84 | CRM 85 | GDI 86 | HMAC 87 | HMACMD 88 | HMACSHA 89 | IE 90 | IO 91 | IP 92 | LA 93 | MD 94 | SEM 95 | SHA 96 | UU 97 | XXTEA 98 | <Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB"><ExtraRule Prefix="" Suffix="" Style="AaBb" /></Policy> 99 | <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> 100 | <Policy Inspect="True" Prefix="_" Suffix="" Style="AA_BB" /> 101 | <Policy Inspect="True" Prefix="_" Suffix="" Style="AaBb" /> 102 | <Policy><Descriptor Staticness="Static, Instance" AccessRightKinds="Private" Description="Methods, properties and events"><ElementKinds><Kind Name="METHOD" /><Kind Name="PROPERTY" /><Kind Name="EVENT" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="_" Suffix="" Style="AaBb" /></Policy> 103 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 104 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 105 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 106 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 107 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 108 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 109 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 110 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 111 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 112 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 113 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 114 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 115 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 116 | <Policy Inspect="True" Prefix="I" Suffix="" Style="AaBb" /> 117 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 118 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 119 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 120 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 121 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 122 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 123 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 124 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 125 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 126 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 127 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 128 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 129 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 130 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 131 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 132 | <Policy Inspect="True" Prefix="T" Suffix="" Style="AaBb" /> 133 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 134 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 135 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 136 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 137 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 138 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 139 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 140 | True 141 | False 142 | True 143 | True 144 | False 145 | 7 146 | TEMP_FOLDER 147 | True 148 | True 149 | True 150 | True 151 | True 152 | True 153 | True 154 | True 155 | True 156 | 06/06/2016 16:21:33 157 | anonymous 158 | True 159 | True 160 | [-56.5,-3.5](1167,879) 161 | CodeCleanup 162 | -739,-185 163 | True 164 | 300 165 | True -------------------------------------------------------------------------------- /sample/CrontabSample.ClassLibrary/CrontabSample.ClassLibrary.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard1.6;net46 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /sample/CrontabSample.ClassLibrary/Schedulers/TestScheduler.cs: -------------------------------------------------------------------------------- 1 | // ) * 2 | // ( /( * ) ( ( ` 3 | // )\()) ( ` ) /( ( )\ )\))( 4 | // ((_)\ )\ ( )(_)))\ ((((_)( ((_)()\ 5 | // __ ((_)((_) (_(_())((_) )\ _ )\ (_()((_) 6 | // \ \ / / (_) |_ _|| __|(_)_\(_)| \/ | 7 | // \ V / | | _ | | | _| / _ \ | |\/| | 8 | // |_| |_|(_)|_| |___|/_/ \_\ |_| |_| 9 | // 10 | // This file is subject to the terms and conditions defined in 11 | // file 'License.txt', which is part of this source code package. 12 | // 13 | // Copyright © Yi.TEAM. All rights reserved. 14 | // ------------------------------------------------------------------------------- 15 | 16 | using System; 17 | using System.Diagnostics; 18 | using Yisoft.Crontab; 19 | 20 | namespace CrontabSample.ClassLibrary.Schedulers 21 | { 22 | public class TestScheduler 23 | { 24 | [Cron("18/1 * * * * ? *", format: CronStringFormat.WithSecondsAndYears)] 25 | public static void Task1() 26 | { 27 | Debug.WriteLine($"Task..............1111_{DateTime.Now}"); 28 | } 29 | 30 | [Cron("28/1 * * * * ? *", format: CronStringFormat.WithSecondsAndYears)] 31 | public static void Task2(DateTime time, CrontabTask task) 32 | { 33 | Debug.WriteLine($"Task..............2222_{time}_{task.Method.Name}"); 34 | } 35 | 36 | [Cron("28/1 * * * * ? *", format: CronStringFormat.WithSecondsAndYears)] 37 | public static void Task3(DateTime time, CrontabTask task) 38 | { 39 | Debug.WriteLine($"Task..............3333_{time}_{task.Method.Name}"); 40 | } 41 | 42 | [Cron("1-8 * * * * ? *", format: CronStringFormat.WithSecondsAndYears)] 43 | [Cron("48/1 * * * * ? *", format: CronStringFormat.WithSecondsAndYears)] 44 | public static void Task4(DateTime time, CrontabTask task, CrontabTaskExecutor taskExecutor) 45 | { 46 | Debug.WriteLine($"Task..............Cron_{time}_{task.Method.Name}_{taskExecutor.Tasks.Count}"); 47 | } 48 | 49 | [Cron("0/1 * * * * *", 100, CronStringFormat.WithSeconds)] 50 | public static void DeferTask1() 51 | { 52 | Debug.WriteLine($"Task..............5555_{DateTime.Now}"); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /sample/CrontabSample.ConsoleApp/CrontabSample.ConsoleApp.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | netcoreapp1.1;net46 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /sample/CrontabSample.ConsoleApp/Program.cs: -------------------------------------------------------------------------------- 1 | // ) * 2 | // ( /( * ) ( ( ` 3 | // )\()) ( ` ) /( ( )\ )\))( 4 | // ((_)\ )\ ( )(_)))\ ((((_)( ((_)()\ 5 | // __ ((_)((_) (_(_())((_) )\ _ )\ (_()((_) 6 | // \ \ / / (_) |_ _|| __|(_)_\(_)| \/ | 7 | // \ V / | | _ | | | _| / _ \ | |\/| | 8 | // |_| |_|(_)|_| |___|/_/ \_\ |_| |_| 9 | // 10 | // This file is subject to the terms and conditions defined in 11 | // file 'License.txt', which is part of this source code package. 12 | // 13 | // Copyright © Yi.TEAM. All rights reserved. 14 | // ------------------------------------------------------------------------------- 15 | 16 | using System; 17 | using CrontabSample.ClassLibrary.Schedulers; 18 | using Yisoft.Crontab; 19 | 20 | namespace CrontabSample.ConsoleApp 21 | { 22 | internal class Program 23 | { 24 | protected static void Main(string[] args) 25 | { 26 | var taskScanner = new CrontabTaskScaner(); 27 | var tasks = taskScanner.ScanTasks(); 28 | 29 | if (tasks != null) 30 | { 31 | foreach (var task in tasks) 32 | { 33 | Console.WriteLine($"{task.ClassType}, {task.Method.Name}, {task.Cron.Expression}, {task.Cron.Format}"); 34 | } 35 | 36 | Console.WriteLine($"{tasks.Count}"); 37 | } 38 | 39 | Console.ReadKey(); 40 | 41 | var executor = new CrontabTaskExecutor(m => 42 | { 43 | var classType = m.DeclaringType; 44 | 45 | return classType == typeof(TestScheduler) ? new TestScheduler() : null; 46 | }); 47 | 48 | executor.AddTasks(tasks); 49 | 50 | executor.Run(); 51 | 52 | while (true) 53 | { 54 | // exit when press 'Q' 55 | if (Console.ReadKey().Key == ConsoleKey.Q) break; 56 | 57 | executor.Stop(); 58 | 59 | Console.ReadKey(); 60 | 61 | executor.Run(); 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Yisoft.Crontab/Constants.cs: -------------------------------------------------------------------------------- 1 | // ) * 2 | // ( /( * ) ( ( ` 3 | // )\()) ( ` ) /( ( )\ )\))( 4 | // ((_)\ )\ ( )(_)))\ ((((_)( ((_)()\ 5 | // __ ((_)((_) (_(_())((_) )\ _ )\ (_()((_) 6 | // \ \ / / (_) |_ _|| __|(_)_\(_)| \/ | 7 | // \ V / | | _ | | | _| / _ \ | |\/| | 8 | // |_| |_|(_)|_| |___|/_/ \_\ |_| |_| 9 | // 10 | // This file is subject to the terms and conditions defined in 11 | // file 'License.txt', which is part of this source code package. 12 | // 13 | // Copyright © Yi.TEAM. All rights reserved. 14 | // ------------------------------------------------------------------------------- 15 | 16 | using System; 17 | using System.Collections.Generic; 18 | 19 | namespace Yisoft.Crontab 20 | { 21 | public static class Constants 22 | { 23 | public static readonly Dictionary MaximumDateTimeValues = new Dictionary 24 | { 25 | {CrontabFieldKind.Second, 60}, 26 | {CrontabFieldKind.Minute, 60}, 27 | {CrontabFieldKind.Hour, 24}, 28 | {CrontabFieldKind.DayOfWeek, 6}, 29 | {CrontabFieldKind.Day, 31}, 30 | {CrontabFieldKind.Month, 12}, 31 | {CrontabFieldKind.Year, 9999} 32 | }; 33 | 34 | public static readonly Dictionary ExpectedFieldCounts = new Dictionary 35 | { 36 | {CronStringFormat.Default, 5}, 37 | {CronStringFormat.WithYears, 6}, 38 | {CronStringFormat.WithSeconds, 6}, 39 | {CronStringFormat.WithSecondsAndYears, 7} 40 | }; 41 | 42 | public static readonly Dictionary CronDays = new Dictionary 43 | { 44 | {DayOfWeek.Sunday, 0}, 45 | {DayOfWeek.Monday, 1}, 46 | {DayOfWeek.Tuesday, 2}, 47 | {DayOfWeek.Wednesday, 3}, 48 | {DayOfWeek.Thursday, 4}, 49 | {DayOfWeek.Friday, 5}, 50 | {DayOfWeek.Saturday, 6} 51 | }; 52 | 53 | public static readonly Dictionary Days = new Dictionary 54 | { 55 | {"SUN", 0}, 56 | {"MON", 1}, 57 | {"TUE", 2}, 58 | {"WED", 3}, 59 | {"THU", 4}, 60 | {"FRI", 5}, 61 | {"SAT", 6} 62 | }; 63 | 64 | public static readonly Dictionary Months = new Dictionary 65 | { 66 | {"JAN", 1}, 67 | {"FEB", 2}, 68 | {"MAR", 3}, 69 | {"APR", 4}, 70 | {"MAY", 5}, 71 | {"JUN", 6}, 72 | {"JUL", 7}, 73 | {"AUG", 8}, 74 | {"SEP", 9}, 75 | {"OCT", 10}, 76 | {"NOV", 11}, 77 | {"DEC", 12} 78 | }; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/Yisoft.Crontab/CronAttribute.cs: -------------------------------------------------------------------------------- 1 | // ) * 2 | // ( /( * ) ( ( ` 3 | // )\()) ( ` ) /( ( )\ )\))( 4 | // ((_)\ )\ ( )(_)))\ ((((_)( ((_)()\ 5 | // __ ((_)((_) (_(_())((_) )\ _ )\ (_()((_) 6 | // \ \ / / (_) |_ _|| __|(_)_\(_)| \/ | 7 | // \ V / | | _ | | | _| / _ \ | |\/| | 8 | // |_| |_|(_)|_| |___|/_/ \_\ |_| |_| 9 | // 10 | // This file is subject to the terms and conditions defined in 11 | // file 'License.txt', which is part of this source code package. 12 | // 13 | // Copyright © Yi.TEAM. All rights reserved. 14 | // ------------------------------------------------------------------------------- 15 | 16 | using System; 17 | 18 | namespace Yisoft.Crontab 19 | { 20 | [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] 21 | public class CronAttribute : Attribute 22 | { 23 | public CronAttribute(string expression, int defer = 0, CronStringFormat format = CronStringFormat.Default) 24 | { 25 | Expression = expression ?? throw new ArgumentNullException(nameof(expression)); 26 | Defer = defer; 27 | Format = format; 28 | } 29 | 30 | public string Expression { get; } 31 | 32 | public CronStringFormat Format { get; } 33 | 34 | public int Defer { get; } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Yisoft.Crontab/CronStringFormat.cs: -------------------------------------------------------------------------------- 1 | // ) * 2 | // ( /( * ) ( ( ` 3 | // )\()) ( ` ) /( ( )\ )\))( 4 | // ((_)\ )\ ( )(_)))\ ((((_)( ((_)()\ 5 | // __ ((_)((_) (_(_())((_) )\ _ )\ (_()((_) 6 | // \ \ / / (_) |_ _|| __|(_)_\(_)| \/ | 7 | // \ V / | | _ | | | _| / _ \ | |\/| | 8 | // |_| |_|(_)|_| |___|/_/ \_\ |_| |_| 9 | // 10 | // This file is subject to the terms and conditions defined in 11 | // file 'License.txt', which is part of this source code package. 12 | // 13 | // Copyright © Yi.TEAM. All rights reserved. 14 | // ------------------------------------------------------------------------------- 15 | 16 | namespace Yisoft.Crontab 17 | { 18 | /// 19 | /// The cron string format to use during parsing 20 | /// 21 | public enum CronStringFormat 22 | { 23 | /// 24 | /// Defined as "MINUTES HOURS DAYS MONTHS DAYS-OF-WEEK" 25 | /// 26 | Default = 0, 27 | 28 | /// 29 | /// Defined as "MINUTES HOURS DAYS MONTHS DAYS-OF-WEEK YEARS" 30 | /// 31 | WithYears = 1, 32 | 33 | /// 34 | /// Defined as "SECONDS MINUTES HOURS DAYS MONTHS DAYS-OF-WEEK" 35 | /// 36 | WithSeconds = 2, 37 | 38 | /// 39 | /// Defined as "SECONDS MINUTES HOURS DAYS MONTHS DAYS-OF-WEEK YEARS" 40 | /// 41 | WithSecondsAndYears = 3 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Yisoft.Crontab/CrontabException.cs: -------------------------------------------------------------------------------- 1 | // ) * 2 | // ( /( * ) ( ( ` 3 | // )\()) ( ` ) /( ( )\ )\))( 4 | // ((_)\ )\ ( )(_)))\ ((((_)( ((_)()\ 5 | // __ ((_)((_) (_(_())((_) )\ _ )\ (_()((_) 6 | // \ \ / / (_) |_ _|| __|(_)_\(_)| \/ | 7 | // \ V / | | _ | | | _| / _ \ | |\/| | 8 | // |_| |_|(_)|_| |___|/_/ \_\ |_| |_| 9 | // 10 | // This file is subject to the terms and conditions defined in 11 | // file 'License.txt', which is part of this source code package. 12 | // 13 | // Copyright © Yi.TEAM. All rights reserved. 14 | // ------------------------------------------------------------------------------- 15 | 16 | using System; 17 | 18 | namespace Yisoft.Crontab 19 | { 20 | public class CrontabException : Exception 21 | { 22 | public CrontabException() { } 23 | 24 | public CrontabException(string message) : base(message) { } 25 | 26 | public CrontabException(string message, Exception innerException) : base(message, innerException) { } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Yisoft.Crontab/CrontabFieldKind.cs: -------------------------------------------------------------------------------- 1 | // ) * 2 | // ( /( * ) ( ( ` 3 | // )\()) ( ` ) /( ( )\ )\))( 4 | // ((_)\ )\ ( )(_)))\ ((((_)( ((_)()\ 5 | // __ ((_)((_) (_(_())((_) )\ _ )\ (_()((_) 6 | // \ \ / / (_) |_ _|| __|(_)_\(_)| \/ | 7 | // \ V / | | _ | | | _| / _ \ | |\/| | 8 | // |_| |_|(_)|_| |___|/_/ \_\ |_| |_| 9 | // 10 | // This file is subject to the terms and conditions defined in 11 | // file 'License.txt', which is part of this source code package. 12 | // 13 | // Copyright © Yi.TEAM. All rights reserved. 14 | // ------------------------------------------------------------------------------- 15 | 16 | namespace Yisoft.Crontab 17 | { 18 | public enum CrontabFieldKind 19 | { 20 | Second = 0, // Keep in order of appearance in expression 21 | Minute = 1, 22 | Hour = 2, 23 | Day = 3, 24 | Month = 4, 25 | DayOfWeek = 5, 26 | Year = 6 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Yisoft.Crontab/CrontabSchedule.cs: -------------------------------------------------------------------------------- 1 | // ) * 2 | // ( /( * ) ( ( ` 3 | // )\()) ( ` ) /( ( )\ )\))( 4 | // ((_)\ )\ ( )(_)))\ ((((_)( ((_)()\ 5 | // __ ((_)((_) (_(_())((_) )\ _ )\ (_()((_) 6 | // \ \ / / (_) |_ _|| __|(_)_\(_)| \/ | 7 | // \ V / | | _ | | | _| / _ \ | |\/| | 8 | // |_| |_|(_)|_| |___|/_/ \_\ |_| |_| 9 | // 10 | // This file is subject to the terms and conditions defined in 11 | // file 'License.txt', which is part of this source code package. 12 | // 13 | // Copyright © Yi.TEAM. All rights reserved. 14 | // ------------------------------------------------------------------------------- 15 | 16 | using System; 17 | using System.Collections.Generic; 18 | using System.Linq; 19 | using Yisoft.Crontab.Filters; 20 | 21 | namespace Yisoft.Crontab 22 | { 23 | public class CrontabSchedule 24 | { 25 | // In the event a developer creates their own instance 26 | public CrontabSchedule() 27 | { 28 | Filters = new Dictionary>(); 29 | Format = CronStringFormat.Default; 30 | } 31 | 32 | public Dictionary> Filters { get; set; } 33 | 34 | public CronStringFormat Format { get; set; } 35 | 36 | public override string ToString() 37 | { 38 | var paramList = new List(); 39 | 40 | if (Format == CronStringFormat.WithSeconds || Format == CronStringFormat.WithSecondsAndYears) _JoinFilters(paramList, CrontabFieldKind.Second); 41 | 42 | _JoinFilters(paramList, CrontabFieldKind.Minute); 43 | _JoinFilters(paramList, CrontabFieldKind.Hour); 44 | _JoinFilters(paramList, CrontabFieldKind.Day); 45 | _JoinFilters(paramList, CrontabFieldKind.Month); 46 | _JoinFilters(paramList, CrontabFieldKind.DayOfWeek); 47 | 48 | if (Format == CronStringFormat.WithYears || Format == CronStringFormat.WithSecondsAndYears) _JoinFilters(paramList, CrontabFieldKind.Year); 49 | 50 | return string.Join(" ", paramList.ToArray()); 51 | } 52 | 53 | public DateTime GetNextOccurrence(DateTime baseValue) { return GetNextOccurrence(baseValue, DateTime.MaxValue); } 54 | 55 | public DateTime GetNextOccurrence(DateTime baseValue, DateTime endValue) { return _InternalGetNextOccurence(baseValue, endValue); } 56 | 57 | public IEnumerable GetNextOccurrences(DateTime baseTime, DateTime endTime) 58 | { 59 | for (var occurrence = GetNextOccurrence(baseTime, endTime); 60 | occurrence < endTime; 61 | occurrence = GetNextOccurrence(occurrence, endTime)) yield return occurrence; 62 | } 63 | 64 | private int Increment(IEnumerable filters, int value, int defaultValue, out bool overflow) 65 | { 66 | var nextValue = filters.Select(x => x.Next(value)).Where(x => x > value).Min() ?? defaultValue; 67 | 68 | overflow = nextValue <= value; 69 | 70 | return nextValue; 71 | } 72 | 73 | private DateTime MinDate(DateTime newValue, DateTime endValue) { return newValue >= endValue ? endValue : newValue; } 74 | 75 | private DateTime _InternalGetNextOccurence(DateTime baseValue, DateTime endValue) 76 | { 77 | var newValue = baseValue; 78 | bool overflow; 79 | 80 | var isSecondFormat = Format == CronStringFormat.WithSeconds || Format == CronStringFormat.WithSecondsAndYears; 81 | var isYearFormat = Format == CronStringFormat.WithYears || Format == CronStringFormat.WithSecondsAndYears; 82 | 83 | // First things first - trim off any time components we don't need 84 | newValue = newValue.AddMilliseconds(-newValue.Millisecond); 85 | 86 | if (!isSecondFormat) newValue = newValue.AddSeconds(-newValue.Second); 87 | 88 | var minuteFilters = Filters[CrontabFieldKind.Minute].Where(x => x is ITimeFilter).Cast().ToList(); 89 | var hourFilters = Filters[CrontabFieldKind.Hour].Where(x => x is ITimeFilter).Cast().ToList(); 90 | 91 | var firstSecondValue = newValue.Second; 92 | var firstMinuteValue = minuteFilters.Select(x => x.First()).Min(); 93 | var firstHourValue = hourFilters.Select(x => x.First()).Min(); 94 | 95 | var newSeconds = newValue.Second; 96 | 97 | if (isSecondFormat) 98 | { 99 | var secondFilters = Filters[CrontabFieldKind.Second].Where(x => x is ITimeFilter).Cast().ToList(); 100 | 101 | firstSecondValue = secondFilters.Select(x => x.First()).Min(); 102 | newSeconds = Increment(secondFilters, newValue.Second, firstSecondValue, out overflow); 103 | newValue = new DateTime(newValue.Year, newValue.Month, newValue.Day, newValue.Hour, newValue.Minute, newSeconds); 104 | 105 | if (!overflow && !IsMatch(newValue)) 106 | { 107 | newSeconds = firstSecondValue; 108 | newValue = new DateTime(newValue.Year, newValue.Month, newValue.Day, newValue.Hour, newValue.Minute, newSeconds); 109 | overflow = true; 110 | } 111 | 112 | if (!overflow) return MinDate(newValue, endValue); 113 | } 114 | 115 | var newMinutes = Increment(minuteFilters, newValue.Minute, firstMinuteValue, out overflow); 116 | 117 | newValue = new DateTime(newValue.Year, newValue.Month, newValue.Day, newValue.Hour, newMinutes, 118 | overflow ? firstSecondValue : newSeconds); 119 | 120 | if (!overflow && !IsMatch(newValue)) 121 | { 122 | newSeconds = firstSecondValue; 123 | newMinutes = firstMinuteValue; 124 | newValue = new DateTime(newValue.Year, newValue.Month, newValue.Day, newValue.Hour, newMinutes, firstSecondValue); 125 | overflow = true; 126 | } 127 | 128 | if (!overflow) return MinDate(newValue, endValue); 129 | 130 | var newHours = Increment(hourFilters, newValue.Hour, firstHourValue, out overflow); 131 | 132 | newValue = new DateTime(newValue.Year, newValue.Month, newValue.Day, newHours, 133 | overflow ? firstMinuteValue : newMinutes, 134 | overflow ? firstSecondValue : newSeconds); 135 | 136 | if (!overflow && !IsMatch(newValue)) 137 | { 138 | newValue = new DateTime(newValue.Year, newValue.Month, newValue.Day, firstHourValue, firstMinuteValue, 139 | firstSecondValue); 140 | overflow = true; 141 | } 142 | 143 | if (!overflow) return MinDate(newValue, endValue); 144 | 145 | List yearFilters = null; 146 | 147 | if (isYearFormat) yearFilters = Filters[CrontabFieldKind.Year].Where(x => x is ITimeFilter).Cast().ToList(); 148 | 149 | // Sooo, this is where things get more complicated. 150 | // Since the filtering of days relies on what month/year you're in 151 | // (for weekday/nth day filters), we'll only increment the day, and 152 | // check all day/month/year filters. Might be a litle slow, but we 153 | // won't miss any days that way. 154 | 155 | // Also, if we increment to the next day, we need to set the hour, minute and second 156 | // fields to their "first" values, since that would be the earliest they'd run. We 157 | // only have to do this after the initial AddDays call. FYI - they're already at their 158 | // first values if overflowHour = True. :-) 159 | 160 | // This feels so dirty. This is to catch the odd case where you specify 161 | // 12/31/9999 23:59:59.999 as your end date, and you don't have any matches, 162 | // so it reaches the max value of DateTime and throws an exception. 163 | try 164 | { 165 | newValue = newValue.AddDays(1); 166 | } 167 | catch 168 | { 169 | return endValue; 170 | } 171 | 172 | while (!(IsMatch(newValue, CrontabFieldKind.Day) && IsMatch(newValue, CrontabFieldKind.DayOfWeek) && 173 | IsMatch(newValue, CrontabFieldKind.Month) && (!isYearFormat || IsMatch(newValue, CrontabFieldKind.Year)))) 174 | { 175 | if (newValue >= endValue) return MinDate(newValue, endValue); 176 | 177 | // In instances where the year is filtered, this will speed up the path to get to endValue 178 | // (without having to actually go to endValue) 179 | if (isYearFormat && yearFilters.Select(x => x.Next(newValue.Year - 1)).All(x => x == null)) return endValue; 180 | 181 | // Ugh...have to do the try/catch again... 182 | try 183 | { 184 | newValue = newValue.AddDays(1); 185 | } 186 | catch 187 | { 188 | return endValue; 189 | } 190 | } 191 | 192 | return MinDate(newValue, endValue); 193 | } 194 | 195 | public bool IsMatch(DateTime value) { return Filters.All(fieldKind => fieldKind.Value.Any(filter => filter.IsMatch(value))); } 196 | 197 | public bool IsMatch(DateTime value, CrontabFieldKind kind) 198 | { 199 | return Filters.Where(x => x.Key == kind).SelectMany(x => x.Value).Any(filter => filter.IsMatch(value)); 200 | } 201 | 202 | private void _JoinFilters(List paramList, CrontabFieldKind kind) 203 | { 204 | paramList.Add( 205 | string.Join(",", Filters 206 | .Where(x => x.Key == kind) 207 | .SelectMany(x => x.Value.Select(y => y.ToString())) 208 | .ToArray() 209 | ) 210 | ); 211 | } 212 | 213 | public static CrontabSchedule Parse(string expression, CronStringFormat format = CronStringFormat.Default) 214 | { 215 | return new CrontabSchedule 216 | { 217 | Format = format, 218 | Filters = _ParseToDictionary(expression, format) 219 | }; 220 | } 221 | 222 | public static CrontabSchedule TryParse(string expression, CronStringFormat format = CronStringFormat.Default) 223 | { 224 | try 225 | { 226 | return Parse(expression, format); 227 | } 228 | catch (Exception) 229 | { 230 | return null; 231 | } 232 | } 233 | 234 | public static void CheckForIllegalFilters(Dictionary> filters) 235 | { 236 | var monthSingle = _GetSpecificFilters(filters, CrontabFieldKind.Month); 237 | var daySingle = _GetSpecificFilters(filters, CrontabFieldKind.Day); 238 | 239 | if (monthSingle.Any() 240 | && monthSingle.All(x => x.SpecificValue == 2) && daySingle.Any() && 241 | daySingle.All(x => x.SpecificValue == 30 || x.SpecificValue == 31)) throw new CrontabException("Nice try, but February 30 and 31 don't exist."); 242 | } 243 | 244 | private static List _GetSpecificFilters(Dictionary> filters, 245 | CrontabFieldKind kind) 246 | { 247 | return filters[kind] 248 | .Where(x => x.GetType() == typeof(SpecificFilter)) 249 | .Cast() 250 | .Union( 251 | filters[kind] 252 | .Where(x => x.GetType() == typeof(RangeFilter)) 253 | .SelectMany(x => ((RangeFilter) x) 254 | .SpecificFilters) 255 | ) 256 | .Union( 257 | filters[kind] 258 | .Where(x => x.GetType() == typeof(StepFilter)) 259 | .SelectMany(x => ((StepFilter) x) 260 | .SpecificFilters) 261 | ) 262 | .ToList(); 263 | } 264 | 265 | private static Dictionary> _ParseToDictionary(string cron, CronStringFormat format) 266 | { 267 | if (string.IsNullOrWhiteSpace(cron)) throw new CrontabException("The provided cron string is null, empty or contains only whitespace"); 268 | 269 | var fields = new Dictionary>(); 270 | var instructions = cron.Split(new[] {' '}, StringSplitOptions.RemoveEmptyEntries); 271 | var expectedCount = Constants.ExpectedFieldCounts[format]; 272 | 273 | if (instructions.Length > expectedCount) throw new CrontabException(string.Format("The provided cron string <{0}> has too many parameters", cron)); 274 | if (instructions.Length < expectedCount) throw new CrontabException(string.Format("The provided cron string <{0}> has too few parameters", cron)); 275 | 276 | var defaultFieldOffset = 0; 277 | 278 | if (format == CronStringFormat.WithSeconds || format == CronStringFormat.WithSecondsAndYears) 279 | { 280 | fields.Add(CrontabFieldKind.Second, _ParseField(instructions[0], CrontabFieldKind.Second)); 281 | defaultFieldOffset = 1; 282 | } 283 | 284 | fields.Add(CrontabFieldKind.Minute, _ParseField(instructions[defaultFieldOffset + 0], CrontabFieldKind.Minute)); 285 | fields.Add(CrontabFieldKind.Hour, _ParseField(instructions[defaultFieldOffset + 1], CrontabFieldKind.Hour)); 286 | fields.Add(CrontabFieldKind.Day, _ParseField(instructions[defaultFieldOffset + 2], CrontabFieldKind.Day)); 287 | fields.Add(CrontabFieldKind.Month, _ParseField(instructions[defaultFieldOffset + 3], CrontabFieldKind.Month)); 288 | fields.Add(CrontabFieldKind.DayOfWeek, _ParseField(instructions[defaultFieldOffset + 4], CrontabFieldKind.DayOfWeek)); 289 | 290 | if (format == CronStringFormat.WithYears || format == CronStringFormat.WithSecondsAndYears) 291 | fields.Add(CrontabFieldKind.Year, _ParseField(instructions[defaultFieldOffset + 5], CrontabFieldKind.Year)); 292 | 293 | CheckForIllegalFilters(fields); 294 | 295 | return fields; 296 | } 297 | 298 | private static List _ParseField(string field, CrontabFieldKind kind) 299 | { 300 | try 301 | { 302 | return field.Split(',').Select(filter => _ParseFilter(filter, kind)).ToList(); 303 | } 304 | catch (Exception e) 305 | { 306 | throw new CrontabException( 307 | $"There was an error parsing '{field}' for the {Enum.GetName(typeof(CrontabFieldKind), kind)} field", e); 308 | } 309 | } 310 | 311 | private static ICronFilter _ParseFilter(string filter, CrontabFieldKind kind) 312 | { 313 | var newFilter = filter.ToUpper(); 314 | 315 | try 316 | { 317 | if (newFilter.StartsWith("*", StringComparison.OrdinalIgnoreCase)) 318 | { 319 | newFilter = newFilter.Substring(1); 320 | 321 | if (newFilter.StartsWith("/", StringComparison.OrdinalIgnoreCase)) 322 | { 323 | newFilter = newFilter.Substring(1); 324 | 325 | var steps = _GetValue(ref newFilter, kind); 326 | 327 | return new StepFilter(0, steps, kind); 328 | } 329 | 330 | return new AnyFilter(kind); 331 | } 332 | 333 | // * * LW * * 334 | // * * L * * 335 | if (newFilter.StartsWith("L") && kind == CrontabFieldKind.Day) 336 | { 337 | newFilter = newFilter.Substring(1); 338 | 339 | if (newFilter == "W") return new LastWeekdayOfMonthFilter(kind); 340 | 341 | return new LastDayOfMonthFilter(kind); 342 | } 343 | 344 | if (newFilter == "?") return new BlankDayOfMonthOrWeekFilter(kind); 345 | 346 | var firstValue = _GetValue(ref newFilter, kind); 347 | 348 | if (string.IsNullOrEmpty(newFilter)) 349 | { 350 | if (kind == CrontabFieldKind.Year) return new SpecificYearFilter(firstValue, kind); 351 | 352 | return new SpecificFilter(firstValue, kind); 353 | } 354 | 355 | switch (newFilter[0]) 356 | { 357 | case '/': 358 | { 359 | newFilter = newFilter.Substring(1); 360 | 361 | var secondValue = _GetValue(ref newFilter, kind); 362 | 363 | return new StepFilter(firstValue, secondValue, kind); 364 | } 365 | case '-': 366 | { 367 | newFilter = newFilter.Substring(1); 368 | 369 | var secondValue = _GetValue(ref newFilter, kind); 370 | int? steps = null; 371 | 372 | if (newFilter.StartsWith("/")) 373 | { 374 | newFilter = newFilter.Substring(1); 375 | steps = _GetValue(ref newFilter, kind); 376 | } 377 | 378 | return new RangeFilter(firstValue, secondValue, steps, kind); 379 | } 380 | case '#': 381 | { 382 | newFilter = newFilter.Substring(1); 383 | 384 | var secondValue = _GetValue(ref newFilter, kind); 385 | 386 | if (!string.IsNullOrEmpty(newFilter)) throw new CrontabException(string.Format("Invalid filter '{0}'", filter)); 387 | 388 | return new SpecificDayOfWeekInMonthFilter(firstValue, secondValue, kind); 389 | } 390 | default: 391 | if (newFilter == "L" && kind == CrontabFieldKind.DayOfWeek) return new LastDayOfWeekInMonthFilter(firstValue, kind); 392 | else if (newFilter == "W" && kind == CrontabFieldKind.Day) return new NearestWeekdayFilter(firstValue, kind); 393 | 394 | break; 395 | } 396 | 397 | throw new CrontabException(string.Format("Invalid filter '{0}'", filter)); 398 | } 399 | catch (Exception e) 400 | { 401 | throw new CrontabException(string.Format("Invalid filter '{0}'. See inner exception for details.", filter), e); 402 | } 403 | } 404 | 405 | private static int _GetValue(ref string filter, CrontabFieldKind kind) 406 | { 407 | var maxValue = Constants.MaximumDateTimeValues[kind]; 408 | 409 | if (string.IsNullOrEmpty(filter)) throw new CrontabException("Expected number, but filter was empty."); 410 | 411 | int i, value; 412 | var isDigit = char.IsDigit(filter[0]); 413 | var isLetter = char.IsLetter(filter[0]); 414 | 415 | // Because this could either numbers, or letters, but not a combination, 416 | // check each condition separately. 417 | for (i = 0; i < filter.Length; i++) if (isDigit && !char.IsDigit(filter[i]) || isLetter && !char.IsLetter(filter[i])) break; 418 | 419 | var valueToParse = filter.Substring(0, i); 420 | 421 | if (int.TryParse(valueToParse, out value)) 422 | { 423 | filter = filter.Substring(i); 424 | 425 | var returnValue = value; 426 | 427 | if (returnValue > maxValue) 428 | throw new CrontabException($"Value for {Enum.GetName(typeof(CrontabFieldKind), kind)} filter exceeded maximum value of {maxValue}"); 429 | 430 | return returnValue; 431 | } 432 | 433 | List> replaceVal = null; 434 | 435 | switch (kind) 436 | { 437 | case CrontabFieldKind.DayOfWeek: 438 | replaceVal = Constants.Days.Where(x => valueToParse.StartsWith(x.Key)).ToList(); 439 | break; 440 | case CrontabFieldKind.Month: 441 | replaceVal = Constants.Months.Where(x => valueToParse.StartsWith(x.Key)).ToList(); 442 | break; 443 | } 444 | 445 | if (replaceVal != null && replaceVal.Count == 1) 446 | { 447 | // missingFilter addresses when a filter string of "SUNL" is passed in, 448 | // which causes the isDigit/isLetter loop above to iterate through the end 449 | // of the string. This catches the edge case, and re-appends L to the end. 450 | var missingFilter = ""; 451 | 452 | if (filter.Length == i && filter.EndsWith("L") && kind == CrontabFieldKind.DayOfWeek) missingFilter = "L"; 453 | 454 | filter = filter.Substring(i) + missingFilter; 455 | 456 | var returnValue = replaceVal.First().Value; 457 | 458 | if (returnValue > maxValue) 459 | throw new CrontabException($"Value for {Enum.GetName(typeof(CrontabFieldKind), kind)} filter exceeded maximum value of {maxValue}"); 460 | 461 | return returnValue; 462 | } 463 | 464 | throw new CrontabException("Filter does not contain expected number"); 465 | } 466 | } 467 | } 468 | -------------------------------------------------------------------------------- /src/Yisoft.Crontab/CrontabTask.cs: -------------------------------------------------------------------------------- 1 | // ) * 2 | // ( /( * ) ( ( ` 3 | // )\()) ( ` ) /( ( )\ )\))( 4 | // ((_)\ )\ ( )(_)))\ ((((_)( ((_)()\ 5 | // __ ((_)((_) (_(_())((_) )\ _ )\ (_()((_) 6 | // \ \ / / (_) |_ _|| __|(_)_\(_)| \/ | 7 | // \ V / | | _ | | | _| / _ \ | |\/| | 8 | // |_| |_|(_)|_| |___|/_/ \_\ |_| |_| 9 | // 10 | // This file is subject to the terms and conditions defined in 11 | // file 'License.txt', which is part of this source code package. 12 | // 13 | // Copyright © Yi.TEAM. All rights reserved. 14 | // ------------------------------------------------------------------------------- 15 | 16 | using System; 17 | using System.Reflection; 18 | 19 | namespace Yisoft.Crontab 20 | { 21 | public class CrontabTask 22 | { 23 | public CrontabTask(MethodInfo method, CronAttribute cron) 24 | { 25 | Method = method ?? throw new ArgumentNullException(nameof(method)); 26 | Cron = cron ?? throw new ArgumentNullException(nameof(cron)); 27 | 28 | ClassType = Method.DeclaringType; 29 | Schedule = CrontabSchedule.TryParse(cron.Expression, cron.Format); 30 | Parameters = Method.GetParameters(); 31 | } 32 | 33 | public ParameterInfo[] Parameters { get; } 34 | 35 | public Type ClassType { get; } 36 | 37 | public MethodInfo Method { get; } 38 | 39 | public CronAttribute Cron { get; } 40 | 41 | public CrontabSchedule Schedule { get; } 42 | 43 | internal Action Action { get; set; } 44 | 45 | internal int WaitingTime { get; set; } 46 | 47 | public CrontabTaskStatus Status { get; set; } = CrontabTaskStatus.Pending; 48 | 49 | public DateTime LastExecuteTime { get; set; } = DateTime.MinValue; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Yisoft.Crontab/CrontabTaskExecutor.cs: -------------------------------------------------------------------------------- 1 | // ) * 2 | // ( /( * ) ( ( ` 3 | // )\()) ( ` ) /( ( )\ )\))( 4 | // ((_)\ )\ ( )(_)))\ ((((_)( ((_)()\ 5 | // __ ((_)((_) (_(_())((_) )\ _ )\ (_()((_) 6 | // \ \ / / (_) |_ _|| __|(_)_\(_)| \/ | 7 | // \ V / | | _ | | | _| / _ \ | |\/| | 8 | // |_| |_|(_)|_| |___|/_/ \_\ |_| |_| 9 | // 10 | // This file is subject to the terms and conditions defined in 11 | // file 'License.txt', which is part of this source code package. 12 | // 13 | // Copyright © Yi.TEAM. All rights reserved. 14 | // ------------------------------------------------------------------------------- 15 | 16 | using System; 17 | using System.Collections.Generic; 18 | using System.Reflection; 19 | using System.Threading; 20 | 21 | namespace Yisoft.Crontab 22 | { 23 | public class CrontabTaskExecutor 24 | { 25 | private readonly List _cronTasks = new List(); 26 | 27 | private readonly Timer _timer; 28 | private readonly Func _typeInstanceCreator; 29 | 30 | public CrontabTaskExecutor(Func typeInstanceCreator) 31 | { 32 | _typeInstanceCreator = typeInstanceCreator ?? throw new ArgumentNullException(nameof(typeInstanceCreator)); 33 | 34 | _timer = new Timer(s => 35 | { 36 | var now = DateTime.Now; 37 | 38 | foreach (var task in _cronTasks) 39 | { 40 | if (task.Cron.Defer > task.WaitingTime) 41 | { 42 | task.WaitingTime++; 43 | 44 | continue; 45 | } 46 | 47 | if (!task.Schedule.IsMatch(now)) continue; 48 | 49 | task.Action(now); 50 | } 51 | }, null, -1, -1); 52 | } 53 | 54 | public IReadOnlyList Tasks => _cronTasks.AsReadOnly(); 55 | 56 | public void AddTasks(IEnumerable tasks) 57 | { 58 | if (tasks == null) throw new ArgumentNullException(nameof(tasks)); 59 | 60 | foreach (var task in tasks) AddTask(task); 61 | } 62 | 63 | public void AddTask(CrontabTask task) 64 | { 65 | if (task == null) throw new ArgumentNullException(nameof(task)); 66 | if (task.Schedule == null) throw new ArgumentException("argument \"task.Schedule\" is null", nameof(task.Schedule)); 67 | 68 | if (task.Action == null) 69 | { 70 | var action = _CreateAction(task); 71 | 72 | task.Action = action; 73 | } 74 | 75 | _cronTasks.Add(task); 76 | } 77 | 78 | public void Run() { _timer.Change(0, 1000); } 79 | 80 | public void Stop() { _timer.Change(-1, -1); } 81 | 82 | private Action _CreateAction(CrontabTask task) 83 | { 84 | if (task == null) throw new ArgumentNullException(nameof(task)); 85 | 86 | var typeInstance = _typeInstanceCreator(task.Method); 87 | 88 | return time => { _TryRun(time, task, typeInstance); }; 89 | } 90 | 91 | private void _TryRun(DateTime time, CrontabTask task, object typeInstance) 92 | { 93 | var method = task.Method; 94 | 95 | task.LastExecuteTime = time; 96 | task.Status = CrontabTaskStatus.Running; 97 | 98 | try 99 | { 100 | switch (task.Parameters.Length) 101 | { 102 | case 0: 103 | method.Invoke(typeInstance, null); 104 | break; 105 | case 1: 106 | method.Invoke(typeInstance, new object[] {time}); 107 | break; 108 | case 2: 109 | method.Invoke(typeInstance, new object[] {time, task}); 110 | break; 111 | case 3: 112 | method.Invoke(typeInstance, new object[] {time, task, this}); 113 | break; 114 | default: 115 | throw new ArgumentException("the number of task parameters is incorrect."); 116 | } 117 | } 118 | catch 119 | { 120 | task.Status = CrontabTaskStatus.Failing; 121 | } 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/Yisoft.Crontab/CrontabTaskScaner.cs: -------------------------------------------------------------------------------- 1 | // ) * 2 | // ( /( * ) ( ( ` 3 | // )\()) ( ` ) /( ( )\ )\))( 4 | // ((_)\ )\ ( )(_)))\ ((((_)( ((_)()\ 5 | // __ ((_)((_) (_(_())((_) )\ _ )\ (_()((_) 6 | // \ \ / / (_) |_ _|| __|(_)_\(_)| \/ | 7 | // \ V / | | _ | | | _| / _ \ | |\/| | 8 | // |_| |_|(_)|_| |___|/_/ \_\ |_| |_| 9 | // 10 | // This file is subject to the terms and conditions defined in 11 | // file 'License.txt', which is part of this source code package. 12 | // 13 | // Copyright © Yi.TEAM. All rights reserved. 14 | // ------------------------------------------------------------------------------- 15 | 16 | using System; 17 | using System.Collections.Generic; 18 | using System.Collections.ObjectModel; 19 | using System.Linq; 20 | using System.Reflection; 21 | 22 | namespace Yisoft.Crontab 23 | { 24 | public class CrontabTaskScaner 25 | { 26 | private readonly Assembly _mainAssembly; 27 | private readonly List _typePrefix; 28 | 29 | public CrontabTaskScaner(Assembly mainAssembly = null, List typePrefix = null) 30 | { 31 | _mainAssembly = mainAssembly; 32 | _typePrefix = typePrefix; 33 | 34 | if (_mainAssembly == null) _mainAssembly = Assembly.GetEntryAssembly(); 35 | if (_typePrefix == null) _typePrefix = new List(); 36 | } 37 | 38 | public ReadOnlyCollection ScanTasks() 39 | { 40 | var types = _LoadTypes(); 41 | 42 | if (types == null) return null; 43 | 44 | var taskMethods = (from type in types 45 | from method in type.GetMethods() 46 | .Where(m => !m.IsGenericMethodDefinition && !m.IsGenericMethod && m.IsPublic) 47 | let parameters = method.GetParameters() 48 | where parameters == null || parameters.Length < 4 49 | let cronAttributes = method.GetCustomAttributes() 50 | from cronAttribute in cronAttributes 51 | select new CrontabTask(method, cronAttribute)).ToList(); 52 | 53 | return taskMethods.AsReadOnly(); 54 | } 55 | 56 | private IEnumerable _LoadTypes() 57 | { 58 | var assemblies = _mainAssembly?.GetReferencedAssemblies() 59 | .Where(x => (_typePrefix == null || _typePrefix.Count == 0) || _typePrefix.Any(t => x.Name.StartsWith(t))) 60 | .Select(Assembly.Load) 61 | .Select(x => x.GetTypes()) 62 | .SelectMany(x => x) 63 | .ToList(); 64 | 65 | if (assemblies == null) return null; 66 | 67 | assemblies.AddRange(_mainAssembly.GetTypes()); 68 | 69 | return assemblies.Where(x => x.GetTypeInfo().IsClass); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Yisoft.Crontab/CrontabTaskStatus.cs: -------------------------------------------------------------------------------- 1 | // ) * 2 | // ( /( * ) ( ( ` 3 | // )\()) ( ` ) /( ( )\ )\))( 4 | // ((_)\ )\ ( )(_)))\ ((((_)( ((_)()\ 5 | // __ ((_)((_) (_(_())((_) )\ _ )\ (_()((_) 6 | // \ \ / / (_) |_ _|| __|(_)_\(_)| \/ | 7 | // \ V / | | _ | | | _| / _ \ | |\/| | 8 | // |_| |_|(_)|_| |___|/_/ \_\ |_| |_| 9 | // 10 | // This file is subject to the terms and conditions defined in 11 | // file 'License.txt', which is part of this source code package. 12 | // 13 | // Copyright © Yi.TEAM. All rights reserved. 14 | // ------------------------------------------------------------------------------- 15 | 16 | namespace Yisoft.Crontab 17 | { 18 | public enum CrontabTaskStatus 19 | { 20 | None = 0, 21 | 22 | Pending = 1, 23 | 24 | Running = 2, 25 | 26 | Passing = 3, 27 | 28 | Failing = 4 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Yisoft.Crontab/Extensions/DayOfWeekExtensions.cs: -------------------------------------------------------------------------------- 1 | // ) * 2 | // ( /( * ) ( ( ` 3 | // )\()) ( ` ) /( ( )\ )\))( 4 | // ((_)\ )\ ( )(_)))\ ((((_)( ((_)()\ 5 | // __ ((_)((_) (_(_())((_) )\ _ )\ (_()((_) 6 | // \ \ / / (_) |_ _|| __|(_)_\(_)| \/ | 7 | // \ V / | | _ | | | _| / _ \ | |\/| | 8 | // |_| |_|(_)|_| |___|/_/ \_\ |_| |_| 9 | // 10 | // This file is subject to the terms and conditions defined in 11 | // file 'License.txt', which is part of this source code package. 12 | // 13 | // Copyright © Yi.TEAM. All rights reserved. 14 | // ------------------------------------------------------------------------------- 15 | 16 | using System; 17 | using System.Linq; 18 | 19 | namespace Yisoft.Crontab.Extensions 20 | { 21 | internal static class DayOfWeekExtensions 22 | { 23 | /// 24 | /// Since there is no guarantee that (int) DayOfWeek returns the same value 25 | /// that cron uses (since the values aren't explicitly set in the DayOfWeek enum) 26 | /// we have to use this method. 27 | /// 28 | /// The DayOfWeek value to convert 29 | /// An integer representing the provided day of week 30 | internal static int ToCronDayOfWeek(this DayOfWeek value) 31 | { 32 | return Constants.CronDays[value]; 33 | } 34 | 35 | /// 36 | /// Since there is no guarantee that (int) DayOfWeek returns the same value 37 | /// that cron uses (since the values aren't explicitly set in the DayOfWeek enum) 38 | /// we have to use this method. 39 | /// 40 | /// The cron day value to convert 41 | /// A DayOfWeek representing the provided day of week 42 | internal static DayOfWeek ToDayOfWeek(this int value) 43 | { 44 | return Constants.CronDays.First(x => x.Value == value).Key; 45 | } 46 | 47 | /// 48 | /// Retrieves the last instance of the specified day of the month 49 | /// 50 | /// The day you want to find 51 | /// The year in which you want to find the day 52 | /// The month in which you want to find the day 53 | /// An integer representing the day that matches the criteria 54 | internal static int LastDayOfMonth(this DayOfWeek dayOfWeek, int year, int month) 55 | { 56 | var daysInMonth = DateTime.DaysInMonth(year, month); 57 | var date = new DateTime(year, month, daysInMonth); 58 | 59 | while (date.DayOfWeek != dayOfWeek) date = date.AddDays(-1); 60 | 61 | return date.Day; 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Yisoft.Crontab/Filters/AnyFilter.cs: -------------------------------------------------------------------------------- 1 | // ) * 2 | // ( /( * ) ( ( ` 3 | // )\()) ( ` ) /( ( )\ )\))( 4 | // ((_)\ )\ ( )(_)))\ ((((_)( ((_)()\ 5 | // __ ((_)((_) (_(_())((_) )\ _ )\ (_()((_) 6 | // \ \ / / (_) |_ _|| __|(_)_\(_)| \/ | 7 | // \ V / | | _ | | | _| / _ \ | |\/| | 8 | // |_| |_|(_)|_| |___|/_/ \_\ |_| |_| 9 | // 10 | // This file is subject to the terms and conditions defined in 11 | // file 'License.txt', which is part of this source code package. 12 | // 13 | // Copyright © Yi.TEAM. All rights reserved. 14 | // ------------------------------------------------------------------------------- 15 | 16 | using System; 17 | 18 | namespace Yisoft.Crontab.Filters 19 | { 20 | /// 21 | /// Handles the filter instance where the user specifies a * (for any value) 22 | /// 23 | public class AnyFilter : ICronFilter, ITimeFilter 24 | { 25 | /// 26 | /// Constructs a new AnyFilter instance 27 | /// 28 | /// The crontab field kind to associate with this filter 29 | public AnyFilter(CrontabFieldKind kind) 30 | { 31 | Kind = kind; 32 | } 33 | 34 | public CrontabFieldKind Kind { get; } 35 | 36 | /// 37 | /// Checks if the value is accepted by the filter 38 | /// 39 | /// The value to check 40 | /// True if the value matches the condition, False if it does not match. 41 | public bool IsMatch(DateTime value) 42 | { 43 | return true; 44 | } 45 | 46 | public int? Next(int value) 47 | { 48 | var max = Constants.MaximumDateTimeValues[Kind]; 49 | if (Kind == CrontabFieldKind.Day 50 | || Kind == CrontabFieldKind.Month 51 | || Kind == CrontabFieldKind.DayOfWeek) throw new CrontabException("Cannot call Next for Day, Month or DayOfWeek types"); 52 | 53 | var newValue = (int?) value + 1; 54 | 55 | if (newValue >= max) newValue = null; 56 | 57 | return newValue; 58 | } 59 | 60 | public int First() 61 | { 62 | if (Kind == CrontabFieldKind.Day 63 | || Kind == CrontabFieldKind.Month 64 | || Kind == CrontabFieldKind.DayOfWeek) throw new CrontabException("Cannot call First for Day, Month or DayOfWeek types"); 65 | 66 | return 0; 67 | } 68 | 69 | public override string ToString() { return "*"; } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/Yisoft.Crontab/Filters/BlankDayOfMonthOrWeekFilter.cs: -------------------------------------------------------------------------------- 1 | // ) * 2 | // ( /( * ) ( ( ` 3 | // )\()) ( ` ) /( ( )\ )\))( 4 | // ((_)\ )\ ( )(_)))\ ((((_)( ((_)()\ 5 | // __ ((_)((_) (_(_())((_) )\ _ )\ (_()((_) 6 | // \ \ / / (_) |_ _|| __|(_)_\(_)| \/ | 7 | // \ V / | | _ | | | _| / _ \ | |\/| | 8 | // |_| |_|(_)|_| |___|/_/ \_\ |_| |_| 9 | // 10 | // This file is subject to the terms and conditions defined in 11 | // file 'License.txt', which is part of this source code package. 12 | // 13 | // Copyright © Yi.TEAM. All rights reserved. 14 | // ------------------------------------------------------------------------------- 15 | 16 | using System; 17 | 18 | namespace Yisoft.Crontab.Filters 19 | { 20 | /// 21 | /// No specific value filter for day-of-week and day-of -month fields 22 | /// 23 | /// http://www.quartz-scheduler.net/documentation/quartz-2.x/tutorial/crontrigger.html 24 | /// https://en.wikipedia.org/wiki/Cron#CRON_expression 25 | /// 26 | /// 27 | public class BlankDayOfMonthOrWeekFilter : ICronFilter 28 | { 29 | public BlankDayOfMonthOrWeekFilter(CrontabFieldKind kind) 30 | { 31 | if (kind != CrontabFieldKind.DayOfWeek && kind != CrontabFieldKind.Day) 32 | throw new CrontabException("The filter can only be used in the Day-of-Week or Day-of-Month fields."); 33 | 34 | Kind = kind; 35 | } 36 | 37 | public CrontabFieldKind Kind { get; } 38 | 39 | public bool IsMatch(DateTime value) { return true; } 40 | 41 | public int? Next(int value) 42 | { 43 | var max = Constants.MaximumDateTimeValues[Kind]; 44 | if (Kind == CrontabFieldKind.Day 45 | || Kind == CrontabFieldKind.Month 46 | || Kind == CrontabFieldKind.DayOfWeek) throw new CrontabException("Cannot call Next for Day, Month or DayOfWeek types"); 47 | 48 | var newValue = (int?) value + 1; 49 | 50 | if (newValue >= max) newValue = null; 51 | 52 | return newValue; 53 | } 54 | 55 | public int First() 56 | { 57 | if (Kind == CrontabFieldKind.Day 58 | || Kind == CrontabFieldKind.Month 59 | || Kind == CrontabFieldKind.DayOfWeek) throw new CrontabException("Cannot call First for Day, Month or DayOfWeek types"); 60 | 61 | return 0; 62 | } 63 | 64 | public override string ToString() { return "?"; } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Yisoft.Crontab/Filters/LastDayOfMonthFilter.cs: -------------------------------------------------------------------------------- 1 | // ) * 2 | // ( /( * ) ( ( ` 3 | // )\()) ( ` ) /( ( )\ )\))( 4 | // ((_)\ )\ ( )(_)))\ ((((_)( ((_)()\ 5 | // __ ((_)((_) (_(_())((_) )\ _ )\ (_()((_) 6 | // \ \ / / (_) |_ _|| __|(_)_\(_)| \/ | 7 | // \ V / | | _ | | | _| / _ \ | |\/| | 8 | // |_| |_|(_)|_| |___|/_/ \_\ |_| |_| 9 | // 10 | // This file is subject to the terms and conditions defined in 11 | // file 'License.txt', which is part of this source code package. 12 | // 13 | // Copyright © Yi.TEAM. All rights reserved. 14 | // ------------------------------------------------------------------------------- 15 | 16 | using System; 17 | 18 | namespace Yisoft.Crontab.Filters 19 | { 20 | /// 21 | /// Handles filtering for the last day of the month 22 | /// 23 | public class LastDayOfMonthFilter : ICronFilter 24 | { 25 | public LastDayOfMonthFilter(CrontabFieldKind kind) 26 | { 27 | if (kind != CrontabFieldKind.Day) throw new CrontabException("The filter can only be used with the Day field."); 28 | 29 | Kind = kind; 30 | } 31 | 32 | public CrontabFieldKind Kind { get; } 33 | 34 | /// 35 | /// Checks if the value is accepted by the filter 36 | /// 37 | /// The value to check 38 | /// True if the value matches the condition, False if it does not match. 39 | public bool IsMatch(DateTime value) 40 | { 41 | return DateTime.DaysInMonth(value.Year, value.Month) == value.Day; 42 | } 43 | 44 | public override string ToString() { return "L"; } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Yisoft.Crontab/Filters/LastDayOfWeekInMonthFilter.cs: -------------------------------------------------------------------------------- 1 | // ) * 2 | // ( /( * ) ( ( ` 3 | // )\()) ( ` ) /( ( )\ )\))( 4 | // ((_)\ )\ ( )(_)))\ ((((_)( ((_)()\ 5 | // __ ((_)((_) (_(_())((_) )\ _ )\ (_()((_) 6 | // \ \ / / (_) |_ _|| __|(_)_\(_)| \/ | 7 | // \ V / | | _ | | | _| / _ \ | |\/| | 8 | // |_| |_|(_)|_| |___|/_/ \_\ |_| |_| 9 | // 10 | // This file is subject to the terms and conditions defined in 11 | // file 'License.txt', which is part of this source code package. 12 | // 13 | // Copyright © Yi.TEAM. All rights reserved. 14 | // ------------------------------------------------------------------------------- 15 | 16 | using System; 17 | using Yisoft.Crontab.Extensions; 18 | 19 | namespace Yisoft.Crontab.Filters 20 | { 21 | /// 22 | /// Handles filtering for the last specified day of the week in the month 23 | /// 24 | public class LastDayOfWeekInMonthFilter : ICronFilter 25 | { 26 | private readonly DayOfWeek _dateTimeDayOfWeek; 27 | 28 | /// 29 | /// Constructs a new instance of LastDayOfWeekInMonthFilter 30 | /// 31 | /// The cron day of the week (0 = Sunday...7 = Saturday) 32 | /// The crontab field kind to associate with this filter 33 | public LastDayOfWeekInMonthFilter(int dayOfWeek, CrontabFieldKind kind) 34 | { 35 | if (kind != CrontabFieldKind.DayOfWeek) throw new CrontabException(string.Format("<{0}L> can only be used in the Day of Week field.", dayOfWeek)); 36 | 37 | DayOfWeek = dayOfWeek; 38 | _dateTimeDayOfWeek = dayOfWeek.ToDayOfWeek(); 39 | Kind = kind; 40 | } 41 | 42 | public int DayOfWeek { get; } 43 | 44 | public CrontabFieldKind Kind { get; } 45 | 46 | /// 47 | /// Checks if the value is accepted by the filter 48 | /// 49 | /// The value to check 50 | /// True if the value matches the condition, False if it does not match. 51 | public bool IsMatch(DateTime value) 52 | { 53 | return value.Day == _dateTimeDayOfWeek.LastDayOfMonth(value.Year, value.Month); 54 | } 55 | 56 | public override string ToString() { return string.Format("{0}L", DayOfWeek); } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Yisoft.Crontab/Filters/LastWeekdayOfMonthFilter.cs: -------------------------------------------------------------------------------- 1 | // ) * 2 | // ( /( * ) ( ( ` 3 | // )\()) ( ` ) /( ( )\ )\))( 4 | // ((_)\ )\ ( )(_)))\ ((((_)( ((_)()\ 5 | // __ ((_)((_) (_(_())((_) )\ _ )\ (_()((_) 6 | // \ \ / / (_) |_ _|| __|(_)_\(_)| \/ | 7 | // \ V / | | _ | | | _| / _ \ | |\/| | 8 | // |_| |_|(_)|_| |___|/_/ \_\ |_| |_| 9 | // 10 | // This file is subject to the terms and conditions defined in 11 | // file 'License.txt', which is part of this source code package. 12 | // 13 | // Copyright © Yi.TEAM. All rights reserved. 14 | // ------------------------------------------------------------------------------- 15 | 16 | using System; 17 | 18 | namespace Yisoft.Crontab.Filters 19 | { 20 | /// 21 | /// Handles filtering for the last weekday of a month 22 | /// 23 | public class LastWeekdayOfMonthFilter : ICronFilter 24 | { 25 | /// 26 | /// Constructs a new RangeFilter instance 27 | /// 28 | /// The crontab field kind to associate with this filter 29 | public LastWeekdayOfMonthFilter(CrontabFieldKind kind) 30 | { 31 | if (kind != CrontabFieldKind.Day) throw new CrontabException(" can only be used in the Day field."); 32 | 33 | Kind = kind; 34 | } 35 | 36 | public CrontabFieldKind Kind { get; } 37 | 38 | /// 39 | /// Checks if the value is accepted by the filter 40 | /// 41 | /// The value to check 42 | /// True if the value matches the condition, False if it does not match. 43 | public bool IsMatch(DateTime value) 44 | { 45 | var specificValue = DateTime.DaysInMonth(value.Year, value.Month); 46 | var specificDay = new DateTime(value.Year, value.Month, specificValue); 47 | 48 | DateTime closestWeekday; 49 | 50 | // ReSharper disable once SwitchStatementMissingSomeCases 51 | switch (specificDay.DayOfWeek) 52 | { 53 | case DayOfWeek.Saturday: 54 | // If the specified day is Saturday, back up to Friday 55 | closestWeekday = specificDay.AddDays(-1); 56 | 57 | // If Friday is in the previous month, then move forward to the following Monday 58 | if (closestWeekday.Month != specificDay.Month) closestWeekday = specificDay.AddDays(2); 59 | 60 | break; 61 | 62 | case DayOfWeek.Sunday: 63 | // If the specified day is Sunday, move forward to Monday 64 | closestWeekday = specificDay.AddDays(1); 65 | 66 | // If Monday is in the next month, then move backward to the previous Friday 67 | if (closestWeekday.Month != specificDay.Month) closestWeekday = specificDay.AddDays(-2); 68 | 69 | break; 70 | 71 | default: 72 | // The specified day happens to be a weekday, so use it 73 | closestWeekday = specificDay; 74 | break; 75 | } 76 | 77 | return value.Day == closestWeekday.Day; 78 | } 79 | 80 | public override string ToString() { return "LW"; } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/Yisoft.Crontab/Filters/NearestWeekdayFilter.cs: -------------------------------------------------------------------------------- 1 | // ) * 2 | // ( /( * ) ( ( ` 3 | // )\()) ( ` ) /( ( )\ )\))( 4 | // ((_)\ )\ ( )(_)))\ ((((_)( ((_)()\ 5 | // __ ((_)((_) (_(_())((_) )\ _ )\ (_()((_) 6 | // \ \ / / (_) |_ _|| __|(_)_\(_)| \/ | 7 | // \ V / | | _ | | | _| / _ \ | |\/| | 8 | // |_| |_|(_)|_| |___|/_/ \_\ |_| |_| 9 | // 10 | // This file is subject to the terms and conditions defined in 11 | // file 'License.txt', which is part of this source code package. 12 | // 13 | // Copyright © Yi.TEAM. All rights reserved. 14 | // ------------------------------------------------------------------------------- 15 | 16 | using System; 17 | 18 | namespace Yisoft.Crontab.Filters 19 | { 20 | /// 21 | /// Handles filtering for the nearest weekday to a specified day 22 | /// 23 | public class NearestWeekdayFilter : ICronFilter 24 | { 25 | /// 26 | /// Constructs a new RangeFilter instance 27 | /// 28 | /// The specific value you wish to match 29 | /// The crontab field kind to associate with this filter 30 | public NearestWeekdayFilter(int specificValue, CrontabFieldKind kind) 31 | { 32 | if (specificValue <= 0 || specificValue > Constants.MaximumDateTimeValues[CrontabFieldKind.Day]) 33 | throw new CrontabException(string.Format("<{0}W> is out of bounds for the Day field.", specificValue)); 34 | 35 | if (kind != CrontabFieldKind.Day) throw new CrontabException(string.Format("<{0}W> can only be used in the Day field.", specificValue)); 36 | 37 | SpecificValue = specificValue; 38 | Kind = kind; 39 | } 40 | 41 | public int SpecificValue { get; } 42 | 43 | public CrontabFieldKind Kind { get; } 44 | 45 | /// 46 | /// Checks if the value is accepted by the filter 47 | /// 48 | /// The value to check 49 | /// True if the value matches the condition, False if it does not match. 50 | public bool IsMatch(DateTime value) 51 | { 52 | var specificDay = new DateTime(value.Year, value.Month, SpecificValue); 53 | 54 | DateTime closestWeekday; 55 | 56 | // ReSharper disable once SwitchStatementMissingSomeCases 57 | switch (specificDay.DayOfWeek) 58 | { 59 | case DayOfWeek.Saturday: 60 | // If the specified day is Saturday, back up to Friday 61 | closestWeekday = specificDay.AddDays(-1); 62 | 63 | // If Friday is in the previous month, then move forward to the following Monday 64 | if (closestWeekday.Month != specificDay.Month) closestWeekday = specificDay.AddDays(2); 65 | 66 | break; 67 | 68 | case DayOfWeek.Sunday: 69 | // If the specified day is Sunday, move forward to Monday 70 | closestWeekday = specificDay.AddDays(1); 71 | 72 | // If Monday is in the next month, then move backward to the previous Friday 73 | if (closestWeekday.Month != specificDay.Month) closestWeekday = specificDay.AddDays(-2); 74 | 75 | break; 76 | 77 | default: 78 | // The specified day happens to be a weekday, so use it 79 | closestWeekday = specificDay; 80 | break; 81 | } 82 | 83 | return value.Day == closestWeekday.Day; 84 | } 85 | 86 | public override string ToString() { return string.Format("{0}W", SpecificValue); } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/Yisoft.Crontab/Filters/RangeFilter.cs: -------------------------------------------------------------------------------- 1 | // ) * 2 | // ( /( * ) ( ( ` 3 | // )\()) ( ` ) /( ( )\ )\))( 4 | // ((_)\ )\ ( )(_)))\ ((((_)( ((_)()\ 5 | // __ ((_)((_) (_(_())((_) )\ _ )\ (_()((_) 6 | // \ \ / / (_) |_ _|| __|(_)_\(_)| \/ | 7 | // \ V / | | _ | | | _| / _ \ | |\/| | 8 | // |_| |_|(_)|_| |___|/_/ \_\ |_| |_| 9 | // 10 | // This file is subject to the terms and conditions defined in 11 | // file 'License.txt', which is part of this source code package. 12 | // 13 | // Copyright © Yi.TEAM. All rights reserved. 14 | // ------------------------------------------------------------------------------- 15 | 16 | using System; 17 | using System.Collections.Generic; 18 | using Yisoft.Crontab.Extensions; 19 | 20 | namespace Yisoft.Crontab.Filters 21 | { 22 | /// 23 | /// Handles filtering ranges (i.e. 1-5) 24 | /// 25 | public class RangeFilter : ICronFilter, ITimeFilter 26 | { 27 | private int? _firstCache; 28 | 29 | /// 30 | /// Constructs a new RangeFilter instance 31 | /// 32 | /// The start of the range 33 | /// The end of the range 34 | /// The steps in the range 35 | /// The crontab field kind to associate with this filter 36 | public RangeFilter(int start, int end, int? steps, CrontabFieldKind kind) 37 | { 38 | var maxValue = Constants.MaximumDateTimeValues[kind]; 39 | 40 | if (start < 0 || start > maxValue) 41 | throw new CrontabException($"Start = {start} is out of bounds for <{Enum.GetName(typeof(CrontabFieldKind), kind)}> field"); 42 | 43 | if (end < 0 || end > maxValue) throw new CrontabException($"End = {end} is out of bounds for <{Enum.GetName(typeof(CrontabFieldKind), kind)}> field"); 44 | 45 | if (steps != null && (steps <= 0 || steps > maxValue)) 46 | throw new CrontabException($"Steps = {steps} is out of bounds for <{Enum.GetName(typeof(CrontabFieldKind), kind)}> field"); 47 | 48 | Start = start; 49 | End = end; 50 | Kind = kind; 51 | Steps = steps; 52 | 53 | var filters = new List(); 54 | 55 | for (var evalValue = Start; evalValue <= End; evalValue++) if (_IsMatch(evalValue)) filters.Add(new SpecificFilter(evalValue, Kind)); 56 | 57 | SpecificFilters = filters; 58 | } 59 | 60 | public int Start { get; } 61 | 62 | public int End { get; } 63 | 64 | public int? Steps { get; } 65 | 66 | /// 67 | /// Returns a list of specific filters that represents this step filter. 68 | /// NOTE - This is only populated on construction, and should NOT be modified. 69 | /// 70 | public IEnumerable SpecificFilters { get; } 71 | 72 | public CrontabFieldKind Kind { get; } 73 | 74 | /// 75 | /// Checks if the value is accepted by the filter 76 | /// 77 | /// The value to check 78 | /// True if the value matches the condition, False if it does not match. 79 | public bool IsMatch(DateTime value) 80 | { 81 | int evalValue; 82 | 83 | switch (Kind) 84 | { 85 | case CrontabFieldKind.Second: 86 | evalValue = value.Second; 87 | break; 88 | case CrontabFieldKind.Minute: 89 | evalValue = value.Minute; 90 | break; 91 | case CrontabFieldKind.Hour: 92 | evalValue = value.Hour; 93 | break; 94 | case CrontabFieldKind.Day: 95 | evalValue = value.Day; 96 | break; 97 | case CrontabFieldKind.Month: 98 | evalValue = value.Month; 99 | break; 100 | case CrontabFieldKind.DayOfWeek: 101 | evalValue = value.DayOfWeek.ToCronDayOfWeek(); 102 | break; 103 | case CrontabFieldKind.Year: 104 | evalValue = value.Year; 105 | break; 106 | default: throw new ArgumentOutOfRangeException(nameof(Kind), Kind, null); 107 | } 108 | 109 | return _IsMatch(evalValue); 110 | } 111 | 112 | public int? Next(int value) 113 | { 114 | if (Kind == CrontabFieldKind.Day 115 | || Kind == CrontabFieldKind.Month 116 | || Kind == CrontabFieldKind.DayOfWeek) throw new CrontabException("Cannot call Next for Day, Month or DayOfWeek types"); 117 | 118 | var max = Constants.MaximumDateTimeValues[Kind]; 119 | 120 | var newValue = (int?) value + 1; 121 | 122 | while (newValue < max && !_IsMatch(newValue.Value)) newValue++; 123 | 124 | if (newValue >= max) newValue = null; 125 | 126 | return newValue; 127 | } 128 | 129 | public int First() 130 | { 131 | if (_firstCache.HasValue) return _firstCache.Value; 132 | 133 | if (Kind == CrontabFieldKind.Day 134 | || Kind == CrontabFieldKind.Month 135 | || Kind == CrontabFieldKind.DayOfWeek) throw new CrontabException("Cannot call First for Day, Month or DayOfWeek types"); 136 | 137 | var max = Constants.MaximumDateTimeValues[Kind]; 138 | 139 | var newValue = 0; 140 | 141 | while (newValue < max && !_IsMatch(newValue)) newValue++; 142 | 143 | if (newValue > max) throw new CrontabException($"Next value for {ToString()} on field {Enum.GetName(typeof(CrontabFieldKind), Kind)} could not be found!"); 144 | 145 | _firstCache = newValue; 146 | return newValue; 147 | } 148 | 149 | private bool _IsMatch(int evalValue) { return evalValue >= Start && evalValue <= End && (!Steps.HasValue || (evalValue - Start) % Steps == 0); } 150 | 151 | public override string ToString() { return Steps.HasValue ? string.Format("{0}-{1}/{2}", Start, End, Steps) : $"{Start}-{End}"; } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/Yisoft.Crontab/Filters/SpecificDayOfWeekInMonthFilter.cs: -------------------------------------------------------------------------------- 1 | // ) * 2 | // ( /( * ) ( ( ` 3 | // )\()) ( ` ) /( ( )\ )\))( 4 | // ((_)\ )\ ( )(_)))\ ((((_)( ((_)()\ 5 | // __ ((_)((_) (_(_())((_) )\ _ )\ (_()((_) 6 | // \ \ / / (_) |_ _|| __|(_)_\(_)| \/ | 7 | // \ V / | | _ | | | _| / _ \ | |\/| | 8 | // |_| |_|(_)|_| |___|/_/ \_\ |_| |_| 9 | // 10 | // This file is subject to the terms and conditions defined in 11 | // file 'License.txt', which is part of this source code package. 12 | // 13 | // Copyright © Yi.TEAM. All rights reserved. 14 | // ------------------------------------------------------------------------------- 15 | 16 | using System; 17 | using Yisoft.Crontab.Extensions; 18 | 19 | namespace Yisoft.Crontab.Filters 20 | { 21 | /// 22 | /// Handles filtering for a specific day of the week in the month (i.e. 3rd Tuesday of the month) 23 | /// 24 | public class SpecificDayOfWeekInMonthFilter : ICronFilter 25 | { 26 | private readonly DayOfWeek _dateTimeDayOfWeek; 27 | 28 | /// 29 | /// Constructs a new instance of LastDayOfWeekInMonthFilter 30 | /// 31 | /// The cron day of the week (0 = Sunday...7 = Saturday) 32 | /// Indicates which occurence of the day to filter against 33 | /// The crontab field kind to associate with this filter 34 | public SpecificDayOfWeekInMonthFilter(int dayOfWeek, int weekNumber, CrontabFieldKind kind) 35 | { 36 | if (weekNumber <= 0 || weekNumber > 5) throw new CrontabException($"Week number = {weekNumber} is out of bounds."); 37 | 38 | if (kind != CrontabFieldKind.DayOfWeek) throw new CrontabException($"<{dayOfWeek}#{weekNumber}> can only be used in the Day of Week field."); 39 | 40 | DayOfWeek = dayOfWeek; 41 | _dateTimeDayOfWeek = dayOfWeek.ToDayOfWeek(); 42 | WeekNumber = weekNumber; 43 | Kind = kind; 44 | } 45 | 46 | public int DayOfWeek { get; } 47 | 48 | public int WeekNumber { get; } 49 | 50 | public CrontabFieldKind Kind { get; } 51 | 52 | /// 53 | /// Checks if the value is accepted by the filter 54 | /// 55 | /// The value to check 56 | /// True if the value matches the condition, False if it does not match. 57 | public bool IsMatch(DateTime value) 58 | { 59 | var weekCount = 0; 60 | var currentDay = new DateTime(value.Year, value.Month, 1); 61 | 62 | while (currentDay.Month == value.Month) 63 | if (currentDay.DayOfWeek == _dateTimeDayOfWeek) 64 | { 65 | weekCount++; 66 | 67 | if (weekCount == WeekNumber) break; 68 | 69 | currentDay = currentDay.AddDays(7); 70 | } 71 | else 72 | { 73 | currentDay = currentDay.AddDays(1); 74 | } 75 | 76 | if (currentDay.Month != value.Month) return false; 77 | 78 | return value.Day == currentDay.Day; 79 | } 80 | 81 | public override string ToString() { return $"{DayOfWeek}#{WeekNumber}"; } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/Yisoft.Crontab/Filters/SpecificFilter.cs: -------------------------------------------------------------------------------- 1 | // ) * 2 | // ( /( * ) ( ( ` 3 | // )\()) ( ` ) /( ( )\ )\))( 4 | // ((_)\ )\ ( )(_)))\ ((((_)( ((_)()\ 5 | // __ ((_)((_) (_(_())((_) )\ _ )\ (_()((_) 6 | // \ \ / / (_) |_ _|| __|(_)_\(_)| \/ | 7 | // \ V / | | _ | | | _| / _ \ | |\/| | 8 | // |_| |_|(_)|_| |___|/_/ \_\ |_| |_| 9 | // 10 | // This file is subject to the terms and conditions defined in 11 | // file 'License.txt', which is part of this source code package. 12 | // 13 | // Copyright © Yi.TEAM. All rights reserved. 14 | // ------------------------------------------------------------------------------- 15 | 16 | using System; 17 | using Yisoft.Crontab.Extensions; 18 | 19 | namespace Yisoft.Crontab.Filters 20 | { 21 | /// 22 | /// Handles filtering for a specific value 23 | /// 24 | public class SpecificFilter : ICronFilter, ITimeFilter 25 | { 26 | /// 27 | /// Constructs a new RangeFilter instance 28 | /// 29 | /// The specific value you wish to match 30 | /// The crontab field kind to associate with this filter 31 | public SpecificFilter(int specificValue, CrontabFieldKind kind) 32 | { 33 | SpecificValue = specificValue; 34 | Kind = kind; 35 | } 36 | 37 | public int SpecificValue { get; } 38 | 39 | public CrontabFieldKind Kind { get; } 40 | 41 | /// 42 | /// Checks if the value is accepted by the filter 43 | /// 44 | /// The value to check 45 | /// True if the value matches the condition, False if it does not match. 46 | public bool IsMatch(DateTime value) 47 | { 48 | int evalValue; 49 | 50 | switch (Kind) 51 | { 52 | case CrontabFieldKind.Second: 53 | evalValue = value.Second; 54 | break; 55 | case CrontabFieldKind.Minute: 56 | evalValue = value.Minute; 57 | break; 58 | case CrontabFieldKind.Hour: 59 | evalValue = value.Hour; 60 | break; 61 | case CrontabFieldKind.Day: 62 | evalValue = value.Day; 63 | break; 64 | case CrontabFieldKind.Month: 65 | evalValue = value.Month; 66 | break; 67 | case CrontabFieldKind.DayOfWeek: 68 | evalValue = value.DayOfWeek.ToCronDayOfWeek(); 69 | break; 70 | case CrontabFieldKind.Year: 71 | evalValue = value.Year; 72 | break; 73 | default: throw new ArgumentOutOfRangeException(nameof(Kind), Kind, null); 74 | } 75 | 76 | return evalValue == SpecificValue; 77 | } 78 | 79 | public virtual int? Next(int value) { return SpecificValue; } 80 | 81 | public int First() { return SpecificValue; } 82 | 83 | public override string ToString() { return SpecificValue.ToString(); } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/Yisoft.Crontab/Filters/SpecificYearFilter.cs: -------------------------------------------------------------------------------- 1 | // ) * 2 | // ( /( * ) ( ( ` 3 | // )\()) ( ` ) /( ( )\ )\))( 4 | // ((_)\ )\ ( )(_)))\ ((((_)( ((_)()\ 5 | // __ ((_)((_) (_(_())((_) )\ _ )\ (_()((_) 6 | // \ \ / / (_) |_ _|| __|(_)_\(_)| \/ | 7 | // \ V / | | _ | | | _| / _ \ | |\/| | 8 | // |_| |_|(_)|_| |___|/_/ \_\ |_| |_| 9 | // 10 | // This file is subject to the terms and conditions defined in 11 | // file 'License.txt', which is part of this source code package. 12 | // 13 | // Copyright © Yi.TEAM. All rights reserved. 14 | // ------------------------------------------------------------------------------- 15 | 16 | namespace Yisoft.Crontab.Filters 17 | { 18 | /// 19 | /// Handles filtering for a specific value 20 | /// 21 | public class SpecificYearFilter : SpecificFilter 22 | { 23 | /// 24 | /// Constructs a new RangeFilter instance 25 | /// 26 | /// The specific value you wish to match 27 | /// The crontab field kind to associate with this filter 28 | public SpecificYearFilter(int specificValue, CrontabFieldKind kind) : base(specificValue, kind) 29 | { 30 | } 31 | 32 | public override int? Next(int value) { return value < SpecificValue ? (int?) SpecificValue : null; } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Yisoft.Crontab/Filters/StepFilter.cs: -------------------------------------------------------------------------------- 1 | // ) * 2 | // ( /( * ) ( ( ` 3 | // )\()) ( ` ) /( ( )\ )\))( 4 | // ((_)\ )\ ( )(_)))\ ((((_)( ((_)()\ 5 | // __ ((_)((_) (_(_())((_) )\ _ )\ (_()((_) 6 | // \ \ / / (_) |_ _|| __|(_)_\(_)| \/ | 7 | // \ V / | | _ | | | _| / _ \ | |\/| | 8 | // |_| |_|(_)|_| |___|/_/ \_\ |_| |_| 9 | // 10 | // This file is subject to the terms and conditions defined in 11 | // file 'License.txt', which is part of this source code package. 12 | // 13 | // Copyright © Yi.TEAM. All rights reserved. 14 | // ------------------------------------------------------------------------------- 15 | 16 | using System; 17 | using System.Collections.Generic; 18 | using Yisoft.Crontab.Extensions; 19 | 20 | namespace Yisoft.Crontab.Filters 21 | { 22 | /// 23 | /// Handles step values (i.e. */5, 2/7) 24 | /// 25 | /// For example, */5 in the minutes field indicates every 5 minutes 26 | /// 27 | /// 28 | public class StepFilter : ICronFilter, ITimeFilter 29 | { 30 | private int? _firstCache; 31 | 32 | /// 33 | /// Constructs a new RangeFilter instance 34 | /// 35 | /// The start of the range 36 | /// step 37 | /// The crontab field kind to associate with this filter 38 | public StepFilter(int start, int step, CrontabFieldKind kind) 39 | { 40 | var maxValue = Constants.MaximumDateTimeValues[kind]; 41 | 42 | if (step <= 0 || step > maxValue) throw new CrontabException($"Steps = {step} is out of bounds for <{Enum.GetName(typeof(CrontabFieldKind), kind)}> field"); 43 | 44 | Start = start; 45 | Step = step; 46 | Kind = kind; 47 | 48 | var filters = new List(); 49 | 50 | for (var evalValue = Start; evalValue <= maxValue; evalValue++) if (_IsMatch(evalValue)) filters.Add(new SpecificFilter(evalValue, Kind)); 51 | 52 | SpecificFilters = filters; 53 | } 54 | 55 | public int Start { get; } 56 | 57 | public int Step { get; } 58 | 59 | /// 60 | /// Returns a list of specific filters that represents this step filter 61 | /// 62 | public IEnumerable SpecificFilters { get; } 63 | 64 | public CrontabFieldKind Kind { get; } 65 | 66 | /// 67 | /// Checks if the value is accepted by the filter 68 | /// 69 | /// The value to check 70 | /// True if the value matches the condition, False if it does not match. 71 | public bool IsMatch(DateTime value) 72 | { 73 | int evalValue; 74 | 75 | switch (Kind) 76 | { 77 | case CrontabFieldKind.Second: 78 | evalValue = value.Second; 79 | break; 80 | case CrontabFieldKind.Minute: 81 | evalValue = value.Minute; 82 | break; 83 | case CrontabFieldKind.Hour: 84 | evalValue = value.Hour; 85 | break; 86 | case CrontabFieldKind.Day: 87 | evalValue = value.Day; 88 | break; 89 | case CrontabFieldKind.Month: 90 | evalValue = value.Month; 91 | break; 92 | case CrontabFieldKind.DayOfWeek: 93 | evalValue = value.DayOfWeek.ToCronDayOfWeek(); 94 | break; 95 | case CrontabFieldKind.Year: 96 | evalValue = value.Year; 97 | break; 98 | default: throw new ArgumentOutOfRangeException(nameof(Kind), Kind, null); 99 | } 100 | 101 | return _IsMatch(evalValue); 102 | } 103 | 104 | public int? Next(int value) 105 | { 106 | if (Kind == CrontabFieldKind.Day 107 | || Kind == CrontabFieldKind.Month 108 | || Kind == CrontabFieldKind.DayOfWeek) throw new CrontabException("Cannot call Next for Day, Month or DayOfWeek types"); 109 | 110 | var max = Constants.MaximumDateTimeValues[Kind]; 111 | 112 | var newValue = (int?) value + 1; 113 | 114 | while (newValue < max && !_IsMatch(newValue.Value)) newValue++; 115 | 116 | if (newValue >= max) newValue = null; 117 | 118 | return newValue; 119 | } 120 | 121 | public int First() 122 | { 123 | if (_firstCache.HasValue) return _firstCache.Value; 124 | 125 | if (Kind == CrontabFieldKind.Day 126 | || Kind == CrontabFieldKind.Month 127 | || Kind == CrontabFieldKind.DayOfWeek) throw new CrontabException("Cannot call First for Day, Month or DayOfWeek types"); 128 | 129 | var max = Constants.MaximumDateTimeValues[Kind]; 130 | 131 | var newValue = 0; 132 | 133 | while (newValue < max && !_IsMatch(newValue)) newValue++; 134 | 135 | if (newValue > max) throw new CrontabException($"Next value for {ToString()} on field {Enum.GetName(typeof(CrontabFieldKind), Kind)} could not be found!"); 136 | 137 | _firstCache = newValue; 138 | 139 | return newValue; 140 | } 141 | 142 | private bool _IsMatch(int evalValue) { return evalValue >= Start && (evalValue - Start) % Step == 0; } 143 | 144 | public override string ToString() { return $"{(Start == 0 ? "*" : Start.ToString())}/{Step}"; } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/Yisoft.Crontab/ICronFilter.cs: -------------------------------------------------------------------------------- 1 | // ) * 2 | // ( /( * ) ( ( ` 3 | // )\()) ( ` ) /( ( )\ )\))( 4 | // ((_)\ )\ ( )(_)))\ ((((_)( ((_)()\ 5 | // __ ((_)((_) (_(_())((_) )\ _ )\ (_()((_) 6 | // \ \ / / (_) |_ _|| __|(_)_\(_)| \/ | 7 | // \ V / | | _ | | | _| / _ \ | |\/| | 8 | // |_| |_|(_)|_| |___|/_/ \_\ |_| |_| 9 | // 10 | // This file is subject to the terms and conditions defined in 11 | // file 'License.txt', which is part of this source code package. 12 | // 13 | // Copyright © Yi.TEAM. All rights reserved. 14 | // ------------------------------------------------------------------------------- 15 | 16 | using System; 17 | 18 | namespace Yisoft.Crontab 19 | { 20 | public interface ICronFilter 21 | { 22 | CrontabFieldKind Kind { get; } 23 | 24 | /// 25 | /// Checks if the value is accepted by the filter 26 | /// 27 | /// The value to check 28 | /// True if the value matches the condition, False if it does not match. 29 | bool IsMatch(DateTime value); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Yisoft.Crontab/ITimeFilter.cs: -------------------------------------------------------------------------------- 1 | // ) * 2 | // ( /( * ) ( ( ` 3 | // )\()) ( ` ) /( ( )\ )\))( 4 | // ((_)\ )\ ( )(_)))\ ((((_)( ((_)()\ 5 | // __ ((_)((_) (_(_())((_) )\ _ )\ (_()((_) 6 | // \ \ / / (_) |_ _|| __|(_)_\(_)| \/ | 7 | // \ V / | | _ | | | _| / _ \ | |\/| | 8 | // |_| |_|(_)|_| |___|/_/ \_\ |_| |_| 9 | // 10 | // This file is subject to the terms and conditions defined in 11 | // file 'License.txt', which is part of this source code package. 12 | // 13 | // Copyright © Yi.TEAM. All rights reserved. 14 | // ------------------------------------------------------------------------------- 15 | 16 | namespace Yisoft.Crontab 17 | { 18 | internal interface ITimeFilter 19 | { 20 | int? Next(int value); 21 | 22 | int First(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Yisoft.Crontab/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | // ) * 2 | // ( /( * ) ( ( ` 3 | // )\()) ( ` ) /( ( )\ )\))( 4 | // ((_)\ )\ ( )(_)))\ ((((_)( ((_)()\ 5 | // __ ((_)((_) (_(_())((_) )\ _ )\ (_()((_) 6 | // \ \ / / (_) |_ _|| __|(_)_\(_)| \/ | 7 | // \ V / | | _ | | | _| / _ \ | |\/| | 8 | // |_| |_|(_)|_| |___|/_/ \_\ |_| |_| 9 | // 10 | // This file is subject to the terms and conditions defined in 11 | // file 'License.txt', which is part of this source code package. 12 | // 13 | // Copyright © Yi.TEAM. All rights reserved. 14 | // ------------------------------------------------------------------------------- 15 | 16 | using System.Reflection; 17 | using System.Resources; 18 | using System.Runtime.InteropServices; 19 | 20 | // General Information about an assembly is controlled through the following 21 | // set of attributes. Change these attribute values to modify the information 22 | // associated with an assembly. 23 | 24 | [assembly: AssemblyConfiguration("")] 25 | [assembly: NeutralResourcesLanguage("en-us")] 26 | [assembly: AssemblyCompany("Yi Team")] 27 | [assembly: AssemblyCopyright("Copyright © Yi.TEAM. All rights reserved.")] 28 | [assembly: AssemblyProduct("Yisoft.Crontab")] 29 | [assembly: AssemblyTrademark("Yi.TEAM")] 30 | 31 | // Setting ComVisible to false makes the types in this assembly not visible 32 | // to COM components. If you need to access a type in this assembly from 33 | // COM, set the ComVisible attribute to true on that type. 34 | 35 | [assembly: ComVisible(false)] 36 | 37 | // The following GUID is for the ID of the typelib if this project is exposed to COM 38 | 39 | [assembly: Guid("f8fec47e-d03a-498f-b925-fac9673f0bea")] 40 | -------------------------------------------------------------------------------- /src/Yisoft.Crontab/Yisoft.Crontab.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | cron expression parser and executor for dotnet core. 5 | Copyright © Yi.TEAM. All rights reserved. 6 | Yisoft.Crontab 7 | 1.3.2 8 | ymind.chan@yi.team 9 | netstandard1.6;net46 10 | Yisoft.Crontab 11 | Yisoft.Crontab 12 | dotnet;crontab;cron;schedule;yisoft;yiteam 13 | https://yi.team/ 14 | https://static.yi.team/images/yiteam-logo-64x64.png 15 | https://github.com/yisoft-dotnet/crontab/blob/master/LICENSE 16 | git 17 | https://github.com/yisoft-dotnet/crontab 18 | false 19 | false 20 | false 21 | false 22 | false 23 | True 24 | 25 | 26 | 27 | 28 | 4.3.0 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /test/Yisoft.Crontab.UnitTests/ConstantTests.cs: -------------------------------------------------------------------------------- 1 | // ) * 2 | // ( /( * ) ( ( ` 3 | // )\()) ( ` ) /( ( )\ )\))( 4 | // ((_)\ )\ ( )(_)))\ ((((_)( ((_)()\ 5 | // __ ((_)((_) (_(_())((_) )\ _ )\ (_()((_) 6 | // \ \ / / (_) |_ _|| __|(_)_\(_)| \/ | 7 | // \ V / | | _ | | | _| / _ \ | |\/| | 8 | // |_| |_|(_)|_| |___|/_/ \_\ |_| |_| 9 | // 10 | // This file is subject to the terms and conditions defined in 11 | // file 'License.txt', which is part of this source code package. 12 | // 13 | // Copyright © Yi.TEAM. All rights reserved. 14 | // ------------------------------------------------------------------------------- 15 | 16 | using System; 17 | using System.Collections; 18 | using Microsoft.VisualStudio.TestTools.UnitTesting; 19 | 20 | namespace Yisoft.Crontab.UnitTests 21 | { 22 | // Why do we test constants? To ensure dictionaries that 23 | // use them are updated as soon as a new value is added! 24 | [TestClass] 25 | public class ConstantTests 26 | { 27 | [TestMethod] 28 | public void VerifyConstants() 29 | { 30 | _ValidateExists(Constants.ExpectedFieldCounts); 31 | _ValidateExists(Constants.MaximumDateTimeValues); 32 | _ValidateExists(Constants.CronDays); 33 | } 34 | 35 | private static void _ValidateExists(IDictionary dictionary) 36 | { 37 | Assert.IsNotNull(dictionary); 38 | 39 | foreach (var value in Enum.GetValues(typeof(T))) Assert.IsTrue(dictionary.Contains(value), "Contains <{0}>", Enum.GetName(typeof(T), value)); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /test/Yisoft.Crontab.UnitTests/CronInstanceTests.cs: -------------------------------------------------------------------------------- 1 | // ) * 2 | // ( /( * ) ( ( ` 3 | // )\()) ( ` ) /( ( )\ )\))( 4 | // ((_)\ )\ ( )(_)))\ ((((_)( ((_)()\ 5 | // __ ((_)((_) (_(_())((_) )\ _ )\ (_()((_) 6 | // \ \ / / (_) |_ _|| __|(_)_\(_)| \/ | 7 | // \ V / | | _ | | | _| / _ \ | |\/| | 8 | // |_| |_|(_)|_| |___|/_/ \_\ |_| |_| 9 | // 10 | // This file is subject to the terms and conditions defined in 11 | // file 'License.txt', which is part of this source code package. 12 | // 13 | // Copyright © Yi.TEAM. All rights reserved. 14 | // ------------------------------------------------------------------------------- 15 | 16 | using System; 17 | using System.Diagnostics; 18 | using System.Globalization; 19 | using System.Linq; 20 | using Microsoft.VisualStudio.TestTools.UnitTesting; 21 | using Yisoft.Crontab.UnitTests.Extensions; 22 | 23 | namespace Yisoft.Crontab.UnitTests 24 | { 25 | [TestClass] 26 | public sealed class CronInstanceTests 27 | { 28 | private const string _TIME_FORMAT = "dd/MM/yyyy HH:mm:ss"; 29 | 30 | [TestMethod] 31 | public void CannotParseNullString() 32 | { 33 | Assert2.Throws(() => CrontabSchedule.Parse(null)); 34 | } 35 | 36 | [TestMethod] 37 | public void CannotParseEmptyString() 38 | { 39 | Assert2.Throws(() => CrontabSchedule.Parse(string.Empty)); 40 | } 41 | 42 | [TestMethod] 43 | public void AllTimeString() 44 | { 45 | var input = "* * * * *"; 46 | var output = CrontabSchedule.Parse(input).ToString(); 47 | Assert.AreEqual(input, output); 48 | } 49 | 50 | [TestMethod] 51 | public void SixPartAllTimeString() 52 | { 53 | Assert.AreEqual("* * * * * *", CrontabSchedule.Parse("* * * * * *", CronStringFormat.WithSeconds).ToString()); 54 | } 55 | 56 | [TestMethod] 57 | public void InvalidPatternCount() 58 | { 59 | Assert2.Throws(() => CrontabSchedule.Parse("* * * *")); 60 | Assert2.Throws(() => CrontabSchedule.Parse("* * * * * *")); 61 | 62 | Assert2.Throws(() => CrontabSchedule.Parse("* * * * *", CronStringFormat.WithSeconds)); 63 | Assert2.Throws(() => CrontabSchedule.Parse("* * * * * * *", CronStringFormat.WithSeconds)); 64 | 65 | Assert2.Throws(() => CrontabSchedule.Parse("* * * * *", CronStringFormat.WithYears)); 66 | Assert2.Throws(() => CrontabSchedule.Parse("* * * * * * *", CronStringFormat.WithYears)); 67 | 68 | Assert2.Throws(() => CrontabSchedule.Parse("* * * * * *", CronStringFormat.WithSecondsAndYears)); 69 | Assert2.Throws(() => CrontabSchedule.Parse("* * * * * * * *", CronStringFormat.WithSecondsAndYears)); 70 | } 71 | 72 | [TestMethod] 73 | public void CannotParseWhenSecondsRequired() 74 | { 75 | Assert2.Throws(() => CrontabSchedule.Parse("* * * * *", CronStringFormat.WithSeconds)); 76 | } 77 | 78 | [TestMethod] 79 | public void Formatting() 80 | { 81 | var tests = new[] 82 | { 83 | new {inputString = "* 1-2,3 * * *", outputString = "* 1-2,3 * * *", cronStringFormat = CronStringFormat.Default}, 84 | new {inputString = "* * * */2 *", outputString = "* * * */2 *", cronStringFormat = CronStringFormat.Default}, 85 | new {inputString = "10-40/15 * * * *", outputString = "10-40/15 * * * *", cronStringFormat = CronStringFormat.Default}, 86 | new {inputString = "* * * Mar,Jan,Aug Fri,Mon-Tue", outputString = "* * * 3,1,8 5,1-2", cronStringFormat = CronStringFormat.Default}, 87 | new {inputString = "1 * 1-2,3 * * *", outputString = "1 * 1-2,3 * * *", cronStringFormat = CronStringFormat.WithSeconds}, 88 | new {inputString = "22 * * * */2 *", outputString = "22 * * * */2 *", cronStringFormat = CronStringFormat.WithSeconds}, 89 | new {inputString = "33 10-40/15 * * * *", outputString = "33 10-40/15 * * * *", cronStringFormat = CronStringFormat.WithSeconds}, 90 | new {inputString = "55 * * * Mar,Jan,Aug Fri,Mon-Tue", outputString = "55 * * * 3,1,8 5,1-2", cronStringFormat = CronStringFormat.WithSeconds} 91 | }; 92 | 93 | foreach (var test in tests) Assert.AreEqual(test.outputString, CrontabSchedule.Parse(test.inputString, test.cronStringFormat).ToString()); 94 | } 95 | 96 | /// 97 | /// Tests to see if the cron class can calculate the previous matching 98 | /// time correctly in various circumstances. 99 | /// 100 | [TestMethod] 101 | public void Evaluations() 102 | { 103 | var tests = new[] 104 | { 105 | new {startTime = "01/01/2016 00:00:00", inputString = "* * ? * *", nextOccurence = "01/01/2016 00:01:00", cronStringFormat = CronStringFormat.Default}, 106 | new {startTime = "01/01/2016 00:01:00", inputString = "* * * * ?", nextOccurence = "01/01/2016 00:02:00", cronStringFormat = CronStringFormat.Default}, 107 | new {startTime = "01/01/2016 00:02:00", inputString = "* * ? * ?", nextOccurence = "01/01/2016 00:03:00", cronStringFormat = CronStringFormat.Default}, 108 | 109 | new {startTime = "01/01/2003 00:00:00", inputString = "* * * * *", nextOccurence = "01/01/2003 00:01:00", cronStringFormat = CronStringFormat.Default}, 110 | new {startTime = "01/01/2003 00:01:00", inputString = "* * * * *", nextOccurence = "01/01/2003 00:02:00", cronStringFormat = CronStringFormat.Default}, 111 | new {startTime = "01/01/2003 00:02:00", inputString = "* * * * *", nextOccurence = "01/01/2003 00:03:00", cronStringFormat = CronStringFormat.Default}, 112 | new {startTime = "01/01/2003 00:59:00", inputString = "* * * * *", nextOccurence = "01/01/2003 01:00:00", cronStringFormat = CronStringFormat.Default}, 113 | new {startTime = "01/01/2003 01:59:00", inputString = "* * * * *", nextOccurence = "01/01/2003 02:00:00", cronStringFormat = CronStringFormat.Default}, 114 | new {startTime = "01/01/2003 23:59:00", inputString = "* * * * *", nextOccurence = "02/01/2003 00:00:00", cronStringFormat = CronStringFormat.Default}, 115 | new {startTime = "31/12/2003 23:59:00", inputString = "* * * * *", nextOccurence = "01/01/2004 00:00:00", cronStringFormat = CronStringFormat.Default}, 116 | new {startTime = "28/02/2003 23:59:00", inputString = "* * * * *", nextOccurence = "01/03/2003 00:00:00", cronStringFormat = CronStringFormat.Default}, 117 | new {startTime = "28/02/2004 23:59:00", inputString = "* * * * *", nextOccurence = "29/02/2004 00:00:00", cronStringFormat = CronStringFormat.Default}, 118 | new 119 | { 120 | startTime = "01/01/2003 00:00:00", 121 | inputString = "* * * * * *", 122 | nextOccurence = "01/01/2003 00:00:01", 123 | cronStringFormat = CronStringFormat.WithSeconds 124 | }, 125 | new 126 | { 127 | startTime = "01/01/2003 00:00:01", 128 | inputString = "* * * * * *", 129 | nextOccurence = "01/01/2003 00:00:02", 130 | cronStringFormat = CronStringFormat.WithSeconds 131 | }, 132 | new 133 | { 134 | startTime = "01/01/2003 00:00:02", 135 | inputString = "* * * * * *", 136 | nextOccurence = "01/01/2003 00:00:03", 137 | cronStringFormat = CronStringFormat.WithSeconds 138 | }, 139 | new 140 | { 141 | startTime = "01/01/2003 00:00:59", 142 | inputString = "* * * * * *", 143 | nextOccurence = "01/01/2003 00:01:00", 144 | cronStringFormat = CronStringFormat.WithSeconds 145 | }, 146 | new 147 | { 148 | startTime = "01/01/2003 00:01:59", 149 | inputString = "* * * * * *", 150 | nextOccurence = "01/01/2003 00:02:00", 151 | cronStringFormat = CronStringFormat.WithSeconds 152 | }, 153 | new 154 | { 155 | startTime = "01/01/2003 23:59:59", 156 | inputString = "* * * * * *", 157 | nextOccurence = "02/01/2003 00:00:00", 158 | cronStringFormat = CronStringFormat.WithSeconds 159 | }, 160 | new 161 | { 162 | startTime = "31/12/2003 23:59:59", 163 | inputString = "* * * * * *", 164 | nextOccurence = "01/01/2004 00:00:00", 165 | cronStringFormat = CronStringFormat.WithSeconds 166 | }, 167 | new 168 | { 169 | startTime = "28/02/2003 23:59:59", 170 | inputString = "* * * * * *", 171 | nextOccurence = "01/03/2003 00:00:00", 172 | cronStringFormat = CronStringFormat.WithSeconds 173 | }, 174 | new 175 | { 176 | startTime = "28/02/2004 23:59:59", 177 | inputString = "* * * * * *", 178 | nextOccurence = "29/02/2004 00:00:00", 179 | cronStringFormat = CronStringFormat.WithSeconds 180 | }, 181 | new {startTime = "01/01/2003 00:00:00", inputString = "* * * * * *", nextOccurence = "01/01/2003 00:01:00", cronStringFormat = CronStringFormat.WithYears}, 182 | new {startTime = "01/01/2003 00:01:00", inputString = "* * * * * *", nextOccurence = "01/01/2003 00:02:00", cronStringFormat = CronStringFormat.WithYears}, 183 | new {startTime = "01/01/2003 00:02:00", inputString = "* * * * * *", nextOccurence = "01/01/2003 00:03:00", cronStringFormat = CronStringFormat.WithYears}, 184 | new {startTime = "01/01/2003 00:59:00", inputString = "* * * * * *", nextOccurence = "01/01/2003 01:00:00", cronStringFormat = CronStringFormat.WithYears}, 185 | new {startTime = "01/01/2003 01:59:00", inputString = "* * * * * *", nextOccurence = "01/01/2003 02:00:00", cronStringFormat = CronStringFormat.WithYears}, 186 | new {startTime = "01/01/2003 23:59:00", inputString = "* * * * * *", nextOccurence = "02/01/2003 00:00:00", cronStringFormat = CronStringFormat.WithYears}, 187 | new {startTime = "31/12/2003 23:59:00", inputString = "* * * * * *", nextOccurence = "01/01/2004 00:00:00", cronStringFormat = CronStringFormat.WithYears}, 188 | new {startTime = "28/02/2003 23:59:00", inputString = "* * * * * *", nextOccurence = "01/03/2003 00:00:00", cronStringFormat = CronStringFormat.WithYears}, 189 | new {startTime = "28/02/2004 23:59:00", inputString = "* * * * * *", nextOccurence = "29/02/2004 00:00:00", cronStringFormat = CronStringFormat.WithYears}, 190 | new 191 | { 192 | startTime = "01/01/2003 00:00:00", 193 | inputString = "* * * * * * *", 194 | nextOccurence = "01/01/2003 00:00:01", 195 | cronStringFormat = CronStringFormat.WithSecondsAndYears 196 | }, 197 | new 198 | { 199 | startTime = "01/01/2003 00:00:01", 200 | inputString = "* * * * * * *", 201 | nextOccurence = "01/01/2003 00:00:02", 202 | cronStringFormat = CronStringFormat.WithSecondsAndYears 203 | }, 204 | new 205 | { 206 | startTime = "01/01/2003 00:00:02", 207 | inputString = "* * * * * * *", 208 | nextOccurence = "01/01/2003 00:00:03", 209 | cronStringFormat = CronStringFormat.WithSecondsAndYears 210 | }, 211 | new 212 | { 213 | startTime = "01/01/2003 00:00:59", 214 | inputString = "* * * * * * *", 215 | nextOccurence = "01/01/2003 00:01:00", 216 | cronStringFormat = CronStringFormat.WithSecondsAndYears 217 | }, 218 | new 219 | { 220 | startTime = "01/01/2003 00:01:59", 221 | inputString = "* * * * * * *", 222 | nextOccurence = "01/01/2003 00:02:00", 223 | cronStringFormat = CronStringFormat.WithSecondsAndYears 224 | }, 225 | new 226 | { 227 | startTime = "01/01/2003 23:59:59", 228 | inputString = "* * * * * * *", 229 | nextOccurence = "02/01/2003 00:00:00", 230 | cronStringFormat = CronStringFormat.WithSecondsAndYears 231 | }, 232 | new 233 | { 234 | startTime = "31/12/2003 23:59:59", 235 | inputString = "* * * * * * *", 236 | nextOccurence = "01/01/2004 00:00:00", 237 | cronStringFormat = CronStringFormat.WithSecondsAndYears 238 | }, 239 | new 240 | { 241 | startTime = "28/02/2003 23:59:59", 242 | inputString = "* * * * * * *", 243 | nextOccurence = "01/03/2003 00:00:00", 244 | cronStringFormat = CronStringFormat.WithSecondsAndYears 245 | }, 246 | new 247 | { 248 | startTime = "28/02/2004 23:59:59", 249 | inputString = "* * * * * * *", 250 | nextOccurence = "29/02/2004 00:00:00", 251 | cronStringFormat = CronStringFormat.WithSecondsAndYears 252 | }, 253 | 254 | // Second tests 255 | new 256 | { 257 | startTime = "01/01/2003 00:00:00", 258 | inputString = "45 * * * * *", 259 | nextOccurence = "01/01/2003 00:00:45", 260 | cronStringFormat = CronStringFormat.WithSeconds 261 | }, 262 | new 263 | { 264 | startTime = "01/01/2003 00:00:00", 265 | inputString = "45 * * * * ?", 266 | nextOccurence = "01/01/2003 00:00:45", 267 | cronStringFormat = CronStringFormat.WithSeconds 268 | }, 269 | new 270 | { 271 | startTime = "01/01/2003 00:00:00", 272 | inputString = "45 * * ? * *", 273 | nextOccurence = "01/01/2003 00:00:45", 274 | cronStringFormat = CronStringFormat.WithSeconds 275 | }, 276 | new 277 | { 278 | startTime = "01/01/2003 00:00:00", 279 | inputString = "45-47,48,49 * * * * *", 280 | nextOccurence = "01/01/2003 00:00:45", 281 | cronStringFormat = CronStringFormat.WithSeconds 282 | }, 283 | new 284 | { 285 | startTime = "01/01/2003 00:00:45", 286 | inputString = "45-47,48,49 * * * * *", 287 | nextOccurence = "01/01/2003 00:00:46", 288 | cronStringFormat = CronStringFormat.WithSeconds 289 | }, 290 | new 291 | { 292 | startTime = "01/01/2003 00:00:46", 293 | inputString = "45-47,48,49 * * * * *", 294 | nextOccurence = "01/01/2003 00:00:47", 295 | cronStringFormat = CronStringFormat.WithSeconds 296 | }, 297 | new 298 | { 299 | startTime = "01/01/2003 00:00:47", 300 | inputString = "45-47,48,49 * * * * *", 301 | nextOccurence = "01/01/2003 00:00:48", 302 | cronStringFormat = CronStringFormat.WithSeconds 303 | }, 304 | new 305 | { 306 | startTime = "01/01/2003 00:00:48", 307 | inputString = "45-47,48,49 * * * * *", 308 | nextOccurence = "01/01/2003 00:00:49", 309 | cronStringFormat = CronStringFormat.WithSeconds 310 | }, 311 | new 312 | { 313 | startTime = "01/01/2003 00:00:49", 314 | inputString = "45-47,48,49 * * * * *", 315 | nextOccurence = "01/01/2003 00:01:45", 316 | cronStringFormat = CronStringFormat.WithSeconds 317 | }, 318 | new 319 | { 320 | startTime = "01/01/2003 00:00:00", 321 | inputString = "2/5 * * * * *", 322 | nextOccurence = "01/01/2003 00:00:02", 323 | cronStringFormat = CronStringFormat.WithSeconds 324 | }, 325 | new 326 | { 327 | startTime = "01/01/2003 00:00:02", 328 | inputString = "2/5 * * * * *", 329 | nextOccurence = "01/01/2003 00:00:07", 330 | cronStringFormat = CronStringFormat.WithSeconds 331 | }, 332 | new 333 | { 334 | startTime = "01/01/2003 00:00:50", 335 | inputString = "2/5 * * * * *", 336 | nextOccurence = "01/01/2003 00:00:52", 337 | cronStringFormat = CronStringFormat.WithSeconds 338 | }, 339 | new 340 | { 341 | startTime = "01/01/2003 00:00:52", 342 | inputString = "2/5 * * * * *", 343 | nextOccurence = "01/01/2003 00:00:57", 344 | cronStringFormat = CronStringFormat.WithSeconds 345 | }, 346 | new 347 | { 348 | startTime = "01/01/2003 00:00:57", 349 | inputString = "2/5 * * * * *", 350 | nextOccurence = "01/01/2003 00:01:02", 351 | cronStringFormat = CronStringFormat.WithSeconds 352 | }, 353 | new 354 | { 355 | startTime = "01/01/2003 00:00:00", 356 | inputString = "45 * * * * * *", 357 | nextOccurence = "01/01/2003 00:00:45", 358 | cronStringFormat = CronStringFormat.WithSecondsAndYears 359 | }, 360 | new 361 | { 362 | startTime = "01/01/2003 00:00:00", 363 | inputString = "45-47,48,49 * * * * * *", 364 | nextOccurence = "01/01/2003 00:00:45", 365 | cronStringFormat = CronStringFormat.WithSecondsAndYears 366 | }, 367 | new 368 | { 369 | startTime = "01/01/2003 00:00:45", 370 | inputString = "45-47,48,49 * * * * * *", 371 | nextOccurence = "01/01/2003 00:00:46", 372 | cronStringFormat = CronStringFormat.WithSecondsAndYears 373 | }, 374 | new 375 | { 376 | startTime = "01/01/2003 00:00:46", 377 | inputString = "45-47,48,49 * * * * * *", 378 | nextOccurence = "01/01/2003 00:00:47", 379 | cronStringFormat = CronStringFormat.WithSecondsAndYears 380 | }, 381 | new 382 | { 383 | startTime = "01/01/2003 00:00:47", 384 | inputString = "45-47,48,49 * * * * * *", 385 | nextOccurence = "01/01/2003 00:00:48", 386 | cronStringFormat = CronStringFormat.WithSecondsAndYears 387 | }, 388 | new 389 | { 390 | startTime = "01/01/2003 00:00:48", 391 | inputString = "45-47,48,49 * * * * * *", 392 | nextOccurence = "01/01/2003 00:00:49", 393 | cronStringFormat = CronStringFormat.WithSecondsAndYears 394 | }, 395 | new 396 | { 397 | startTime = "01/01/2003 00:00:49", 398 | inputString = "45-47,48,49 * * * * * *", 399 | nextOccurence = "01/01/2003 00:01:45", 400 | cronStringFormat = CronStringFormat.WithSecondsAndYears 401 | }, 402 | new 403 | { 404 | startTime = "01/01/2003 00:00:00", 405 | inputString = "2/5 * * * * * *", 406 | nextOccurence = "01/01/2003 00:00:02", 407 | cronStringFormat = CronStringFormat.WithSecondsAndYears 408 | }, 409 | new 410 | { 411 | startTime = "01/01/2003 00:00:02", 412 | inputString = "2/5 * * * * * *", 413 | nextOccurence = "01/01/2003 00:00:07", 414 | cronStringFormat = CronStringFormat.WithSecondsAndYears 415 | }, 416 | new 417 | { 418 | startTime = "01/01/2003 00:00:50", 419 | inputString = "2/5 * * * * * *", 420 | nextOccurence = "01/01/2003 00:00:52", 421 | cronStringFormat = CronStringFormat.WithSecondsAndYears 422 | }, 423 | new 424 | { 425 | startTime = "01/01/2003 00:00:52", 426 | inputString = "2/5 * * * * * *", 427 | nextOccurence = "01/01/2003 00:00:57", 428 | cronStringFormat = CronStringFormat.WithSecondsAndYears 429 | }, 430 | new 431 | { 432 | startTime = "01/01/2003 00:00:57", 433 | inputString = "2/5 * * * * * *", 434 | nextOccurence = "01/01/2003 00:01:02", 435 | cronStringFormat = CronStringFormat.WithSecondsAndYears 436 | }, 437 | 438 | // Minute tests 439 | new {startTime = "01/01/2003 00:00:00", inputString = "45 * * * *", nextOccurence = "01/01/2003 00:45:00", cronStringFormat = CronStringFormat.Default}, 440 | new 441 | { 442 | startTime = "01/01/2003 00:00:00", 443 | inputString = "45-47,48,49 * * * *", 444 | nextOccurence = "01/01/2003 00:45:00", 445 | cronStringFormat = CronStringFormat.Default 446 | }, 447 | new 448 | { 449 | startTime = "01/01/2003 00:45:00", 450 | inputString = "45-47,48,49 * * * *", 451 | nextOccurence = "01/01/2003 00:46:00", 452 | cronStringFormat = CronStringFormat.Default 453 | }, 454 | new 455 | { 456 | startTime = "01/01/2003 00:46:00", 457 | inputString = "45-47,48,49 * * * *", 458 | nextOccurence = "01/01/2003 00:47:00", 459 | cronStringFormat = CronStringFormat.Default 460 | }, 461 | new 462 | { 463 | startTime = "01/01/2003 00:47:00", 464 | inputString = "45-47,48,49 * * * *", 465 | nextOccurence = "01/01/2003 00:48:00", 466 | cronStringFormat = CronStringFormat.Default 467 | }, 468 | new 469 | { 470 | startTime = "01/01/2003 00:48:00", 471 | inputString = "45-47,48,49 * * * *", 472 | nextOccurence = "01/01/2003 00:49:00", 473 | cronStringFormat = CronStringFormat.Default 474 | }, 475 | new 476 | { 477 | startTime = "01/01/2003 00:49:00", 478 | inputString = "45-47,48,49 * * * *", 479 | nextOccurence = "01/01/2003 01:45:00", 480 | cronStringFormat = CronStringFormat.Default 481 | }, 482 | new {startTime = "01/01/2003 00:00:00", inputString = "2/5 * * * *", nextOccurence = "01/01/2003 00:02:00", cronStringFormat = CronStringFormat.Default}, 483 | new {startTime = "01/01/2003 00:02:00", inputString = "2/5 * * * *", nextOccurence = "01/01/2003 00:07:00", cronStringFormat = CronStringFormat.Default}, 484 | new {startTime = "01/01/2003 00:50:00", inputString = "2/5 * * * *", nextOccurence = "01/01/2003 00:52:00", cronStringFormat = CronStringFormat.Default}, 485 | new {startTime = "01/01/2003 00:52:00", inputString = "2/5 * * * *", nextOccurence = "01/01/2003 00:57:00", cronStringFormat = CronStringFormat.Default}, 486 | new {startTime = "01/01/2003 00:57:00", inputString = "2/5 * * * *", nextOccurence = "01/01/2003 01:02:00", cronStringFormat = CronStringFormat.Default}, 487 | new 488 | { 489 | startTime = "01/01/2003 00:00:30", 490 | inputString = "3 45 * * * *", 491 | nextOccurence = "01/01/2003 00:45:03", 492 | cronStringFormat = CronStringFormat.WithSeconds 493 | }, 494 | new 495 | { 496 | startTime = "01/01/2003 00:00:30", 497 | inputString = "6 45-47,48,49 * * * *", 498 | nextOccurence = "01/01/2003 00:45:06", 499 | cronStringFormat = CronStringFormat.WithSeconds 500 | }, 501 | new 502 | { 503 | startTime = "01/01/2003 00:45:30", 504 | inputString = "6 45-47,48,49 * * * *", 505 | nextOccurence = "01/01/2003 00:46:06", 506 | cronStringFormat = CronStringFormat.WithSeconds 507 | }, 508 | new 509 | { 510 | startTime = "01/01/2003 00:46:30", 511 | inputString = "6 45-47,48,49 * * * *", 512 | nextOccurence = "01/01/2003 00:47:06", 513 | cronStringFormat = CronStringFormat.WithSeconds 514 | }, 515 | new 516 | { 517 | startTime = "01/01/2003 00:47:30", 518 | inputString = "6 45-47,48,49 * * * *", 519 | nextOccurence = "01/01/2003 00:48:06", 520 | cronStringFormat = CronStringFormat.WithSeconds 521 | }, 522 | new 523 | { 524 | startTime = "01/01/2003 00:48:30", 525 | inputString = "6 45-47,48,49 * * * *", 526 | nextOccurence = "01/01/2003 00:49:06", 527 | cronStringFormat = CronStringFormat.WithSeconds 528 | }, 529 | new 530 | { 531 | startTime = "01/01/2003 00:49:30", 532 | inputString = "6 45-47,48,49 * * * *", 533 | nextOccurence = "01/01/2003 01:45:06", 534 | cronStringFormat = CronStringFormat.WithSeconds 535 | }, 536 | new 537 | { 538 | startTime = "01/01/2003 00:00:30", 539 | inputString = "9 2/5 * * * *", 540 | nextOccurence = "01/01/2003 00:02:09", 541 | cronStringFormat = CronStringFormat.WithSeconds 542 | }, 543 | new 544 | { 545 | startTime = "01/01/2003 00:02:30", 546 | inputString = "9 2/5 * * * *", 547 | nextOccurence = "01/01/2003 00:07:09", 548 | cronStringFormat = CronStringFormat.WithSeconds 549 | }, 550 | new 551 | { 552 | startTime = "01/01/2003 00:50:30", 553 | inputString = "9 2/5 * * * *", 554 | nextOccurence = "01/01/2003 00:52:09", 555 | cronStringFormat = CronStringFormat.WithSeconds 556 | }, 557 | new 558 | { 559 | startTime = "01/01/2003 00:52:30", 560 | inputString = "9 2/5 * * * *", 561 | nextOccurence = "01/01/2003 00:57:09", 562 | cronStringFormat = CronStringFormat.WithSeconds 563 | }, 564 | new 565 | { 566 | startTime = "01/01/2003 00:57:30", 567 | inputString = "9 2/5 * * * *", 568 | nextOccurence = "01/01/2003 01:02:09", 569 | cronStringFormat = CronStringFormat.WithSeconds 570 | }, 571 | 572 | // Hour tests 573 | new {startTime = "20/12/2003 10:00:00", inputString = " * 3/4 * * *", nextOccurence = "20/12/2003 11:00:00", cronStringFormat = CronStringFormat.Default}, 574 | new {startTime = "20/12/2003 00:30:00", inputString = " * 3 * * *", nextOccurence = "20/12/2003 03:00:00", cronStringFormat = CronStringFormat.Default}, 575 | new {startTime = "20/12/2003 01:45:00", inputString = "30 3 * * *", nextOccurence = "20/12/2003 03:30:00", cronStringFormat = CronStringFormat.Default}, 576 | 577 | // Day of month tests 578 | new {startTime = "07/01/2003 00:00:00", inputString = "30 * 1 * *", nextOccurence = "01/02/2003 00:30:00", cronStringFormat = CronStringFormat.Default}, 579 | new {startTime = "01/02/2003 00:30:00", inputString = "30 * 1 * *", nextOccurence = "01/02/2003 01:30:00", cronStringFormat = CronStringFormat.Default}, 580 | new 581 | { 582 | startTime = "01/01/2003 00:00:00", 583 | inputString = "10 * 22 * *", 584 | nextOccurence = "22/01/2003 00:10:00", 585 | cronStringFormat = CronStringFormat.Default 586 | }, 587 | new 588 | { 589 | startTime = "01/01/2003 00:00:00", 590 | inputString = "30 23 19 * *", 591 | nextOccurence = "19/01/2003 23:30:00", 592 | cronStringFormat = CronStringFormat.Default 593 | }, 594 | new 595 | { 596 | startTime = "01/01/2003 00:00:00", 597 | inputString = "30 23 21 * *", 598 | nextOccurence = "21/01/2003 23:30:00", 599 | cronStringFormat = CronStringFormat.Default 600 | }, 601 | new 602 | { 603 | startTime = "01/01/2003 00:01:00", 604 | inputString = " * * 21 * *", 605 | nextOccurence = "21/01/2003 00:00:00", 606 | cronStringFormat = CronStringFormat.Default 607 | }, 608 | new 609 | { 610 | startTime = "10/07/2003 00:00:00", 611 | inputString = " * * 30,31 * *", 612 | nextOccurence = "30/07/2003 00:00:00", 613 | cronStringFormat = CronStringFormat.Default 614 | }, 615 | new {startTime = "20/01/2016 00:00:00", inputString = " * * 1W * *", nextOccurence = "01/02/2016 00:00:00", cronStringFormat = CronStringFormat.Default}, 616 | new {startTime = "28/04/2016 00:00:00", inputString = " * * 1W * *", nextOccurence = "02/05/2016 00:00:00", cronStringFormat = CronStringFormat.Default}, 617 | new {startTime = "30/09/2016 00:00:00", inputString = " * * 1W * *", nextOccurence = "03/10/2016 00:00:00", cronStringFormat = CronStringFormat.Default}, 618 | new {startTime = "01/02/2003 00:00:00", inputString = " * * 15W * *", nextOccurence = "14/02/2003 00:00:00", cronStringFormat = CronStringFormat.Default}, 619 | new {startTime = "01/06/2003 00:00:00", inputString = " * * 15W * *", nextOccurence = "16/06/2003 00:00:00", cronStringFormat = CronStringFormat.Default}, 620 | new {startTime = "10/08/2003 00:00:00", inputString = " * * LW * *", nextOccurence = "29/08/2003 00:00:00", cronStringFormat = CronStringFormat.Default}, 621 | new {startTime = "10/10/2015 00:00:00", inputString = " * * LW * *", nextOccurence = "30/10/2015 00:00:00", cronStringFormat = CronStringFormat.Default}, 622 | new {startTime = "10/07/2003 00:00:00", inputString = " * * L * *", nextOccurence = "31/07/2003 00:00:00", cronStringFormat = CronStringFormat.Default}, 623 | new {startTime = "01/02/2015 00:00:00", inputString = " * * L * *", nextOccurence = "28/02/2015 00:00:00", cronStringFormat = CronStringFormat.Default}, 624 | new {startTime = "01/02/2016 00:00:00", inputString = " * * L * *", nextOccurence = "29/02/2016 00:00:00", cronStringFormat = CronStringFormat.Default}, 625 | 626 | // Test month rollovers for months with 28,29,30 and 31 days 627 | new {startTime = "28/02/2002 23:59:59", inputString = "* * * 3 *", nextOccurence = "01/03/2002 00:00:00", cronStringFormat = CronStringFormat.Default}, 628 | new {startTime = "29/02/2004 23:59:59", inputString = "* * * 3 *", nextOccurence = "01/03/2004 00:00:00", cronStringFormat = CronStringFormat.Default}, 629 | new {startTime = "31/03/2002 23:59:59", inputString = "* * * 4 *", nextOccurence = "01/04/2002 00:00:00", cronStringFormat = CronStringFormat.Default}, 630 | new {startTime = "30/04/2002 23:59:59", inputString = "* * * 5 *", nextOccurence = "01/05/2002 00:00:00", cronStringFormat = CronStringFormat.Default}, 631 | 632 | // Test month 30,31 days 633 | new 634 | { 635 | startTime = "01/01/2000 00:00:00", 636 | inputString = "0 0 15,30,31 * *", 637 | nextOccurence = "15/01/2000 00:00:00", 638 | cronStringFormat = CronStringFormat.Default 639 | }, 640 | new 641 | { 642 | startTime = "15/01/2000 00:00:00", 643 | inputString = "0 0 15,30,31 * *", 644 | nextOccurence = "30/01/2000 00:00:00", 645 | cronStringFormat = CronStringFormat.Default 646 | }, 647 | new 648 | { 649 | startTime = "30/01/2000 00:00:00", 650 | inputString = "0 0 15,30,31 * *", 651 | nextOccurence = "31/01/2000 00:00:00", 652 | cronStringFormat = CronStringFormat.Default 653 | }, 654 | new 655 | { 656 | startTime = "31/01/2000 00:00:00", 657 | inputString = "0 0 15,30,31 * *", 658 | nextOccurence = "15/02/2000 00:00:00", 659 | cronStringFormat = CronStringFormat.Default 660 | }, 661 | new 662 | { 663 | startTime = "15/02/2000 00:00:00", 664 | inputString = "0 0 15,30,31 * *", 665 | nextOccurence = "15/03/2000 00:00:00", 666 | cronStringFormat = CronStringFormat.Default 667 | }, 668 | new 669 | { 670 | startTime = "15/03/2000 00:00:00", 671 | inputString = "0 0 15,30,31 * *", 672 | nextOccurence = "30/03/2000 00:00:00", 673 | cronStringFormat = CronStringFormat.Default 674 | }, 675 | new 676 | { 677 | startTime = "30/03/2000 00:00:00", 678 | inputString = "0 0 15,30,31 * *", 679 | nextOccurence = "31/03/2000 00:00:00", 680 | cronStringFormat = CronStringFormat.Default 681 | }, 682 | new 683 | { 684 | startTime = "31/03/2000 00:00:00", 685 | inputString = "0 0 15,30,31 * *", 686 | nextOccurence = "15/04/2000 00:00:00", 687 | cronStringFormat = CronStringFormat.Default 688 | }, 689 | new 690 | { 691 | startTime = "15/04/2000 00:00:00", 692 | inputString = "0 0 15,30,31 * *", 693 | nextOccurence = "30/04/2000 00:00:00", 694 | cronStringFormat = CronStringFormat.Default 695 | }, 696 | new 697 | { 698 | startTime = "30/04/2000 00:00:00", 699 | inputString = "0 0 15,30,31 * *", 700 | nextOccurence = "15/05/2000 00:00:00", 701 | cronStringFormat = CronStringFormat.Default 702 | }, 703 | new 704 | { 705 | startTime = "15/05/2000 00:00:00", 706 | inputString = "0 0 15,30,31 * *", 707 | nextOccurence = "30/05/2000 00:00:00", 708 | cronStringFormat = CronStringFormat.Default 709 | }, 710 | new 711 | { 712 | startTime = "30/05/2000 00:00:00", 713 | inputString = "0 0 15,30,31 * *", 714 | nextOccurence = "31/05/2000 00:00:00", 715 | cronStringFormat = CronStringFormat.Default 716 | }, 717 | new 718 | { 719 | startTime = "31/05/2000 00:00:00", 720 | inputString = "0 0 15,30,31 * *", 721 | nextOccurence = "15/06/2000 00:00:00", 722 | cronStringFormat = CronStringFormat.Default 723 | }, 724 | new 725 | { 726 | startTime = "15/06/2000 00:00:00", 727 | inputString = "0 0 15,30,31 * *", 728 | nextOccurence = "30/06/2000 00:00:00", 729 | cronStringFormat = CronStringFormat.Default 730 | }, 731 | new 732 | { 733 | startTime = "30/06/2000 00:00:00", 734 | inputString = "0 0 15,30,31 * *", 735 | nextOccurence = "15/07/2000 00:00:00", 736 | cronStringFormat = CronStringFormat.Default 737 | }, 738 | new 739 | { 740 | startTime = "15/07/2000 00:00:00", 741 | inputString = "0 0 15,30,31 * *", 742 | nextOccurence = "30/07/2000 00:00:00", 743 | cronStringFormat = CronStringFormat.Default 744 | }, 745 | new 746 | { 747 | startTime = "30/07/2000 00:00:00", 748 | inputString = "0 0 15,30,31 * *", 749 | nextOccurence = "31/07/2000 00:00:00", 750 | cronStringFormat = CronStringFormat.Default 751 | }, 752 | new 753 | { 754 | startTime = "31/07/2000 00:00:00", 755 | inputString = "0 0 15,30,31 * *", 756 | nextOccurence = "15/08/2000 00:00:00", 757 | cronStringFormat = CronStringFormat.Default 758 | }, 759 | new 760 | { 761 | startTime = "15/08/2000 00:00:00", 762 | inputString = "0 0 15,30,31 * *", 763 | nextOccurence = "30/08/2000 00:00:00", 764 | cronStringFormat = CronStringFormat.Default 765 | }, 766 | new 767 | { 768 | startTime = "30/08/2000 00:00:00", 769 | inputString = "0 0 15,30,31 * *", 770 | nextOccurence = "31/08/2000 00:00:00", 771 | cronStringFormat = CronStringFormat.Default 772 | }, 773 | new 774 | { 775 | startTime = "31/08/2000 00:00:00", 776 | inputString = "0 0 15,30,31 * *", 777 | nextOccurence = "15/09/2000 00:00:00", 778 | cronStringFormat = CronStringFormat.Default 779 | }, 780 | new 781 | { 782 | startTime = "15/09/2000 00:00:00", 783 | inputString = "0 0 15,30,31 * *", 784 | nextOccurence = "30/09/2000 00:00:00", 785 | cronStringFormat = CronStringFormat.Default 786 | }, 787 | new 788 | { 789 | startTime = "30/09/2000 00:00:00", 790 | inputString = "0 0 15,30,31 * *", 791 | nextOccurence = "15/10/2000 00:00:00", 792 | cronStringFormat = CronStringFormat.Default 793 | }, 794 | new 795 | { 796 | startTime = "15/10/2000 00:00:00", 797 | inputString = "0 0 15,30,31 * *", 798 | nextOccurence = "30/10/2000 00:00:00", 799 | cronStringFormat = CronStringFormat.Default 800 | }, 801 | new 802 | { 803 | startTime = "30/10/2000 00:00:00", 804 | inputString = "0 0 15,30,31 * *", 805 | nextOccurence = "31/10/2000 00:00:00", 806 | cronStringFormat = CronStringFormat.Default 807 | }, 808 | new 809 | { 810 | startTime = "31/10/2000 00:00:00", 811 | inputString = "0 0 15,30,31 * *", 812 | nextOccurence = "15/11/2000 00:00:00", 813 | cronStringFormat = CronStringFormat.Default 814 | }, 815 | new 816 | { 817 | startTime = "15/11/2000 00:00:00", 818 | inputString = "0 0 15,30,31 * *", 819 | nextOccurence = "30/11/2000 00:00:00", 820 | cronStringFormat = CronStringFormat.Default 821 | }, 822 | new 823 | { 824 | startTime = "30/11/2000 00:00:00", 825 | inputString = "0 0 15,30,31 * *", 826 | nextOccurence = "15/12/2000 00:00:00", 827 | cronStringFormat = CronStringFormat.Default 828 | }, 829 | new 830 | { 831 | startTime = "15/12/2000 00:00:00", 832 | inputString = "0 0 15,30,31 * *", 833 | nextOccurence = "30/12/2000 00:00:00", 834 | cronStringFormat = CronStringFormat.Default 835 | }, 836 | new 837 | { 838 | startTime = "30/12/2000 00:00:00", 839 | inputString = "0 0 15,30,31 * *", 840 | nextOccurence = "31/12/2000 00:00:00", 841 | cronStringFormat = CronStringFormat.Default 842 | }, 843 | new 844 | { 845 | startTime = "31/12/2000 00:00:00", 846 | inputString = "0 0 15,30,31 * *", 847 | nextOccurence = "15/01/2001 00:00:00", 848 | cronStringFormat = CronStringFormat.Default 849 | }, 850 | 851 | // Other month tests (including year rollover) 852 | new {startTime = "01/12/2003 05:00:00", inputString = "10 * * 6 *", nextOccurence = "01/06/2004 00:10:00", cronStringFormat = CronStringFormat.Default}, 853 | new {startTime = "04/01/2003 00:00:00", inputString = " 1 2 3 * *", nextOccurence = "03/02/2003 02:01:00", cronStringFormat = CronStringFormat.Default}, 854 | new 855 | { 856 | startTime = "01/07/2002 05:00:00", 857 | inputString = "10 * * February,April-Jun *", 858 | nextOccurence = "01/02/2003 00:10:00", 859 | cronStringFormat = CronStringFormat.Default 860 | }, 861 | new {startTime = "01/01/2003 00:00:00", inputString = "0 12 1 6 *", nextOccurence = "01/06/2003 12:00:00", cronStringFormat = CronStringFormat.Default}, 862 | new {startTime = "11/09/1988 14:23:00", inputString = "* 12 1 6 *", nextOccurence = "01/06/1989 12:00:00", cronStringFormat = CronStringFormat.Default}, 863 | new {startTime = "11/03/1988 14:23:00", inputString = "* 12 1 6 *", nextOccurence = "01/06/1988 12:00:00", cronStringFormat = CronStringFormat.Default}, 864 | new 865 | { 866 | startTime = "11/03/1988 14:23:00", 867 | inputString = "* 2,4-8,15 * 6 *", 868 | nextOccurence = "01/06/1988 02:00:00", 869 | cronStringFormat = CronStringFormat.Default 870 | }, 871 | new 872 | { 873 | startTime = "11/03/1988 14:23:00", 874 | inputString = "20 * * january,FeB,Mar,april,May,JuNE,July,Augu,SEPT-October,Nov,DECEM *", 875 | nextOccurence = "11/03/1988 15:20:00", 876 | cronStringFormat = CronStringFormat.Default 877 | }, 878 | new 879 | { 880 | startTime = "11/09/1988 14:23:00", 881 | inputString = "* 12 1 6 * 1988,1989", 882 | nextOccurence = "01/06/1989 12:00:00", 883 | cronStringFormat = CronStringFormat.WithYears 884 | }, 885 | new 886 | { 887 | startTime = "11/09/1988 14:23:00", 888 | inputString = "* 12 1 6 * 1988,2000", 889 | nextOccurence = "01/06/2000 12:00:00", 890 | cronStringFormat = CronStringFormat.WithYears 891 | }, 892 | new 893 | { 894 | startTime = "11/09/1988 14:23:00", 895 | inputString = "* 12 1 6 * 1988/5", 896 | nextOccurence = "01/06/1993 12:00:00", 897 | cronStringFormat = CronStringFormat.WithYears 898 | }, 899 | 900 | // Day of week tests 901 | new {startTime = "26/06/2003 10:00:00", inputString = "30 6 * * 0", nextOccurence = "29/06/2003 06:30:00", cronStringFormat = CronStringFormat.Default}, 902 | new 903 | { 904 | startTime = "26/06/2003 10:00:00", 905 | inputString = "30 6 * * sunday", 906 | nextOccurence = "29/06/2003 06:30:00", 907 | cronStringFormat = CronStringFormat.Default 908 | }, 909 | new 910 | { 911 | startTime = "26/06/2003 10:00:00", 912 | inputString = "30 6 * * SUNDAY", 913 | nextOccurence = "29/06/2003 06:30:00", 914 | cronStringFormat = CronStringFormat.Default 915 | }, 916 | new {startTime = "19/06/2003 00:00:00", inputString = "1 12 * * 2", nextOccurence = "24/06/2003 12:01:00", cronStringFormat = CronStringFormat.Default}, 917 | new {startTime = "24/06/2003 12:01:00", inputString = "1 12 * * 2", nextOccurence = "01/07/2003 12:01:00", cronStringFormat = CronStringFormat.Default}, 918 | new {startTime = "01/06/2003 14:55:00", inputString = "15 18 * * Mon", nextOccurence = "02/06/2003 18:15:00", cronStringFormat = CronStringFormat.Default}, 919 | new {startTime = "02/06/2003 18:15:00", inputString = "15 18 * * Mon", nextOccurence = "09/06/2003 18:15:00", cronStringFormat = CronStringFormat.Default}, 920 | new {startTime = "09/06/2003 18:15:00", inputString = "15 18 * * Mon", nextOccurence = "16/06/2003 18:15:00", cronStringFormat = CronStringFormat.Default}, 921 | new {startTime = "16/06/2003 18:15:00", inputString = "15 18 * * Mon", nextOccurence = "23/06/2003 18:15:00", cronStringFormat = CronStringFormat.Default}, 922 | new {startTime = "23/06/2003 18:15:00", inputString = "15 18 * * Mon", nextOccurence = "30/06/2003 18:15:00", cronStringFormat = CronStringFormat.Default}, 923 | new {startTime = "30/06/2003 18:15:00", inputString = "15 18 * * Mon", nextOccurence = "07/07/2003 18:15:00", cronStringFormat = CronStringFormat.Default}, 924 | new {startTime = "01/01/2003 00:00:00", inputString = "* * * * Mon", nextOccurence = "06/01/2003 00:00:00", cronStringFormat = CronStringFormat.Default}, 925 | new {startTime = "01/01/2003 12:00:00", inputString = "45 16 1 * Mon", nextOccurence = "01/09/2003 16:45:00", cronStringFormat = CronStringFormat.Default}, 926 | new {startTime = "01/09/2003 23:45:00", inputString = "45 16 1 * Mon", nextOccurence = "01/12/2003 16:45:00", cronStringFormat = CronStringFormat.Default}, 927 | new 928 | { 929 | startTime = "01/09/2003 23:45:00", 930 | inputString = "45 16 * * Mon#2", 931 | nextOccurence = "08/09/2003 16:45:00", 932 | cronStringFormat = CronStringFormat.Default 933 | }, 934 | new {startTime = "01/09/2003 23:45:00", inputString = "45 16 * * 2#4", nextOccurence = "23/09/2003 16:45:00", cronStringFormat = CronStringFormat.Default}, 935 | new {startTime = "01/01/2003 23:45:00", inputString = "0 0 * * 0L", nextOccurence = "26/01/2003 00:00:00", cronStringFormat = CronStringFormat.Default}, 936 | new {startTime = "01/01/2003 23:45:00", inputString = "0 0 * * SUNL", nextOccurence = "26/01/2003 00:00:00", cronStringFormat = CronStringFormat.Default}, 937 | new {startTime = "01/01/2003 23:45:00", inputString = "0 0 * * SUNDL", nextOccurence = "26/01/2003 00:00:00", cronStringFormat = CronStringFormat.Default}, 938 | new 939 | { 940 | startTime = "01/01/2003 23:45:00", 941 | inputString = "0 0 * * SUNDAYL", 942 | nextOccurence = "26/01/2003 00:00:00", 943 | cronStringFormat = CronStringFormat.Default 944 | }, 945 | new {startTime = "01/01/2003 23:45:00", inputString = "0 0 * * 6L", nextOccurence = "25/01/2003 00:00:00", cronStringFormat = CronStringFormat.Default}, 946 | new {startTime = "01/01/2003 23:45:00", inputString = "0 0 * * SATL", nextOccurence = "25/01/2003 00:00:00", cronStringFormat = CronStringFormat.Default}, 947 | new {startTime = "01/01/2003 23:45:00", inputString = "0 0 * * SATUL", nextOccurence = "25/01/2003 00:00:00", cronStringFormat = CronStringFormat.Default}, 948 | new 949 | { 950 | startTime = "01/01/2003 23:45:00", 951 | inputString = "0 0 * * SATURDAYL", 952 | nextOccurence = "25/01/2003 00:00:00", 953 | cronStringFormat = CronStringFormat.Default 954 | }, 955 | 956 | // Leap year tests 957 | new {startTime = "01/01/2000 12:00:00", inputString = "1 12 29 2 *", nextOccurence = "29/02/2000 12:01:00", cronStringFormat = CronStringFormat.Default}, 958 | new {startTime = "29/02/2000 12:01:00", inputString = "1 12 29 2 *", nextOccurence = "29/02/2004 12:01:00", cronStringFormat = CronStringFormat.Default}, 959 | new {startTime = "29/02/2004 12:01:00", inputString = "1 12 29 2 *", nextOccurence = "29/02/2008 12:01:00", cronStringFormat = CronStringFormat.Default}, 960 | new 961 | { 962 | startTime = "29/02/2004 12:01:00", 963 | inputString = "1 12 29 2 * */3", 964 | nextOccurence = "29/02/2016 12:01:00", 965 | cronStringFormat = CronStringFormat.WithYears 966 | }, 967 | new 968 | { 969 | startTime = "29/02/2004 12:01:00", 970 | inputString = "1 12 29 2 * 2005/3", 971 | nextOccurence = "29/02/2008 12:01:00", 972 | cronStringFormat = CronStringFormat.WithYears 973 | }, 974 | new 975 | { 976 | startTime = "29/02/2004 12:01:00", 977 | inputString = "1 12 29 2 * 2002/7", 978 | nextOccurence = "29/02/2016 12:01:00", 979 | cronStringFormat = CronStringFormat.WithYears 980 | }, 981 | 982 | // Non-leap year tests 983 | new {startTime = "01/01/2000 12:00:00", inputString = "1 12 28 2 *", nextOccurence = "28/02/2000 12:01:00", cronStringFormat = CronStringFormat.Default}, 984 | new {startTime = "28/02/2000 12:01:00", inputString = "1 12 28 2 *", nextOccurence = "28/02/2001 12:01:00", cronStringFormat = CronStringFormat.Default}, 985 | new {startTime = "28/02/2001 12:01:00", inputString = "1 12 28 2 *", nextOccurence = "28/02/2002 12:01:00", cronStringFormat = CronStringFormat.Default}, 986 | new {startTime = "28/02/2002 12:01:00", inputString = "1 12 28 2 *", nextOccurence = "28/02/2003 12:01:00", cronStringFormat = CronStringFormat.Default}, 987 | new {startTime = "28/02/2003 12:01:00", inputString = "1 12 28 2 *", nextOccurence = "28/02/2004 12:01:00", cronStringFormat = CronStringFormat.Default}, 988 | new {startTime = "29/02/2004 12:01:00", inputString = "1 12 28 2 *", nextOccurence = "28/02/2005 12:01:00", cronStringFormat = CronStringFormat.Default} 989 | 990 | // ? filter tests 991 | }; 992 | 993 | foreach (var test in tests) _CronCall(test.startTime, test.inputString, test.nextOccurence, test.cronStringFormat); 994 | } 995 | 996 | [TestMethod] 997 | public void EvaluationsBlank() 998 | { 999 | var tests = new[] 1000 | { 1001 | // Fire at 12pm (noon) every day 1002 | new 1003 | { 1004 | startTime = "22/05/1983 00:00:00", 1005 | inputString = "0 0 12 * * ?", 1006 | nextOccurence = "22/05/1983 12:00:00", 1007 | cronStringFormat = CronStringFormat.WithSeconds 1008 | }, 1009 | 1010 | // Fire at 10:15am every day 1011 | new 1012 | { 1013 | startTime = "22/05/1983 00:00:00", 1014 | inputString = "0 15 10 ? * *", 1015 | nextOccurence = "22/05/1983 10:15:00", 1016 | cronStringFormat = CronStringFormat.WithSeconds 1017 | }, 1018 | new 1019 | { 1020 | startTime = "22/05/1983 00:00:00", 1021 | inputString = "0 15 10 * * ?", 1022 | nextOccurence = "22/05/1983 10:15:00", 1023 | cronStringFormat = CronStringFormat.WithSeconds 1024 | }, 1025 | new 1026 | { 1027 | startTime = "22/05/1983 00:00:00", 1028 | inputString = "0 15 10 * * ? *", 1029 | nextOccurence = "22/05/1983 10:15:00", 1030 | cronStringFormat = CronStringFormat.WithSecondsAndYears 1031 | }, 1032 | 1033 | //Fire at 2:10pm and at 2:44pm every Wednesday in the month of March. 1034 | new 1035 | { 1036 | startTime = "22/05/1983 00:00:00", 1037 | inputString = "0 10,44 14 ? 3 WED", 1038 | nextOccurence = "07/03/1984 14:10:00", 1039 | cronStringFormat = CronStringFormat.WithSeconds 1040 | }, 1041 | 1042 | // Fire at 10:15 AM on the last day of every month 1043 | new 1044 | { 1045 | startTime = "22/05/1983 00:00:00", 1046 | inputString = "0 15 10 L * ?", 1047 | nextOccurence = "31/05/1983 10:15:00", 1048 | cronStringFormat = CronStringFormat.WithSeconds 1049 | }, 1050 | 1051 | // Fire at 10:15 AM on the last Friday of every month 1052 | new 1053 | { 1054 | startTime = "01/07/1984 00:00:00", 1055 | inputString = "0 15 10 ? * 6L", 1056 | nextOccurence = "28/07/1984 10:15:00", 1057 | cronStringFormat = CronStringFormat.WithSeconds 1058 | }, 1059 | 1060 | //Fire at ... AM on every last friday of every month during the years 2002, 2003, 2004, and 2005 1061 | new 1062 | { 1063 | startTime = "01/07/1984 00:00:00", 1064 | inputString = "39 26 13 ? * 5L 2002-2005", 1065 | nextOccurence = "25/01/2002 13:26:39", 1066 | cronStringFormat = CronStringFormat.WithSecondsAndYears 1067 | }, 1068 | 1069 | //Fire at .. AM on the third SATURDAY of every month 1070 | new 1071 | { 1072 | startTime = "01/06/2016 00:00:00", 1073 | inputString = "1 16 11 ? * 6#3", 1074 | nextOccurence = "18/06/2016 11:16:01", 1075 | cronStringFormat = CronStringFormat.WithSeconds 1076 | }, 1077 | 1078 | //Fire at 12 PM (noon) every 5 days every month, starting on the first day of the month 1079 | new 1080 | { 1081 | startTime = "01/07/1984 00:00:00", 1082 | inputString = "1 2 12 1/5 * ?", 1083 | nextOccurence = "01/07/1984 12:02:01", 1084 | cronStringFormat = CronStringFormat.WithSeconds 1085 | }, 1086 | 1087 | //Fire every November 11 at 11:11 AM 1088 | new 1089 | { 1090 | startTime = "01/07/1984 00:00:00", 1091 | inputString = "0 11 11 11 11 ?", 1092 | nextOccurence = "11/11/1984 11:11:00", 1093 | cronStringFormat = CronStringFormat.WithSeconds 1094 | } 1095 | }; 1096 | 1097 | foreach (var test in tests) _CronCall(test.startTime, test.inputString, test.nextOccurence, test.cronStringFormat); 1098 | } 1099 | 1100 | [TestMethod] 1101 | public void FiniteOccurrences() 1102 | { 1103 | var tests = new[] 1104 | { 1105 | new {inputString = " * * * * * ", startTime = "01/01/2003 00:00:00", endTime = "01/01/2003 00:00:00", cronStringFormat = CronStringFormat.Default}, 1106 | new {inputString = " * * * * * ", startTime = "31/12/2002 23:59:59", endTime = "01/01/2003 00:00:00", cronStringFormat = CronStringFormat.Default}, 1107 | new {inputString = " * * * * Mon", startTime = "31/12/2002 23:59:59", endTime = "01/01/2003 00:00:00", cronStringFormat = CronStringFormat.Default}, 1108 | new {inputString = " * * * * Mon", startTime = "01/01/2003 00:00:00", endTime = "02/01/2003 00:00:00", cronStringFormat = CronStringFormat.Default}, 1109 | new {inputString = " * * * * Mon", startTime = "01/01/2003 00:00:00", endTime = "02/01/2003 12:00:00", cronStringFormat = CronStringFormat.Default}, 1110 | new {inputString = "30 12 * * Mon", startTime = "01/01/2003 00:00:00", endTime = "06/01/2003 12:00:00", cronStringFormat = CronStringFormat.Default}, 1111 | new {inputString = " * * * * * * ", startTime = "01/01/2003 00:00:00", endTime = "01/01/2003 00:00:00", cronStringFormat = CronStringFormat.WithSeconds}, 1112 | new {inputString = " * * * * * * ", startTime = "31/12/2002 23:59:59", endTime = "01/01/2003 00:00:00", cronStringFormat = CronStringFormat.WithSeconds}, 1113 | new {inputString = " * * * * * Mon", startTime = "31/12/2002 23:59:59", endTime = "01/01/2003 00:00:00", cronStringFormat = CronStringFormat.WithSeconds}, 1114 | new {inputString = " * * * * * Mon", startTime = "01/01/2003 00:00:00", endTime = "02/01/2003 00:00:00", cronStringFormat = CronStringFormat.WithSeconds}, 1115 | new {inputString = " * * * * * Mon", startTime = "01/01/2003 00:00:00", endTime = "02/01/2003 12:00:00", cronStringFormat = CronStringFormat.WithSeconds}, 1116 | new {inputString = "10 30 12 * * Mon", startTime = "01/01/2003 00:00:00", endTime = "06/01/2003 12:00:10", cronStringFormat = CronStringFormat.WithSeconds} 1117 | }; 1118 | 1119 | foreach (var test in tests) _CronFinite(test.inputString, test.startTime, test.endTime, test.cronStringFormat); 1120 | } 1121 | 1122 | // 1123 | // Test to check we don't loop indefinitely looking for a February 1124 | // 31st because no such date would ever exist! 1125 | // 1126 | [TestMethod] 1127 | public void IllegalDates() 1128 | { 1129 | _BadField("* * 31 Feb *", CronStringFormat.Default); 1130 | _BadField("* * * 31 Feb *", CronStringFormat.WithSeconds); 1131 | _BadField("* * 31 Feb * *", CronStringFormat.WithYears); 1132 | _BadField("* * * 31 Feb * *", CronStringFormat.WithSecondsAndYears); 1133 | _BadField("* * 30-31 Feb *", CronStringFormat.Default); 1134 | } 1135 | 1136 | [TestMethod] 1137 | private static void _BadField(string expression, CronStringFormat format) 1138 | { 1139 | Assert2.Throws(() => CrontabSchedule.Parse(expression, format)); 1140 | } 1141 | 1142 | [TestMethod] 1143 | public void BadSecondsField() 1144 | { 1145 | _BadField("bad * * * * *", CronStringFormat.Default); 1146 | } 1147 | 1148 | [TestMethod] 1149 | public void BadMinutesField() 1150 | { 1151 | _BadField("bad * * * *", CronStringFormat.Default); 1152 | _BadField("* bad * * * *", CronStringFormat.WithSeconds); 1153 | } 1154 | 1155 | [TestMethod] 1156 | public void BadHoursField() 1157 | { 1158 | _BadField("* bad * * *", CronStringFormat.Default); 1159 | _BadField("* * bad * * *", CronStringFormat.WithSeconds); 1160 | } 1161 | 1162 | [TestMethod] 1163 | public void BadDayField() 1164 | { 1165 | _BadField("* * bad * *", CronStringFormat.Default); 1166 | _BadField("* * * bad * *", CronStringFormat.WithSeconds); 1167 | } 1168 | 1169 | [TestMethod] 1170 | public void BadMonthField() 1171 | { 1172 | _BadField("* * * bad *", CronStringFormat.Default); 1173 | _BadField("* * * * bad *", CronStringFormat.WithSeconds); 1174 | } 1175 | 1176 | [TestMethod] 1177 | public void BadDayOfWeekField() 1178 | { 1179 | _BadField("* * * * mon,bad,wed", CronStringFormat.Default); 1180 | _BadField("* * * * * mon,bad,wed", CronStringFormat.WithSeconds); 1181 | } 1182 | 1183 | [TestMethod] 1184 | public void OutOfRangeField() 1185 | { 1186 | _BadField("* 1,2,3,456,7,8,9 * * *", CronStringFormat.Default); 1187 | _BadField("* * 1,2,3,456,7,8,9 * * *", CronStringFormat.WithSeconds); 1188 | } 1189 | 1190 | [TestMethod] 1191 | public void BadYearField() 1192 | { 1193 | _BadField("* * * * * Bad", CronStringFormat.WithYears); 1194 | _BadField("* * * * * * Bad", CronStringFormat.WithSecondsAndYears); 1195 | } 1196 | 1197 | [TestMethod] 1198 | public void NonNumberValueInNumericOnlyField() 1199 | { 1200 | _BadField("* 1,Z,3,4 * * *", CronStringFormat.Default); 1201 | _BadField("* * 1,Z,3,4 * * *", CronStringFormat.WithSeconds); 1202 | } 1203 | 1204 | [TestMethod] 1205 | public void BadDayOfWeekStringParsing() 1206 | { 1207 | _BadField("Mon * * * * * *", CronStringFormat.WithSecondsAndYears); 1208 | _BadField("* Mon * * * * *", CronStringFormat.WithSecondsAndYears); 1209 | _BadField("* * Mon * * * *", CronStringFormat.WithSecondsAndYears); 1210 | _BadField("* * * Mon * * *", CronStringFormat.WithSecondsAndYears); 1211 | _BadField("* * * * Mon * *", CronStringFormat.WithSecondsAndYears); 1212 | _BadField("* * * * * * Mon", CronStringFormat.WithSecondsAndYears); 1213 | } 1214 | 1215 | [TestMethod] 1216 | public void BadMonthStringParsing() 1217 | { 1218 | _BadField("Oct * * * * * *", CronStringFormat.WithSecondsAndYears); 1219 | _BadField("* Oct * * * * *", CronStringFormat.WithSecondsAndYears); 1220 | _BadField("* * Oct * * * *", CronStringFormat.WithSecondsAndYears); 1221 | _BadField("* * * Oct * * *", CronStringFormat.WithSecondsAndYears); 1222 | _BadField("* * * * * Oct *", CronStringFormat.WithSecondsAndYears); 1223 | _BadField("* * * * * * Oct", CronStringFormat.WithSecondsAndYears); 1224 | } 1225 | 1226 | [TestMethod] 1227 | public void NonNumericFieldInterval() 1228 | { 1229 | _BadField("* 1/Z * * *", CronStringFormat.Default); 1230 | _BadField("* * 1/Z * * *", CronStringFormat.WithSeconds); 1231 | } 1232 | 1233 | [TestMethod] 1234 | public void NonNumericFieldRangeComponent() 1235 | { 1236 | _BadField("* 3-l2 * * *", CronStringFormat.Default); 1237 | _BadField("* * 3-l2 * * *", CronStringFormat.WithSeconds); 1238 | } 1239 | 1240 | [TestMethod] 1241 | public void MultipleInstancesTest() 1242 | { 1243 | var input = DateTime.Parse("2015-1-1 00:00:00"); 1244 | var cronString = "30 8 17W Jan,February 4 2000-2050"; 1245 | 1246 | var parser = CrontabSchedule.Parse(cronString, CronStringFormat.WithYears); 1247 | var instances = parser.GetNextOccurrences(input, DateTime.MaxValue).ToList(); 1248 | 1249 | Assert.AreEqual(10, instances.Count, "Make sure only 10 instances were generated"); 1250 | 1251 | // Now we'll manually iterate through getting values, and check the 11th and 12th 1252 | // instance to make sure nothing blows up. 1253 | var newInput = input; 1254 | 1255 | for (var i = 0; i < 10; i++) newInput = parser.GetNextOccurrence(newInput); 1256 | 1257 | Assert.IsTrue((newInput = parser.GetNextOccurrence(newInput)) == DateTime.MaxValue, "Make sure 11th instance is the endDate"); 1258 | Assert.IsTrue(parser.GetNextOccurrence(newInput) == DateTime.MaxValue, "Make sure 12th instance is the endDate"); 1259 | } 1260 | 1261 | [TestMethod] 1262 | public void NoNextInstanceTest() 1263 | { 1264 | var stopWatch = new Stopwatch(); 1265 | 1266 | var cron = CrontabSchedule.Parse("0 0 1 1 * 0001", CronStringFormat.WithYears); 1267 | var date = DateTime.Parse("0001-01-01"); 1268 | 1269 | stopWatch.Start(); 1270 | 1271 | var result = cron.GetNextOccurrence(date); 1272 | stopWatch.Stop(); 1273 | 1274 | Assert.AreEqual(DateTime.MaxValue, result, "Next date returned is end date"); 1275 | Assert.IsFalse(stopWatch.ElapsedMilliseconds > 250, 1276 | string.Format("Elapsed time should not exceed 250ms (was {0} ms)", stopWatch.ElapsedMilliseconds)); 1277 | } 1278 | 1279 | private static void _CronCall(string startTimeString, string cronExpression, string nextTimeString, 1280 | CronStringFormat format) 1281 | { 1282 | var schedule = CrontabSchedule.Parse(cronExpression, format); 1283 | var next = schedule.GetNextOccurrence(_Time(startTimeString)); 1284 | 1285 | var message = string.Format("Occurrence of <{0}> after <{1}>, format <{2}>.", cronExpression, startTimeString, 1286 | Enum.GetName(typeof(CronStringFormat), format)); 1287 | 1288 | Assert.AreEqual(nextTimeString, _TimeString(next), message); 1289 | } 1290 | 1291 | private static void _CronFinite(string cronExpression, string startTimeString, string endTimeString, CronStringFormat format) 1292 | { 1293 | var schedule = CrontabSchedule.Parse(cronExpression, format); 1294 | var occurrence = schedule.GetNextOccurrence(_Time(startTimeString), _Time(endTimeString)); 1295 | 1296 | Assert.AreEqual(endTimeString, _TimeString(occurrence), 1297 | "Occurrence of <{0}> after <{1}> did not terminate with <{2}>.", 1298 | cronExpression, startTimeString, endTimeString); 1299 | } 1300 | 1301 | private static string _TimeString(DateTime time) { return time.ToString(_TIME_FORMAT, CultureInfo.InvariantCulture); } 1302 | 1303 | private static DateTime _Time(string str) { return DateTime.ParseExact(str, _TIME_FORMAT, CultureInfo.InvariantCulture); } 1304 | } 1305 | } 1306 | -------------------------------------------------------------------------------- /test/Yisoft.Crontab.UnitTests/Extensions/AssertExtensions.cs: -------------------------------------------------------------------------------- 1 | // ) * 2 | // ( /( * ) ( ( ` 3 | // )\()) ( ` ) /( ( )\ )\))( 4 | // ((_)\ )\ ( )(_)))\ ((((_)( ((_)()\ 5 | // __ ((_)((_) (_(_())((_) )\ _ )\ (_()((_) 6 | // \ \ / / (_) |_ _|| __|(_)_\(_)| \/ | 7 | // \ V / | | _ | | | _| / _ \ | |\/| | 8 | // |_| |_|(_)|_| |___|/_/ \_\ |_| |_| 9 | // 10 | // This file is subject to the terms and conditions defined in 11 | // file 'License.txt', which is part of this source code package. 12 | // 13 | // Copyright © Yi.TEAM. All rights reserved. 14 | // ------------------------------------------------------------------------------- 15 | 16 | using System; 17 | using Microsoft.VisualStudio.TestTools.UnitTesting; 18 | 19 | namespace Yisoft.Crontab.UnitTests.Extensions 20 | { 21 | public static class Assert2 22 | { 23 | public static void Throws(Action methodToCall, string message = "", params object[] values) where T : Exception 24 | { 25 | var additionalInfo = string.Format(message, values); 26 | 27 | try 28 | { 29 | methodToCall(); 30 | } 31 | catch (T) 32 | { 33 | return; 34 | } 35 | catch (Exception e) 36 | { 37 | throw new AssertFailedException( 38 | $"Expected exception '{typeof(T).Name}', but '{e.GetType().Name}' was thrown\n\n{e}. {additionalInfo}"); 39 | } 40 | 41 | Assert.Fail("Expected exception '{0}', but no exception was thrown. {1}", typeof(T).Name, additionalInfo); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /test/Yisoft.Crontab.UnitTests/FilterTest.cs: -------------------------------------------------------------------------------- 1 | // ) * 2 | // ( /( * ) ( ( ` 3 | // )\()) ( ` ) /( ( )\ )\))( 4 | // ((_)\ )\ ( )(_)))\ ((((_)( ((_)()\ 5 | // __ ((_)((_) (_(_())((_) )\ _ )\ (_()((_) 6 | // \ \ / / (_) |_ _|| __|(_)_\(_)| \/ | 7 | // \ V / | | _ | | | _| / _ \ | |\/| | 8 | // |_| |_|(_)|_| |___|/_/ \_\ |_| |_| 9 | // 10 | // This file is subject to the terms and conditions defined in 11 | // file 'License.txt', which is part of this source code package. 12 | // 13 | // Copyright © Yi.TEAM. All rights reserved. 14 | // ------------------------------------------------------------------------------- 15 | 16 | using System; 17 | using System.Collections.Generic; 18 | using System.Diagnostics.CodeAnalysis; 19 | using System.Linq; 20 | using Microsoft.VisualStudio.TestTools.UnitTesting; 21 | using Yisoft.Crontab.Filters; 22 | using Yisoft.Crontab.UnitTests.Extensions; 23 | 24 | namespace Yisoft.Crontab.UnitTests 25 | { 26 | [TestClass] 27 | [SuppressMessage("ReSharper", "ObjectCreationAsStatement")] 28 | public class FilterTest 29 | { 30 | #region SpecificFilter tests 31 | 32 | [TestMethod] 33 | public void SpecificFilterTest() 34 | { 35 | var tests = new[] 36 | { 37 | new {specific = 6, output = new DateTime(2016, 1, 5), kind = CrontabFieldKind.Day, isMatch = false}, 38 | new {specific = 6, output = new DateTime(2016, 1, 6), kind = CrontabFieldKind.Day, isMatch = true}, 39 | new {specific = 6, output = new DateTime(2016, 1, 7), kind = CrontabFieldKind.Day, isMatch = false} 40 | }; 41 | 42 | foreach (var test in tests) 43 | { 44 | var method = new SpecificFilter(test.specific, test.kind); 45 | 46 | Assert.AreEqual(test.isMatch, method.IsMatch(test.output), "Is {0} a match to specific {1} of {2}?", test.output, 47 | Enum.GetName(typeof(CrontabFieldKind), test.kind), 48 | test.specific); 49 | } 50 | } 51 | 52 | #endregion 53 | 54 | #region StepFilter tests 55 | 56 | [TestMethod] 57 | public void StepFilterTest() 58 | { 59 | var tests = new[] 60 | { 61 | new {start = 5, step = 3, input = new DateTime(2016, 1, 1), isMatch = false}, 62 | new {start = 5, step = 3, input = new DateTime(2016, 1, 2), isMatch = false}, 63 | new {start = 5, step = 3, input = new DateTime(2016, 1, 3), isMatch = false}, 64 | new {start = 5, step = 3, input = new DateTime(2016, 1, 4), isMatch = false}, 65 | new {start = 5, step = 3, input = new DateTime(2016, 1, 5), isMatch = true}, 66 | new {start = 5, step = 3, input = new DateTime(2016, 1, 6), isMatch = false}, 67 | new {start = 5, step = 3, input = new DateTime(2016, 1, 7), isMatch = false}, 68 | new {start = 5, step = 3, input = new DateTime(2016, 1, 8), isMatch = true}, 69 | new {start = 5, step = 3, input = new DateTime(2016, 1, 9), isMatch = false}, 70 | new {start = 5, step = 3, input = new DateTime(2016, 1, 10), isMatch = false} 71 | }; 72 | 73 | foreach (var test in tests) 74 | { 75 | var method = new StepFilter(test.start, test.step, CrontabFieldKind.Day); 76 | 77 | Assert.AreEqual(test.isMatch, method.IsMatch(test.input), "Is {0} a match to {1}/{2}?", test.input, test.start, test.step); 78 | } 79 | } 80 | 81 | #endregion 82 | 83 | [TestMethod] 84 | public void BlankDayOfMonthOrWeekFilterInvalidState() 85 | { 86 | var values = new[] 87 | { 88 | CrontabFieldKind.Hour, 89 | CrontabFieldKind.Minute, 90 | CrontabFieldKind.Month, 91 | CrontabFieldKind.Second, 92 | CrontabFieldKind.Year 93 | }; 94 | 95 | foreach (var val in values) 96 | Assert2.Throws(() => new BlankDayOfMonthOrWeekFilter(val), 97 | "Ensure BlankDayOfMonthOrWeekFilter can't be instantiated with <{0}>", 98 | Enum.GetName(typeof(CrontabFieldKind), val)); 99 | 100 | Assert.IsTrue(new BlankDayOfMonthOrWeekFilter(CrontabFieldKind.Day).IsMatch(DateTime.Now)); 101 | Assert.IsTrue(new BlankDayOfMonthOrWeekFilter(CrontabFieldKind.DayOfWeek).IsMatch(DateTime.UtcNow)); 102 | } 103 | 104 | #region LastDayOfMonthFilter tests 105 | 106 | [TestMethod] 107 | public void LastDayOfMonthFilterWorks() 108 | { 109 | var tests = new Dictionary 110 | { 111 | {new DateTime(2016, 1, 1), false}, 112 | {new DateTime(2016, 1, 30), false}, 113 | {new DateTime(2016, 1, 31), true}, 114 | {new DateTime(2015, 2, 28), true}, 115 | {new DateTime(2016, 2, 28), false}, 116 | {new DateTime(2016, 2, 29), true}, 117 | {new DateTime(2016, 4, 1), false}, 118 | {new DateTime(2016, 4, 29), false}, 119 | {new DateTime(2016, 4, 30), true}, 120 | {new DateTime(2016, 12, 1), false}, 121 | {new DateTime(2016, 12, 30), false}, 122 | {new DateTime(2016, 12, 31), true} 123 | }; 124 | 125 | var method = new LastDayOfMonthFilter(CrontabFieldKind.Day); 126 | 127 | foreach (var pair in tests) Assert.AreEqual(pair.Value, method.IsMatch(pair.Key), "Is {0} the last day of the month"); 128 | } 129 | 130 | [TestMethod] 131 | public void LastDayOfMonthFilterInvalidState() 132 | { 133 | var values = Enum.GetValues(typeof(CrontabFieldKind)).Cast().Where(x => x != CrontabFieldKind.Day); 134 | 135 | foreach (var type in values) 136 | Assert2.Throws(() => new LastDayOfMonthFilter(type), "Ensure LastDayOfMonthFilter can't be instantiated with <{0}>", 137 | Enum.GetName(typeof(CrontabFieldKind), type)); 138 | } 139 | 140 | #endregion 141 | 142 | #region LastDayOfWeekInMonthFilter tests 143 | 144 | [TestMethod] 145 | public void LastDayOfWeekInMonthFilterTest() 146 | { 147 | var tests = new[] 148 | { 149 | new {day = DayOfWeek.Sunday, output = new DateTime(2016, 1, 31)}, 150 | new {day = DayOfWeek.Monday, output = new DateTime(2016, 1, 25)}, 151 | new {day = DayOfWeek.Tuesday, output = new DateTime(2016, 1, 26)}, 152 | new {day = DayOfWeek.Wednesday, output = new DateTime(2016, 1, 27)}, 153 | new {day = DayOfWeek.Thursday, output = new DateTime(2016, 1, 28)}, 154 | new {day = DayOfWeek.Friday, output = new DateTime(2016, 1, 29)}, 155 | new {day = DayOfWeek.Saturday, output = new DateTime(2016, 1, 30)} 156 | }; 157 | 158 | foreach (var test in tests) 159 | { 160 | var method = new LastDayOfWeekInMonthFilter(Constants.CronDays[test.day], CrontabFieldKind.DayOfWeek); 161 | 162 | Assert.IsTrue(method.IsMatch(test.output), "Is {0} the last instance of that day in the month"); 163 | } 164 | } 165 | 166 | [TestMethod] 167 | public void LastDayOfWeekInMonthFilterInvalidState() 168 | { 169 | var values = Enum.GetValues(typeof(CrontabFieldKind)) 170 | .Cast() 171 | .Where(x => x != CrontabFieldKind.DayOfWeek); 172 | 173 | foreach (var type in values) 174 | Assert2.Throws(() => new LastDayOfWeekInMonthFilter(0, type), "Ensure LastDayOfWeekInMonthFilter can't be instantiated with <{0}>", 175 | Enum.GetName(typeof(CrontabFieldKind), type)); 176 | } 177 | 178 | #endregion 179 | 180 | #region LastWeekdayOfMonthFilter tests 181 | 182 | [TestMethod] 183 | public void LastWeekdayOfMonthFilterTest() 184 | { 185 | var tests = new[] 186 | { 187 | new {output = new DateTime(2015, 2, 27)}, 188 | new {output = new DateTime(2016, 1, 29)}, 189 | new {output = new DateTime(2016, 2, 29)}, 190 | new {output = new DateTime(2016, 3, 31)}, 191 | new {output = new DateTime(2016, 4, 29)}, 192 | new {output = new DateTime(2016, 5, 31)}, 193 | new {output = new DateTime(2016, 6, 30)}, 194 | new {output = new DateTime(2016, 7, 29)}, 195 | new {output = new DateTime(2016, 8, 31)}, 196 | new {output = new DateTime(2016, 9, 30)}, 197 | new {output = new DateTime(2016, 10, 31)}, 198 | new {output = new DateTime(2016, 11, 30)}, 199 | new {output = new DateTime(2016, 12, 30)} 200 | }; 201 | 202 | var method = new LastWeekdayOfMonthFilter(CrontabFieldKind.Day); 203 | 204 | foreach (var test in tests) Assert.IsTrue(method.IsMatch(test.output), "Is {0} the last week day in a month"); 205 | } 206 | 207 | [TestMethod] 208 | public void LastWeekdayOfMonthFilterInvalidState() 209 | { 210 | var values = Enum.GetValues(typeof(CrontabFieldKind)).Cast().Where(x => x != CrontabFieldKind.Day); 211 | 212 | foreach (var type in values) 213 | Assert2.Throws(() => new LastWeekdayOfMonthFilter(type), "Ensure LastWeekdayOfMonth can't be instantiated with <{0}>", 214 | Enum.GetName(typeof(CrontabFieldKind), type)); 215 | } 216 | 217 | #endregion 218 | 219 | #region NearestWeekdayFilter tests 220 | 221 | [TestMethod] 222 | public void NearestWeekdayFilterTest() 223 | { 224 | var tests = new[] 225 | { 226 | new {day = 1, output = new DateTime(2015, 1, 1)}, 227 | new {day = 2, output = new DateTime(2016, 1, 1)}, 228 | new {day = 3, output = new DateTime(2016, 1, 4)}, 229 | new {day = 4, output = new DateTime(2016, 1, 4)}, 230 | new {day = 29, output = new DateTime(2016, 1, 29)}, 231 | new {day = 30, output = new DateTime(2016, 1, 29)}, 232 | new {day = 31, output = new DateTime(2016, 1, 29)}, 233 | new {day = 1, output = new DateTime(2016, 10, 3)}, 234 | new {day = 2, output = new DateTime(2016, 10, 3)}, 235 | new {day = 3, output = new DateTime(2016, 10, 3)} 236 | }; 237 | 238 | foreach (var test in tests) 239 | { 240 | var method = new NearestWeekdayFilter(test.day, CrontabFieldKind.Day); 241 | 242 | Assert.IsTrue(method.IsMatch(test.output), "Is {0} the nearest weekday for day = {1}", test.output, test.day); 243 | } 244 | } 245 | 246 | [TestMethod] 247 | public void NearestWeekdayFilterInvalidState() 248 | { 249 | var values = Enum.GetValues(typeof(CrontabFieldKind)).Cast().Where(x => x != CrontabFieldKind.Day); 250 | 251 | foreach (var type in values) 252 | Assert2.Throws(() => new NearestWeekdayFilter(1, type), "Ensure NearestWeekdayFilter can't be instantiated with <{0}>", 253 | Enum.GetName(typeof(CrontabFieldKind), type)); 254 | 255 | Assert2.Throws(() => new NearestWeekdayFilter(-1, CrontabFieldKind.Day)); 256 | Assert2.Throws(() => new NearestWeekdayFilter(32, CrontabFieldKind.Day)); 257 | } 258 | 259 | #endregion 260 | 261 | #region RangeFilter tests 262 | 263 | [TestMethod] 264 | public void RangeFilterTest() 265 | { 266 | var tests = new[] 267 | { 268 | new {start = 5, end = 8, steps = (int?) null, input = new DateTime(2015, 1, 4), result = false}, 269 | new {start = 5, end = 8, steps = (int?) null, input = new DateTime(2015, 1, 5), result = true}, 270 | new {start = 5, end = 8, steps = (int?) null, input = new DateTime(2015, 1, 6), result = true}, 271 | new {start = 5, end = 8, steps = (int?) null, input = new DateTime(2015, 1, 7), result = true}, 272 | new {start = 5, end = 8, steps = (int?) null, input = new DateTime(2015, 1, 8), result = true}, 273 | new {start = 5, end = 8, steps = (int?) null, input = new DateTime(2015, 1, 9), result = false}, 274 | 275 | new {start = 5, end = 8, steps = (int?) 2, input = new DateTime(2015, 1, 4), result = false}, 276 | new {start = 5, end = 8, steps = (int?) 2, input = new DateTime(2015, 1, 5), result = true}, 277 | new {start = 5, end = 8, steps = (int?) 2, input = new DateTime(2015, 1, 6), result = false}, 278 | new {start = 5, end = 8, steps = (int?) 2, input = new DateTime(2015, 1, 7), result = true}, 279 | new {start = 5, end = 8, steps = (int?) 2, input = new DateTime(2015, 1, 8), result = false}, 280 | new {start = 5, end = 8, steps = (int?) 2, input = new DateTime(2015, 1, 9), result = false} 281 | }; 282 | 283 | foreach (var test in tests) 284 | { 285 | var method = new RangeFilter(test.start, test.end, test.steps, CrontabFieldKind.Day); 286 | 287 | Assert.AreEqual(test.result, method.IsMatch(test.input), "Is {0} in the range of {1}-{2}/{3}?", test.input, test.start, test.end, test.steps ?? 1); 288 | } 289 | } 290 | 291 | [TestMethod] 292 | public void RangeFilterInvalidState() 293 | { 294 | Assert2.Throws(() => new RangeFilter(-1, 1, null, CrontabFieldKind.Day)); 295 | Assert2.Throws(() => new RangeFilter(1, -1, null, CrontabFieldKind.Day)); 296 | Assert2.Throws(() => new RangeFilter(1, 1, -1, CrontabFieldKind.Day)); 297 | Assert2.Throws(() => new RangeFilter(1, 1, 0, CrontabFieldKind.Day)); 298 | 299 | Assert2.Throws(() => new RangeFilter(32, 1, null, CrontabFieldKind.Day)); 300 | Assert2.Throws(() => new RangeFilter(1, 32, null, CrontabFieldKind.Day)); 301 | Assert2.Throws(() => new RangeFilter(1, 1, 32, CrontabFieldKind.Day)); 302 | } 303 | 304 | #endregion 305 | 306 | #region SpecificDayOfWeekInMonthFilter tests 307 | 308 | [TestMethod] 309 | public void SpecificDayOfWeekInMonthFilterTest() 310 | { 311 | var tests = new[] 312 | { 313 | new {day = DayOfWeek.Sunday, week = 1, output = new DateTime(2016, 1, 3)}, 314 | new {day = DayOfWeek.Monday, week = 1, output = new DateTime(2016, 1, 4)}, 315 | new {day = DayOfWeek.Tuesday, week = 1, output = new DateTime(2016, 1, 5)}, 316 | new {day = DayOfWeek.Wednesday, week = 1, output = new DateTime(2016, 1, 6)}, 317 | new {day = DayOfWeek.Thursday, week = 1, output = new DateTime(2016, 1, 7)}, 318 | new {day = DayOfWeek.Friday, week = 1, output = new DateTime(2016, 1, 1)}, 319 | new {day = DayOfWeek.Saturday, week = 1, output = new DateTime(2016, 1, 2)}, 320 | new {day = DayOfWeek.Sunday, week = 2, output = new DateTime(2016, 1, 10)}, 321 | new {day = DayOfWeek.Monday, week = 2, output = new DateTime(2016, 1, 11)}, 322 | new {day = DayOfWeek.Tuesday, week = 2, output = new DateTime(2016, 1, 12)}, 323 | new {day = DayOfWeek.Wednesday, week = 2, output = new DateTime(2016, 1, 13)}, 324 | new {day = DayOfWeek.Thursday, week = 2, output = new DateTime(2016, 1, 14)}, 325 | new {day = DayOfWeek.Friday, week = 2, output = new DateTime(2016, 1, 8)}, 326 | new {day = DayOfWeek.Saturday, week = 2, output = new DateTime(2016, 1, 9)} 327 | }; 328 | 329 | foreach (var test in tests) 330 | { 331 | var method = new SpecificDayOfWeekInMonthFilter(Constants.CronDays[test.day], test.week, CrontabFieldKind.DayOfWeek); 332 | 333 | Assert.IsTrue(method.IsMatch(test.output), "Is {0} instance number {1} of {2}?", test.output, test.week, Enum.GetName(typeof(DayOfWeek), test.day)); 334 | } 335 | } 336 | 337 | [TestMethod] 338 | public void SpecificDayOfWeekInMonthFilterInvalidState() 339 | { 340 | var values = Enum.GetValues(typeof(CrontabFieldKind)) 341 | .Cast() 342 | .Where(x => x != CrontabFieldKind.DayOfWeek); 343 | 344 | foreach (var type in values) 345 | Assert2.Throws(() => new SpecificDayOfWeekInMonthFilter(0, 1, type), 346 | "Ensure SpecificDayOfWeekInMonthFilter can't be instantiated with <{0}>", 347 | Enum.GetName(typeof(CrontabFieldKind), type)); 348 | 349 | Assert2.Throws(() => new SpecificDayOfWeekInMonthFilter(0, -1, CrontabFieldKind.DayOfWeek), "Make sure instance of -1 throws exception"); 350 | Assert2.Throws(() => new SpecificDayOfWeekInMonthFilter(0, 0, CrontabFieldKind.DayOfWeek), "Make sure instance of 0 throws exception"); 351 | Assert2.Throws(() => new SpecificDayOfWeekInMonthFilter(0, 6, CrontabFieldKind.DayOfWeek), "Makes sure instance of 6 throws exception"); 352 | } 353 | 354 | #endregion 355 | } 356 | } 357 | -------------------------------------------------------------------------------- /test/Yisoft.Crontab.UnitTests/Yisoft.Crontab.UnitTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp1.1;net46 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | --------------------------------------------------------------------------------