Giter Club home page Giter Club logo

croniter's People

Contributors

abeja-yuki avatar cuu508 avatar dark-light-cz avatar darkk avatar djmitche avatar eumiro avatar hugovk avatar iddoav avatar int3rlop3r avatar jaredkoontz avatar josegonzalez avatar kbrose avatar kiorky avatar lowell80 avatar mrcrilly avatar mwojnars avatar olivierdalang avatar otherpirate avatar perlence avatar petervtzand avatar rfinnie avatar roderick-jonsson avatar scls19fr avatar scop avatar solartechnologies avatar steffenschroeder avatar taichino avatar vshih avatar wiggin15 avatar zed2015 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

croniter's Issues

croniter("30 22 * * sun-thu") fails ValueError

Hi,
the following test fails with ValueError: [30 22 * * sun-thu] is not acceptable:
Cron accepts this format.

import unittest

from croniter import croniter
from datetime import datetime


class CroniterTest(unittest.TestCase):
    def sunday_to_thursday(self):
        base = datetime.today()
        exp = "30 22 * * sun-thu"
        my_iter = croniter(exp, base)

        next_execution = my_iter.get_next(datetime)
        print(next_execution.time())


if __name__ == '__main__':
    unittest.main()

get_prev new bug

Hi tanks for the fix.

I've found another bug with the get prev :

5 0 */2 * * every 2 days the script froze.

Wrong behavior around DST change in "America/Sao_Paulo" timezone

Consider the following example:

import pytz
from datetime import datetime
from croniter import croniter

tz = pytz.timezone("America/Sao_Paulo")

local_dates = [tz.localize(datetime(2018, 2, 17, 0, 0, 0)),
               tz.localize(datetime(2018, 2, 17, 1, 0, 0)),
               tz.localize(datetime(2018, 2, 17, 2, 0, 0)),
               tz.localize(datetime(2018, 2, 17, 3, 0, 0))]

[print(d, '=>', croniter("0 0 * * *", d).get_next(datetime)) for d in local_dates]

Output:

2018-02-17 00:00:00-02:00 => 2018-02-18 00:00:00-03:00
2018-02-17 01:00:00-02:00 => 2018-02-18 00:00:00-03:00
2018-02-17 02:00:00-02:00 => 2018-02-18 00:00:00-03:00
2018-02-17 03:00:00-02:00 => 2018-02-18 00:00:00-03:00

-This is correct (note the DST changes).

Now lets add 5 minutes to each local date...

local_dates = [tz.localize(datetime(2018, 2, 17, 0, 5, 0)),
               tz.localize(datetime(2018, 2, 17, 1, 5, 0)),
               tz.localize(datetime(2018, 2, 17, 2, 5, 0)),
               tz.localize(datetime(2018, 2, 17, 3, 5, 0))]
[print(d, '=>', croniter("0 0 * * *", d).get_next(datetime)) for d in local_dates]

Output:

2018-02-17 00:05:00-02:00 => 2018-02-17 23:00:00-03:00
2018-02-17 01:05:00-02:00 => 2018-02-17 23:00:00-03:00
2018-02-17 02:05:00-02:00 => 2018-02-17 23:00:00-03:00
2018-02-17 03:05:00-02:00 => 2018-02-17 23:00:00-03:00

-This is clearly a BUG!

Just add 1 second, or even 1 microseconds and it brakes.
Note, that DST in Brazil changes on Feb 18th 2018 at midnight 00:00:00.

The problem disappears for another timezones, e.g., its all good for 2017-03-26 00:05 in 'Europe/Warsaw'. I suggest adding test cases like this one for other timezones as well.

setuptools should not be required on installation

By requiring setuptools as part of installation of the package, you're forcing everyone to upgrade their setuptools package. This may be a hold-over from 2011 but setuptools and distutils have now merged into setuptools. Most systems will have it installed. It's also generally frowned upon by the community unless the package itself is a packaging project.

Support for year in Croniter

I want to execute a job at 6 AM on 2nd, Feb 2018 only once. The corresponding cron expression would be "0 6 2 2 * 0 2018".

In croniter, seconds is the 6th part of the cron expression. But I am getting CroniterBadError when I try to provide year also. Is there any way to accomplish this. Or this might be a new feature request.

Does not produce the correct output when the expression is something like (1-10/2 * * * *)

In the croniter.py file, line 89 should be changed from:
for j in xrange(int(low), int(high)+1):
if j % int(step) == 0:
e_list.append(j)
To:
for j in xrange(int(low), int(high)+1, int(step)):
#if j % int(step) == 0:
e_list.append(j)

Because, % returns intervals that are divisible by denominator between the start and end value whereas it should actually return start value plus the interval and next value should be result plus the interval and goes on.

After the change, the result will be like : [[1, 3, 5, 7, 9], [''], [''], [''], ['']]

Infinite loop with valid cron syntax

Hi guys!

Found issue with specific cron syntax. Could anybody help me with that?

croniter==0.3.16
run on Python2.7

croniter('0 0 29-31 * 3-4', day_or=False).get_prev(datetime.datetime)

It will go in infinite loop.

February and future get_prev

Hi, we are using croniter at our project for IT inventory at github.com/stic-ull/arritranco

The thing is that all our backups scheduled on days 29 and 30 failed to schedule today.

Look what happens:

datetime.now() >> datetime.datetime(2013, 3, 1, 12, 17, 34, 257877)

c = croniter('00 03 16,30 * *',datetime.now())

c.get_next(datetime) >>>> datetime.datetime(2013, 3, 16, 3, 0)
c.get_next(datetime) >>>> datetime.datetime(2013, 3, 30, 3, 0)
c.get_next(datetime) >>>> datetime.datetime(2013, 4, 16, 3, 0)

