├── .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 | [](https://ci.appveyor.com/project/yiteam/crontab)
4 | [](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 |
--------------------------------------------------------------------------------