From 311b6689fe97d7d2bbb68a4bd51ed94c8d3050b4 Mon Sep 17 00:00:00 2001
From: vincent <vincent@groupe-sa.fr>
Date: Wed, 23 Dec 2015 10:03:39 +0100
Subject: [PATCH] QCron::next returns the next date matching the cron pattern.

---
 src/qcron.cpp        | 181 ++++++++++++++++++++++-
 src/qcron.hpp        |   6 +-
 src/qcronfield.cpp   | 165 +++++++++++++--------
 src/qcronfield.hpp   |  30 +++-
 src/qcronnode.cpp    | 228 +++++++++++++++++++++++++++--
 src/qcronnode.hpp    |  47 +++++-
 test/qcron_test.cpp  | 337 +++++++++++++++++++++++++++++++++++++++++--
 test/qcron_test.hpp  |   6 +-
 test/syntax_test.cpp |  39 ++---
 9 files changed, 921 insertions(+), 118 deletions(-)

diff --git a/src/qcron.cpp b/src/qcron.cpp
index 734db92..d06bc92 100644
--- a/src/qcron.cpp
+++ b/src/qcron.cpp
@@ -1,4 +1,5 @@
 #include "qcron.hpp"
+#include "qcronnode.hpp"
 
 #include <cmath>
 
@@ -75,11 +76,11 @@ _parsePattern(QString & pattern)
     }
     try
     {
-    for (int i = 0; i < 6; ++i)
-    {
-        _fields[i].parse(fields[i]);
-        _is_valid &= _fields[i].isValid();
-    }
+        for (int i = 0; i < 6; ++i)
+        {
+            _fields[i].parse(fields[i]);
+            _is_valid &= _fields[i].isValid();
+        }
     }
     catch (QCronFieldException & e)
     {
@@ -87,15 +88,167 @@ _parsePattern(QString & pattern)
     }
 }
 
+/******************************************************************************/
+
+QList<EField>
+getPreviousFields(EField field)
+{
+    QList<EField> fields;
+
+    switch (field)
+    {
+        case YEAR:
+            fields << MONTH;
+        case MONTH:
+            fields << DOM;
+        case DOW:
+        case DOM:
+            fields << HOUR;
+        case HOUR:
+            fields << MINUTE;
+        case MINUTE:
+            break;
+        default:
+            qFatal("Should not be in getPreviousTimeUnit");
+    }
+
+    return fields;
+}
+
+/******************************************************************************/
+
+void
+QCron::
+add(QDateTime & dt, EField field, int value)
+{
+    switch (field)
+    {
+        case YEAR:
+            dt = dt.addYears(value);
+            break;
+        case MONTH:
+            dt = dt.addMonths(value);
+            break;
+        case DOW:
+        case DOM:
+            dt = dt.addDays(value);
+            break;
+        case HOUR:
+            dt = dt.addSecs(3600 * value);
+            break;
+        case MINUTE:
+            dt = dt.addSecs(60 * value);
+            break;
+        default:
+            qFatal("Unknown value in add");
+    }
+    QList<EField> previous_fields = getPreviousFields(field);
+    foreach (EField field, previous_fields)
+    {
+        _fields[field].reset(dt);
+    }
+}
+
+/******************************************************************************/
+
+void
+set(QDateTime & dt, EField field, int value)
+{
+    QDate date = dt.date();
+    QTime time = dt.time();
+
+    switch (field)
+    {
+        case YEAR:
+            dt.setDate(QDate(value, date.month(), date.day()));
+            break;
+        case MONTH:
+            dt.setDate(QDate(date.year(), value, date.day()));
+            break;
+        case DOW:
+        case DOM:
+            dt.setDate(QDate(date.year(), date.month(), value));
+            break;
+        case HOUR:
+            dt.setTime(QTime(value, time.minute(), 0));
+            break;
+        case MINUTE:
+            dt.setTime(QTime(time.hour(), value, 0));
+            break;
+        default:
+            qFatal("Unknown value in add");
+    }
+}
+
+/******************************************************************************/
+
+void
+QCron::
+catchUp(QDateTime & dt, EField field, int value)
+{
+    int current_time_unit = _fields[field].getDateTimeSection(dt);
+    if (current_time_unit < value)
+    {
+        add(dt, field, value - current_time_unit);
+    }
+    else if (current_time_unit == value)
+    {
+        // Nothing
+    }
+    else if (current_time_unit > value)
+    {
+        if (YEAR == field)
+        {
+            dt = QDateTime();
+        }
+        else if (MINUTE != field)
+        {
+            int max = _fields[field].getMax();
+            add(dt, field, (max - current_time_unit + value));
+        }
+    }
+}
+
+/******************************************************************************/
+
+void
+QCron::
+chiche(QDateTime & dt, EField field)
+{
+    QCronNode * node = _fields[field].getRoot();
+    if (NULL == node)
+    {
+        qFatal("Problem");
+    }
+    node->process(this, dt, field);
+}
+
+
 /******************************************************************************/
 
 QDateTime
 QCron::
 next(QDateTime dt)
 {
-    for (int i = 0; i < 6; ++i)
+    dt = dt.addSecs(60);
+    while (!match(dt))
     {
-        _fields[i].next(dt);
+        //qDebug() << dt << "doesn't match";
+        for (int i = YEAR; i >= 0; --i)
+        {
+            chiche(dt, (EField)i);
+            //qDebug() << dt << "after Chcihe";
+            if (!dt.isValid())
+            {
+                return dt;
+            }
+            while (!_fields[(EField)i].match(dt))
+            {
+                //qDebug() << dt << (EField)i << "doesn't match";
+                int dummy = 1;
+                _fields[(EField)i].applyOffset(dt, dummy);
+            }
+        }
     }
     return dt;
 }
@@ -111,3 +264,17 @@ next(int n)
 }
 
 /******************************************************************************/