Then i go backwards:

c.get_prev(datetime) >>>> datetime.datetime(2013, 3, 30, 3, 0)
c.get_prev(datetime) >>>> datetime.datetime(2013, 3, 16, 3, 0)
c.get_prev(datetime) >>>> datetime.datetime(2013, 3, 2, 3, 0)
c.get_prev(datetime) >>>> datetime.datetime(2013, 3, 2, 3, 0)
c.get_prev(datetime) >>>> datetime.datetime(2013, 3, 2, 3, 0)
c.get_prev(datetime) >>>> datetime.datetime(2013, 3, 2, 3, 0)

Its get stalled at these date, that is, incorrect.

Possible error near DST?

I am calculating the last date range based on a cron schedule. The functionality needs to be DST aware, and the cron schedule is specified in "local time", although the range values should be UTC for consistency throughout the rest of the app.

Here's a brief script to illustrate this behaviour

from datetime import datetime
import pytz
from croniter import croniter


def main():
    base = '00 00 * * 6'
    now = datetime(2017, 10, 3, 2, 07).replace(tzinfo=pytz.UTC)
    iter = localtz_iterator(now, base)
    last_window_utc(iter)


def localtz_iterator(now, base):
    tz = pytz.timezone('Australia/Sydney')  # usually coming from some other database field, not
    print 'initialising cron-iterator for {}'.format(tz)
    reference_localtz = now.astimezone(pytz.timezone('Australia/Sydney'))
    print 'local time is {} ({})'.format(reference_localtz, now)

    return croniter(base, reference_localtz)


def last_window_utc(iter):
    closing_marker = iter.get_prev(datetime)
    opening_marker = iter.get_prev(datetime)

    print 'opening record marker {} ({})'.format(opening_marker, opening_marker.astimezone(pytz.UTC))
    print 'closing record marker {} ({})'.format(closing_marker, closing_marker.astimezone(pytz.UTC))

    recalc_closing_marker = iter.get_next(datetime)
    assert closing_marker == recalc_closing_marker

    return (opening_marker, closing_marker)


if __name__ == '__main__':
    main()

So, my date range is expected to go from opening_marker to closing_marker - if the assertion is commented out, the closing marker is at 23:00 (i.e. 11pm) local time, even though the cron schedule is explicit that it should be at midnight.
The recalc_closing_marker produces the correct value, by moving the marker forward once again...

All the same, thanks for an awesome module - this exactly fills the requirements I have in my project!

Development Issue: `python bootstrap.py -d` doesn't work

Two Issues:

  1. bootstrap.py: error: no such option: -d
  2. -bash: bin/test: No such file or directory

I was following the wiki to develop but encountered issues with running bootstrap as per instructions.

python bootstrap.py -d
Usage: [DESIRED PYTHON FOR BUILDOUT] bootstrap.py [options]

Bootstraps a buildout-based project.

Simply run this script in a directory containing a buildout.cfg, using the
Python that you want bin/buildout to use.

Note that by using --find-links to point to local resources, you can keep 
this script from going over the network.

bootstrap.py: error: no such option: -d

So I ignored the option and ran python bootstrap, which completed fine.
Then followed this by running bin/buildout -vvvvvvN, which completed just fine.
But I don't see bin/test script in the repo. -bash: bin/test: No such file or directory

@kiorky

croniter('* * 0 * *') works

croniter('* * 0 * *') gives object that considers day of the month to be 1. I believe it should fail allowing entry between 1 to 31 only.

Smear dst transitions

I think the best way to handle DST transition is to "smear" them. That means:

  • If the clocks jump from 2:00 to 3:00, we instead speed up the passing of time by 3/2 starting at 1:00. So at 2:00, our clock reads 2:30, and at 4:00 our clock is aligned with the official time.
  • If the clocks jump from 3:00 to 2:00, we instead slow down the passing of time by 1/2, starting at (the first) 2:00. So at the jump point, our clock reads 2:30 and the second time the official time reaches 3:00, our clock also reads 3:00.

I think the most important aspect of DST shift handling is that tasks run as often as expected. So a task that is scheduled for a fixed time of day should run every day โ€“ independent of whether a DST shift happened at that day. A task that is scheduled to run once per hour should always run exactly 24 times a day.

An alternative to my suggestion is what cron is doing:

Local time changes of less than three hours, such as those caused by the Daylight Saving Time changes, are handled in a special way. This only applies to jobs that run at a specific time and jobs that run with a granularity greater than one hour. Jobs that run more frequently are scheduled normally.

If time was adjusted one hour forward, those jobs that would have run in the interval that has been skipped will be run immediately. Conversely, if time was adjusted backward, running the same job twice is avoided.

The smearing method has two advantages:

  • The load is distributed more evenly. With the cron way, there is an hour in which no tasks will be started.
  • The order of multiple events is preserved. cron will smush all events that would have happened over an our into a single second, so instead of happening after one another, they all happen at once.

If the maintainers agree with this approach, I will prepare a PR. This should fix #90 and #91.

Enable travis

It doesn't look like travis is enabled, so builds aren't actually running for pull requests.

string comparison is not appropriate for integers

On line 62 of croniter.py there is a greater than comparison between two string integers (low > high). When a range such as '5-15 * * * *' is passed in to croniter this condition returns False and thus ValueError("[%s] is not acceptable" % expr_format) is raised.

I fixed this locally with int(low) > int(high), but I haven't tested if this itself might raise a ValueError in some cases.

problems with weekdays

