├── library.json ├── library.properties ├── keywords.txt ├── src ├── CronAlarms.h ├── ccronexpr │ ├── ccronexpr.h │ └── ccronexpr.c └── CronAlarms.cpp ├── examples └── CronAlarms_example │ └── CronAlarms_example.ino ├── README.md └── COPYING /library.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "CronAlarms", 3 | "frameworks": "Arduino", 4 | "keywords": "cron, Time, alarm, schedule, date, hour, minute, second, day, week, month, year", 5 | "description": "Schedule alarms to occur at specific times with crontab syntax", 6 | "url": "https://github.com/Martin-Laclaustra/CronAlarms", 7 | "authors": 8 | { 9 | "name": "Martin Laclaustra" 10 | }, 11 | "repository": 12 | { 13 | "type": "git", 14 | "url": "https://github.com/Martin-Laclaustra/CronAlarms" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=CronAlarms 2 | version=0.1.0 3 | author=Martin Laclaustra 4 | maintainer=Martin Laclaustra 5 | sentence=Schedule alarms to occur at specific times with crontab syntax. 6 | paragraph=Using expressions suitable for the program cron (crontab syntax), the library allows performing tasks at specific times or after specific intervals. It depends on ctime library, provided by SDKs. API resembles the popular TimeAlarms library. Tasks can be created to continuously repeat or to occur only once. It is a wrapper of ccronexpr. 7 | category=Timing 8 | url=https://github.com/Martin-Laclaustra/CronAlarms 9 | architectures=* 10 | 11 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | ####################################### 2 | # Syntax Coloring Map For CronAlarms 3 | ####################################### 4 | 5 | ####################################### 6 | # Datatypes (KEYWORD1) 7 | ####################################### 8 | CronId LITERAL2 9 | ####################################### 10 | # Methods and Functions (KEYWORD2) 11 | ####################################### 12 | create KEYWORD2 13 | enable KEYWORD2 14 | disable KEYWORD2 15 | free KEYWORD2 16 | delay KEYWORD2 17 | getTriggeredCronId KEYWORD2 18 | getIsServicing KEYWORD2 19 | ####################################### 20 | # Instances (KEYWORD2) 21 | ####################################### 22 | Cron KEYWORD1 23 | ####################################### 24 | # Constants (LITERAL1) 25 | ####################################### 26 | dtINVALID_ALARM_ID LITERAL1 27 | dtINVALID_TIME LITERAL1 28 | -------------------------------------------------------------------------------- /src/CronAlarms.h: -------------------------------------------------------------------------------- 1 | // CronAlarms.h - Arduino cron alarms header 2 | 3 | #ifndef CronAlarms_h 4 | #define CronAlarms_h 5 | 6 | #include 7 | #include 8 | 9 | extern "C" { 10 | #include "ccronexpr/ccronexpr.h" 11 | } 12 | 13 | #if !defined(dtNBR_ALARMS ) 14 | #if defined(__AVR__) 15 | #define dtNBR_ALARMS 6 // max is 255 16 | #elif defined(ESP8266) 17 | #define dtNBR_ALARMS 20 // for esp8266 chip - max is 255 18 | #else 19 | #define dtNBR_ALARMS 12 // assume non-AVR has more memory 20 | #endif 21 | #endif 22 | 23 | #define USE_SPECIALIST_METHODS // define this for testing 24 | 25 | typedef uint8_t CronID_t; 26 | typedef CronID_t CronId; // Arduino friendly name 27 | 28 | #define dtINVALID_ALARM_ID 255 29 | #define dtINVALID_TIME (time_t)(-1) 30 | 31 | typedef void (*OnTick_t)(); // alarm callback function typedef 32 | 33 | // class defining an alarm instance, only used by dtAlarmsClass 34 | class CronEventClass 35 | { 36 | public: 37 | CronEventClass(); 38 | void updateNextTrigger(bool forced=false); 39 | cron_expr expr; 40 | OnTick_t onTickHandler; 41 | time_t nextTrigger; 42 | bool isEnabled; // the timer is only actioned if isEnabled is true 43 | bool isOneShot; // the timer will be de-allocated after trigger is processed 44 | }; 45 | 46 | // class containing the collection of alarms 47 | class CronClass 48 | { 49 | private: 50 | CronEventClass Alarm[dtNBR_ALARMS]; 51 | uint8_t isServicing; 52 | uint8_t servicedCronId; // the alarm currently being serviced 53 | bool globalEnabled = true; 54 | void serviceAlarms(); 55 | 56 | public: 57 | CronClass(); 58 | 59 | // Function to create alarms and timers with cron 60 | CronID_t create(const char * cronstring, OnTick_t onTickHandler, bool isOneShot); 61 | // isOneShot - trigger once at the given time in the future 62 | 63 | // Function that must be evaluated often (at least once every main loop) 64 | void delay(unsigned long ms = 0); 65 | 66 | // low level methods 67 | void globalUpdateNextTrigger(); 68 | void globalenable(); // stop silencing all alarms 69 | void globaldisable(); // silence all alarms 70 | void enable(CronID_t ID); // enable the alarm to trigger 71 | void disable(CronID_t ID); // prevent the alarm from triggering 72 | CronID_t getTriggeredCronId() const; // returns the currently triggered alarm id 73 | bool getIsServicing() const; // returns isServicing 74 | 75 | void free(CronID_t ID); // free the id to allow its reuse 76 | 77 | #ifndef USE_SPECIALIST_METHODS 78 | private: // the following methods are for testing and are not documented as part of the standard library 79 | #endif 80 | uint8_t count() const; // returns the number of allocated timers 81 | time_t getNextTrigger() const; // returns the time of the next scheduled alarm 82 | time_t getNextTrigger(CronID_t ID) const; // returns the time of scheduled alarm 83 | bool isAllocated(CronID_t ID) const; // returns true if this id is allocated 84 | }; 85 | 86 | extern CronClass Cron; // make an instance for the user 87 | 88 | #endif /* CronAlarms_h */ 89 | -------------------------------------------------------------------------------- /src/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 | #define CRON_USE_LOCAL_TIME 28 | 29 | #if defined(__cplusplus) && !defined(CRON_COMPILE_AS_CXX) 30 | extern "C" { 31 | #endif 32 | 33 | #ifndef ANDROID 34 | #include 35 | #else /* ANDROID */ 36 | #include 37 | #endif /* ANDROID */ 38 | 39 | #include /*added for use if uint*_t data types*/ 40 | 41 | /** 42 | * Parsed cron expression 43 | */ 44 | typedef struct { 45 | uint8_t seconds[8]; 46 | uint8_t minutes[8]; 47 | uint8_t hours[3]; 48 | uint8_t days_of_week[1]; 49 | uint8_t days_of_month[4]; 50 | uint8_t months[2]; 51 | } cron_expr; 52 | 53 | /** 54 | * Parses specified cron expression. 55 | * 56 | * @param expression cron expression as nul-terminated string, 57 | * should be no longer that 256 bytes 58 | * @param pointer to cron expression structure, it's client code responsibility 59 | * to free/destroy it afterwards 60 | * @param error output error message, will be set to string literal 61 | * error message in case of error. Will be set to NULL on success. 62 | * The error message should NOT be freed by client. 63 | */ 64 | void cron_parse_expr(const char* expression, cron_expr* target, const char** error); 65 | 66 | /** 67 | * Uses the specified expression to calculate the next 'fire' date after 68 | * the specified date. All dates are processed as UTC (GMT) dates 69 | * without timezones information. To use local dates (current system timezone) 70 | * instead of GMT compile with '-DCRON_USE_LOCAL_TIME' 71 | * 72 | * @param expr parsed cron expression to use in next date calculation 73 | * @param date start date to start calculation from 74 | * @return next 'fire' date in case of success, '((time_t) -1)' in case of error. 75 | */ 76 | time_t cron_next(cron_expr* expr, time_t date); 77 | 78 | /** 79 | * Uses the specified expression to calculate the previous 'fire' date after 80 | * the specified date. All dates are processed as UTC (GMT) dates 81 | * without timezones information. To use local dates (current system timezone) 82 | * instead of GMT compile with '-DCRON_USE_LOCAL_TIME' 83 | * 84 | * @param expr parsed cron expression to use in previous date calculation 85 | * @param date start date to start calculation from 86 | * @return previous 'fire' date in case of success, '((time_t) -1)' in case of error. 87 | */ 88 | time_t cron_prev(cron_expr* expr, time_t date); 89 | 90 | 91 | #if defined(__cplusplus) && !defined(CRON_COMPILE_AS_CXX) 92 | } /* extern "C"*/ 93 | #endif 94 | 95 | #endif /* CCRONEXPR_H */ 96 | -------------------------------------------------------------------------------- /examples/CronAlarms_example/CronAlarms_example.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * CronAlarmsExample.ino 3 | * 4 | * This example calls alarm functions at 8:30 am and at 5:45 pm (17:45) 5 | * and simulates turning lights on at night and off in the morning 6 | * A weekly timer is set for Saturdays at 8:30:30 7 | * 8 | * A timer is called every 15 seconds 9 | * Another timer is called once only after 10 seconds 10 | * 11 | * At startup the time is set to Jan 1 2011 8:29 am 12 | */ 13 | #ifdef ESP8266 14 | #include // in order to use WiFi.mode(WIFI_OFF) 15 | #elif defined(ESP32) 16 | #include 17 | #endif 18 | 19 | #include // time() ctime() 20 | #ifdef ESP8266 21 | #include // struct timeval 22 | #endif 23 | #include "CronAlarms.h" 24 | 25 | CronId id; 26 | 27 | void setup() { 28 | #if defined(ESP8266) || defined(ESP32) 29 | WiFi.mode(WIFI_OFF); // disconect wifi to prevent NTP setting the time 30 | #endif 31 | Serial.begin(9600); 32 | while (!Serial) ; // wait for Arduino Serial Monitor 33 | Serial.println("Starting setup..."); 34 | 35 | struct tm tm_newtime; // set time to Saturday 8:29:00am Jan 1 2011 36 | tm_newtime.tm_year = 2011 - 1900; 37 | tm_newtime.tm_mon = 1 - 1; 38 | tm_newtime.tm_mday = 1; 39 | tm_newtime.tm_hour = 8; 40 | tm_newtime.tm_min = 29; 41 | tm_newtime.tm_sec = 0; 42 | tm_newtime.tm_isdst = 0; 43 | #if defined(ESP8266) || defined(ESP32) 44 | timeval tv = { mktime(&tm_newtime), 0 }; 45 | settimeofday(&tv, nullptr); 46 | #elif defined(__AVR__) 47 | set_zone(0); 48 | set_dst(0); 49 | set_system_time( mktime(&tm_newtime) ); 50 | #endif 51 | 52 | // create the alarms, to trigger at specific times 53 | Cron.create("0 30 8 * * *", MorningAlarm, false); // 8:30am every day 54 | Cron.create("0 45 17 * * *", EveningAlarm, false); // 5:45pm every day 55 | Cron.create("30 30 8 * * 6", WeeklyAlarm, false); // 8:30:30 every Saturday 56 | 57 | // create timers, to trigger relative to when they're created 58 | Cron.create("*/15 * * * * *", Repeats, false); // timer for every 15 seconds 59 | id = Cron.create("*/2 * * * * *", Repeats2, false); // timer for every 2 seconds 60 | Cron.create("*/10 * * * * *", OnceOnly, true); // called once after 10 seconds 61 | Serial.println("Ending setup..."); 62 | } 63 | 64 | void loop() { 65 | #ifdef __AVR__ 66 | system_tick(); // must be implemented at 1Hz 67 | #endif 68 | time_t tnow = time(nullptr); 69 | Serial.println(asctime(gmtime(&tnow))); 70 | Cron.delay();// if the loop has nothing else to do, delay in ms 71 | // should be provided as argument to ensure immediate 72 | // trigger when the time is reached 73 | delay(1000);// do other things... like waiting one second between clock display 74 | } 75 | 76 | // functions to be called when an alarm triggers: 77 | void MorningAlarm() { 78 | Serial.println("Alarm: - turn lights off"); 79 | } 80 | 81 | void EveningAlarm() { 82 | Serial.println("Alarm: - turn lights on"); 83 | } 84 | 85 | void WeeklyAlarm() { 86 | Serial.println("Alarm: - its Saturday Morning"); 87 | } 88 | 89 | void ExplicitAlarm() { 90 | Serial.println("Alarm: - this triggers only at the given date and time"); 91 | } 92 | 93 | void Repeats() { 94 | Serial.println("15 second timer"); 95 | } 96 | 97 | void Repeats2() { 98 | Serial.println("2 second timer"); 99 | } 100 | 101 | void OnceOnly() { 102 | Serial.println("This timer only triggers once, stop the 2 second timer"); 103 | // use Cron.free(id) to disable a timer and recycle its memory. 104 | Cron.free(id); 105 | // optional, but safest to "forget" the ID after memory recycled 106 | id = dtINVALID_ALARM_ID; 107 | // you can also use Cron.disable() to turn the timer off, but keep 108 | // it in memory, to turn back on later with Alarm.enable(). 109 | } 110 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | CronAlarms 2 | ========== 3 | Using expressions suitable for the program cron (crontab syntax), the library allows performing tasks at specific times or after specific intervals. 4 | 5 | It depends on ctime library, provided by SDKs. 6 | 7 | API resembles the popular TimeAlarms library. Tasks can be created to continuously repeat or to occur only once. It is a wrapper of ccronexpr. 8 | 9 | Usage 10 | ----- 11 | Your sketch should call the `Cron.delay()` function in the main loop. It can also replace the Arduino delay() function if a time in milliseconds is specified as argument. The timeliness of triggers depends on sketch calling this function often. Alarms are serviced in the `Cron.delay()` method. 12 | 13 | Here is how you create an alarm to trigger a task repeatedly at a particular time of day: 14 | 15 | `Cron.create("0 30 8 * * *", MorningAlarm, false);` 16 | 17 | This would call the function MorningAlarm() at 8:30 am every day. 18 | 19 | If you want the alarm to trigger only once you can set the last argument to true: 20 | 21 | `Cron.create("0 30 8 * * *", MorningAlarm, true);` 22 | 23 | This calls a MorningAlarm() function in a sketch once only (when the time is next 8:30am) 24 | 25 | Alarms can be specified to trigger a task repeatedly at a particular day of week and time of day: 26 | 27 | `Cron.create("0 15 9 * * 6", WeeklyAlarm, false);` 28 | 29 | This would call the function WeeklyAlarm() at 9:15am every Saturday. 30 | 31 | If you want the alarm to trigger once only on a particular day and time you can do this: 32 | 33 | `Cron.create("0 15 9 * * 6", SaturdayMorning, true);` 34 | 35 | This would call the function SaturdayMorning() Alarm on the next Saturday at 9:15am. 36 | 37 | Periodicity can trigger tasks that occur after a specified interval of time has passed. 38 | Intervals can be in fractions of minutes, of hours, or of days. 39 | 40 | `Cron.create("*/15 * * * * *", Repeats, false);` 41 | 42 | This calls the Repeats() function in your sketch every 15 seconds. 43 | Please take into account that only round fractions work well. This is a limitation of cron. 44 | 45 | If you want an action to happen once only, this might not be the optimal library, although you can define the next fraction of time in which it will occur. 46 | 47 | `Cron.create("*/10 * * * * *", OnceOnly, true);` 48 | 49 | This calls the onceOnly() function the next time seconds in the clock reaches a multiple of 10, after the timer is created. 50 | 51 | You can also set specific dates and times within a year, i.e. noon of 4th July. 52 | 53 | `Cron.create("0 0 12 4 7 *", Celebration, true);` 54 | 55 | Other low level functions: 56 | - disable( ID); - prevent the alarm associated with the given ID from triggering 57 | - enable(ID); - enable the alarm 58 | - getTriggeredAlarmId(); - returns the currently triggered alarm id, only valid in an alarm callback 59 | 60 | - globalUpdateNextTrigger(), globalenable(), and globaldisable() - can be used to temporarily suspend activity during timesetting or time zone change 61 | 62 | FAQ 63 | --- 64 | _Q: What hardware and software is needed to use this library?_ 65 | 66 | A: This library requires an SDK with a ctime implementation. No internal or external hardware is used by the Alarm library. 67 | 68 | _Q: Why must I use Cron.delay() instead of delay()?_ 69 | 70 | A: Task scheduling is handled in the Cron.delay function. 71 | Tasks are monitored and triggered from within the Cron.delay call so Cron.delay should be called whenever a delay is required in your sketch. 72 | If your sketch waits on an external event (for example, a sensor change), make sure you repeatedly call Cron.delay while checking the sensor. 73 | You can call Cron.delay() if you need to service the scheduler without a delay. 74 | 75 | _Q: Are there any restrictions on the code in a task handler function?_ 76 | 77 | A: No. The scheduler does not use interrupts so your task handling function is no different from other functions you create in your sketch. 78 | 79 | _Q: What are the intervals that can be scheduled?_ 80 | 81 | A: You can find an introduction to crontab here: 82 | https://en.wikipedia.org/wiki/Cron#CRON_expression 83 | 84 | (If you need timer intervals shorter than 1 second then you should look for a different library) 85 | 86 | _Q: How are scheduled tasks affected if the system time is changed?_ 87 | 88 | A: Tasks are scheduled for specific times designated by the system clock. If the system time is reset to a later time (for example one hour ahead) then all alarms will occur one hour later. 89 | 90 | If the system time is set backwards (for example one hour back) then the alarms will occur an hour earlier. 91 | 92 | If the time is reset before the time a task was scheduled, then the task will be triggered on the next service (the next call to Cron.delay). 93 | This is the expected behaviour for Alarms (tasks scheduled for a specific time of day will trigger at that time), but the affect on task for subfractions may not be intuitive. If a timer is scheduled to trigger in 5 minutes time and the clock is set ahead by one hour, that timer will not trigger until one hour and 5 minutes has elapsed. 94 | 95 | _Q: How many alarms can be created?_ 96 | 97 | A: It depends on the system. Up to six alarms can be scheduled in Arduino. 98 | The number of alarms can be changed in the CronAlarms header file (set by the constant dtNBR_ALARMS) 99 | 100 | onceOnly Alarms are freed when they are triggered so another onceOnly alarm can be set to trigger again. 101 | 102 | There is no limit to the number of times a onceOnly alarm can be reset. 103 | 104 | -------------------------------------------------------------------------------- /src/CronAlarms.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | CronAlarms.cpp - Arduino cron alarms 3 | Copyright (c) 2019 Martin Laclaustra 4 | 5 | This library is free software; you can redistribute it and/or 6 | modify it under the terms of the GNU Lesser General Public 7 | License as published by the Free Software Foundation; either 8 | version 3 of the License, or (at your option) any later version. 9 | 10 | This library is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | Lesser General Public License for more details. 14 | 15 | This is a wrapper of ccronexpr 16 | Copyright 2015, alex at staticlibs.net 17 | Licensed under the Apache License, Version 2.0 18 | https://github.com/staticlibs/ccronexpr 19 | 20 | API and implementation are inspired in TimeAlarms 21 | Copyright 2008-2011 Michael Margolis, maintainer:Paul Stoffregen 22 | GNU Lesser General Public License, Version 2.1 or later 23 | https://github.com/PaulStoffregen/TimeAlarms 24 | */ 25 | 26 | #include "CronAlarms.h" 27 | 28 | extern "C" { 29 | #include "ccronexpr/ccronexpr.h" 30 | } 31 | 32 | 33 | //************************************************************** 34 | //* Cron Event Class Constructor 35 | 36 | CronEventClass::CronEventClass() 37 | { 38 | memset(&expr, 0, sizeof(expr)); 39 | onTickHandler = NULL; // prevent a callback until this pointer is explicitly set 40 | nextTrigger = 0; 41 | isEnabled = isOneShot = false; 42 | } 43 | 44 | 45 | //************************************************************** 46 | //* Cron Event Class Methods 47 | 48 | void CronEventClass::updateNextTrigger(bool forced) 49 | { 50 | if (isEnabled) { 51 | time_t timenow = time(nullptr); 52 | if (onTickHandler != NULL && ((nextTrigger <= timenow) || forced)) { 53 | // update alarm if next trigger is not yet in the future 54 | nextTrigger = cron_next(&expr, timenow); 55 | } 56 | } 57 | } 58 | 59 | //************************************************************** 60 | //* Cron Class Public Methods 61 | 62 | CronClass::CronClass() 63 | { 64 | isServicing = false; 65 | for(uint8_t id = 0; id < dtNBR_ALARMS; id++) { 66 | free(id); // ensure all Alarms are cleared and available for allocation 67 | } 68 | } 69 | 70 | void CronClass::globalUpdateNextTrigger() 71 | { 72 | for (uint8_t eachCronId = 0; eachCronId < dtNBR_ALARMS; eachCronId++) { 73 | if (Alarm[eachCronId].isEnabled) { 74 | Alarm[eachCronId].updateNextTrigger(true); 75 | } 76 | } 77 | } 78 | 79 | void CronClass::globalenable() 80 | { 81 | globalUpdateNextTrigger(); 82 | globalEnabled = true; 83 | } 84 | 85 | void CronClass::globaldisable() 86 | { 87 | globalEnabled = false; 88 | } 89 | 90 | void CronClass::enable(CronID_t ID) 91 | { 92 | if (isAllocated(ID)) { 93 | Alarm[ID].isEnabled = true; 94 | Alarm[ID].updateNextTrigger(); 95 | } 96 | } 97 | 98 | void CronClass::disable(CronID_t ID) 99 | { 100 | if (isAllocated(ID)) { 101 | Alarm[ID].isEnabled = false; 102 | } 103 | } 104 | 105 | void CronClass::free(CronID_t ID) 106 | { 107 | if (isAllocated(ID)) { 108 | memset(&(Alarm[ID].expr), 0, sizeof(Alarm[ID].expr)); 109 | Alarm[ID].onTickHandler = NULL; 110 | Alarm[ID].nextTrigger = 0; 111 | Alarm[ID].isEnabled = false; 112 | Alarm[ID].isOneShot = false; 113 | } 114 | } 115 | 116 | // returns the number of allocated timers 117 | uint8_t CronClass::count() const 118 | { 119 | uint8_t c = 0; 120 | for(uint8_t id = 0; id < dtNBR_ALARMS; id++) { 121 | if (isAllocated(id)) c++; 122 | } 123 | return c; 124 | } 125 | 126 | // returns true if this id is allocated 127 | bool CronClass::isAllocated(CronID_t ID) const 128 | { 129 | return (ID < dtNBR_ALARMS && Alarm[ID].onTickHandler != NULL); 130 | } 131 | 132 | // returns the currently triggered alarm id 133 | // returns dtINVALID_ALARM_ID if not invoked from within an alarm handler 134 | CronID_t CronClass::getTriggeredCronId() const 135 | { 136 | if (isServicing) { 137 | return servicedCronId; // new private data member used instead of local loop variable i in serviceAlarms(); 138 | } else { 139 | return dtINVALID_ALARM_ID; // valid ids only available when servicing a callback 140 | } 141 | } 142 | 143 | // following functions are not Alarm ID specific. 144 | void CronClass::delay(unsigned long ms) 145 | { 146 | unsigned long start = millis(); 147 | do { 148 | serviceAlarms(); 149 | yield(); 150 | } while (millis() - start <= ms); 151 | } 152 | 153 | //returns isServicing 154 | bool CronClass::getIsServicing() const 155 | { 156 | return isServicing; 157 | } 158 | 159 | //*********************************************************** 160 | //* Private Methods 161 | 162 | void CronClass::serviceAlarms() 163 | { 164 | if (globalEnabled && !isServicing) { 165 | isServicing = true; 166 | for (servicedCronId = 0; servicedCronId < dtNBR_ALARMS; servicedCronId++) { 167 | if (Alarm[servicedCronId].isEnabled && (time(nullptr) >= Alarm[servicedCronId].nextTrigger)) { 168 | OnTick_t TickHandler = Alarm[servicedCronId].onTickHandler; 169 | if (Alarm[servicedCronId].isOneShot) { 170 | free(servicedCronId); // free the ID if mode is OnShot 171 | } else { 172 | Alarm[servicedCronId].updateNextTrigger(); 173 | } 174 | if (TickHandler != NULL) { 175 | (*TickHandler)(); // call the handler 176 | } 177 | } 178 | } 179 | isServicing = false; 180 | } 181 | } 182 | 183 | // returns the absolute time of the next scheduled alarm, or 0 if none 184 | time_t CronClass::getNextTrigger() const 185 | { 186 | time_t nextTrigger = 0; 187 | 188 | for (uint8_t id = 0; id < dtNBR_ALARMS; id++) { 189 | if (isAllocated(id)) { 190 | if (nextTrigger == 0) { 191 | nextTrigger = Alarm[id].nextTrigger; 192 | } 193 | else if (Alarm[id].nextTrigger < nextTrigger) { 194 | nextTrigger = Alarm[id].nextTrigger; 195 | } 196 | } 197 | } 198 | return nextTrigger; 199 | } 200 | 201 | time_t CronClass::getNextTrigger(CronID_t ID) const 202 | { 203 | if (isAllocated(ID)) { 204 | return Alarm[ID].nextTrigger; 205 | } else { 206 | return 0; 207 | } 208 | } 209 | 210 | // attempt to create a cron alarm and return CronID if successful 211 | CronID_t CronClass::create(const char * cronstring, OnTick_t onTickHandler, bool isOneShot) 212 | { 213 | for (uint8_t id = 0; id < dtNBR_ALARMS; id++) { 214 | if (!isAllocated(id)) { 215 | // here if there is an Alarm id that is not allocated 216 | const char* err = NULL; 217 | memset(&(Alarm[id].expr), 0, sizeof(Alarm[id].expr)); 218 | cron_parse_expr(cronstring, &(Alarm[id].expr), &err); 219 | if (err) { 220 | memset(&(Alarm[id].expr), 0, sizeof(Alarm[id].expr)); 221 | return dtINVALID_ALARM_ID; 222 | } 223 | Alarm[id].onTickHandler = onTickHandler; 224 | Alarm[id].isOneShot = isOneShot; 225 | enable(id); 226 | return id; // alarm created ok 227 | } 228 | } 229 | return dtINVALID_ALARM_ID; // no IDs available or time is invalid 230 | } 231 | 232 | // make one instance for the user to use 233 | CronClass Cron = CronClass() ; 234 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /src/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_MAX_SECONDS 60 35 | #define CRON_MAX_MINUTES 60 36 | #define CRON_MAX_HOURS 24 37 | #define CRON_MAX_DAYS_OF_WEEK 8 38 | #define CRON_MAX_DAYS_OF_MONTH 32 39 | #define CRON_MAX_MONTHS 12 40 | #define CRON_MAX_YEARS_DIFF 4 41 | 42 | #define CRON_CF_SECOND 0 43 | #define CRON_CF_MINUTE 1 44 | #define CRON_CF_HOUR_OF_DAY 2 45 | #define CRON_CF_DAY_OF_WEEK 3 46 | #define CRON_CF_DAY_OF_MONTH 4 47 | #define CRON_CF_MONTH 5 48 | #define CRON_CF_YEAR 6 49 | 50 | #define CRON_CF_ARR_LEN 7 51 | 52 | #define CRON_INVALID_INSTANT ((time_t) -1) 53 | 54 | static const char* const DAYS_ARR[] = { "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" }; 55 | #define CRON_DAYS_ARR_LEN 7 56 | static const char* const MONTHS_ARR[] = { "FOO", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" }; 57 | #define CRON_MONTHS_ARR_LEN 13 58 | 59 | #define CRON_MAX_STR_LEN_TO_SPLIT 256 60 | #define CRON_MAX_NUM_TO_SRING 1000000000 61 | /* computes number of digits in decimal number */ 62 | #define CRON_NUM_OF_DIGITS(num) (abs(num) < 10 ? 1 : \ 63 | (abs(num) < 100 ? 2 : \ 64 | (abs(num) < 1000 ? 3 : \ 65 | (abs(num) < 10000 ? 4 : \ 66 | (abs(num) < 100000 ? 5 : \ 67 | (abs(num) < 1000000 ? 6 : \ 68 | (abs(num) < 10000000 ? 7 : \ 69 | (abs(num) < 100000000 ? 8 : \ 70 | (abs(num) < 1000000000 ? 9 : 10))))))))) 71 | 72 | #ifndef CRON_TEST_MALLOC 73 | #define cron_malloc(x) malloc(x); 74 | #define cron_free(x) free(x); 75 | #else /* CRON_TEST_MALLOC */ 76 | void* cron_malloc(size_t n); 77 | void cron_free(void* p); 78 | #endif /* CRON_TEST_MALLOC */ 79 | 80 | /** 81 | * Time functions from standard library. 82 | * This part defines: cron_mktime: create time_t from tm 83 | * cron_time: create tm from time_t 84 | */ 85 | 86 | /* forward declarations for platforms that may need them */ 87 | /* can be hidden in time.h */ 88 | #if !defined(_WIN32) && !defined(__AVR__) && !defined(ESP8266) && !defined(ANDROID) 89 | struct tm *gmtime_r(const time_t *timep, struct tm *result); 90 | time_t timegm(struct tm* __tp); 91 | struct tm *localtime_r(const time_t *timep, struct tm *result); 92 | #endif /* PLEASE CHECK _WIN32 AND ANDROID NEEDS FOR THESE DECLARATIONS */ 93 | #ifdef __MINGW32__ 94 | /* To avoid warning when building with mingw */ 95 | time_t _mkgmtime(struct tm* tm); 96 | #endif /* __MINGW32__ */ 97 | 98 | /* function definitions */ 99 | time_t cron_mktime_gm(struct tm* tm) { 100 | #if defined(_WIN32) 101 | /* http://stackoverflow.com/a/22557778 */ 102 | return _mkgmtime(tm); 103 | #elif defined(__AVR__) 104 | /* https://www.nongnu.org/avr-libc/user-manual/group__avr__time.html */ 105 | return mk_gmtime(tm); 106 | #elif defined(ESP8266) 107 | /* https://linux.die.net/man/3/timegm */ 108 | /* http://www.catb.org/esr/time-programming/ */ 109 | /* portable version of timegm() */ 110 | time_t ret; 111 | char *tz; 112 | tz = getenv("TZ"); 113 | if (tz) 114 | tz = strdup(tz); 115 | setenv("TZ", "UTC+0", 1); 116 | tzset(); 117 | ret = mktime(tm); 118 | if (tz) { 119 | setenv("TZ", tz, 1); 120 | free(tz); 121 | } else 122 | unsetenv("TZ"); 123 | tzset(); 124 | return ret; 125 | #elif defined(ANDROID) 126 | /* https://github.com/adobe/chromium/blob/cfe5bf0b51b1f6b9fe239c2a3c2f2364da9967d7/base/os_compat_android.cc#L20 */ 127 | static const time_t kTimeMax = ~(1L << (sizeof (time_t) * CHAR_BIT - 1)); 128 | static const time_t kTimeMin = (1L << (sizeof (time_t) * CHAR_BIT - 1)); 129 | time64_t result = timegm64(tm); 130 | if (result < kTimeMin || result > kTimeMax) return -1; 131 | return result; 132 | #else 133 | return timegm(tm); 134 | #endif 135 | } 136 | 137 | struct tm* cron_time_gm(time_t* date, struct tm* out) { 138 | #if defined(__MINGW32__) 139 | (void)(out); /* To avoid unused warning */ 140 | return gmtime(date); 141 | #elif defined(_WIN32) 142 | errno_t err = gmtime_s(out, date); 143 | return 0 == err ? out : NULL; 144 | #elif defined(__AVR__) 145 | /* https://www.nongnu.org/avr-libc/user-manual/group__avr__time.html */ 146 | gmtime_r(date, out); 147 | return out; 148 | #else 149 | return gmtime_r(date, out); 150 | #endif 151 | } 152 | 153 | time_t cron_mktime_local(struct tm* tm) { 154 | tm->tm_isdst = -1; 155 | return mktime(tm); 156 | } 157 | 158 | struct tm* cron_time_local(time_t* date, struct tm* out) { 159 | #if defined(_WIN32) 160 | errno_t err = localtime_s(out, date); 161 | return 0 == err ? out : NULL; 162 | #elif defined(__AVR__) 163 | /* https://www.nongnu.org/avr-libc/user-manual/group__avr__time.html */ 164 | localtime_r(date, out); 165 | return out; 166 | #else 167 | return localtime_r(date, out); 168 | #endif 169 | } 170 | 171 | /* Defining 'cron_' time functions to use use UTC (default) or local time */ 172 | #ifndef CRON_USE_LOCAL_TIME 173 | time_t cron_mktime(struct tm* tm) { 174 | return cron_mktime_gm(tm); 175 | } 176 | 177 | struct tm* cron_time(time_t* date, struct tm* out) { 178 | return cron_time_gm(date, out); 179 | } 180 | 181 | #else /* CRON_USE_LOCAL_TIME */ 182 | time_t cron_mktime(struct tm* tm) { 183 | return cron_mktime_local(tm); 184 | } 185 | 186 | struct tm* cron_time(time_t* date, struct tm* out) { 187 | return cron_time_local(date, out); 188 | } 189 | 190 | #endif /* CRON_USE_LOCAL_TIME */ 191 | 192 | /** 193 | * Functions. 194 | */ 195 | 196 | void cron_set_bit(uint8_t* rbyte, int idx) { 197 | uint8_t j = (uint8_t) (idx / 8); 198 | uint8_t k = (uint8_t) (idx % 8); 199 | 200 | rbyte[j] |= (1 << k); 201 | } 202 | 203 | void cron_del_bit(uint8_t* rbyte, int idx) { 204 | uint8_t j = (uint8_t) (idx / 8); 205 | uint8_t k = (uint8_t) (idx % 8); 206 | 207 | rbyte[j] &= ~(1 << k); 208 | } 209 | 210 | uint8_t cron_get_bit(uint8_t* rbyte, int idx) { 211 | uint8_t j = (uint8_t) (idx / 8); 212 | uint8_t k = (uint8_t) (idx % 8); 213 | 214 | if (rbyte[j] & (1 << k)) { 215 | return 1; 216 | } else { 217 | return 0; 218 | } 219 | } 220 | 221 | static void free_splitted(char** splitted, size_t len) { 222 | size_t i; 223 | if (!splitted) return; 224 | for (i = 0; i < len; i++) { 225 | if (splitted[i]) { 226 | cron_free(splitted[i]); 227 | } 228 | } 229 | cron_free(splitted); 230 | } 231 | 232 | static char* strdupl(const char* str, size_t len) { 233 | if (!str) return NULL; 234 | char* res = (char*) cron_malloc(len + 1); 235 | if (!res) return NULL; 236 | memset(res, 0, len + 1); 237 | memcpy(res, str, len); 238 | return res; 239 | } 240 | 241 | static unsigned int next_set_bit(uint8_t* bits, unsigned int max, unsigned int from_index, int* notfound) { 242 | unsigned int i; 243 | if (!bits) { 244 | *notfound = 1; 245 | return 0; 246 | } 247 | for (i = from_index; i < max; i++) { 248 | if (cron_get_bit(bits, i)) return i; 249 | } 250 | *notfound = 1; 251 | return 0; 252 | } 253 | 254 | static void push_to_fields_arr(int* arr, int fi) { 255 | int i; 256 | if (!arr || -1 == fi) { 257 | return; 258 | } 259 | for (i = 0; i < CRON_CF_ARR_LEN; i++) { 260 | if (arr[i] == fi) return; 261 | } 262 | for (i = 0; i < CRON_CF_ARR_LEN; i++) { 263 | if (-1 == arr[i]) { 264 | arr[i] = fi; 265 | return; 266 | } 267 | } 268 | } 269 | 270 | static int add_to_field(struct tm* calendar, int field, int val) { 271 | if (!calendar || -1 == field) { 272 | return 1; 273 | } 274 | switch (field) { 275 | case CRON_CF_SECOND: 276 | calendar->tm_sec = calendar->tm_sec + val; 277 | break; 278 | case CRON_CF_MINUTE: 279 | calendar->tm_min = calendar->tm_min + val; 280 | break; 281 | case CRON_CF_HOUR_OF_DAY: 282 | calendar->tm_hour = calendar->tm_hour + val; 283 | break; 284 | case CRON_CF_DAY_OF_WEEK: /* mkgmtime ignores this field */ 285 | case CRON_CF_DAY_OF_MONTH: 286 | calendar->tm_mday = calendar->tm_mday + val; 287 | break; 288 | case CRON_CF_MONTH: 289 | calendar->tm_mon = calendar->tm_mon + val; 290 | break; 291 | case CRON_CF_YEAR: 292 | calendar->tm_year = calendar->tm_year + val; 293 | break; 294 | default: 295 | return 1; /* unknown field */ 296 | } 297 | time_t res = cron_mktime(calendar); 298 | if (CRON_INVALID_INSTANT == res) { 299 | return 1; 300 | } 301 | return 0; 302 | } 303 | 304 | /** 305 | * Reset the calendar setting all the fields provided to zero. 306 | */ 307 | static int reset_min(struct tm* calendar, int field) { 308 | if (!calendar || -1 == field) { 309 | return 1; 310 | } 311 | switch (field) { 312 | case CRON_CF_SECOND: 313 | calendar->tm_sec = 0; 314 | break; 315 | case CRON_CF_MINUTE: 316 | calendar->tm_min = 0; 317 | break; 318 | case CRON_CF_HOUR_OF_DAY: 319 | calendar->tm_hour = 0; 320 | break; 321 | case CRON_CF_DAY_OF_WEEK: 322 | calendar->tm_wday = 0; 323 | break; 324 | case CRON_CF_DAY_OF_MONTH: 325 | calendar->tm_mday = 1; 326 | break; 327 | case CRON_CF_MONTH: 328 | calendar->tm_mon = 0; 329 | break; 330 | case CRON_CF_YEAR: 331 | calendar->tm_year = 0; 332 | break; 333 | default: 334 | return 1; /* unknown field */ 335 | } 336 | time_t res = cron_mktime(calendar); 337 | if (CRON_INVALID_INSTANT == res) { 338 | return 1; 339 | } 340 | return 0; 341 | } 342 | 343 | static int reset_all_min(struct tm* calendar, int* fields) { 344 | int i; 345 | int res = 0; 346 | if (!calendar || !fields) { 347 | return 1; 348 | } 349 | for (i = 0; i < CRON_CF_ARR_LEN; i++) { 350 | if (-1 != fields[i]) { 351 | res = reset_min(calendar, fields[i]); 352 | if (0 != res) return res; 353 | } 354 | } 355 | return 0; 356 | } 357 | 358 | static int set_field(struct tm* calendar, int field, int val) { 359 | if (!calendar || -1 == field) { 360 | return 1; 361 | } 362 | switch (field) { 363 | case CRON_CF_SECOND: 364 | calendar->tm_sec = val; 365 | break; 366 | case CRON_CF_MINUTE: 367 | calendar->tm_min = val; 368 | break; 369 | case CRON_CF_HOUR_OF_DAY: 370 | calendar->tm_hour = val; 371 | break; 372 | case CRON_CF_DAY_OF_WEEK: 373 | calendar->tm_wday = val; 374 | break; 375 | case CRON_CF_DAY_OF_MONTH: 376 | calendar->tm_mday = val; 377 | break; 378 | case CRON_CF_MONTH: 379 | calendar->tm_mon = val; 380 | break; 381 | case CRON_CF_YEAR: 382 | calendar->tm_year = val; 383 | break; 384 | default: 385 | return 1; /* unknown field */ 386 | } 387 | time_t res = cron_mktime(calendar); 388 | if (CRON_INVALID_INSTANT == res) { 389 | return 1; 390 | } 391 | return 0; 392 | } 393 | 394 | /** 395 | * Search the bits provided for the next set bit after the value provided, 396 | * and reset the calendar. 397 | */ 398 | 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) { 399 | int notfound = 0; 400 | int err = 0; 401 | unsigned int next_value = next_set_bit(bits, max, value, ¬found); 402 | /* roll over if needed */ 403 | if (notfound) { 404 | err = add_to_field(calendar, nextField, 1); 405 | if (err) goto return_error; 406 | err = reset_min(calendar, field); 407 | if (err) goto return_error; 408 | notfound = 0; 409 | next_value = next_set_bit(bits, max, 0, ¬found); 410 | } 411 | if (notfound || next_value != value) { 412 | err = set_field(calendar, field, next_value); 413 | if (err) goto return_error; 414 | err = reset_all_min(calendar, lower_orders); 415 | if (err) goto return_error; 416 | } 417 | return next_value; 418 | 419 | return_error: 420 | *res_out = 1; 421 | return 0; 422 | } 423 | 424 | 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) { 425 | int err; 426 | unsigned int count = 0; 427 | unsigned int max = 366; 428 | while ((!cron_get_bit(days_of_month, day_of_month) || !cron_get_bit(days_of_week, day_of_week)) && count++ < max) { 429 | err = add_to_field(calendar, CRON_CF_DAY_OF_MONTH, 1); 430 | 431 | if (err) goto return_error; 432 | day_of_month = calendar->tm_mday; 433 | day_of_week = calendar->tm_wday; 434 | reset_all_min(calendar, resets); 435 | } 436 | return day_of_month; 437 | 438 | return_error: 439 | *res_out = 1; 440 | return 0; 441 | } 442 | 443 | static int do_next(cron_expr* expr, struct tm* calendar, unsigned int dot) { 444 | int i; 445 | int res = 0; 446 | int* resets = NULL; 447 | int* empty_list = NULL; 448 | unsigned int second = 0; 449 | unsigned int update_second = 0; 450 | unsigned int minute = 0; 451 | unsigned int update_minute = 0; 452 | unsigned int hour = 0; 453 | unsigned int update_hour = 0; 454 | unsigned int day_of_week = 0; 455 | unsigned int day_of_month = 0; 456 | unsigned int update_day_of_month = 0; 457 | unsigned int month = 0; 458 | unsigned int update_month = 0; 459 | 460 | resets = (int*) cron_malloc(CRON_CF_ARR_LEN * sizeof(int)); 461 | if (!resets) goto return_result; 462 | empty_list = (int*) cron_malloc(CRON_CF_ARR_LEN * sizeof(int)); 463 | if (!empty_list) goto return_result; 464 | for (i = 0; i < CRON_CF_ARR_LEN; i++) { 465 | resets[i] = -1; 466 | empty_list[i] = -1; 467 | } 468 | 469 | second = calendar->tm_sec; 470 | update_second = find_next(expr->seconds, CRON_MAX_SECONDS, second, calendar, CRON_CF_SECOND, CRON_CF_MINUTE, empty_list, &res); 471 | if (0 != res) goto return_result; 472 | if (second == update_second) { 473 | push_to_fields_arr(resets, CRON_CF_SECOND); 474 | } 475 | 476 | minute = calendar->tm_min; 477 | update_minute = find_next(expr->minutes, CRON_MAX_MINUTES, minute, calendar, CRON_CF_MINUTE, CRON_CF_HOUR_OF_DAY, resets, &res); 478 | if (0 != res) goto return_result; 479 | if (minute == update_minute) { 480 | push_to_fields_arr(resets, CRON_CF_MINUTE); 481 | } else { 482 | res = do_next(expr, calendar, dot); 483 | if (0 != res) goto return_result; 484 | } 485 | 486 | hour = calendar->tm_hour; 487 | update_hour = find_next(expr->hours, CRON_MAX_HOURS, hour, calendar, CRON_CF_HOUR_OF_DAY, CRON_CF_DAY_OF_WEEK, resets, &res); 488 | if (0 != res) goto return_result; 489 | if (hour == update_hour) { 490 | push_to_fields_arr(resets, CRON_CF_HOUR_OF_DAY); 491 | } else { 492 | res = do_next(expr, calendar, dot); 493 | if (0 != res) goto return_result; 494 | } 495 | 496 | day_of_week = calendar->tm_wday; 497 | day_of_month = calendar->tm_mday; 498 | update_day_of_month = find_next_day(calendar, expr->days_of_month, day_of_month, expr->days_of_week, day_of_week, resets, &res); 499 | if (0 != res) goto return_result; 500 | if (day_of_month == update_day_of_month) { 501 | push_to_fields_arr(resets, CRON_CF_DAY_OF_MONTH); 502 | } else { 503 | res = do_next(expr, calendar, dot); 504 | if (0 != res) goto return_result; 505 | } 506 | 507 | month = calendar->tm_mon; /*day already adds one if no day in same month is found*/ 508 | update_month = find_next(expr->months, CRON_MAX_MONTHS, month, calendar, CRON_CF_MONTH, CRON_CF_YEAR, resets, &res); 509 | if (0 != res) goto return_result; 510 | if (month != update_month) { 511 | if (calendar->tm_year - dot > 4) { 512 | res = -1; 513 | goto return_result; 514 | } 515 | res = do_next(expr, calendar, dot); 516 | if (0 != res) goto return_result; 517 | } 518 | goto return_result; 519 | 520 | return_result: 521 | if (!resets || !empty_list) { 522 | res = -1; 523 | } 524 | if (resets) { 525 | cron_free(resets); 526 | } 527 | if (empty_list) { 528 | cron_free(empty_list); 529 | } 530 | return res; 531 | } 532 | 533 | static int to_upper(char* str) { 534 | if (!str) return 1; 535 | int i; 536 | for (i = 0; '\0' != str[i]; i++) { 537 | int c = (int)str[i]; 538 | str[i] = (char) toupper(c); 539 | } 540 | return 0; 541 | } 542 | 543 | static char* to_string(int num) { 544 | if (abs(num) >= CRON_MAX_NUM_TO_SRING) return NULL; 545 | char* str = (char*) cron_malloc(CRON_NUM_OF_DIGITS(num) + 1); 546 | if (!str) return NULL; 547 | int res = sprintf(str, "%d", num); 548 | if (res < 0) { 549 | cron_free(str); 550 | return NULL; 551 | } 552 | return str; 553 | } 554 | 555 | static char* str_replace(char *orig, const char *rep, const char *with) { 556 | char *result; /* the return string */ 557 | char *ins; /* the next insert point */ 558 | char *tmp; /* varies */ 559 | size_t len_rep; /* length of rep */ 560 | size_t len_with; /* length of with */ 561 | size_t len_front; /* distance between rep and end of last rep */ 562 | int count; /* number of replacements */ 563 | if (!orig) return NULL; 564 | if (!rep) rep = ""; 565 | if (!with) with = ""; 566 | len_rep = strlen(rep); 567 | len_with = strlen(with); 568 | 569 | ins = orig; 570 | for (count = 0; NULL != (tmp = strstr(ins, rep)); ++count) { 571 | ins = tmp + len_rep; 572 | } 573 | 574 | /* first time through the loop, all the variable are set correctly 575 | from here on, 576 | tmp points to the end of the result string 577 | ins points to the next occurrence of rep in orig 578 | orig points to the remainder of orig after "end of rep" 579 | */ 580 | tmp = result = (char*) cron_malloc(strlen(orig) + (len_with - len_rep) * count + 1); 581 | if (!result) return NULL; 582 | 583 | while (count--) { 584 | ins = strstr(orig, rep); 585 | len_front = ins - orig; 586 | tmp = strncpy(tmp, orig, len_front) + len_front; 587 | tmp = strcpy(tmp, with) + len_with; 588 | orig += len_front + len_rep; /* move to next "end of rep" */ 589 | } 590 | strcpy(tmp, orig); 591 | return result; 592 | } 593 | 594 | static unsigned int parse_uint(const char* str, int* errcode) { 595 | char* endptr; 596 | errno = 0; 597 | long int l = strtol(str, &endptr, 0); 598 | if (errno == ERANGE || *endptr != '\0' || l < 0 || l > INT_MAX) { 599 | *errcode = 1; 600 | return 0; 601 | } else { 602 | *errcode = 0; 603 | return (unsigned int) l; 604 | } 605 | } 606 | 607 | static char** split_str(const char* str, char del, size_t* len_out) { 608 | size_t i; 609 | size_t stlen = 0; 610 | size_t len = 0; 611 | int accum = 0; 612 | char* buf = NULL; 613 | char** res = NULL; 614 | size_t bi = 0; 615 | size_t ri = 0; 616 | char* tmp; 617 | 618 | if (!str) goto return_error; 619 | for (i = 0; '\0' != str[i]; i++) { 620 | stlen += 1; 621 | if (stlen >= CRON_MAX_STR_LEN_TO_SPLIT) goto return_error; 622 | } 623 | 624 | for (i = 0; i < stlen; i++) { 625 | int c = str[i]; 626 | if (del == str[i]) { 627 | if (accum > 0) { 628 | len += 1; 629 | accum = 0; 630 | } 631 | } else if (!isspace(c)) { 632 | accum += 1; 633 | } 634 | } 635 | /* tail */ 636 | if (accum > 0) { 637 | len += 1; 638 | } 639 | if (0 == len) return NULL; 640 | 641 | buf = (char*) cron_malloc(stlen + 1); 642 | if (!buf) goto return_error; 643 | memset(buf, 0, stlen + 1); 644 | res = (char**) cron_malloc(len * sizeof(char*)); 645 | if (!res) goto return_error; 646 | memset(res, 0, len * sizeof(char*)); 647 | 648 | for (i = 0; i < stlen; i++) { 649 | int c = str[i]; 650 | if (del == str[i]) { 651 | if (bi > 0) { 652 | tmp = strdupl(buf, bi); 653 | if (!tmp) goto return_error; 654 | res[ri++] = tmp; 655 | memset(buf, 0, stlen + 1); 656 | bi = 0; 657 | } 658 | } else if (!isspace(c)) { 659 | buf[bi++] = str[i]; 660 | } 661 | } 662 | /* tail */ 663 | if (bi > 0) { 664 | tmp = strdupl(buf, bi); 665 | if (!tmp) goto return_error; 666 | res[ri++] = tmp; 667 | } 668 | cron_free(buf); 669 | *len_out = len; 670 | return res; 671 | 672 | return_error: 673 | if (buf) { 674 | cron_free(buf); 675 | } 676 | free_splitted(res, len); 677 | *len_out = 0; 678 | return NULL; 679 | } 680 | 681 | static char* replace_ordinals(char* value, const char* const * arr, size_t arr_len) { 682 | size_t i; 683 | char* cur = value; 684 | char* res = NULL; 685 | int first = 1; 686 | for (i = 0; i < arr_len; i++) { 687 | char* strnum = to_string((int) i); 688 | if (!strnum) { 689 | if (!first) { 690 | cron_free(cur); 691 | } 692 | return NULL; 693 | } 694 | res = str_replace(cur, arr[i], strnum); 695 | cron_free(strnum); 696 | if (!first) { 697 | cron_free(cur); 698 | } 699 | if (!res) { 700 | return NULL; 701 | } 702 | cur = res; 703 | if (first) { 704 | first = 0; 705 | } 706 | } 707 | return res; 708 | } 709 | 710 | static int has_char(char* str, char ch) { 711 | size_t i; 712 | size_t len = 0; 713 | if (!str) return 0; 714 | len = strlen(str); 715 | for (i = 0; i < len; i++) { 716 | if (str[i] == ch) return 1; 717 | } 718 | return 0; 719 | } 720 | 721 | static unsigned int* get_range(char* field, unsigned int min, unsigned int max, const char** error) { 722 | 723 | char** parts = NULL; 724 | size_t len = 0; 725 | unsigned int* res = (unsigned int*) cron_malloc(2 * sizeof(unsigned int)); 726 | if (!res) goto return_error; 727 | 728 | res[0] = 0; 729 | res[1] = 0; 730 | if (1 == strlen(field) && '*' == field[0]) { 731 | res[0] = min; 732 | res[1] = max - 1; 733 | } else if (!has_char(field, '-')) { 734 | int err = 0; 735 | unsigned int val = parse_uint(field, &err); 736 | if (err) { 737 | *error = "Unsigned integer parse error 1"; 738 | goto return_error; 739 | } 740 | 741 | res[0] = val; 742 | res[1] = val; 743 | } else { 744 | parts = split_str(field, '-', &len); 745 | if (2 != len) { 746 | *error = "Specified range requires two fields"; 747 | goto return_error; 748 | } 749 | int err = 0; 750 | res[0] = parse_uint(parts[0], &err); 751 | if (err) { 752 | *error = "Unsigned integer parse error 2"; 753 | goto return_error; 754 | } 755 | res[1] = parse_uint(parts[1], &err); 756 | if (err) { 757 | *error = "Unsigned integer parse error 3"; 758 | goto return_error; 759 | } 760 | } 761 | if (res[0] >= max || res[1] >= max) { 762 | *error = "Specified range exceeds maximum"; 763 | goto return_error; 764 | } 765 | if (res[0] < min || res[1] < min) { 766 | *error = "Specified range is less than minimum"; 767 | goto return_error; 768 | } 769 | if (res[0] > res[1]) { 770 | *error = "Specified range start exceeds range end"; 771 | goto return_error; 772 | } 773 | 774 | free_splitted(parts, len); 775 | *error = NULL; 776 | return res; 777 | 778 | return_error: 779 | free_splitted(parts, len); 780 | if (res) { 781 | cron_free(res); 782 | } 783 | 784 | return NULL; 785 | } 786 | 787 | static void set_number_hits(const char* value, uint8_t* target, unsigned int min, unsigned int max, const char** error) { 788 | size_t i; 789 | unsigned int i1; 790 | size_t len = 0; 791 | 792 | char** fields = split_str(value, ',', &len); 793 | if (!fields) { 794 | *error = "Comma split error"; 795 | goto return_result; 796 | } 797 | 798 | for (i = 0; i < len; i++) { 799 | if (!has_char(fields[i], '/')) { 800 | /* Not an incrementer so it must be a range (possibly empty) */ 801 | 802 | unsigned int* range = get_range(fields[i], min, max, error); 803 | 804 | if (*error) { 805 | if (range) { 806 | cron_free(range); 807 | } 808 | goto return_result; 809 | 810 | } 811 | 812 | for (i1 = range[0]; i1 <= range[1]; i1++) { 813 | cron_set_bit(target, i1); 814 | 815 | } 816 | cron_free(range); 817 | 818 | } else { 819 | size_t len2 = 0; 820 | char** split = split_str(fields[i], '/', &len2); 821 | if (2 != len2) { 822 | *error = "Incrementer must have two fields"; 823 | free_splitted(split, len2); 824 | goto return_result; 825 | } 826 | unsigned int* range = get_range(split[0], min, max, error); 827 | if (*error) { 828 | if (range) { 829 | cron_free(range); 830 | } 831 | free_splitted(split, len2); 832 | goto return_result; 833 | } 834 | if (!has_char(split[0], '-')) { 835 | range[1] = max - 1; 836 | } 837 | int err = 0; 838 | unsigned int delta = parse_uint(split[1], &err); 839 | if (err) { 840 | *error = "Unsigned integer parse error 4"; 841 | cron_free(range); 842 | free_splitted(split, len2); 843 | goto return_result; 844 | } 845 | if (0 == delta) { 846 | *error = "Incrementer may not be zero"; 847 | cron_free(range); 848 | free_splitted(split, len2); 849 | goto return_result; 850 | } 851 | for (i1 = range[0]; i1 <= range[1]; i1 += delta) { 852 | cron_set_bit(target, i1); 853 | } 854 | free_splitted(split, len2); 855 | cron_free(range); 856 | 857 | } 858 | } 859 | goto return_result; 860 | 861 | return_result: 862 | free_splitted(fields, len); 863 | 864 | } 865 | 866 | static void set_months(char* value, uint8_t* targ, const char** error) { 867 | unsigned int i; 868 | unsigned int max = 12; 869 | 870 | char* replaced = NULL; 871 | 872 | to_upper(value); 873 | replaced = replace_ordinals(value, MONTHS_ARR, CRON_MONTHS_ARR_LEN); 874 | if (!replaced) { 875 | *error = "Invalid month format"; 876 | return; 877 | } 878 | set_number_hits(replaced, targ, 1, max + 1, error); 879 | cron_free(replaced); 880 | 881 | /* ... and then rotate it to the front of the months */ 882 | for (i = 1; i <= max; i++) { 883 | if (cron_get_bit(targ, i)) { 884 | cron_set_bit(targ, i - 1); 885 | cron_del_bit(targ, i); 886 | } 887 | } 888 | } 889 | 890 | static void set_days_of_week(char* field, uint8_t* targ, const char** error) { 891 | unsigned int max = 7; 892 | char* replaced = NULL; 893 | 894 | if (1 == strlen(field) && '?' == field[0]) { 895 | field[0] = '*'; 896 | } 897 | to_upper(field); 898 | replaced = replace_ordinals(field, DAYS_ARR, CRON_DAYS_ARR_LEN); 899 | if (!replaced) { 900 | *error = "Invalid day format"; 901 | return; 902 | } 903 | set_number_hits(replaced, targ, 0, max + 1, error); 904 | cron_free(replaced); 905 | if (cron_get_bit(targ, 7)) { 906 | /* Sunday can be represented as 0 or 7*/ 907 | cron_set_bit(targ, 0); 908 | cron_del_bit(targ, 7); 909 | } 910 | } 911 | 912 | static void set_days_of_month(char* field, uint8_t* targ, const char** error) { 913 | /* Days of month start with 1 (in Cron and Calendar) so add one */ 914 | if (1 == strlen(field) && '?' == field[0]) { 915 | field[0] = '*'; 916 | } 917 | set_number_hits(field, targ, 1, CRON_MAX_DAYS_OF_MONTH, error); 918 | } 919 | 920 | void cron_parse_expr(const char* expression, cron_expr* target, const char** error) { 921 | const char* err_local; 922 | size_t len = 0; 923 | char** fields = NULL; 924 | if (!error) { 925 | error = &err_local; 926 | } 927 | *error = NULL; 928 | if (!expression) { 929 | *error = "Invalid NULL expression"; 930 | goto return_res; 931 | } 932 | if (!target) { 933 | *error = "Invalid NULL target"; 934 | goto return_res; 935 | } 936 | 937 | fields = split_str(expression, ' ', &len); 938 | if (len != 6) { 939 | *error = "Invalid number of fields, expression must consist of 6 fields"; 940 | goto return_res; 941 | } 942 | memset(target, 0, sizeof(*target)); 943 | set_number_hits(fields[0], target->seconds, 0, 60, error); 944 | if (*error) goto return_res; 945 | set_number_hits(fields[1], target->minutes, 0, 60, error); 946 | if (*error) goto return_res; 947 | set_number_hits(fields[2], target->hours, 0, 24, error); 948 | if (*error) goto return_res; 949 | set_days_of_month(fields[3], target->days_of_month, error); 950 | if (*error) goto return_res; 951 | set_months(fields[4], target->months, error); 952 | if (*error) goto return_res; 953 | set_days_of_week(fields[5], target->days_of_week, error); 954 | if (*error) goto return_res; 955 | 956 | goto return_res; 957 | 958 | return_res: 959 | free_splitted(fields, len); 960 | } 961 | 962 | time_t cron_next(cron_expr* expr, time_t date) { 963 | /* 964 | The plan: 965 | 966 | 1 Round up to the next whole second 967 | 968 | 2 If seconds match move on, otherwise find the next match: 969 | 2.1 If next match is in the next minute then roll forwards 970 | 971 | 3 If minute matches move on, otherwise find the next match 972 | 3.1 If next match is in the next hour then roll forwards 973 | 3.2 Reset the seconds and go to 2 974 | 975 | 4 If hour matches move on, otherwise find the next match 976 | 4.1 If next match is in the next day then roll forwards, 977 | 4.2 Reset the minutes and seconds and go to 2 978 | 979 | ... 980 | */ 981 | if (!expr) return CRON_INVALID_INSTANT; 982 | struct tm calval; 983 | memset(&calval, 0, sizeof(struct tm)); 984 | struct tm* calendar = cron_time(&date, &calval); 985 | if (!calendar) return CRON_INVALID_INSTANT; 986 | time_t original = cron_mktime(calendar); 987 | if (CRON_INVALID_INSTANT == original) return CRON_INVALID_INSTANT; 988 | 989 | int res = do_next(expr, calendar, calendar->tm_year); 990 | if (0 != res) return CRON_INVALID_INSTANT; 991 | 992 | time_t calculated = cron_mktime(calendar); 993 | if (CRON_INVALID_INSTANT == calculated) return CRON_INVALID_INSTANT; 994 | if (calculated == original) { 995 | /* We arrived at the original timestamp - round up to the next whole second and try again... */ 996 | res = add_to_field(calendar, CRON_CF_SECOND, 1); 997 | if (0 != res) return CRON_INVALID_INSTANT; 998 | res = do_next(expr, calendar, calendar->tm_year); 999 | if (0 != res) return CRON_INVALID_INSTANT; 1000 | } 1001 | 1002 | return cron_mktime(calendar); 1003 | } 1004 | 1005 | 1006 | /* https://github.com/staticlibs/ccronexpr/pull/8 */ 1007 | 1008 | static unsigned int prev_set_bit(uint8_t* bits, int from_index, int to_index, int* notfound) { 1009 | int i; 1010 | if (!bits) { 1011 | *notfound = 1; 1012 | return 0; 1013 | } 1014 | for (i = from_index; i >= to_index; i--) { 1015 | if (cron_get_bit(bits, i)) return i; 1016 | } 1017 | *notfound = 1; 1018 | return 0; 1019 | } 1020 | 1021 | static int last_day_of_month(int month, int year) { 1022 | struct tm cal; 1023 | time_t t; 1024 | memset(&cal,0,sizeof(cal)); 1025 | cal.tm_sec=0; 1026 | cal.tm_min=0; 1027 | cal.tm_hour=0; 1028 | cal.tm_mon = month+1; 1029 | cal.tm_mday = 0; 1030 | cal.tm_year=year; 1031 | t=mktime(&cal); 1032 | return gmtime(&t)->tm_mday; 1033 | } 1034 | 1035 | /** 1036 | * Reset the calendar setting all the fields provided to zero. 1037 | */ 1038 | static int reset_max(struct tm* calendar, int field) { 1039 | if (!calendar || -1 == field) { 1040 | return 1; 1041 | } 1042 | switch (field) { 1043 | case CRON_CF_SECOND: 1044 | calendar->tm_sec = 59; 1045 | break; 1046 | case CRON_CF_MINUTE: 1047 | calendar->tm_min = 59; 1048 | break; 1049 | case CRON_CF_HOUR_OF_DAY: 1050 | calendar->tm_hour = 23; 1051 | break; 1052 | case CRON_CF_DAY_OF_WEEK: 1053 | calendar->tm_wday = 6; 1054 | break; 1055 | case CRON_CF_DAY_OF_MONTH: 1056 | calendar->tm_mday = last_day_of_month(calendar->tm_mon, calendar->tm_year); 1057 | break; 1058 | case CRON_CF_MONTH: 1059 | calendar->tm_mon = 11; 1060 | break; 1061 | case CRON_CF_YEAR: 1062 | /* I don't think this is supposed to happen ... */ 1063 | fprintf(stderr, "reset CRON_CF_YEAR\n"); 1064 | break; 1065 | default: 1066 | return 1; /* unknown field */ 1067 | } 1068 | time_t res = cron_mktime(calendar); 1069 | if (CRON_INVALID_INSTANT == res) { 1070 | return 1; 1071 | } 1072 | return 0; 1073 | } 1074 | 1075 | static int reset_all_max(struct tm* calendar, int* fields) { 1076 | int i; 1077 | int res = 0; 1078 | if (!calendar || !fields) { 1079 | return 1; 1080 | } 1081 | for (i = 0; i < CRON_CF_ARR_LEN; i++) { 1082 | if (-1 != fields[i]) { 1083 | res = reset_max(calendar, fields[i]); 1084 | if (0 != res) return res; 1085 | } 1086 | } 1087 | return 0; 1088 | } 1089 | 1090 | /** 1091 | * Search the bits provided for the next set bit after the value provided, 1092 | * and reset the calendar. 1093 | */ 1094 | 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) { 1095 | int notfound = 0; 1096 | int err = 0; 1097 | unsigned int next_value = prev_set_bit(bits, value, 0, ¬found); 1098 | /* roll under if needed */ 1099 | if (notfound) { 1100 | err = add_to_field(calendar, nextField, -1); 1101 | if (err) goto return_error; 1102 | err = reset_max(calendar, field); 1103 | if (err) goto return_error; 1104 | notfound = 0; 1105 | next_value = prev_set_bit(bits, max - 1, value, ¬found); 1106 | } 1107 | if (notfound || next_value != value) { 1108 | err = set_field(calendar, field, next_value); 1109 | if (err) goto return_error; 1110 | err = reset_all_max(calendar, lower_orders); 1111 | if (err) goto return_error; 1112 | } 1113 | return next_value; 1114 | 1115 | return_error: 1116 | *res_out = 1; 1117 | return 0; 1118 | } 1119 | 1120 | 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) { 1121 | int err; 1122 | unsigned int count = 0; 1123 | unsigned int max = 366; 1124 | while ((!cron_get_bit(days_of_month, day_of_month) || !cron_get_bit(days_of_week, day_of_week)) && count++ < max) { 1125 | err = add_to_field(calendar, CRON_CF_DAY_OF_MONTH, -1); 1126 | 1127 | if (err) goto return_error; 1128 | day_of_month = calendar->tm_mday; 1129 | day_of_week = calendar->tm_wday; 1130 | reset_all_max(calendar, resets); 1131 | } 1132 | return day_of_month; 1133 | 1134 | return_error: 1135 | *res_out = 1; 1136 | return 0; 1137 | } 1138 | 1139 | static int do_prev(cron_expr* expr, struct tm* calendar, unsigned int dot) { 1140 | int i; 1141 | int res = 0; 1142 | int* resets = NULL; 1143 | int* empty_list = NULL; 1144 | unsigned int second = 0; 1145 | unsigned int update_second = 0; 1146 | unsigned int minute = 0; 1147 | unsigned int update_minute = 0; 1148 | unsigned int hour = 0; 1149 | unsigned int update_hour = 0; 1150 | unsigned int day_of_week = 0; 1151 | unsigned int day_of_month = 0; 1152 | unsigned int update_day_of_month = 0; 1153 | unsigned int month = 0; 1154 | unsigned int update_month = 0; 1155 | 1156 | resets = (int*) cron_malloc(CRON_CF_ARR_LEN * sizeof(int)); 1157 | if (!resets) goto return_result; 1158 | empty_list = (int*) cron_malloc(CRON_CF_ARR_LEN * sizeof(int)); 1159 | if (!empty_list) goto return_result; 1160 | for (i = 0; i < CRON_CF_ARR_LEN; i++) { 1161 | resets[i] = -1; 1162 | empty_list[i] = -1; 1163 | } 1164 | 1165 | second = calendar->tm_sec; 1166 | update_second = find_prev(expr->seconds, CRON_MAX_SECONDS, second, calendar, CRON_CF_SECOND, CRON_CF_MINUTE, empty_list, &res); 1167 | if (0 != res) goto return_result; 1168 | if (second == update_second) { 1169 | push_to_fields_arr(resets, CRON_CF_SECOND); 1170 | } 1171 | 1172 | minute = calendar->tm_min; 1173 | update_minute = find_prev(expr->minutes, CRON_MAX_MINUTES, minute, calendar, CRON_CF_MINUTE, CRON_CF_HOUR_OF_DAY, resets, &res); 1174 | if (0 != res) goto return_result; 1175 | if (minute == update_minute) { 1176 | push_to_fields_arr(resets, CRON_CF_MINUTE); 1177 | } else { 1178 | res = do_prev(expr, calendar, dot); 1179 | if (0 != res) goto return_result; 1180 | } 1181 | 1182 | hour = calendar->tm_hour; 1183 | update_hour = find_prev(expr->hours, CRON_MAX_HOURS, hour, calendar, CRON_CF_HOUR_OF_DAY, CRON_CF_DAY_OF_WEEK, resets, &res); 1184 | if (0 != res) goto return_result; 1185 | if (hour == update_hour) { 1186 | push_to_fields_arr(resets, CRON_CF_HOUR_OF_DAY); 1187 | } else { 1188 | res = do_prev(expr, calendar, dot); 1189 | if (0 != res) goto return_result; 1190 | } 1191 | 1192 | day_of_week = calendar->tm_wday; 1193 | day_of_month = calendar->tm_mday; 1194 | update_day_of_month = find_prev_day(calendar, expr->days_of_month, day_of_month, expr->days_of_week, day_of_week, resets, &res); 1195 | if (0 != res) goto return_result; 1196 | if (day_of_month == update_day_of_month) { 1197 | push_to_fields_arr(resets, CRON_CF_DAY_OF_MONTH); 1198 | } else { 1199 | res = do_prev(expr, calendar, dot); 1200 | if (0 != res) goto return_result; 1201 | } 1202 | 1203 | month = calendar->tm_mon; /*day already adds one if no day in same month is found*/ 1204 | update_month = find_prev(expr->months, CRON_MAX_MONTHS, month, calendar, CRON_CF_MONTH, CRON_CF_YEAR, resets, &res); 1205 | if (0 != res) goto return_result; 1206 | if (month != update_month) { 1207 | if (dot - calendar->tm_year > CRON_MAX_YEARS_DIFF) { 1208 | res = -1; 1209 | goto return_result; 1210 | } 1211 | res = do_prev(expr, calendar, dot); 1212 | if (0 != res) goto return_result; 1213 | } 1214 | goto return_result; 1215 | 1216 | return_result: 1217 | if (!resets || !empty_list) { 1218 | res = -1; 1219 | } 1220 | if (resets) { 1221 | cron_free(resets); 1222 | } 1223 | if (empty_list) { 1224 | cron_free(empty_list); 1225 | } 1226 | return res; 1227 | } 1228 | 1229 | time_t cron_prev(cron_expr* expr, time_t date) { 1230 | /* 1231 | The plan: 1232 | 1233 | 1 Round down to a whole second 1234 | 1235 | 2 If seconds match move on, otherwise find the next match: 1236 | 2.1 If next match is in the next minute then roll forwards 1237 | 1238 | 3 If minute matches move on, otherwise find the next match 1239 | 3.1 If next match is in the next hour then roll forwards 1240 | 3.2 Reset the seconds and go to 2 1241 | 1242 | 4 If hour matches move on, otherwise find the next match 1243 | 4.1 If next match is in the next day then roll forwards, 1244 | 4.2 Reset the minutes and seconds and go to 2 1245 | 1246 | ... 1247 | */ 1248 | if (!expr) return CRON_INVALID_INSTANT; 1249 | struct tm calval; 1250 | memset(&calval, 0, sizeof(struct tm)); 1251 | struct tm* calendar = cron_time(&date, &calval); 1252 | if (!calendar) return CRON_INVALID_INSTANT; 1253 | time_t original = cron_mktime(calendar); 1254 | if (CRON_INVALID_INSTANT == original) return CRON_INVALID_INSTANT; 1255 | 1256 | /* calculate the previous occurrence */ 1257 | int res = do_prev(expr, calendar, calendar->tm_year); 1258 | if (0 != res) return CRON_INVALID_INSTANT; 1259 | 1260 | /* check for a match, try from the next second if one wasn't found */ 1261 | time_t calculated = cron_mktime(calendar); 1262 | if (CRON_INVALID_INSTANT == calculated) return CRON_INVALID_INSTANT; 1263 | if (calculated == original) { 1264 | /* We arrived at the original timestamp - round up to the next whole second and try again... */ 1265 | res = add_to_field(calendar, CRON_CF_SECOND, -1); 1266 | if (0 != res) return CRON_INVALID_INSTANT; 1267 | res = do_prev(expr, calendar, calendar->tm_year); 1268 | if (0 != res) return CRON_INVALID_INSTANT; 1269 | } 1270 | 1271 | return cron_mktime(calendar); 1272 | } 1273 | --------------------------------------------------------------------------------