├── CMakeLists.txt ├── LICENSE.txt ├── README.md ├── component.mk ├── cron.c ├── include └── cron.h ├── library ├── ccronexpr │ ├── .gitignore │ ├── .travis.yml │ ├── LICENSE.txt │ ├── README.md │ ├── appveyor.yml │ ├── ccronexpr.c │ ├── ccronexpr.h │ └── ccronexpr_test.c └── jobs │ ├── jobs.c │ └── jobs.h └── test ├── component.mk └── test_cron.c /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(COMPONENT_ADD_INCLUDEDIRS "include" "library/ccronexpr" "library/jobs" ".") 2 | set(COMPONENT_SRCDIRS "library/ccronexpr" "library/jobs" ".") 3 | 4 | register_component() 5 | 6 | target_compile_definitions(${COMPONENT_TARGET} PUBLIC -D CRON_USE_LOCAL_TIME) -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CRON like component for the ESP-IDF framework 2 | 3 | This is a cron-like clone for the esp-idf framework. It uses cron-like sytanx and time libraries included in newlib (esp-idf framework) for task scheduling. 4 | 5 | ## How to use 6 | 7 | We tried to keep module functions interface at minimum there is a creator, a destroyer a cron module starter and a cron module stopper. The workflow would be to define at least one job and then start the module. Then create and destroy jobs as desired. Keep in mind that if there are no jobs to be scheduled the cron module will stop itself, this is by design as we don't want to waste cpu time. 8 | 9 | Please remember that this module relies heavilly on the time.h library. **Time has to be initialized before any job creation.** The library time.h can be set manually or with another component like sntp, but it must have started before to this module is in use. This component will not perform any checks to idetify if time has been set. 10 | 11 | ### Create 12 | 13 | Usage is pretty simple, we provided a component factory for cron-job creation. 14 | 15 | 16 | ```C 17 | cron_job *cron_job_create(const char *schedule, cron_job_callback callback, void *data) 18 | ``` 19 | 20 | * Where schedule is a cron-like string with seconds resolution. 21 | 22 | ┌────────────── second (0 - 59) 23 | | ┌───────────── minute (0 - 59) 24 | | │ ┌───────────── hour (0 - 23) 25 | | │ │ ┌───────────── day of month (1 - 31) 26 | | │ │ │ ┌───────────── month (1 - 12) 27 | | │ │ │ │ ┌───────────── day of week (0 - 6) (Sunday to Saturday; 28 | | │ │ │ │ │ 7 is also Sunday on some systems) 29 | | │ │ │ │ │ 30 | | │ │ │ │ │ 31 | * * * * * * 32 | 33 | Thank you alex at staticlibs.net for the good work on the parser!!. 34 | 35 | * The callback is just a function pointer for the job that will be scheduled with the running cron_job as an argument, defined as: 36 | 37 | ```C 38 | typedef void (*cron_job_callback)(cron_job *); 39 | ``` 40 | 41 | Please note that the callback is a simple function, no need for infinite loops or vTask calls, the cron module will handle this for you 42 | 43 | * And data is a non managed, non typed pointer that will be stored in the cron_job structure that can be used as the user needs. 44 | 45 | ### Destroy 46 | 47 | 48 | 49 | If you want to stop a previously created cron job simply call the destroy method with the returned cron_job from the creator. 50 | 51 | 52 | 53 | ```C 54 | int cron_job_destroy(cron_job * job); 55 | ``` 56 | 57 | 58 | ### Starting the module 59 | 60 | You can start the module with at least one defined job by calling 61 | 62 | ```C 63 | int cron_start(); 64 | ``` 65 | 66 | ### Stopping the module 67 | 68 | You can stop the module by calling 69 | 70 | ```C 71 | int cron_stop(); 72 | ``` 73 | 74 | ### Clearing all jobs a.k.a destroying all jobs 75 | 76 | We defined a helper to stop all cron jobs, we think it might be useful in some situations 77 | 78 | ```C 79 | int cron_job_clear_all(); 80 | ``` 81 | 82 | 83 | ## Example 84 | 85 | The first thing you need to pay attention to is to initialize the time module of newlib, you can do this in several ways, one good example is to use sntp for this. But if you want to do it manually the following code will work. 86 | 87 | ```C 88 | /* YOU MUST SET THE TIME FIRST BE CAREFUL ABOUT THIS - NOT PART OF THE MODULE*/ 89 | struct timeval tv; 90 | time_t begin=1530000000; 91 | tv.tv_sec = begin; // SOMEWHERE IN JUNE 2018 92 | settimeofday(&tv, NULL); 93 | /* END TIME SET */ 94 | ``` 95 | 96 | The header for this is just `cron.h`. 97 | 98 | The code below is all you need to run the code notice that we are running a sample callback function which you can find after this code. 99 | 100 | ```C 101 | cron_job * jobs[2]; 102 | jobs[0]=cron_job_create("* * * * * *",test_cron_job_sample_callback,(void *)0); 103 | jobs[1]=cron_job_create("*/5 * * * * *",test_cron_job_sample_callback,(void *)10000); 104 | cron_start(); 105 | vTaskDelay((running_seconds * 1000) / portTICK_PERIOD_MS); // This is just to emulate a delay between the calls 106 | cron_stop(); 107 | cron_job_clear_all(); 108 | ``` 109 | 110 | Sample callback: 111 | 112 | ```C 113 | void test_cron_job_sample_callback(cron_job *job) 114 | { 115 | /* DO YOUR WORK IN HERE */ 116 | return; 117 | } 118 | ``` 119 | -------------------------------------------------------------------------------- /component.mk: -------------------------------------------------------------------------------- 1 | ESP_IDF=${IDF_PATH} 2 | COMPONENT_ADD_INCLUDEDIRS := include ${ESP_IDF}/tools/unit-test-app/components/unity/include/ library/ccronexpr library/jobs . 3 | COMPONENT_SRCDIRS := library/ccronexpr library/jobs test src . 4 | CFLAGS += -D CRON_USE_LOCAL_TIME 5 | -------------------------------------------------------------------------------- /cron.c: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Insite SAS 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // 16 | // 17 | // Author: David Mora Rodriguez dmorar (at) insite.com.co 18 | // 19 | #include 20 | #include "freertos/FreeRTOS.h" 21 | #include "freertos/task.h" 22 | #include "cron.h" 23 | #include "jobs.h" 24 | 25 | 26 | static struct 27 | { 28 | unsigned char running; 29 | TaskHandle_t handle; 30 | time_t seconds_until_next_execution; 31 | 32 | } state = { 33 | .running = 0, 34 | .handle = NULL, 35 | .seconds_until_next_execution = -1 36 | }; 37 | 38 | cron_job *cron_job_create(const char *schedule, cron_job_callback callback, void *data) 39 | { 40 | cron_job_list_init();// CALL THIS ON ANY CREATE 41 | cron_job *job = calloc(sizeof(cron_job), 1); 42 | if (job == NULL) 43 | goto end; 44 | job->callback = callback; 45 | job->data = data; 46 | job->id = -1; 47 | job->load=NULL; 48 | cron_job_load_expression(job, schedule); 49 | cron_job_schedule(job); 50 | goto end; 51 | 52 | end: 53 | return job; 54 | } 55 | 56 | int cron_job_destroy(cron_job *job) 57 | { 58 | int ret=0; 59 | if (job == NULL) { 60 | ret = -1; 61 | goto end; 62 | } 63 | ret = cron_job_unschedule(job); 64 | free(job); 65 | end: 66 | return ret; 67 | } 68 | 69 | int cron_job_clear_all() 70 | { 71 | // REFACTOR THIS! 72 | while (cron_job_list_first()) 73 | { 74 | cron_job_destroy(cron_job_list_first()->job); 75 | } 76 | cron_job_list_reset_id(); 77 | return 0; 78 | } 79 | 80 | int cron_stop() 81 | { 82 | if (state.running == 0) 83 | { 84 | return -1; 85 | } 86 | TaskHandle_t xHandle; 87 | state.running = 0; 88 | xHandle = state.handle; 89 | state.handle = NULL; 90 | if (xHandle != NULL) { 91 | 92 | vTaskDelete(xHandle); 93 | } 94 | return 0; 95 | } 96 | 97 | int cron_start() 98 | { 99 | BaseType_t xReturned; 100 | if (state.running == 1 || state.handle != NULL) 101 | { 102 | return -1; 103 | } 104 | 105 | /* Create the task, storing the handle. */ 106 | xReturned = xTaskCreatePinnedToCore( 107 | cron_schedule_task, /* Function that implements the task. */ 108 | "cron_schedule_task", /* Text name for the task. */ 109 | 4096, /* Stack size in words, not bytes. */ 110 | (void *)0, /* Parameter passed into the task. */ 111 | tskIDLE_PRIORITY + 2, /* Priority at which the task is created. */ 112 | &state.handle, /* Save handle for further task delete. */ 113 | tskNO_AFFINITY); /* NO SPECIFIC CORE */ 114 | if (xReturned != pdPASS) 115 | { 116 | /* The task was created. Use the task's handle to delete the task. */ 117 | } 118 | state.running = 1; 119 | return 0; 120 | } 121 | 122 | 123 | int cron_job_schedule(cron_job *job) 124 | { 125 | if (job == NULL) 126 | { 127 | return -1; 128 | } 129 | if (!cron_job_has_loaded(job)) 130 | { 131 | return -1; 132 | } 133 | time_t now; 134 | time(&now); 135 | job->next_execution = cron_next(&(job->expression), now); 136 | int id = cron_job_list_insert(job); 137 | if (id < 0) 138 | { 139 | return -1; 140 | } 141 | else 142 | { 143 | return 0; 144 | } 145 | 146 | return 0; // WONT RUN 147 | } 148 | 149 | int cron_job_unschedule(cron_job *job) 150 | { 151 | int ret = 0; 152 | if (job == NULL) 153 | { 154 | ret = -1; 155 | } 156 | else if (job->id >= 0) 157 | { 158 | ret = cron_job_list_remove(job->id); 159 | } 160 | return ret; 161 | } 162 | 163 | 164 | int cron_job_load_expression(cron_job *job, const char * schedule) 165 | { 166 | const char *error = NULL; 167 | 168 | if (schedule != NULL) 169 | { 170 | memset(&(job->expression), 0, sizeof(job->expression)); 171 | cron_parse_expr(schedule, &(job->expression), &error); 172 | job->load = &(job->expression); 173 | } 174 | return 0; 175 | } 176 | 177 | int cron_job_has_loaded(cron_job *job) 178 | { 179 | if (&(job->expression) == job->load) 180 | { 181 | return 1; 182 | } 183 | else 184 | { 185 | return 0; 186 | } 187 | return 0; // WONT RUN 188 | } 189 | 190 | time_t cron_job_seconds_until_next_execution() 191 | { 192 | return state.seconds_until_next_execution; 193 | } 194 | 195 | 196 | 197 | // CRON TASKS 198 | 199 | 200 | void cron_schedule_job_launcher(void * args){ 201 | if (args==NULL) { 202 | goto end; 203 | } 204 | cron_job * job = (cron_job *) args; 205 | job->callback(job); 206 | goto end; 207 | 208 | 209 | end: 210 | vTaskDelete(NULL); 211 | return; 212 | } 213 | 214 | void cron_schedule_task(void *args) 215 | { 216 | time_t now; 217 | cron_job *job = NULL; 218 | int r1 = 0; // RUN ONCE!! 219 | // IF ARGS ARE A STRING DEFINED AS R1 220 | if (args != NULL) 221 | { 222 | if (strncmp(args, "R1", 2) == 0) // OK I ADMIT IT, ITS NOT THE MOST BEAUTIFUL CODE EVER, BUT I NEED IT TO BE TESTABLE... DON'T WANT TO GROW OLD WAITING FOR TIME TO PASS... :P 223 | r1 = 1; 224 | } 225 | 226 | while (true) 227 | { 228 | state.running = 1; 229 | time(&now); 230 | job = cron_job_list_first()->job; 231 | if (job == NULL) 232 | { 233 | break;// THIS IS IT!!! THIS WILL 234 | } 235 | if (now >= job->next_execution) 236 | { 237 | /* Create the task, IT WILL KILL ITSELF AFTER THE JOB IS DONE. */ 238 | xTaskCreatePinnedToCore( 239 | cron_schedule_job_launcher, /* Function that implements the task. */ 240 | "cron_schedule_job_launcher", /* Text name for the task. */ 241 | 4096, /* Stack size in BYTES, not bytes. */ 242 | (void *)job, /* Job is passed into the task. */ 243 | tskIDLE_PRIORITY + 2, /* Priority at which the task is created. */ 244 | (NULL), /* No need for the handle */ 245 | tskNO_AFFINITY); /* No specific core */ 246 | cron_job_list_remove(job->id); // There is mutex in there that can mess with our timing, but i am not sure if we should move this to the new task. 247 | cron_job_schedule(job); // There is mutex in there that can mess with our timing, but i am not sure if we should move this to the new task. 248 | } 249 | else 250 | { 251 | state.seconds_until_next_execution = job->next_execution - now; 252 | vTaskDelay((state.seconds_until_next_execution * 1000) / portTICK_PERIOD_MS); 253 | } 254 | if (r1 != 0) 255 | { 256 | break; 257 | } 258 | } 259 | cron_stop(); 260 | return; 261 | } 262 | 263 | 264 | -------------------------------------------------------------------------------- /include/cron.h: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Insite SAS 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // 16 | // 17 | // Author: David Mora Rodriguez dmorar (at) insite.com.co 18 | // 19 | 20 | #ifndef ESP_CRON_JOB 21 | #define ESP_CRON_JOB 22 | #include "freertos/FreeRTOS.h" 23 | #include 24 | #include "ccronexpr.h" 25 | 26 | 27 | 28 | /* 29 | * STRUCT INFORMATION: Holds the information needed to run the cron job. 30 | * 31 | * - callback: Function pointer to be called on execution 32 | * - schedule: Cron syntax for the schedule 33 | * ┌────────────── second (0 - 59) 34 | * | ┌───────────── minute (0 - 59) 35 | * | │ ┌───────────── hour (0 - 23) 36 | * | │ │ ┌───────────── day of month (1 - 31) 37 | * | │ │ │ ┌───────────── month (1 - 12) 38 | * | │ │ │ │ ┌───────────── day of week (0 - 6) (Sunday to Saturday; 39 | * | │ │ │ │ │ 7 is also Sunday on some systems) 40 | * | │ │ │ │ │ 41 | * | │ │ │ │ │ 42 | * * * * * * * 43 | * - data: pointer to arbitrary data needed by the cron_job 44 | * - id: identifier in the module for this cron job, this allows to unschedule similar tasks 45 | * - next execution: this information holds the time when it will run next, is managed by the cron module 46 | * - see https://github.com/staticlibs/ccronexpr 47 | */ 48 | 49 | // FORWARD DECLARATIONS ARE NEEDED FOR CALLBACK DECLARATION INSIDE THE STRUCT 50 | struct cron_job_struct; 51 | typedef struct cron_job_struct cron_job; 52 | 53 | struct cron_job_struct 54 | { 55 | void (* callback)(cron_job *); 56 | cron_expr expression; 57 | void * data; 58 | int id; 59 | void * load; 60 | time_t next_execution; 61 | }; 62 | 63 | 64 | // FUNCTION POINTER TO CALLBACKS 65 | typedef void (*cron_job_callback)(cron_job *); 66 | 67 | 68 | /* 69 | * SUMARY: Allocates a cron job on the heap with supplied parameters 70 | * 71 | * PARAMS: CRON SYNTAX SCHEDULE, CALLBACK (JOB), DATA FOR THE CALLBACK 72 | * 73 | * RETURNS: heap allocated cron_job 74 | */ 75 | 76 | cron_job * cron_job_create(const char * schedule,cron_job_callback callback, void * data); 77 | 78 | /* 79 | * SUMARY: Deallocates, and remove from scheduling 80 | * 81 | * PARAMS: Cron job to deallocate 82 | * 83 | * RETURNS: heap allocated cron_job 84 | */ 85 | 86 | int cron_job_destroy(cron_job * job); 87 | 88 | 89 | /* 90 | * SUMMARY: Removes all cron_job from the module 91 | * 92 | * PARAMS: removes all cron_jobs (no deallocation is performed by the call, memory must be handled out of this module) 93 | * 94 | * RETURNS: 0 on success 95 | */ 96 | 97 | int cron_job_clear_all(); 98 | 99 | /* 100 | * SUMMARY: Starts the schedule module (creates a new task on the operating system) 101 | * 102 | * RETURNS: 0 on success 103 | */ 104 | 105 | int cron_start(); 106 | 107 | /* 108 | * SUMMARY: Stops the schedule module (deletes the cron task on the operating system) 109 | * 110 | * RETURNS: 0 on success 111 | */ 112 | 113 | int cron_stop(); 114 | 115 | 116 | /* 117 | * SUMMARY: Schedule a new cron_job 118 | * 119 | * PARAMS: cron_job to be scheduled (no allocation is performed by the call, memory must be handled out of this module) 120 | * 121 | * RETURNS: 0 on success 122 | */ 123 | 124 | 125 | int cron_job_schedule(cron_job * job); 126 | 127 | /* 128 | * SUMMARY: Removes a cron_job from the schedule, this is ID based 129 | * 130 | * PARAMS: cron_job to be removed (no deallocation is performed by the call, memory must be handled out of this module) 131 | * 132 | * RETURNS: 0 on success, -1 on job NULL, -2 on JOB not loaded 133 | */ 134 | 135 | int cron_job_unschedule(cron_job * job); 136 | 137 | 138 | 139 | 140 | /* 141 | * SUMMARY: Returns seconds until next execution 142 | * RETURNS: seconds until next execution 143 | */ 144 | 145 | time_t cron_job_seconds_until_next_execution(); 146 | 147 | /* 148 | * SUMARY: TASK SCHEDULER WITH DELAYS AND CALL TO CALLBACKS DO NOT RUN THIS TAST 149 | * 150 | * PARAMS: ARGS MUST BE NULL IN REGULAR USE, IF YOU WANT TO RUN JUST ONCE ARGS MUST BE A CHAR POINTER TO "R1" LITERAL 151 | * 152 | * RETURNS: NO RETURN 153 | */ 154 | 155 | void cron_schedule_task(void *args); 156 | /* 157 | * SUMARY: Parses cron expression and sets the state structure 158 | * 159 | * PARAMS: Cron job structure 160 | * 161 | * RETURNS: NO RETURN 162 | */ 163 | int cron_job_load_expression(cron_job *job, const char * schedule); 164 | /* 165 | * SUMARY: Checks if load expression has loaded 166 | * 167 | * PARAMS: Cron job structure 168 | * 169 | * RETURNS: 1 if it has loaded, 0 on error 170 | */ 171 | int cron_job_has_loaded(cron_job *job); 172 | /* 173 | * SUMARY: Returns seconds until next execution 174 | * 175 | * PARAMS: NONE 176 | * 177 | * RETURNS: seconds until next execution 178 | */ 179 | time_t cron_job_seconds_until_next_execution(); 180 | 181 | 182 | 183 | #endif 184 | -------------------------------------------------------------------------------- /library/ccronexpr/.gitignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | /nbproject 3 | /Makefile 4 | -------------------------------------------------------------------------------- /library/ccronexpr/.travis.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2017, alex at staticlibs.net 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | language: cpp 16 | 17 | sudo: false 18 | 19 | os: 20 | - linux 21 | - osx 22 | 23 | compiler: 24 | - gcc 25 | - clang 26 | 27 | script: 28 | - $CC ccronexpr.c ccronexpr_test.c -I. -Wall -Wextra -std=c89 -DCRON_TEST_MALLOC -o a.out && ./a.out 29 | - $CXX ccronexpr.c ccronexpr_test.c -I. -Wall -Wextra -std=c++11 -DCRON_TEST_MALLOC -o a.out && ./a.out 30 | - $CXX ccronexpr.c ccronexpr_test.c -I. -Wall -Wextra -std=c++11 -DCRON_TEST_MALLOC -DCRON_COMPILE_AS_CXX -o a.out && ./a.out 31 | 32 | notifications: 33 | email: 34 | on_success: always 35 | -------------------------------------------------------------------------------- /library/ccronexpr/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2015, staticlibs.net 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /library/ccronexpr/README.md: -------------------------------------------------------------------------------- 1 | Cron expression parsing in ANSI C 2 | ================================= 3 | 4 | [![travis](https://travis-ci.org/staticlibs/ccronexpr.svg?branch=master)](https://travis-ci.org/staticlibs/ccronexpr) 5 | [![appveyor](https://ci.appveyor.com/api/projects/status/github/staticlibs/ccronexpr?svg=true)](https://ci.appveyor.com/project/staticlibs/ccronexpr) 6 | 7 | Given a cron expression and a date, you can get the next date which satisfies the cron expression. 8 | 9 | Supports cron expressions with `seconds` field. Based on implementation of [CronSequenceGenerator](https://github.com/spring-projects/spring-framework/blob/babbf6e8710ab937cd05ece20270f51490299270/spring-context/src/main/java/org/springframework/scheduling/support/CronSequenceGenerator.java) from Spring Framework. 10 | 11 | Compiles and should work on Linux (GCC/Clang), Mac OS (Clang), Windows (MSVC), Android NDK, iOS and possibly on other platforms with `time.h` support. 12 | 13 | Supports compilation in C (89) and in C++ modes. 14 | 15 | Usage example 16 | ------------- 17 | 18 | #include "ccronexpr.h" 19 | 20 | cron_expr expr; 21 | const char* err = NULL; 22 | memset(&expr, 0, sizeof(expr)); 23 | cron_parse_expr("0 */2 1-4 * * *", &expr, &err); 24 | if (err) ... /* invalid expression */ 25 | time_t cur = time(NULL); 26 | time_t next = cron_next(&expr, cur); 27 | 28 | 29 | Compilation and tests run examples 30 | ---------------------------------- 31 | 32 | gcc ccronexpr.c ccronexpr_test.c -I. -Wall -Wextra -std=c89 -DCRON_TEST_MALLOC -o a.out && ./a.out 33 | g++ ccronexpr.c ccronexpr_test.c -I. -Wall -Wextra -std=c++11 -DCRON_TEST_MALLOC -o a.out && ./a.out 34 | g++ ccronexpr.c ccronexpr_test.c -I. -Wall -Wextra -std=c++11 -DCRON_TEST_MALLOC -DCRON_COMPILE_AS_CXX -o a.out && ./a.out 35 | 36 | clang ccronexpr.c ccronexpr_test.c -I. -Wall -Wextra -std=c89 -DCRON_TEST_MALLOC -o a.out && ./a.out 37 | clang++ ccronexpr.c ccronexpr_test.c -I. -Wall -Wextra -std=c++11 -DCRON_TEST_MALLOC -o a.out && ./a.out 38 | clang++ ccronexpr.c ccronexpr_test.c -I. -Wall -Wextra -std=c++11 -DCRON_TEST_MALLOC -DCRON_COMPILE_AS_CXX -o a.out && ./a.out 39 | 40 | cl ccronexpr.c ccronexpr_test.c /W4 /D_CRT_SECURE_NO_WARNINGS && ccronexpr.exe 41 | 42 | Examples of supported expressions 43 | --------------------------------- 44 | 45 | Expression, input date, next date: 46 | 47 | "*/15 * 1-4 * * *", "2012-07-01_09:53:50", "2012-07-02_01:00:00" 48 | "0 */2 1-4 * * *", "2012-07-01_09:00:00", "2012-07-02_01:00:00" 49 | "0 0 7 ? * MON-FRI", "2009-09-26_00:42:55", "2009-09-28_07:00:00" 50 | "0 30 23 30 1/3 ?", "2011-04-30_23:30:00", "2011-07-30_23:30:00" 51 | 52 | See more examples in [tests](https://github.com/staticlibs/ccronexpr/blob/a1343bc5a546b13430bd4ac72f3b047ac08f8192/ccronexpr_test.c#L251). 53 | 54 | Timezones 55 | --------- 56 | 57 | This implementation does not support explicit timezones handling. By default all dates are 58 | processed as UTC (GMT) dates without timezone infomation. 59 | 60 | To use local dates (current system timezone) instead of GMT compile with `-DCRON_USE_LOCAL_TIME`. 61 | 62 | License information 63 | ------------------- 64 | 65 | This project is released under the [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0). 66 | 67 | Changelog 68 | --------- 69 | 70 | **2018-05-23** 71 | 72 | * merged [#8](https://github.com/staticlibs/ccronexpr/pull/8) 73 | * merged [#9](https://github.com/staticlibs/ccronexpr/pull/9) 74 | * minor cleanups 75 | 76 | **2018-01-27** 77 | 78 | * merged [#6](https://github.com/staticlibs/ccronexpr/pull/6) 79 | * updated license file (to the one parse-able by github) 80 | 81 | **2017-09-24** 82 | 83 | * merged [#4](https://github.com/staticlibs/ccronexpr/pull/4) 84 | 85 | **2016-06-17** 86 | 87 | * use thread-safe versions of `gmtime` and `localtime` 88 | 89 | **2015-02-28** 90 | 91 | * initial public version 92 | -------------------------------------------------------------------------------- /library/ccronexpr/appveyor.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2017, alex at staticlibs.net 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | image: Visual Studio 2017 16 | 17 | configuration: Release 18 | 19 | build: off 20 | 21 | build_script: 22 | - call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars32.bat" 23 | - cl ccronexpr.c ccronexpr_test.c /W4 /D_CRT_SECURE_NO_WARNINGS /Feccronexpr_32.exe 24 | - call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars64.bat" 25 | - cl ccronexpr.c ccronexpr_test.c /W4 /D_CRT_SECURE_NO_WARNINGS /Feccronexpr_64.exe 26 | 27 | test_script: 28 | - ccronexpr_32.exe 29 | - ccronexpr_64.exe 30 | -------------------------------------------------------------------------------- /library/ccronexpr/ccronexpr.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015, alex at staticlibs.net 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /* 18 | * File: ccronexpr.c 19 | * Author: alex 20 | * 21 | * Created on February 24, 2015, 9:35 AM 22 | */ 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #include "ccronexpr.h" 33 | 34 | #define CRON_USE_LOCAL_TIME 35 | 36 | #define CRON_MAX_SECONDS 60 37 | #define CRON_MAX_MINUTES 60 38 | #define CRON_MAX_HOURS 24 39 | #define CRON_MAX_DAYS_OF_WEEK 8 40 | #define CRON_MAX_DAYS_OF_MONTH 32 41 | #define CRON_MAX_MONTHS 12 42 | #define CRON_MAX_YEARS_DIFF 4 43 | 44 | #define CRON_CF_SECOND 0 45 | #define CRON_CF_MINUTE 1 46 | #define CRON_CF_HOUR_OF_DAY 2 47 | #define CRON_CF_DAY_OF_WEEK 3 48 | #define CRON_CF_DAY_OF_MONTH 4 49 | #define CRON_CF_MONTH 5 50 | #define CRON_CF_YEAR 6 51 | 52 | #define CRON_CF_ARR_LEN 7 53 | 54 | #define CRON_INVALID_INSTANT ((time_t) -1) 55 | 56 | static const char* const DAYS_ARR[] = { "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" }; 57 | #define CRON_DAYS_ARR_LEN 7 58 | static const char* const MONTHS_ARR[] = { "FOO", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" }; 59 | #define CRON_MONTHS_ARR_LEN 13 60 | 61 | #define CRON_MAX_STR_LEN_TO_SPLIT 256 62 | #define CRON_MAX_NUM_TO_SRING 1000000000 63 | /* computes number of digits in decimal number */ 64 | #define CRON_NUM_OF_DIGITS(num) (abs(num) < 10 ? 1 : \ 65 | (abs(num) < 100 ? 2 : \ 66 | (abs(num) < 1000 ? 3 : \ 67 | (abs(num) < 10000 ? 4 : \ 68 | (abs(num) < 100000 ? 5 : \ 69 | (abs(num) < 1000000 ? 6 : \ 70 | (abs(num) < 10000000 ? 7 : \ 71 | (abs(num) < 100000000 ? 8 : \ 72 | (abs(num) < 1000000000 ? 9 : 10))))))))) 73 | 74 | #ifndef _WIN32 75 | struct tm *gmtime_r(const time_t *timep, struct tm *result); 76 | #ifdef CRON_USE_LOCAL_TIME 77 | struct tm *localtime_r(const time_t *timep, struct tm *result); 78 | #endif /* CRON_USE_LOCAL_TIME */ 79 | #endif /* _WIN32 */ 80 | 81 | #ifndef CRON_TEST_MALLOC 82 | #define cron_malloc(x) malloc(x); 83 | #define cron_free(x) free(x); 84 | #else /* CRON_TEST_MALLOC */ 85 | void* cron_malloc(size_t n); 86 | void cron_free(void* p); 87 | #endif /* CRON_TEST_MALLOC */ 88 | 89 | #ifdef __MINGW32__ 90 | /* To avoid warning when building with mingw */ 91 | time_t _mkgmtime(struct tm* tm); 92 | #endif /* __MINGW32__ */ 93 | 94 | /* Defining 'cron_mktime' to use use UTC (default) or local time */ 95 | #ifndef CRON_USE_LOCAL_TIME 96 | 97 | /* http://stackoverflow.com/a/22557778 */ 98 | #ifdef _WIN32 99 | time_t cron_mktime(struct tm* tm) { 100 | return _mkgmtime(tm); 101 | } 102 | #else /* !_WIN32 */ 103 | #ifndef ANDROID 104 | /* can be hidden in time.h */ 105 | time_t timegm(struct tm* __tp); 106 | #endif /* ANDROID */ 107 | time_t cron_mktime(struct tm* tm) { 108 | #ifndef ANDROID 109 | return timegm(tm); 110 | #else /* ANDROID */ 111 | /* https://github.com/adobe/chromium/blob/cfe5bf0b51b1f6b9fe239c2a3c2f2364da9967d7/base/os_compat_android.cc#L20 */ 112 | static const time_t kTimeMax = ~(1L << (sizeof (time_t) * CHAR_BIT - 1)); 113 | static const time_t kTimeMin = (1L << (sizeof (time_t) * CHAR_BIT - 1)); 114 | time64_t result = timegm64(tm); 115 | if (result < kTimeMin || result > kTimeMax) return -1; 116 | return result; 117 | #endif /* ANDROID */ 118 | } 119 | #endif /* _WIN32 */ 120 | 121 | struct tm* cron_time(time_t* date, struct tm* out) { 122 | #ifdef __MINGW32__ 123 | (void)(out); /* To avoid unused warning */ 124 | return gmtime(date); 125 | #else /* !__MINGW32__ */ 126 | #ifdef _WIN32 127 | errno_t err = gmtime_s(out, date); 128 | return 0 == err ? out : NULL; 129 | #else /* !_WIN32 */ 130 | return gmtime_r(date, out); 131 | #endif /* _WIN32 */ 132 | #endif /* __MINGW32__ */ 133 | } 134 | 135 | #else /* CRON_USE_LOCAL_TIME */ 136 | 137 | time_t cron_mktime(struct tm* tm) { 138 | return mktime(tm); 139 | } 140 | 141 | struct tm* cron_time(time_t* date, struct tm* out) { 142 | #ifdef _WIN32 143 | errno_t err = localtime_s(out, date); 144 | return 0 == err ? out : NULL; 145 | #else /* _WIN32 */ 146 | return localtime_r(date, out); 147 | #endif /* _WIN32 */ 148 | } 149 | 150 | #endif /* CRON_USE_LOCAL_TIME */ 151 | 152 | void cron_set_bit(uint8_t* rbyte, int idx) { 153 | uint8_t j = (uint8_t) (idx / 8); 154 | uint8_t k = (uint8_t) (idx % 8); 155 | 156 | rbyte[j] |= (1 << k); 157 | } 158 | 159 | void cron_del_bit(uint8_t* rbyte, int idx) { 160 | uint8_t j = (uint8_t) (idx / 8); 161 | uint8_t k = (uint8_t) (idx % 8); 162 | 163 | rbyte[j] &= ~(1 << k); 164 | } 165 | 166 | uint8_t cron_get_bit(uint8_t* rbyte, int idx) { 167 | uint8_t j = (uint8_t) (idx / 8); 168 | uint8_t k = (uint8_t) (idx % 8); 169 | 170 | if (rbyte[j] & (1 << k)) { 171 | return 1; 172 | } else { 173 | return 0; 174 | } 175 | } 176 | 177 | static void free_splitted(char** splitted, size_t len) { 178 | size_t i; 179 | if (!splitted) return; 180 | for (i = 0; i < len; i++) { 181 | if (splitted[i]) { 182 | cron_free(splitted[i]); 183 | } 184 | } 185 | cron_free(splitted); 186 | } 187 | 188 | static char* strdupl(const char* str, size_t len) { 189 | if (!str) return NULL; 190 | char* res = (char*) cron_malloc(len + 1); 191 | if (!res) return NULL; 192 | memset(res, 0, len + 1); 193 | memcpy(res, str, len); 194 | return res; 195 | } 196 | 197 | static unsigned int next_set_bit(uint8_t* bits, unsigned int max, unsigned int from_index, int* notfound) { 198 | unsigned int i; 199 | if (!bits) { 200 | *notfound = 1; 201 | return 0; 202 | } 203 | for (i = from_index; i < max; i++) { 204 | if (cron_get_bit(bits, i)) return i; 205 | } 206 | *notfound = 1; 207 | return 0; 208 | } 209 | 210 | static void push_to_fields_arr(int* arr, int fi) { 211 | int i; 212 | if (!arr || -1 == fi) { 213 | return; 214 | } 215 | for (i = 0; i < CRON_CF_ARR_LEN; i++) { 216 | if (arr[i] == fi) return; 217 | } 218 | for (i = 0; i < CRON_CF_ARR_LEN; i++) { 219 | if (-1 == arr[i]) { 220 | arr[i] = fi; 221 | return; 222 | } 223 | } 224 | } 225 | 226 | static int add_to_field(struct tm* calendar, int field, int val) { 227 | if (!calendar || -1 == field) { 228 | return 1; 229 | } 230 | switch (field) { 231 | case CRON_CF_SECOND: 232 | calendar->tm_sec = calendar->tm_sec + val; 233 | break; 234 | case CRON_CF_MINUTE: 235 | calendar->tm_min = calendar->tm_min + val; 236 | break; 237 | case CRON_CF_HOUR_OF_DAY: 238 | calendar->tm_hour = calendar->tm_hour + val; 239 | break; 240 | case CRON_CF_DAY_OF_WEEK: /* mkgmtime ignores this field */ 241 | case CRON_CF_DAY_OF_MONTH: 242 | calendar->tm_mday = calendar->tm_mday + val; 243 | break; 244 | case CRON_CF_MONTH: 245 | calendar->tm_mon = calendar->tm_mon + val; 246 | break; 247 | case CRON_CF_YEAR: 248 | calendar->tm_year = calendar->tm_year + val; 249 | break; 250 | default: 251 | return 1; /* unknown field */ 252 | } 253 | time_t res = cron_mktime(calendar); 254 | if (CRON_INVALID_INSTANT == res) { 255 | return 1; 256 | } 257 | return 0; 258 | } 259 | 260 | /** 261 | * Reset the calendar setting all the fields provided to zero. 262 | */ 263 | static int reset_min(struct tm* calendar, int field) { 264 | if (!calendar || -1 == field) { 265 | return 1; 266 | } 267 | switch (field) { 268 | case CRON_CF_SECOND: 269 | calendar->tm_sec = 0; 270 | break; 271 | case CRON_CF_MINUTE: 272 | calendar->tm_min = 0; 273 | break; 274 | case CRON_CF_HOUR_OF_DAY: 275 | calendar->tm_hour = 0; 276 | break; 277 | case CRON_CF_DAY_OF_WEEK: 278 | calendar->tm_wday = 0; 279 | break; 280 | case CRON_CF_DAY_OF_MONTH: 281 | calendar->tm_mday = 1; 282 | break; 283 | case CRON_CF_MONTH: 284 | calendar->tm_mon = 0; 285 | break; 286 | case CRON_CF_YEAR: 287 | calendar->tm_year = 0; 288 | break; 289 | default: 290 | return 1; /* unknown field */ 291 | } 292 | time_t res = cron_mktime(calendar); 293 | if (CRON_INVALID_INSTANT == res) { 294 | return 1; 295 | } 296 | return 0; 297 | } 298 | 299 | static int reset_all_min(struct tm* calendar, int* fields) { 300 | int i; 301 | int res = 0; 302 | if (!calendar || !fields) { 303 | return 1; 304 | } 305 | for (i = 0; i < CRON_CF_ARR_LEN; i++) { 306 | if (-1 != fields[i]) { 307 | res = reset_min(calendar, fields[i]); 308 | if (0 != res) return res; 309 | } 310 | } 311 | return 0; 312 | } 313 | 314 | static int set_field(struct tm* calendar, int field, int val) { 315 | if (!calendar || -1 == field) { 316 | return 1; 317 | } 318 | switch (field) { 319 | case CRON_CF_SECOND: 320 | calendar->tm_sec = val; 321 | break; 322 | case CRON_CF_MINUTE: 323 | calendar->tm_min = val; 324 | break; 325 | case CRON_CF_HOUR_OF_DAY: 326 | calendar->tm_hour = val; 327 | break; 328 | case CRON_CF_DAY_OF_WEEK: 329 | calendar->tm_wday = val; 330 | break; 331 | case CRON_CF_DAY_OF_MONTH: 332 | calendar->tm_mday = val; 333 | break; 334 | case CRON_CF_MONTH: 335 | calendar->tm_mon = val; 336 | break; 337 | case CRON_CF_YEAR: 338 | calendar->tm_year = val; 339 | break; 340 | default: 341 | return 1; /* unknown field */ 342 | } 343 | time_t res = cron_mktime(calendar); 344 | if (CRON_INVALID_INSTANT == res) { 345 | return 1; 346 | } 347 | return 0; 348 | } 349 | 350 | /** 351 | * Search the bits provided for the next set bit after the value provided, 352 | * and reset the calendar. 353 | */ 354 | static unsigned int find_next(uint8_t* bits, unsigned int max, unsigned int value, struct tm* calendar, unsigned int field, unsigned int nextField, int* lower_orders, int* res_out) { 355 | int notfound = 0; 356 | int err = 0; 357 | unsigned int next_value = next_set_bit(bits, max, value, ¬found); 358 | /* roll over if needed */ 359 | if (notfound) { 360 | err = add_to_field(calendar, nextField, 1); 361 | if (err) goto return_error; 362 | err = reset_min(calendar, field); 363 | if (err) goto return_error; 364 | notfound = 0; 365 | next_value = next_set_bit(bits, max, 0, ¬found); 366 | } 367 | if (notfound || next_value != value) { 368 | err = set_field(calendar, field, next_value); 369 | if (err) goto return_error; 370 | err = reset_all_min(calendar, lower_orders); 371 | if (err) goto return_error; 372 | } 373 | return next_value; 374 | 375 | return_error: 376 | *res_out = 1; 377 | return 0; 378 | } 379 | 380 | static unsigned int find_next_day(struct tm* calendar, uint8_t* days_of_month, unsigned int day_of_month, uint8_t* days_of_week, unsigned int day_of_week, int* resets, int* res_out) { 381 | int err; 382 | unsigned int count = 0; 383 | unsigned int max = 366; 384 | while ((!cron_get_bit(days_of_month, day_of_month) || !cron_get_bit(days_of_week, day_of_week)) && count++ < max) { 385 | err = add_to_field(calendar, CRON_CF_DAY_OF_MONTH, 1); 386 | 387 | if (err) goto return_error; 388 | day_of_month = calendar->tm_mday; 389 | day_of_week = calendar->tm_wday; 390 | reset_all_min(calendar, resets); 391 | } 392 | return day_of_month; 393 | 394 | return_error: 395 | *res_out = 1; 396 | return 0; 397 | } 398 | 399 | static int do_next(cron_expr* expr, struct tm* calendar, unsigned int dot) { 400 | int i; 401 | int res = 0; 402 | int* resets = NULL; 403 | int* empty_list = NULL; 404 | unsigned int second = 0; 405 | unsigned int update_second = 0; 406 | unsigned int minute = 0; 407 | unsigned int update_minute = 0; 408 | unsigned int hour = 0; 409 | unsigned int update_hour = 0; 410 | unsigned int day_of_week = 0; 411 | unsigned int day_of_month = 0; 412 | unsigned int update_day_of_month = 0; 413 | unsigned int month = 0; 414 | unsigned int update_month = 0; 415 | 416 | resets = (int*) cron_malloc(CRON_CF_ARR_LEN * sizeof(int)); 417 | if (!resets) goto return_result; 418 | empty_list = (int*) cron_malloc(CRON_CF_ARR_LEN * sizeof(int)); 419 | if (!empty_list) goto return_result; 420 | for (i = 0; i < CRON_CF_ARR_LEN; i++) { 421 | resets[i] = -1; 422 | empty_list[i] = -1; 423 | } 424 | 425 | second = calendar->tm_sec; 426 | update_second = find_next(expr->seconds, CRON_MAX_SECONDS, second, calendar, CRON_CF_SECOND, CRON_CF_MINUTE, empty_list, &res); 427 | if (0 != res) goto return_result; 428 | if (second == update_second) { 429 | push_to_fields_arr(resets, CRON_CF_SECOND); 430 | } 431 | 432 | minute = calendar->tm_min; 433 | update_minute = find_next(expr->minutes, CRON_MAX_MINUTES, minute, calendar, CRON_CF_MINUTE, CRON_CF_HOUR_OF_DAY, resets, &res); 434 | if (0 != res) goto return_result; 435 | if (minute == update_minute) { 436 | push_to_fields_arr(resets, CRON_CF_MINUTE); 437 | } else { 438 | res = do_next(expr, calendar, dot); 439 | if (0 != res) goto return_result; 440 | } 441 | 442 | hour = calendar->tm_hour; 443 | update_hour = find_next(expr->hours, CRON_MAX_HOURS, hour, calendar, CRON_CF_HOUR_OF_DAY, CRON_CF_DAY_OF_WEEK, resets, &res); 444 | if (0 != res) goto return_result; 445 | if (hour == update_hour) { 446 | push_to_fields_arr(resets, CRON_CF_HOUR_OF_DAY); 447 | } else { 448 | res = do_next(expr, calendar, dot); 449 | if (0 != res) goto return_result; 450 | } 451 | 452 | day_of_week = calendar->tm_wday; 453 | day_of_month = calendar->tm_mday; 454 | update_day_of_month = find_next_day(calendar, expr->days_of_month, day_of_month, expr->days_of_week, day_of_week, resets, &res); 455 | if (0 != res) goto return_result; 456 | if (day_of_month == update_day_of_month) { 457 | push_to_fields_arr(resets, CRON_CF_DAY_OF_MONTH); 458 | } else { 459 | res = do_next(expr, calendar, dot); 460 | if (0 != res) goto return_result; 461 | } 462 | 463 | month = calendar->tm_mon; /*day already adds one if no day in same month is found*/ 464 | update_month = find_next(expr->months, CRON_MAX_MONTHS, month, calendar, CRON_CF_MONTH, CRON_CF_YEAR, resets, &res); 465 | if (0 != res) goto return_result; 466 | if (month != update_month) { 467 | if (calendar->tm_year - dot > 4) { 468 | res = -1; 469 | goto return_result; 470 | } 471 | res = do_next(expr, calendar, dot); 472 | if (0 != res) goto return_result; 473 | } 474 | goto return_result; 475 | 476 | return_result: 477 | if (!resets || !empty_list) { 478 | res = -1; 479 | } 480 | if (resets) { 481 | cron_free(resets); 482 | } 483 | if (empty_list) { 484 | cron_free(empty_list); 485 | } 486 | return res; 487 | } 488 | 489 | static int to_upper(char* str) { 490 | if (!str) return 1; 491 | int i; 492 | for (i = 0; '\0' != str[i]; i++) { 493 | int c = (int)str[i]; 494 | str[i] = (char) toupper(c); 495 | } 496 | return 0; 497 | } 498 | 499 | static char* to_string(int num) { 500 | if (abs(num) >= CRON_MAX_NUM_TO_SRING) return NULL; 501 | char* str = (char*) cron_malloc(CRON_NUM_OF_DIGITS(num) + 1); 502 | if (!str) return NULL; 503 | int res = sprintf(str, "%d", num); 504 | if (res < 0) return NULL; 505 | return str; 506 | } 507 | 508 | static char* str_replace(char *orig, const char *rep, const char *with) { 509 | char *result; /* the return string */ 510 | char *ins; /* the next insert point */ 511 | char *tmp; /* varies */ 512 | size_t len_rep; /* length of rep */ 513 | size_t len_with; /* length of with */ 514 | size_t len_front; /* distance between rep and end of last rep */ 515 | int count; /* number of replacements */ 516 | if (!orig) return NULL; 517 | if (!rep) rep = ""; 518 | if (!with) with = ""; 519 | len_rep = strlen(rep); 520 | len_with = strlen(with); 521 | 522 | ins = orig; 523 | for (count = 0; NULL != (tmp = strstr(ins, rep)); ++count) { 524 | ins = tmp + len_rep; 525 | } 526 | 527 | /* first time through the loop, all the variable are set correctly 528 | from here on, 529 | tmp points to the end of the result string 530 | ins points to the next occurrence of rep in orig 531 | orig points to the remainder of orig after "end of rep" 532 | */ 533 | tmp = result = (char*) cron_malloc(strlen(orig) + (len_with - len_rep) * count + 1); 534 | if (!result) return NULL; 535 | 536 | while (count--) { 537 | ins = strstr(orig, rep); 538 | len_front = ins - orig; 539 | tmp = strncpy(tmp, orig, len_front) + len_front; 540 | tmp = strcpy(tmp, with) + len_with; 541 | orig += len_front + len_rep; /* move to next "end of rep" */ 542 | } 543 | strcpy(tmp, orig); 544 | return result; 545 | } 546 | 547 | static unsigned int parse_uint(const char* str, int* errcode) { 548 | char* endptr; 549 | errno = 0; 550 | long int l = strtol(str, &endptr, 0); 551 | if (errno == ERANGE || *endptr != '\0' || l < 0 || l > INT_MAX) { 552 | *errcode = 1; 553 | return 0; 554 | } else { 555 | *errcode = 0; 556 | return (unsigned int) l; 557 | } 558 | } 559 | 560 | static char** split_str(const char* str, char del, size_t* len_out) { 561 | size_t i; 562 | size_t stlen = 0; 563 | size_t len = 0; 564 | int accum = 0; 565 | char* buf = NULL; 566 | char** res = NULL; 567 | size_t bi = 0; 568 | size_t ri = 0; 569 | char* tmp; 570 | 571 | if (!str) goto return_error; 572 | for (i = 0; '\0' != str[i]; i++) { 573 | stlen += 1; 574 | if (stlen >= CRON_MAX_STR_LEN_TO_SPLIT) goto return_error; 575 | } 576 | 577 | for (i = 0; i < stlen; i++) { 578 | int c = str[i]; 579 | if (del == str[i]) { 580 | if (accum > 0) { 581 | len += 1; 582 | accum = 0; 583 | } 584 | } else if (!isspace(c)) { 585 | accum += 1; 586 | } 587 | } 588 | /* tail */ 589 | if (accum > 0) { 590 | len += 1; 591 | } 592 | if (0 == len) return NULL; 593 | 594 | buf = (char*) cron_malloc(stlen + 1); 595 | if (!buf) goto return_error; 596 | memset(buf, 0, stlen + 1); 597 | res = (char**) cron_malloc(len * sizeof(char*)); 598 | if (!res) goto return_error; 599 | memset(res, 0, len * sizeof(char*)); 600 | 601 | for (i = 0; i < stlen; i++) { 602 | int c = str[i]; 603 | if (del == str[i]) { 604 | if (bi > 0) { 605 | tmp = strdupl(buf, bi); 606 | if (!tmp) goto return_error; 607 | res[ri++] = tmp; 608 | memset(buf, 0, stlen + 1); 609 | bi = 0; 610 | } 611 | } else if (!isspace(c)) { 612 | buf[bi++] = str[i]; 613 | } 614 | } 615 | /* tail */ 616 | if (bi > 0) { 617 | tmp = strdupl(buf, bi); 618 | if (!tmp) goto return_error; 619 | res[ri++] = tmp; 620 | } 621 | cron_free(buf); 622 | *len_out = len; 623 | return res; 624 | 625 | return_error: 626 | if (buf) { 627 | cron_free(buf); 628 | } 629 | free_splitted(res, len); 630 | *len_out = 0; 631 | return NULL; 632 | } 633 | 634 | static char* replace_ordinals(char* value, const char* const * arr, size_t arr_len) { 635 | size_t i; 636 | char* cur = value; 637 | char* res = NULL; 638 | int first = 1; 639 | for (i = 0; i < arr_len; i++) { 640 | char* strnum = to_string((int) i); 641 | if (!strnum) { 642 | if (!first) { 643 | cron_free(cur); 644 | } 645 | return NULL; 646 | } 647 | res = str_replace(cur, arr[i], strnum); 648 | cron_free(strnum); 649 | if (!first) { 650 | cron_free(cur); 651 | } 652 | if (!res) { 653 | return NULL; 654 | } 655 | cur = res; 656 | if (first) { 657 | first = 0; 658 | } 659 | } 660 | return res; 661 | } 662 | 663 | static int has_char(char* str, char ch) { 664 | size_t i; 665 | size_t len = 0; 666 | if (!str) return 0; 667 | len = strlen(str); 668 | for (i = 0; i < len; i++) { 669 | if (str[i] == ch) return 1; 670 | } 671 | return 0; 672 | } 673 | 674 | static unsigned int* get_range(char* field, unsigned int min, unsigned int max, const char** error) { 675 | 676 | char** parts = NULL; 677 | size_t len = 0; 678 | unsigned int* res = (unsigned int*) cron_malloc(2 * sizeof(unsigned int)); 679 | if (!res) goto return_error; 680 | 681 | res[0] = 0; 682 | res[1] = 0; 683 | if (1 == strlen(field) && '*' == field[0]) { 684 | res[0] = min; 685 | res[1] = max - 1; 686 | } else if (!has_char(field, '-')) { 687 | int err = 0; 688 | unsigned int val = parse_uint(field, &err); 689 | if (err) { 690 | *error = "Unsigned integer parse error 1"; 691 | goto return_error; 692 | } 693 | 694 | res[0] = val; 695 | res[1] = val; 696 | } else { 697 | parts = split_str(field, '-', &len); 698 | if (2 != len) { 699 | *error = "Specified range requires two fields"; 700 | goto return_error; 701 | } 702 | int err = 0; 703 | res[0] = parse_uint(parts[0], &err); 704 | if (err) { 705 | *error = "Unsigned integer parse error 2"; 706 | goto return_error; 707 | } 708 | res[1] = parse_uint(parts[1], &err); 709 | if (err) { 710 | *error = "Unsigned integer parse error 3"; 711 | goto return_error; 712 | } 713 | } 714 | if (res[0] >= max || res[1] >= max) { 715 | *error = "Specified range exceeds maximum"; 716 | goto return_error; 717 | } 718 | if (res[0] < min || res[1] < min) { 719 | *error = "Specified range is less than minimum"; 720 | goto return_error; 721 | } 722 | if (res[0] > res[1]) { 723 | *error = "Specified range start exceeds range end"; 724 | goto return_error; 725 | } 726 | 727 | free_splitted(parts, len); 728 | *error = NULL; 729 | return res; 730 | 731 | return_error: 732 | free_splitted(parts, len); 733 | if (res) { 734 | cron_free(res); 735 | } 736 | 737 | return NULL; 738 | } 739 | 740 | static void set_number_hits(const char* value, uint8_t* target, unsigned int min, unsigned int max, const char** error) { 741 | size_t i; 742 | unsigned int i1; 743 | size_t len = 0; 744 | 745 | char** fields = split_str(value, ',', &len); 746 | if (!fields) { 747 | *error = "Comma split error"; 748 | goto return_result; 749 | } 750 | 751 | for (i = 0; i < len; i++) { 752 | if (!has_char(fields[i], '/')) { 753 | /* Not an incrementer so it must be a range (possibly empty) */ 754 | 755 | unsigned int* range = get_range(fields[i], min, max, error); 756 | 757 | if (*error) { 758 | if (range) { 759 | cron_free(range); 760 | } 761 | goto return_result; 762 | 763 | } 764 | 765 | for (i1 = range[0]; i1 <= range[1]; i1++) { 766 | cron_set_bit(target, i1); 767 | 768 | } 769 | cron_free(range); 770 | 771 | } else { 772 | size_t len2 = 0; 773 | char** split = split_str(fields[i], '/', &len2); 774 | if (2 != len2) { 775 | *error = "Incrementer must have two fields"; 776 | free_splitted(split, len2); 777 | goto return_result; 778 | } 779 | unsigned int* range = get_range(split[0], min, max, error); 780 | if (*error) { 781 | if (range) { 782 | cron_free(range); 783 | } 784 | free_splitted(split, len2); 785 | goto return_result; 786 | } 787 | if (!has_char(split[0], '-')) { 788 | range[1] = max - 1; 789 | } 790 | int err = 0; 791 | unsigned int delta = parse_uint(split[1], &err); 792 | if (err) { 793 | *error = "Unsigned integer parse error 4"; 794 | cron_free(range); 795 | free_splitted(split, len2); 796 | goto return_result; 797 | } 798 | if (0 == delta) { 799 | *error = "Incrementer may not be zero"; 800 | cron_free(range); 801 | free_splitted(split, len2); 802 | goto return_result; 803 | } 804 | for (i1 = range[0]; i1 <= range[1]; i1 += delta) { 805 | cron_set_bit(target, i1); 806 | } 807 | free_splitted(split, len2); 808 | cron_free(range); 809 | 810 | } 811 | } 812 | goto return_result; 813 | 814 | return_result: 815 | free_splitted(fields, len); 816 | 817 | } 818 | 819 | static void set_months(char* value, uint8_t* targ, const char** error) { 820 | int err; 821 | unsigned int i; 822 | unsigned int max = 12; 823 | 824 | char* replaced = NULL; 825 | 826 | err = to_upper(value); 827 | if (err) return; 828 | replaced = replace_ordinals(value, MONTHS_ARR, CRON_MONTHS_ARR_LEN); 829 | if (!replaced) return; 830 | 831 | set_number_hits(replaced, targ, 1, max + 1, error); 832 | cron_free(replaced); 833 | 834 | /* ... and then rotate it to the front of the months */ 835 | for (i = 1; i <= max; i++) { 836 | if (cron_get_bit(targ, i)) { 837 | cron_set_bit(targ, i - 1); 838 | cron_del_bit(targ, i); 839 | } 840 | } 841 | } 842 | 843 | static void set_days(char* field, uint8_t* targ, int max, const char** error) { 844 | if (1 == strlen(field) && '?' == field[0]) { 845 | field[0] = '*'; 846 | } 847 | set_number_hits(field, targ, 0, max, error); 848 | } 849 | 850 | static void set_days_of_month(char* field, uint8_t* targ, const char** error) { 851 | /* Days of month start with 1 (in Cron and Calendar) so add one */ 852 | if (1 == strlen(field) && '?' == field[0]) { 853 | field[0] = '*'; 854 | } 855 | set_number_hits(field, targ, 1, CRON_MAX_DAYS_OF_MONTH, error); 856 | } 857 | 858 | void cron_parse_expr(const char* expression, cron_expr* target, const char** error) { 859 | const char* err_local; 860 | size_t len = 0; 861 | char** fields = NULL; 862 | char* days_replaced = NULL; 863 | if (!error) { 864 | error = &err_local; 865 | } 866 | *error = NULL; 867 | if (!expression) { 868 | *error = "Invalid NULL expression"; 869 | goto return_res; 870 | } 871 | 872 | fields = split_str(expression, ' ', &len); 873 | if (len != 6) { 874 | *error = "Invalid number of fields, expression must consist of 6 fields"; 875 | goto return_res; 876 | } 877 | set_number_hits(fields[0], target->seconds, 0, 60, error); 878 | if (*error) goto return_res; 879 | set_number_hits(fields[1], target->minutes, 0, 60, error); 880 | if (*error) goto return_res; 881 | set_number_hits(fields[2], target->hours, 0, 24, error); 882 | if (*error) goto return_res; 883 | to_upper(fields[5]); 884 | days_replaced = replace_ordinals(fields[5], DAYS_ARR, CRON_DAYS_ARR_LEN); 885 | set_days(days_replaced, target->days_of_week, 8, error); 886 | cron_free(days_replaced); 887 | if (*error) goto return_res; 888 | if (cron_get_bit(target->days_of_week, 7)) { 889 | /* Sunday can be represented as 0 or 7*/ 890 | cron_set_bit(target->days_of_week, 0); 891 | cron_del_bit(target->days_of_week, 7); 892 | } 893 | set_days_of_month(fields[3], target->days_of_month, error); 894 | if (*error) goto return_res; 895 | set_months(fields[4], target->months, error); 896 | if (*error) goto return_res; 897 | 898 | goto return_res; 899 | 900 | return_res: 901 | free_splitted(fields, len); 902 | } 903 | 904 | time_t cron_next(cron_expr* expr, time_t date) { 905 | /* 906 | The plan: 907 | 908 | 1 Round up to the next whole second 909 | 910 | 2 If seconds match move on, otherwise find the next match: 911 | 2.1 If next match is in the next minute then roll forwards 912 | 913 | 3 If minute matches move on, otherwise find the next match 914 | 3.1 If next match is in the next hour then roll forwards 915 | 3.2 Reset the seconds and go to 2 916 | 917 | 4 If hour matches move on, otherwise find the next match 918 | 4.1 If next match is in the next day then roll forwards, 919 | 4.2 Reset the minutes and seconds and go to 2 920 | 921 | ... 922 | */ 923 | if (!expr) return CRON_INVALID_INSTANT; 924 | struct tm calval; 925 | memset(&calval, 0, sizeof(struct tm)); 926 | struct tm* calendar = cron_time(&date, &calval); 927 | if (!calendar) return CRON_INVALID_INSTANT; 928 | time_t original = cron_mktime(calendar); 929 | if (CRON_INVALID_INSTANT == original) return CRON_INVALID_INSTANT; 930 | 931 | int res = do_next(expr, calendar, calendar->tm_year); 932 | if (0 != res) return CRON_INVALID_INSTANT; 933 | 934 | time_t calculated = cron_mktime(calendar); 935 | if (CRON_INVALID_INSTANT == calculated) return CRON_INVALID_INSTANT; 936 | if (calculated == original) { 937 | /* We arrived at the original timestamp - round up to the next whole second and try again... */ 938 | res = add_to_field(calendar, CRON_CF_SECOND, 1); 939 | if (0 != res) return CRON_INVALID_INSTANT; 940 | res = do_next(expr, calendar, calendar->tm_year); 941 | if (0 != res) return CRON_INVALID_INSTANT; 942 | } 943 | 944 | return cron_mktime(calendar); 945 | } 946 | 947 | 948 | /* https://github.com/staticlibs/ccronexpr/pull/8 */ 949 | 950 | static unsigned int prev_set_bit(uint8_t* bits, int from_index, int to_index, int* notfound) { 951 | int i; 952 | if (!bits) { 953 | *notfound = 1; 954 | return 0; 955 | } 956 | for (i = from_index; i >= to_index; i--) { 957 | if (cron_get_bit(bits, i)) return i; 958 | } 959 | *notfound = 1; 960 | return 0; 961 | } 962 | 963 | static int last_day_of_month(int month, int year) { 964 | struct tm cal; 965 | time_t t; 966 | memset(&cal,0,sizeof(cal)); 967 | cal.tm_sec=0; 968 | cal.tm_min=0; 969 | cal.tm_hour=0; 970 | cal.tm_mon = month+1; 971 | cal.tm_mday = 0; 972 | cal.tm_year=year; 973 | t=mktime(&cal); 974 | return gmtime(&t)->tm_mday; 975 | } 976 | 977 | /** 978 | * Reset the calendar setting all the fields provided to zero. 979 | */ 980 | static int reset_max(struct tm* calendar, int field) { 981 | if (!calendar || -1 == field) { 982 | return 1; 983 | } 984 | switch (field) { 985 | case CRON_CF_SECOND: 986 | calendar->tm_sec = 59; 987 | break; 988 | case CRON_CF_MINUTE: 989 | calendar->tm_min = 59; 990 | break; 991 | case CRON_CF_HOUR_OF_DAY: 992 | calendar->tm_hour = 23; 993 | break; 994 | case CRON_CF_DAY_OF_WEEK: 995 | calendar->tm_wday = 6; 996 | break; 997 | case CRON_CF_DAY_OF_MONTH: 998 | calendar->tm_mday = last_day_of_month(calendar->tm_mon, calendar->tm_year); 999 | break; 1000 | case CRON_CF_MONTH: 1001 | calendar->tm_mon = 11; 1002 | break; 1003 | case CRON_CF_YEAR: 1004 | /* I don't think this is supposed to happen ... */ 1005 | fprintf(stderr, "reset CRON_CF_YEAR\n"); 1006 | break; 1007 | default: 1008 | return 1; /* unknown field */ 1009 | } 1010 | time_t res = cron_mktime(calendar); 1011 | if (CRON_INVALID_INSTANT == res) { 1012 | return 1; 1013 | } 1014 | return 0; 1015 | } 1016 | 1017 | static int reset_all_max(struct tm* calendar, int* fields) { 1018 | int i; 1019 | int res = 0; 1020 | if (!calendar || !fields) { 1021 | return 1; 1022 | } 1023 | for (i = 0; i < CRON_CF_ARR_LEN; i++) { 1024 | if (-1 != fields[i]) { 1025 | res = reset_max(calendar, fields[i]); 1026 | if (0 != res) return res; 1027 | } 1028 | } 1029 | return 0; 1030 | } 1031 | 1032 | /** 1033 | * Search the bits provided for the next set bit after the value provided, 1034 | * and reset the calendar. 1035 | */ 1036 | static unsigned int find_prev(uint8_t* bits, unsigned int max, unsigned int value, struct tm* calendar, unsigned int field, unsigned int nextField, int* lower_orders, int* res_out) { 1037 | int notfound = 0; 1038 | int err = 0; 1039 | unsigned int next_value = prev_set_bit(bits, value, 0, ¬found); 1040 | /* roll under if needed */ 1041 | if (notfound) { 1042 | err = add_to_field(calendar, nextField, -1); 1043 | if (err) goto return_error; 1044 | err = reset_max(calendar, field); 1045 | if (err) goto return_error; 1046 | notfound = 0; 1047 | next_value = prev_set_bit(bits, max - 1, value, ¬found); 1048 | } 1049 | if (notfound || next_value != value) { 1050 | err = set_field(calendar, field, next_value); 1051 | if (err) goto return_error; 1052 | err = reset_all_max(calendar, lower_orders); 1053 | if (err) goto return_error; 1054 | } 1055 | return next_value; 1056 | 1057 | return_error: 1058 | *res_out = 1; 1059 | return 0; 1060 | } 1061 | 1062 | static unsigned int find_prev_day(struct tm* calendar, uint8_t* days_of_month, unsigned int day_of_month, uint8_t* days_of_week, unsigned int day_of_week, int* resets, int* res_out) { 1063 | int err; 1064 | unsigned int count = 0; 1065 | unsigned int max = 366; 1066 | while ((!cron_get_bit(days_of_month, day_of_month) || !cron_get_bit(days_of_week, day_of_week)) && count++ < max) { 1067 | err = add_to_field(calendar, CRON_CF_DAY_OF_MONTH, -1); 1068 | 1069 | if (err) goto return_error; 1070 | day_of_month = calendar->tm_mday; 1071 | day_of_week = calendar->tm_wday; 1072 | reset_all_max(calendar, resets); 1073 | } 1074 | return day_of_month; 1075 | 1076 | return_error: 1077 | *res_out = 1; 1078 | return 0; 1079 | } 1080 | 1081 | static int do_prev(cron_expr* expr, struct tm* calendar, unsigned int dot) { 1082 | int i; 1083 | int res = 0; 1084 | int* resets = NULL; 1085 | int* empty_list = NULL; 1086 | unsigned int second = 0; 1087 | unsigned int update_second = 0; 1088 | unsigned int minute = 0; 1089 | unsigned int update_minute = 0; 1090 | unsigned int hour = 0; 1091 | unsigned int update_hour = 0; 1092 | unsigned int day_of_week = 0; 1093 | unsigned int day_of_month = 0; 1094 | unsigned int update_day_of_month = 0; 1095 | unsigned int month = 0; 1096 | unsigned int update_month = 0; 1097 | 1098 | resets = (int*) cron_malloc(CRON_CF_ARR_LEN * sizeof(int)); 1099 | if (!resets) goto return_result; 1100 | empty_list = (int*) cron_malloc(CRON_CF_ARR_LEN * sizeof(int)); 1101 | if (!empty_list) goto return_result; 1102 | for (i = 0; i < CRON_CF_ARR_LEN; i++) { 1103 | resets[i] = -1; 1104 | empty_list[i] = -1; 1105 | } 1106 | 1107 | second = calendar->tm_sec; 1108 | update_second = find_prev(expr->seconds, CRON_MAX_SECONDS, second, calendar, CRON_CF_SECOND, CRON_CF_MINUTE, empty_list, &res); 1109 | if (0 != res) goto return_result; 1110 | if (second == update_second) { 1111 | push_to_fields_arr(resets, CRON_CF_SECOND); 1112 | } 1113 | 1114 | minute = calendar->tm_min; 1115 | update_minute = find_prev(expr->minutes, CRON_MAX_MINUTES, minute, calendar, CRON_CF_MINUTE, CRON_CF_HOUR_OF_DAY, resets, &res); 1116 | if (0 != res) goto return_result; 1117 | if (minute == update_minute) { 1118 | push_to_fields_arr(resets, CRON_CF_MINUTE); 1119 | } else { 1120 | res = do_prev(expr, calendar, dot); 1121 | if (0 != res) goto return_result; 1122 | } 1123 | 1124 | hour = calendar->tm_hour; 1125 | update_hour = find_prev(expr->hours, CRON_MAX_HOURS, hour, calendar, CRON_CF_HOUR_OF_DAY, CRON_CF_DAY_OF_WEEK, resets, &res); 1126 | if (0 != res) goto return_result; 1127 | if (hour == update_hour) { 1128 | push_to_fields_arr(resets, CRON_CF_HOUR_OF_DAY); 1129 | } else { 1130 | res = do_prev(expr, calendar, dot); 1131 | if (0 != res) goto return_result; 1132 | } 1133 | 1134 | day_of_week = calendar->tm_wday; 1135 | day_of_month = calendar->tm_mday; 1136 | update_day_of_month = find_prev_day(calendar, expr->days_of_month, day_of_month, expr->days_of_week, day_of_week, resets, &res); 1137 | if (0 != res) goto return_result; 1138 | if (day_of_month == update_day_of_month) { 1139 | push_to_fields_arr(resets, CRON_CF_DAY_OF_MONTH); 1140 | } else { 1141 | res = do_prev(expr, calendar, dot); 1142 | if (0 != res) goto return_result; 1143 | } 1144 | 1145 | month = calendar->tm_mon; /*day already adds one if no day in same month is found*/ 1146 | update_month = find_prev(expr->months, CRON_MAX_MONTHS, month, calendar, CRON_CF_MONTH, CRON_CF_YEAR, resets, &res); 1147 | if (0 != res) goto return_result; 1148 | if (month != update_month) { 1149 | if (dot - calendar->tm_year > CRON_MAX_YEARS_DIFF) { 1150 | res = -1; 1151 | goto return_result; 1152 | } 1153 | res = do_prev(expr, calendar, dot); 1154 | if (0 != res) goto return_result; 1155 | } 1156 | goto return_result; 1157 | 1158 | return_result: 1159 | if (!resets || !empty_list) { 1160 | res = -1; 1161 | } 1162 | if (resets) { 1163 | cron_free(resets); 1164 | } 1165 | if (empty_list) { 1166 | cron_free(empty_list); 1167 | } 1168 | return res; 1169 | } 1170 | 1171 | time_t cron_prev(cron_expr* expr, time_t date) { 1172 | /* 1173 | The plan: 1174 | 1175 | 1 Round down to a whole second 1176 | 1177 | 2 If seconds match move on, otherwise find the next match: 1178 | 2.1 If next match is in the next minute then roll forwards 1179 | 1180 | 3 If minute matches move on, otherwise find the next match 1181 | 3.1 If next match is in the next hour then roll forwards 1182 | 3.2 Reset the seconds and go to 2 1183 | 1184 | 4 If hour matches move on, otherwise find the next match 1185 | 4.1 If next match is in the next day then roll forwards, 1186 | 4.2 Reset the minutes and seconds and go to 2 1187 | 1188 | ... 1189 | */ 1190 | if (!expr) return CRON_INVALID_INSTANT; 1191 | struct tm calval; 1192 | memset(&calval, 0, sizeof(struct tm)); 1193 | struct tm* calendar = cron_time(&date, &calval); 1194 | if (!calendar) return CRON_INVALID_INSTANT; 1195 | time_t original = cron_mktime(calendar); 1196 | if (CRON_INVALID_INSTANT == original) return CRON_INVALID_INSTANT; 1197 | 1198 | /* calculate the previous occurrence */ 1199 | int res = do_prev(expr, calendar, calendar->tm_year); 1200 | if (0 != res) return CRON_INVALID_INSTANT; 1201 | 1202 | /* check for a match, try from the next second if one wasn't found */ 1203 | time_t calculated = cron_mktime(calendar); 1204 | if (CRON_INVALID_INSTANT == calculated) return CRON_INVALID_INSTANT; 1205 | if (calculated == original) { 1206 | /* We arrived at the original timestamp - round up to the next whole second and try again... */ 1207 | res = add_to_field(calendar, CRON_CF_SECOND, -1); 1208 | if (0 != res) return CRON_INVALID_INSTANT; 1209 | res = do_prev(expr, calendar, calendar->tm_year); 1210 | if (0 != res) return CRON_INVALID_INSTANT; 1211 | } 1212 | 1213 | return cron_mktime(calendar); 1214 | } 1215 | -------------------------------------------------------------------------------- /library/ccronexpr/ccronexpr.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015, alex at staticlibs.net 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /* 18 | * File: ccronexpr.h 19 | * Author: alex 20 | * 21 | * Created on February 24, 2015, 9:35 AM 22 | */ 23 | 24 | #ifndef CCRONEXPR_H 25 | #define CCRONEXPR_H 26 | 27 | #if defined(__cplusplus) && !defined(CRON_COMPILE_AS_CXX) 28 | extern "C" { 29 | #endif 30 | 31 | #ifndef ANDROID 32 | #include 33 | #else /* ANDROID */ 34 | #include 35 | #endif /* ANDROID */ 36 | 37 | #include /*added for use if uint*_t data types*/ 38 | 39 | 40 | /** 41 | * Parsed cron expression 42 | */ 43 | typedef struct { 44 | uint8_t seconds[8]; 45 | uint8_t minutes[8]; 46 | uint8_t hours[3]; 47 | uint8_t days_of_week[1]; 48 | uint8_t days_of_month[4]; 49 | uint8_t months[2]; 50 | } cron_expr; 51 | 52 | /** 53 | * Parses specified cron expression. 54 | * 55 | * @param expression cron expression as nul-terminated string, 56 | * should be no longer that 256 bytes 57 | * @param pointer to cron expression structure, it's client code responsibility 58 | * to free/destroy it afterwards 59 | * @param error output error message, will be set to string literal 60 | * error message in case of error. Will be set to NULL on success. 61 | * The error message should NOT be freed by client. 62 | */ 63 | void cron_parse_expr(const char* expression, cron_expr* target, const char** error); 64 | 65 | /** 66 | * Uses the specified expression to calculate the next 'fire' date after 67 | * the specified date. All dates are processed as UTC (GMT) dates 68 | * without timezones information. To use local dates (current system timezone) 69 | * instead of GMT compile with '-DCRON_USE_LOCAL_TIME' 70 | * 71 | * @param expr parsed cron expression to use in next date calculation 72 | * @param date start date to start calculation from 73 | * @return next 'fire' date in case of success, '((time_t) -1)' in case of error. 74 | */ 75 | time_t cron_next(cron_expr* expr, time_t date); 76 | 77 | /** 78 | * Uses the specified expression to calculate the previous 'fire' date after 79 | * the specified date. All dates are processed as UTC (GMT) dates 80 | * without timezones information. To use local dates (current system timezone) 81 | * instead of GMT compile with '-DCRON_USE_LOCAL_TIME' 82 | * 83 | * @param expr parsed cron expression to use in previous date calculation 84 | * @param date start date to start calculation from 85 | * @return previous 'fire' date in case of success, '((time_t) -1)' in case of error. 86 | */ 87 | time_t cron_prev(cron_expr* expr, time_t date); 88 | 89 | 90 | #if defined(__cplusplus) && !defined(CRON_COMPILE_AS_CXX) 91 | } /* extern "C"*/ 92 | #endif 93 | 94 | #endif /* CCRONEXPR_H */ 95 | 96 | 97 | -------------------------------------------------------------------------------- /library/ccronexpr/ccronexpr_test.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015, alex at staticlibs.net 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /* 18 | * File: CronExprParser_test.cpp 19 | * Author: alex 20 | * 21 | * Created on February 24, 2015, 9:36 AM 22 | */ 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #include "ccronexpr.h" 31 | 32 | #define MAX_SECONDS 60 33 | #define CRON_MAX_MINUTES 60 34 | #define CRON_MAX_HOURS 24 35 | #define CRON_MAX_DAYS_OF_WEEK 8 36 | #define CRON_MAX_DAYS_OF_MONTH 32 37 | #define CRON_MAX_MONTHS 12 38 | 39 | #define INVALID_INSTANT ((time_t) -1) 40 | 41 | #define DATE_FORMAT "%Y-%m-%d_%H:%M:%S" 42 | 43 | #ifndef ARRAY_LEN 44 | #define ARRAY_LEN(x) sizeof(x)/sizeof(x[0]) 45 | #endif 46 | 47 | #ifdef CRON_TEST_MALLOC 48 | static int cronAllocations = 0; 49 | static int cronTotalAllocations = 0; 50 | static int maxAlloc = 0; 51 | void* cron_malloc(size_t n) { 52 | cronAllocations++; 53 | cronTotalAllocations++; 54 | if (cronAllocations > maxAlloc) { 55 | maxAlloc = cronAllocations; 56 | } 57 | return malloc(n); 58 | } 59 | 60 | void cron_free(void* p) { 61 | cronAllocations--; 62 | free(p); 63 | } 64 | #endif 65 | 66 | #ifndef ANDROID 67 | #ifndef _WIN32 68 | time_t timegm(struct tm* __tp); 69 | #else /* _WIN32 */ 70 | static time_t timegm(struct tm* tm) { 71 | return _mkgmtime(tm); 72 | } 73 | #endif /* _WIN32 */ 74 | #else /* ANDROID */ 75 | static time_t timegm(struct tm * const t) { 76 | /* time_t is signed on Android. */ 77 | static const time_t kTimeMax = ~(1L << (sizeof (time_t) * CHAR_BIT - 1)); 78 | static const time_t kTimeMin = (1L << (sizeof (time_t) * CHAR_BIT - 1)); 79 | time64_t result = timegm64(t); 80 | if (result < kTimeMin || result > kTimeMax) 81 | return -1; 82 | return result; 83 | } 84 | #endif 85 | 86 | /** 87 | * uint8_t* replace char* for storing hit dates, set_bit and get_bit are used as handlers 88 | */ 89 | uint8_t cron_get_bit(uint8_t* rbyte, int idx); 90 | void cron_set_bit(uint8_t* rbyte, int idx); 91 | void cron_del_bit(uint8_t* rbyte, int idx); 92 | 93 | static int crons_equal(cron_expr* cr1, cron_expr* cr2) { 94 | unsigned int i; 95 | for (i = 0; i < ARRAY_LEN(cr1->seconds); i++) { 96 | if (cr1->seconds[i] != cr2->seconds[i]) { 97 | printf("seconds not equal @%d %02x != %02x", i, cr1->seconds[i], cr2->seconds[i]); 98 | return 0; 99 | } 100 | } 101 | for (i = 0; i < ARRAY_LEN(cr1->minutes); i++) { 102 | if (cr1->minutes[i] != cr2->minutes[i]) { 103 | printf("minutes not equal @%d %02x != %02x", i, cr1->minutes[i], cr2->minutes[i]); 104 | return 0; 105 | } 106 | } 107 | for (i = 0; i < ARRAY_LEN(cr1->hours); i++) { 108 | if (cr1->hours[i] != cr2->hours[i]) { 109 | printf("hours not equal @%d %02x != %02x", i, cr1->hours[i], cr2->hours[i]); 110 | return 0; 111 | } 112 | } 113 | for (i = 0; i < ARRAY_LEN(cr1->days_of_week); i++) { 114 | if (cr1->days_of_week[i] != cr2->days_of_week[i]) { 115 | printf("days_of_week not equal @%d %02x != %02x", i, cr1->days_of_week[i], cr2->days_of_week[i]); 116 | return 0; 117 | } 118 | } 119 | for (i = 0; i < ARRAY_LEN(cr1->days_of_month); i++) { 120 | if (cr1->days_of_month[i] != cr2->days_of_month[i]) { 121 | printf("days_of_month not equal @%d %02x != %02x", i, cr1->days_of_month[i], cr2->days_of_month[i]); 122 | return 0; 123 | } 124 | } 125 | for (i = 0; i < ARRAY_LEN(cr1->months); i++) { 126 | if (cr1->months[i] != cr2->months[i]) { 127 | printf("months not equal @%d %02x != %02x", i, cr1->months[i], cr2->months[i]); 128 | return 0; 129 | } 130 | } 131 | return 1; 132 | } 133 | 134 | int one_dec_num(const char ch) { 135 | switch (ch) { 136 | case '0': 137 | return 0; 138 | case '1': 139 | return 1; 140 | case '2': 141 | return 2; 142 | case '3': 143 | return 3; 144 | case '4': 145 | return 4; 146 | case '5': 147 | return 5; 148 | case '6': 149 | return 6; 150 | case '7': 151 | return 7; 152 | case '8': 153 | return 8; 154 | case '9': 155 | return 9; 156 | default: 157 | return -1; 158 | } 159 | } 160 | 161 | int two_dec_num(const char* first) { 162 | return one_dec_num(first[0]) * 10 + one_dec_num(first[1]); 163 | } 164 | 165 | /* strptime is not available in msvc */ 166 | /* 2012-07-01_09:53:50 */ 167 | /* 0123456789012345678 */ 168 | struct tm* poors_mans_strptime(const char* str) { 169 | struct tm* cal = (struct tm*) malloc(sizeof(struct tm)); 170 | switch (str[3]) { 171 | case '7': 172 | cal->tm_year = 107; 173 | break; 174 | case '8': 175 | cal->tm_year = 108; 176 | break; 177 | case '9': 178 | cal->tm_year = 109; 179 | break; 180 | case '0': 181 | cal->tm_year = 110; 182 | break; 183 | case '1': 184 | cal->tm_year = 111; 185 | break; 186 | case '2': 187 | cal->tm_year = 112; 188 | break; 189 | } 190 | cal->tm_mon = two_dec_num(str + 5) - 1; 191 | cal->tm_mday = two_dec_num(str + 8); 192 | cal->tm_wday = 0; 193 | cal->tm_yday = 0; 194 | cal->tm_hour = two_dec_num(str + 11); 195 | cal->tm_min = two_dec_num(str + 14); 196 | cal->tm_sec = two_dec_num(str + 17); 197 | return cal; 198 | } 199 | 200 | void check_next(const char* pattern, const char* initial, const char* expected) { 201 | const char* err = NULL; 202 | cron_expr parsed; 203 | memset(&parsed, 0, sizeof(parsed)); 204 | cron_parse_expr(pattern, &parsed, &err); 205 | 206 | struct tm* calinit = poors_mans_strptime(initial); 207 | time_t dateinit = timegm(calinit); 208 | assert(-1 != dateinit); 209 | time_t datenext = cron_next(&parsed, dateinit); 210 | struct tm* calnext = gmtime(&datenext); 211 | assert(calnext); 212 | char* buffer = (char*) malloc(21); 213 | memset(buffer, 0, 21); 214 | strftime(buffer, 20, DATE_FORMAT, calnext); 215 | if (0 != strcmp(expected, buffer)) { 216 | printf("Pattern: %s\n", pattern); 217 | printf("Initial: %s\n", initial); 218 | printf("Expected: %s\n", expected); 219 | printf("Actual: %s\n", buffer); 220 | assert(0); 221 | } 222 | free(buffer); 223 | free(calinit); 224 | } 225 | 226 | void check_same(const char* expr1, const char* expr2) { 227 | cron_expr parsed1; 228 | memset(&parsed1, 0, sizeof(parsed1)); 229 | cron_parse_expr(expr1, &parsed1, NULL); 230 | cron_expr parsed2; 231 | memset(&parsed2, 0, sizeof(parsed2)); 232 | cron_parse_expr(expr2, &parsed2, NULL); 233 | assert(crons_equal(&parsed1, &parsed2)); 234 | } 235 | 236 | void check_calc_invalid() { 237 | cron_expr parsed; 238 | memset(&parsed, 0, sizeof(parsed)); 239 | cron_parse_expr("0 0 0 31 6 *", &parsed, NULL); 240 | struct tm * calinit = poors_mans_strptime("2012-07-01_09:53:50"); 241 | time_t dateinit = timegm(calinit); 242 | time_t res = cron_next(&parsed, dateinit); 243 | assert(INVALID_INSTANT == res); 244 | free(calinit); 245 | } 246 | 247 | void check_expr_invalid(const char* expr) { 248 | const char* err = NULL; 249 | cron_expr test; 250 | memset(&test, 0, sizeof(test)); 251 | cron_parse_expr(expr, &test, &err); 252 | assert(err); 253 | } 254 | 255 | void test_expr() { 256 | check_next("*/15 * 1-4 * * *", "2012-07-01_09:53:50", "2012-07-02_01:00:00"); 257 | check_next("*/15 * 1-4 * * *", "2012-07-01_09:53:00", "2012-07-02_01:00:00"); 258 | check_next("0 */2 1-4 * * *", "2012-07-01_09:00:00", "2012-07-02_01:00:00"); 259 | check_next("* * * * * *", "2012-07-01_09:00:00", "2012-07-01_09:00:01"); 260 | check_next("* * * * * *", "2012-12-01_09:00:58", "2012-12-01_09:00:59"); 261 | check_next("10 * * * * *", "2012-12-01_09:42:09", "2012-12-01_09:42:10"); 262 | check_next("11 * * * * *", "2012-12-01_09:42:10", "2012-12-01_09:42:11"); 263 | check_next("10 * * * * *", "2012-12-01_09:42:10", "2012-12-01_09:43:10"); 264 | check_next("10-15 * * * * *", "2012-12-01_09:42:09", "2012-12-01_09:42:10"); 265 | check_next("10-15 * * * * *", "2012-12-01_21:42:14", "2012-12-01_21:42:15"); 266 | check_next("0 * * * * *", "2012-12-01_21:10:42", "2012-12-01_21:11:00"); 267 | check_next("0 * * * * *", "2012-12-01_21:11:00", "2012-12-01_21:12:00"); 268 | check_next("0 11 * * * *", "2012-12-01_21:10:42", "2012-12-01_21:11:00"); 269 | check_next("0 10 * * * *", "2012-12-01_21:11:00", "2012-12-01_22:10:00"); 270 | check_next("0 0 * * * *", "2012-09-30_11:01:00", "2012-09-30_12:00:00"); 271 | check_next("0 0 * * * *", "2012-09-30_12:00:00", "2012-09-30_13:00:00"); 272 | check_next("0 0 * * * *", "2012-09-10_23:01:00", "2012-09-11_00:00:00"); 273 | check_next("0 0 * * * *", "2012-09-11_00:00:00", "2012-09-11_01:00:00"); 274 | check_next("0 0 0 * * *", "2012-09-01_14:42:43", "2012-09-02_00:00:00"); 275 | check_next("0 0 0 * * *", "2012-09-02_00:00:00", "2012-09-03_00:00:00"); 276 | check_next("* * * 10 * *", "2012-10-09_15:12:42", "2012-10-10_00:00:00"); 277 | check_next("* * * 10 * *", "2012-10-11_15:12:42", "2012-11-10_00:00:00"); 278 | check_next("0 0 0 * * *", "2012-09-30_15:12:42", "2012-10-01_00:00:00"); 279 | check_next("0 0 0 * * *", "2012-10-01_00:00:00", "2012-10-02_00:00:00"); 280 | check_next("0 0 0 * * *", "2012-08-30_15:12:42", "2012-08-31_00:00:00"); 281 | check_next("0 0 0 * * *", "2012-08-31_00:00:00", "2012-09-01_00:00:00"); 282 | check_next("0 0 0 * * *", "2012-10-30_15:12:42", "2012-10-31_00:00:00"); 283 | check_next("0 0 0 * * *", "2012-10-31_00:00:00", "2012-11-01_00:00:00"); 284 | check_next("0 0 0 1 * *", "2012-10-30_15:12:42", "2012-11-01_00:00:00"); 285 | check_next("0 0 0 1 * *", "2012-11-01_00:00:00", "2012-12-01_00:00:00"); 286 | check_next("0 0 0 1 * *", "2010-12-31_15:12:42", "2011-01-01_00:00:00"); 287 | check_next("0 0 0 1 * *", "2011-01-01_00:00:00", "2011-02-01_00:00:00"); 288 | check_next("0 0 0 31 * *", "2011-10-30_15:12:42", "2011-10-31_00:00:00"); 289 | check_next("0 0 0 1 * *", "2011-10-30_15:12:42", "2011-11-01_00:00:00"); 290 | check_next("* * * * * 2", "2010-10-25_15:12:42", "2010-10-26_00:00:00"); 291 | check_next("* * * * * 2", "2010-10-20_15:12:42", "2010-10-26_00:00:00"); 292 | check_next("* * * * * 2", "2010-10-27_15:12:42", "2010-11-02_00:00:00"); 293 | check_next("55 5 * * * *", "2010-10-27_15:04:54", "2010-10-27_15:05:55"); 294 | check_next("55 5 * * * *", "2010-10-27_15:05:55", "2010-10-27_16:05:55"); 295 | check_next("55 * 10 * * *", "2010-10-27_09:04:54", "2010-10-27_10:00:55"); 296 | check_next("55 * 10 * * *", "2010-10-27_10:00:55", "2010-10-27_10:01:55"); 297 | check_next("* 5 10 * * *", "2010-10-27_09:04:55", "2010-10-27_10:05:00"); 298 | check_next("* 5 10 * * *", "2010-10-27_10:05:00", "2010-10-27_10:05:01"); 299 | check_next("55 * * 3 * *", "2010-10-02_10:05:54", "2010-10-03_00:00:55"); 300 | check_next("55 * * 3 * *", "2010-10-03_00:00:55", "2010-10-03_00:01:55"); 301 | check_next("* * * 3 11 *", "2010-10-02_14:42:55", "2010-11-03_00:00:00"); 302 | check_next("* * * 3 11 *", "2010-11-03_00:00:00", "2010-11-03_00:00:01"); 303 | check_next("0 0 0 29 2 *", "2007-02-10_14:42:55", "2008-02-29_00:00:00"); 304 | check_next("0 0 0 29 2 *", "2008-02-29_00:00:00", "2012-02-29_00:00:00"); 305 | check_next("0 0 7 ? * MON-FRI", "2009-09-26_00:42:55", "2009-09-28_07:00:00"); 306 | check_next("0 0 7 ? * MON-FRI", "2009-09-28_07:00:00", "2009-09-29_07:00:00"); 307 | check_next("0 30 23 30 1/3 ?", "2010-12-30_00:00:00", "2011-01-30_23:30:00"); 308 | check_next("0 30 23 30 1/3 ?", "2011-01-30_23:30:00", "2011-04-30_23:30:00"); 309 | check_next("0 30 23 30 1/3 ?", "2011-04-30_23:30:00", "2011-07-30_23:30:00"); 310 | } 311 | 312 | void test_parse() { 313 | 314 | check_same("* * * 2 * *", "* * * 2 * ?"); 315 | check_same("57,59 * * * * *", "57/2 * * * * *"); 316 | check_same("1,3,5 * * * * *", "1-6/2 * * * * *"); 317 | check_same("* * 4,8,12,16,20 * * *", "* * 4/4 * * *"); 318 | check_same("* * * * * 0-6", "* * * * * TUE,WED,THU,FRI,SAT,SUN,MON"); 319 | check_same("* * * * * 0", "* * * * * SUN"); 320 | check_same("* * * * * 0", "* * * * * 7"); 321 | check_same("* * * * 1-12 *", "* * * * FEB,JAN,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC *"); 322 | check_same("* * * * 2 *", "* * * * Feb *"); 323 | check_same("* * * * 1 *", "* * * * 1 *"); 324 | 325 | check_expr_invalid("77 * * * * *"); 326 | check_expr_invalid("44-77 * * * * *"); 327 | check_expr_invalid("* 77 * * * *"); 328 | check_expr_invalid("* 44-77 * * * *"); 329 | check_expr_invalid("* * 27 * * *"); 330 | check_expr_invalid("* * 23-28 * * *"); 331 | check_expr_invalid("* * * 45 * *"); 332 | check_expr_invalid("* * * 28-45 * *"); 333 | check_expr_invalid("0 0 0 25 13 ?"); 334 | check_expr_invalid("0 0 0 25 0 ?"); 335 | check_expr_invalid("0 0 0 32 12 ?"); 336 | check_expr_invalid("* * * * 11-13 *"); 337 | check_expr_invalid("-5 * * * * *"); 338 | check_expr_invalid("3-2 */5 * * * *"); 339 | check_expr_invalid("/5 * * * * *"); 340 | check_expr_invalid("*/0 * * * * *"); 341 | check_expr_invalid("*/-0 * * * * *"); 342 | check_expr_invalid("* 1 1 0 * *"); 343 | } 344 | 345 | void test_bits() { 346 | 347 | uint8_t testbyte[8]; 348 | memset(testbyte, 0, 8); 349 | int err = 0; 350 | int i; 351 | 352 | for (i = 0; i <= 63; i++) { 353 | cron_set_bit(testbyte, i); 354 | if (!cron_get_bit(testbyte, i)) { 355 | printf("Bit set error! Bit: %d!\n", i); 356 | err = 1; 357 | } 358 | cron_del_bit(testbyte, i); 359 | if (cron_get_bit(testbyte, i)) { 360 | printf("Bit clear error! Bit: %d!\n", i); 361 | err = 1; 362 | } 363 | assert(!err); 364 | } 365 | 366 | for (i = 0; i < 12; i++) { 367 | cron_set_bit(testbyte, i); 368 | } 369 | if (testbyte[0] != 0xff) { 370 | err = 1; 371 | } 372 | if (testbyte[1] != 0x0f) { 373 | err = 1; 374 | } 375 | 376 | assert(!err); 377 | } 378 | 379 | /* For this test to work you need to set "-DCRON_TEST_MALLOC=1"*/ 380 | #ifdef CRON_TEST_MALLOC 381 | void test_memory() { 382 | cron_expr cron; 383 | const char* err; 384 | 385 | cron_parse_expr("* * * * * *", &cron, &err); 386 | if (cronAllocations != 0) { 387 | printf("Allocations != 0 but %d", cronAllocations); 388 | assert(0); 389 | } 390 | printf("Allocations: total: %d, max: %d", cronTotalAllocations, maxAlloc); 391 | } 392 | #endif 393 | 394 | int main() { 395 | 396 | test_bits(); 397 | 398 | test_expr(); 399 | test_parse(); 400 | check_calc_invalid(); 401 | #ifdef CRON_TEST_MALLOC 402 | test_memory(); /* For this test to work you need to set "-DCRON_TEST_MALLOC=1"*/ 403 | #endif 404 | printf("\nAll OK!"); 405 | return 0; 406 | } 407 | 408 | -------------------------------------------------------------------------------- /library/jobs/jobs.c: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Insite SAS 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // 16 | // 17 | // Author: David Mora Rodriguez dmorar (at) insite.com.co 18 | // 19 | #include "freertos/FreeRTOS.h" 20 | #include "freertos/semphr.h" 21 | #include "jobs.h" 22 | 23 | // STATIC FUNCTION DECLARATIONS 24 | struct cron_job_node *_cron_job_list_insert(struct cron_job_node *next_node, struct cron_job_node *new_node); 25 | // STATIC STRUCTS 26 | static struct 27 | { 28 | int next_id; 29 | struct cron_job_node *first; 30 | SemaphoreHandle_t semaphore; 31 | int init; 32 | 33 | } linked_link_state = { 34 | .next_id = 0, 35 | .first = NULL, 36 | .semaphore = NULL, 37 | .init = 0 38 | }; 39 | 40 | void cron_job_list_init() 41 | { 42 | if (linked_link_state.init == 0) { 43 | linked_link_state.semaphore = xSemaphoreCreateMutex(); 44 | linked_link_state.init=1; 45 | } 46 | } 47 | 48 | struct cron_job_node *cron_job_list_first() 49 | { 50 | return linked_link_state.first; 51 | } 52 | 53 | /* RECURSIVE BUT STACK EXHAUSTION? */ 54 | struct cron_job_node *_cron_job_list_insert(struct cron_job_node *next_node, struct cron_job_node *new_node) 55 | { 56 | if (next_node == NULL || new_node->job->next_execution < next_node->job->next_execution) 57 | { 58 | new_node->next = next_node; 59 | return new_node; 60 | } 61 | else 62 | { 63 | next_node->next = _cron_job_list_insert(next_node->next, new_node); 64 | } 65 | return next_node; 66 | } 67 | 68 | int cron_job_list_insert(cron_job *job) 69 | { 70 | if (linked_link_state.semaphore == NULL) 71 | cron_job_list_init(); 72 | if (job == NULL) 73 | { 74 | return -1; 75 | } 76 | struct cron_job_node *new_node = calloc(sizeof(struct cron_job_node),1); 77 | if (new_node == NULL) 78 | { 79 | return -1; 80 | } 81 | new_node->job = job; 82 | if (xSemaphoreTake(linked_link_state.semaphore, (TickType_t)10) == pdTRUE) 83 | { 84 | linked_link_state.first = _cron_job_list_insert(linked_link_state.first, new_node); 85 | xSemaphoreGive(linked_link_state.semaphore); 86 | } 87 | else 88 | { 89 | free(new_node); 90 | return -1; 91 | } 92 | if (new_node->job->id == -1) // NOT INITIALIZED ON -1 93 | new_node->job->id = linked_link_state.next_id++; 94 | return new_node->job->id; 95 | } 96 | 97 | int cron_job_list_remove(int id) 98 | { 99 | int ret = -1; 100 | struct cron_job_node *node = linked_link_state.first, *prev_node = NULL; 101 | if (xSemaphoreTake(linked_link_state.semaphore, (TickType_t)10) == pdTRUE) 102 | { 103 | do 104 | { 105 | if (node->job->id == id) 106 | { 107 | if (node == linked_link_state.first) 108 | { 109 | linked_link_state.first = node->next; 110 | } 111 | else 112 | { 113 | prev_node->next = node->next; 114 | } 115 | free(node); 116 | node = NULL; 117 | 118 | ret = 0; 119 | break; 120 | } 121 | else 122 | { 123 | prev_node = node; 124 | node = node->next; 125 | } 126 | 127 | } while (node->next); 128 | xSemaphoreGive(linked_link_state.semaphore); 129 | } 130 | else 131 | { 132 | ret = -1; 133 | } 134 | return ret; 135 | } 136 | 137 | int cron_job_node_count() 138 | { 139 | int cnt = 0; 140 | struct cron_job_node *node = cron_job_list_first(); 141 | if (node == NULL) 142 | { 143 | cnt = 0; 144 | } 145 | else 146 | { 147 | do 148 | { 149 | cnt++; 150 | node = node->next; 151 | } while (node); 152 | } 153 | return cnt; 154 | } 155 | 156 | 157 | int cron_job_list_reset_id(){ 158 | if (cron_job_node_count()==0) { 159 | linked_link_state.next_id=0; 160 | return 0; 161 | } 162 | return -1; 163 | } -------------------------------------------------------------------------------- /library/jobs/jobs.h: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Insite SAS 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // 16 | // 17 | // Author: David Mora Rodriguez dmorar (at) insite.com.co 18 | // 19 | 20 | #ifndef _ESP_CRON_JOBS_LINKED_LIST 21 | #define _ESP_CRON_JOBS_LINKED_LIST 22 | #include 23 | #include "cron.h" 24 | 25 | struct cron_job_node { 26 | struct cron_job_node * next; 27 | cron_job * job; 28 | }; 29 | 30 | /* 31 | * SUMMARY: Returns the first element in the linkedlist. 32 | * 33 | * PARAMS: Time input (timestamp) 34 | * 35 | * RETURNS: hour 36 | */ 37 | 38 | struct cron_job_node * cron_job_list_first(); 39 | /* 40 | * SUMMARY: Adds a job to the list in execution order. 41 | * 42 | * PARAMS: job 43 | * 44 | * RETURNS: id or -1 on error 45 | */ 46 | int cron_job_list_insert(cron_job * job); 47 | /* 48 | * SUMMARY: Removes a node from the list. 49 | * Note that for a new node the id must be -1 and this method will give you a new id. 50 | * Ids start at zero and grow from there, so job->id must be set to -1. 51 | * 52 | * PARAMS: id for the node 53 | * 54 | * RETURNS: 0 on success, -1 on not found 55 | */ 56 | int cron_job_list_remove(int id); 57 | 58 | 59 | /* 60 | * SUMMARY:Counts elements on list o(N). 61 | * 62 | * PARAMS: NONE 63 | * 64 | * RETURNS: number of nodes on list 65 | */ 66 | int cron_job_node_count(); 67 | /* 68 | * SUMMARY: initializes the needed structures for the module to work (like mutex). It is safe to call it multiple times. 69 | * 70 | * PARAMS: NONE 71 | * 72 | * RETURNS:none 73 | */ 74 | void cron_job_list_init(); 75 | /* 76 | * SUMMARY: If the node count is zero then make id 0 to start over. 77 | * 78 | * PARAMS: NONE 79 | * 80 | * RETURNS: 0 on success, -1 on no action 81 | */ 82 | 83 | int cron_job_list_reset_id(); 84 | 85 | #endif -------------------------------------------------------------------------------- /test/component.mk: -------------------------------------------------------------------------------- 1 | CFLAGS += -D CRON_USE_LOCAL_TIME 2 | COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive -------------------------------------------------------------------------------- /test/test_cron.c: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Insite SAS 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // 16 | // 17 | // Author: David Mora Rodriguez dmorar (at) insite.com.co 18 | // 19 | 20 | #include 21 | #include 22 | #include 23 | #include "freertos/FreeRTOS.h" 24 | #include "freertos/task.h" 25 | #include "unity.h" 26 | #include "cron.h" 27 | #include "jobs.h" 28 | 29 | TEST_CASE("**CRON_JOB - INFO -- INIT TEST, THIS INITIALIZES THE TIME DATA MAY LEAK SOME MEMORY", "[cron_job]") { 30 | struct timeval tv; 31 | tv.tv_sec = 1530000000; // SOMEWHERE IN JUNE 2018 32 | settimeofday(&tv, NULL); 33 | 34 | } 35 | 36 | 37 | 38 | TEST_CASE("**CRON_JOB - cron_job_schedule and cron_job_remove IS IT WORKING? ", "[cron_job]") 39 | { 40 | 41 | int cnt = 0, cnt2 = 0, ans = 0; 42 | cnt = cron_job_node_count(); 43 | cron_job * job=cron_job_create("* * * * * *",NULL,NULL); 44 | cnt2 = cron_job_node_count(); 45 | TEST_ASSERT_MESSAGE(job != NULL, "INSERTION FAILED WITH ERRORS"); 46 | TEST_ASSERT_EQUAL_INT_MESSAGE(cnt + 1, cnt2, "LIST DIDNT GROW"); 47 | ans = cron_job_destroy(job); 48 | cnt2 = cron_job_node_count(); 49 | TEST_ASSERT_EQUAL_INT_MESSAGE( 0, ans, "DESTROY FAILED WITH ERRORS"); 50 | TEST_ASSERT_EQUAL_INT_MESSAGE(cnt, cnt2, "LIST DIDNT REDUCE"); 51 | } 52 | 53 | TEST_CASE("**CRON_JOB - cron_job_schedule CRON PARSER AND SCHEDULER IS AS EXPECTED ", "[cron_job]") 54 | { 55 | time_t now; 56 | struct timeval tv; 57 | struct tm timeinfo; 58 | char buffer[256], buffer2[256]; 59 | int buffer_len = 256; 60 | int ans = 0; 61 | tv.tv_sec = 1530000000; // SOMEWHERE IN JUNE 2018 62 | settimeofday(&tv, NULL); 63 | cron_job * job=cron_job_create("* * * * * *",NULL,NULL); 64 | time(&now); 65 | localtime_r(&(job->next_execution), &timeinfo); 66 | strftime(buffer, buffer_len, "%c", &timeinfo); 67 | localtime_r(&now, &timeinfo); 68 | strftime(buffer2, buffer_len, "%c", &timeinfo); 69 | printf("now: %s next execution: %s\n", buffer2, buffer); 70 | printf("now: %d next_execution: %d\n", (int)now, (int)job->next_execution); 71 | TEST_ASSERT_EQUAL_INT_MESSAGE(now + 1, job->next_execution, "SCHEDULE IS NOT FOR THE NEXT SECOND AS IT SHOULD BE"); 72 | ans = cron_job_destroy(job); 73 | TEST_ASSERT_EQUAL_INT_MESSAGE(0, ans, "UNABLE TO DESTROY A JOB"); 74 | } 75 | 76 | TEST_CASE("**CRON_JOB - cron_clear_all TEST CLEAR ALL JOBS ", "[cron_job]") 77 | { 78 | int cnt_init=0,cnt=0,i=0; 79 | cron_job * jobs[10]; 80 | cnt_init = cron_job_node_count(); 81 | 82 | for (i =0;i<10;i++) { 83 | jobs[i]=cron_job_create("* * * * * *",NULL,NULL); 84 | } 85 | cnt = cron_job_node_count(); 86 | TEST_ASSERT_EQUAL_INT_MESSAGE(i, cnt-cnt_init,"Creation count"); 87 | cron_job_destroy(jobs[0]); 88 | cnt = cron_job_node_count(); 89 | TEST_ASSERT_EQUAL_INT_MESSAGE(i-1, cnt-cnt_init,"Single destroy call"); 90 | cron_job_clear_all(); 91 | cnt = cron_job_node_count(); 92 | TEST_ASSERT_EQUAL_INT_MESSAGE(0, cnt-cnt_init,"Destroy all call"); 93 | } 94 | 95 | void test_cron_job_sample_callback(cron_job *job) 96 | { 97 | time_t now; 98 | struct tm timeinfo; 99 | char buffer[256],buffer2[256]; 100 | int buffer_len = 256; 101 | time(&now); 102 | localtime_r(&now, &timeinfo); 103 | strftime(buffer, buffer_len, "%c", &timeinfo); 104 | localtime_r(&(job->next_execution), &timeinfo); 105 | strftime(buffer2, buffer_len, "%c", &timeinfo); 106 | job->data = job->data+5; 107 | printf("CALLBACK RUNNED AT TIME: %s SHOULD RUN AT: %s WITH DATA: %d \n", buffer,buffer2,(unsigned int)job->data); 108 | return; 109 | } 110 | 111 | TEST_CASE("**CRON_JOB - cron_schedule_task TEST IF THE SCHEDULER RUNS ONCE AND WITH EXPECTED RESULTS", "[cron_job]") 112 | { 113 | time_t begin=1530000000; 114 | const int seconds_to_run=10*2; //whatever you want to run must be twice because one for the delay and one for the callback :P 115 | cron_job * jobs[2]; 116 | struct timeval tv; 117 | tv.tv_sec = begin; // SOMEWHERE IN JUNE 2018 118 | settimeofday(&tv, NULL); 119 | jobs[0]=cron_job_create("* * * * * *",test_cron_job_sample_callback,(void *)0); 120 | jobs[1]=cron_job_create("*/5 * * * * *",test_cron_job_sample_callback,(void *)10000); 121 | for (int i =0;idata > 5*8, "Unexpected delay");//IT DEPENDS ON TIME MOST OF THE TIME WILL DO 45, SOME 40 AND OTHERS 50 125 | TEST_ASSERT_MESSAGE((int)jobs[1]->data > 10005, "Unexpected delay"); // SOMETIMES IT RUNS TWICE BECAUSE OF BEGIN TIME 126 | cron_job_destroy(jobs[0]); 127 | cron_job_destroy(jobs[1]); 128 | //cron_job_clear_all(); 129 | } 130 | 131 | 132 | 133 | 134 | 135 | --------------------------------------------------------------------------------