base = datetime(2010, 8, 7, 0, 0)
iter = croniter('0 9 * * mon,tue,wed,thu,fri', base)
print iter.get_next(datetime) # 2010-08-07 09:00:00 => is a saturday
print iter.get_next(datetime) # 2010-08-10 09:00:00 => is a tuesday => where is monday...

get_prev() is pointing to wrong date in a period of one second after the cron time

Version: 0.3.23
System: macOS 10.12.6
Python: 2.7.10

I tested croniter in a very time sensitive application and discovered that get_prev() is pointing to the wrong date in a period of one second after the cron time.

Example:

date = datetime.datetime(
    year=2018,
    month=1,
    day=2,
    hour=10,
    minute=0,
    second=0,
    microsecond=0
)
c = croniter.croniter("0 10 * * * ", start_time=date)
print(datetime.datetime.utcfromtimestamp(c.get_prev()))

--> 2018-01-01 10:00:00

croniter is pointing to the date from the day before.

date = datetime.datetime(
    year=2018,
    month=1,
    day=2,
    hour=10,
    minute=0,
    second=0,
    microsecond=500000
)
c = croniter.croniter("0 10 * * * ", start_time=date)
print(datetime.datetime.utcfromtimestamp(c.get_prev()))

--> 2018-01-01 10:00:00

croniter is still pointing to the day before, but it should point to 2018-01-02 10:00:00

date = datetime.datetime(
    year=2018,
    month=1,
    day=2,
    hour=10,
    minute=0,
    second=1,
    microsecond=0
)
c = croniter.croniter("0 10 * * * ", start_time=date)
print(datetime.datetime.utcfromtimestamp(c.get_prev()))

--> 2018-01-02 10:00:00

Not before 1 second after the date croniter switches to the right one.

Is it possible to increase the time resolution to a millisecond base?

Kind regards

Nils

this issue can be closed

man 5 crontab:

           field         allowed values
           -----         --------------
           minute        0-59
           hour          0-23
           day of month  1-31
           month         1-12 (or names, see below)
           day of week   0-7 (0 or 7 is Sun, or use names)

but when use croniter:

>>> from croniter import croniter
>>> c = croniter('15 16 12 12 3')
>>> c.get_next()
1417594500.0
>>> c.get_next()
1418199300.0
>>> import datetime
>>> datetime.datetime.fromtimestamp(1417594500)
datetime.datetime(2014, 12, 3, 16, 15)
>>> datetime.datetime(2014, 12, 10, 16, 15)
datetime.datetime(2014, 12, 10, 16, 15)
>>>

[0.3.20] croniter return repeated timestamps in a particular hour (might relate to daylight saving time but not sure)

System:

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 16.04.3 LTS
Release:        16.04
Codename:       xenial

Versions:

$ python --version
Python 3.5.2
$ pip list | grep croniter
croniter (0.3.20)

Test code (name it test.py):

from croniter import croniter
from datetime import datetime
import pandas as pd
import tzlocal
import pytz
import sys

local = pytz.timezone('America/Los_Angeles')
print(local)

c = croniter('* * * * *', datetime(2017, 3, 12, tzinfo=local))
s = set()
l = list()
while True:
    t = c.get_next(datetime)
    ms = int(t.timestamp() * 1000)
    if ms >= 1489309140000:
        s.add(ms)
        l.append(ms)
        print(t, ms)
    if ms >= 1489312800000:
        break

print(len(s))
print(len(l))
assert(len(s) == len(l))

Expected behavior:

  • Console output should not contain repeated timestamps.
  • Console output should not contain timestamps which go backwards in time.
  • Assertion in the code should pass, since timestamps should not repeat.

Observed behavior:

  • Console output contains repeated timestamps.
  • Console output contains timestamps which go backwards in time.
  • Assertion in the code fails.

Observed console output:

$ python test.py
America/Los_Angeles
2017-03-12 00:59:00-08:00 1489309140000
2017-03-12 01:00:00-08:00 1489309200000
2017-03-12 01:01:00-08:00 1489309260000
2017-03-12 01:02:00-08:00 1489309320000
2017-03-12 01:03:00-08:00 1489309380000
2017-03-12 01:04:00-08:00 1489309440000
2017-03-12 01:05:00-08:00 1489309500000
2017-03-12 01:06:00-08:00 1489309560000
2017-03-12 01:07:00-08:00 1489309620000
2017-03-12 01:08:00-08:00 1489309680000
2017-03-12 01:09:00-08:00 1489309740000
2017-03-12 01:10:00-08:00 1489309800000
2017-03-12 01:11:00-08:00 1489309860000
2017-03-12 01:12:00-08:00 1489309920000
2017-03-12 01:13:00-08:00 1489309980000
2017-03-12 01:14:00-08:00 1489310040000
2017-03-12 01:15:00-08:00 1489310100000
2017-03-12 01:16:00-08:00 1489310160000
2017-03-12 01:17:00-08:00 1489310220000
2017-03-12 01:18:00-08:00 1489310280000
2017-03-12 01:19:00-08:00 1489310340000
2017-03-12 01:20:00-08:00 1489310400000
2017-03-12 01:21:00-08:00 1489310460000
2017-03-12 01:22:00-08:00 1489310520000
2017-03-12 01:23:00-08:00 1489310580000
2017-03-12 01:24:00-08:00 1489310640000
2017-03-12 01:25:00-08:00 1489310700000
2017-03-12 01:26:00-08:00 1489310760000
2017-03-12 01:27:00-08:00 1489310820000
2017-03-12 01:28:00-08:00 1489310880000
2017-03-12 01:29:00-08:00 1489310940000
2017-03-12 01:30:00-08:00 1489311000000
2017-03-12 01:31:00-08:00 1489311060000
2017-03-12 01:32:00-08:00 1489311120000
2017-03-12 01:33:00-08:00 1489311180000
2017-03-12 01:34:00-08:00 1489311240000
2017-03-12 01:35:00-08:00 1489311300000
2017-03-12 01:36:00-08:00 1489311360000
2017-03-12 01:37:00-08:00 1489311420000
2017-03-12 01:38:00-08:00 1489311480000
2017-03-12 01:39:00-08:00 1489311540000
2017-03-12 01:40:00-08:00 1489311600000
2017-03-12 01:41:00-08:00 1489311660000
2017-03-12 01:42:00-08:00 1489311720000
2017-03-12 01:43:00-08:00 1489311780000
2017-03-12 01:44:00-08:00 1489311840000
2017-03-12 01:45:00-08:00 1489311900000
2017-03-12 01:46:00-08:00 1489311960000
2017-03-12 01:47:00-08:00 1489312020000
2017-03-12 01:48:00-08:00 1489312080000
2017-03-12 01:49:00-08:00 1489312140000
2017-03-12 01:50:00-08:00 1489312200000
2017-03-12 01:51:00-08:00 1489312260000
2017-03-12 01:52:00-08:00 1489312320000
2017-03-12 01:53:00-08:00 1489312380000
2017-03-12 01:54:00-08:00 1489312440000
2017-03-12 01:55:00-08:00 1489312500000
2017-03-12 01:56:00-08:00 1489312560000
2017-03-12 01:57:00-08:00 1489312620000
2017-03-12 01:58:00-08:00 1489312680000
2017-03-12 01:59:00-08:00 1489312740000
2017-03-12 02:00:00-07:00 1489309200000
2017-03-12 01:01:00-08:00 1489309260000
2017-03-12 01:02:00-08:00 1489309320000
2017-03-12 01:03:00-08:00 1489309380000
2017-03-12 01:04:00-08:00 1489309440000
2017-03-12 01:05:00-08:00 1489309500000
2017-03-12 01:06:00-08:00 1489309560000
2017-03-12 01:07:00-08:00 1489309620000
2017-03-12 01:08:00-08:00 1489309680000
2017-03-12 01:09:00-08:00 1489309740000
2017-03-12 01:10:00-08:00 1489309800000
2017-03-12 01:11:00-08:00 1489309860000
2017-03-12 01:12:00-08:00 1489309920000
2017-03-12 01:13:00-08:00 1489309980000
2017-03-12 01:14:00-08:00 1489310040000
2017-03-12 01:15:00-08:00 1489310100000
2017-03-12 01:16:00-08:00 1489310160000
2017-03-12 01:17:00-08:00 1489310220000
2017-03-12 01:18:00-08:00 1489310280000
2017-03-12 01:19:00-08:00 1489310340000
2017-03-12 01:20:00-08:00 1489310400000
2017-03-12 01:21:00-08:00 1489310460000
2017-03-12 01:22:00-08:00 1489310520000
2017-03-12 01:23:00-08:00 1489310580000
2017-03-12 01:24:00-08:00 1489310640000
2017-03-12 01:25:00-08:00 1489310700000
2017-03-12 01:26:00-08:00 1489310760000
2017-03-12 01:27:00-08:00 1489310820000
2017-03-12 01:28:00-08:00 1489310880000
2017-03-12 01:29:00-08:00 1489310940000
2017-03-12 01:30:00-08:00 1489311000000
2017-03-12 01:31:00-08:00 1489311060000
2017-03-12 01:32:00-08:00 1489311120000
2017-03-12 01:33:00-08:00 1489311180000
2017-03-12 01:34:00-08:00 1489311240000
2017-03-12 01:35:00-08:00 1489311300000
2017-03-12 01:36:00-08:00 1489311360000
2017-03-12 01:37:00-08:00 1489311420000
2017-03-12 01:38:00-08:00 1489311480000
2017-03-12 01:39:00-08:00 1489311540000
2017-03-12 01:40:00-08:00 1489311600000
2017-03-12 01:41:00-08:00 1489311660000
2017-03-12 01:42:00-08:00 1489311720000
2017-03-12 01:43:00-08:00 1489311780000
2017-03-12 01:44:00-08:00 1489311840000
2017-03-12 01:45:00-08:00 1489311900000
2017-03-12 01:46:00-08:00 1489311960000
2017-03-12 01:47:00-08:00 1489312020000
2017-03-12 01:48:00-08:00 1489312080000
2017-03-12 01:49:00-08:00 1489312140000
2017-03-12 01:50:00-08:00 1489312200000
2017-03-12 01:51:00-08:00 1489312260000
2017-03-12 01:52:00-08:00 1489312320000
2017-03-12 01:53:00-08:00 1489312380000
2017-03-12 01:54:00-08:00 1489312440000
2017-03-12 01:55:00-08:00 1489312500000
2017-03-12 01:56:00-08:00 1489312560000
2017-03-12 01:57:00-08:00 1489312620000
2017-03-12 01:58:00-08:00 1489312680000
2017-03-12 01:59:00-08:00 1489312740000
2017-03-12 03:00:00-07:00 1489312800000
62
122
Traceback (most recent call last):
  File "test.py", line 26, in <module>
    assert(len(s) == len(l))
AssertionError

Notice that every minute between 2017-03-12 01:00:00-08:00 and 2017-03-12 01:59:00-08:00 are repeated once, apparently at the hour of daylight saving time switch-over.

Incorrect "get_next" value in 0.3.22