+
+bool
+QCron::
+match(const QDateTime & dt) const
+{
+    bool does_match = true;
+    for (int i = 0; i < 6; ++i)
+    {
+        does_match &= _fields[i].match(dt);
+    }
+    return does_match;
+}
+
+/******************************************************************************/
diff --git a/src/qcron.hpp b/src/qcron.hpp
index 0296ee1..e0cba5c 100644
--- a/src/qcron.hpp
+++ b/src/qcron.hpp
@@ -28,6 +28,10 @@ public:
 
     QDateTime next(int n = 1);
     QDateTime next(QDateTime dt);
+    void catchUp(QDateTime & dt, EField field, int value);
+    bool match(const QDateTime & dt) const;
+    void add(QDateTime & dt, EField field, int value);
+
 
 signals:
     void activated();
@@ -45,7 +49,7 @@ private:
     void _parseField(QString & field_str,
                      EField field);
     QString _validCharacters(EField field);
-
+    void chiche(QDateTime & dt, EField field);
 };
 
 #endif
diff --git a/src/qcronfield.cpp b/src/qcronfield.cpp
index 93d84bc..ae10e8c 100644
--- a/src/qcronfield.cpp
+++ b/src/qcronfield.cpp
@@ -1,4 +1,5 @@
 #include "qcronfield.hpp"
+#include "qcronnode.hpp"
 
 #include <QDebug>
 
@@ -29,7 +30,6 @@ _parseInt(QString & str)
                                   .arg(str));
     }
     str.remove(0, char_idx);
