14 |
15 | ## Status
16 |
17 | [](https://circleci.com/gh/marxjmoura/cronquery)
18 | [](https://codecov.io/gh/marxjmoura/cronquery)
19 | [](https://img.shields.io/nuget/v/cronquery.svg)
20 | [](https://www.nuget.org/packages/cronquery)
21 |
22 | ## Changelog
23 |
24 | The changes for each release are documented in the [release notes](https://github.com/marxjmoura/cronquery/releases).
25 |
26 | ## Bugs and features
27 |
28 | Please, fell free to [open a new issue](https://github.com/marxjmoura/cronquery/issues/new) on GitHub.
29 |
30 | ## License
31 |
32 | [MIT](https://github.com/marxjmoura/cronquery/blob/master/LICENSE)
33 |
34 | Copyright (c) 2018-present, [Marx J. Moura](https://github.com/marxjmoura)
35 |
--------------------------------------------------------------------------------
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version : 2.1
2 |
3 | executors:
4 | ubuntu:
5 | machine:
6 | image: ubuntu-2004:202201-02
7 |
8 | jobs:
9 | build:
10 | executor: ubuntu
11 | steps:
12 | - checkout
13 | - run: ./tools/install-dotnet.sh 8.0.100
14 | - run: dotnet tool install --local dotnet-reportgenerator-globaltool --version 5.2.0
15 | - run: ./tools/test.sh
16 | - run: curl -s https://codecov.io/bash > ./codecov
17 | - run: chmod +x ./codecov
18 | - run: ./codecov -f "./src/CronQuery.Tests/coverage/opencover.xml" -t $CODECOV_TOKEN
19 | - store_artifacts:
20 | path: src/CronQuery.Tests/coverage/report
21 | destination: coverage
22 | deploy:
23 | executor: ubuntu
24 | steps:
25 | - checkout
26 | - run: ./tools/install-dotnet.sh 8.0.100
27 | - run: dotnet pack src/CronQuery.API/CronQuery.API.csproj -o dist
28 | - run: dotnet nuget push dist/CronQuery.${CIRCLE_TAG/v/}.nupkg -k $NUGET_TOKEN -s https://api.nuget.org/v3/index.json
29 |
30 | workflows:
31 | version: 2.1
32 | build_and_deploy:
33 | jobs:
34 | - build:
35 | filters:
36 | tags:
37 | only: /.*/
38 | - deploy:
39 | requires:
40 | - build
41 | filters:
42 | tags:
43 | only: /^v\d\.\d\.\d/
44 | branches:
45 | ignore: /.*/
46 |
--------------------------------------------------------------------------------
/src/CronQuery.Tests/CronQuery.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net8.0
4 | 12.0
5 | enable
6 | enable
7 | false
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | all
19 | runtime; build; native; contentfiles; analyzers
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | PreserveNewest
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/src/CronQuery.API/Mvc/Options/JobOptions.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * MIT License
3 | *
4 | * Copyright (c) 2018 Marx J. Moura
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 |
25 | namespace CronQuery.Mvc.Options;
26 |
27 | public sealed class JobOptions
28 | {
29 | public bool Running { get; set; }
30 |
31 | public string Name { get; set; } = null!;
32 |
33 | public string Cron { get; set; } = null!;
34 | }
35 |
--------------------------------------------------------------------------------
/example/Jobs/MySecondJob.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * MIT License
3 | *
4 | * Copyright (c) 2018 Marx J. Moura
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 |
25 | namespace Example.Jobs;
26 |
27 | using System;
28 | using System.Threading.Tasks;
29 | using CronQuery.Mvc.Jobs;
30 |
31 | public class MySecondJob : IJob
32 | {
33 | public Task RunAsync()
34 | {
35 | throw new NotImplementedException();
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/CronQuery.API/Mvc/Options/JobRunnerOptions.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * MIT License
3 | *
4 | * Copyright (c) 2018 Marx J. Moura
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 |
25 | namespace CronQuery.Mvc.Options;
26 |
27 | public sealed class JobRunnerOptions
28 | {
29 | public bool Running { get; set; }
30 |
31 | public string TimeZone { get; set; } = null!;
32 |
33 | public ICollection Jobs { get; } = new List();
34 | }
35 |
--------------------------------------------------------------------------------
/docs/www/versions.hbs:
--------------------------------------------------------------------------------
1 | {{#> www/page }}
2 |
3 | {{#*inline "body-block"}}
4 |
5 |
6 | Major versions of CronQuery only support the current versions of .NET Long Term Support (LTS).
7 |
14 | The changes and breaking changes for each release are documented on the
15 | GitHub release notes page.
16 |
17 |
18 |
19 |
20 |
21 |
Version
22 |
Target Framework
23 |
Release Month
24 |
25 |
26 |
27 |
v3.1
28 |
.NET 8
29 |
December, 2023
30 |
31 |
32 |
v3.0
33 |
.NET 6
34 |
April, 2023
35 |
36 |
37 |
v2.x
38 |
.NET Core 3.1
39 |
March, 2020
40 |
41 |
42 |
v1.x
43 |
.NET Core 2.2
44 |
January, 2019
45 |
46 |
47 |
48 |
49 |
50 |
51 | Install previous versions of CronQuery
52 | if you need to use end-of-life versions of .NET
53 |
54 |
55 | {{/inline}}
56 |
57 | {{/www/page}}
58 |
--------------------------------------------------------------------------------
/src/CronQuery.API/CronQuery.API.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net8.0
4 | 12.0
5 | enable
6 | enable
7 | CronQuery
8 | CronQuery
9 | 3.1.0
10 | 3.1.0
11 | Marx J. Moura
12 | Marx J. Moura
13 | Copyright (c) 2018 Marx J. Moura
14 | Lightweight job runner for ASP.NET Core.
15 | git
16 | https://github.com/marxjmoura/cronquery
17 | CronQuery
18 | https://marxjmoura.github.io/cronquery
19 | https://github.com/marxjmoura/cronquery/blob/master/LICENSE
20 | https://github.com/marxjmoura/cronquery/releases
21 | false
22 | job runner;background tasks;scheduler
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/example/Jobs/MyFirstJob.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * MIT License
3 | *
4 | * Copyright (c) 2018 Marx J. Moura
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 |
25 | namespace Example.Jobs;
26 |
27 | using CronQuery.Mvc.Jobs;
28 | using System;
29 | using System.Threading.Tasks;
30 |
31 | public class MyFirstJob : IJob
32 | {
33 | public Task RunAsync()
34 | {
35 | Console.WriteLine($"My first job: {DateTime.Now:yyyy-MM-dd HH:mm:ss}");
36 |
37 | return Task.CompletedTask;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/CronQuery.Tests/Fakes/LoggerFactoryFake.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * MIT License
3 | *
4 | * Copyright (c) 2018 Marx J. Moura
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 |
25 | namespace CronQuery.Tests.Fakes;
26 |
27 | using Microsoft.Extensions.Logging;
28 |
29 | public sealed class LoggerFactoryFake : ILoggerFactory
30 | {
31 | public LoggerFake Logger { get; } = new LoggerFake();
32 |
33 | public void AddProvider(ILoggerProvider provider) { }
34 |
35 | public ILogger CreateLogger(string categoryName)
36 | {
37 | return Logger;
38 | }
39 |
40 | public void Dispose() { }
41 | }
42 |
--------------------------------------------------------------------------------
/example/Program.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * MIT License
3 | *
4 | * Copyright (c) 2018 Marx J. Moura
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 |
25 | using CronQuery.Mvc.DependencyInjection;
26 | using Example.Jobs;
27 | using Microsoft.AspNetCore.Builder;
28 | using Microsoft.Extensions.DependencyInjection;
29 |
30 | var builder = WebApplication.CreateBuilder(args);
31 |
32 | builder.Services.AddCronQuery(builder.Configuration.GetSection("CronQuery"));
33 |
34 | builder.Services.AddTransient();
35 | builder.Services.AddTransient();
36 |
37 | var api = builder.Build();
38 |
39 | api.MapGet("/", () => "Jobs are running...");
40 |
41 | api.Run();
42 |
--------------------------------------------------------------------------------
/docs/www/_page.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | CronQuery
14 | {{#> head-block}}{{/head-block}}
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | CronQuery
26 |
27 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | CronQuery
41 |
42 | {{#> body-block}}{{/body-block}}
43 |
44 |
45 |
46 | {{#> scripts-block}}{{/scripts-block}}
47 |
48 |
49 |
--------------------------------------------------------------------------------
/src/CronQuery.Tests/Fakes/LoggerFake.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * MIT License
3 | *
4 | * Copyright (c) 2018 Marx J. Moura
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 |
25 | namespace CronQuery.Tests.Fakes;
26 |
27 | using Microsoft.Extensions.Logging;
28 |
29 | public sealed class LoggerFake : ILogger
30 | {
31 | public ICollection Messages { get; } = new List();
32 |
33 | public IDisposable BeginScope(TState state) => null!;
34 |
35 | public bool IsEnabled(LogLevel logLevel) => true;
36 |
37 | public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception,
38 | Func formatter)
39 | {
40 | Messages.Add(formatter(state, exception));
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/CronQuery.Tests/Fakes/TestServerExtensions.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * MIT License
3 | *
4 | * Copyright (c) 2018 Marx J. Moura
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 |
25 | namespace CronQuery.Tests.Fakes;
26 |
27 | using CronQuery.Mvc.Jobs;
28 | using Microsoft.AspNetCore.TestHost;
29 | using Microsoft.Extensions.DependencyInjection;
30 | using Microsoft.Extensions.Logging;
31 |
32 | public static class TestServerExtensions
33 | {
34 | public static LoggerFake Logger(this TestServer server) =>
35 | (LoggerFake)server.Host.Services.GetRequiredService().CreateLogger(string.Empty);
36 |
37 | public static TJob Job(this TestServer server) where TJob : IJob =>
38 | server.Host.Services.GetRequiredService();
39 | }
40 |
--------------------------------------------------------------------------------
/src/CronQuery.API/Mvc/DependencyInjection/CronQueryExtensions.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * MIT License
3 | *
4 | * Copyright (c) 2018 Marx J. Moura
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 |
25 | namespace CronQuery.Mvc.DependencyInjection;
26 |
27 | using CronQuery.Mvc.Jobs;
28 | using CronQuery.Mvc.Options;
29 | using Microsoft.Extensions.Configuration;
30 | using Microsoft.Extensions.DependencyInjection;
31 |
32 | public static class CronQueryExtensions
33 | {
34 | public static void AddCronQuery(this IServiceCollection services, IConfigurationSection configuration)
35 | {
36 | services.Configure(configuration);
37 |
38 | services.AddSingleton();
39 | services.AddSingleton(serviceProvider => new JobCollection(services));
40 |
41 | services.AddHostedService();
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/docs/handlebars.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | const path = require('path')
3 | const through = require('through2')
4 | const handlebars = require('handlebars')
5 | const PluginError = require('plugin-error')
6 |
7 | const ROOTDIR = process.cwd()
8 | const PLUGIN_NAME = 'gulp-site-engine'
9 |
10 | handlebars.registerHelper('baseUrl', function () {
11 | return process.env.NODE_ENV === 'production' ? 'https://marxjmoura.github.io/cronquery' : ''
12 | })
13 |
14 | handlebars.registerHelper('include', function (file) {
15 | try {
16 | const source = fs.readFileSync(file, 'utf8')
17 | const template = handlebars.compile(source)
18 |
19 | return template({})
20 | } catch (error) {
21 | console.error(PLUGIN_NAME, error.message)
22 | return ''
23 | }
24 | })
25 |
26 | function transform(file, encoding, callback) {
27 | if (file.isNull()) {
28 | // nothing to do
29 | return callback(null, file)
30 | }
31 |
32 | if (file.isStream()) {
33 | this.emit('error', new PluginError(PLUGIN_NAME, 'Streams not supported!'))
34 | }
35 |
36 | const filedir = path.parse(file.path).dir
37 | const filename = path.parse(file.path).name
38 | const isPartial = filename.startsWith('_')
39 | const source = file.contents.toString()
40 |
41 | if (isPartial) {
42 | const partialPrefix = filedir.replace(ROOTDIR + '/', '')
43 | const partialSuffix = filename.replace('_', '')
44 | const partialName = path.join(partialPrefix, partialSuffix)
45 |
46 | handlebars.registerPartial(partialName, source)
47 | } else {
48 | try {
49 | const template = handlebars.compile(source)
50 | const compiledHTML = template({})
51 |
52 | file.contents = Buffer.from(compiledHTML)
53 | } catch (err) {
54 | this.emit('error', new PluginError(PLUGIN_NAME, err))
55 | }
56 |
57 | this.push(file)
58 | }
59 |
60 | return callback()
61 | }
62 |
63 | module.exports = function () {
64 | return through.obj(transform)
65 | }
66 |
--------------------------------------------------------------------------------
/src/CronQuery.Tests/Fakes/Jobs/JobNotEnqueued.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * MIT License
3 | *
4 | * Copyright (c) 2018 Marx J. Moura
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 |
25 | namespace CronQuery.Tests.Fakes.Jobs;
26 |
27 | using CronQuery.Mvc.Jobs;
28 | using CronQuery.Mvc.Options;
29 |
30 | public sealed class JobNotEnqueued : IJob
31 | {
32 | public Task RunAsync()
33 | {
34 | return Task.CompletedTask;
35 | }
36 |
37 | public static JobRunnerOptions Options
38 | {
39 | get
40 | {
41 | var options = new JobRunnerOptions();
42 | options.Running = true;
43 | options.Jobs.Add(new JobOptions
44 | {
45 | Running = true,
46 | Name = nameof(JobNotEnqueued),
47 | Cron = "* * * * * *"
48 | });
49 |
50 | return options;
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/CronQuery.Tests/Fakes/Jobs/JobWithError.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * MIT License
3 | *
4 | * Copyright (c) 2018 Marx J. Moura
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 |
25 | namespace CronQuery.Tests.Fakes.Jobs;
26 |
27 | using CronQuery.Mvc.Jobs;
28 | using CronQuery.Mvc.Options;
29 |
30 | public sealed class JobWithError : IJob
31 | {
32 | public Task RunAsync()
33 | {
34 | throw new NotImplementedException();
35 | }
36 |
37 | public static JobRunnerOptions Options
38 | {
39 | get
40 | {
41 | var options = new JobRunnerOptions();
42 | options.Running = true;
43 | options.Jobs.Add(new JobOptions
44 | {
45 | Running = true,
46 | Name = nameof(JobWithError),
47 | Cron = "* * * * * *"
48 | });
49 |
50 | return options;
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/CronQuery.Tests/Fakes/Jobs/JobBadlyConfigured.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * MIT License
3 | *
4 | * Copyright (c) 2018 Marx J. Moura
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 |
25 | namespace CronQuery.Tests.Fakes.Jobs;
26 |
27 | using CronQuery.Mvc.Jobs;
28 | using CronQuery.Mvc.Options;
29 |
30 | public sealed class JobBadlyConfigured : IJob
31 | {
32 | public Task RunAsync()
33 | {
34 | return Task.CompletedTask;
35 | }
36 |
37 | public static JobRunnerOptions Options
38 | {
39 | get
40 | {
41 | var options = new JobRunnerOptions();
42 | options.Running = true;
43 | options.Jobs.Add(new JobOptions
44 | {
45 | Running = true,
46 | Name = nameof(JobBadlyConfigured),
47 | Cron = "IN V A L I D"
48 | });
49 |
50 | return options;
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/CronQuery.API/Mvc/Jobs/JobCollection.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * MIT License
3 | *
4 | * Copyright (c) 2018 Marx J. Moura
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 |
25 | namespace CronQuery.Mvc.Jobs;
26 |
27 | using Microsoft.Extensions.DependencyInjection;
28 | using System.Collections;
29 |
30 | public sealed class JobCollection : IEnumerable
31 | {
32 | private readonly IList _descriptors;
33 |
34 | public JobCollection(IList descriptors)
35 | {
36 | _descriptors = descriptors;
37 | }
38 |
39 | public IEnumerator GetEnumerator()
40 | {
41 | return _descriptors
42 | .Where(service => typeof(IJob).IsAssignableFrom(service.ServiceType))
43 | .GetEnumerator();
44 | }
45 |
46 | IEnumerator IEnumerable.GetEnumerator()
47 | {
48 | return GetEnumerator();
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/CronQuery.API/Extensions/Int32Extensions.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * MIT License
3 | *
4 | * Copyright (c) 2018 Marx J. Moura
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 |
25 | namespace CronQuery.Extensions;
26 |
27 | internal static class Int32Extensions
28 | {
29 | public static IEnumerable Step(this IEnumerable source, int step)
30 | {
31 | if (step == 0)
32 | {
33 | return source;
34 | }
35 |
36 | return source.Where((value, index) => index % step == 0);
37 | }
38 |
39 | public static IEnumerable To(this int from, int to)
40 | {
41 | if (from <= to)
42 | {
43 | while (from <= to)
44 | {
45 | yield return from++;
46 | }
47 | }
48 | else
49 | {
50 | while (from >= to)
51 | {
52 | yield return from--;
53 | }
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/CronQuery.Tests/Fakes/Jobs/JobStopped.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * MIT License
3 | *
4 | * Copyright (c) 2018 Marx J. Moura
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 |
25 | namespace CronQuery.Tests.Fakes.Jobs;
26 |
27 | using CronQuery.Mvc.Jobs;
28 | using CronQuery.Mvc.Options;
29 |
30 | public sealed class JobStopped : IJob
31 | {
32 | public bool Executed { get; private set; }
33 |
34 | public Task RunAsync()
35 | {
36 | Executed = true;
37 |
38 | return Task.CompletedTask;
39 | }
40 |
41 | public static JobRunnerOptions Options
42 | {
43 | get
44 | {
45 | var options = new JobRunnerOptions();
46 | options.Running = true;
47 | options.Jobs.Add(new JobOptions
48 | {
49 | Running = false,
50 | Name = nameof(JobStopped),
51 | Cron = "* * * * * *"
52 | });
53 |
54 | return options;
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/CronQuery.Tests/Fakes/Jobs/JobSuccessful.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * MIT License
3 | *
4 | * Copyright (c) 2018 Marx J. Moura
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 |
25 | namespace CronQuery.Tests.Fakes.Jobs;
26 |
27 | using CronQuery.Mvc.Jobs;
28 | using CronQuery.Mvc.Options;
29 |
30 | public sealed class JobSuccessful : IJob, IDisposable
31 | {
32 | public bool Executed { get; private set; }
33 |
34 | public void Dispose() { }
35 |
36 | public Task RunAsync()
37 | {
38 | Executed = true;
39 |
40 | return Task.CompletedTask;
41 | }
42 |
43 | public static JobRunnerOptions Options
44 | {
45 | get
46 | {
47 | var options = new JobRunnerOptions();
48 | options.Running = true;
49 | options.Jobs.Add(new JobOptions
50 | {
51 | Running = true,
52 | Name = nameof(JobSuccessful),
53 | Cron = "* * * * * *"
54 | });
55 |
56 | return options;
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/CronQuery.Tests/Unit/Cron/UnreachableConditionTest.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * MIT License
3 | *
4 | * Copyright (c) 2018 Marx J. Moura
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 |
25 | namespace CronQuery.Tests.Unit.Cron;
26 |
27 | using CronQuery.Cron;
28 | using Xunit;
29 |
30 | public sealed class UnreachableConditionTest
31 | {
32 | [Fact]
33 | public void ShouldNotEvaluateNearestWeekdayOnlyOnSunday()
34 | {
35 | var expression = new CronExpression("0 0 8 15W * 0");
36 | var current = new DateTime(2019, 01, 01, 00, 00, 00);
37 | var expected = DateTime.MinValue;
38 |
39 | Assert.Equal(expected, expression.Next(current));
40 | }
41 |
42 | [Fact]
43 | public void ShouldNotEvaluateOnlyMonthsThatNotReachTheGivenDay()
44 | {
45 | var expression = new CronExpression("0 0 8 31 2,4,6 *");
46 | var current = new DateTime(2019, 01, 01, 00, 00, 00);
47 | var expected = DateTime.MinValue;
48 |
49 | Assert.Equal(expected, expression.Next(current));
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/CronQuery.Tests/Unit/Cron/ExpressionWithHashTest.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * MIT License
3 | *
4 | * Copyright (c) 2018 Marx J. Moura
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 |
25 | namespace CronQuery.Tests.Unit.Cron;
26 |
27 | using CronQuery.Cron;
28 | using Xunit;
29 |
30 | public sealed class ExpressionWithHashTest
31 | {
32 | [Fact]
33 | public void ShouldGetNextDayOfWeekInTheSameMonth()
34 | {
35 | var expression = new CronExpression("* * * * * 1#3");
36 | var current = new DateTime(2018, 12, 01, 23, 59, 59);
37 | var expected = new DateTime(2018, 12, 17, 00, 00, 00);
38 |
39 | Assert.Equal(expected, expression.Next(current));
40 | }
41 |
42 | [Fact]
43 | public void ShouldGetNextDayOfWeekInTheNextMonth()
44 | {
45 | var expression = new CronExpression("* * * * * 1#3");
46 | var current = new DateTime(2018, 12, 25, 23, 59, 59);
47 | var expected = new DateTime(2019, 01, 21, 00, 00, 00);
48 |
49 | Assert.Equal(expected, expression.Next(current));
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/CronQuery.Tests/Functional/AppTest.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * MIT License
3 | *
4 | * Copyright (c) 2018 Marx J. Moura
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 |
25 | namespace CronQuery.Tests.Functional;
26 |
27 | using CronQuery.Tests.Fakes;
28 | using CronQuery.Tests.Fakes.Jobs;
29 | using Xunit;
30 |
31 | public sealed class AppTest
32 | {
33 | [Fact]
34 | public async Task Run()
35 | {
36 | var server = TestProgram.CreateServer();
37 |
38 | await Task.Delay(1500); // Waiting for the jobs
39 | await server.Host.StopAsync();
40 |
41 | Assert.True(server.Job().Executed);
42 | Assert.False(server.Job().Executed);
43 |
44 | Assert.Contains(server.Logger().Messages, message =>
45 | message == $"Job '{nameof(JobWithError)}' failed during running.");
46 |
47 | Assert.Contains(server.Logger().Messages, message =>
48 | message == $"Job {nameof(JobNotEnqueued)} is not in the queue.");
49 |
50 | Assert.Contains(server.Logger().Messages, message =>
51 | message == $"Invalid cron expression for '{nameof(JobBadlyConfigured)}'.");
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/CronQuery.Tests/Fakes/ServiceProviderFake.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * MIT License
3 | *
4 | * Copyright (c) 2018 Marx J. Moura
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 |
25 | namespace CronQuery.Tests.Fakes;
26 |
27 | using CronQuery.Mvc.Jobs;
28 | using Microsoft.Extensions.DependencyInjection;
29 |
30 | public sealed class ServiceProviderFake : IServiceProvider, IServiceScopeFactory, IServiceScope
31 | {
32 | private readonly IDictionary _instances;
33 |
34 | public ServiceProviderFake()
35 | {
36 | _instances = new Dictionary();
37 | }
38 |
39 | public IServiceProvider ServiceProvider => this;
40 |
41 | public IServiceScope CreateScope() => this;
42 |
43 | public void Dispose() { }
44 |
45 | public object? GetService(Type serviceType)
46 | {
47 | if (typeof(IJob).IsAssignableFrom(serviceType))
48 | {
49 | if (!_instances.ContainsKey(serviceType))
50 | {
51 | _instances.Add(serviceType, Activator.CreateInstance(serviceType)!);
52 | }
53 |
54 | return _instances[serviceType];
55 | }
56 |
57 | return null;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/CronQuery.API/Cron/TimeUnit.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * MIT License
3 | *
4 | * Copyright (c) 2018 Marx J. Moura
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 |
25 | namespace CronQuery.Cron;
26 |
27 | internal sealed class TimeUnit
28 | {
29 | private TimeUnit(int minValue, int maxValue)
30 | {
31 | MinValue = minValue;
32 | MaxValue = maxValue;
33 | }
34 |
35 | public int MinValue { get; private set; }
36 | public int MaxValue { get; private set; }
37 | public bool IsSecond { get; private set; }
38 | public bool IsMinute { get; private set; }
39 | public bool IsHour { get; private set; }
40 | public bool IsDay { get; private set; }
41 | public bool IsMonth { get; private set; }
42 | public bool IsDayOfWeek { get; private set; }
43 |
44 | public static TimeUnit Second => new TimeUnit(0, 59) { IsSecond = true };
45 | public static TimeUnit Minute => new TimeUnit(0, 59) { IsMinute = true };
46 | public static TimeUnit Hour => new TimeUnit(0, 23) { IsHour = true };
47 | public static TimeUnit Day => new TimeUnit(1, 31) { IsDay = true };
48 | public static TimeUnit Month => new TimeUnit(1, 12) { IsMonth = true };
49 | public static TimeUnit DayOfWeek => new TimeUnit(0, 6) { IsDayOfWeek = true };
50 | }
51 |
--------------------------------------------------------------------------------
/src/CronQuery.Tests/Fakes/OptionsMonitorFake.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * MIT License
3 | *
4 | * Copyright (c) 2018 Marx J. Moura
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 |
25 | namespace CronQuery.Tests.Fakes;
26 |
27 | using CronQuery.Mvc.Options;
28 | using Microsoft.Extensions.Options;
29 |
30 | public sealed class OptionsMonitorFake : IOptionsMonitor, IDisposable
31 | {
32 | private readonly JobRunnerOptions _options;
33 |
34 | private Action _listener = null!;
35 |
36 | public OptionsMonitorFake(JobRunnerOptions options)
37 | {
38 | _options = options;
39 | }
40 |
41 | public JobRunnerOptions CurrentValue => _options;
42 |
43 | public void Change(Action change)
44 | {
45 | change(_options);
46 |
47 | if (_listener != null)
48 | {
49 | _listener(_options, string.Empty);
50 | }
51 | }
52 |
53 | public void Dispose()
54 | {
55 | _listener = null!;
56 | }
57 |
58 | public JobRunnerOptions Get(string name)
59 | {
60 | return _options;
61 | }
62 |
63 | public IDisposable OnChange(Action listener)
64 | {
65 | _listener = listener;
66 |
67 | return this;
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/CronQuery.API/Mvc/Options/TimeZoneOptions.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * MIT License
3 | *
4 | * Copyright (c) 2018 Marx J. Moura
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 |
25 | namespace CronQuery.Mvc.Options;
26 |
27 | using System.Text.RegularExpressions;
28 |
29 | public sealed class TimeZoneOptions
30 | {
31 | const string UtcOffsetRegex = @"^UTC[+-]\d{2}:\d{2}$";
32 |
33 | private readonly string _timeZone;
34 |
35 | public TimeZoneOptions(string timeZone)
36 | {
37 | _timeZone = timeZone;
38 | }
39 |
40 | public TimeZoneInfo ToTimeZoneInfo()
41 | {
42 | if (string.IsNullOrWhiteSpace(_timeZone))
43 | {
44 | return TimeZoneInfo.Utc;
45 | }
46 | else if (Regex.IsMatch(_timeZone, UtcOffsetRegex))
47 | {
48 | var timeSpanString = Regex.Replace(_timeZone, "UTC[+]?", string.Empty);
49 |
50 | return TimeZoneInfo.CreateCustomTimeZone(
51 | id: "CronQuery",
52 | baseUtcOffset: TimeSpan.Parse(timeSpanString),
53 | displayName: $"({_timeZone}) CronQuery",
54 | standardDisplayName: "CronQuery Custom Time"
55 | );
56 | }
57 | else
58 | {
59 | return TimeZoneInfo.FindSystemTimeZoneById(_timeZone);
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/CronQuery.Tests/Unit/Runner/TimeZoneTest.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * MIT License*
3 | * Copyright (c) 2018 Marx J. Moura
4 | *
5 | * Permission is hereby granted, free of charge, to any person obtaining a copy
6 | * of this software and associated documentation files (the "Software"), to deal
7 | * in the Software without restriction, including without limitation the rights
8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | * copies of the Software, and to permit persons to whom the Software is
10 | * furnished to do so, subject to the following conditions:
11 | *
12 | * The above copyright notice and this permission notice shall be included in all
13 | * copies or substantial portions of the Software.
14 | *
15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | * SOFTWARE.
22 | */
23 |
24 | namespace CronQuery.Tests.Unit.Runner;
25 |
26 | using CronQuery.Mvc.Options;
27 | using Xunit;
28 |
29 | public sealed class TimeZoneTest
30 | {
31 | private readonly DateTime _mockUtcTime;
32 |
33 | public TimeZoneTest()
34 | {
35 | _mockUtcTime = new DateTime(2000, 01, 01, 00, 00, 00, DateTimeKind.Utc);
36 | }
37 |
38 | [Theory]
39 | [InlineData(null, "2000-01-01T00:00:00")]
40 | [InlineData("", "2000-01-01T00:00:00")]
41 | [InlineData(" ", "2000-01-01T00:00:00")]
42 | [InlineData("UTC+00:00", "2000-01-01T00:00:00")]
43 | [InlineData("UTC+01:00", "2000-01-01T01:00:00")]
44 | [InlineData("UTC-03:00", "1999-12-31T21:00:00")]
45 | [InlineData("UTC+01:30", "2000-01-01T01:30:00")]
46 | [InlineData("Europe/Budapest", "2000-01-01T01:00:00")]
47 | [InlineData("America/Sao_Paulo", "1999-12-31T22:00:00")]
48 | public void ShouldConvertToLocalTime(string timeZone, string expected)
49 | {
50 | var timeZoneOptions = new TimeZoneOptions(timeZone);
51 | var timeZoneInfo = timeZoneOptions.ToTimeZoneInfo();
52 | var localDateTime = TimeZoneInfo.ConvertTime(_mockUtcTime, timeZoneInfo);
53 | var iso8601Format = localDateTime.ToString("yyyy-MM-ddTHH:mm:ss");
54 |
55 | Assert.Equal(expected, iso8601Format);
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/docs/gulpfile.js:
--------------------------------------------------------------------------------
1 | const gulp = require('gulp')
2 | const autoprefixer = require('gulp-autoprefixer')
3 | const babelify = require('babelify')
4 | const bro = require('gulp-bro')
5 | const connect = require('gulp-connect')
6 | const htmlmin = require('gulp-htmlmin')
7 | const rename = require("gulp-rename")
8 | const sass = require('gulp-sass')(require('sass'))
9 | const uglify = require('gulp-uglify')
10 | const handlebars = require('./handlebars')
11 |
12 | function printError(error) {
13 | console.log(error)
14 | }
15 |
16 | function scss() {
17 | return gulp.src('www/scss/docs.scss')
18 | .pipe(sass({ outputStyle: 'compressed' }).on('error', printError))
19 | .pipe(autoprefixer({ overrideBrowserslist: ['last 2 versions'], cascade: false }))
20 | .pipe(rename({ extname: '.min.css' }))
21 | .pipe(gulp.dest('dist'))
22 | }
23 |
24 | function js() {
25 | return gulp.src('www/js/docs.js')
26 | .pipe(bro({ transform: [babelify.configure({ presets: ['@babel/preset-env'] })] }))
27 | .pipe(uglify().on('error', printError))
28 | .pipe(rename({ extname: '.min.js' }))
29 | .pipe(gulp.dest('dist'))
30 | }
31 |
32 | function hbs() {
33 | return gulp.src(['www/**/_*.hbs', 'www/**/*.hbs'], { base: './www' })
34 | .pipe(handlebars())
35 | .pipe(rename({ extname: '.html' }))
36 | .pipe(htmlmin({ collapseWhitespace: true }).on('error', printError))
37 | .pipe(gulp.dest('dist'))
38 | }
39 |
40 | function fonts() {
41 | return gulp.src('www/**/*.ttf')
42 | .pipe(gulp.dest('dist'))
43 | }
44 |
45 | function img() {
46 | return gulp.src(['www/**/*.{svg,jpg,png,ico}', '!www/**/_*.*'])
47 | .pipe(gulp.dest('dist'))
48 | }
49 |
50 | function build(done) {
51 | gulp.parallel(scss, js, hbs, fonts, img)(done)
52 | }
53 |
54 | function watch(done) {
55 | gulp.watch('www/**/*.scss', gulp.series(scss))
56 | gulp.watch('www/**/*.js', gulp.series(js))
57 | gulp.watch('www/**/*.hbs', gulp.series(hbs))
58 | gulp.watch('www/**/*.{svg,jpg,png,ico}', gulp.series(fonts))
59 | gulp.watch('www/**/*.ttf', gulp.series(img))
60 |
61 | done()
62 | }
63 |
64 | function serve(done) {
65 | connect.server({ root: 'dist', port: 8888, fallback: 'dist/404.html' })
66 | connect.serverClose()
67 |
68 | done()
69 | }
70 |
71 | function start(done) {
72 | process.env.NODE_ENV = 'development'
73 | gulp.series(serve, watch, build)(done)
74 | }
75 |
76 | function publish(done) {
77 | process.env.NODE_ENV = 'production'
78 | gulp.series(build)(done)
79 | }
80 |
81 | exports.start = start
82 | exports.publish = publish
83 |
--------------------------------------------------------------------------------
/src/CronQuery.API/Mvc/Jobs/JobInterval.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * MIT License
3 | *
4 | * Copyright (c) 2018 Marx J. Moura
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 |
25 | namespace CronQuery.Mvc.Jobs;
26 |
27 | using CronQuery.Cron;
28 | using System.Reactive.Linq;
29 |
30 | public sealed class JobInterval : IDisposable
31 | {
32 | private readonly CronExpression _cron;
33 | private readonly TimeZoneInfo _timezone;
34 | private readonly Func _work;
35 |
36 | private IDisposable _subscription = null!;
37 |
38 | public JobInterval(CronExpression cron, TimeZoneInfo timezone, Func work)
39 | {
40 | _cron = cron ?? throw new ArgumentNullException(nameof(cron));
41 | _timezone = timezone ?? throw new ArgumentNullException(nameof(timezone));
42 | _work = work ?? throw new ArgumentNullException(nameof(work));
43 | }
44 |
45 | public void Dispose()
46 | {
47 | if (_subscription != null)
48 | {
49 | _subscription.Dispose();
50 | }
51 | }
52 |
53 | public void Run()
54 | {
55 | var now = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, _timezone);
56 | var nextTime = _cron.Next(now);
57 |
58 | if (nextTime == DateTime.MinValue)
59 | {
60 | return;
61 | }
62 |
63 | var interval = nextTime - now;
64 |
65 | _subscription = Observable.Timer(interval)
66 | .Select(tick => Observable.FromAsync(_work))
67 | .Concat()
68 | .Subscribe(
69 | onNext: tick => { /* noop */ },
70 | onCompleted: Run
71 | );
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/CronQuery.Tests/Unit/Cron/ExpressionWithAsteriskTest.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * MIT License
3 | *
4 | * Copyright (c) 2018 Marx J. Moura
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 |
25 | namespace CronQuery.Tests.Unit.Cron;
26 |
27 | using CronQuery.Cron;
28 | using Xunit;
29 |
30 | public sealed class ExpressionWithAsteriskTest
31 | {
32 | [Fact]
33 | public void ShouldGetNextSecond()
34 | {
35 | var expression = new CronExpression("* * * * * *");
36 | var current = new DateTime(2018, 12, 30, 08, 30, 20);
37 | var expected = new DateTime(2018, 12, 30, 08, 30, 21);
38 |
39 | Assert.Equal(expected, expression.Next(current));
40 | }
41 |
42 | [Fact]
43 | public void ShouldGetNextMinute()
44 | {
45 | var expression = new CronExpression("* * * * * *");
46 | var current = new DateTime(2018, 12, 30, 08, 30, 59);
47 | var expected = new DateTime(2018, 12, 30, 08, 31, 00);
48 |
49 | Assert.Equal(expected, expression.Next(current));
50 | }
51 |
52 | [Fact]
53 | public void ShouldGetNextHour()
54 | {
55 | var expression = new CronExpression("* * * * * *");
56 | var current = new DateTime(2018, 12, 30, 08, 59, 59);
57 | var expected = new DateTime(2018, 12, 30, 09, 00, 00);
58 |
59 | Assert.Equal(expected, expression.Next(current));
60 | }
61 |
62 | [Fact]
63 | public void ShouldGetNextDay()
64 | {
65 | var expression = new CronExpression("* * * * * *");
66 | var current = new DateTime(2018, 12, 30, 23, 59, 59);
67 | var expected = new DateTime(2018, 12, 31, 00, 00, 00);
68 |
69 | Assert.Equal(expected, expression.Next(current));
70 | }
71 |
72 | [Fact]
73 | public void ShouldGetNextMonth()
74 | {
75 | var expression = new CronExpression("* * * * * *");
76 | var current = new DateTime(2018, 12, 31, 23, 59, 59);
77 | var expected = new DateTime(2019, 01, 01, 00, 00, 00);
78 |
79 | Assert.Equal(expected, expression.Next(current));
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/CronQuery.Tests/Unit/Cron/ExpressionWithCommaTest.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * MIT License
3 | *
4 | * Copyright (c) 2018 Marx J. Moura
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 |
25 | namespace CronQuery.Tests.Unit.Cron;
26 |
27 | using CronQuery.Cron;
28 | using Xunit;
29 |
30 | public sealed class ExpressionWithCommaTest
31 | {
32 | [Fact]
33 | public void ShouldGetNextMinute()
34 | {
35 | var expression = new CronExpression("10,20,30 * * * * *");
36 | var current = new DateTime(2018, 12, 30, 08, 30, 10);
37 | var expected = new DateTime(2018, 12, 30, 08, 30, 20);
38 |
39 | Assert.Equal(expected, expression.Next(current));
40 | }
41 |
42 | [Fact]
43 | public void ShouldGetNextMinuteAfterLastSecond()
44 | {
45 | var expression = new CronExpression("10,20,30 * * * * *");
46 | var current = new DateTime(2018, 12, 30, 08, 30, 30);
47 | var expected = new DateTime(2018, 12, 30, 08, 31, 10);
48 |
49 | Assert.Equal(expected, expression.Next(current));
50 | }
51 |
52 | [Fact]
53 | public void ShouldGetNextHourAfterLastMinute()
54 | {
55 | var expression = new CronExpression("* 10,20,30 * * * *");
56 | var current = new DateTime(2018, 12, 30, 08, 30, 59);
57 | var expected = new DateTime(2018, 12, 30, 09, 10, 00);
58 |
59 | Assert.Equal(expected, expression.Next(current));
60 | }
61 |
62 | [Fact]
63 | public void ShouldGetNextDayAfterLastHour()
64 | {
65 | var expression = new CronExpression("* * 6,18 * * *");
66 | var current = new DateTime(2018, 12, 30, 18, 59, 59);
67 | var expected = new DateTime(2018, 12, 31, 06, 00, 00);
68 |
69 | Assert.Equal(expected, expression.Next(current));
70 | }
71 |
72 | [Fact]
73 | public void ShouldGetNextMonthAfterLastDay()
74 | {
75 | var expression = new CronExpression("* * * 15,20,25 * *");
76 | var current = new DateTime(2018, 12, 25, 23, 59, 59);
77 | var expected = new DateTime(2019, 01, 15, 00, 00, 00);
78 |
79 | Assert.Equal(expected, expression.Next(current));
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/CronQuery.Tests/Functional/TestProgram.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * MIT License
3 | *
4 | * Copyright (c) 2018 Marx J. Moura
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 |
25 | namespace CronQuery.Tests.Functional;
26 |
27 | using CronQuery.Mvc.DependencyInjection;
28 | using CronQuery.Tests.Fakes;
29 | using CronQuery.Tests.Fakes.Jobs;
30 | using Microsoft.AspNetCore.Builder;
31 | using Microsoft.AspNetCore.Hosting;
32 | using Microsoft.AspNetCore.TestHost;
33 | using Microsoft.Extensions.Configuration;
34 | using Microsoft.Extensions.DependencyInjection;
35 | using Microsoft.Extensions.Logging;
36 | using System.Diagnostics;
37 |
38 | public static class TestProgram
39 | {
40 | public static TestServer CreateServer()
41 | {
42 | var host = new WebHostBuilder()
43 | .UseEnvironment("Testing")
44 | .ConfigureAppConfiguration((builderContext, config) =>
45 | {
46 | var targetFramework = "net6.0";
47 | var mode = Debugger.IsAttached ? "Debug" : "Release";
48 | var buildPath = Path.Combine("bin", mode, targetFramework);
49 | var projectPath = AppContext.BaseDirectory.Replace($"{buildPath}/", string.Empty);
50 | var appsettings = Path.Combine(projectPath, "Functional", "appsettings.json");
51 |
52 | config.AddJsonFile(appsettings, optional: false, reloadOnChange: true);
53 | })
54 | .ConfigureServices((builder, services) =>
55 | {
56 | services.AddRouting();
57 |
58 | services.AddCronQuery(builder.Configuration.GetSection("CronQuery"));
59 |
60 | services.AddSingleton();
61 |
62 | services.AddSingleton();
63 | services.AddSingleton();
64 | services.AddSingleton();
65 | services.AddSingleton();
66 | })
67 | .Configure(builder =>
68 | {
69 | builder.UseRouting();
70 | builder.UseEndpoints(api => api.MapGet("/", () => "Testing jobs..."));
71 | });
72 |
73 | return new(host);
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/CronQuery.Tests/Unit/Runner/JobIntervalTest.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * MIT License
3 | *
4 | * Copyright (c) 2018 Marx J. Moura
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 |
25 | namespace CronQuery.Tests.Unit.Runner;
26 |
27 | using CronQuery.Cron;
28 | using CronQuery.Mvc.Jobs;
29 | using Xunit;
30 |
31 | public sealed class JobIntervalTest
32 | {
33 | [Fact]
34 | public void ShouldRunWork()
35 | {
36 | var fires = 0;
37 | var cron = new CronExpression("* * * * * *");
38 | var interval = new JobInterval(cron, TimeZoneInfo.Utc, () => Task.FromResult(++fires));
39 |
40 | interval.Run();
41 |
42 | Task.Delay(2500).GetAwaiter().GetResult(); // Waiting for the job
43 |
44 | Assert.Equal(2, fires);
45 | }
46 |
47 | [Fact]
48 | public void ShouldNotRunWorkAfterDispose()
49 | {
50 | var fires = 0;
51 | var cron = new CronExpression("* * * * * *");
52 | var interval = new JobInterval(cron, TimeZoneInfo.Utc, () => Task.FromResult(++fires));
53 |
54 | interval.Run();
55 |
56 | Task.Delay(1500).GetAwaiter().GetResult(); // Waiting for the job
57 |
58 | interval.Dispose();
59 |
60 | Task.Delay(1500).GetAwaiter().GetResult(); // Waiting for the job
61 |
62 | Assert.Equal(1, fires);
63 | }
64 |
65 | [Fact]
66 | public void ShouldNotRunWorkForUnreachableCondition()
67 | {
68 | var fires = 0;
69 | var cron = new CronExpression("* * * 30 2 *");
70 | var interval = new JobInterval(cron, TimeZoneInfo.Utc, () => Task.FromResult(++fires));
71 |
72 | interval.Run();
73 |
74 | Assert.Equal(0, fires);
75 | }
76 |
77 | [Fact]
78 | public void ShouldNotInstanceWithNullCron()
79 | {
80 | Assert.Throws(() => new JobInterval(null!, TimeZoneInfo.Utc, () => Task.CompletedTask));
81 | }
82 |
83 | [Fact]
84 | public void ShouldNotInstanceWithNullNullTimezone()
85 | {
86 | var cron = new CronExpression("* * * 30 2 *");
87 |
88 | Assert.Throws(() => new JobInterval(cron, null!, () => Task.CompletedTask));
89 | }
90 |
91 | [Fact]
92 | public void ShouldNotInstanceWithNullWork()
93 | {
94 | var cron = new CronExpression("* * * 30 2 *");
95 |
96 | Assert.Throws(() => new JobInterval(cron, TimeZoneInfo.Utc, null!));
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/CronQuery.Tests/Unit/Cron/InvalidExpressionTest.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * MIT License
3 | *
4 | * Copyright (c) 2018 Marx J. Moura
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 |
25 | namespace CronQuery.Tests.Unit.Cron;
26 |
27 | using CronQuery.Cron;
28 | using Xunit;
29 |
30 | public sealed class InvalidExpressionTest
31 | {
32 | [Fact]
33 | public void ShouldNotEvaluateLowerThan6Fields()
34 | {
35 | var expression = new CronExpression("* * * * *");
36 | var current = DateTime.UtcNow;
37 | var expected = DateTime.MinValue;
38 |
39 | Assert.False(expression.IsValid);
40 | Assert.Equal(expected, expression.Next(current));
41 | }
42 |
43 | [Fact]
44 | public void ShouldNotEvaluateMoreThan6Fields()
45 | {
46 | var expression = new CronExpression("* * * * * * *");
47 | var current = DateTime.UtcNow;
48 | var expected = DateTime.MinValue;
49 |
50 | Assert.False(expression.IsValid);
51 | Assert.Equal(expected, expression.Next(current));
52 | }
53 |
54 | [Fact]
55 | public void ShouldNotEvaluateInvalidExpression()
56 | {
57 | var expression = new CronExpression("IN V A L I D");
58 | var current = DateTime.UtcNow;
59 | var expected = DateTime.MinValue;
60 |
61 | Assert.False(expression.IsValid);
62 | Assert.Equal(expected, expression.Next(current));
63 | }
64 |
65 | [Fact]
66 | public void ShouldIgnoreSecondOutOfRange()
67 | {
68 | var expression = new CronExpression("99 * * * * *");
69 | var current = new DateTime(2019, 01, 01, 00, 00, 00);
70 | var expected = new DateTime(2019, 01, 01, 00, 00, 59);
71 |
72 | Assert.Equal(expected, expression.Next(current));
73 | }
74 |
75 | [Fact]
76 | public void ShouldIgnoreIncrementByZero()
77 | {
78 | var expression = new CronExpression("*/0 * * * * *");
79 | var current = new DateTime(2019, 01, 01, 00, 00, 00);
80 | var expected = new DateTime(2019, 01, 01, 00, 00, 01);
81 |
82 | Assert.Equal(expected, expression.Next(current));
83 | }
84 |
85 | [Fact]
86 | public void ShouldIgnoreCharacterNotAllowed()
87 | {
88 | var expression = new CronExpression("* * 10#5 * * *");
89 | var current = new DateTime(2019, 01, 01, 00, 00, 00);
90 | var expected = DateTime.MinValue;
91 |
92 | Assert.Equal(expected, expression.Next(current));
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/CronQuery.Tests/Unit/Cron/ExpressionWithLAndWTest.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * MIT License
3 | *
4 | * Copyright (c) 2018 Marx J. Moura
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 |
25 | namespace CronQuery.Tests.Unit.Cron;
26 |
27 | using CronQuery.Cron;
28 | using Xunit;
29 |
30 | public sealed class ExpressionWithLAndWTest
31 | {
32 | [Fact]
33 | public void ShouldGetNearestWeekdayFromWeekday()
34 | {
35 | var expression = new CronExpression("* * * LW * *");
36 | var current = new DateTime(2018, 12, 01, 08, 00, 00);
37 | var expected = new DateTime(2018, 12, 31, 00, 00, 00);
38 |
39 | Assert.Equal(expected, expression.Next(current));
40 | }
41 |
42 | [Fact]
43 | public void ShouldGetNextMonthNearestWeekdayFromWeekday()
44 | {
45 | var expression = new CronExpression("* * * LW * *");
46 | var current = new DateTime(2019, 01, 31, 23, 59, 59);
47 | var expected = new DateTime(2019, 02, 28, 00, 00, 00);
48 |
49 | Assert.Equal(expected, expression.Next(current));
50 | }
51 |
52 | [Fact]
53 | public void ShouldGetNearestWeekdayFromSaturday()
54 | {
55 | var expression = new CronExpression("* * * LW * *");
56 | var current = new DateTime(2019, 08, 01, 08, 00, 00);
57 | var expected = new DateTime(2019, 08, 30, 00, 00, 00);
58 |
59 | Assert.Equal(expected, expression.Next(current));
60 | }
61 |
62 | [Fact]
63 | public void ShouldGetNextMonthNearestWeekdayFromSaturday()
64 | {
65 | var expression = new CronExpression("* * * LW * *");
66 | var current = new DateTime(2019, 08, 30, 23, 59, 59);
67 | var expected = new DateTime(2019, 09, 30, 00, 00, 00);
68 |
69 | Assert.Equal(expected, expression.Next(current));
70 | }
71 |
72 | [Fact]
73 | public void ShouldGetNearestWeekdayFromSunday()
74 | {
75 | var expression = new CronExpression("* * * LW * *");
76 | var current = new DateTime(2019, 03, 01, 08, 00, 00);
77 | var expected = new DateTime(2019, 03, 29, 00, 00, 00);
78 |
79 | Assert.Equal(expected, expression.Next(current));
80 | }
81 |
82 | [Fact]
83 | public void ShouldGetNextMonthNearestWeekdayFromSunday()
84 | {
85 | var expression = new CronExpression("* * * LW * *");
86 | var current = new DateTime(2019, 03, 29, 23, 59, 59);
87 | var expected = new DateTime(2019, 04, 30, 00, 00, 00);
88 |
89 | Assert.Equal(expected, expression.Next(current));
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/src/CronQuery.API/Cron/CronSyntax.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * MIT License
3 | *
4 | * Copyright (c) 2018 Marx J. Moura
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 |
25 | namespace CronQuery.Cron;
26 |
27 | using System.Text.RegularExpressions;
28 |
29 | internal sealed class CronSyntax
30 | {
31 | const string Asterisk = @"^\*$"; // Matches: *
32 | const string Dash = @"^\d{1,2}-\d{1,2}$"; // Matches: 00-00
33 | const string Hash = @"^\d{1,2}#[1-5]$"; // Matches: 00#0
34 | const string LAndW = @"^(\d)?L$|L-\d{1,2}|^LW$|^\d{1,2}W$"; // Matches: L, 0L, L-00, LW or 00W
35 | const string Slash = @"^(\*|\d{1,2}(-\d{1,2})?)/\d{1,2}$"; // Matches: */00, 00/00 or 00-00/00
36 | const string ListValue = @"\d{1,2}|\d{1,2}-\d{1,2}|\d{1,2}(-\d{1,2})?/\d{1,2}"; // Matches: 00, 00-00, 00/00 or 00-00/00
37 |
38 | const int SecondPosition = 0;
39 | const int MinutePosition = 1;
40 | const int HourPosition = 2;
41 | const int DayPosition = 3;
42 | const int MonthPosition = 4;
43 | const int DayOfWeekPosition = 5;
44 |
45 | private readonly IEnumerable _expression;
46 |
47 | public CronSyntax(IEnumerable expression)
48 | {
49 | _expression = expression;
50 | }
51 |
52 | public bool IsValid()
53 | {
54 | if (_expression.Count() != 6)
55 | {
56 | return false;
57 | }
58 |
59 | if (!AllowedCharacters("*-,/", _expression.ElementAt(SecondPosition))) return false;
60 | if (!AllowedCharacters("*-,/", _expression.ElementAt(MinutePosition))) return false;
61 | if (!AllowedCharacters("*-,/", _expression.ElementAt(HourPosition))) return false;
62 | if (!AllowedCharacters("*-,/LW", _expression.ElementAt(DayPosition))) return false;
63 | if (!AllowedCharacters("*-,/", _expression.ElementAt(MonthPosition))) return false;
64 | if (!AllowedCharacters("*-,/L#", _expression.ElementAt(DayOfWeekPosition))) return false;
65 |
66 | return IsWellFormed();
67 | }
68 |
69 | private bool AllowedCharacters(string allowedCharacters, string subExpression)
70 | {
71 | var characters = Regex.Replace(subExpression, @"\d", string.Empty);
72 |
73 | if (!characters.Any())
74 | {
75 | return true;
76 | }
77 |
78 | return !characters.Where(character => !allowedCharacters.Contains(character)).Any();
79 | }
80 |
81 | private bool IsWellFormed()
82 | {
83 | var list = $@"^{ListValue}(,({ListValue}))*$";
84 | var pattern = $@"{Asterisk}|{Dash}|{Hash}|{Slash}|{LAndW}|{list}";
85 | var regex = new Regex(pattern, RegexOptions.Compiled);
86 |
87 | return _expression.All(expression => regex.IsMatch(expression));
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/CronQuery.Tests/Unit/Cron/ExpressionWithSlashTest.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * MIT License
3 | *
4 | * Copyright (c) 2018 Marx J. Moura
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 |
25 | namespace CronQuery.Tests.Unit.Cron;
26 |
27 | using CronQuery.Cron;
28 | using Xunit;
29 |
30 | public sealed class ExpressionWithSlashTest
31 | {
32 | [Fact]
33 | public void ShouldGetNextSecond()
34 | {
35 | var expression = new CronExpression("*/10 * * * * *");
36 | var current = new DateTime(2018, 12, 30, 08, 15, 20);
37 | var expected = new DateTime(2018, 12, 30, 08, 15, 30);
38 |
39 | Assert.Equal(expected, expression.Next(current));
40 | }
41 |
42 | [Fact]
43 | public void ShouldGetNextMinuteAfterLastSecond()
44 | {
45 | var expression = new CronExpression("*/10 * * * * *");
46 | var current = new DateTime(2018, 12, 30, 08, 15, 50);
47 | var expected = new DateTime(2018, 12, 30, 08, 16, 00);
48 |
49 | Assert.Equal(expected, expression.Next(current));
50 | }
51 |
52 | [Fact]
53 | public void ShouldGetNextHourAfterLastMinute()
54 | {
55 | var expression = new CronExpression("* */10 * * * *");
56 | var current = new DateTime(2018, 12, 30, 08, 50, 59);
57 | var expected = new DateTime(2018, 12, 30, 09, 00, 00);
58 |
59 | Assert.Equal(expected, expression.Next(current));
60 | }
61 |
62 | [Fact]
63 | public void ShouldGetNextDayAfterLastHour()
64 | {
65 | var expression = new CronExpression("* * */10 * * *");
66 | var current = new DateTime(2018, 12, 30, 20, 59, 59);
67 | var expected = new DateTime(2018, 12, 31, 00, 00, 00);
68 |
69 | Assert.Equal(expected, expression.Next(current));
70 | }
71 |
72 | [Fact]
73 | public void ShouldGetNextMonthAfterLastDay()
74 | {
75 | var expression = new CronExpression("* * * */7 * *");
76 | var current = new DateTime(2018, 12, 29, 23, 59, 59);
77 | var expected = new DateTime(2019, 01, 01, 00, 00, 00);
78 |
79 | Assert.Equal(expected, expression.Next(current));
80 | }
81 |
82 | [Fact]
83 | public void ShouldGetNextYearAfterLastMonth()
84 | {
85 | var expression = new CronExpression("* * * * */6 *");
86 | var current = new DateTime(2018, 07, 31, 23, 59, 59);
87 | var expected = new DateTime(2019, 01, 01, 00, 00, 00);
88 |
89 | Assert.Equal(expected, expression.Next(current));
90 | }
91 |
92 | [Fact]
93 | public void ShouldGetNextDayOfWeek()
94 | {
95 | var expression = new CronExpression("* * * * * */2");
96 | var current = new DateTime(2018, 12, 25, 23, 59, 59);
97 | var expected = new DateTime(2018, 12, 27, 00, 00, 00);
98 |
99 | Assert.Equal(expected, expression.Next(current));
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/src/CronQuery.Tests/Unit/Cron/ExpressionWithDigitTest.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * MIT License
3 | *
4 | * Copyright (c) 2018 Marx J. Moura
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 |
25 | namespace CronQuery.Tests.Unit.Cron;
26 |
27 | using CronQuery.Cron;
28 | using Xunit;
29 |
30 | public sealed class ExpressionWithDigitTest
31 | {
32 | [Fact]
33 | public void ShouldGetNextMinuteAfterLastSecond()
34 | {
35 | var expression = new CronExpression("30 * * * * *");
36 | var current = new DateTime(2018, 12, 30, 08, 30, 30);
37 | var expected = new DateTime(2018, 12, 30, 08, 31, 30);
38 |
39 | Assert.Equal(expected, expression.Next(current));
40 | }
41 |
42 | [Fact]
43 | public void ShouldGetNextHourAfterLastMinute()
44 | {
45 | var expression = new CronExpression("* 30 * * * *");
46 | var current = new DateTime(2018, 12, 30, 08, 30, 59);
47 | var expected = new DateTime(2018, 12, 30, 09, 30, 00);
48 |
49 | Assert.Equal(expected, expression.Next(current));
50 | }
51 |
52 | [Fact]
53 | public void ShouldGetNextDayAfterLastHour()
54 | {
55 | var expression = new CronExpression("* * 8 * * *");
56 | var current = new DateTime(2018, 12, 30, 23, 59, 59);
57 | var expected = new DateTime(2018, 12, 31, 08, 00, 00);
58 |
59 | Assert.Equal(expected, expression.Next(current));
60 | }
61 |
62 | [Fact]
63 | public void ShouldGetNextMonthAfterLastDay()
64 | {
65 | var expression = new CronExpression("* * * 15 * *");
66 | var current = new DateTime(2018, 12, 31, 23, 59, 59);
67 | var expected = new DateTime(2019, 01, 15, 00, 00, 00);
68 |
69 | Assert.Equal(expected, expression.Next(current));
70 | }
71 |
72 | [Fact]
73 | public void ShouldGetNextMonthAfterLastDayInMonth()
74 | {
75 | var expression = new CronExpression("* * * 31 * *");
76 | var current = new DateTime(2019, 01, 31, 23, 59, 59);
77 | var expected = new DateTime(2019, 03, 31, 00, 00, 00);
78 |
79 | Assert.Equal(expected, expression.Next(current));
80 | }
81 |
82 | [Fact]
83 | public void ShouldGetNextYearAfterLastMonth()
84 | {
85 | var expression = new CronExpression("* * * * 6 *");
86 | var current = new DateTime(2018, 12, 31, 23, 59, 59);
87 | var expected = new DateTime(2019, 06, 01, 00, 00, 00);
88 |
89 | Assert.Equal(expected, expression.Next(current));
90 | }
91 |
92 | [Fact]
93 | public void ShouldGetNextDayOfWeek()
94 | {
95 | var expression = new CronExpression("* * * * * 3");
96 | var current = new DateTime(2018, 12, 28, 23, 59, 59);
97 | var expected = new DateTime(2019, 01, 02, 00, 00, 00);
98 |
99 | Assert.Equal(expected, expression.Next(current));
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/src/CronQuery.Tests/Unit/Cron/ExpressionWithWTest.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * MIT License
3 | *
4 | * Copyright (c) 2018 Marx J. Moura
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 |
25 | namespace CronQuery.Tests.Unit.Cron;
26 |
27 | using CronQuery.Cron;
28 | using Xunit;
29 |
30 | public sealed class ExpressionWithWTest
31 | {
32 | [Fact]
33 | public void ShouldGetNearestWeekdayFromSaturday()
34 | {
35 | var expression = new CronExpression("* * * 15W * *");
36 | var current = new DateTime(2018, 12, 01, 23, 59, 59);
37 | var expected = new DateTime(2018, 12, 14, 00, 00, 00);
38 |
39 | Assert.Equal(expected, expression.Next(current));
40 | }
41 |
42 | [Fact]
43 | public void ShouldGetNextMonthNearestWeekdayFromSaturday()
44 | {
45 | var expression = new CronExpression("* * * 15W * *");
46 | var current = new DateTime(2018, 12, 17, 23, 59, 59);
47 | var expected = new DateTime(2019, 01, 15, 00, 00, 00);
48 |
49 | Assert.Equal(expected, expression.Next(current));
50 | }
51 |
52 | [Fact]
53 | public void ShouldGetNearestWeekdayFromSaturdayFirstDayOfMonth()
54 | {
55 | var expression = new CronExpression("* * * 1W * *");
56 | var current = new DateTime(2018, 12, 01, 23, 59, 59);
57 | var expected = new DateTime(2018, 12, 03, 00, 00, 00);
58 |
59 | Assert.Equal(expected, expression.Next(current));
60 | }
61 |
62 | [Fact]
63 | public void ShouldGetNextMonthNearestWeekdayFromSaturdayFirstDayOfMonth()
64 | {
65 | var expression = new CronExpression("* * * 1W * *");
66 | var current = new DateTime(2019, 03, 30, 23, 59, 59);
67 | var expected = new DateTime(2019, 04, 01, 00, 00, 00);
68 |
69 | Assert.Equal(expected, expression.Next(current));
70 | }
71 |
72 | [Fact]
73 | public void ShouldGetNearestWeekdayFromSunday()
74 | {
75 | var expression = new CronExpression("* * * 9W * *");
76 | var current = new DateTime(2018, 12, 01, 23, 59, 59);
77 | var expected = new DateTime(2018, 12, 10, 00, 00, 00);
78 |
79 | Assert.Equal(expected, expression.Next(current));
80 | }
81 |
82 | [Fact]
83 | public void ShouldGetNextMonthNearestWeekdayFromSunday()
84 | {
85 | var expression = new CronExpression("* * * 9W * *");
86 | var current = new DateTime(2018, 12, 10, 23, 59, 59);
87 | var expected = new DateTime(2019, 01, 09, 00, 00, 00);
88 |
89 | Assert.Equal(expected, expression.Next(current));
90 | }
91 |
92 | [Fact]
93 | public void ShouldGetNearestWeekdayFromSundayLastDayOfMonth()
94 | {
95 | var expression = new CronExpression("* * * 31W * *");
96 | var current = new DateTime(2019, 03, 01, 23, 59, 59);
97 | var expected = new DateTime(2019, 03, 29, 00, 00, 00);
98 |
99 | Assert.Equal(expected, expression.Next(current));
100 | }
101 |
102 | [Fact]
103 | public void ShouldGetNextMonthNearestWeekdayFromSundayLastDayOfMonth()
104 | {
105 | var expression = new CronExpression("* * * 31W * *");
106 | var current = new DateTime(2019, 03, 31, 23, 59, 59);
107 | var expected = new DateTime(2019, 05, 31, 00, 00, 00);
108 |
109 | Assert.Equal(expected, expression.Next(current));
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/src/CronQuery.API/Cron/CronValue.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * MIT License
3 | *
4 | * Copyright (c) 2018 Marx J. Moura
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 |
25 | namespace CronQuery.Cron;
26 |
27 | using CronQuery.Extensions;
28 | using System.Text.RegularExpressions;
29 |
30 | internal sealed class CronValue
31 | {
32 | const int NoValue = -1;
33 |
34 | private readonly TimeUnit _timeUnit;
35 |
36 | public CronValue(string expression, TimeUnit timeUnit)
37 | {
38 | _timeUnit = timeUnit;
39 |
40 | IsAsterisk = expression == "*";
41 | IsL = expression == "L";
42 | HasAsterisk = expression.Contains("*");
43 | HasDash = expression.Contains("-");
44 | HasSlash = expression.Contains("/");
45 | HasHash = expression.Contains("#");
46 | HasL = expression.Contains("L");
47 | HasW = expression.Contains("W");
48 |
49 | var symbol = HasSlash ? '/' : HasDash ? '-' : HasHash ? '#' : '\0';
50 | var parameters = expression.Split(symbol);
51 | var nestedParameters = HasSlash && HasDash ? parameters.First().Split('-') : null!;
52 |
53 | var start =
54 | IsAsterisk ? timeUnit.MinValue : // Expression is *
55 | IsL ? NoValue : // Expression is L
56 | HasL && HasDash ? ToInt32(parameters.Last()) : // Expression is L-00
57 | HasL && HasW ? NoValue : // Expression is LW
58 | HasHash ? ToInt32(parameters.First()) : // Expression is 00#0
59 | HasSlash && HasAsterisk ? timeUnit.MinValue : // Expression is */00
60 | HasSlash && HasDash ? ToInt32(nestedParameters.First()) : // Expression is 00-00/00
61 | HasSlash || HasDash || HasW ? ToInt32(parameters.First()) : // Expression is 00/00 or 00-00 or 00W
62 | Math.Max(ToInt32(expression), timeUnit.MinValue); // Expression is 00
63 |
64 | var end =
65 | IsAsterisk ? timeUnit.MaxValue : // Expression is *
66 | IsL ? NoValue : // Expression is L
67 | HasL && HasDash ? ToInt32(parameters.Last()) : // Expression is L-00
68 | HasL && HasW ? NoValue : // Expression is LW
69 | HasHash ? start : // Expression is 00#0
70 | HasSlash && HasAsterisk ? timeUnit.MaxValue : // Expression is */00
71 | HasSlash && HasDash ? ToInt32(nestedParameters.Last()) : // Expression is 00-00/00
72 | HasSlash ? timeUnit.MaxValue : // Expression is 00/00
73 | HasW ? ToInt32(parameters.First()) : // Expression is 00W
74 | HasDash ? ToInt32(parameters.Last()) : // Expression is 00-00
75 | Math.Min(ToInt32(expression), timeUnit.MaxValue); // Expression is 00
76 |
77 | Nth = HasHash ? ToInt32(parameters.Last()) : 0;
78 | Step = HasSlash ? ToInt32(parameters.Last()) : 1;
79 | Values = start.To(end).Step(Step)
80 | .Where(value => value >= _timeUnit.MinValue)
81 | .Where(value => value <= _timeUnit.MaxValue)
82 | .ToList();
83 | }
84 |
85 | public IEnumerable Values { get; }
86 | public int Nth { get; }
87 | public int Step { get; }
88 | public bool IsAsterisk { get; }
89 | public bool IsL { get; }
90 | public bool HasAsterisk { get; }
91 | public bool HasL { get; }
92 | public bool HasW { get; }
93 | public bool HasDash { get; }
94 | public bool HasSlash { get; }
95 | public bool HasHash { get; }
96 |
97 | private int ToInt32(string value)
98 | {
99 | var digits = Regex.Replace(value, @"[^\d]+", string.Empty);
100 | return Convert.ToInt32(digits);
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/src/CronQuery.API/Cron/CronExpression.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * MIT License
3 | *
4 | * Copyright (c) 2018 Marx J. Moura
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 |
25 | namespace CronQuery.Cron;
26 |
27 | using CronQuery.Extensions;
28 |
29 | public sealed class CronExpression
30 | {
31 | private readonly CronField _second = null!;
32 | private readonly CronField _minute = null!;
33 | private readonly CronField _hour = null!;
34 | private readonly CronField _day = null!;
35 | private readonly CronField _month = null!;
36 | private readonly CronField _dayOfWeek = null!;
37 |
38 | public CronExpression(string expression)
39 | {
40 | var subExpressions = (expression ?? string.Empty).ToUpper().Split(' ');
41 | var syntax = new CronSyntax(subExpressions);
42 |
43 | IsValid = syntax.IsValid();
44 |
45 | if (IsValid)
46 | {
47 | _second = new CronField(subExpressions[0], TimeUnit.Second);
48 | _minute = new CronField(subExpressions[1], TimeUnit.Minute);
49 | _hour = new CronField(subExpressions[2], TimeUnit.Hour);
50 | _day = new CronField(subExpressions[3], TimeUnit.Day);
51 | _month = new CronField(subExpressions[4], TimeUnit.Month);
52 | _dayOfWeek = new CronField(subExpressions[5], TimeUnit.DayOfWeek);
53 | }
54 | }
55 |
56 | public bool IsValid { get; }
57 |
58 | public DateTime Next(DateTime query)
59 | {
60 | if (!IsValid || IsUnreachableCondition())
61 | {
62 | return DateTime.MinValue;
63 | }
64 |
65 | var time = _second.Next(query);
66 |
67 | if (time <= query || !_minute.Matches(time))
68 | {
69 | time = _second.Reset(time);
70 | time = _minute.Next(time);
71 | }
72 |
73 | if (time <= query || !_hour.Matches(time))
74 | {
75 | time = _second.Reset(time);
76 | time = _minute.Reset(time);
77 | time = _hour.Next(time);
78 | }
79 |
80 | while (time <= query || !_day.Matches(time) || !_month.Matches(time) || !_dayOfWeek.Matches(time))
81 | {
82 | time = _second.Reset(time);
83 | time = _minute.Reset(time);
84 | time = _hour.Reset(time);
85 |
86 | time = _day.Next(time);
87 |
88 | if (!_dayOfWeek.Matches(time))
89 | {
90 | time = _dayOfWeek.Next(time);
91 | }
92 |
93 | if (time <= query || !_month.Matches(time))
94 | {
95 | time = _day.Reset(time);
96 | time = _month.Next(time);
97 | }
98 |
99 | if (time <= query)
100 | {
101 | time = _month.Reset(time);
102 | time = time.AddYears(1);
103 | }
104 | }
105 |
106 | return time;
107 | }
108 |
109 | private bool IsUnreachableCondition()
110 | {
111 | // E.g. * * * 15W * 0
112 |
113 | var hasW = _day.Values.Any(cron => cron.HasW);
114 |
115 | var onlyWeekend = !_dayOfWeek.Values
116 | .SelectMany(cron => cron.Values)
117 | .Any(value => 1.To(5).Contains(value));
118 |
119 | if (hasW && onlyWeekend) return true;
120 |
121 | // E.g. * * * 31 2,4,6 *
122 |
123 | var days = _day.Values.SelectMany(cron => cron.Values);
124 | var months = _month.Values.SelectMany(cron => cron.Values);
125 |
126 | var monthsNotReachGivenDays = days.Any() && !days.Any(day =>
127 | months.Any(month => DateTime.DaysInMonth(1970, month) >= day));
128 |
129 | return monthsNotReachGivenDays;
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/src/CronQuery.API/Extensions/DateTimeExtensions.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * MIT License
3 | *
4 | * Copyright (c) 2018 Marx J. Moura
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 |
25 | namespace CronQuery.Extensions;
26 |
27 | using CronQuery.Cron;
28 |
29 | internal static class DateTimeExtensions
30 | {
31 | public static DateTime Last(this DateTime dateTime, int daysBefore = 0)
32 | {
33 | var daysInMonth = DateTime.DaysInMonth(dateTime.Year, dateTime.Month);
34 | var day = daysInMonth > daysBefore ? daysInMonth - daysBefore : 1;
35 |
36 | return dateTime.Set(day: day);
37 | }
38 |
39 | public static DateTime NearestWeekday(this DateTime dateTime)
40 | {
41 | if (dateTime.DayOfWeek == DayOfWeek.Saturday)
42 | {
43 | return dateTime.Day == 1 ?
44 | dateTime.Next(DayOfWeek.Monday) :
45 | dateTime.Previous(DayOfWeek.Friday);
46 | }
47 |
48 | if (dateTime.DayOfWeek == DayOfWeek.Sunday)
49 | {
50 | var lastDay = DateTime.DaysInMonth(dateTime.Year, dateTime.Month);
51 |
52 | return dateTime.Day == lastDay ?
53 | dateTime.Previous(DayOfWeek.Friday) :
54 | dateTime.Next(DayOfWeek.Monday);
55 | }
56 |
57 | return dateTime;
58 | }
59 |
60 | public static DateTime Next(this DateTime dateTime, DayOfWeek dayOfWeek)
61 | {
62 | var diff = dayOfWeek - dateTime.DayOfWeek;
63 |
64 | if (diff <= 0)
65 | {
66 | diff += 7;
67 | }
68 |
69 | return dateTime.AddDays(diff);
70 | }
71 |
72 | public static DateTime Next(this DateTime dateTime, DayOfWeek dayOfWeek, int occurrence)
73 | {
74 | var firstDate = dateTime.Set(day: 1);
75 |
76 | dateTime = firstDate.AddDays((7 - ((int)firstDate.DayOfWeek - (int)dayOfWeek)) % 7);
77 | dateTime = dateTime.AddDays(7 * (occurrence - 1));
78 |
79 | return dateTime;
80 | }
81 |
82 | public static DateTime Previous(this DateTime dateTime, DayOfWeek dayOfWeek)
83 | {
84 | while (dateTime.DayOfWeek != dayOfWeek)
85 | {
86 | dateTime = dateTime.AddDays(-1);
87 | }
88 |
89 | return dateTime;
90 | }
91 |
92 | public static int Get(this DateTime dateTime, TimeUnit timeUnit)
93 | {
94 | if (timeUnit.IsDayOfWeek) return (int)dateTime.DayOfWeek;
95 | if (timeUnit.IsMonth) return dateTime.Month;
96 | if (timeUnit.IsDay) return dateTime.Day;
97 | if (timeUnit.IsHour) return dateTime.Hour;
98 | if (timeUnit.IsMinute) return dateTime.Minute;
99 |
100 | return dateTime.Second;
101 | }
102 |
103 | public static DateTime Set(this DateTime dateTime, TimeUnit timeUnit, int value)
104 | {
105 | if (timeUnit.IsMonth) return dateTime.Set(month: value);
106 | if (timeUnit.IsDay) return dateTime.Set(day: value);
107 | if (timeUnit.IsHour) return dateTime.Set(hour: value);
108 | if (timeUnit.IsMinute) return dateTime.Set(minute: value);
109 |
110 | return dateTime.Set(second: value);
111 | }
112 |
113 | public static DateTime Set(this DateTime dateTime,
114 | int? year = null, int? month = null, int? day = null,
115 | int? hour = null, int? minute = null, int? second = null)
116 | {
117 | return new DateTime(
118 | year ?? dateTime.Year,
119 | month ?? dateTime.Month,
120 | day ?? dateTime.Day,
121 | hour ?? dateTime.Hour,
122 | minute ?? dateTime.Minute,
123 | second ?? dateTime.Second,
124 | dateTime.Millisecond,
125 | dateTime.Kind);
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/docs/www/index.hbs:
--------------------------------------------------------------------------------
1 | {{#> www/page }}
2 |
3 | {{#*inline "body-block"}}
4 |
5 |
6 | CronQuery is an open source
7 | and lightweight job runner for ASP.NET Core.
8 |
70 | Schedule your jobs using CRON expressions of six fields to a
71 | specific timezone (UTC is default). Save the configuration in your
72 | appsettings.json like the example below:
73 |
74 |
75 |
76 |
77 |
Job
78 |
Configuration
79 |
80 |
81 |
82 |
MyFirstJob
83 |
Runs every second on every day, except Sunday.
84 |
85 |
86 |
MySecondJob
87 |
Runs every day at 2:00 A.M.
88 |
89 |
90 |
MyThirdJob
91 |
Runs every second between 2:00 P.M. and 6:59 P.M. only on Saturday every 15 days.
92 |
93 |
94 |
95 |
96 |
{{ include 'www/snippets/SettingUpJob.json' }}
97 |
98 |
99 | Whenever you save the appsettings.json CronQuery immediately assumes the new configuration.
100 |
101 |
102 |
103 |
104 |
105 |
106 |
Property
107 |
Description
108 |
109 |
110 |
111 |
112 |
113 | CronQuery.Running
114 |
115 |
116 | Turn on (true) or turn off (false) the CronQuery runner.
117 |
118 |
119 |
120 |
121 | CronQuery.TimeZone
122 |
123 |
124 | System time zone ID or a custom UTC offset, e.g. UTC-03:00, UTC+01:00.
125 |
126 |
127 |
128 |
129 | CronQuery.Jobs[].Name
130 |
131 |
132 | Job class name.
133 |
134 |
135 |
136 |
137 | CronQuery.Jobs[].Running
138 |
139 |
140 | Turn on (true) or turn off (false) the job.
141 |
72 | The following table shows the special characters present in CRON syntax and their respective meanings.
73 |
74 |
75 |
76 |
77 |
78 |
79 |
Character
80 |
Description
81 |
Example
82 |
83 |
84 |
85 |
86 |
* (all)
87 |
Every time unit
88 |
Asterisk in the hour means "every hour"
89 |
90 |
91 |
- (range)
92 |
Range of values
93 |
1-5, which is equivalent to 1,2,3,4,5
94 |
95 |
96 |
, (list)
97 |
List of values
98 |
2,4,6,8
99 |
100 |
101 |
/ (increment)
102 |
Step values
103 |
*/6 in the hour means "every 6 hours", which is equivalent to 0,6,12,18
104 |
105 |
106 |
L (last)
107 |
Last day of month or last X day of week
108 |
109 | In month L means 31 for January or day 28 for February (non-leap years),
110 | in day of week 5L means "the last Friday of the month"
111 |
112 |
113 |
114 |
W (weekday)
115 |
Weekday (Monday to Friday) nearest the given day
116 |
117 | If you specify 15W and 15th is a Saturday the trigger fires on Friday the 14th,
118 | however for 1W and 1th is a Saturday the trigger fires on Monday the 3rd
119 | (because it does not exceed the limit of days of the month),
120 | if the 15h is a Sunday the trigger fires on Monday the 16th
121 | and if 15th is a weekday the trigger fires that day
122 |
138 | CRON allows the combination of subexpressions to produce a more complex CRON expression.
139 | The following list shows the combinations supported by CronQuery:
140 |
141 |
142 |
143 |
144 | *:
145 | Every time unit.
146 |
147 |
148 | 00-00:
149 | Range of values.
150 |
151 |
152 | */00:
153 | Every time unit skipping a given number of values.
154 |
155 |
156 | 00/00:
157 | Skipping a given number of values starting at specific value.
158 |
159 |
160 | 00-00/00:
161 | Skipping a given number of values in a given range of values.
162 |
163 |
164 | 00,00-00,00/00,00-00/00:
165 | List of values (you can produce variations).
166 | List can only contains single numbers, range, increment and range combined with increment.
167 |
168 |
169 | L (day of month only):
170 | Last day of the month (including weekend).
171 |
172 |
173 | LW (day of month only):
174 | Last weekday (Monday to Friday) of the month.
175 |
176 |
177 | L-00 (day of month only):
178 | Days (1-31) before the last day of month.
179 |
180 |
181 | 00W (day of month only):
182 | Weekday nearest the given day (1-31).
183 |
184 |
185 | 0L (day of week only):
186 | Last day of week (0-6) of the month.
187 |
188 |
189 | 00#0 (day of week only):
190 | The nth (1-5) day of the month.
191 |
192 |
193 |
194 |
195 | Read zero as any numeric value that you pass to the expression.
196 |