In 0.3.20 version, the croniter.get_next() returns correct value.

>>> from datetime import datetime
>>> import croniter
>>>
>>> dt = datetime.strptime('2018/05/15 09:00:30+0000', '%Y/%m/%d %H:%M:%S%z')
>>> print(dt)
# outputs: 2018-05-15 09:00:30+00:00
>>> print(croniter.croniter('0,30 * * * *', dt).get_next(datetime))
# outputs: 2018-05-15 09:30:00+00:00

However, with 0.3.22, the croniter.get_next() returns incorrect value. Calling get_next() at 9:00:30 time with 0,30 * * * * cron returns 9:00:00:

>>> from datetime import datetime
>>> import croniter
>>>
>>> dt = datetime.strptime('2018/05/15 09:00:30+0000', '%Y/%m/%d %H:%M:%S%z')
>>> print(dt)
# outputs: 2018-05-15 09:00:30+00:00
>>> print(croniter.croniter('0,30 * * * *', dt).get_next(datetime))
# outputs: 2018-05-15 09:00:00+00:00

Cannot specify weekdays

I wish to use: '0 0 * * 1-5'

but it gives the error:

  File "/usr/local/lib/python2.7/dist-packages/croniter/croniter.py", line 69, in __init__
    raise ValueError(self.bad_length)
ValueError: Exactly 5 or 6 columns has to be specified for iteratorexpression.
>>> '0 0 * * 1-5'.split()
['0', '0', '*', '*', '1-5']

I don't understand what's wrong here.

get_prev() method doesn't take kare about seconds

Hi, when i try, if it is proper solution fo mre, i find this:

base = datetime(2014, 6, 10, 12, 0, 10)

iter = croniter('0 */2 * * *', base)
print "current: %s" % iter.get_current(datetime).time()

iter = croniter('0 */2 * * *', base)
print "prev:    %s" % iter.get_prev(datetime).time()

iter = croniter('0 */2 * * *', base)
print "next:    %s" % iter.get_next(datetime).time()

Which prints:

current: 12:00:10
prev:    10:00:00
next:    14:00:00

When base time is 12:00:10, i expect that the get_prev() returns the "12:00:00", not the "10:00:00". When the base time is changed to "12:01:10" all is as expected:

current: 12:01:10
prev:    12:00:00
next:    14:00:00

To i get really previous time (and date) of the execution, i need to use:

iter.get_next(datetime)
print "prev:    %s" % iter.get_prev(datetime).time()

regards

Does not work on DST changes

This module does not generate the correct datetime on time shifts during daylight-saving times.

>>> dt = datetime.datetime(2013, 10, 27, 0, 0, 0)
>>> dt_tz = pytz.timezone('Europe/Athens').localize(dt)
>>> dt_tz
datetime.datetime(2013, 10, 27, 0, 0, tzinfo=<DstTzInfo 'Europe/Athens' EEST+3:00:00 DST>)
>>> ct = croniter.croniter('*/30 * * * *', dt_tz)
>>> for i in range(12):
...     ct.get_next(datetime.datetime)
... 
datetime.datetime(2013, 10, 27, 0, 30)
datetime.datetime(2013, 10, 27, 1, 0)
datetime.datetime(2013, 10, 27, 1, 30)
datetime.datetime(2013, 10, 27, 2, 0)
datetime.datetime(2013, 10, 27, 2, 30)
datetime.datetime(2013, 10, 27, 3, 0)
datetime.datetime(2013, 10, 27, 3, 30)
# <----- missing hour 3:00 - 4:00 in Europe/Athens TZ
datetime.datetime(2013, 10, 27, 4, 0)
datetime.datetime(2013, 10, 27, 4, 30)
datetime.datetime(2013, 10, 27, 5, 0)
datetime.datetime(2013, 10, 27, 5, 30)
datetime.datetime(2013, 10, 27, 6, 0)
>>> 

http://www.timeanddate.com/worldclock/clockchange.html?n=26

Run a job Every 5sec

I am trying to run a job every 5 sec by adding a 6th *. Not sure if it is supposed to work

from croniter import croniter
from datetime import datetime
base = datetime(2010, 1, 25, 4, 46)
iter = croniter('*/5 * * * * *', base)  # every 5 sec
print iter.get_next(datetime)   # Should return 2010-01-25 04:46:05 but returns 2010-01-25 04:50:00
print iter.get_next(datetime)   # Should return  2010-01-25 04:46:10 but returns 2010-01-25 04:50:01
print iter.get_next(datetime)   # Should return  2010-01-25 04:46:15 but returns 2010-01-25 04:50:02

Am I missing something here?

February 29

The following expression gives an exception with 0.3.11:

from croniter import croniter
from datetime import datetime
croniter("0 9 29 2 *", datetime(2016, 2, 29, 9, 0)).get_next(datetime)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Python27\lib\site-packages\croniter\croniter.py", line 141, in get_next
    return self._get_next(ret_type, is_prev=False)
  File "C:\Python27\lib\site-packages\croniter\croniter.py", line 224, in _get_next
    result = self._calc(self.cur, expanded, is_prev)
  File "C:\Python27\lib\site-packages\croniter\croniter.py", line 362, in _calc
    raise Exception("failed to find prev date")
Exception: failed to find prev date

The same in 0.3.8 worked, gave datetime.datetime(2020, 2, 29, 9, 0) as the answer.

Error to get next time

Hello,

I am trying to get next date, but this method return it a previous date to the current date

Example