-    //qDebug() << "Parsing int" << value;
     if (value < _min || _max < value)
     {
         throw QCronFieldException(QString("Value %1 out of range [%2;%3]")
@@ -40,11 +40,10 @@ _parseInt(QString & str)
 
 /******************************************************************************/
 
-QCronRangeNode*
+QCronRangeNode *
 QCronField::
 _parseRange(QString & str)
 {
-    //   qDebug() << "Parsing a Range";
     if (_last_node == NULL)
     {
         throw QCronFieldException(QString("Syntax error at %1: range has no beginning")
@@ -73,28 +72,29 @@ _parseRange(QString & str)
 
 /******************************************************************************/
 
-QCronEveryNode*
+QCronEveryNode *
 QCronField::
 _parseEvery(QString & str)
 {
-//    qDebug() << "Parsing an Every";
     str.remove(0, 1);
     return new QCronEveryNode(_last_node, _parseInt(str));
 }
 
 /******************************************************************************/
 
-QCronListNode*
+QCronListNode *
 QCronField::
 _parseList(QString & str)
 {
-//    qDebug() << "Parsing a List";
     QCronListNode * list = new QCronListNode();
     list->nodes() << _last_node;
     _last_node = list;
-    while (str[0] == ',')
+    while (!str.isEmpty())
     {
-        str.remove(0, 1);
+        if (str[0] == ',')
+        {
+            str.remove(0, 1);
+        }
         QCronNode * node = _parseNode(str);
         list->nodes() << node;
         _last_node = node;
@@ -108,29 +108,35 @@ QCronNode *
 QCronField::
 _parseNode(QString & str)
 {
-    //qDebug() << "Parsing a node";
+    QCronNode * node = NULL;
     QChar c = str[0];
     if (c.isDigit())
     {
-        return _parseInt(str);
+        node = _parseInt(str);
     }
     else if ("-" == c)
     {
-        return _parseRange(str);
+        node = _parseRange(str);
     }
     else if ("/" == c)
     {
-        return _parseEvery(str);
+        node = _parseEvery(str);
     }
     else if ("*" == c)
     {
-        return new QCronAllNode;
+        str.remove(0, 1);
+        node = new QCronAllNode;
     }
     else if ("," == c)
     {
-        return _parseList(str);
+        node = _parseList(str);
     }
-    throw QCronFieldException(QString("Unexpected character %1").arg(c));
+    if (NULL == node)
+    {
+        throw QCronFieldException(QString("Unexpected character %1").arg(c));
+    }
+    node->setField(this);
+    return node;
 }
 
 /******************************************************************************/
@@ -139,38 +145,38 @@ void
 QCronField::
 parse(QString & str)
 {
-    try
+    _last_node = NULL;
+    _root = _parseNode(str);
+    while (!str.isEmpty())
     {
-        _last_node = NULL;
+        _last_node = _root;
         _root = _parseNode(str);
-        if (!str.isEmpty())
-        {
-            _last_node = _root;
-            _root = _parseNode(str);
-        }
-        _is_valid = true;
-    }
-    catch (int)
-    {
-        _is_valid = false;
     }
+    _is_valid = true;
 }
 
 /******************************************************************************/
 
 int
 QCronField::
-getDateTimeSection(QDateTime & dt) const
+getDateTimeSection(const QDateTime & dt) const
 {
     switch (_field)
     {
-    case MINUTE: return dt.time().minute();
-    case HOUR:   return dt.time().hour();
-    case DOM:    return dt.date().day();
-    case MONTH:  return dt.date().month();
-    case DOW:    return dt.date().dayOfWeek();
-    case YEAR:   return dt.date().year();
-    default:     qFatal("Shouldn't be here");
+        case MINUTE:
+            return dt.time().minute();
+        case HOUR:
+            return dt.time().hour();
+        case DOM:
+            return dt.date().day();
+        case MONTH:
+            return dt.date().month();
+        case DOW:
+            return dt.date().dayOfWeek();
+        case YEAR:
+            return dt.date().year();
+        default:
+            qFatal("Shouldn't be here");
     }
 }
 
@@ -178,43 +184,37 @@ getDateTimeSection(QDateTime & dt) const
 
 void
 QCronField::
-applyOffset(QDateTime & dt, int offset) const
+applyOffset(QDateTime & dt, int & offset) const
 {
-    bool overflow = offset < 0 || (offset == 0 && _field == MINUTE);
-
-    offset += overflow ? 1 : 0;
-
     switch (_field)
     {
-    case MINUTE:
-    {
-        offset -= overflow ? 1 : 0;
-        offset += offset <= 0 ? 60 : 0;
-        dt = dt.addSecs(60 * offset);
-        break;
-    }
-    case HOUR:
+        case MINUTE:
         {
-            dt.addSecs(3600 * offset);
+            dt = dt.addSecs(60 * offset);
             break;
         }
-    case DOM:
-    case DOW:
+        case HOUR:
         {
-            dt.addDays(offset);
+            dt = dt.addSecs(3600 * offset);
             break;
         }
-    case MONTH:
+        case DOM:
+        case DOW:
         {
-            dt.addMonths(offset);
+            dt = dt.addDays(offset);
             break;
         }
-    case YEAR:
+        case MONTH:
         {
-            dt.addYears(offset);
+            dt = dt.addMonths(offset);
             break;
         }
-    default:
+        case YEAR:
+        {
+            dt = dt.addYears(offset);
+            break;
+        }
+        default:
         {
             qFatal("Shouldn't be here");
         }
@@ -223,11 +223,54 @@ applyOffset(QDateTime & dt, int offset) const
 
 /******************************************************************************/
 
-void
+int
 QCronField::
 next(QDateTime & dt)
 {
     int time_section = getDateTimeSection(dt);
-    int offset = _root->next(time_section);
-    applyOffset(dt, offset);
+    return _root->next(time_section);
 }
+
+/******************************************************************************/
+
+void
+QCronField::
+reset(QDateTime & dt)
+{
+    int value = _min;
+    QDate date = dt.date();
+    QTime time = dt.time();
+
+    switch (_field)
+    {
+        case YEAR:
+            dt.setDate(QDate(value, date.month(), date.day()));
+            break;
+        case MONTH:
+            dt.setDate(QDate(date.year(), value, date.day()));
+            break;
+        case DOW:
+        case DOM:
+            dt.setDate(QDate(date.year(), date.month(), value));
+            break;
+        case HOUR:
+            dt.setTime(QTime(value, time.minute(), 0));
+            break;
+        case MINUTE:
+            dt.setTime(QTime(time.hour(), value, 0));
+            break;
+        default:
+            qFatal("Unknown value in add");
+    }
+}
+
+/******************************************************************************/
+
+bool
+QCronField::
+match(const QDateTime & dt) const
+{
+    return _root->match(getDateTimeSection(dt));
+}
+
+/******************************************************************************/
diff --git a/src/qcronfield.hpp b/src/qcronfield.hpp
index 4aa60ce..577bf0f 100644
--- a/src/qcronfield.hpp
+++ b/src/qcronfield.hpp
@@ -4,7 +4,6 @@
 #include <QDateTime>
 #include <QList>
 #include <QString>
-#include "qcronnode.hpp"
 
 enum EField
 {
@@ -16,6 +15,12 @@ enum EField
     YEAR
 };
 
+class QCronIntNode;
+class QCronRangeNode;
+class QCronNode;
+class QCronEveryNode;
+class QCronListNode;
+
 class QCronFieldException
 {
 public:
@@ -47,21 +52,36 @@ public:
             case DOM:    _min = 1; _max = 31;      break;
             case MONTH:  _min = 1; _max = 12;      break;
             case DOW:    _min = 1; _max = 7 ;      break;
-            case YEAR:   _min = 2016; _max = 2099; break;
+            case YEAR:   _min = 1; _max = 2099; break;
             default:     throw 42;
             }
         }
+    EField getField() const
+        { return _field; }
+
+    int getMax() const
+        { return _max; }
+
+    int getMin() const
+        { return _min; }
 
     // Features.
     void parse(QString & str);
 
     bool isValid() const
         { return _is_valid; }
+    bool match(const QDateTime & dt) const;
 
-    void next(QDateTime & dt);
+    QCronNode * getRoot() const
+        { return _root; }
+
+    int next(QDateTime & dt);
+
+    int getDateTimeSection(const QDateTime & dt) const;
+    void applyOffset(QDateTime & dt, int & offset) const;
+
+    void reset(QDateTime & dt);
 
-    int getDateTimeSection(QDateTime & dt) const;
-    void applyOffset(QDateTime & dt, int offset) const;
 
 private:
     int _min;
diff --git a/src/qcronnode.cpp b/src/qcronnode.cpp
index d4b89fc..965ea09 100644
--- a/src/qcronnode.cpp
+++ b/src/qcronnode.cpp
@@ -1,4 +1,10 @@
 #include "qcronnode.hpp"
+#include "qcronfield.hpp"
+#include "qcron.hpp"
+
+#include <QDebug>
+
+#include <QDebug>
 
 /******************************************************************************/
 
@@ -9,6 +15,16 @@ QCronNode::
 
 /******************************************************************************/
 
+void
+QCronNode::
+setField(QCronField * field)
+{
+    _field = field;
+}
+
+/******************************************************************************/
+/******************************************************************************/
+
 QCronIntNode::
 QCronIntNode(int v)
     : _value(v)
@@ -35,25 +51,91 @@ next(int t) const
 
 /******************************************************************************/
 
+void
+QCronIntNode::
+process(QCron * cron,
+        QDateTime & dt,
+        EField field)
+{
+    cron->catchUp(dt, field, _value);
+}
+
+/******************************************************************************/
+
+bool
+QCronIntNode::
+match(int tu) const
+{
+    return tu == _value;
+}
+
+/******************************************************************************/
+/******************************************************************************/
+
 int
 QCronStrNode::
 next(int t) const
-        {
-            return t - 1;
-        }
+{
+    return t - 1;
+}
 
 /******************************************************************************/
 
+void
+QCronStrNode::
+process(QCron *,
+        QDateTime &,
+        EField )
+{
+    // TODO
+}
+
+/******************************************************************************/
+
+bool
+QCronStrNode::
+match(int tu) const
+{
+    return tu;
+}
+
+/******************************************************************************/
+/******************************************************************************/
+
 int
 QCronAllNode::
 next(int t) const
 {
     Q_UNUSED(t);
-    return 1;
+    return 0;
 }
 
 /******************************************************************************/
 
+void
+QCronAllNode::
+process(QCron * cron,
+        QDateTime & dt,
+        EField field)
+{
+    Q_UNUSED(cron);
+    Q_UNUSED(dt);
+    Q_UNUSED(field);
+}
+
+/******************************************************************************/
+
+bool
+QCronAllNode::
+match(int tu) const
+{
+    Q_UNUSED(tu);
+    return true;
+}
+
+/******************************************************************************/
+/******************************************************************************/
+
 QCronRangeNode::
 QCronRangeNode(const QCronIntNode * begin,
                const QCronIntNode * end)
@@ -64,19 +146,71 @@ QCronRangeNode(const QCronIntNode * begin,
 
 /******************************************************************************/
 
+void
+QCronRangeNode::
+process(QCron * cron,
+        QDateTime & dt,
+        EField field)
+{
+    int begin = beginValue();
+    int end = endValue();
+    int current_time_unit = _field->getDateTimeSection(dt);
+    if (current_time_unit < begin || end < current_time_unit)
+    {
+        cron->catchUp(dt, field, begin);
+    }
+    else
+    {
+        if (field == MINUTE)
+        {
+            cron->add(dt, field, 1);
+        }
+    }
+}
+
+/******************************************************************************/
+
+int
+QCronRangeNode::
+beginValue() const
+{
+    return _begin->value();
+}
+
+/******************************************************************************/
+
+int
+QCronRangeNode::
+endValue() const
+{
+    return _end->value();
+}
+
+/******************************************************************************/
+
 int
 QCronRangeNode::
 next(int t) const
 {
-    if (_begin->value() <= t && t < _end->value())
+    if (_begin->value() <= t && t <= _end->value())
     {
-        return 0;
+        return 1;
     }
     return _begin->value() - t;
 }
 
 /******************************************************************************/
 
+bool
+QCronRangeNode::
+match(int tu) const
+{
+    return _begin->value() <= tu && tu <= _end->value();
+}
+
+/******************************************************************************/
+/******************************************************************************/
+
 QCronEveryNode::
 QCronEveryNode(QCronNode * what,
                QCronIntNode * freq)
@@ -85,27 +219,82 @@ QCronEveryNode(QCronNode * what,
 {
 }
 
+/******************************************************************************/
+
+void
+QCronEveryNode::
+process(QCron * cron ,
+        QDateTime & dt,
+        EField field)
+{
+    int freq = _freq->value();
+    _what->process(cron, dt, field);
+    while (_field->getDateTimeSection(dt) % freq)
+    {
+        cron->add(dt, field, 1);
+        _what->process(cron, dt, field);
+    }
+}
+
+
 /******************************************************************************/
 
 int
 QCronEveryNode::
 next(int t) const
 {
-    int next = _what->next(t) % _freq->value();
-    return _freq->value() - next;
+    int what_next = _what->next(t);
+    if (what_next == 1)
+    {
+        /* what_next == 1 if we're in the right range AND if we're one
+         * time unit ahead of it */
+        int next = t % _freq->value();
+        return _freq->value() - next;
+    }
+    return what_next;
 }
 
 /******************************************************************************/
 
-QList<QCronNode *>
+bool
+QCronEveryNode::
+match(int tu) const
+{
+    return _what->match(tu) && tu % _freq->value() == 0;
+}
+
+/******************************************************************************/
+/******************************************************************************/
+
+QList<QCronNode *> &
 QCronListNode::
-nodes() const
+nodes()
 {
     return _nodes;
 }
 
 /******************************************************************************/
 
+void
+QCronListNode::
+process(QCron * cron,
+        QDateTime & dt,
+        EField field)
+{
+    foreach (QCronNode * node, _nodes)
+    {
+        int node_next = node->next(_field->getDateTimeSection(dt));
+        if (node_next >= 0)
+        {
+            node->process(cron, dt, field);
+            return;
+        }
+    }
+    _nodes[0]->process(cron, dt, field);
+}
+
+/******************************************************************************/
+
 int
 QCronListNode::
 next(int t) const
@@ -113,7 +302,7 @@ next(int t) const
     foreach (const QCronNode * node, _nodes)
     {
         int node_next = node->next(t);
-        if (t > 0)
+        if (node_next > 0 && node_next != _field->getMax() + 1)
         {
             return node_next;
         }
@@ -122,3 +311,20 @@ next(int t) const
 }
 
 /******************************************************************************/
+
+bool
+QCronListNode::
+match(int tu) const
+{
+    foreach (const QCronNode * node, _nodes)
+    {
+        if (node->match(tu))
+        {
+            return true;
+        }
+    }
+    return false;
+}
+
+/******************************************************************************/
+/******************************************************************************/
diff --git a/src/qcronnode.hpp b/src/qcronnode.hpp
index bd28803..6a8ed4b 100644
--- a/src/qcronnode.hpp
+++ b/src/qcronnode.hpp
@@ -4,12 +4,27 @@
 #include <QList>
 #include <QtGlobal>
 
+#include "qcronfield.hpp"
+
+class QCron;
+class QCronField;
+
 class QCronNode
 {
 public:
     virtual ~QCronNode();
 
+    void setField(QCronField * field);
+
     virtual int next(int t) const = 0;
+
+    virtual bool match(int tu) const = 0;
+    virtual void process(QCron * cron,
+                         QDateTime & dt,
+                         EField field) = 0;
+
+protected:
+    QCronField * _field;
 };
 
 class QCronValueNode : public QCronNode
@@ -24,6 +39,11 @@ public:
     int value() const;
 
     virtual int next(int t) const Q_DECL_OVERRIDE;
+    virtual bool match(int tu) const Q_DECL_OVERRIDE;
+
+    virtual void process(QCron * cron,
+                         QDateTime & dt,
+                         EField field) Q_DECL_OVERRIDE;
 
 private:
     int _value;
@@ -33,12 +53,20 @@ class QCronStrNode : public QCronValueNode
 {
 public:
     virtual int next(int t) const Q_DECL_OVERRIDE;
+    virtual bool match(int tu) const Q_DECL_OVERRIDE;
+    virtual void process(QCron * cron,
+                         QDateTime & dt,
+                         EField field) Q_DECL_OVERRIDE;
 };
 
 class QCronAllNode : public QCronValueNode
 {
 public:
     virtual int next(int t) const Q_DECL_OVERRIDE;
+    virtual bool match(int tu) const Q_DECL_OVERRIDE;
+    virtual void process(QCron * cron,
+                         QDateTime & dt,
+                         EField field) Q_DECL_OVERRIDE;
 };
 
 class QCronRangeNode : public QCronNode
@@ -47,7 +75,15 @@ public:
     QCronRangeNode(const QCronIntNode * begin,
                    const QCronIntNode * end);
 
+    int beginValue() const;
+    int endValue() const;
+
     virtual int next(int t) const Q_DECL_OVERRIDE;
+    virtual bool match(int tu) const Q_DECL_OVERRIDE;
+
+    virtual void process(QCron * cron,
+                         QDateTime & dt,
+                         EField field) Q_DECL_OVERRIDE;
 
 private:
     const QCronIntNode * _begin;
@@ -60,6 +96,11 @@ public:
     QCronEveryNode(QCronNode *, QCronIntNode *);
 
     virtual int next(int t) const Q_DECL_OVERRIDE;
+    virtual bool match(int tu) const Q_DECL_OVERRIDE;
+
+    virtual void process(QCron * cron,
+                         QDateTime & dt,
+                         EField field) Q_DECL_OVERRIDE;
 
 private:
     QCronNode * _what;
@@ -69,9 +110,13 @@ private:
 class QCronListNode : public QCronNode
 {
 public:
-    QList<QCronNode *> nodes() const;
+    QList<QCronNode *> & nodes();
 
     virtual int next(int t) const Q_DECL_OVERRIDE;
+    virtual bool match(int tu) const Q_DECL_OVERRIDE;
+    virtual void process(QCron * cron,
+                         QDateTime & dt,
+                         EField field) Q_DECL_OVERRIDE;
 
 private:
     QList<QCronNode*> _nodes;
diff --git a/test/qcron_test.cpp b/test/qcron_test.cpp
index 10023eb..76956d1 100644
--- a/test/qcron_test.cpp
+++ b/test/qcron_test.cpp
@@ -11,8 +11,8 @@ void
 QCronTest::
 init()
 {
-    _dnow = QDate::currentDate();
-    _tnow = QTime(0,0,0);
+    _dnow = QDate(1, 1, 1);
+    _tnow = QTime(0, 0, 0);
 }
 
 /******************************************************************************/
@@ -28,29 +28,338 @@ actual(QString & pattern)
 
 /******************************************************************************/
 
+void
+QCronTest::
+minutes()
+{
+    // Star
+    QString pattern = "* * * * * *";
+    _tnow.setHMS(0, 0, 0);
+    QCOMPARE(actual(pattern), now().addSecs(60 * 1));
+
+    // Int
+    pattern = "30 * * * * *";
+    QCOMPARE(actual(pattern), now().addSecs(60 * 30));
+    _tnow.setHMS(0, 31, 0);
+    QCOMPARE(actual(pattern), now().addSecs(60 * 59));
+    _tnow.setHMS(0, 30, 0);
+    QCOMPARE(actual(pattern), now().addSecs(60 * 60));
+
+    pattern = "0 * * * * *";
+    _tnow.setHMS(0, 0, 0);
+    QCOMPARE(actual(pattern), now().addSecs(60 * 60));
+    _tnow.setHMS(0, 31, 0);
+    QCOMPARE(actual(pattern), now().addSecs(60 * 29));
+    _tnow.setHMS(0, 59, 0);
+    QCOMPARE(actual(pattern), now().addSecs(60 * 1));
+
+    pattern = "59 * * * * *";
+    _tnow.setHMS(0, 0, 0);
+    QCOMPARE(actual(pattern), now().addSecs(60 * 59));
+    _tnow.setHMS(0, 58, 0);
+    QCOMPARE(actual(pattern), now().addSecs(60 * 1));
+    _tnow.setHMS(0, 59, 0);
+    QCOMPARE(actual(pattern), now().addSecs(60 * 60));
+
+    // Range
+    pattern = "10-20 * * * * *";
+    _tnow.setHMS(0, 5, 0);
+    QCOMPARE(actual(pattern), now().addSecs(60 * 5));
+    _tnow.setHMS(0, 9, 0);
+    QCOMPARE(actual(pattern), now().addSecs(60 * 1));
+    _tnow.setHMS(0, 10, 0);
+    QCOMPARE(actual(pattern), now().addSecs(60 * 1));
+    _tnow.setHMS(0, 15, 0);
+    QCOMPARE(actual(pattern), now().addSecs(60 * 1));
+    _tnow.setHMS(0, 20, 0);
+    QCOMPARE(actual(pattern), now().addSecs(60 * 50));
+    _tnow.setHMS(0, 30, 0);
+    QCOMPARE(actual(pattern), now().addSecs(60 * 40));
+
+    // Every - easy
+    pattern = "*/5 * * * * *";
+    _tnow.setHMS(0, 0, 0);
+    QCOMPARE(actual(pattern), now().addSecs(60 * 5));
+    _tnow.setHMS(0, 4, 0);
+    QCOMPARE(actual(pattern), now().addSecs(60 * 1));
+    _tnow.setHMS(0, 5, 0);
+    QCOMPARE(actual(pattern), now().addSecs(60 * 5));
+    _tnow.setHMS(0, 16, 0);
+    QCOMPARE(actual(pattern), now().addSecs(60 * 4));
+    _tnow.setHMS(0, 54, 0);
+    QCOMPARE(actual(pattern), now().addSecs(60 * 1));
+    _tnow.setHMS(0, 57, 0);
+    QCOMPARE(actual(pattern), now().addSecs(60 * 3));
+
+    // Every - medium
+    pattern = "14-28/2 * * * * *";
+    _tnow.setHMS(0, 0, 0);
+    QCOMPARE(actual(pattern), now().addSecs(60 * 14));
+    _tnow.setHMS(0, 13, 0);
+    QCOMPARE(actual(pattern), now().addSecs(60 * 1));
+    _tnow.setHMS(0, 14, 0);
+    QCOMPARE(actual(pattern), now().addSecs(60 * 2));
+    _tnow.setHMS(0, 15, 0);
+    QCOMPARE(actual(pattern), now().addSecs(60 * 1));
+    _tnow.setHMS(0, 16, 0);
+    QCOMPARE(actual(pattern), now().addSecs(60 * 2));
+    _tnow.setHMS(0, 28, 0);
+    QCOMPARE(actual(pattern), now().addSecs(60 * 46));
+
+    // List - easy
+    pattern = "10,15,16 * * * * *";
+    _tnow.setHMS(0, 5, 0);
+    QCOMPARE(actual(pattern), now().addSecs(60 * 5));
+    _tnow.setHMS(0, 9, 0);
+    QCOMPARE(actual(pattern), now().addSecs(60 * 1));
+    _tnow.setHMS(0, 10, 0);
+    QCOMPARE(actual(pattern), now().addSecs(60 * 5));
+    _tnow.setHMS(0, 14, 0);
+    QCOMPARE(actual(pattern), now().addSecs(60 * 1));
+    _tnow.setHMS(0, 15, 0);
+    QCOMPARE(actual(pattern), now().addSecs(60 * 1));
+    _tnow.setHMS(0, 16, 0);
+    QCOMPARE(actual(pattern), now().addSecs(60 * 54));
+    _tnow.setHMS(0, 17, 0);
+    QCOMPARE(actual(pattern), now().addSecs(60 * 53));
+
+    // List - medium
+    pattern = "10,15-20,30 * * * * *";
+    _tnow.setHMS(0, 5, 0);
+    QCOMPARE(actual(pattern), now().addSecs(60 * 5));
+    _tnow.setHMS(0, 9, 0);
+    QCOMPARE(actual(pattern), now().addSecs(60 * 1));
+    _tnow.setHMS(0, 10, 0);
+    QCOMPARE(actual(pattern), now().addSecs(60 * 5));
+    _tnow.setHMS(0, 15, 0);
+    QCOMPARE(actual(pattern), now().addSecs(60 * 1));
+    _tnow.setHMS(0, 19, 0);
+    QCOMPARE(actual(pattern), now().addSecs(60 * 1));
+    _tnow.setHMS(0, 20, 0);
+    QCOMPARE(actual(pattern), now().addSecs(60 * 10));
+    _tnow.setHMS(0, 30, 0);
+    QCOMPARE(actual(pattern), now().addSecs(60 * 40));
+}
+
+/******************************************************************************/
+
 QDateTime
 QCronTest::
-expected(int offset)
+now()
 {
-    QDateTime exp(_dnow, _tnow);
-    return exp.addSecs(offset * 60);
+    return QDateTime(_dnow, _tnow);
+}
+
+/******************************************************************************/
+
+
+void
+QCronTest::
+years()
+{
+    // Int
+    QString pattern = "* * * * * 30";
+    _dnow.setDate(15, 1, 2);
+    QCOMPARE(actual(pattern), now().addYears(15).addDays(-1));
+    _dnow.setDate(31, 1, 1);
+    QCOMPARE(actual(pattern), QDateTime());
+    _dnow.setDate(30, 1, 1);
+    QCOMPARE(actual(pattern), now().addSecs(60));
+    _dnow.setDate(30, 12, 31);
+    _tnow.setHMS(23, 59, 0);
+    QCOMPARE(actual(pattern), QDateTime());
+
+    pattern = "* * * 6 * 30";
+    _dnow.setDate(15, 1, 1);
+    _tnow.setHMS(0, 0, 0);
+    QCOMPARE(actual(pattern), QDateTime(QDate(30, 6, 1), _tnow));
+    _dnow.setDate(31, 1, 1);
+    QCOMPARE(actual(pattern), QDateTime());
+    _dnow.setDate(30, 6, 1);
+    QCOMPARE(actual(pattern), now().addSecs(60));
+
+    // Range
+    pattern = "* * * * * 10-20";
+    _dnow.setDate(5, 1, 1);
+    QCOMPARE(actual(pattern), now().addYears(5));
+    _dnow.setDate(9, 1, 1);
+    QCOMPARE(actual(pattern), now().addYears(1));
+    _dnow.setDate(10, 1, 1);
+    QCOMPARE(actual(pattern), now().addSecs(60));
+    _dnow.setDate(15, 1, 1);
+    QCOMPARE(actual(pattern), now().addSecs(60));
+    _dnow.setDate(20, 12, 31);
+    _tnow.setHMS(23, 59, 0);
+    QCOMPARE(actual(pattern), QDateTime());
+    _dnow.setDate(20, 12, 31);
+    QCOMPARE(actual(pattern), QDateTime());
+
+    // Every - easy
+    pattern = "* * * * * */5";
+    _dnow.setDate(1, 1, 1);
+    _tnow.setHMS(0, 0, 0);
+    QCOMPARE(actual(pattern), now().addYears(4));
+    _dnow.setDate(4, 1, 1);
+    QCOMPARE(actual(pattern), now().addYears(1));
+    _dnow.setDate(5, 1, 1);
+    QCOMPARE(actual(pattern), now().addSecs(60));
+    _dnow.setDate(16, 1, 1);
+    QCOMPARE(actual(pattern), now().addYears(4));
+    _dnow.setDate(54, 1, 1);
+    QCOMPARE(actual(pattern), now().addYears(1));
+    _dnow.setDate(57, 1, 1);
+    QCOMPARE(actual(pattern), now().addYears(3));
+
+    // Every - medium
+    pattern = "* * * * * 14-28/2";
+    _dnow.setDate(1, 1, 1);
+    QCOMPARE(actual(pattern), now().addYears(13));
+    _dnow.setDate(13, 1, 1);
+    QCOMPARE(actual(pattern), now().addYears(1));
+    _dnow.setDate(14, 1, 1);
+    QCOMPARE(actual(pattern), now().addSecs(60));
+    _dnow.setDate(15, 1, 1);
+    QCOMPARE(actual(pattern), now().addYears(1));
+    _dnow.setDate(16, 1, 1);
+    QCOMPARE(actual(pattern), now().addSecs(60));
+    _dnow.setDate(28, 12, 31);
+    QCOMPARE(actual(pattern), now().addSecs(60));
+    _tnow.setHMS(23, 59, 0);
+    QCOMPARE(actual(pattern), QDateTime());
+
+    // List - easy
+    pattern = "* * * * * 10,15,16";
+    _dnow.setDate(5, 1, 1);
+    _tnow.setHMS(0, 0, 0);
+    QCOMPARE(actual(pattern), now().addYears(5));
+    _dnow.setDate(9, 1, 1);
+    QCOMPARE(actual(pattern), now().addYears(1));
+    _dnow.setDate(10, 1, 1);
+    QCOMPARE(actual(pattern), now().addSecs(60));
+    _dnow.setDate(14, 1, 1);
+    QCOMPARE(actual(pattern), now().addYears(1));
+    _dnow.setDate(15, 1, 1);
+    QCOMPARE(actual(pattern), now().addSecs(60));
+    _dnow.setDate(16, 1, 1);
+    QCOMPARE(actual(pattern), now().addSecs(60));
+    _dnow.setDate(16, 12, 31);
+    _tnow.setHMS(23, 59, 00);
+    QCOMPARE(actual(pattern), QDateTime());
+
+    // List - medium
+    pattern = "* * * * * 10,15-20,30";
+    _dnow.setDate(5, 1, 1);
+    _tnow.setHMS(0, 0, 0);
+    QCOMPARE(actual(pattern), now().addYears(5));
+    _dnow.setDate(9, 1, 1);
+    QCOMPARE(actual(pattern), now().addYears(1));
+    _dnow.setDate(10, 1, 1);
+    QCOMPARE(actual(pattern), now().addSecs(60));
+    _dnow.setDate(15, 1, 1);
+    QCOMPARE(actual(pattern), now().addSecs(60));
+    _dnow.setDate(20, 1, 1);
+    QCOMPARE(actual(pattern), now().addSecs(60));
+    _dnow.setDate(20, 12, 31);
+    _tnow.setHMS(23, 59, 0);
+    QCOMPARE(actual(pattern), now().addSecs(60).addYears(9));
+
+    // Star
+    pattern = "* * * * * *";
+    _dnow.setDate(1, 1, 1);
+    _tnow.setHMS(0, 0, 0);
+    QCOMPARE(actual(pattern), now().addSecs(60));
 }
 
 /******************************************************************************/
 
 void
 QCronTest::
-minutes()
+hours()
 {
-    QString pattern = "* * * * * *";
-    QCOMPARE(actual(pattern), expected(1));
+    // Int
+    QString pattern = "* 1 * * * *";
+    _dnow.setDate(2016, 1, 1);
+    _tnow.setHMS(0, 0, 0);
+    QCOMPARE(actual(pattern), now().addSecs(60 * 60));
+    _tnow.setHMS(0, 59, 0);
+    QCOMPARE(actual(pattern), now().addSecs(60 * 1));
+    _tnow.setHMS(1, 0, 0);
+    QCOMPARE(actual(pattern), now().addSecs(60 * 1));
+    _tnow.setHMS(1, 58, 0);
+    QCOMPARE(actual(pattern), now().addSecs(60 * 1));
+    _tnow.setHMS(1, 59, 0);
+    QCOMPARE(actual(pattern), now().addDays(1).addSecs(-60 * 59));
+
+    pattern = "30 1,3 * * * *";
+    _tnow.setHMS(0, 0, 0);
+    QCOMPARE(actual(pattern), now().addSecs(60 * 90));
+    _tnow.setHMS(1, 30, 0);
+    QCOMPARE(actual(pattern), now().addSecs(60 * 120));
+    _tnow.setHMS(3, 30, 0);
+    QCOMPARE(actual(pattern), now().addDays(1).addSecs(-120 * 60));
+
+    pattern = "* * * * * 2017";
+    _tnow.setHMS(0, 0, 0);
+    QCOMPARE(actual(pattern), now().addYears(1));
+
+    pattern = "* 2 * * * 2017";
+    QCOMPARE(actual(pattern), now().addYears(1).addSecs(60 * 120));
+}
+
+/******************************************************************************/
+
+void
+QCronTest::
+dow()
+{
+    // 1 = Mon; 7 = Sun
+    QString pattern = "* * * * 1 *"; // Mon
+
+    _dnow.setDate(2016, 1, 1); // Fri
+    _tnow.setHMS(0, 0, 0);
+    QCOMPARE(actual(pattern), now().addDays(3));
+
+    pattern = "* * * * 5 *"; // Fri
+    QCOMPARE(actual(pattern), now().addSecs(60));
+    _tnow.setHMS(23, 59, 59);
+    QCOMPARE(actual(pattern), now().addSecs(1).addDays(6));
+
+    pattern = "* * * * 3 *"; // Wed
+    _tnow.setHMS(0, 0, 0);
+    QCOMPARE(actual(pattern), now().addDays(5));
+}
+
+/******************************************************************************/
+
+void
+QCronTest::
+realLife()
+{
+    /* From monday to friday, from 8:00 to 11:59 and from 14:00 to 17:59. */
+    QString working_hours = "* 8-11,14-17 * * 1-5 *";
+    _dnow.setDate(2016, 1, 4); // Mon
+    _tnow.setHMS(0, 0, 0);
+    QCOMPARE(actual(working_hours), now().addSecs(8 * 3600));
+    _tnow.setHMS(7, 59, 59);
+    QCOMPARE(actual(working_hours), now().addSecs(60));
+    _tnow.setHMS(8, 0, 0);
+    QCOMPARE(actual(working_hours), now().addSecs(60));
+    _tnow.setHMS(11, 58, 0);
+    QCOMPARE(actual(working_hours), now().addSecs(60));
+    _tnow.setHMS(11, 59, 0);
+    QCOMPARE(actual(working_hours), now().addSecs(60 + 3600 * 2));
+    _tnow.setHMS(13, 59, 0);
+    QCOMPARE(actual(working_hours), now().addSecs(60));
+    _tnow.setHMS(14, 0, 0);
+    QCOMPARE(actual(working_hours), now().addSecs(60));
+    _tnow.setHMS(17, 58, 0);
+    QCOMPARE(actual(working_hours), now().addSecs(60));
+    _tnow.setHMS(17, 59, 0);
+    QCOMPARE(actual(working_hours), now().addSecs(60 + 14 * 3600));
+
+    _dnow.setDate(2016, 1, 8); // Fri
+    QCOMPARE(actual(working_hours), now().addSecs(60 + 14 * 3600).addDays(2));
 
-    pattern = "30 * * * * *";
-    QCOMPARE(actual(pattern), expected(30));
-    _tnow.setHMS(0, 31, 0);
-    QCOMPARE(actual(pattern), expected(59));
-    _tnow.setHMS(0, 30, 0);
-    QCOMPARE(actual(pattern), expected(60));
 }
 
 /******************************************************************************/
diff --git a/test/qcron_test.hpp b/test/qcron_test.hpp
index 2337dcd..8d2e85e 100644
--- a/test/qcron_test.hpp
+++ b/test/qcron_test.hpp
@@ -10,14 +10,18 @@ class QCronTest : public QObject
 
 private slots:
     void init();
+    void years();
+    void dow();
+    void hours();
     void minutes();
+    void realLife();
 
 private:
     QDate _dnow;
     QTime _tnow;
 
     QDateTime actual(QString & pattern);
-    QDateTime expected(int offset);
+    QDateTime now();
 
 };
 
diff --git a/test/syntax_test.cpp b/test/syntax_test.cpp
index 10570da..a0eb695 100644
--- a/test/syntax_test.cpp
+++ b/test/syntax_test.cpp
@@ -44,17 +44,22 @@ buildTests(QStringList & good,
         buildPattern(pattern_idx, "2-a") <<
         buildPattern(pattern_idx, QString("%1-%2").arg(min).arg(max + 1)) <<
         buildPattern(pattern_idx, QString("%1-%2").arg(min - 1).arg(max)) <<
-        buildPattern(pattern_idx, QString("%1,%2,").arg(min).arg(min +1)) <<
+        buildPattern(pattern_idx, QString("%1,%2,").arg(min).arg(min + 1)) <<
         buildPattern(pattern_idx, "1,a") <<
         buildPattern(pattern_idx, QString("%1,%2").arg(min).arg(max + 1)) <<
-        buildPattern(pattern_idx, QString("%1,%2").arg(min).arg(min - 1));
+        buildPattern(pattern_idx, QString("%1,%2").arg(min).arg(min - 1)) <<
+        buildPattern(pattern_idx, "1/2/3 * * * * *") <<
+        buildPattern(pattern_idx, "1-2-3 * * * * *") << // Bad range
+        buildPattern(pattern_idx, "** * * * * *") << // Bad star
+        buildPattern(pattern_idx, "1,,2 * * * * *") // Bad list
+        ;
     good << buildPattern(pattern_idx, QString::number(min)) <<
-        buildPattern(pattern_idx, QString("000000000%1").arg(min)) <<
-        buildPattern(pattern_idx, QString::number(max)) <<
-        buildPattern(pattern_idx, QString("%1-%2").arg(min).arg(max)) <<
-        buildPattern(pattern_idx, QString("%1-%1").arg(min)) <<
-        buildPattern(pattern_idx, QString("%1,%2,%3").arg(min).arg(min + 1).arg(max -1)) <<
-        buildPattern(pattern_idx, QString("%1,%1").arg(min));
+         buildPattern(pattern_idx, QString("000000000%1").arg(min)) <<
+         buildPattern(pattern_idx, QString::number(max)) <<
+         buildPattern(pattern_idx, QString("%1-%2").arg(min).arg(max)) <<
+         buildPattern(pattern_idx, QString("%1-%1").arg(min)) <<
+         buildPattern(pattern_idx, QString("%1,%2,%3").arg(min).arg(min + 1).arg(max - 1)) <<
+         buildPattern(pattern_idx, QString("%1,%1").arg(min));
 }
 
 /******************************************************************************/
@@ -64,13 +69,13 @@ SyntaxTest::
 global()
 {
     QStringList good = QStringList() <<
-        "* * * * * *" <<
-        "*         * * * * *" <<
-        "*\t* *\t\t * * *";
+                       "* * * * * *" <<
+                       "*         * * * * *" <<
+                       "*\t* *\t\t * * *";
     QStringList bad = QStringList() <<
-        "* * * * *" << // Not enough fields
-        "* * * * * * *" << // To many fields
-        "* * *\n* * *"; // Bad separator
+                      "* * * * *" << // Not enough fields
+                      "* * * * * * *" << // To many fields
+                      "* * *\n* * *"; // Bad separator
     doTest(good, bad);
 }
 
@@ -142,7 +147,7 @@ years()
 {
     QStringList good;
     QStringList bad;
-    buildTests(good, bad, 5, 2016, 2099);
+    buildTests(good, bad, 5, 1, 2099);
     doTest(good, bad);
 }
 
@@ -154,13 +159,13 @@ doTest(QStringList good, QStringList bad)
 {
     foreach (QString s, good)
     {
-//        qDebug() << s;
+        //        qDebug() << s;
         QCron cron(s);
         QVERIFY(cron.isValid());
     }
     foreach (QString s, bad)
     {
-//        qDebug() << s;
+                qDebug() << s;
         QCron cron(s);
         QVERIFY(!cron.isValid());
     }