Hello community, here is the log from the commit of package python-schedule for openSUSE:Factory checked in at 2019-03-26 22:34:51 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-schedule (Old) and /work/SRC/openSUSE:Factory/.python-schedule.new.25356 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-schedule" Tue Mar 26 22:34:51 2019 rev:3 rq:688868 version:0.6.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-schedule/python-schedule.changes 2018-12-24 11:46:19.341190178 +0100 +++ /work/SRC/openSUSE:Factory/.python-schedule.new.25356/python-schedule.changes 2019-03-26 22:35:22.957655538 +0100 @@ -1,0 +2,7 @@ +Tue Mar 26 16:40:26 UTC 2019 - pgaj...@suse.com + +- version update to 0.6.0 + * Make at() accept timestamps with 1 second precision (#267). + * Introduce proper exception hierarchy (#271). + +------------------------------------------------------------------- Old: ---- schedule-0.5.0.tar.gz New: ---- schedule-0.6.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-schedule.spec ++++++ --- /var/tmp/diff_new_pack.cNNgaC/_old 2019-03-26 22:35:24.817654736 +0100 +++ /var/tmp/diff_new_pack.cNNgaC/_new 2019-03-26 22:35:24.817654736 +0100 @@ -1,7 +1,7 @@ # # spec file for package python-schedule # -# Copyright (c) 2018 SUSE LINUX GmbH, Nuernberg, Germany. +# Copyright (c) 2019 SUSE LINUX GmbH, Nuernberg, Germany. # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -18,7 +18,7 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} Name: python-schedule -Version: 0.5.0 +Version: 0.6.0 Release: 0 Summary: Job scheduling module for Python License: MIT ++++++ schedule-0.5.0.tar.gz -> schedule-0.6.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/schedule-0.5.0/HISTORY.rst new/schedule-0.6.0/HISTORY.rst --- old/schedule-0.5.0/HISTORY.rst 2017-11-16 15:58:47.000000000 +0100 +++ new/schedule-0.6.0/HISTORY.rst 2019-01-21 01:26:22.000000000 +0100 @@ -3,6 +3,14 @@ History ------- + +0.6.0 (2019-01-20) +++++++++++++++++++ + +- Make at() accept timestamps with 1 second precision (#267). Thanks @NathanWailes! +- Introduce proper exception hierarchy (#271). Thanks @ConnorSkees! + + 0.5.0 (2017-11-16) ++++++++++++++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/schedule-0.5.0/PKG-INFO new/schedule-0.6.0/PKG-INFO --- old/schedule-0.5.0/PKG-INFO 2017-11-16 16:03:22.000000000 +0100 +++ new/schedule-0.6.0/PKG-INFO 2019-01-21 02:01:43.000000000 +0100 @@ -1,13 +1,12 @@ Metadata-Version: 1.1 Name: schedule -Version: 0.5.0 +Version: 0.6.0 Summary: Job scheduling for humans. Home-page: https://github.com/dbader/schedule Author: Daniel Bader Author-email: m...@dbader.org License: MIT -Download-URL: https://github.com/dbader/schedule/tarball/0.5.0 -Description-Content-Type: UNKNOWN +Download-URL: https://github.com/dbader/schedule/tarball/0.6.0 Description: schedule ======== @@ -55,9 +54,10 @@ schedule.every(10).minutes.do(job) schedule.every().hour.do(job) schedule.every().day.at("10:30").do(job) - schedule.every(5).to(10).days.do(job) + schedule.every(5).to(10).minutes.do(job) schedule.every().monday.do(job) schedule.every().wednesday.at("13:15").do(job) + schedule.every().minute.at(":17").do(job) while True: schedule.run_pending() @@ -76,7 +76,7 @@ Daniel Bader - `@dbader_org <https://twitter.com/dbader_org>`_ - m...@dbader.org - Distributed under the MIT license. See ``LICENSE.txt`` for more information. + Distributed under the MIT license. See `LICENSE.txt <https://github.com/dbader/schedule/blob/master/LICENSE.txt>`_ for more information. https://github.com/dbader/schedule @@ -89,4 +89,5 @@ Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 Classifier: Natural Language :: English diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/schedule-0.5.0/README.rst new/schedule-0.6.0/README.rst --- old/schedule-0.5.0/README.rst 2017-11-16 16:00:14.000000000 +0100 +++ new/schedule-0.6.0/README.rst 2019-01-21 01:23:51.000000000 +0100 @@ -45,9 +45,10 @@ schedule.every(10).minutes.do(job) schedule.every().hour.do(job) schedule.every().day.at("10:30").do(job) - schedule.every(5).to(10).days.do(job) + schedule.every(5).to(10).minutes.do(job) schedule.every().monday.do(job) schedule.every().wednesday.at("13:15").do(job) + schedule.every().minute.at(":17").do(job) while True: schedule.run_pending() @@ -66,6 +67,6 @@ Daniel Bader - `@dbader_org <https://twitter.com/dbader_org>`_ - m...@dbader.org -Distributed under the MIT license. See ``LICENSE.txt`` for more information. +Distributed under the MIT license. See `LICENSE.txt <https://github.com/dbader/schedule/blob/master/LICENSE.txt>`_ for more information. https://github.com/dbader/schedule diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/schedule-0.5.0/schedule/__init__.py new/schedule-0.6.0/schedule/__init__.py --- old/schedule-0.5.0/schedule/__init__.py 2017-11-16 16:00:14.000000000 +0100 +++ new/schedule-0.6.0/schedule/__init__.py 2019-01-21 01:57:47.000000000 +0100 @@ -15,7 +15,7 @@ - A simple to use API for scheduling jobs. - Very lightweight and no external dependencies. - Excellent test coverage. - - Works with Python 2.7 and 3.3 + - Tested on Python 2.7, 3.5 and 3.6 Usage: >>> import schedule @@ -42,11 +42,27 @@ import functools import logging import random +import re import time logger = logging.getLogger('schedule') +class ScheduleError(Exception): + """Base schedule exception""" + pass + + +class ScheduleValueError(ScheduleError): + """Base schedule value error""" + pass + + +class IntervalError(ScheduleValueError): + """An improper interval was used""" + pass + + class CancelJob(object): """ Can be returned from a job to unschedule itself. @@ -227,7 +243,8 @@ @property def second(self): - assert self.interval == 1, 'Use seconds instead of second' + if self.interval != 1: + raise IntervalError('Use seconds instead of second') return self.seconds @property @@ -237,7 +254,8 @@ @property def minute(self): - assert self.interval == 1, 'Use minutes instead of minute' + if self.interval != 1: + raise IntervalError('Use minutes instead of minute') return self.minutes @property @@ -247,7 +265,8 @@ @property def hour(self): - assert self.interval == 1, 'Use hours instead of hour' + if self.interval != 1: + raise IntervalError('Use hours instead of hour') return self.hours @property @@ -257,7 +276,8 @@ @property def day(self): - assert self.interval == 1, 'Use days instead of day' + if self.interval != 1: + raise IntervalError('Use days instead of day') return self.days @property @@ -267,7 +287,8 @@ @property def week(self): - assert self.interval == 1, 'Use weeks instead of week' + if self.interval != 1: + raise IntervalError('Use weeks instead of week') return self.weeks @property @@ -277,43 +298,50 @@ @property def monday(self): - assert self.interval == 1, 'Use mondays instead of monday' + if self.interval != 1: + raise IntervalError('Use mondays instead of monday') self.start_day = 'monday' return self.weeks @property def tuesday(self): - assert self.interval == 1, 'Use tuesdays instead of tuesday' + if self.interval != 1: + raise IntervalError('Use tuesdays instead of tuesday') self.start_day = 'tuesday' return self.weeks @property def wednesday(self): - assert self.interval == 1, 'Use wedesdays instead of wednesday' + if self.interval != 1: + raise IntervalError('Use wednesdays instead of wednesday') self.start_day = 'wednesday' return self.weeks @property def thursday(self): - assert self.interval == 1, 'Use thursday instead of thursday' + if self.interval != 1: + raise IntervalError('Use thursdays instead of thursday') self.start_day = 'thursday' return self.weeks @property def friday(self): - assert self.interval == 1, 'Use fridays instead of friday' + if self.interval != 1: + raise IntervalError('Use fridays instead of friday') self.start_day = 'friday' return self.weeks @property def saturday(self): - assert self.interval == 1, 'Use saturdays instead of saturday' + if self.interval != 1: + raise IntervalError('Use saturdays instead of saturday') self.start_day = 'saturday' return self.weeks @property def sunday(self): - assert self.interval == 1, 'Use sundays instead of sunday' + if self.interval != 1: + raise IntervalError('Use sundays instead of sunday') self.start_day = 'sunday' return self.weeks @@ -326,9 +354,6 @@ :param tags: A unique list of ``Hashable`` tags. :return: The invoked job instance """ - if any([not isinstance(tag, collections.Hashable) for tag in tags]): - raise TypeError('Every tag should be hashable') - if not all(isinstance(tag, collections.Hashable) for tag in tags): raise TypeError('Tags must be hashable') self.tags.update(tags) @@ -336,24 +361,54 @@ def at(self, time_str): """ - Schedule the job every day at a specific time. - - Calling this is only valid for jobs scheduled to run - every N day(s). + Specify a particular time that the job should be run at. - :param time_str: A string in `XX:YY` format. + :param time_str: A string in one of the following formats: `HH:MM:SS`, + `HH:MM`,`:MM`, `:SS`. The format must make sense given how often + the job is repeating; for example, a job that repeats every minute + should not be given a string in the form `HH:MM:SS`. The difference + between `:MM` and `:SS` is inferred from the selected time-unit + (e.g. `every().hour.at(':30')` vs. `every().minute.at(':30')`). :return: The invoked job instance """ - assert self.unit in ('days', 'hours') or self.start_day - hour, minute = time_str.split(':') - minute = int(minute) + if (self.unit not in ('days', 'hours', 'minutes') + and not self.start_day): + raise ScheduleValueError('Invalid unit') + if not isinstance(time_str, str): + raise TypeError('at() should be passed a string') + if self.unit == 'days' or self.start_day: + if not re.match(r'^([0-2]\d:)?[0-5]\d:[0-5]\d$', time_str): + raise ScheduleValueError('Invalid time format') + if self.unit == 'hours': + if not re.match(r'^([0-5]\d)?:[0-5]\d$', time_str): + raise ScheduleValueError(('Invalid time format for' + ' an hourly job')) + if self.unit == 'minutes': + if not re.match(r'^:[0-5]\d$', time_str): + raise ScheduleValueError(('Invalid time format for' + ' a minutely job')) + time_values = time_str.split(':') + if len(time_values) == 3: + hour, minute, second = time_values + elif len(time_values) == 2 and self.unit == 'minutes': + hour = 0 + minute = 0 + _, second = time_values + else: + hour, minute = time_values + second = 0 if self.unit == 'days' or self.start_day: hour = int(hour) - assert 0 <= hour <= 23 + if not (0 <= hour <= 23): + raise ScheduleValueError('Invalid number of hours') elif self.unit == 'hours': hour = 0 - assert 0 <= minute <= 59 - self.at_time = datetime.time(hour, minute) + elif self.unit == 'minutes': + hour = 0 + minute = 0 + minute = int(minute) + second = int(second) + self.at_time = datetime.time(hour, minute, second) return self def to(self, latest): @@ -417,10 +472,12 @@ """ Compute the instant when this job should run next. """ - assert self.unit in ('seconds', 'minutes', 'hours', 'days', 'weeks') + if self.unit not in ('seconds', 'minutes', 'hours', 'days', 'weeks'): + raise ScheduleValueError('Invalid unit') if self.latest is not None: - assert self.latest >= self.interval + if not (self.latest >= self.interval): + raise ScheduleError('`latest` is greater than `interval`') interval = random.randint(self.interval, self.latest) else: interval = self.interval @@ -428,7 +485,8 @@ self.period = datetime.timedelta(**{self.unit: interval}) self.next_run = datetime.datetime.now() + self.period if self.start_day is not None: - assert self.unit == 'weeks' + if self.unit != 'weeks': + raise ScheduleValueError('`unit` should be \'weeks\'') weekdays = ( 'monday', 'tuesday', @@ -438,21 +496,26 @@ 'saturday', 'sunday' ) - assert self.start_day in weekdays + if self.start_day not in weekdays: + raise ScheduleValueError('Invalid start day') weekday = weekdays.index(self.start_day) days_ahead = weekday - self.next_run.weekday() if days_ahead <= 0: # Target day already happened this week days_ahead += 7 self.next_run += datetime.timedelta(days_ahead) - self.period if self.at_time is not None: - assert self.unit in ('days', 'hours') or self.start_day is not None + if (self.unit not in ('days', 'hours', 'minutes') + and self.start_day is None): + raise ScheduleValueError(('Invalid unit without' + ' specifying start day')) kwargs = { - 'minute': self.at_time.minute, 'second': self.at_time.second, 'microsecond': 0 } if self.unit == 'days' or self.start_day is not None: kwargs['hour'] = self.at_time.hour + if self.unit in ['days', 'hours'] or self.start_day is not None: + kwargs['minute'] = self.at_time.minute self.next_run = self.next_run.replace(**kwargs) # If we are running for the first time, make sure we run # at the specified time *today* (or *this hour*) as well @@ -461,8 +524,15 @@ if (self.unit == 'days' and self.at_time > now.time() and self.interval == 1): self.next_run = self.next_run - datetime.timedelta(days=1) - elif self.unit == 'hours' and self.at_time.minute > now.minute: + elif self.unit == 'hours' \ + and self.at_time.minute > now.minute \ + or (self.at_time.minute == now.minute + and self.at_time.second > now.second): self.next_run = self.next_run - datetime.timedelta(hours=1) + elif self.unit == 'minutes' \ + and self.at_time.second > now.second: + self.next_run = self.next_run - \ + datetime.timedelta(minutes=1) if self.start_day is not None and self.at_time is not None: # Let's see if we will still make that time we specified today if (self.next_run - datetime.datetime.now()).days >= 7: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/schedule-0.5.0/schedule.egg-info/PKG-INFO new/schedule-0.6.0/schedule.egg-info/PKG-INFO --- old/schedule-0.5.0/schedule.egg-info/PKG-INFO 2017-11-16 16:03:22.000000000 +0100 +++ new/schedule-0.6.0/schedule.egg-info/PKG-INFO 2019-01-21 02:01:43.000000000 +0100 @@ -1,13 +1,12 @@ Metadata-Version: 1.1 Name: schedule -Version: 0.5.0 +Version: 0.6.0 Summary: Job scheduling for humans. Home-page: https://github.com/dbader/schedule Author: Daniel Bader Author-email: m...@dbader.org License: MIT -Download-URL: https://github.com/dbader/schedule/tarball/0.5.0 -Description-Content-Type: UNKNOWN +Download-URL: https://github.com/dbader/schedule/tarball/0.6.0 Description: schedule ======== @@ -55,9 +54,10 @@ schedule.every(10).minutes.do(job) schedule.every().hour.do(job) schedule.every().day.at("10:30").do(job) - schedule.every(5).to(10).days.do(job) + schedule.every(5).to(10).minutes.do(job) schedule.every().monday.do(job) schedule.every().wednesday.at("13:15").do(job) + schedule.every().minute.at(":17").do(job) while True: schedule.run_pending() @@ -76,7 +76,7 @@ Daniel Bader - `@dbader_org <https://twitter.com/dbader_org>`_ - m...@dbader.org - Distributed under the MIT license. See ``LICENSE.txt`` for more information. + Distributed under the MIT license. See `LICENSE.txt <https://github.com/dbader/schedule/blob/master/LICENSE.txt>`_ for more information. https://github.com/dbader/schedule @@ -89,4 +89,5 @@ Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 Classifier: Natural Language :: English diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/schedule-0.5.0/setup.py new/schedule-0.6.0/setup.py --- old/schedule-0.5.0/setup.py 2017-11-16 15:59:38.000000000 +0100 +++ new/schedule-0.6.0/setup.py 2019-01-21 01:24:51.000000000 +0100 @@ -14,11 +14,12 @@ from setuptools import setup -SCHEDULE_VERSION = '0.5.0' +SCHEDULE_VERSION = '0.6.0' SCHEDULE_DOWNLOAD_URL = ( 'https://github.com/dbader/schedule/tarball/' + SCHEDULE_VERSION ) + def read_file(filename): """ Read a utf8 encoded text file and return its contents. @@ -26,6 +27,7 @@ with codecs.open(filename, 'r', 'utf8') as f: return f.read() + setup( name='schedule', packages=['schedule'], @@ -49,6 +51,7 @@ 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', 'Natural Language :: English', ], ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/schedule-0.5.0/test_schedule.py new/schedule-0.6.0/test_schedule.py --- old/schedule-0.5.0/test_schedule.py 2017-11-16 16:00:14.000000000 +0100 +++ new/schedule-0.6.0/test_schedule.py 2019-01-21 01:57:17.000000000 +0100 @@ -9,7 +9,7 @@ # pylint: disable-msg=R0201,C0111,E0102,R0904,R0901 import schedule -from schedule import every +from schedule import every, ScheduleError, ScheduleValueError, IntervalError def make_mock_job(name=None): @@ -22,12 +22,13 @@ """ Monkey-patch datetime for predictable results """ - def __init__(self, year, month, day, hour, minute): + def __init__(self, year, month, day, hour, minute, second=0): self.year = year self.month = month self.day = day self.hour = hour self.minute = minute + self.second = second def __enter__(self): class MockDate(datetime.datetime): @@ -38,7 +39,7 @@ @classmethod def now(cls): return cls(self.year, self.month, self.day, - self.hour, self.minute) + self.hour, self.minute, self.second) self.original_datetime = datetime.datetime datetime.datetime = MockDate @@ -57,6 +58,70 @@ assert every().days.unit == 'days' assert every().weeks.unit == 'weeks' + job_instance = schedule.Job(interval=2) + # without a context manager, it incorrectly raises an error because + # it is not callable + with self.assertRaises(IntervalError): + job_instance.minute + with self.assertRaises(IntervalError): + job_instance.hour + with self.assertRaises(IntervalError): + job_instance.day + with self.assertRaises(IntervalError): + job_instance.week + with self.assertRaises(IntervalError): + job_instance.monday + with self.assertRaises(IntervalError): + job_instance.tuesday + with self.assertRaises(IntervalError): + job_instance.wednesday + with self.assertRaises(IntervalError): + job_instance.thursday + with self.assertRaises(IntervalError): + job_instance.friday + with self.assertRaises(IntervalError): + job_instance.saturday + with self.assertRaises(IntervalError): + job_instance.sunday + + # test an invalid unit + job_instance.unit = "foo" + self.assertRaises(ScheduleValueError, job_instance.at, "1:0:0") + self.assertRaises(ScheduleValueError, job_instance._schedule_next_run) + + # test start day exists but unit is not 'weeks' + job_instance.unit = "days" + job_instance.start_day = 1 + self.assertRaises(ScheduleValueError, job_instance._schedule_next_run) + + # test weeks with an invalid start day + job_instance.unit = "weeks" + job_instance.start_day = "bar" + self.assertRaises(ScheduleValueError, job_instance._schedule_next_run) + + # test a valid unit with invalid hours/minutes/seconds + job_instance.unit = "days" + self.assertRaises(ScheduleValueError, job_instance.at, "25:00:00") + self.assertRaises(ScheduleValueError, job_instance.at, "00:61:00") + self.assertRaises(ScheduleValueError, job_instance.at, "00:00:61") + + # test invalid time format + self.assertRaises(ScheduleValueError, job_instance.at, "25:0:0") + self.assertRaises(ScheduleValueError, job_instance.at, "0:61:0") + self.assertRaises(ScheduleValueError, job_instance.at, "0:0:61") + + # test (very specific) seconds with unspecified start_day + job_instance.unit = "seconds" + job_instance.at_time = datetime.datetime.now() + job_instance.start_day = None + self.assertRaises(ScheduleValueError, job_instance._schedule_next_run) + + # test self.latest >= self.interval + job_instance.latest = 1 + self.assertRaises(ScheduleError, job_instance._schedule_next_run) + job_instance.latest = 3 + self.assertRaises(ScheduleError, job_instance._schedule_next_run) + def test_singular_time_units_match_plural_units(self): assert every().second.unit == every().seconds.unit assert every().minute.unit == every().minutes.unit @@ -91,16 +156,84 @@ mock_job = make_mock_job() assert every().day.at('10:30').do(mock_job).next_run.hour == 10 assert every().day.at('10:30').do(mock_job).next_run.minute == 30 + assert every().day.at('10:30:50').do(mock_job).next_run.second == 50 + + self.assertRaises(ScheduleValueError, every().day.at, '2:30:000001') + self.assertRaises(ScheduleValueError, every().day.at, '::2') + self.assertRaises(ScheduleValueError, every().day.at, '.2') + self.assertRaises(ScheduleValueError, every().day.at, '2') + self.assertRaises(ScheduleValueError, every().day.at, ':2') + self.assertRaises(ScheduleValueError, every().day.at, ' 2:30:00') + self.assertRaises(ScheduleValueError, every().do, lambda: 0) + self.assertRaises(TypeError, every().day.at, 2) + + # without a context manager, it incorrectly raises an error because + # it is not callable + with self.assertRaises(IntervalError): + every(interval=2).second + with self.assertRaises(IntervalError): + every(interval=2).minute + with self.assertRaises(IntervalError): + every(interval=2).hour + with self.assertRaises(IntervalError): + every(interval=2).day + with self.assertRaises(IntervalError): + every(interval=2).week + with self.assertRaises(IntervalError): + every(interval=2).monday + with self.assertRaises(IntervalError): + every(interval=2).tuesday + with self.assertRaises(IntervalError): + every(interval=2).wednesday + with self.assertRaises(IntervalError): + every(interval=2).thursday + with self.assertRaises(IntervalError): + every(interval=2).friday + with self.assertRaises(IntervalError): + every(interval=2).saturday + with self.assertRaises(IntervalError): + every(interval=2).sunday def test_at_time_hour(self): with mock_datetime(2010, 1, 6, 12, 20): mock_job = make_mock_job() assert every().hour.at(':30').do(mock_job).next_run.hour == 12 assert every().hour.at(':30').do(mock_job).next_run.minute == 30 + assert every().hour.at(':30').do(mock_job).next_run.second == 0 assert every().hour.at(':10').do(mock_job).next_run.hour == 13 assert every().hour.at(':10').do(mock_job).next_run.minute == 10 + assert every().hour.at(':10').do(mock_job).next_run.second == 0 assert every().hour.at(':00').do(mock_job).next_run.hour == 13 assert every().hour.at(':00').do(mock_job).next_run.minute == 0 + assert every().hour.at(':00').do(mock_job).next_run.second == 0 + + self.assertRaises(ScheduleValueError, every().hour.at, '2:30:00') + self.assertRaises(ScheduleValueError, every().hour.at, '::2') + self.assertRaises(ScheduleValueError, every().hour.at, '.2') + self.assertRaises(ScheduleValueError, every().hour.at, '2') + self.assertRaises(ScheduleValueError, every().hour.at, ' 2:30') + self.assertRaises(ScheduleValueError, every().hour.at, "61:00") + self.assertRaises(ScheduleValueError, every().hour.at, "00:61") + self.assertRaises(ScheduleValueError, every().hour.at, "01:61") + self.assertRaises(TypeError, every().hour.at, 2) + + def test_at_time_minute(self): + with mock_datetime(2010, 1, 6, 12, 20, 30): + mock_job = make_mock_job() + assert every().minute.at(':40').do(mock_job).next_run.hour == 12 + assert every().minute.at(':40').do(mock_job).next_run.minute == 20 + assert every().minute.at(':40').do(mock_job).next_run.second == 40 + assert every().minute.at(':10').do(mock_job).next_run.hour == 12 + assert every().minute.at(':10').do(mock_job).next_run.minute == 21 + assert every().minute.at(':10').do(mock_job).next_run.second == 10 + + self.assertRaises(ScheduleValueError, every().minute.at, '::2') + self.assertRaises(ScheduleValueError, every().minute.at, '.2') + self.assertRaises(ScheduleValueError, every().minute.at, '2') + self.assertRaises(ScheduleValueError, every().minute.at, '2:30:00') + self.assertRaises(ScheduleValueError, every().minute.at, '2:30') + self.assertRaises(ScheduleValueError, every().minute.at, ' :30') + self.assertRaises(TypeError, every().minute.at, 2) def test_next_run_time(self): with mock_datetime(2010, 1, 6, 12, 15):