cron = croniter.croniter("%s %s %s %s %s" % (x.recurrence.minute, x.recurrence.hour, x.recurrence.day, x.recurrence.month, x.recurrence.weekday), x.recurrence.datestart)
cron.get_next()  -> This method return a previous date to the current date

datestart : 2015-09-18 15:40:00
get_next: 2015-09-18 15:41:00

KeyError when using '0 4/6 * * *'

This is a valid cron expression. http://crontab.guru/#0_4/6___*

>>> iter = croniter('0 4/6 * * *', base)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python2.7/site-packages/croniter/croniter.py", line 113, in __init__
    t = self.ALPHACONV[i][t.lower()]
KeyError: '4/6'

Invalid results when crossing DST transition

First of all thanks for fixing #35

Still, there are more issues with local time and DST transition.
CRON uses local system time on Linux machines and users expect cron masks to work using local system time. However there are problems with e.g. daily masks when crossing DST transition time.
In the simplest scenario I want to use hourly mask 0 * * * * and daily masks 0 0 * * * in local time and users expect them to just work in their local time.
Examples using: croniter 0.3.15, Python 3.5.2

import json, calendar, time, pytz
from datetime import datetime
from croniter import croniter

tz = pytz.timezone('Europe/Warsaw')
standard -> DST transition at 2017-03-26 01:59+1:00 -> 03:00+2:00
dt_day1 = tz.localize(datetime(2017, 3, 26))
print(dt_day1)                                            # 2017-03-26 00:00:00+01:00
print(croniter('0 0 * * *', dt_day1).get_next(datetime))  # 2017-03-27 01:00:00+02:00 - INVALID
dt_hour1 = tz.localize(datetime(2017, 3, 26, 1))
print(dt_hour1)                                            # 2017-03-26 01:00:00+01:00
print(croniter('0 * * * *', dt_hour1).get_next(datetime))  # 2017-03-26 03:00:00+02:00 - OK
DST-> standard transition at 2017-10-29 02:59+2:00 -> 02:00+1:00
dt_day1 = tz.localize(datetime(2017, 10, 29))
print(dt_day1)                                            # 2017-10-29 00:00:00+02:00
print(croniter('0 0 * * *', dt_day1).get_next(datetime))  # 2017-10-29 23:00:00+01:00 - INVALID
dt_hour1 = tz.localize(datetime(2017, 10, 29, 2), is_dst=True)
print(dt_hour1)                                            # 2017-10-29 02:00:00+02:00
print(croniter('0 * * * *', dt_hour1).get_next(datetime))  # 2017-10-29 02:00:00+01:00 - OK

The reason this gives invalid results is that croniter converts datetime to timestamp internally, does arithmetics and converts back to datetime. This way information about local time is lost at conversion point and this method gives bad results after converting back to local. The only way to do this work correctly with local time is to do the arithmetics on localized datetime object using timedeltas and tz.normalize(...) - to check if e.g. midnight is still midnight after post-arithmetics normalization.

Incorrect `job.get_next()` time after DST (shift backwards)

I created a crontab that should run each monday at 8am, which works perfectly except for the first monday after the DST change in october (the clock is shifted an hour backwards). There is an extra schedule scheduled at 7am (as well as 8am), which you can see in the output.

This is my code

import croniter
import datetime
import pytz

#current time
current_time = datetime.datetime.now(tz=pytz.timezone('Europe/Amsterdam'))
crontab_string = "0 8 * * 1"  # every day monday at 0800

# create a job
job = croniter.croniter(crontab_string, current_time)

# print 
for i in range(0,200):
   print job.get_next(datetime.datetime)

This is my output

....
2017-10-16 08:00:00.685732+02:00
2017-10-23 08:00:00.685732+02:00
2017-10-30 07:00:00.685732+01:00
2017-10-30 08:00:00.685732+01:00
2017-11-06 08:00:00.685732+01:00
2017-11-13 08:00:00.685732+01:00
...
2018-10-15 08:00:00.685732+02:00
2018-10-22 08:00:00.685732+02:00
2018-10-29 07:00:00.685732+01:00
2018-10-29 08:00:00.685732+01:00
2018-11-05 08:00:00.685732+01:00
...
2019-10-07 08:00:00.685732+02:00
2019-10-14 08:00:00.685732+02:00
2019-10-21 08:00:00.685732+02:00
2019-10-28 07:00:00.685732+01:00
2019-10-28 08:00:00.685732+01:00
2019-11-04 08:00:00.685732+01:00
2019-11-11 08:00:00.685732+01:00

This bug doesn't appear when removing the timezone from the current time

get_next() returns datetime before `start_time`

In [61]: croniter.croniter('32 16 * * *', datetime.datetime(2018, 5, 29, 16, 32, 0), ret_type=datetime.datetime).get_next()
Out[61]: datetime.datetime(2018, 5, 30, 16, 32)

In [62]: croniter.croniter('32 16 * * *', datetime.datetime(2018, 5, 29, 16, 32, 1), ret_type=datetime.datetime).get_next()
Out[62]: datetime.datetime(2018, 5, 29, 16, 32)

The second command should return the same value as the first one.

Does not work on DST changes (depends on system timezone)

croniter 0.3.5, Python 2.7.8
Consider test case executed on machine with 'Europe/Warsaw' (UTC+1) timezone, change from DST to normal occurs on 2014-10-26 02:59 +2:00 (UTC 00:59) -> 02:00 +1:00 (UTC 01:00):

from datetime import datetime
from croniter import croniter
import pytz

Using datetime objects:

