LCOV - code coverage report
Current view: top level - ezlibs - ezCron.hpp (source / functions) Coverage Total Hit
Test: Coverage (llvm-cov → lcov → genhtml) Lines: 89.8 % 394 354
Test Date: 2025-09-16 22:55:37 Functions: 96.2 % 26 25
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 85.6 % 222 190

             Branch data     Line data    Source code
       1                 :             : #pragma once
       2                 :             : 
       3                 :             : /*
       4                 :             : MIT License
       5                 :             : 
       6                 :             : Copyright (c) 2014-2024 Stephane Cuillerdier (aka aiekick)
       7                 :             : 
       8                 :             : Permission is hereby granted, free of charge, to any person obtaining a copy
       9                 :             : of this software and associated documentation files (the "Software"), to deal
      10                 :             : in the Software without restriction, including without limitation the rights
      11                 :             : to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
      12                 :             : copies of the Software, and to permit persons to whom the Software is
      13                 :             : furnished to do so, subject to the following conditions:
      14                 :             : 
      15                 :             : The above copyright notice and this permission notice shall be included in all
      16                 :             : copies or substantial portions of the Software.
      17                 :             : 
      18                 :             : THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
      19                 :             : IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
      20                 :             : FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
      21                 :             : AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
      22                 :             : LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
      23                 :             : OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
      24                 :             : SOFTWARE.
      25                 :             : */
      26                 :             : 
      27                 :             : // ezCron is part of the ezLibs project : https://github.com/aiekick/ezLibs.git
      28                 :             : 
      29                 :             : #include <set>
      30                 :             : #include <ctime>
      31                 :             : #include <array>
      32                 :             : #include <string>
      33                 :             : #include <vector>
      34                 :             : #include <cstdint>
      35                 :             : #include <sstream>
      36                 :             : #include <iterator>
      37                 :             : #include <iostream>
      38                 :             : 
      39                 :             : ////////////////////////////////////////////////////////////////////////////
      40                 :             : ////////////////////////////////////////////////////////////////////////////
      41                 :             : ////////////////////////////////////////////////////////////////////////////
      42                 :             : 
      43                 :             : #ifdef _MSC_VER
      44                 :             : #pragma warning(push)
      45                 :             : #pragma warning(disable : 4244)  // Conversion from 'double' to 'float', possible loss of data
      46                 :             : #pragma warning(disable : 4305)  // Truncation from 'double' to 'float'
      47                 :             : #elif defined(__GNUC__) || defined(__clang__)
      48                 :             : #pragma GCC diagnostic push
      49                 :             : #pragma GCC diagnostic ignored "-Wconversion"
      50                 :             : #pragma GCC diagnostic ignored "-Wfloat-conversion"
      51                 :             : #endif
      52                 :             : 
      53                 :             : ////////////////////////////////////////////////////////////////////////////
      54                 :             : ////////////////////////////////////////////////////////////////////////////
      55                 :             : ////////////////////////////////////////////////////////////////////////////
      56                 :             : 
      57                 :             : namespace ez {
      58                 :             : namespace time {
      59                 :             : 
      60                 :             : // Supported Crontab format
      61                 :             : //  'mm hh dd MM DD'
      62                 :             : //  mm : the minutes from 0 to 59
      63                 :             : //  hh : the hour from 0 to 23
      64                 :             : //  dd : the day of the month from 1 to 31
      65                 :             : //  MM : the month from 1 to 12
      66                 :             : //  DD : the day of the week from 0 to 7. 0 and 7 are the sunday
      67                 :             : // For each fields, thoses forms are accepted :
      68                 :             : //  * : always valid units (0,1,3,4, etc..)
      69                 :             : //  5,8 : the units 5 and 8
      70                 :             : //  2-5 : the units from 2 to 5 (2, 3, 4, 5)
      71                 :             : //  */3 : all the 3 interval units(0, 3, 6, 9, etc..)
      72                 :             : 
      73                 :             : class Cron {
      74                 :             :     friend class TestCron;
      75                 :             : 
      76                 :             : public:
      77                 :             :     enum ErrorFlags {  //
      78                 :             :         NONE = (0),
      79                 :             :         INVALID_MINUTE = (1 << 0),
      80                 :             :         INVALID_HOUR = (1 << 1),
      81                 :             :         INVALID_MONTH_DAY = (1 << 2),
      82                 :             :         INVALID_MONTH = (1 << 3),
      83                 :             :         INVALID_WEEK_DAY = (1 << 4),
      84                 :             :         INVALID_FIELDS_COUNT = (1 << 5),
      85                 :             :         INVALID_CHAR = (1 << 6),
      86                 :             :         INVALID_RANGE = (1 << 7),
      87                 :             :         INVALID_INTERVAL = (1 << 8),
      88                 :             :         INVALID_VALUES = (1 << 9),
      89                 :             :         INVALID_FIELD = (1 << 10)
      90                 :             :     };
      91                 :             :     enum class FieldType {  //
      92                 :             :         VALUE = 0,          // Value
      93                 :             :         INTERVAL,           // Interval. each values
      94                 :             :         RANGE,              // Range [min:max]
      95                 :             :         VALUES,             // Values [v0,v1,v2]
      96                 :             :         WILDCARD,           // Valid value for all
      97                 :             :         Count
      98                 :             :     };
      99                 :             :     enum class FieldIndex {  //
     100                 :             :         MINUTE = 0,
     101                 :             :         HOUR,
     102                 :             :         DAY_MONTH,
     103                 :             :         MONTH,
     104                 :             :         DAY_WEEK,
     105                 :             :         Count
     106                 :             :     };
     107                 :             :     struct Field {
     108                 :             :         std::string str;                                            // field str
     109                 :             :         FieldIndex index{FieldIndex::MINUTE};                       // field index
     110                 :             :         int32_t cpos{-1};                                           // char pos in the cron rule
     111                 :             :         FieldType type{FieldType::VALUE};                           // field type
     112                 :             :         int32_t value{-1};                                          // the value with type is 'value'
     113                 :             :         int32_t interval{-1};                                       // the interval value when type is 'interval'
     114                 :             :         std::pair<int32_t, int32_t> range{std::make_pair(-1, -1)};  // the range when type is 'range'
     115                 :             :         std::set<int32_t> values{};                                 // the valude when type is 'values'
     116                 :             :     };
     117                 :             :     struct ErrorDetail {
     118                 :             :         int32_t flag{};
     119                 :             :         int32_t position{-1};
     120                 :             :         std::string message;
     121                 :             :     };
     122                 :             : 
     123                 :             : private:
     124                 :             :     int32_t m_errorFlags;
     125                 :             :     std::string m_cronRule;
     126                 :             :     std::vector<Field> m_fields;
     127                 :             :     std::vector<ErrorDetail> m_errorDetails;
     128                 :             : 
     129                 :             : public:
     130                 :             :     Cron(const std::string& m_cronRule)  //
     131                 :          39 :         : m_errorFlags(NONE), m_cronRule(m_cronRule) {
     132                 :          39 :         m_parseExpression();
     133                 :          39 :     }
     134                 :             : 
     135                 :          73 :     bool isOk() const {
     136                 :          73 :         return (m_errorFlags == NONE);
     137                 :          73 :     }
     138                 :             : 
     139                 :           2 :     const std::string& getCronRule() const {
     140                 :           2 :         return m_cronRule;
     141                 :           2 :     }
     142                 :             : 
     143                 :          28 :     int getErrorFlags() const {
     144                 :          28 :         return m_errorFlags;
     145                 :          28 :     }
     146                 :             : 
     147                 :           1 :     const std::vector<Field>& getFields() const {
     148                 :           1 :         return m_fields;
     149                 :           1 :     }
     150                 :             : 
     151                 :           4 :     bool isTimeToAct() const {
     152                 :           4 :         time_t currentTime = std::time(nullptr);
     153                 :           4 :         return isTimeToAct(currentTime);
     154                 :           4 :     }
     155                 :             : 
     156                 :          34 :     bool isTimeToAct(time_t vCurrentTime) const {
     157         [ +  - ]:          34 :         if (isOk()) {
     158                 :             : #ifdef _MSC_VER
     159                 :             :             struct tm _tm;
     160                 :             :             localtime_s(&_tm, &vCurrentTime);
     161                 :             :             auto* currentTm = &_tm;
     162                 :             : #else
     163                 :          34 :             auto* currentTm = std::localtime(&vCurrentTime);
     164                 :          34 : #endif
     165                 :          34 :             return                                                          //
     166         [ +  + ]:          34 :                 m_checkTimeForField(FieldIndex::MINUTE, currentTm->tm_min) &&  //
     167         [ +  + ]:          34 :                 m_checkTimeForField(FieldIndex::HOUR, currentTm->tm_hour) &&   //
     168         [ +  - ]:          34 :                 m_checkTimeForField(FieldIndex::DAY_MONTH, currentTm->tm_mday) &&  //
     169         [ +  - ]:          34 :                 m_checkTimeForField(FieldIndex::MONTH, currentTm->tm_mon + 1) &&   //
     170         [ +  - ]:          34 :                 m_checkTimeForField(FieldIndex::DAY_WEEK, currentTm->tm_wday);
     171                 :          34 :         }
     172                 :           0 :         return false;
     173                 :          34 :     }
     174                 :             : 
     175                 :          39 :     std::string getErrorMessage() const {
     176                 :          39 :         std::stringstream err;
     177         [ +  + ]:          39 :         if (m_errorDetails.empty()) {
     178                 :          11 :             std::cout << "No errors found.\n";
     179                 :          28 :         } else {
     180                 :          28 :             err << "Errors found in cron rule : " << std::endl;
     181                 :          28 :             err << m_cronRule << std::endl;
     182         [ +  + ]:         106 :             for (auto rev_it = m_errorDetails.rbegin(); rev_it != m_errorDetails.rend(); ++rev_it) {
     183         [ +  + ]:          78 :                 if (rev_it->position >= 0) {
     184                 :          61 :                     std::string marker_line(m_cronRule.size(), ' ');
     185         [ +  + ]:         309 :                     for (auto it = m_errorDetails.begin(); it != m_errorDetails.end(); ++it) {
     186         [ +  + ]:         248 :                         if (it->position >= 0) {
     187                 :         231 :                             marker_line[it->position] = '|';
     188                 :         231 :                         }
     189                 :         248 :                     }
     190                 :          61 :                     marker_line[rev_it->position] = '^';
     191                 :          61 :                     marker_line = marker_line.substr(0, rev_it->position + 1);
     192                 :          61 :                     err << marker_line << "--<| " << m_getErrorString(rev_it->flag) << " " << rev_it->message << std::endl;
     193                 :          61 :                 }
     194                 :          78 :             }
     195                 :          28 :         }
     196                 :          39 :         return err.str();
     197                 :          39 :     }
     198                 :             : 
     199                 :           0 :     std::string getSupportedFormat() const {
     200                 :           0 :         return u8R"(
     201                 :           0 : Supported Crontab format
     202                 :           0 :  mm hh dd MM DD    
     203                 :           0 :  mm : the minutes from 0 to 59
     204                 :           0 :  hh : the hour from 0 to 23
     205                 :           0 :  dd : the day of the month from 1 to 31
     206                 :           0 :  MM : the month from 1 to 12
     207                 :           0 :  DD : the day of the week from 0 to 7. 0 and 7 are the sunday
     208                 :           0 : For each fields, thoses forms are accepted :
     209                 :           0 :  * : always valid units (0,1,3,4, etc..)
     210                 :           0 :  5,8 : the units 5 and 8
     211                 :           0 :  2-5 : the units from 2 to 5 (2, 3, 4, 5)
     212                 :           0 :  */3 : all the 3 interval units (0, 3, 6, 9, etc..)
     213                 :           0 : )";
     214                 :           0 :     }
     215                 :             : 
     216                 :             : private:
     217                 :             :     typedef int32_t CharPos;
     218                 :             :     typedef std::pair<CharPos, std::string> Token;
     219                 :         160 :     std::vector<Token> m_split(const std::string& vText, const std::string& vDelimiters) {
     220                 :         160 :         std::vector<Token> arr;
     221         [ +  - ]:         160 :         if (!vText.empty()) {
     222                 :         160 :             size_t start = 0;
     223                 :         160 :             std::string token;
     224                 :         160 :             auto end = vText.find_first_of(vDelimiters, start);
     225         [ +  + ]:         160 :             if (end != std::string::npos) {
     226         [ +  + ]:         221 :                 while (end != std::string::npos) {
     227                 :         172 :                     token = vText.substr(start, end - start);
     228                 :         172 :                     arr.push_back(std::make_pair(static_cast<CharPos>(start), token));  // empty token or not
     229                 :         172 :                     start = end + 1;
     230                 :         172 :                     end = vText.find_first_of(vDelimiters, start);
     231                 :         172 :                 }
     232                 :          49 :                 token = vText.substr(start);
     233                 :          49 :                 arr.push_back(std::make_pair(static_cast<CharPos>(start), token));  // empty token or not
     234                 :          49 :             }
     235                 :         160 :         }
     236                 :         160 :         return arr;
     237                 :         160 :     }
     238                 :             : 
     239                 :          61 :     int32_t m_getErrorFlagFromIndex(FieldIndex vIndex) {
     240                 :          61 :         return (1 << static_cast<int32_t>(vIndex));
     241                 :          61 :     }
     242                 :             : 
     243                 :             :     template <typename U, typename V, typename W>
     244                 :          99 :     void m_addError(U vFlag, V vCharPos, W vIndex, const std::string& vMessage = {}) {
     245                 :          99 :         auto idx = static_cast<size_t>(vIndex);
     246 [ +  + ][ +  + ]:         177 :         while (m_errorDetails.size() <= idx) {
         [ +  + ][ +  + ]
                 [ -  + ]
     247                 :          78 :             m_errorDetails.push_back({});
     248                 :          78 :         }
     249                 :          99 :         auto& ed = m_errorDetails.at(idx);
     250                 :          99 :         ed.flag |= static_cast<int32_t>(vFlag);
     251                 :          99 :         auto cp = static_cast<int32_t>(vCharPos);
     252 [ +  + ][ +  + ]:          99 :         if (ed.position < 0) {
         [ +  - ][ +  + ]
                 [ -  + ]
     253                 :          61 :             ed.position = cp;
     254 [ -  + ][ -  + ]:          61 :         } else if (cp > ed.position) {
         [ +  + ][ -  + ]
                 [ #  # ]
     255                 :          12 :             ed.position = cp;
     256                 :          12 :         }
     257 [ -  + ][ +  + ]:          99 :         if (!vMessage.empty()) {
         [ -  + ][ -  + ]
                 [ -  + ]
     258                 :           2 :             ed.message += vMessage;
     259                 :           2 :         }
     260                 :          99 :         m_errorFlags |= ed.flag;
     261                 :          99 :     }
     262                 :             :     
     263                 :           5 :     bool m_fieldHaveError(Field& vInOutField) {
     264                 :           5 :         auto idx = static_cast<size_t>(vInOutField.index);
     265         [ +  + ]:           5 :         if (m_errorDetails.size() > idx) {
     266                 :           1 :             return (m_errorDetails.at(idx).position > 0);
     267                 :           1 :         }
     268                 :           4 :         return false;
     269                 :           5 :     }
     270                 :             :     
     271                 :         113 :     bool m_parseValue(Field& vInOutField, const std::string& vValue, int32_t vCharPos) {
     272                 :         113 :         try {
     273                 :         113 :             auto wpos = vInOutField.str.find_first_not_of("0123456789/*-,");
     274         [ +  + ]:         113 :             if (wpos != std::string::npos) {
     275                 :          10 :                 m_addError(INVALID_CHAR | m_getErrorFlagFromIndex(vInOutField.index), vInOutField.cpos + wpos, vInOutField.index);
     276                 :          10 :                 return false;
     277                 :          10 :             }
     278                 :         103 :             auto v = std::stoi(vValue);
     279                 :             :             // check min/max
     280                 :         103 :             switch (vInOutField.index) {
     281         [ +  + ]:          30 :                 case FieldIndex::MINUTE: {
     282 [ -  + ][ +  + ]:          30 :                     if (v < 0 || v > 59) {
     283                 :           3 :                         m_addError(ErrorFlags::INVALID_MINUTE, vCharPos, vInOutField.index);
     284                 :           3 :                         return false;
     285                 :           3 :                     }
     286                 :          30 :                 } break;
     287         [ +  + ]:          27 :                 case FieldIndex::HOUR: {
     288 [ -  + ][ +  + ]:          13 :                     if (v < 0 || v > 23) {
     289                 :           3 :                         m_addError(ErrorFlags::INVALID_HOUR, vCharPos, vInOutField.index);
     290                 :           3 :                         return false;
     291                 :           3 :                     }
     292                 :          13 :                 } break;
     293         [ +  + ]:          10 :                 case FieldIndex::DAY_MONTH: {
     294 [ -  + ][ +  + ]:           9 :                     if (v < 1 || v > 31) {
     295                 :           3 :                         m_addError(ErrorFlags::INVALID_MONTH_DAY, vCharPos, vInOutField.index);
     296                 :           3 :                         return false;
     297                 :           3 :                     }
     298                 :           9 :                 } break;
     299         [ +  + ]:           8 :                 case FieldIndex::MONTH: {
     300 [ -  + ][ +  + ]:           8 :                     if (v < 1 || v > 12) {
     301                 :           3 :                         m_addError(ErrorFlags::INVALID_MONTH, vCharPos, vInOutField.index);
     302                 :           3 :                         return false;
     303                 :           3 :                     }
     304                 :           8 :                 } break;
     305         [ +  + ]:          12 :                 case FieldIndex::DAY_WEEK: {
     306 [ -  + ][ +  + ]:          12 :                     if (v < 0 || v > 7) {
     307                 :           3 :                         m_addError(ErrorFlags::INVALID_WEEK_DAY, vCharPos, vInOutField.index);
     308                 :           3 :                         return false;
     309                 :           3 :                     }
     310                 :          12 :                 } break;
     311         [ +  + ]:           9 :                 case FieldIndex::Count:
     312         [ -  + ]:           1 :                 default: break;
     313                 :         103 :             }
     314                 :             :             // set value
     315                 :          58 :             switch (vInOutField.type) {
     316         [ +  + ]:          22 :                 case FieldType::VALUE: {
     317                 :          22 :                     vInOutField.value = v;
     318                 :          22 :                 } break;
     319         [ +  + ]:          21 :                 case FieldType::INTERVAL: {
     320                 :          21 :                     vInOutField.interval = v;
     321                 :          21 :                 } break;
     322         [ +  + ]:           8 :                 case FieldType::RANGE: {
     323         [ +  + ]:           8 :                     if (vInOutField.range.first == -1) {
     324                 :           4 :                         vInOutField.range.first = v;
     325                 :           4 :                     } else {
     326                 :           4 :                         vInOutField.range.second = v;
     327                 :           4 :                     }
     328                 :           8 :                 } break;
     329         [ +  + ]:           7 :                 case FieldType::VALUES: {
     330                 :           7 :                     vInOutField.values.emplace(v);
     331                 :           7 :                 } break;
     332         [ -  + ]:           0 :                 case FieldType::WILDCARD:
     333         [ -  + ]:           0 :                 case FieldType::Count:
     334         [ -  + ]:           0 :                 default: break;
     335                 :          58 :             }
     336                 :          58 :         } catch (...) {
     337                 :          30 :             m_addError(m_getErrorFlagFromIndex(vInOutField.index), vCharPos, vInOutField.index);
     338                 :          30 :             return false;
     339                 :          30 :         }
     340                 :          58 :         return true;
     341                 :         113 :     }
     342                 :             : 
     343                 :          77 :     bool m_parseValue(Field& vInOutField, const Token& vToken) {
     344                 :          77 :         return m_parseValue(vInOutField, vToken.second, vToken.first);
     345                 :          77 :     }
     346                 :             :     
     347                 :         195 :     bool m_isInterval(Field& vInOutField) {
     348         [ +  - ]:         195 :         if (!vInOutField.str.empty()) {
     349                 :         195 :             auto wpos = vInOutField.str.find("*");
     350         [ +  + ]:         195 :             if (wpos != std::string::npos) {
     351                 :         136 :                 auto bad_pos = vInOutField.str.find_first_not_of("0123456789/*");
     352         [ -  + ]:         136 :                 if (bad_pos != std::string::npos) {
     353                 :           0 :                     m_addError(INVALID_INTERVAL | m_getErrorFlagFromIndex(vInOutField.index),  //
     354                 :           0 :                                vInOutField.cpos + bad_pos,
     355                 :           0 :                                vInOutField.index);
     356                 :           0 :                     return true;  // we not want to continue the field check
     357                 :           0 :                 }
     358         [ +  + ]:         136 :                 if (wpos < (vInOutField.str.size() - 1)) {  // interval pattern '*/'
     359                 :          46 :                     ++wpos;
     360         [ +  + ]:          46 :                     if (vInOutField.str[wpos] == '/') {
     361                 :          36 :                         ++wpos;
     362                 :          36 :                         vInOutField.str = vInOutField.str.substr(wpos);
     363                 :          36 :                         vInOutField.type = FieldType::INTERVAL;
     364         [ +  + ]:          36 :                         if (!m_parseValue(vInOutField, vInOutField.str, static_cast<int32_t>(wpos))) {
     365                 :          15 :                             m_addError(INVALID_INTERVAL, vInOutField.cpos + wpos, vInOutField.index);
     366                 :          15 :                             return false;  // wildcard, stop field checking
     367                 :          15 :                         }
     368                 :          36 :                     } else {
     369                 :          10 :                         m_addError(INVALID_INTERVAL | m_getErrorFlagFromIndex(vInOutField.index),  //
     370                 :          10 :                                    vInOutField.cpos + wpos,
     371                 :          10 :                                    vInOutField.index);
     372                 :          10 :                     }
     373                 :          90 :                 } else {  // generic pattern '*'
     374                 :          90 :                     vInOutField.type = FieldType::WILDCARD;
     375                 :          90 :                 }
     376                 :         121 :                 return true;  // wildcard, stop field checking
     377                 :         136 :             }
     378                 :         195 :         }
     379                 :          59 :         return false;  // no wildcard, continue field checking
     380                 :         195 :     }
     381                 :             :     
     382                 :          74 :     bool m_isRange(Field& vInOutField) {
     383         [ +  + ]:          74 :         if (!vInOutField.str.empty()) {
     384         [ +  + ]:          69 :             if (vInOutField.str.front() == '-') {
     385                 :           2 :                 m_addError(INVALID_RANGE | m_getErrorFlagFromIndex(vInOutField.index),  //
     386                 :           2 :                            vInOutField.cpos,
     387                 :           2 :                            vInOutField.index);
     388                 :           2 :                 return true;  // range, stop field checking
     389         [ +  + ]:          67 :             } else if (vInOutField.str.back() == '-') {
     390                 :           1 :                 m_addError(INVALID_RANGE | m_getErrorFlagFromIndex(vInOutField.index),  //
     391                 :           1 :                            vInOutField.cpos + vInOutField.str.size() - 1,
     392                 :           1 :                            vInOutField.index);
     393                 :           1 :                 return true;  // range, stop field checking
     394                 :           1 :             }
     395                 :          66 :             auto tokens = m_split(vInOutField.str, "-");
     396         [ +  + ]:          66 :             if (!tokens.empty()) {
     397                 :           7 :                 vInOutField.type = FieldType::RANGE;
     398         [ +  + ]:           7 :                 if (tokens.size() != 2) {
     399                 :           2 :                     auto err_pos = vInOutField.str.size();
     400         [ +  + ]:           5 :                     for (const auto& token : tokens) {
     401         [ +  + ]:           5 :                         if (token.second.empty()) {
     402                 :           1 :                             err_pos = token.first;
     403                 :           1 :                             break;
     404                 :           1 :                         }
     405                 :           5 :                     }
     406                 :           2 :                     m_addError(INVALID_RANGE | m_getErrorFlagFromIndex(vInOutField.index),  //
     407                 :           2 :                                vInOutField.cpos + err_pos,
     408                 :           2 :                                vInOutField.index);
     409                 :           2 :                     return true;  // range, stop field checking
     410                 :           2 :                 }
     411         [ +  + ]:          10 :                 for (const auto& token : tokens) {
     412         [ +  + ]:          10 :                     if (!m_parseValue(vInOutField, token)) {
     413                 :           2 :                         m_addError(INVALID_RANGE, vInOutField.cpos + token.first, vInOutField.index);
     414                 :           2 :                     }
     415                 :          10 :                 }                
     416         [ +  + ]:           5 :                 if (!m_fieldHaveError(vInOutField)) {
     417         [ +  + ]:           4 :                     if (vInOutField.range.first == vInOutField.range.second) {                // 5-5
     418                 :           1 :                         m_addError(INVALID_RANGE | m_getErrorFlagFromIndex(vInOutField.index),  //
     419                 :           1 :                                    vInOutField.cpos + tokens[1].first,
     420                 :           1 :                                    vInOutField.index,
     421                 :           1 :                                    " End must be different than Start.");
     422         [ +  + ]:           3 :                     } else if (vInOutField.range.first > vInOutField.range.second) {          // 5-4
     423                 :           1 :                         m_addError(INVALID_RANGE | m_getErrorFlagFromIndex(vInOutField.index),  //
     424                 :           1 :                                    vInOutField.cpos + tokens[1].first,
     425                 :           1 :                                    vInOutField.index,
     426                 :           1 :                                    " End must be greater than Start.");
     427                 :           1 :                     }
     428                 :           4 :                 }
     429                 :           5 :                 return true;  // range, stop field checking
     430                 :           7 :             }
     431                 :          66 :         }
     432                 :          64 :         return false;  // no range, continue field checking
     433                 :          74 :     }
     434                 :             :     
     435                 :          64 :     bool m_isValues(Field& vInOutField) {
     436         [ +  + ]:          64 :         if (!vInOutField.str.empty()) {
     437         [ +  + ]:          59 :             if (vInOutField.str.front() == ',') {
     438                 :           3 :                 m_addError(INVALID_VALUES | m_getErrorFlagFromIndex(vInOutField.index),  //
     439                 :           3 :                            vInOutField.cpos,
     440                 :           3 :                            vInOutField.index);
     441                 :           3 :                 return true;  // range, stop field checking
     442         [ +  + ]:          56 :             } else if (vInOutField.str.back() == ',') {
     443                 :           1 :                 m_addError(INVALID_VALUES | m_getErrorFlagFromIndex(vInOutField.index),  //
     444                 :           1 :                            vInOutField.cpos + vInOutField.str.size() - 1,
     445                 :           1 :                            vInOutField.index);
     446                 :           1 :                 return true;  // range, stop field checking
     447                 :           1 :             }
     448                 :          55 :             auto tokens = m_split(vInOutField.str, ",");
     449         [ +  + ]:          55 :             if (!tokens.empty()) {
     450                 :           3 :                 vInOutField.type = FieldType::VALUES;
     451         [ -  + ]:           3 :                 if (tokens.size() == 1) {
     452                 :           0 :                     m_addError(INVALID_VALUES | m_getErrorFlagFromIndex(vInOutField.index),  //
     453                 :           0 :                                vInOutField.cpos + vInOutField.str.size(),
     454                 :           0 :                                vInOutField.index);
     455                 :           0 :                     return true;  // we not want to continue the field check
     456                 :           0 :                 }
     457         [ +  + ]:          10 :                 for (const auto& token : tokens) {
     458         [ +  + ]:          10 :                     if (!m_parseValue(vInOutField, token)) {
     459                 :           3 :                         m_addError(INVALID_VALUES, vInOutField.cpos + token.first, vInOutField.index);
     460                 :           3 :                     }
     461                 :          10 :                 }
     462                 :           3 :                 return true;  // values, stop field checking
     463                 :           3 :             }
     464                 :          55 :         }
     465                 :          57 :         return false;  // no values, continue field checking
     466                 :          64 :     }
     467                 :             :     
     468                 :         195 :     void m_parseField(const Token& vToken) {
     469                 :         195 :         Field field;
     470                 :         195 :         field.str = vToken.second;
     471                 :         195 :         field.index = static_cast<FieldIndex>(m_fields.size());
     472                 :         195 :         field.cpos = vToken.first;
     473         [ +  + ]:         195 :         if (!m_isInterval(field)) {
     474         [ +  + ]:          74 :             if (!m_isRange(field)) {
     475         [ +  + ]:          64 :                 if (!m_isValues(field)) {
     476                 :          57 :                     m_parseValue(field, vToken);
     477                 :          57 :                 }
     478                 :          64 :             }
     479                 :          74 :         }
     480                 :         195 :         m_fields.push_back(field);
     481                 :         195 :     }
     482                 :             :     
     483                 :          39 :     void m_parseExpression() {
     484                 :          39 :         m_fields.clear();
     485                 :          39 :         auto tokens = m_split(m_cronRule, " ");
     486                 :          39 :         auto count = static_cast<size_t>(FieldIndex::Count);
     487         [ +  + ]:          39 :         if (tokens.size() != count) {
     488                 :           3 :             m_addError(INVALID_FIELDS_COUNT, tokens.back().first, tokens.size() - 1);  // put the error on the last available field
     489                 :           3 :         }
     490         [ +  + ]:         195 :         for (const auto& token : tokens) {
     491                 :         195 :             m_parseField(token);
     492                 :         195 :         }
     493                 :          39 :     }
     494                 :             : 
     495                 :          61 :     std::string m_getErrorString(int32_t vFlag) const {
     496                 :          61 :         std::stringstream res;
     497         [ +  + ]:          61 :         if (vFlag & INVALID_MINUTE) {
     498                 :          22 :             res << " Invalid minute.";
     499                 :          22 :         }
     500         [ +  + ]:          61 :         if (vFlag & INVALID_HOUR) {
     501                 :           9 :             res << " Invalid hour.";
     502                 :           9 :         }
     503         [ +  + ]:          61 :         if (vFlag & INVALID_MONTH_DAY) {
     504                 :           9 :             res << " Invalid month day.";
     505                 :           9 :         }
     506         [ +  + ]:          61 :         if (vFlag & INVALID_MONTH) {
     507                 :           9 :             res << " Invalid month.";
     508                 :           9 :         }
     509         [ +  + ]:          61 :         if (vFlag & INVALID_WEEK_DAY) {
     510                 :           9 :             res << " Invalid week day.";
     511                 :           9 :         }
     512         [ +  + ]:          61 :         if (vFlag & INVALID_FIELDS_COUNT) {
     513                 :           3 :             res << " Invalid fields count.";
     514                 :           3 :         }
     515         [ +  + ]:          61 :         if (vFlag & INVALID_CHAR) {
     516                 :           7 :             res << " Invalid char.";
     517                 :           7 :         }
     518         [ +  + ]:          61 :         if (vFlag & INVALID_INTERVAL) {
     519                 :          25 :             res << " Invalid interval.";
     520                 :          25 :         }
     521         [ +  + ]:          61 :         if (vFlag & INVALID_RANGE) {
     522                 :           8 :             res << " Invalid range.";
     523                 :           8 :         }
     524         [ +  + ]:          61 :         if (vFlag & INVALID_VALUES) {
     525                 :           5 :             res << " Invalid values.";
     526                 :           5 :         }
     527         [ -  + ]:          61 :         if (vFlag & INVALID_FIELD) {
     528                 :           0 :             res << " Invalid field.";
     529                 :           0 :         }
     530                 :          61 :         return res.str();
     531                 :          61 :     }
     532                 :             : 
     533                 :          91 :     bool m_checkTimeForField(FieldIndex vFieldIndex, int32_t vValue) const {
     534                 :          91 :         const auto& field = m_fields.at(static_cast<size_t>(vFieldIndex));
     535                 :          91 :         switch (field.type) {
     536         [ +  + ]:          28 :             case FieldType::WILDCARD: {  // '*' is valid for all values
     537                 :          28 :                 return true;
     538                 :           0 :             }
     539         [ +  + ]:          33 :             case FieldType::VALUE: { // a == v
     540                 :          33 :                 return (field.value == vValue);
     541                 :           0 :             }
     542         [ +  + ]:          11 :             case FieldType::INTERVAL: { // each v
     543                 :          11 :                 return ((vValue % field.interval) == 0);
     544                 :           0 :             }
     545         [ +  + ]:          10 :             case FieldType::RANGE: { // a > v > b
     546         [ +  + ]:          10 :                 return (vValue >= field.range.first &&  //
     547         [ +  + ]:          10 :                         vValue <= field.range.second);
     548                 :           0 :             }
     549         [ +  + ]:           9 :             case FieldType::VALUES: { // a == v or b == v or ..
     550                 :           9 :                 return (field.values.find(vValue) != field.values.end());
     551                 :           0 :             }
     552         [ -  + ]:           0 :             case FieldType::Count:
     553         [ -  + ]:           0 :             default: break;
     554                 :          91 :         }
     555                 :           0 :         return false;
     556                 :          91 :     }
     557                 :             : };
     558                 :             : 
     559                 :             : }  // namespace time
     560                 :             : }  // namespace ez
     561                 :             : 
     562                 :             : ////////////////////////////////////////////////////////////////////////////
     563                 :             : ////////////////////////////////////////////////////////////////////////////
     564                 :             : ////////////////////////////////////////////////////////////////////////////
     565                 :             : 
     566                 :             : #ifdef _MSC_VER
     567                 :             : #pragma warning(pop)
     568                 :             : #elif defined(__GNUC__) || defined(__clang__)
     569                 :             : #pragma GCC diagnostic pop
     570                 :             : #endif
     571                 :             : 
     572                 :             : ////////////////////////////////////////////////////////////////////////////
     573                 :             : ////////////////////////////////////////////////////////////////////////////
     574                 :             : ////////////////////////////////////////////////////////////////////////////
        

Generated by: LCOV version 2.0-1