Before change (naive datetime) - VALID
cr = croniter('0 * * * *', datetime(2014, 10, 26, 0))
print 'next = ', cr.get_next(datetime)   # 2014-10-26 01:00:00, OK
print 'prev = ', cr.get_prev(datetime)   # 2014-10-26 00:00:00, OK 
Before change (UTC datetime) - INVALID
cr = croniter('0 * * * *', datetime(2014, 10, 26, 0).replace(tzinfo=pytz.utc))
print 'next = ', cr.get_next(datetime)   # 2014-10-26 02:00:00+00:00, INVALID
print 'prev = ', cr.get_prev(datetime)   # 2014-10-26 02:00:00+00:00, INVALID
After change (naive datetime) - INVALID
cr = croniter('0 * * * *', datetime(2014, 10, 26, 1))
print 'next = ', cr.get_next(datetime)   # 2014-10-26 02:00:00, OK
print 'prev = ', cr.get_prev(datetime)   # 2014-10-26 02:00:00, INVALID
After change (UTC datetime) - INVALID
cr = croniter('0 * * * *', datetime(2014, 10, 26, 1).replace(tzinfo=pytz.utc))
print 'next = ', cr.get_next(datetime)   # 2014-10-26 03:00:00+00:00, INVALID
print 'prev = ', cr.get_prev(datetime)   # 2014-10-26 02:00:00+00:00, INVALID

Using timestamps:

Before change - INVALID
cr = croniter('0 * * * *', 1414281600) # UTC 00:00
print 'next = ', cr.get_next()   # 1414288800.0 (UTC 02:00), INVALID
print 'prev = ', cr.get_prev()   # 1414285200.0 (UTC 01:00), INVALID
After change - VALID
cr = croniter('0 * * * *', 1414285200) # UTC 01:00
print 'next = ', cr.get_next()   # 1414288800.0 (UTC 02:00), OK
print 'prev = ', cr.get_prev()   # 1414285200.0 (UTC 01:00), OK

Despite the fact that timestamps are UTC hardcoded all of test cases give much better results when system timezone is changed to e.g. Fiji UTC+12:00

Add a date matching method

Hello,
This is mostly a feature request...
Would it be possible to implement a simple function that would tell if a datetime match a crontab expression ?
In some cases, datetime given in crontiter constructor exactly match the crontab expression. Il that case the behavior needed for the get_next() or get_prev() methods may be ambiguous.
I would like to be able to code something like this :

iter = croniter("0 0 * * *", dtetime)
if iter.match(dtetime):
   next = dtetime
else:
   next = iter.get_next()

Do you think such a feature could be possible ?

Best regards,

get_prev using weekday v.0.2.5 bug

here's the error that I am getting :

Traceback (most recent call last):
File "./backup.py", line 363, in
days_to_check.append(iter.get_prev(datetime))
File "./backup.py", line 95, in get_prev
return self._get_next(ret_type, is_prev=True)
File "./backup.py", line 114, in _get_next
result = self._calc(self.cur, expanded, is_prev)
File "./backup.py", line 175, in _calc
raise "failed to find prev date"
TypeError: exceptions must be old-style classes or derived from BaseException, not str

it happen when we set a cron at 10 0 * * 0 using the get_prev
but it only work if we set the time to midnight : 0 0 * * 0

setup.py requires setuptools

The entry setuptools entry in the install_requires parameter of setup() in setup.py isn't needed and actual counter productive when using different package solution such as Distribute (a successor of setuptools). I recommend removing it.

Wrong behaviour using exact date and day of week

When using an exact date and time without day of week the behavior is the expected:

In [10]: c = croniter('44 17 2 10 * 49', ret_type=datetime)

In [11]: c.get_next()
Out[11]: datetime.datetime(2016, 10, 2, 17, 44, 49)

In [12]: c.get_next()
Out[12]: datetime.datetime(2017, 10, 2, 17, 44, 49)

In [13]: c.get_next()
Out[13]: datetime.datetime(2018, 10, 2, 17, 44, 49)

In [14]: c.get_next()
Out[14]: datetime.datetime(2019, 10, 2, 17, 44, 49)

In [15]: c.get_next()
Out[15]: datetime.datetime(2020, 10, 2, 17, 44, 49)

In [16]: c.get_next()
Out[16]: datetime.datetime(2021, 10, 2, 17, 44, 49)

In [17]: c.get_next()
Out[17]: datetime.datetime(2022, 10, 2, 17, 44, 49)

In [18]: c.get_next()
Out[18]: datetime.datetime(2023, 10, 2, 17, 44, 49)

In [19]: c.get_next()
Out[19]: datetime.datetime(2024, 10, 2, 17, 44, 49)

In [20]: c.get_next()
Out[20]: datetime.datetime(2025, 10, 2, 17, 44, 49)

In [21]: c.get_next()
Out[21]: datetime.datetime(2026, 10, 2, 17, 44, 49)

In [22]: c.get_next()
Out[22]: datetime.datetime(2027, 10, 2, 17, 44, 49)

In [23]: c.get_next()
Out[23]: datetime.datetime(2028, 10, 2, 17, 44, 49)

But, if the DOW is given, the behavior is erratic:

In [5]: c = croniter('44 17 2 10 6 49', ret_type=datetime)

In [6]: c.get_next()
Out[6]: datetime.datetime(2016, 10, 2, 17, 44, 49)

In [7]: c.get_next()
Out[7]: datetime.datetime(2016, 10, 8, 17, 44, 49)

In [8]: c.get_next()
Out[8]: datetime.datetime(2016, 10, 15, 17, 44, 49)

In [9]: c.get_next()
Out[9]: datetime.datetime(2016, 10, 22, 17, 44, 49)

Return repeated timestamps

I think this is related to #96.

The following fails at 2019-03-31 01:00:00+01:00 when going from winter- to summer-time?
I know the value should be 0-59 but it passes as valid.

import datetime
import dateutil
from croniter import croniter

TZ = dateutil.tz.gettz('Europe/Berlin')
start_time = datetime.datetime(year=2018, month=5, day=10, hour=0, minute=0, second=0, microsecond=0, tzinfo=TZ)
cron = '*/60 * * * *'
print('chron string is valid:', croniter.is_valid(cron))
_iter = croniter(expr_format=cron, start_time=start_time, ret_type=datetime.datetime, day_or=True)
last = next(_iter)
i = 0
for current in _iter:
    if last == current:
        print('fail', i, current)
        break
    last = current
    i += 1

Output:

chron string is valid: True
fail 7799 2019-03-31 01:00:00+01:00

get_prev doubt

a=croniter("*/5 * * * *", datetime.datetime(2012, 04, 01, 20, 25, 00))
a.get_prev(datetime.datetime)
datetime.datetime(2012, 4, 1, 20, 20)

a=croniter("*/5 * * * *", datetime.datetime(2012, 04, 01, 20, 26, 01))
a.get_prev(datetime.datetime)
datetime.datetime(2012, 4, 1, 20, 25)

a=croniter("0 * * * *", datetime.datetime(2012, 04, 01, 20, 00, 59))
a.get_prev(datetime.datetime)
datetime.datetime(2012, 4, 1, 19, 0)

a=croniter("0 * * * *", datetime.datetime(2012, 04, 01, 20, 01, 00))
a.get_prev(datetime.datetime)
datetime.datetime(2012, 4, 1, 20, 0)

as linux crontab instance

when i set cron as "*/5 * * * *
and the time is datetime.datetime(2012, 04, 01, 20, 25, 00)

i think it may better to retun get_prev as datetime.datetime(2012, 4, 1, 20, 25)
but not datetime.datetime(2012, 4, 1, 20, 20) ?

or, i have the wrong usage? thanks

get_next starts returning the same timestamps after DST switch in Europe

This example shows the issue:

from croniter.croniter import croniter
from datetime import datetime
import pytz

tz = pytz.timezone('Europe/Vienna')

def test_croniter(start_time, repeat=40):
    start_time = tz.localize(start_time)
    print("-" * 100)
    print(f"start_time = {str(start_time)}")
    print("-" * 100)

    test_cron = croniter('30 * * * *', start_time=start_time)
    for i in range(repeat):
        print(test_cron.get_next(datetime))

    print("-" * 100)

# First test: cron timestamps start repeating after the DST change from 03:00 AM back to 02:00 AM
test_croniter(datetime(2017, 10, 28, 10, 9))

# Second test: It get's even weirder when the start_time is at the same day as the DST change,
# because the timestamps start repeating at around 21:00 (i.e. 09:00 PM)
test_croniter(datetime(2017, 10, 29, 0, 5))

# Third test: Here's another test that shows repeating timestamps after a DST change
# that changes the clock from 02:00 AM to 03:00 AM
test_croniter(datetime(2017, 3, 25, 20, 5))

It just creates some croniter instances with different start_time values that are all around this year's DST changes in Europe and then repeatedly calls get_next on them and prints the returned datetime objects.

Just for background information:

Europe always switches from standard time to DST at 02:00 AM (switching to 03:00 AM) on the last Sunday of March (was the 26th this year).
The switch from DST back to standard time then happens at 03:00 AM (back to 02:00 AM) on the last Sunday of October (was the 29th this year).

The first test_croniter call produces output like this:

----------------------------------------------------------------------------------------------------
start_time = 2017-10-28 10:09:00+02:00
----------------------------------------------------------------------------------------------------
2017-10-28 10:30:00+02:00
2017-10-28 11:30:00+02:00
...
2017-10-28 23:30:00+02:00
2017-10-29 00:30:00+02:00
2017-10-29 01:30:00+02:00
2017-10-29 02:30:00+02:00
2017-10-29 01:30:00+01:00
2017-10-29 01:30:00+01:00
2017-10-29 01:30:00+01:00
2017-10-29 01:30:00+01:00
... (stays the same after that)

Here's the second one:

----------------------------------------------------------------------------------------------------
start_time = 2017-10-29 00:05:00+02:00
----------------------------------------------------------------------------------------------------
2017-10-29 00:30:00+02:00
2017-10-29 01:30:00+02:00
2017-10-29 02:30:00+02:00
2017-10-29 02:30:00+01:00
2017-10-29 03:30:00+01:00
2017-10-29 04:30:00+01:00
...
2017-10-29 20:30:00+01:00
2017-10-29 21:30:00+01:00
2017-10-29 21:30:00+01:00
2017-10-29 21:30:00+01:00
2017-10-29 21:30:00+01:00
... (stays the same after that)

And here the third one:

----------------------------------------------------------------------------------------------------
start_time = 2017-03-25 20:05:00+01:00
----------------------------------------------------------------------------------------------------
2017-03-25 20:30:00+01:00
2017-03-25 21:30:00+01:00
2017-03-25 22:30:00+01:00
2017-03-25 23:30:00+01:00
2017-03-26 00:30:00+01:00
2017-03-26 01:30:00+01:00
2017-03-26 02:30:00+02:00
2017-03-26 02:30:00+02:00
2017-03-26 02:30:00+02:00
2017-03-26 02:30:00+02:00
... (stays the same after that)

This is also not limited to the "Europe/Vienna" time zone. The issue also comes up when using "Europe/Kiev", "Europe/Lisbon", "Europe/London", ...

Tested against croniter 0.3.19

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.