diff --git a/.bzrignore b/.bzrignore new file mode 100644 index 0000000..3061b41 --- /dev/null +++ b/.bzrignore @@ -0,0 +1,2 @@ +*.egg-info +build diff --git a/.gitignore b/.gitignore deleted file mode 100644 index ae08812..0000000 --- a/.gitignore +++ /dev/null @@ -1,14 +0,0 @@ -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] - -build/ -dist/ -*.egg-info/ -.tox/ - -# Sphinx documentation -docs/_build/ - -# Timezone information -tzdata*.tar.gz diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index fb75b71..0000000 --- a/.travis.yml +++ /dev/null @@ -1,16 +0,0 @@ -language: python -python: - - "2.6" - - "2.7" - - "3.2" - - "3.3" - - "3.4" - - "pypy" - - "pypy3" -install: - - pip install six - - if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install unittest2; fi - - python updatezinfo.py -script: - - python setup.py test -sudo: false diff --git a/LICENSE b/LICENSE index 2fc67ab..5834335 100644 --- a/LICENSE +++ b/LICENSE @@ -1,8 +1,7 @@ dateutil - Extensions to the standard Python datetime module. Copyright (c) 2003-2011 - Gustavo Niemeyer -Copyright (c) 2012-2014 - Tomi Pieviläinen -Copyright (c) 2014 - Yaron de Leeuw +Copyright (c) 2012 - Tomi Pieviläinen All rights reserved. diff --git a/MANIFEST.in b/MANIFEST.in index fdfe94a..453742b 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1 +1,4 @@ -include LICENSE NEWS zonefile_metadata.json updatezinfo.py +recursive-include dateutil *.py *.tar.* +recursive-include sandbox *.py +include setup.py setup.cfg MANIFEST.in README LICENSE NEWS Makefile +include test.py example.py diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a6ae933 --- /dev/null +++ b/Makefile @@ -0,0 +1,25 @@ +# +# Simple wrapper for setup.py script +# + +DESTDIR=/ +PYTHON=python + +prefix=/usr +bindir=$(prefix)/bin + +all: + $(PYTHON) setup.py build + +install: + $(PYTHON) setup.py install \ + --root=$(DESTDIR) \ + --prefix=$(prefix) \ + --install-scripts=$(bindir) + +dist: + $(PYTHON) setup.py sdist + +rpm: + $(PYTHON) setup.py bdist_rpm + diff --git a/NEWS b/NEWS index fe0de4e..d1c59fb 100644 --- a/NEWS +++ b/NEWS @@ -1,232 +1,173 @@ -Version 2.4.2 -------------- -- Updated zoneinfo to 2015b. -- Fixed issue with parsing of tzstr on Python 2.7.x; tzstr will now be decoded - if not a unicode type. gh #51 (lp:1331576), gh pr #55. -- Fix a parser issue where AM and PM tokens were showing up in fuzzy date - stamps, triggering inappropriate errors. gh #56 (lp: 1428895), gh pr #63. -- Missing function "setcachesize" removed from zoneinfo __all__ list by @ryanss, - fixing an issue with wildcard imports of dateutil.zoneinfo. (gh pr #66). -- (PyPi only) Fix an issue with source distributions not including the test - suite. - - -Version 2.4.1 -------------- - -- Added explicit check for valid hours if AM/PM is specified in parser. - (gh pr #22, issue #21) -- Fix bug in rrule introduced in 2.4.0 where byweekday parameter was not - handled properly. (gh pr #35, issue #34) -- Fix error where parser allowed some invalid dates, overwriting existing hours - with the last 2-digit number in the string. (gh pr #32, issue #31) -- Fix and add test for Python 2.x compatibility with boolean checking of - relativedelta objects. Implemented by @nimasmi (gh pr #43) and Cédric Krier - (lp: 1035038) -- Replaced parse() calls with explicit datetime objects in unit tests unrelated - to parser. (gh pr #36) -- Changed private _byxxx from sets to sorted tuples and fixed one currently - unreachable bug in _construct_byset. (gh pr #54) -- Additional documentation for parser (gh pr #29, #33, #41) and rrule. -- Formatting fixes to documentation of rrule and README.rst. -- Updated zoneinfo to 2015a. - -Version 2.4.0 -------------- - -- Fix an issue with relativedelta and freezegun (lp:1374022) -- Fix tzinfo in windows for timezones without dst (lp:1010050, gh #2) -- Ignore missing timezones in windows like in POSIX -- Fix minimal version requirement for six (gh #6) -- Many rrule changes and fixes by @pganssle (gh pull requests #13 #14 #17), - including defusing some infinite loops (gh #4) - -Version 2.3 ------------ - -- Cleanup directory structure, moved test.py to dateutil/tests/test.py - -- Changed many aspects of dealing with the zone info file. Instead of a cache, - all the zones are loaded to memory, but symbolic links are loaded only once, - so not much memory is used. - -- The package is now zip-safe, and universal-wheelable, thanks to changes in - the handling of the zoneinfo file. - -- Fixed tzwin silently not imported on windows python2 - -- New maintainer, together with new hosting: GitHub, Travis, Read-The-Docs - -Version 2.2 ------------ - -- Updated zoneinfo to 2013h - -- fuzzy_with_tokens parse addon from Christopher Corley - -- Bug with LANG=C fixed by Mike Gilbert - -Version 2.1 ------------ - -- New maintainer - -- Dateutil now works on Python 2.6, 2.7 and 3.2 from same codebase (with six) - -- #704047: Ismael Carnales' patch for a new time format - -- Small bug fixes, thanks for reporters! - - -Version 2.0 ------------ - -- Ported to Python 3, by Brian Jones. If you need dateutil for Python 2.X, - please continue using the 1.X series. - -- There's no such thing as a "PSF License". This source code is now - made available under the Simplified BSD license. See LICENSE for - details. - -Version 1.5 ------------ - -- As reported by Mathieu Bridon, rrules were matching the bysecond rules - incorrectly against byminute in some circumstances when the SECONDLY - frequency was in use, due to a copy & paste bug. The problem has been - unittested and corrected. - -- Adam Ryan reported a problem in the relativedelta implementation which - affected the yearday parameter in the month of January specifically. - This has been unittested and fixed. - -- Updated timezone information. - - -Version 1.4.1 -------------- - -- Updated timezone information. - - -Version 1.4 ------------ - -- Fixed another parser precision problem on conversion of decimal seconds - to microseconds, as reported by Erik Brown. Now these issues are gone - for real since it's not using floating point arithmetic anymore. - -- Fixed case where tzrange.utcoffset and tzrange.dst() might fail due - to a date being used where a datetime was expected (reported and fixed - by Lennart Regebro). - -- Prevent tzstr from introducing daylight timings in strings that didn't - specify them (reported by Lennart Regebro). - -- Calls like gettz("GMT+3") and gettz("UTC-2") will now return the - expected values, instead of the TZ variable behavior. - -- Fixed DST signal handling in zoneinfo files. Reported by - Nicholas F. Fabry and John-Mark Gurney. - - -Version 1.3 ------------ - -- Fixed precision problem on conversion of decimal seconds to - microseconds, as reported by Skip Montanaro. - -- Fixed bug in constructor of parser, and converted parser classes to - new-style classes. Original report and patch by Michael Elsdörfer. - -- Initialize tzid and comps in tz.py, to prevent the code from ever - raising a NameError (even with broken files). Johan Dahlin suggested - the fix after a pyflakes run. - -- Version is now published in dateutil.__version__, as requested - by Darren Dale. - -- All code is compatible with new-style division. - - -Version 1.2 ------------ - -- Now tzfile will round timezones to full-minutes if necessary, - since Python's datetime doesn't support sub-minute offsets. - Thanks to Ilpo Nyyssönen for reporting the issue. - -- Removed bare string exceptions, as reported and fixed by - Wilfredo Sánchez Vega. - -- Fix bug in leap count parsing (reported and fixed by Eugene Oden). - - -Version 1.1 ------------ - -- Fixed rrule byyearday handling. Abramo Bagnara pointed out that - RFC2445 allows negative numbers. - -- Fixed --prefix handling in setup.py (by Sidnei da Silva). - -- Now tz.gettz() returns a tzlocal instance when not given any - arguments and no other timezone information is found. - -- Updating timezone information to version 2005q. - - -Version 1.0 ------------ - -- Fixed parsing of XXhXXm formatted time after day/month/year - has been parsed. - -- Added patch by Jeffrey Harris optimizing rrule.__contains__. - - -Version 0.9 ------------ - -- Fixed pickling of timezone types, as reported by - Andreas Köhler. - -- Implemented internal timezone information with binary - timezone files [1]. datautil.tz.gettz() function will now - try to use the system timezone files, and fallback to - the internal versions. It's also possible to ask for - the internal versions directly by using - dateutil.zoneinfo.gettz(). - -- New tzwin timezone type, allowing access to Windows - internal timezones (contributed by Jeffrey Harris). - -- Fixed parsing of unicode date strings. - -- Accept parserinfo instances as the parser constructor - parameter, besides parserinfo (sub)classes. - -- Changed weekday to spell the not-set n value as None - instead of 0. - -- Fixed other reported bugs. - -[1] http://www.twinsun.com/tz/tz-link.htm - - -Version 0.5 ------------ - -- Removed FREQ_ prefix from rrule frequency constants - WARNING: this breaks compatibility with previous versions. - -- Fixed rrule.between() for cases where "after" is achieved - before even starting, as reported by Andreas Köhler. - -- Fixed two digit zero-year parsing (such as 31-Dec-00), as - reported by Jim Abramson, and included test case for this. - -- Sort exdate and rdate before iterating over them, so that - it's not necessary to sort them before adding to the rruleset, - as reported by Nicholas Piper. - +Version 2.2 +----------- + +- Updated zoneinfo to 2013h + +- fuzzy_with_tokens parse addon from Christopher Corley + +- Bug with LANG=C fixed by Mike Gilbert + +Version 2.1 +----------- + +- New maintainer + +- Dateutil now works on Python 2.6, 2.7 and 3.2 from same codebase (with six) + +- #704047: Ismael Carnales' patch for a new time format + +- Small bug fixes, thanks for reporters! + + +Version 2.0 +----------- + +- Ported to Python 3, by Brian Jones. If you need dateutil for Python 2.X, + please continue using the 1.X series. + +- There's no such thing as a "PSF License". This source code is now + made available under the Simplified BSD license. See LICENSE for + details. + +Version 1.5 +----------- + +- As reported by Mathieu Bridon, rrules were matching the bysecond rules + incorrectly against byminute in some circumstances when the SECONDLY + frequency was in use, due to a copy & paste bug. The problem has been + unittested and corrected. + +- Adam Ryan reported a problem in the relativedelta implementation which + affected the yearday parameter in the month of January specifically. + This has been unittested and fixed. + +- Updated timezone information. + + +Version 1.4.1 +------------- + +- Updated timezone information. + + +Version 1.4 +----------- + +- Fixed another parser precision problem on conversion of decimal seconds + to microseconds, as reported by Erik Brown. Now these issues are gone + for real since it's not using floating point arithmetic anymore. + +- Fixed case where tzrange.utcoffset and tzrange.dst() might fail due + to a date being used where a datetime was expected (reported and fixed + by Lennart Regebro). + +- Prevent tzstr from introducing daylight timings in strings that didn't + specify them (reported by Lennart Regebro). + +- Calls like gettz("GMT+3") and gettz("UTC-2") will now return the + expected values, instead of the TZ variable behavior. + +- Fixed DST signal handling in zoneinfo files. Reported by + Nicholas F. Fabry and John-Mark Gurney. + + +Version 1.3 +----------- + +- Fixed precision problem on conversion of decimal seconds to + microseconds, as reported by Skip Montanaro. + +- Fixed bug in constructor of parser, and converted parser classes to + new-style classes. Original report and patch by Michael Elsdörfer. + +- Initialize tzid and comps in tz.py, to prevent the code from ever + raising a NameError (even with broken files). Johan Dahlin suggested + the fix after a pyflakes run. + +- Version is now published in dateutil.__version__, as requested + by Darren Dale. + +- All code is compatible with new-style division. + + +Version 1.2 +----------- + +- Now tzfile will round timezones to full-minutes if necessary, + since Python's datetime doesn't support sub-minute offsets. + Thanks to Ilpo Nyyssönen for reporting the issue. + +- Removed bare string exceptions, as reported and fixed by + Wilfredo Sánchez Vega. + +- Fix bug in leap count parsing (reported and fixed by Eugene Oden). + + +Version 1.1 +----------- + +- Fixed rrule byyearday handling. Abramo Bagnara pointed out that + RFC2445 allows negative numbers. + +- Fixed --prefix handling in setup.py (by Sidnei da Silva). + +- Now tz.gettz() returns a tzlocal instance when not given any + arguments and no other timezone information is found. + +- Updating timezone information to version 2005q. + + +Version 1.0 +----------- + +- Fixed parsing of XXhXXm formatted time after day/month/year + has been parsed. + +- Added patch by Jeffrey Harris optimizing rrule.__contains__. + + +Version 0.9 +----------- + +- Fixed pickling of timezone types, as reported by + Andreas Köhler. + +- Implemented internal timezone information with binary + timezone files [1]. datautil.tz.gettz() function will now + try to use the system timezone files, and fallback to + the internal versions. It's also possible to ask for + the internal versions directly by using + dateutil.zoneinfo.gettz(). + +- New tzwin timezone type, allowing access to Windows + internal timezones (contributed by Jeffrey Harris). + +- Fixed parsing of unicode date strings. + +- Accept parserinfo instances as the parser constructor + parameter, besides parserinfo (sub)classes. + +- Changed weekday to spell the not-set n value as None + instead of 0. + +- Fixed other reported bugs. + +[1] http://www.twinsun.com/tz/tz-link.htm + + +Version 0.5 +----------- + +- Removed FREQ_ prefix from rrule frequency constants + WARNING: this breaks compatibility with previous versions. + +- Fixed rrule.between() for cases where "after" is achieved + before even starting, as reported by Andreas Köhler. + +- Fixed two digit zero-year parsing (such as 31-Dec-00), as + reported by Jim Abramson, and included test case for this. + +- Sort exdate and rdate before iterating over them, so that + it's not necessary to sort them before adding to the rruleset, + as reported by Nicholas Piper. + diff --git a/README b/README new file mode 100644 index 0000000..b1afd9c --- /dev/null +++ b/README @@ -0,0 +1,2000 @@ +## This file is in the moin format. The latest version is found +## at https://moin.conectiva.com.br/DateUtil + +== Contents == +[[TableOfContents]] + +== Description == +The '''dateutil''' module provides powerful extensions to +the standard '''datetime''' module, available in Python. + +== Features == + + * Computing of relative deltas (next month, next year, + next monday, last week of month, etc); + + * Computing of relative deltas between two given + date and/or datetime objects; + + * Computing of dates based on very flexible recurrence rules, + using a superset of the + [ftp://ftp.rfc-editor.org/in-notes/rfc2445.txt iCalendar] + specification. Parsing of RFC strings is supported as well. + + * Generic parsing of dates in almost any string format; + + * Timezone (tzinfo) implementations for tzfile(5) format + files (/etc/localtime, /usr/share/zoneinfo, etc), TZ + environment string (in all known formats), iCalendar + format files, given ranges (with help from relative deltas), + local machine timezone, fixed offset timezone, UTC timezone, + and Windows registry-based time zones. + + * Internal up-to-date world timezone information based on + Olson's database. + + * Computing of Easter Sunday dates for any given year, + using Western, Orthodox or Julian algorithms; + + * More than 400 test cases. + +== Quick example == +Here's a snapshot, just to give an idea about the power of the +package. For more examples, look at the documentation below. + +Suppose you want to know how much time is left, in +years/months/days/etc, before the next easter happening on a +year with a Friday 13th in August, and you want to get today's +date out of the "date" unix system command. Here is the code: +{{{ +from dateutil.relativedelta import * +from dateutil.easter import * +from dateutil.rrule import * +from dateutil.parser import * +from datetime import * +import commands +import os +now = parse(commands.getoutput("date")) +today = now.date() +year = rrule(YEARLY,bymonth=8,bymonthday=13,byweekday=FR)[0].year +rdelta = relativedelta(easter(year), today) +print "Today is:", today +print "Year with next Aug 13th on a Friday is:", year +print "How far is the Easter of that year:", rdelta +print "And the Easter of that year is:", today+rdelta +}}} + +And here's the output: +{{{ +Today is: 2003-10-11 +Year with next Aug 13th on a Friday is: 2004 +How far is the Easter of that year: relativedelta(months=+6) +And the Easter of that year is: 2004-04-11 +}}} + +{i} Being exactly 6 months ahead was '''really''' a coincidence :) + +== Download == +The following files are available. + * attachment:python-dateutil-1.0.tar.bz2 + * attachment:python-dateutil-1.0-1.noarch.rpm + +== Author == +The dateutil module was written by Gustavo Niemeyer and +is currently maintained by Tomi Pieviläinen . The latest +code is available in [https://launchpad.net/dateutil Launchpad]. + +== Documentation == +The following modules are available. + +=== relativedelta === +This module offers the '''relativedelta''' type, which is based +on the specification of the excelent work done by M.-A. Lemburg in his +[http://www.egenix.com/files/python/mxDateTime.html mxDateTime] +extension. However, notice that this type '''does not''' implement the +same algorithm as his work. Do not expect it to behave like +{{{mxDateTime}}}'s counterpart. + +==== relativedelta type ==== + +There's two different ways to build a relativedelta instance. The +first one is passing it two {{{date}}}/{{{datetime}}} instances: +{{{ +relativedelta(datetime1, datetime2) +}}} + +This will build the relative difference between {{{datetime1}}} and +{{{datetime2}}}, so that the following constraint is always true: +{{{ +datetime2+relativedelta(datetime1, datetime2) == datetime1 +}}} + +Notice that instead of {{{datetime}}} instances, you may use +{{{date}}} instances, or a mix of both. + +And the other way is to use any of the following keyword arguments: + + year, month, day, hour, minute, second, microsecond:: + Absolute information. + + years, months, weeks, days, hours, minutes, seconds, microseconds:: + Relative information, may be negative. + + weekday:: + One of the weekday instances ({{{MO}}}, {{{TU}}}, etc). These + instances may receive a parameter {{{n}}}, specifying the {{{n}}}th + weekday, which could be positive or negative (like {{{MO(+2)}}} or + {{{MO(-3)}}}. Not specifying it is the same as specifying {{{+1}}}. + You can also use an integer, where {{{0=MO}}}. Notice that, + for example, if the calculated date is already Monday, using + {{{MO}}} or {{{MO(+1)}}} (which is the same thing in this context), + won't change the day. + + leapdays:: + Will add given days to the date found, but only if the computed + year is a leap year and the computed date is post 28 of february. + + yearday, nlyearday:: + Set the yearday or the non-leap year day (jump leap days). + These are converted to {{{day}}}/{{{month}}}/{{{leapdays}}} + information. + +==== Behavior of operations ==== +If you're curious about exactly how the relative delta will act +on operations, here is a description of its behavior. + + 1. Calculate the absolute year, using the {{{year}}} argument, or the + original datetime year, if the argument is not present. + 1. Add the relative {{{years}}} argument to the absolute year. + 1. Do steps 1 and 2 for {{{month}}}/{{{months}}}. + 1. Calculate the absolute day, using the {{{day}}} argument, or the + original datetime day, if the argument is not present. Then, subtract + from the day until it fits in the year and month found after their + operations. + 1. Add the relative {{{days}}} argument to the absolute day. Notice + that the {{{weeks}}} argument is multiplied by 7 and added to {{{days}}}. + 1. If {{{leapdays}}} is present, the computed year is a leap year, and + the computed month is after february, remove one day from the found date. + 1. Do steps 1 and 2 for {{{hour}}}/{{{hours}}}, {{{minute}}}/{{{minutes}}}, + {{{second}}}/{{{seconds}}}, {{{microsecond}}}/{{{microseconds}}}. + 1. If the {{{weekday}}} argument is present, calculate the {{{n}}}th + occurrence of the given weekday. + +==== Examples ==== + +Let's begin our trip. +{{{ +>>> from datetime import *; from dateutil.relativedelta import * +>>> import calendar +}}} + +Store some values. +{{{ +>>> NOW = datetime.now() +>>> TODAY = date.today() +>>> NOW +datetime.datetime(2003, 9, 17, 20, 54, 47, 282310) +>>> TODAY +datetime.date(2003, 9, 17) +}}} + +Next month. +{{{ +>>> NOW+relativedelta(months=+1) +datetime.datetime(2003, 10, 17, 20, 54, 47, 282310) +}}} + +Next month, plus one week. +{{{ +>>> NOW+relativedelta(months=+1, weeks=+1) +datetime.datetime(2003, 10, 24, 20, 54, 47, 282310) +}}} + +Next month, plus one week, at 10am. +{{{ +>>> TODAY+relativedelta(months=+1, weeks=+1, hour=10) +datetime.datetime(2003, 10, 24, 10, 0) +}}} + +Let's try the other way around. Notice that the +hour setting we get in the relativedelta is relative, +since it's a difference, and the weeks parameter +has gone. +{{{ +>>> relativedelta(datetime(2003, 10, 24, 10, 0), TODAY) +relativedelta(months=+1, days=+7, hours=+10) +}}} + +One month before one year. +{{{ +>>> NOW+relativedelta(years=+1, months=-1) +datetime.datetime(2004, 8, 17, 20, 54, 47, 282310) +}}} + +How does it handle months with different numbers of days? +Notice that adding one month will never cross the month +boundary. +{{{ +>>> date(2003,1,27)+relativedelta(months=+1) +datetime.date(2003, 2, 27) +>>> date(2003,1,31)+relativedelta(months=+1) +datetime.date(2003, 2, 28) +>>> date(2003,1,31)+relativedelta(months=+2) +datetime.date(2003, 3, 31) +}}} + +The logic for years is the same, even on leap years. +{{{ +>>> date(2000,2,28)+relativedelta(years=+1) +datetime.date(2001, 2, 28) +>>> date(2000,2,29)+relativedelta(years=+1) +datetime.date(2001, 2, 28) + +>>> date(1999,2,28)+relativedelta(years=+1) +datetime.date(2000, 2, 28) +>>> date(1999,3,1)+relativedelta(years=+1) +datetime.date(2000, 3, 1) + +>>> date(2001,2,28)+relativedelta(years=-1) +datetime.date(2000, 2, 28) +>>> date(2001,3,1)+relativedelta(years=-1) +datetime.date(2000, 3, 1) +}}} + +Next friday. +{{{ +>>> TODAY+relativedelta(weekday=FR) +datetime.date(2003, 9, 19) + +>>> TODAY+relativedelta(weekday=calendar.FRIDAY) +datetime.date(2003, 9, 19) +}}} + +Last friday in this month. +{{{ +>>> TODAY+relativedelta(day=31, weekday=FR(-1)) +datetime.date(2003, 9, 26) +}}} + +Next wednesday (it's today!). +{{{ +>>> TODAY+relativedelta(weekday=WE(+1)) +datetime.date(2003, 9, 17) +}}} + +Next wednesday, but not today. +{{{ +>>> TODAY+relativedelta(days=+1, weekday=WE(+1)) +datetime.date(2003, 9, 24) +}}} + +Following +[http://www.cl.cam.ac.uk/~mgk25/iso-time.html ISO year week number notation] +find the first day of the 15th week of 1997. +{{{ +>>> datetime(1997,1,1)+relativedelta(day=4, weekday=MO(-1), weeks=+14) +datetime.datetime(1997, 4, 7, 0, 0) +}}} + +How long ago has the millennium changed? +{{{ +>>> relativedelta(NOW, date(2001,1,1)) +relativedelta(years=+2, months=+8, days=+16, + hours=+20, minutes=+54, seconds=+47, microseconds=+282310) +}}} + +How old is John? +{{{ +>>> johnbirthday = datetime(1978, 4, 5, 12, 0) +>>> relativedelta(NOW, johnbirthday) +relativedelta(years=+25, months=+5, days=+12, + hours=+8, minutes=+54, seconds=+47, microseconds=+282310) +}}} + +It works with dates too. +{{{ +>>> relativedelta(TODAY, johnbirthday) +relativedelta(years=+25, months=+5, days=+11, hours=+12) +}}} + +Obtain today's date using the yearday: +{{{ +>>> date(2003, 1, 1)+relativedelta(yearday=260) +datetime.date(2003, 9, 17) +}}} + +We can use today's date, since yearday should be absolute +in the given year: +{{{ +>>> TODAY+relativedelta(yearday=260) +datetime.date(2003, 9, 17) +}}} + +Last year it should be in the same day: +{{{ +>>> date(2002, 1, 1)+relativedelta(yearday=260) +datetime.date(2002, 9, 17) +}}} + +But not in a leap year: +{{{ +>>> date(2000, 1, 1)+relativedelta(yearday=260) +datetime.date(2000, 9, 16) +}}} + +We can use the non-leap year day to ignore this: +{{{ +>>> date(2000, 1, 1)+relativedelta(nlyearday=260) +datetime.date(2000, 9, 17) +}}} + +=== rrule === +The rrule module offers a small, complete, and very fast, implementation +of the recurrence rules documented in the +[ftp://ftp.rfc-editor.org/in-notes/rfc2445.txt iCalendar RFC], including +support for caching of results. + +==== rrule type ==== +That's the base of the rrule operation. It accepts all the keywords +defined in the RFC as its constructor parameters (except {{{byday}}}, +which was renamed to {{{byweekday}}}) and more. The constructor +prototype is: +{{{ +rrule(freq) +}}} + +Where {{{freq}}} must be one of {{{YEARLY}}}, {{{MONTHLY}}}, +{{{WEEKLY}}}, {{{DAILY}}}, {{{HOURLY}}}, {{{MINUTELY}}}, +or {{{SECONDLY}}}. + +Additionally, it supports the following keyword arguments: + + cache:: + If given, it must be a boolean value specifying to enable + or disable caching of results. If you will use the same + {{{rrule}}} instance multiple times, enabling caching will + improve the performance considerably. + + dtstart:: + The recurrence start. Besides being the base for the + recurrence, missing parameters in the final recurrence + instances will also be extracted from this date. If not + given, {{{datetime.now()}}} will be used instead. + + interval:: + The interval between each {{{freq}}} iteration. For example, + when using {{{YEARLY}}}, an interval of {{{2}}} means + once every two years, but with {{{HOURLY}}}, it means + once every two hours. The default interval is {{{1}}}. + + wkst:: + The week start day. Must be one of the {{{MO}}}, {{{TU}}}, + {{{WE}}} constants, or an integer, specifying the first day + of the week. This will affect recurrences based on weekly + periods. The default week start is got from + {{{calendar.firstweekday()}}}, and may be modified by + {{{calendar.setfirstweekday()}}}. + + count:: + How many occurrences will be generated. + + until:: + If given, this must be a {{{datetime}}} instance, that will + specify the limit of the recurrence. If a recurrence instance + happens to be the same as the {{{datetime}}} instance given + in the {{{until}}} keyword, this will be the last occurrence. + + bysetpos:: + If given, it must be either an integer, or a sequence of + integers, positive or negative. Each given integer will + specify an occurrence number, corresponding to the nth + occurrence of the rule inside the frequency period. For + example, a {{{bysetpos}}} of {{{-1}}} if combined with a + {{{MONTHLY}}} frequency, and a {{{byweekday}}} of + {{{(MO, TU, WE, TH, FR)}}}, will result in the last work + day of every month. + + bymonth:: + If given, it must be either an integer, or a sequence of + integers, meaning the months to apply the recurrence to. + + bymonthday:: + If given, it must be either an integer, or a sequence of + integers, meaning the month days to apply the recurrence to. + + byyearday:: + If given, it must be either an integer, or a sequence of + integers, meaning the year days to apply the recurrence to. + + byweekno:: + If given, it must be either an integer, or a sequence of + integers, meaning the week numbers to apply the recurrence + to. Week numbers have the meaning described in ISO8601, + that is, the first week of the year is that containing at + least four days of the new year. + + byweekday:: + If given, it must be either an integer ({{{0 == MO}}}), a + sequence of integers, one of the weekday constants + ({{{MO}}}, {{{TU}}}, etc), or a sequence of these constants. + When given, these variables will define the weekdays where + the recurrence will be applied. It's also possible to use + an argument {{{n}}} for the weekday instances, which will + mean the {{{n}}}''th'' occurrence of this weekday in the + period. For example, with {{{MONTHLY}}}, or with + {{{YEARLY}}} and {{{BYMONTH}}}, using {{{FR(+1)}}} + in {{{byweekday}}} will specify the first friday of the + month where the recurrence happens. Notice that in the RFC + documentation, this is specified as {{{BYDAY}}}, but was + renamed to avoid the ambiguity of that keyword. + + byhour:: + If given, it must be either an integer, or a sequence of + integers, meaning the hours to apply the recurrence to. + + byminute:: + If given, it must be either an integer, or a sequence of + integers, meaning the minutes to apply the recurrence to. + + bysecond:: + If given, it must be either an integer, or a sequence of + integers, meaning the seconds to apply the recurrence to. + + byeaster:: + If given, it must be either an integer, or a sequence of + integers, positive or negative. Each integer will define + an offset from the Easter Sunday. Passing the offset + {{{0}}} to {{{byeaster}}} will yield the Easter Sunday + itself. This is an extension to the RFC specification. + +==== rrule methods ==== +The following methods are available in {{{rrule}}} instances: + + rrule.before(dt, inc=False):: + Returns the last recurrence before the given {{{datetime}}} + instance. The {{{inc}}} keyword defines what happens if + {{{dt}}} '''is''' an occurrence. With {{{inc == True}}}, + if {{{dt}}} itself is an occurrence, it will be returned. + + rrule.after(dt, inc=False):: + Returns the first recurrence after the given {{{datetime}}} + instance. The {{{inc}}} keyword defines what happens if + {{{dt}}} '''is''' an occurrence. With {{{inc == True}}}, + if {{{dt}}} itself is an occurrence, it will be returned. + + rrule.between(after, before, inc=False):: + Returns all the occurrences of the rrule between {{{after}}} + and {{{before}}}. The {{{inc}}} keyword defines what happens + if {{{after}}} and/or {{{before}}} are themselves occurrences. + With {{{inc == True}}}, they will be included in the list, + if they are found in the recurrence set. + + rrule.count():: + Returns the number of recurrences in this set. It will have + go trough the whole recurrence, if this hasn't been done + before. + +Besides these methods, {{{rrule}}} instances also support +the {{{__getitem__()}}} and {{{__contains__()}}} special methods, +meaning that these are valid expressions: +{{{ +rr = rrule(...) +if datetime(...) in rr: + ... +print rr[0] +print rr[-1] +print rr[1:2] +print rr[::-2] +}}} + +The getitem/slicing mechanism is smart enough to avoid getting the whole +recurrence set, if possible. + +==== Notes ==== + + * The rrule type has no {{{byday}}} keyword. The equivalent keyword + has been replaced by the {{{byweekday}}} keyword, to remove the + ambiguity present in the original keyword. + + * Unlike documented in the RFC, the starting datetime ({{{dtstart}}}) + is not the first recurrence instance, unless it does fit in the + specified rules. In a python module context, this behavior makes more + sense than otherwise. Notice that you can easily get the original + behavior by using a rruleset and adding the {{{dtstart}}} as an + {{{rdate}}} recurrence. + + * Unlike documented in the RFC, every keyword is valid on every + frequency (the RFC documents that {{{byweekno}}} is only valid + on yearly frequencies, for example). + + * In addition to the documented keywords, a {{{byeaster}}} keyword + was introduced, making it easy to compute recurrent events relative + to the Easter Sunday. + +==== rrule examples ==== +These examples were converted from the RFC. + +Prepare the environment. +{{{ +>>> from dateutil.rrule import * +>>> from dateutil.parser import * +>>> from datetime import * + +>>> import pprint +>>> import sys +>>> sys.displayhook = pprint.pprint +}}} + +Daily, for 10 occurrences. +{{{ +>>> list(rrule(DAILY, count=10, + dtstart=parse("19970902T090000"))) +[datetime.datetime(1997, 9, 2, 9, 0), + datetime.datetime(1997, 9, 3, 9, 0), + datetime.datetime(1997, 9, 4, 9, 0), + datetime.datetime(1997, 9, 5, 9, 0), + datetime.datetime(1997, 9, 6, 9, 0), + datetime.datetime(1997, 9, 7, 9, 0), + datetime.datetime(1997, 9, 8, 9, 0), + datetime.datetime(1997, 9, 9, 9, 0), + datetime.datetime(1997, 9, 10, 9, 0), + datetime.datetime(1997, 9, 11, 9, 0)] +}}} + +Daily until December 24, 1997 +{{{ +>>> list(rrule(DAILY, + dtstart=parse("19970902T090000"), + until=parse("19971224T000000"))) +[datetime.datetime(1997, 9, 2, 9, 0), + datetime.datetime(1997, 9, 3, 9, 0), + datetime.datetime(1997, 9, 4, 9, 0), + (...) + datetime.datetime(1997, 12, 21, 9, 0), + datetime.datetime(1997, 12, 22, 9, 0), + datetime.datetime(1997, 12, 23, 9, 0)] +}}} + +Every other day, 5 occurrences. +{{{ +>>> list(rrule(DAILY, interval=2, count=5, + dtstart=parse("19970902T090000"))) +[datetime.datetime(1997, 9, 2, 9, 0), + datetime.datetime(1997, 9, 4, 9, 0), + datetime.datetime(1997, 9, 6, 9, 0), + datetime.datetime(1997, 9, 8, 9, 0), + datetime.datetime(1997, 9, 10, 9, 0)] +}}} + +Every 10 days, 5 occurrences. +{{{ +>>> list(rrule(DAILY, interval=10, count=5, + dtstart=parse("19970902T090000"))) +[datetime.datetime(1997, 9, 2, 9, 0), + datetime.datetime(1997, 9, 12, 9, 0), + datetime.datetime(1997, 9, 22, 9, 0), + datetime.datetime(1997, 10, 2, 9, 0), + datetime.datetime(1997, 10, 12, 9, 0)] +}}} + +Everyday in January, for 3 years. +{{{ +>>> list(rrule(YEARLY, bymonth=1, byweekday=range(7), + dtstart=parse("19980101T090000"), + until=parse("20000131T090000"))) +[datetime.datetime(1998, 1, 1, 9, 0), + datetime.datetime(1998, 1, 2, 9, 0), + (...) + datetime.datetime(1998, 1, 30, 9, 0), + datetime.datetime(1998, 1, 31, 9, 0), + datetime.datetime(1999, 1, 1, 9, 0), + datetime.datetime(1999, 1, 2, 9, 0), + (...) + datetime.datetime(1999, 1, 30, 9, 0), + datetime.datetime(1999, 1, 31, 9, 0), + datetime.datetime(2000, 1, 1, 9, 0), + datetime.datetime(2000, 1, 2, 9, 0), + (...) + datetime.datetime(2000, 1, 29, 9, 0), + datetime.datetime(2000, 1, 31, 9, 0)] +}}} + +Same thing, in another way. +{{{ +>>> list(rrule(DAILY, bymonth=1, + dtstart=parse("19980101T090000"), + until=parse("20000131T090000"))) +(...) +}}} + +Weekly for 10 occurrences. +{{{ +>>> list(rrule(WEEKLY, count=10, + dtstart=parse("19970902T090000"))) +[datetime.datetime(1997, 9, 2, 9, 0), + datetime.datetime(1997, 9, 9, 9, 0), + datetime.datetime(1997, 9, 16, 9, 0), + datetime.datetime(1997, 9, 23, 9, 0), + datetime.datetime(1997, 9, 30, 9, 0), + datetime.datetime(1997, 10, 7, 9, 0), + datetime.datetime(1997, 10, 14, 9, 0), + datetime.datetime(1997, 10, 21, 9, 0), + datetime.datetime(1997, 10, 28, 9, 0), + datetime.datetime(1997, 11, 4, 9, 0)] +}}} + +Every other week, 6 occurrences. +{{{ +>>> list(rrule(WEEKLY, interval=2, count=6, + dtstart=parse("19970902T090000"))) +[datetime.datetime(1997, 9, 2, 9, 0), + datetime.datetime(1997, 9, 16, 9, 0), + datetime.datetime(1997, 9, 30, 9, 0), + datetime.datetime(1997, 10, 14, 9, 0), + datetime.datetime(1997, 10, 28, 9, 0), + datetime.datetime(1997, 11, 11, 9, 0)] +}}} + +Weekly on Tuesday and Thursday for 5 weeks. +{{{ +>>> list(rrule(WEEKLY, count=10, wkst=SU, byweekday=(TU,TH), + dtstart=parse("19970902T090000"))) +[datetime.datetime(1997, 9, 2, 9, 0), + datetime.datetime(1997, 9, 4, 9, 0), + datetime.datetime(1997, 9, 9, 9, 0), + datetime.datetime(1997, 9, 11, 9, 0), + datetime.datetime(1997, 9, 16, 9, 0), + datetime.datetime(1997, 9, 18, 9, 0), + datetime.datetime(1997, 9, 23, 9, 0), + datetime.datetime(1997, 9, 25, 9, 0), + datetime.datetime(1997, 9, 30, 9, 0), + datetime.datetime(1997, 10, 2, 9, 0)] +}}} + +Every other week on Tuesday and Thursday, for 8 occurrences. +{{{ +>>> list(rrule(WEEKLY, interval=2, count=8, + wkst=SU, byweekday=(TU,TH), + dtstart=parse("19970902T090000"))) +[datetime.datetime(1997, 9, 2, 9, 0), + datetime.datetime(1997, 9, 4, 9, 0), + datetime.datetime(1997, 9, 16, 9, 0), + datetime.datetime(1997, 9, 18, 9, 0), + datetime.datetime(1997, 9, 30, 9, 0), + datetime.datetime(1997, 10, 2, 9, 0), + datetime.datetime(1997, 10, 14, 9, 0), + datetime.datetime(1997, 10, 16, 9, 0)] +}}} + +Monthly on the 1st Friday for ten occurrences. +{{{ +>>> list(rrule(MONTHLY, count=10, byweekday=FR(1), + dtstart=parse("19970905T090000"))) +[datetime.datetime(1997, 9, 5, 9, 0), + datetime.datetime(1997, 10, 3, 9, 0), + datetime.datetime(1997, 11, 7, 9, 0), + datetime.datetime(1997, 12, 5, 9, 0), + datetime.datetime(1998, 1, 2, 9, 0), + datetime.datetime(1998, 2, 6, 9, 0), + datetime.datetime(1998, 3, 6, 9, 0), + datetime.datetime(1998, 4, 3, 9, 0), + datetime.datetime(1998, 5, 1, 9, 0), + datetime.datetime(1998, 6, 5, 9, 0)] +}}} + +Every other month on the 1st and last Sunday of the month for 10 occurrences. +{{{ +>>> list(rrule(MONTHLY, interval=2, count=10, + byweekday=(SU(1), SU(-1)), + dtstart=parse("19970907T090000"))) +[datetime.datetime(1997, 9, 7, 9, 0), + datetime.datetime(1997, 9, 28, 9, 0), + datetime.datetime(1997, 11, 2, 9, 0), + datetime.datetime(1997, 11, 30, 9, 0), + datetime.datetime(1998, 1, 4, 9, 0), + datetime.datetime(1998, 1, 25, 9, 0), + datetime.datetime(1998, 3, 1, 9, 0), + datetime.datetime(1998, 3, 29, 9, 0), + datetime.datetime(1998, 5, 3, 9, 0), + datetime.datetime(1998, 5, 31, 9, 0)] +}}} + +Monthly on the second to last Monday of the month for 6 months. +{{{ +>>> list(rrule(MONTHLY, count=6, byweekday=MO(-2), + dtstart=parse("19970922T090000"))) +[datetime.datetime(1997, 9, 22, 9, 0), + datetime.datetime(1997, 10, 20, 9, 0), + datetime.datetime(1997, 11, 17, 9, 0), + datetime.datetime(1997, 12, 22, 9, 0), + datetime.datetime(1998, 1, 19, 9, 0), + datetime.datetime(1998, 2, 16, 9, 0)] +}}} + +Monthly on the third to the last day of the month, for 6 months. +{{{ +>>> list(rrule(MONTHLY, count=6, bymonthday=-3, + dtstart=parse("19970928T090000"))) +[datetime.datetime(1997, 9, 28, 9, 0), + datetime.datetime(1997, 10, 29, 9, 0), + datetime.datetime(1997, 11, 28, 9, 0), + datetime.datetime(1997, 12, 29, 9, 0), + datetime.datetime(1998, 1, 29, 9, 0), + datetime.datetime(1998, 2, 26, 9, 0)] +}}} + +Monthly on the 2nd and 15th of the month for 5 occurrences. +{{{ +>>> list(rrule(MONTHLY, count=5, bymonthday=(2,15), + dtstart=parse("19970902T090000"))) +[datetime.datetime(1997, 9, 2, 9, 0), + datetime.datetime(1997, 9, 15, 9, 0), + datetime.datetime(1997, 10, 2, 9, 0), + datetime.datetime(1997, 10, 15, 9, 0), + datetime.datetime(1997, 11, 2, 9, 0)] +}}} + +Monthly on the first and last day of the month for 3 occurrences. +{{{ +>>> list(rrule(MONTHLY, count=5, bymonthday=(-1,1,), + dtstart=parse("1997090 +2T090000"))) +[datetime.datetime(1997, 9, 30, 9, 0), + datetime.datetime(1997, 10, 1, 9, 0), + datetime.datetime(1997, 10, 31, 9, 0), + datetime.datetime(1997, 11, 1, 9, 0), + datetime.datetime(1997, 11, 30, 9, 0)] +}}} + +Every 18 months on the 10th thru 15th of the month for 10 occurrences. +{{{ +>>> list(rrule(MONTHLY, interval=18, count=10, + bymonthday=range(10,16), + dtstart=parse("19970910T090000"))) +[datetime.datetime(1997, 9, 10, 9, 0), + datetime.datetime(1997, 9, 11, 9, 0), + datetime.datetime(1997, 9, 12, 9, 0), + datetime.datetime(1997, 9, 13, 9, 0), + datetime.datetime(1997, 9, 14, 9, 0), + datetime.datetime(1997, 9, 15, 9, 0), + datetime.datetime(1999, 3, 10, 9, 0), + datetime.datetime(1999, 3, 11, 9, 0), + datetime.datetime(1999, 3, 12, 9, 0), + datetime.datetime(1999, 3, 13, 9, 0)] +}}} + +Every Tuesday, every other month, 6 occurences. +{{{ +>>> list(rrule(MONTHLY, interval=2, count=6, byweekday=TU, + dtstart=parse("19970902T090000"))) +[datetime.datetime(1997, 9, 2, 9, 0), + datetime.datetime(1997, 9, 9, 9, 0), + datetime.datetime(1997, 9, 16, 9, 0), + datetime.datetime(1997, 9, 23, 9, 0), + datetime.datetime(1997, 9, 30, 9, 0), + datetime.datetime(1997, 11, 4, 9, 0)] +}}} + +Yearly in June and July for 10 occurrences. +{{{ +>>> list(rrule(YEARLY, count=4, bymonth=(6,7), + dtstart=parse("19970610T0900 +00"))) +[datetime.datetime(1997, 6, 10, 9, 0), + datetime.datetime(1997, 7, 10, 9, 0), + datetime.datetime(1998, 6, 10, 9, 0), + datetime.datetime(1998, 7, 10, 9, 0)] +}}} + +Every 3rd year on the 1st, 100th and 200th day for 4 occurrences. +{{{ +>>> list(rrule(YEARLY, count=4, interval=3, byyearday=(1,100,200), + dtstart=parse("19970101T090000"))) +[datetime.datetime(1997, 1, 1, 9, 0), + datetime.datetime(1997, 4, 10, 9, 0), + datetime.datetime(1997, 7, 19, 9, 0), + datetime.datetime(2000, 1, 1, 9, 0)] +}}} + +Every 20th Monday of the year, 3 occurrences. +{{{ +>>> list(rrule(YEARLY, count=3, byweekday=MO(20), + dtstart=parse("19970519T090000"))) +[datetime.datetime(1997, 5, 19, 9, 0), + datetime.datetime(1998, 5, 18, 9, 0), + datetime.datetime(1999, 5, 17, 9, 0)] +}}} + +Monday of week number 20 (where the default start of the week is Monday), +3 occurrences. +{{{ +>>> list(rrule(YEARLY, count=3, byweekno=20, byweekday=MO, + dtstart=parse("19970512T090000"))) +[datetime.datetime(1997, 5, 12, 9, 0), + datetime.datetime(1998, 5, 11, 9, 0), + datetime.datetime(1999, 5, 17, 9, 0)] +}}} + +The week number 1 may be in the last year. +{{{ +>>> list(rrule(WEEKLY, count=3, byweekno=1, byweekday=MO, + dtstart=parse("19970902T090000"))) +[datetime.datetime(1997, 12, 29, 9, 0), + datetime.datetime(1999, 1, 4, 9, 0), + datetime.datetime(2000, 1, 3, 9, 0)] +}}} + +And the week numbers greater than 51 may be in the next year. +{{{ +>>> list(rrule(WEEKLY, count=3, byweekno=52, byweekday=SU, + dtstart=parse("19970902T090000"))) +[datetime.datetime(1997, 12, 28, 9, 0), + datetime.datetime(1998, 12, 27, 9, 0), + datetime.datetime(2000, 1, 2, 9, 0)] +}}} + +Only some years have week number 53: +{{{ +>>> list(rrule(WEEKLY, count=3, byweekno=53, byweekday=MO, + dtstart=parse("19970902T090000"))) +[datetime.datetime(1998, 12, 28, 9, 0), + datetime.datetime(2004, 12, 27, 9, 0), + datetime.datetime(2009, 12, 28, 9, 0)] +}}} + +Every Friday the 13th, 4 occurrences. +{{{ +>>> list(rrule(YEARLY, count=4, byweekday=FR, bymonthday=13, + dtstart=parse("19970902T090000"))) +[datetime.datetime(1998, 2, 13, 9, 0), + datetime.datetime(1998, 3, 13, 9, 0), + datetime.datetime(1998, 11, 13, 9, 0), + datetime.datetime(1999, 8, 13, 9, 0)] +}}} + +Every four years, the first Tuesday after a Monday in November, +3 occurrences (U.S. Presidential Election day): +{{{ +>>> list(rrule(YEARLY, interval=4, count=3, bymonth=11, + byweekday=TU, bymonthday=(2,3,4,5,6,7,8), + dtstart=parse("19961105T090000"))) +[datetime.datetime(1996, 11, 5, 9, 0), + datetime.datetime(2000, 11, 7, 9, 0), + datetime.datetime(2004, 11, 2, 9, 0)] +}}} + +The 3rd instance into the month of one of Tuesday, Wednesday or +Thursday, for the next 3 months: +{{{ +>>> list(rrule(MONTHLY, count=3, byweekday=(TU,WE,TH), + bysetpos=3, dtstart=parse("19970904T090000"))) +[datetime.datetime(1997, 9, 4, 9, 0), + datetime.datetime(1997, 10, 7, 9, 0), + datetime.datetime(1997, 11, 6, 9, 0)] +}}} + +The 2nd to last weekday of the month, 3 occurrences. +{{{ +>>> list(rrule(MONTHLY, count=3, byweekday=(MO,TU,WE,TH,FR), + bysetpos=-2, dtstart=parse("19970929T090000"))) +[datetime.datetime(1997, 9, 29, 9, 0), + datetime.datetime(1997, 10, 30, 9, 0), + datetime.datetime(1997, 11, 27, 9, 0)] +}}} + +Every 3 hours from 9:00 AM to 5:00 PM on a specific day. +{{{ +>>> list(rrule(HOURLY, interval=3, + dtstart=parse("19970902T090000"), + until=parse("19970902T170000"))) +[datetime.datetime(1997, 9, 2, 9, 0), + datetime.datetime(1997, 9, 2, 12, 0), + datetime.datetime(1997, 9, 2, 15, 0)] +}}} + +Every 15 minutes for 6 occurrences. +{{{ +>>> list(rrule(MINUTELY, interval=15, count=6, + dtstart=parse("19970902T090000"))) +[datetime.datetime(1997, 9, 2, 9, 0), + datetime.datetime(1997, 9, 2, 9, 15), + datetime.datetime(1997, 9, 2, 9, 30), + datetime.datetime(1997, 9, 2, 9, 45), + datetime.datetime(1997, 9, 2, 10, 0), + datetime.datetime(1997, 9, 2, 10, 15)] +}}} + +Every hour and a half for 4 occurrences. +{{{ +>>> list(rrule(MINUTELY, interval=90, count=4, + dtstart=parse("19970902T090000"))) +[datetime.datetime(1997, 9, 2, 9, 0), + datetime.datetime(1997, 9, 2, 10, 30), + datetime.datetime(1997, 9, 2, 12, 0), + datetime.datetime(1997, 9, 2, 13, 30)] +}}} + +Every 20 minutes from 9:00 AM to 4:40 PM for two days. +{{{ +>>> list(rrule(MINUTELY, interval=20, count=48, + byhour=range(9,17), byminute=(0,20,40), + dtstart=parse("19970902T090000"))) +[datetime.datetime(1997, 9, 2, 9, 0), + datetime.datetime(1997, 9, 2, 9, 20), + (...) + datetime.datetime(1997, 9, 2, 16, 20), + datetime.datetime(1997, 9, 2, 16, 40), + datetime.datetime(1997, 9, 3, 9, 0), + datetime.datetime(1997, 9, 3, 9, 20), + (...) + datetime.datetime(1997, 9, 3, 16, 20), + datetime.datetime(1997, 9, 3, 16, 40)] +}}} + +An example where the days generated makes a difference because of {{{wkst}}}. +{{{ +>>> list(rrule(WEEKLY, interval=2, count=4, + byweekday=(TU,SU), wkst=MO, + dtstart=parse("19970805T090000"))) +[datetime.datetime(1997, 8, 5, 9, 0), + datetime.datetime(1997, 8, 10, 9, 0), + datetime.datetime(1997, 8, 19, 9, 0), + datetime.datetime(1997, 8, 24, 9, 0)] + +>>> list(rrule(WEEKLY, interval=2, count=4, + byweekday=(TU,SU), wkst=SU, + dtstart=parse("19970805T090000"))) +[datetime.datetime(1997, 8, 5, 9, 0), + datetime.datetime(1997, 8, 17, 9, 0), + datetime.datetime(1997, 8, 19, 9, 0), + datetime.datetime(1997, 8, 31, 9, 0)] +}}} + +==== rruleset type ==== +The {{{rruleset}}} type allows more complex recurrence setups, mixing +multiple rules, dates, exclusion rules, and exclusion dates. +The type constructor takes the following keyword arguments: + + cache:: + If True, caching of results will be enabled, improving performance + of multiple queries considerably. + +==== rruleset methods ==== +The following methods are available: + + rruleset.rrule(rrule):: + Include the given {{{rrule}}} instance in the recurrence set + generation. + + rruleset.rdate(dt):: + Include the given {{{datetime}}} instance in the recurrence + set generation. + + rruleset.exrule(rrule):: + Include the given {{{rrule}}} instance in the recurrence set + exclusion list. Dates which are part of the given recurrence + rules will not be generated, even if some inclusive {{{rrule}}} + or {{{rdate}}} matches them. + + rruleset.exdate(dt):: + Include the given {{{datetime}}} instance in the recurrence set + exclusion list. Dates included that way will not be generated, + even if some inclusive {{{rrule}}} or {{{rdate}}} matches them. + + rruleset.before(dt, inc=False):: + Returns the last recurrence before the given {{{datetime}}} + instance. The {{{inc}}} keyword defines what happens if + {{{dt}}} '''is''' an occurrence. With {{{inc == True}}}, + if {{{dt}}} itself is an occurrence, it will be returned. + + rruleset.after(dt, inc=False):: + Returns the first recurrence after the given {{{datetime}}} + instance. The {{{inc}}} keyword defines what happens if + {{{dt}}} '''is''' an occurrence. With {{{inc == True}}}, + if {{{dt}}} itself is an occurrence, it will be returned. + + rruleset.between(after, before, inc=False):: + Returns all the occurrences of the rrule between {{{after}}} + and {{{before}}}. The {{{inc}}} keyword defines what happens + if {{{after}}} and/or {{{before}}} are themselves occurrences. + With {{{inc == True}}}, they will be included in the list, + if they are found in the recurrence set. + + rruleset.count():: + Returns the number of recurrences in this set. It will have + go trough the whole recurrence, if this hasn't been done + before. + +Besides these methods, {{{rruleset}}} instances also support +the {{{__getitem__()}}} and {{{__contains__()}}} special methods, +meaning that these are valid expressions: +{{{ +set = rruleset(...) +if datetime(...) in set: + ... +print set[0] +print set[-1] +print set[1:2] +print set[::-2] +}}} + +The getitem/slicing mechanism is smart enough to avoid getting the whole +recurrence set, if possible. + +==== rruleset examples ==== +Daily, for 7 days, jumping Saturday and Sunday occurrences. +{{{ +>>> set = rruleset() +>>> set.rrule(rrule(DAILY, count=7, + dtstart=parse("19970902T090000"))) +>>> set.exrule(rrule(YEARLY, byweekday=(SA,SU), + dtstart=parse("19970902T090000"))) +>>> list(set) +[datetime.datetime(1997, 9, 2, 9, 0), + datetime.datetime(1997, 9, 3, 9, 0), + datetime.datetime(1997, 9, 4, 9, 0), + datetime.datetime(1997, 9, 5, 9, 0), + datetime.datetime(1997, 9, 8, 9, 0)] +}}} + +Weekly, for 4 weeks, plus one time on day 7, and not on day 16. +{{{ +>>> set = rruleset() +>>> set.rrule(rrule(WEEKLY, count=4, + dtstart=parse("19970902T090000"))) +>>> set.rdate(datetime.datetime(1997, 9, 7, 9, 0)) +>>> set.exdate(datetime.datetime(1997, 9, 16, 9, 0)) +>>> list(set) +[datetime.datetime(1997, 9, 2, 9, 0), + datetime.datetime(1997, 9, 7, 9, 0), + datetime.datetime(1997, 9, 9, 9, 0), + datetime.datetime(1997, 9, 23, 9, 0)] +}}} + +==== rrulestr() function ==== +The {{{rrulestr()}}} function is a parser for ''RFC-like'' syntaxes. +The function prototype is: +{{{ +rrulestr(str) +}}} + +The string passed as parameter may be a multiple line string, a +single line string, or just the {{{RRULE}}} property value. + +Additionally, it accepts the following keyword arguments: + + cache:: + If {{{True}}}, the {{{rruleset}}} or {{{rrule}}} created instance + will cache its results. Default is not to cache. + + dtstart:: + If given, it must be a {{{datetime}}} instance that will be used + when no {{{DTSTART}}} property is found in the parsed string. If + it is not given, and the property is not found, {{{datetime.now()}}} + will be used instead. + + unfold:: + If set to {{{True}}}, lines will be unfolded following the RFC + specification. It defaults to {{{False}}}, meaning that spaces + before every line will be stripped. + + forceset:: + If set to {{{True}}} a {{{rruleset}}} instance will be returned, + even if only a single rule is found. The default is to return an + {{{rrule}}} if possible, and an {{{rruleset}}} if necessary. + + compatible:: + If set to {{{True}}}, the parser will operate in RFC-compatible + mode. Right now it means that {{{unfold}}} will be turned on, + and if a {{{DTSTART}}} is found, it will be considered the first + recurrence instance, as documented in the RFC. + + ignoretz:: + If set to {{{True}}}, the date parser will ignore timezone + information available in the {{{DTSTART}}} property, or the + {{{UNTIL}}} attribute. + + tzinfos:: + If set, it will be passed to the datetime string parser to + resolve unknown timezone settings. For more information about + what could be used here, check the parser documentation. + +==== rrulestr() examples ==== + +Every 10 days, 5 occurrences. +{{{ +>>> list(rrulestr(""" +... DTSTART:19970902T090000 +... RRULE:FREQ=DAILY;INTERVAL=10;COUNT=5 +... """)) +[datetime.datetime(1997, 9, 2, 9, 0), + datetime.datetime(1997, 9, 12, 9, 0), + datetime.datetime(1997, 9, 22, 9, 0), + datetime.datetime(1997, 10, 2, 9, 0), + datetime.datetime(1997, 10, 12, 9, 0)] +}}} + +Same thing, but passing only the {{{RRULE}}} value. +{{{ +>>> list(rrulestr("FREQ=DAILY;INTERVAL=10;COUNT=5", + dtstart=parse("19970902T090000"))) +[datetime.datetime(1997, 9, 2, 9, 0), + datetime.datetime(1997, 9, 12, 9, 0), + datetime.datetime(1997, 9, 22, 9, 0), + datetime.datetime(1997, 10, 2, 9, 0), + datetime.datetime(1997, 10, 12, 9, 0)] +}}} + +Notice that when using a single rule, it returns an +{{{rrule}}} instance, unless {{{forceset}}} was used. +{{{ +>>> rrulestr("FREQ=DAILY;INTERVAL=10;COUNT=5") + + +>>> rrulestr(""" +... DTSTART:19970902T090000 +... RRULE:FREQ=DAILY;INTERVAL=10;COUNT=5 +... """) + + +>>> rrulestr("FREQ=DAILY;INTERVAL=10;COUNT=5", forceset=True) + +}}} + +But when an {{{rruleset}}} is needed, it is automatically used. +{{{ +>>> rrulestr(""" +... DTSTART:19970902T090000 +... RRULE:FREQ=DAILY;INTERVAL=10;COUNT=5 +... RRULE:FREQ=DAILY;INTERVAL=5;COUNT=3 +... """) + +}}} + +=== parser === +This module offers a generic date/time string parser which is +able to parse most known formats to represent a date and/or +time. + +==== parse() function ==== +That's probably the only function you'll need from this module. +It offers you an interface to access the parser functionality and +extract a {{{datetime}}} type out of a string. + +The prototype of this function is: +{{{ +parse(timestr) +}}} + +Additionally, the following keyword arguments are available: + + default:: + If given, this must be a {{{datetime}}} instance. Any fields + missing in the parsed date will be copied from this instance. + The default value is the current date, at 00:00:00am. + + ignoretz:: + If this is true, even if a timezone is found in the string, + the parser will not use it. + + tzinfos:: + Using this keyword argument you may provide custom timezones + to the parser. If given, it must be either a dictionary with + the timezone abbreviation as key, or a function accepting a + timezone abbreviation and offset as argument. The dictionary + values and the function return must be a timezone offset + in seconds, a tzinfo subclass, or a string defining the + timezone (in the TZ environment variable format). + + dayfirst:: + This option allow one to change the precedence in which + days are parsed in date strings. The default is given in the + parserinfo instance (the default parserinfo has it set to + False). If {{{dayfirst}}} is False, the {{{MM-DD-YYYY}}} + format will have precedence over {{{DD-MM-YYYY}}} in an + ambiguous date. + + yearfirst:: + This option allow one to change the precedence in which + years are parsed in date strings. The default is given in + the parserinfo instance (the default parserinfo has it set + to False). If {{{yearfirst}}} is false, the {{{MM-DD-YY}}} + format will have precedence over {{{YY-MM-DD}}} in an + ambiguous date. + + fuzzy:: + If {{{fuzzy}}} is set to True, unknown tokens in the string + will be ignored. + + fuzzy_with_tokens:: + Similar to {{{fuzzy}}}, but will result a tuple of the normal + results and the skipped tokens. + + parserinfo:: + This parameter allows one to change how the string is parsed, + by using a different parserinfo class instance. Using it you + may, for example, intenationalize the parser strings, or make + it ignore additional words. + +==== Format precedence ==== +Whenever an ambiguous date is found, the {{{dayfirst}}} and +{{{yearfirst}}} parameters will control how the information +is processed. Here is the precedence in each case: + +If {{{dayfirst}}} is {{{False}}} and {{{yearfirst}}} is {{{False}}}, +(default, if no parameter is given): + + * {{{MM-DD-YY}}} + * {{{DD-MM-YY}}} + * {{{YY-MM-DD}}} + +If {{{dayfirst}}} is {{{True}}} and {{{yearfirst}}} is {{{False}}}: + + * {{{DD-MM-YY}}} + * {{{MM-DD-YY}}} + * {{{YY-MM-DD}}} + +If {{{dayfirst}}} is {{{False}}} and {{{yearfirst}}} is {{{True}}}: + + * {{{YY-MM-DD}}} + * {{{MM-DD-YY}}} + * {{{DD-MM-YY}}} + +If {{{dayfirst}}} is {{{True}}} and {{{yearfirst}}} is {{{True}}}: + + * {{{YY-MM-DD}}} + * {{{DD-MM-YY}}} + * {{{MM-DD-YY}}} + +==== Converting two digit years ==== +When a two digit year is found, it is processed considering +the current year, so that the computed year is never more +than 49 years after the current year, nor 50 years before the +current year. In other words, if we are in year 2003, and the +year 30 is found, it will be considered as 2030, but if the +year 60 is found, it will be considered 1960. + +==== Examples ==== +The following code will prepare the environment: +{{{ +>>> from dateutil.parser import * +>>> from dateutil.tz import * +>>> from datetime import * +>>> TZOFFSETS = {"BRST": -10800} +>>> BRSTTZ = tzoffset(-10800, "BRST") +>>> DEFAULT = datetime(2003, 9, 25) +}}} + +Some simple examples based on the {{{date}}} command, using the +{{{TZOFFSET}}} dictionary to provide the BRST timezone offset. +{{{ +>>> parse("Thu Sep 25 10:36:28 BRST 2003", tzinfos=TZOFFSETS) +datetime.datetime(2003, 9, 25, 10, 36, 28, + tzinfo=tzoffset('BRST', -10800)) + +>>> parse("2003 10:36:28 BRST 25 Sep Thu", tzinfos=TZOFFSETS) +datetime.datetime(2003, 9, 25, 10, 36, 28, + tzinfo=tzoffset('BRST', -10800)) +}}} + +Notice that since BRST is my local timezone, parsing it without +further timezone settings will yield a {{{tzlocal}}} timezone. +{{{ +>>> parse("Thu Sep 25 10:36:28 BRST 2003") +datetime.datetime(2003, 9, 25, 10, 36, 28, tzinfo=tzlocal()) +}}} + +We can also ask to ignore the timezone explicitly: +{{{ +>>> parse("Thu Sep 25 10:36:28 BRST 2003", ignoretz=True) +datetime.datetime(2003, 9, 25, 10, 36, 28) +}}} + +That's the same as processing a string without timezone: +{{{ +>>> parse("Thu Sep 25 10:36:28 2003") +datetime.datetime(2003, 9, 25, 10, 36, 28) +}}} + +Without the year, but passing our {{{DEFAULT}}} datetime to return +the same year, no mattering what year we currently are in: +{{{ +>>> parse("Thu Sep 25 10:36:28", default=DEFAULT) +datetime.datetime(2003, 9, 25, 10, 36, 28) +}}} + +Strip it further: +{{{ +>>> parse("Thu Sep 10:36:28", default=DEFAULT) +datetime.datetime(2003, 9, 25, 10, 36, 28) + +>>> parse("Thu 10:36:28", default=DEFAULT) +datetime.datetime(2003, 9, 25, 10, 36, 28) + +>>> parse("Thu 10:36", default=DEFAULT) +datetime.datetime(2003, 9, 25, 10, 36) + +>>> parse("10:36", default=DEFAULT) +datetime.datetime(2003, 9, 25, 10, 36) +>>> +}}} + +Strip in a different way: +{{{ +>>> parse("Thu Sep 25 2003") +datetime.datetime(2003, 9, 25, 0, 0) + +>>> parse("Sep 25 2003") +datetime.datetime(2003, 9, 25, 0, 0) + +>>> parse("Sep 2003", default=DEFAULT) +datetime.datetime(2003, 9, 25, 0, 0) + +>>> parse("Sep", default=DEFAULT) +datetime.datetime(2003, 9, 25, 0, 0) + +>>> parse("2003", default=DEFAULT) +datetime.datetime(2003, 9, 25, 0, 0) +}}} + +Another format, based on {{{date -R}}} (RFC822): +{{{ +>>> parse("Thu, 25 Sep 2003 10:49:41 -0300") +datetime.datetime(2003, 9, 25, 10, 49, 41, + tzinfo=tzoffset(None, -10800)) +}}} + +ISO format: +{{{ +>>> parse("2003-09-25T10:49:41.5-03:00") +datetime.datetime(2003, 9, 25, 10, 49, 41, 500000, + tzinfo=tzoffset(None, -10800)) +}}} + +Some variations: +{{{ +>>> parse("2003-09-25T10:49:41") +datetime.datetime(2003, 9, 25, 10, 49, 41) + +>>> parse("2003-09-25T10:49") +datetime.datetime(2003, 9, 25, 10, 49) + +>>> parse("2003-09-25T10") +datetime.datetime(2003, 9, 25, 10, 0) + +>>> parse("2003-09-25") +datetime.datetime(2003, 9, 25, 0, 0) +}}} + +ISO format, without separators: +{{{ +>>> parse("20030925T104941.5-0300") +datetime.datetime(2003, 9, 25, 10, 49, 41, 500000, + tzinfo=tzinfo=tzoffset(None, -10800)) + +>>> parse("20030925T104941-0300") +datetime.datetime(2003, 9, 25, 10, 49, 41, + tzinfo=tzoffset(None, -10800)) + +>>> parse("20030925T104941") +datetime.datetime(2003, 9, 25, 10, 49, 41) + +>>> parse("20030925T1049") +datetime.datetime(2003, 9, 25, 10, 49) + +>>> parse("20030925T10") +datetime.datetime(2003, 9, 25, 10, 0) + +>>> parse("20030925") +datetime.datetime(2003, 9, 25, 0, 0) +}}} + +Everything together. +{{{ +>>> parse("199709020900") +datetime.datetime(1997, 9, 2, 9, 0) +>>> parse("19970902090059") +datetime.datetime(1997, 9, 2, 9, 0, 59) +}}} + +Different date orderings: +{{{ +>>> parse("2003-09-25") +datetime.datetime(2003, 9, 25, 0, 0) + +>>> parse("2003-Sep-25") +datetime.datetime(2003, 9, 25, 0, 0) + +>>> parse("25-Sep-2003") +datetime.datetime(2003, 9, 25, 0, 0) + +>>> parse("Sep-25-2003") +datetime.datetime(2003, 9, 25, 0, 0) + +>>> parse("09-25-2003") +datetime.datetime(2003, 9, 25, 0, 0) + +>>> parse("25-09-2003") +datetime.datetime(2003, 9, 25, 0, 0) +}}} + +Check some ambiguous dates: +{{{ +>>> parse("10-09-2003") +datetime.datetime(2003, 10, 9, 0, 0) + +>>> parse("10-09-2003", dayfirst=True) +datetime.datetime(2003, 9, 10, 0, 0) + +>>> parse("10-09-03") +datetime.datetime(2003, 10, 9, 0, 0) + +>>> parse("10-09-03", yearfirst=True) +datetime.datetime(2010, 9, 3, 0, 0) +}}} + +Other date separators are allowed: +{{{ +>>> parse("2003.Sep.25") +datetime.datetime(2003, 9, 25, 0, 0) + +>>> parse("2003/09/25") +datetime.datetime(2003, 9, 25, 0, 0) +}}} + +Even with spaces: +{{{ +>>> parse("2003 Sep 25") +datetime.datetime(2003, 9, 25, 0, 0) + +>>> parse("2003 09 25") +datetime.datetime(2003, 9, 25, 0, 0) +}}} + +Hours with letters work: +{{{ +>>> parse("10h36m28.5s", default=DEFAULT) +datetime.datetime(2003, 9, 25, 10, 36, 28, 500000) + +>>> parse("01s02h03m", default=DEFAULT) +datetime.datetime(2003, 9, 25, 2, 3, 1) + +>>> parse("01h02m03", default=DEFAULT) +datetime.datetime(2003, 9, 3, 1, 2) + +>>> parse("01h02", default=DEFAULT) +datetime.datetime(2003, 9, 2, 1, 0) + +>>> parse("01h02s", default=DEFAULT) +datetime.datetime(2003, 9, 25, 1, 0, 2) +}}} + +With AM/PM: +{{{ +>>> parse("10h am", default=DEFAULT) +datetime.datetime(2003, 9, 25, 10, 0) + +>>> parse("10pm", default=DEFAULT) +datetime.datetime(2003, 9, 25, 22, 0) + +>>> parse("12:00am", default=DEFAULT) +datetime.datetime(2003, 9, 25, 0, 0) + +>>> parse("12pm", default=DEFAULT) +datetime.datetime(2003, 9, 25, 12, 0) +}}} + +Some special treating for ''pertain'' relations: +{{{ +>>> parse("Sep 03", default=DEFAULT) +datetime.datetime(2003, 9, 3, 0, 0) + +>>> parse("Sep of 03", default=DEFAULT) +datetime.datetime(2003, 9, 25, 0, 0) +}}} + +Fuzzy parsing: +{{{ +>>> s = "Today is 25 of September of 2003, exactly " \ +... "at 10:49:41 with timezone -03:00." +>>> parse(s, fuzzy=True) +datetime.datetime(2003, 9, 25, 10, 49, 41, + tzinfo=tzoffset(None, -10800)) +}}} + +Other random formats: +{{{ +>>> parse("Wed, July 10, '96") +datetime.datetime(1996, 7, 10, 0, 0) + +>>> parse("1996.07.10 AD at 15:08:56 PDT", ignoretz=True) +datetime.datetime(1996, 7, 10, 15, 8, 56) + +>>> parse("Tuesday, April 12, 1952 AD 3:30:42pm PST", ignoretz=True) +datetime.datetime(1952, 4, 12, 15, 30, 42) + +>>> parse("November 5, 1994, 8:15:30 am EST", ignoretz=True) +datetime.datetime(1994, 11, 5, 8, 15, 30) + +>>> parse("3rd of May 2001") +datetime.datetime(2001, 5, 3, 0, 0) + +>>> parse("5:50 A.M. on June 13, 1990") +datetime.datetime(1990, 6, 13, 5, 50) +}}} + +=== easter === +This module offers a generic easter computing method for +any given year, using Western, Orthodox or Julian algorithms. + +==== easter() function ==== +This method was ported from the work done by +[http://users.chariot.net.au/~gmarts/eastalg.htm GM Arts], +on top of the algorithm by +[http://www.tondering.dk/claus/calendar.html Claus Tondering], +which was based in part on the algorithm of Ouding (1940), +as quoted in "Explanatory Supplement to the Astronomical +Almanac", P. Kenneth Seidelmann, editor. + +This algorithm implements three different easter +calculation methods: + + 1. Original calculation in Julian calendar, valid in + dates after 326 AD + 1. Original method, with date converted to Gregorian + calendar, valid in years 1583 to 4099 + 1. Revised method, in Gregorian calendar, valid in + years 1583 to 4099 as well + +These methods are represented by the constants: +{{{ +EASTER_JULIAN = 1 +EASTER_ORTHODOX = 2 +EASTER_WESTERN = 3 +}}} + +The default method is method 3. + +=== tz === +This module offers timezone implementations subclassing +the abstract {{{datetime.tzinfo}}} type. There are +classes to handle [http://www.twinsun.com/tz/tz-link.htm tzfile] +format files (usually are in /etc/localtime, +/usr/share/zoneinfo, etc), TZ environment string (in all +known formats), given ranges (with help from relative +deltas), local machine timezone, fixed offset timezone, +and UTC timezone. + +==== tzutc type ==== +This type implements a basic UTC timezone. The constructor of this +type accepts no parameters. + +==== tzutc examples ==== +{{{ +>>> from datetime import * +>>> from dateutil.tz import * + +>>> datetime.now() +datetime.datetime(2003, 9, 27, 9, 40, 1, 521290) + +>>> datetime.now(tzutc()) +datetime.datetime(2003, 9, 27, 12, 40, 12, 156379, tzinfo=tzutc()) + +>>> datetime.now(tzutc()).tzname() +'UTC' +}}} + +==== tzoffset type ==== +This type implements a fixed offset timezone, with no +support to daylight saving times. Here is the prototype of the +type constructor: +{{{ +tzoffset(name, offset) +}}} + +The {{{name}}} parameter may be optionally set to {{{None}}}, and +{{{offset}}} must be given in seconds. + +==== tzoffset examples ==== +{{{ +>>> from datetime import * +>>> from dateutil.tz import * + +>>> datetime.now(tzoffset("BRST", -10800)) +datetime.datetime(2003, 9, 27, 9, 52, 43, 624904, + tzinfo=tzinfo=tzoffset('BRST', -10800)) + +>>> datetime.now(tzoffset("BRST", -10800)).tzname() +'BRST' + +>>> datetime.now(tzoffset("BRST", -10800)).astimezone(tzutc()) +datetime.datetime(2003, 9, 27, 12, 53, 11, 446419, + tzinfo=tzutc()) +}}} + +==== tzlocal type ==== +This type implements timezone settings as known by the +operating system. The constructor of this type accepts no +parameters. + +==== tzlocal examples ==== +{{{ +>>> from datetime import * +>>> from dateutil.tz import * + +>>> datetime.now(tzlocal()) +datetime.datetime(2003, 9, 27, 10, 1, 43, 673605, + tzinfo=tzlocal()) + +>>> datetime.now(tzlocal()).tzname() +'BRST' + +>>> datetime.now(tzlocal()).astimezone(tzoffset(None, 0)) +datetime.datetime(2003, 9, 27, 13, 3, 0, 11493, + tzinfo=tzoffset(None, 0)) +}}} + +==== tzstr type ==== +This type implements timezone settings extracted from a +string in known TZ environment variable formats. Here is the prototype +of the constructor: +{{{ +tzstr(str) +}}} + +==== tzstr examples ==== +Here are examples of the recognized formats: + + * {{{EST5EDT}}} + * {{{EST5EDT,4,0,6,7200,10,0,26,7200,3600}}} + * {{{EST5EDT,4,1,0,7200,10,-1,0,7200,3600}}} + * {{{EST5EDT4,M4.1.0/02:00:00,M10-5-0/02:00}}} + * {{{EST5EDT4,95/02:00:00,298/02:00}}} + * {{{EST5EDT4,J96/02:00:00,J299/02:00}}} + +Notice that if daylight information is not present, but a +daylight abbreviation was provided, {{{tzstr}}} will follow the +convention of using the first sunday of April to start daylight +saving, and the last sunday of October to end it. If start or +end time is not present, 2AM will be used, and if the daylight +offset is not present, the standard offset plus one hour will +be used. This convention is the same as used in the GNU libc. + +This also means that some of the above examples are exactly +equivalent, and all of these examples are equivalent +in the year of 2003. + +Here is the example mentioned in the +[http://www.python.org/doc/current/lib/module-time.html time module documentation]. +{{{ +>>> os.environ['TZ'] = 'EST+05EDT,M4.1.0,M10.5.0' +>>> time.tzset() +>>> time.strftime('%X %x %Z') +'02:07:36 05/08/03 EDT' +>>> os.environ['TZ'] = 'AEST-10AEDT-11,M10.5.0,M3.5.0' +>>> time.tzset() +>>> time.strftime('%X %x %Z') +'16:08:12 05/08/03 AEST' +}}} + +And here is an example showing the same information using {{{tzstr}}}, +without touching system settings. +{{{ +>>> tz1 = tzstr('EST+05EDT,M4.1.0,M10.5.0') +>>> tz2 = tzstr('AEST-10AEDT-11,M10.5.0,M3.5.0') +>>> dt = datetime(2003, 5, 8, 2, 7, 36, tzinfo=tz1) +>>> dt.strftime('%X %x %Z') +'02:07:36 05/08/03 EDT' +>>> dt.astimezone(tz2).strftime('%X %x %Z') +'16:07:36 05/08/03 AEST' +}}} + +Are these really equivalent? +{{{ +>>> tzstr('EST5EDT') == tzstr('EST5EDT,4,1,0,7200,10,-1,0,7200,3600') +True +}}} + +Check the daylight limit. +{{{ +>>> datetime(2003, 4, 6, 1, 59, tzinfo=tz).tzname() +'EST' +>>> datetime(2003, 4, 6, 2, 00, tzinfo=tz).tzname() +'EDT' +>>> datetime(2003, 10, 26, 0, 59, tzinfo=tz).tzname() +'EDT' +>>> datetime(2003, 10, 26, 1, 00, tzinfo=tz).tzname() +'EST' +}}} + +==== tzrange type ==== +This type offers the same functionality as the {{{tzstr}}} type, but +instead of timezone strings, information is passed using +{{{relativedelta}}}s which are applied to a datetime set to the first +day of the year. Here is the prototype of this type's constructor: +{{{ +tzrange(stdabbr, stdoffset=None, dstabbr=None, dstoffset=None, + start=None, end=None): +}}} + +Offsets must be given in seconds. Information not provided will be +set to the defaults, as explained in the {{{tzstr}}} section above. + +==== tzrange examples ==== +{{{ +>>> tzstr('EST5EDT') == tzrange("EST", -18000, "EDT") +True + +>>> from dateutil.relativedelta import * +>>> range1 = tzrange("EST", -18000, "EDT") +>>> range2 = tzrange("EST", -18000, "EDT", -14400, +... relativedelta(hours=+2, month=4, day=1, + weekday=SU(+1)), +... relativedelta(hours=+1, month=10, day=31, + weekday=SU(-1))) +>>> tzstr('EST5EDT') == range1 == range2 +True +}}} + +Notice a minor detail in the last example: while the DST should end +at 2AM, the delta will catch 1AM. That's because the daylight saving +time should end at 2AM standard time (the difference between STD and +DST is 1h in the given example) instead of the DST time. That's how +the {{{tzinfo}}} subtypes should deal with the extra hour that happens +when going back to the standard time. Check +[http://www.python.org/doc/current/lib/datetime-tzinfo.html tzinfo documentation] +for more information. + +==== tzfile type ==== +This type allows one to use tzfile(5) format timezone files to extract +current and historical zone information. Here is the type constructor +prototype: +{{{ +tzfile(fileobj) +}}} + +Where {{{fileobj}}} is either a filename or a file-like object with +a {{{read()}}} method. + +==== tzfile examples ==== +{{{ +>>> tz = tzfile("/etc/localtime") +>>> datetime.now(tz) +datetime.datetime(2003, 9, 27, 12, 3, 48, 392138, + tzinfo=tzfile('/etc/localtime')) + +>>> datetime.now(tz).astimezone(tzutc()) +datetime.datetime(2003, 9, 27, 15, 3, 53, 70863, + tzinfo=tzutc()) + +>>> datetime.now(tz).tzname() +'BRST' +>>> datetime(2003, 1, 1, tzinfo=tz).tzname() +'BRDT' +}}} + +Check the daylight limit. +{{{ +>>> tz = tzfile('/usr/share/zoneinfo/EST5EDT') +>>> datetime(2003, 4, 6, 1, 59, tzinfo=tz).tzname() +'EST' +>>> datetime(2003, 4, 6, 2, 00, tzinfo=tz).tzname() +'EDT' +>>> datetime(2003, 10, 26, 0, 59, tzinfo=tz).tzname() +'EDT' +>>> datetime(2003, 10, 26, 1, 00, tzinfo=tz).tzname() +'EST' +}}} + +==== tzical type ==== +This type is able to parse +[ftp://ftp.rfc-editor.org/in-notes/rfc2445.txt iCalendar] +style {{{VTIMEZONE}}} sessions into a Python timezone object. +The constuctor prototype is: +{{{ +tzical(fileobj) +}}} + +Where {{{fileobj}}} is either a filename or a file-like object with +a {{{read()}}} method. + +==== tzical methods ==== + + tzical.get(tzid=None):: + Since a single iCalendar file may contain more than one timezone, + you must ask for the timezone you want with this method. If there's + more than one timezone in the parsed file, you'll need to pass the + {{{tzid}}} parameter. Otherwise, leaving it empty will yield the only + available timezone. + +==== tzical examples ==== +Here is a sample file extracted from the RFC. This file defines +the {{{EST5EDT}}} timezone, and will be used in the following example. +{{{ +BEGIN:VTIMEZONE +TZID:US-Eastern +LAST-MODIFIED:19870101T000000Z +TZURL:http://zones.stds_r_us.net/tz/US-Eastern +BEGIN:STANDARD +DTSTART:19671029T020000 +RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +TZNAME:EST +END:STANDARD +BEGIN:DAYLIGHT +DTSTART:19870405T020000 +RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +TZNAME:EDT +END:DAYLIGHT +END:VTIMEZONE +}}} + +And here is an example exploring a {{{tzical}}} type: +{{{ +>>> from dateutil.tz import *; from datetime import * + +>>> tz = tzical('EST5EDT.ics') +>>> tz.keys() +['US-Eastern'] + +>>> est = tz.get('US-Eastern') +>>> est + + +>>> datetime.now(est) +datetime.datetime(2003, 10, 6, 19, 44, 18, 667987, + tzinfo=) + +>>> est == tz.get() +True +}}} + +Let's check the daylight ranges, as usual: +{{{ +>>> datetime(2003, 4, 6, 1, 59, tzinfo=est).tzname() +'EST' +>>> datetime(2003, 4, 6, 2, 00, tzinfo=est).tzname() +'EDT' + +>>> datetime(2003, 10, 26, 0, 59, tzinfo=est).tzname() +'EDT' +>>> datetime(2003, 10, 26, 1, 00, tzinfo=est).tzname() +'EST' +}}} + +==== tzwin type ==== +This type offers access to internal registry-based Windows timezones. +The constuctor prototype is: +{{{ +tzwin(name) +}}} + +Where {{{name}}} is the timezone name. There's a static {{{tzwin.list()}}} +method to check the available names, + +==== tzwin methods ==== + + tzwin.display():: + This method returns the timezone extended name. + + tzwin.list():: + This static method lists all available timezone names. + +==== tzwin examples ==== +{{{ +>>> tz = tzwin("E. South America Standard Time") +}}} + +==== tzwinlocal type ==== +This type offers access to internal registry-based Windows timezones. +The constructor accepts no parameters, so the prototype is: +{{{ +tzwinlocal() +}}} + +==== tzwinlocal methods ==== + + tzwinlocal.display():: + This method returns the timezone extended name, and returns + {{{None}}} if one is not available. + +==== tzwinlocal examples ==== +{{{ +>>> tz = tzwinlocal() +}}} + +==== gettz() function ==== +This function is a helper that will try its best to get the right +timezone for your environment, or for the given string. The prototype +is as follows: +{{{ +gettz(name=None) +}}} + +If given, the parameter may be a filename, a path relative to the base +of the timezone information path (the base could be +{{{/usr/share/zoneinfo}}}, for example), a string timezone +specification, or a timezone abbreviation. If {{{name}}} is not given, +and the {{{TZ}}} environment variable is set, it's used instead. If the +parameter is not given, and {{{TZ}}} is not set, the default tzfile +paths will be tried. Then, if no timezone information is found, +an internal compiled database of timezones is used. When running +on Windows, the internal registry-based Windows timezones are also +considered. + +Example: +{{{ +>>> from dateutil.tz import * +>>> gettz() +tzfile('/etc/localtime') + +>>> gettz("America/Sao Paulo") +tzfile('/usr/share/zoneinfo/America/Sao_Paulo') + +>>> gettz("EST5EDT") +tzfile('/usr/share/zoneinfo/EST5EDT') + +>>> gettz("EST5") +tzstr('EST5') + +>>> gettz('BRST') +tzlocal() + +>>> os.environ["TZ"] = "America/Sao Paulo" +>>> gettz() +tzfile('/usr/share/zoneinfo/America/Sao_Paulo') + +>>> os.environ["TZ"] = "BRST" +>>> gettz() +tzlocal() + +>>> gettz("Unavailable") +>>> +}}} + +=== zoneinfo === +This module provides direct access to the internal compiled +database of timezones. The timezone data and the compiling tools +are obtained from the following project: + + http://www.twinsun.com/tz/tz-link.htm + +==== gettz() function ==== +This function will try to retrieve the given timezone information +from the internal compiled database, and will cache its results. + +Example: +{{{ +>>> from dateutil import zoneinfo +>>> zoneinfo.gettz("Brazil/East") +tzfile('Brazil/East') +}}} + + +== Building and releasing == +When you get the source, it does not contain the internal zoneinfo +database. To get (and update) the database, run the updatezinfo.py script. Make sure +that the zic command is in your path, and that you have network connectivity +to get the latest timezone information from IANA. If you have downloaded +the timezone data earlier, you can give the tarball as a parameter to +updatezinfo.py. + +After testing any changes, changing the version information in dateutil/__init__.py +and updating NEWS, a new release can be uploaded to PyPI with +{{{ +python setup.py sdist upload +}}} + + +== Testing == +dateutil has a comprehensive test suite, which can be run simply by running the +test.py script in the project root. Note that if you don't have the internal +zoneinfo database, some tests will fail. Apart from that, all tests should pass. + +To easily test dateutil against all supported Python versions, you can use +[[[http://tox.readthedocs.org/en/latest/ tox]]]. + +## vim:ft=moin diff --git a/README.rst b/README.rst deleted file mode 100644 index 2fd7bcc..0000000 --- a/README.rst +++ /dev/null @@ -1,124 +0,0 @@ -dateutil - powerful extensions to datetime -========================================== - -.. image:: https://img.shields.io/travis/dateutil/dateutil/master.svg?style=flat-square - :target: https://travis-ci.org/dateutil/dateutil - :alt: travis build status - -.. image:: https://img.shields.io/appveyor/ci/dateutil/dateutil/master.svg?style=flat-square - :target: https://ci.appveyor.com/project/dateutil/dateutil - :alt: appveyor build status - -.. image:: https://img.shields.io/pypi/dd/python-dateutil.svg?style=flat-square - :target: https://pypi.python.org/pypi/python-dateutil/ - :alt: pypi downloads per day - -.. image:: https://img.shields.io/pypi/v/python-dateutil.svg?style=flat-square - :target: https://pypi.python.org/pypi/python-dateutil/ - :alt: pypi version - - - -The `dateutil` module provides powerful extensions to -the standard `datetime` module, available in Python. - - -Download -======== -dateutil is available on PyPI -https://pypi.python.org/pypi/python-dateutil/ - -The documentation is hosted at: -https://dateutil.readthedocs.org/ - -Code -==== -https://github.com/dateutil/dateutil/ - -Features -======== - -* Computing of relative deltas (next month, next year, - next monday, last week of month, etc); -* Computing of relative deltas between two given - date and/or datetime objects; -* Computing of dates based on very flexible recurrence rules, - using a superset of the `iCalendar `_ - specification. Parsing of RFC strings is supported as well. -* Generic parsing of dates in almost any string format; -* Timezone (tzinfo) implementations for tzfile(5) format - files (/etc/localtime, /usr/share/zoneinfo, etc), TZ - environment string (in all known formats), iCalendar - format files, given ranges (with help from relative deltas), - local machine timezone, fixed offset timezone, UTC timezone, - and Windows registry-based time zones. -* Internal up-to-date world timezone information based on - Olson's database. -* Computing of Easter Sunday dates for any given year, - using Western, Orthodox or Julian algorithms; -* More than 400 test cases. - -Quick example -============= -Here's a snapshot, just to give an idea about the power of the -package. For more examples, look at the documentation. - -Suppose you want to know how much time is left, in -years/months/days/etc, before the next easter happening on a -year with a Friday 13th in August, and you want to get today's -date out of the "date" unix system command. Here is the code: - -.. doctest:: readmeexample - - >>> from dateutil.relativedelta import * - >>> from dateutil.easter import * - >>> from dateutil.rrule import * - >>> from dateutil.parser import * - >>> from datetime import * - >>> now = parse("Sat Oct 11 17:13:46 UTC 2003") - >>> today = now.date() - >>> year = rrule(YEARLY,dtstart=now,bymonth=8,bymonthday=13,byweekday=FR)[0].year - >>> rdelta = relativedelta(easter(year), today) - >>> print("Today is: %s" % today) - Today is: 2003-10-11 - >>> print("Year with next Aug 13th on a Friday is: %s" % year) - Year with next Aug 13th on a Friday is: 2004 - >>> print("How far is the Easter of that year: %s" % rdelta) - How far is the Easter of that year: relativedelta(months=+6) - >>> print("And the Easter of that year is: %s" % (today+rdelta)) - And the Easter of that year is: 2004-04-11 - -Being exactly 6 months ahead was **really** a coincidence :) - - -Author -====== -The dateutil module was written by Gustavo Niemeyer -in 2003 - -It is maintained by: - -* Gustavo Niemeyer 2003-2011 -* Tomi Pieviläinen 2012-2014 -* Yaron de Leeuw 2014- - -Building and releasing -====================== -When you get the source, it does not contain the internal zoneinfo -database. To get (and update) the database, run the updatezinfo.py script. Make sure -that the zic command is in your path, and that you have network connectivity -to get the latest timezone information from IANA. If you have downloaded -the timezone data earlier, you can give the tarball as a parameter to -updatezinfo.py. - - -Testing -======= -dateutil has a comprehensive test suite, which can be run simply by running -`python setup.py test [-q]` in the project root. Note that if you don't have the internal -zoneinfo database, some tests will fail. Apart from that, all tests should pass. - -To easily test dateutil against all supported Python versions, you can use -`tox `_. - -All github pull requests are automatically tested using travis. diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 4d1d974..0000000 --- a/appveyor.yml +++ /dev/null @@ -1,18 +0,0 @@ -build: false -environment: - matrix: - - PYTHON: "C:/Python27" - - PYTHON: "C:/Python27-x64" - - PYTHON: "C:/Python33" - - PYTHON: "C:/Python33-x64" - - PYTHON: "C:/Python34" - - PYTHON: "C:/Python34-x64" -install: - - ps: Start-FileDownload 'https://raw.github.com/pypa/pip/master/contrib/get-pip.py' - - "%PYTHON%/python.exe get-pip.py" - - "%PYTHON%/Scripts/pip.exe install six" - # use postgres' zic - - set path=c:\Program Files\PostgreSQL\9.3\bin\;%PATH% - - "%PYTHON%/python.exe updatezinfo.py" -test_script: - - "%PYTHON%/python.exe setup.py test" diff --git a/dateutil/__init__.py b/dateutil/__init__.py index 743669c..1020e72 100644 --- a/dateutil/__init__.py +++ b/dateutil/__init__.py @@ -1,2 +1,10 @@ # -*- coding: utf-8 -*- -__version__ = "2.4.2" +""" +Copyright (c) 2003-2010 Gustavo Niemeyer + +This module offers extensions to the standard Python +datetime module. +""" +__author__ = "Tomi Pieviläinen " +__license__ = "Simplified BSD" +__version__ = "2.2" diff --git a/dateutil/easter.py b/dateutil/easter.py index 8d30c4e..d8a3884 100644 --- a/dateutil/easter.py +++ b/dateutil/easter.py @@ -1,17 +1,18 @@ -# -*- coding: utf-8 -*- """ -This module offers a generic easter computing method for any given year, using -Western, Orthodox or Julian algorithms. +Copyright (c) 2003-2007 Gustavo Niemeyer + +This module offers extensions to the standard Python +datetime module. """ +__license__ = "Simplified BSD" import datetime __all__ = ["easter", "EASTER_JULIAN", "EASTER_ORTHODOX", "EASTER_WESTERN"] -EASTER_JULIAN = 1 +EASTER_JULIAN = 1 EASTER_ORTHODOX = 2 -EASTER_WESTERN = 3 - +EASTER_WESTERN = 3 def easter(year, method=EASTER_WESTERN): """ @@ -23,7 +24,7 @@ def easter(year, method=EASTER_WESTERN): This algorithm implements three different easter calculation methods: - + 1 - Original calculation in Julian calendar, valid in dates after 326 AD 2 - Original method, with date converted to Gregorian @@ -38,7 +39,7 @@ def easter(year, method=EASTER_WESTERN): EASTER_WESTERN = 3 The default method is method 3. - + More about the algorithm may be found at: http://users.chariot.net.au/~gmarts/eastalg.htm @@ -67,23 +68,24 @@ def easter(year, method=EASTER_WESTERN): e = 0 if method < 3: # Old method - i = (19*g + 15) % 30 - j = (y + y//4 + i) % 7 + i = (19*g+15)%30 + j = (y+y//4+i)%7 if method == 2: # Extra dates to convert Julian to Gregorian date e = 10 if y > 1600: - e = e + y//100 - 16 - (y//100 - 16)//4 + e = e+y//100-16-(y//100-16)//4 else: # New method c = y//100 - h = (c - c//4 - (8*c + 13)//25 + 19*g + 15) % 30 - i = h - (h//28)*(1 - (h//28)*(29//(h + 1))*((21 - g)//11)) - j = (y + y//4 + i + 2 - c + c//4) % 7 + h = (c-c//4-(8*c+13)//25+19*g+15)%30 + i = h-(h//28)*(1-(h//28)*(29//(h+1))*((21-g)//11)) + j = (y+y//4+i+2-c+c//4)%7 # p can be from -6 to 56 corresponding to dates 22 March to 23 May # (later dates apply to method 2, although 23 May never actually occurs) - p = i - j + e - d = 1 + (p + 27 + (p + 6)//40) % 31 - m = 3 + (p + 26)//30 + p = i-j+e + d = 1+(p+27+(p+6)//40)%31 + m = 3+(p+26)//30 return datetime.date(int(y), int(m), int(d)) + diff --git a/dateutil/parser.py b/dateutil/parser.py index eaf6ea5..aef8362 100644 --- a/dateutil/parser.py +++ b/dateutil/parser.py @@ -1,75 +1,50 @@ # -*- coding:iso-8859-1 -*- """ -This module offers a generic date/time string parser which is able to parse -most known formats to represent a date and/or time. - -This module attempts to be forgiving with regards to unlikely input formats, -returning a datetime object even for dates which are ambiguous. If an element -of a date/time stamp is omitted, the following rules are applied: -- If AM or PM is left unspecified, a 24-hour clock is assumed, however, an hour - on a 12-hour clock (``0 <= hour <= 12``) *must* be specified if AM or PM is - specified. -- If a time zone is omitted, a timezone-naive datetime is returned. - -If any other elements are missing, they are taken from the -:class:`datetime.datetime` object passed to the parameter ``default``. If this -results in a day number exceeding the valid number of days per month, one can -fall back to the last day of the month by setting ``fallback_on_invalid_day`` -parameter to ``True``. - -Also provided is the ``smart_defaults`` option, which attempts to fill in the -missing elements from context. If specified, the logic is: -- If the omitted element is smaller than the largest specified element, select - the *earliest* time matching the specified conditions; so ``"June 2010"`` is - interpreted as ``June 1, 2010 0:00:00``) and the (somewhat strange) - ``"Feb 1997 3:15 PM"`` is interpreted as ``February 1, 1997 15:15:00``. -- If the element is larger than the largest specified element, select the - *most recent* time matching the specified conditions (e.g parsing ``"May"`` - in June 2015 returns the date May 1st, 2015, whereas parsing it in April 2015 - returns May 1st 2014). If using the ``date_in_future`` flag, this logic is - inverted, and instead the *next* time matching the specified conditions is - returned. - -Additional resources about date/time string formats can be found below: - -- `A summary of the international standard date and time notation - `_ -- `W3C Date and Time Formats `_ -- `Time Formats (Planetary Rings Node) `_ -- `CPAN ParseDate module - `_ -- `Java SimpleDateFormat Class - `_ +Copyright (c) 2003-2007 Gustavo Niemeyer + +This module offers extensions to the standard Python +datetime module. """ from __future__ import unicode_literals +__license__ = "Simplified BSD" + import datetime import string import time +import sys +import os import collections -import re -from io import StringIO -from calendar import monthrange, isleap + +try: + from io import StringIO +except ImportError: + from io import StringIO from six import text_type, binary_type, integer_types from . import relativedelta from . import tz + __all__ = ["parse", "parserinfo"] +# Some pointers: +# +# http://www.cl.cam.ac.uk/~mgk25/iso-time.html +# http://www.iso.ch/iso/en/prods-services/popstds/datesandtime.html +# http://www.w3.org/TR/NOTE-datetime +# http://ringmaster.arc.nasa.gov/tools/time_formats.html +# http://search.cpan.org/author/MUIR/Time-modules-2003.0211/lib/Time/ParseDate.pm +# http://stein.cshl.org/jade/distrib/docs/java.text.SimpleDateFormat.html + + class _timelex(object): - # Fractional seconds are sometimes split by a comma - _split_decimal = re.compile("([\.,])") def __init__(self, instream): - if isinstance(instream, binary_type): - instream = instream.decode() - if isinstance(instream, text_type): instream = StringIO(instream) - self.instream = instream self.wordchars = ('abcdfeghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ_' @@ -82,47 +57,25 @@ def __init__(self, instream): self.eof = False def get_token(self): - """ - This function breaks the time string into lexical units (tokens), which - can be parsed by the parser. Lexical units are demarcated by changes in - the character set, so any continuous string of letters is considered - one unit, any continuous string of numbers is considered one unit. - - The main complication arises from the fact that dots ('.') can be used - both as separators (e.g. "Sep.20.2009") or decimal points (e.g. - "4:30:21.447"). As such, it is necessary to read the full context of - any dot-separated strings before breaking it into tokens; as such, this - function maintains a "token stack", for when the ambiguous context - demands that multiple tokens be parsed at once. - """ if self.tokenstack: return self.tokenstack.pop(0) - seenletters = False token = None state = None wordchars = self.wordchars numchars = self.numchars whitespace = self.whitespace - while not self.eof: - # We only realize that we've reached the end of a token when we - # find a character that's not part of the current token - since - # that character may be part of the next token, it's stored in the - # charstack. if self.charstack: nextchar = self.charstack.pop(0) else: nextchar = self.instream.read(1) while nextchar == '\x00': nextchar = self.instream.read(1) - if not nextchar: self.eof = True break elif not state: - # First character of the token - determines if we're starting - # to parse a word, a number or something else. token = nextchar if nextchar in wordchars: state = 'a' @@ -130,12 +83,10 @@ def get_token(self): state = '0' elif nextchar in whitespace: token = ' ' - break # emit token + break # emit token else: - break # emit token + break # emit token elif state == 'a': - # If we've already started reading a word, we keep reading - # letters until we find something that's not part of a word. seenletters = True if nextchar in wordchars: token += nextchar @@ -144,21 +95,17 @@ def get_token(self): state = 'a.' else: self.charstack.append(nextchar) - break # emit token + break # emit token elif state == '0': - # If we've already started reading a number, we keep reading - # numbers until we find something that doesn't fit. if nextchar in numchars: token += nextchar - elif nextchar == '.' or (nextchar == ',' and len(token) >= 2): + elif nextchar == '.': token += nextchar state = '0.' else: self.charstack.append(nextchar) - break # emit token + break # emit token elif state == 'a.': - # If we've seen some letters and a dot separator, continue - # parsing, and the tokens will be broken up later. seenletters = True if nextchar == '.' or nextchar in wordchars: token += nextchar @@ -167,10 +114,8 @@ def get_token(self): state = '0.' else: self.charstack.append(nextchar) - break # emit token + break # emit token elif state == '0.': - # If we've seen at least one dot separator, keep going, we'll - # break up the tokens later. if nextchar == '.' or nextchar in numchars: token += nextchar elif nextchar in wordchars and token[-1] == '.': @@ -178,19 +123,15 @@ def get_token(self): state = 'a.' else: self.charstack.append(nextchar) - break # emit token - - if (state in ('a.', '0.') and (seenletters or token.count('.') > 1 or - token[-1] in '.,')): - l = self._split_decimal.split(token) + break # emit token + if (state in ('a.', '0.') and + (seenletters or token.count('.') > 1 or token[-1] == '.')): + l = token.split('.') token = l[0] for tok in l[1:]: + self.tokenstack.append('.') if tok: self.tokenstack.append(tok) - - if state == '0.' and token.count('.') == 0: - token = token.replace(',', '.') - return token def __iter__(self): @@ -200,7 +141,6 @@ def __next__(self): token = self.get_token() if token is None: raise StopIteration - return token def next(self): @@ -230,22 +170,6 @@ def __repr__(self): class parserinfo(object): - """ - Class which handles what inputs are accepted. Subclass this to customize - the language and acceptable values for each parameter. - - :param dayfirst: - Whether to interpret the first value in an ambiguous 3-integer date - (e.g. 01/05/09) as the day (``True``) or month (``False``). If - ``yearfirst`` is set to ``True``, this distinguishes between YDM - and YMD. Default is ``False``. - - :param yearfirst: - Whether to interpret the first value in an ambiguous 3-integer date - (e.g. 01/05/09) as the year. If ``True``, the first number is taken - to be the year, otherwise the last number is taken to be the year. - Default is ``False``. - """ # m from a.m/p.m, t from ISO T separator JUMP = [" ", ".", ",", ";", "-", "/", "'", @@ -259,18 +183,18 @@ class parserinfo(object): ("Fri", "Friday"), ("Sat", "Saturday"), ("Sun", "Sunday")] - MONTHS = [("Jan", "January"), - ("Feb", "February"), - ("Mar", "March"), - ("Apr", "April"), - ("May", "May"), - ("Jun", "June"), - ("Jul", "July"), - ("Aug", "August"), - ("Sep", "Sept", "September"), - ("Oct", "October"), - ("Nov", "November"), - ("Dec", "December")] + MONTHS = [("Jan", "January"), + ("Feb", "February"), + ("Mar", "March"), + ("Apr", "April"), + ("May", "May"), + ("Jun", "June"), + ("Jul", "July"), + ("Aug", "August"), + ("Sep", "Sept", "September"), + ("Oct", "October"), + ("Nov", "November"), + ("Dec", "December")] HMS = [("h", "hour", "hours"), ("m", "minute", "minutes"), ("s", "second", "seconds")] @@ -280,7 +204,7 @@ class parserinfo(object): PERTAIN = ["of"] TZOFFSET = {} - def __init__(self, dayfirst=False, yearfirst=False, smart_defaults=False): + def __init__(self, dayfirst=False, yearfirst=False): self._jump = self._convert(self.JUMP) self._weekdays = self._convert(self.WEEKDAYS) self._months = self._convert(self.MONTHS) @@ -291,14 +215,14 @@ def __init__(self, dayfirst=False, yearfirst=False, smart_defaults=False): self.dayfirst = dayfirst self.yearfirst = yearfirst - self.smart_defaults = smart_defaults self._year = time.localtime().tm_year - self._century = self._year // 100 * 100 + self._century = self._year//100*100 def _convert(self, lst): dct = {} - for i, v in enumerate(lst): + for i in range(len(lst)): + v = lst[i] if isinstance(v, tuple): for v in v: dct[v.lower()] = i @@ -320,7 +244,7 @@ def weekday(self, name): def month(self, name): if len(name) >= 3: try: - return self._months[name.lower()] + 1 + return self._months[name.lower()]+1 except KeyError: pass return None @@ -346,13 +270,12 @@ def utczone(self, name): def tzoffset(self, name): if name in self._utczone: return 0 - return self.TZOFFSET.get(name) def convertyear(self, year): if year < 100: year += self._century - if abs(year - self._year) >= 50: + if abs(year-self._year) >= 50: if year < self._year: year += 100 else: @@ -363,7 +286,6 @@ def validate(self, res): # move to info if res.year is not None: res.year = self.convertyear(res.year) - if res.tzoffset == 0 and not res.tzname or res.tzname == 'Z': res.tzname = "UTC" res.tzoffset = 0 @@ -373,209 +295,37 @@ def validate(self, res): class parser(object): + def __init__(self, info=None): self.info = info or parserinfo() - def parse(self, timestr, default=None, ignoretz=False, tzinfos=None, - smart_defaults=None, date_in_future=False, - fallback_on_invalid_day=None, **kwargs): - """ - Parse the date/time string into a :class:`datetime.datetime` object. - - :param timestr: - Any date/time string using the supported formats. - - :param default: - The default datetime object, if this is a datetime object and not - ``None``, elements specified in ``timestr`` replace elements in the - default object, unless ``smart_defaults`` is set to ``True``, in - which case to the extent necessary, timestamps are calculated - relative to this date. - - :param smart_defaults: - If using smart defaults, the ``default`` parameter is treated as - the effective parsing date/time, and the context of the datetime - string is determined relative to ``default``. If ``None``, this - parameter is inherited from the :class:`parserinfo` object. - - :param date_in_future: - If ``smart_defaults`` is ``True``, the parser assumes by default - that the timestamp refers to a date in the past, and will return - the beginning of the most recent timespan which matches the time - string (e.g. if ``default`` is March 3rd, 2013, "Feb" parses to - "Feb 1, 2013" and "May 3" parses to May 3rd, 2012). Setting this - parameter to ``True`` inverts this assumption, and returns the - beginning of the *next* matching timespan. - - :param fallback_on_invalid_day: - If specified ``True``, an otherwise invalid date such as "Feb 30" - or "June 32" falls back to the last day of the month. If specified - as "False", the parser is strict about parsing otherwise valid - dates that would turn up as invalid because of the fallback rules - (e.g. "Feb 2010" run with a default of January 30, 2010 and - ``smartparser`` set to ``False`` would would throw an error, rather - than falling back to the end of February). If ``None`` or - unspecified, the date falls back to the most recent valid date only - if the invalid date is created as a result of an unspecified day in - the time string. - - :param ignoretz: - If set ``True``, time zones in parsed strings are ignored and a - naive :class:`datetime.datetime` object is returned. - - :param tzinfos: - Additional time zone names / aliases which may be present in the - string. This argument maps time zone names (and optionally offsets - from those time zones) to time zones. This parameter can be a - dictionary with timezone aliases mapping time zone names to time - zones or a function taking two parameters (``tzname`` and - ``tzoffset``) and returning a time zone. - - The timezones to which the names are mapped can be an integer - offset from UTC in minutes or a :class:`tzinfo` object. - - .. doctest:: - - >>> from dateutil.parser import parse - >>> from dateutil.tz import gettz - >>> tzinfos = {"BRST": -10800, "CST": gettz("America/Chicago")} - >>> parse("2012-01-19 17:21:00 BRST", tzinfos=tzinfos) - datetime.datetime(2014, 2, 19, 17, 21, tzinfo=tzoffset(u'BRST', -10800)) - >>> parse("2012-01-19 17:21:00 CST", tzinfos=tzinfos) - datetime.datetime(2014, 2, 19, 17, 21, tzinfo=tzfile('America/Chicago')) - - This parameter is ignored if ``ignoretz`` is set. - - :param **kwargs: - Keyword arguments as passed to ``_parse()``. - - :return: - Returns a :class:`datetime.datetime` object or, if the - ``fuzzy_with_tokens`` option is ``True``, returns a tuple, the - first element being a :class:`datetime.datetime` object, the second - a tuple containing the fuzzy tokens. - - :raises ValueError: - Raised for invalid or unknown string format, if the provided - :class:`tzinfo` is not in a valid format, or if an invalid date - would be created. - - :raises OverFlowError: - Raised if the parsed date exceeds the largest valid C integer on - your system. - """ - - if smart_defaults is None: - smart_defaults = self.info.smart_defaults - - if default is None: - effective_dt = datetime.datetime.now() + def parse(self, timestr, default=None, + ignoretz=False, tzinfos=None, + **kwargs): + if not default: default = datetime.datetime.now().replace(hour=0, minute=0, second=0, microsecond=0) - else: - effective_dt = default + res, skipped_tokens = self._parse(timestr, **kwargs) if res is None: - raise ValueError("Unknown string format") - + raise ValueError("unknown string format") repl = {} - for attr in ("year", "month", "day", "hour", - "minute", "second", "microsecond"): + for attr in ["year", "month", "day", "hour", + "minute", "second", "microsecond"]: value = getattr(res, attr) if value is not None: repl[attr] = value - - # Choose the correct fallback position if requested by the - # ``smart_defaults`` parameter. - if smart_defaults: - # Determine if it refers to this year, last year or next year - if res.year is None: - if res.month is not None: - # Explicitly deal with leap year problems - if res.month == 2 and (res.day is not None and - res.day == 29): - - ly_offset = 4 if date_in_future else -4 - next_year = 4 * (default.year // 4) - - if date_in_future: - next_year += ly_offset - - if not isleap(next_year): - next_year += ly_offset - - if not isleap(default.year): - default = default.replace(year=next_year) - elif date_in_future: - next_year = default.year + 1 - else: - next_year = default.year - 1 - - if ((res.month == default.month and res.day is not None and - ((res.day < default.day and date_in_future) or - (res.day > default.day and not date_in_future))) or - ((res.month < default.month and date_in_future) or - (res.month > default.month and not date_in_future))): - - default = default.replace(year=next_year) - - # Select a proper month - if res.month is None: - if res.year is not None: - default = default.replace(month=1) - - # I'm not sure if this is even possible. - if res.day is not None: - if res.day < default.day and date_in_future: - default += datetime.timedelta(months=1) - elif res.day > default.day and not date_in_future: - default -= datetime.timedelta(months=1) - - if res.day is None: - # Determine if it's today, tomorrow or yesterday. - if res.year is None and res.month is None: - t_repl = {} - for key, val in repl.iteritems(): - if key in ('hour', 'minute', 'second', 'microsecond'): - t_repl[key] = val - - stime = effective_dt.replace(**t_repl) - - if stime < effective_dt and date_in_future: - default += datetime.timedelta(days=1) - elif stime > effective_dt and not date_in_future: - default -= datetime.timedelta(days=1) - else: - # Otherwise it's the beginning of the month - default = default.replace(day=1) - - if fallback_on_invalid_day or (fallback_on_invalid_day is None and - 'day' not in repl): - # If the default day exceeds the last day of the month, fall back to - # the end of the month. - cyear = default.year if res.year is None else res.year - cmonth = default.month if res.month is None else res.month - cday = default.day if res.day is None else res.day - - if cday > monthrange(cyear, cmonth)[1]: - repl['day'] = monthrange(cyear, cmonth)[1] - ret = default.replace(**repl) - if res.weekday is not None and not res.day: ret = ret+relativedelta.relativedelta(weekday=res.weekday) - if not ignoretz: - if (isinstance(tzinfos, collections.Callable) or - tzinfos and res.tzname in tzinfos): - + if isinstance(tzinfos, collections.Callable) or tzinfos and res.tzname in tzinfos: if isinstance(tzinfos, collections.Callable): tzdata = tzinfos(res.tzname, res.tzoffset) else: tzdata = tzinfos.get(res.tzname) - if isinstance(tzdata, datetime.tzinfo): tzinfo = tzdata elif isinstance(tzdata, text_type): @@ -583,8 +333,8 @@ def parse(self, timestr, default=None, ignoretz=False, tzinfos=None, elif isinstance(tzdata, integer_types): tzinfo = tz.tzoffset(res.tzname, tzdata) else: - raise ValueError("Offset must be tzinfo subclass, " - "tz string, or int offset.") + raise ValueError("offset must be tzinfo subclass, " \ + "tz string, or int offset") ret = ret.replace(tzinfo=tzinfo) elif res.tzname and res.tzname in time.tzname: ret = ret.replace(tzinfo=tz.tzlocal()) @@ -593,70 +343,28 @@ def parse(self, timestr, default=None, ignoretz=False, tzinfos=None, elif res.tzoffset: ret = ret.replace(tzinfo=tz.tzoffset(res.tzname, res.tzoffset)) - if kwargs.get('fuzzy_with_tokens', False): + if skipped_tokens: return ret, skipped_tokens - else: - return ret + + return ret class _result(_resultbase): __slots__ = ["year", "month", "day", "weekday", "hour", "minute", "second", "microsecond", - "tzname", "tzoffset", "ampm"] - - def _parse(self, timestr, dayfirst=None, yearfirst=None, fuzzy=False, - fuzzy_with_tokens=False): - """ - Private method which performs the heavy lifting of parsing, called from - ``parse()``, which passes on its ``kwargs`` to this function. - - :param timestr: - The string to parse. - - :param dayfirst: - Whether to interpret the first value in an ambiguous 3-integer date - (e.g. 01/05/09) as the day (``True``) or month (``False``). If - ``yearfirst`` is set to ``True``, this distinguishes between YDM - and YMD. If set to ``None``, this value is retrieved from the - current :class:`parserinfo` object (which itself defaults to - ``False``). - - :param yearfirst: - Whether to interpret the first value in an ambiguous 3-integer date - (e.g. 01/05/09) as the year. If ``True``, the first number is taken - to be the year, otherwise the last number is taken to be the year. - If this is set to ``None``, the value is retrieved from the current - :class:`parserinfo` object (which itself defaults to ``False``). - - :param fuzzy: - Whether to allow fuzzy parsing, allowing for string like "Today is - January 1, 2047 at 8:21:00AM". - - :param fuzzy_with_tokens: - If ``True``, ``fuzzy`` is automatically set to True, and the parser - will return a tuple where the first element is the parsed - :class:`datetime.datetime` datetimestamp and the second element is - a tuple containing the portions of the string which were ignored: - - .. doctest:: - - >>> from dateutil.parser import parse - >>> parse("Today is January 1, 2047 at 8:21:00AM", fuzzy_with_tokens=True) - (datetime.datetime(2011, 1, 1, 8, 21), (u'Today is ', u' ', u'at ')) - - """ + "tzname", "tzoffset"] + + def _parse(self, timestr, dayfirst=None, yearfirst=None, fuzzy=False, fuzzy_with_tokens=False): if fuzzy_with_tokens: fuzzy = True info = self.info - if dayfirst is None: dayfirst = info.dayfirst - if yearfirst is None: yearfirst = info.yearfirst - res = self._result() - l = _timelex.split(timestr) # Splits the timestr into tokens + l = _timelex.split(timestr) + # keep up with the last token skipped so we can recombine # consecutively skipped tokens (-2 for when i begins at 0). @@ -664,6 +372,7 @@ def _parse(self, timestr, dayfirst=None, yearfirst=None, fuzzy=False, skipped_tokens = list() try: + # year/month/day list ymd = [] @@ -685,21 +394,17 @@ def _parse(self, timestr, dayfirst=None, yearfirst=None, fuzzy=False, # Token is a number len_li = len(l[i]) i += 1 - if (len(ymd) == 3 and len_li in (2, 4) - and res.hour is None and (i >= len_l or (l[i] != ':' and - info.hms(l[i]) is None))): + and (i >= len_l or (l[i] != ':' and + info.hms(l[i]) is None))): # 19990101T23[59] s = l[i-1] res.hour = int(s[:2]) - if len_li == 4: res.minute = int(s[2:]) - elif len_li == 6 or (len_li > 6 and l[i-1].find('.') == 6): # YYMMDD or HHMMSS[.ss] s = l[i-1] - if not ymd and l[i-1].find('.') == -1: ymd.append(info.convertyear(int(s[:2]))) ymd.append(int(s[2:4])) @@ -709,14 +414,12 @@ def _parse(self, timestr, dayfirst=None, yearfirst=None, fuzzy=False, res.hour = int(s[:2]) res.minute = int(s[2:4]) res.second, res.microsecond = _parsems(s[4:]) - elif len_li == 8: # YYYYMMDD s = l[i-1] ymd.append(int(s[:4])) ymd.append(int(s[4:6])) ymd.append(int(s[6:])) - elif len_li in (12, 14): # YYYYMMDDhhmm[ss] s = l[i-1] @@ -725,42 +428,30 @@ def _parse(self, timestr, dayfirst=None, yearfirst=None, fuzzy=False, ymd.append(int(s[6:8])) res.hour = int(s[8:10]) res.minute = int(s[10:12]) - if len_li == 14: res.second = int(s[12:]) - elif ((i < len_l and info.hms(l[i]) is not None) or (i+1 < len_l and l[i] == ' ' and info.hms(l[i+1]) is not None)): - # HH[ ]h or MM[ ]m or SS[.ss][ ]s if l[i] == ' ': i += 1 - idx = info.hms(l[i]) - while True: if idx == 0: res.hour = int(value) - - if value % 1: - res.minute = int(60*(value % 1)) - + if value%1: + res.minute = int(60*(value%1)) elif idx == 1: res.minute = int(value) - - if value % 1: - res.second = int(60*(value % 1)) - + if value%1: + res.second = int(60*(value%1)) elif idx == 2: res.second, res.microsecond = \ _parsems(value_repr) - i += 1 - if i >= len_l or idx == 2: break - # 12h00 try: value_repr = l[i] @@ -770,49 +461,37 @@ def _parse(self, timestr, dayfirst=None, yearfirst=None, fuzzy=False, else: i += 1 idx += 1 - if i < len_l: newidx = info.hms(l[i]) - if newidx is not None: idx = newidx - - elif (i == len_l and l[i-2] == ' ' and - info.hms(l[i-3]) is not None): + elif i == len_l and l[i-2] == ' ' and info.hms(l[i-3]) is not None: # X h MM or X m SS idx = info.hms(l[i-3]) + 1 - if idx == 1: res.minute = int(value) - - if value % 1: - res.second = int(60*(value % 1)) + if value%1: + res.second = int(60*(value%1)) elif idx == 2: res.second, res.microsecond = \ - _parsems(value_repr) + _parsems(value_repr) i += 1 - elif i+1 < len_l and l[i] == ':': # HH:MM[:SS[.ss]] res.hour = int(value) i += 1 value = float(l[i]) res.minute = int(value) - - if value % 1: - res.second = int(60*(value % 1)) - + if value%1: + res.second = int(60*(value%1)) i += 1 - if i < len_l and l[i] == ':': res.second, res.microsecond = _parsems(l[i+1]) i += 2 - elif i < len_l and l[i] in ('-', '/', '.'): sep = l[i] ymd.append(int(value)) i += 1 - if i < len_l and not info.jump(l[i]): try: # 01-01[-01] @@ -820,57 +499,47 @@ def _parse(self, timestr, dayfirst=None, yearfirst=None, fuzzy=False, except ValueError: # 01-Jan[-01] value = info.month(l[i]) - if value is not None: ymd.append(value) assert mstridx == -1 mstridx = len(ymd)-1 else: - return None, None - + return None i += 1 - if i < len_l and l[i] == sep: # We have three members i += 1 value = info.month(l[i]) - if value is not None: ymd.append(value) mstridx = len(ymd)-1 assert mstridx == -1 else: ymd.append(int(l[i])) - i += 1 elif i >= len_l or info.jump(l[i]): if i+1 < len_l and info.ampm(l[i+1]) is not None: # 12 am res.hour = int(value) - if res.hour < 12 and info.ampm(l[i+1]) == 1: res.hour += 12 elif res.hour == 12 and info.ampm(l[i+1]) == 0: res.hour = 0 - i += 1 else: # Year, month or day ymd.append(int(value)) i += 1 elif info.ampm(l[i]) is not None: - # 12am res.hour = int(value) - if res.hour < 12 and info.ampm(l[i]) == 1: res.hour += 12 elif res.hour == 12 and info.ampm(l[i]) == 0: res.hour = 0 i += 1 - elif not fuzzy: - return None, None + return None else: i += 1 continue @@ -888,7 +557,6 @@ def _parse(self, timestr, dayfirst=None, yearfirst=None, fuzzy=False, ymd.append(value) assert mstridx == -1 mstridx = len(ymd)-1 - i += 1 if i < len_l: if l[i] in ('-', '/'): @@ -897,13 +565,11 @@ def _parse(self, timestr, dayfirst=None, yearfirst=None, fuzzy=False, i += 1 ymd.append(int(l[i])) i += 1 - if i < len_l and l[i] == sep: # Jan-01-99 i += 1 ymd.append(int(l[i])) i += 1 - elif (i+3 < len_l and l[i] == l[i+2] == ' ' and info.pertain(l[i+1])): # Jan of 01 @@ -922,47 +588,17 @@ def _parse(self, timestr, dayfirst=None, yearfirst=None, fuzzy=False, # Check am/pm value = info.ampm(l[i]) if value is not None: - # For fuzzy parsing, 'a' or 'am' (both valid English words) - # may erroneously trigger the AM/PM flag. Deal with that - # here. - val_is_ampm = True - - # If there's already an AM/PM flag, this one isn't one. - if fuzzy and res.ampm is not None: - val_is_ampm = False - - # If AM/PM is found and hour is not, raise a ValueError - if res.hour is None: - if fuzzy: - val_is_ampm = False - else: - raise ValueError('No hour specified with ' + - 'AM or PM flag.') - elif not 0 <= res.hour <= 12: - # If AM/PM is found, it's a 12 hour clock, so raise - # an error for invalid range - if fuzzy: - val_is_ampm = False - else: - raise ValueError('Invalid hour specified for ' + - '12-hour clock.') - - if val_is_ampm: - if value == 1 and res.hour < 12: - res.hour += 12 - elif value == 0 and res.hour == 12: - res.hour = 0 - - res.ampm = value - + if value == 1 and res.hour < 12: + res.hour += 12 + elif value == 0 and res.hour == 12: + res.hour = 0 i += 1 continue # Check for a timezone name if (res.hour is not None and len(l[i]) <= 5 and - res.tzname is None and res.tzoffset is None and - not [x for x in l[i] if x not in - string.ascii_uppercase]): + res.tzname is None and res.tzoffset is None and + not [x for x in l[i] if x not in string.ascii_uppercase]): res.tzname = l[i] res.tzoffset = info.tzoffset(res.tzname) i += 1 @@ -987,7 +623,6 @@ def _parse(self, timestr, dayfirst=None, yearfirst=None, fuzzy=False, signal = (-1, 1)[l[i] == '+'] i += 1 len_li = len(l[i]) - if len_li == 4: # -0300 res.tzoffset = int(l[i][:2])*3600+int(l[i][2:])*60 @@ -999,9 +634,8 @@ def _parse(self, timestr, dayfirst=None, yearfirst=None, fuzzy=False, # -[0]3 res.tzoffset = int(l[i][:2])*3600 else: - return None, None + return None i += 1 - res.tzoffset *= signal # Look for a timezone name between parenthesis @@ -1009,7 +643,7 @@ def _parse(self, timestr, dayfirst=None, yearfirst=None, fuzzy=False, info.jump(l[i]) and l[i+1] == '(' and l[i+3] == ')' and 3 <= len(l[i+2]) <= 5 and not [x for x in l[i+2] - if x not in string.ascii_uppercase]): + if x not in string.ascii_uppercase]): # -0300 (BRST) res.tzname = l[i+2] i += 4 @@ -1017,7 +651,7 @@ def _parse(self, timestr, dayfirst=None, yearfirst=None, fuzzy=False, # Check jumps if not (info.jump(l[i]) or fuzzy): - return None, None + return None if last_skipped_token_i == i - 1: # recombine the tokens @@ -1032,19 +666,17 @@ def _parse(self, timestr, dayfirst=None, yearfirst=None, fuzzy=False, len_ymd = len(ymd) if len_ymd > 3: # More than three members!? - return None, None + return None elif len_ymd == 1 or (mstridx != -1 and len_ymd == 2): # One member, or two members with a month string if mstridx != -1: res.month = ymd[mstridx] del ymd[mstridx] - if len_ymd > 1 or mstridx == -1: if ymd[0] > 31: res.year = ymd[0] else: res.day = ymd[0] - elif len_ymd == 2: # Two members with numbers if ymd[0] > 31: @@ -1059,8 +691,7 @@ def _parse(self, timestr, dayfirst=None, yearfirst=None, fuzzy=False, else: # 01-13 res.month, res.day = ymd - - elif len_ymd == 3: + if len_ymd == 3: # Three members if mstridx == 0: res.month, res.day, res.year = ymd @@ -1073,7 +704,6 @@ def _parse(self, timestr, dayfirst=None, yearfirst=None, fuzzy=False, # Give precendence to day-first, since # two-digit years is usually hand-written. res.day, res.month, res.year = ymd - elif mstridx == 2: # WTF!? if ymd[1] > 31: @@ -1082,7 +712,6 @@ def _parse(self, timestr, dayfirst=None, yearfirst=None, fuzzy=False, else: # 99-01-Jan res.year, res.day, res.month = ymd - else: if ymd[0] > 31 or \ (yearfirst and ymd[1] <= 12 and ymd[2] <= 31): @@ -1096,112 +725,23 @@ def _parse(self, timestr, dayfirst=None, yearfirst=None, fuzzy=False, res.month, res.day, res.year = ymd except (IndexError, ValueError, AssertionError): - return None, None + return None if not info.validate(res): - return None, None + return None if fuzzy_with_tokens: return res, tuple(skipped_tokens) - else: - return res, None - -DEFAULTPARSER = parser() + return res, None +DEFAULTPARSER = parser() def parse(timestr, parserinfo=None, **kwargs): - """ - - Parse a string in one of the supported formats, using the - ``parserinfo`` parameters. - - :param timestr: - A string containing a date/time stamp. - - :param parserinfo: - A :class:`parserinfo` object containing parameters for the parser. - If ``None``, the default arguments to the :class:`parserinfo` - constructor are used. - - The ``**kwargs`` parameter takes the following keyword arguments: - - :param default: - The default datetime object, if this is a datetime object and not - ``None``, elements specified in ``timestr`` replace elements in the - default object. - - :param ignoretz: - If set ``True``, time zones in parsed strings are ignored and a naive - :class:`datetime` object is returned. - - :param tzinfos: - Additional time zone names / aliases which may be present in the - string. This argument maps time zone names (and optionally offsets - from those time zones) to time zones. This parameter can be a - dictionary with timezone aliases mapping time zone names to time - zones or a function taking two parameters (``tzname`` and - ``tzoffset``) and returning a time zone. - - The timezones to which the names are mapped can be an integer - offset from UTC in minutes or a :class:`tzinfo` object. - - .. doctest:: - - >>> from dateutil.parser import parse - >>> from dateutil.tz import gettz - >>> tzinfos = {"BRST": -10800, "CST": gettz("America/Chicago")} - >>> parse("2012-01-19 17:21:00 BRST", tzinfos=tzinfos) - datetime.datetime(2014, 2, 19, 17, 21, tzinfo=tzoffset(u'BRST', -10800)) - >>> parse("2012-01-19 17:21:00 CST", tzinfos=tzinfos) - datetime.datetime(2014, 2, 19, 17, 21, tzinfo=tzfile('America/Chicago')) - - This parameter is ignored if ``ignoretz`` is set. - - :param dayfirst: - Whether to interpret the first value in an ambiguous 3-integer date - (e.g. 01/05/09) as the day (``True``) or month (``False``). If - ``yearfirst`` is set to ``True``, this distinguishes between YDM and - YMD. If set to ``None``, this value is retrieved from the current - :class:`parserinfo` object (which itself defaults to ``False``). - - :param yearfirst: - Whether to interpret the first value in an ambiguous 3-integer date - (e.g. 01/05/09) as the year. If ``True``, the first number is taken to - be the year, otherwise the last number is taken to be the year. If - this is set to ``None``, the value is retrieved from the current - :class:`parserinfo` object (which itself defaults to ``False``). - - :param fuzzy: - Whether to allow fuzzy parsing, allowing for string like "Today is - January 1, 2047 at 8:21:00AM". - - :param fuzzy_with_tokens: - If ``True``, ``fuzzy`` is automatically set to True, and the parser - will return a tuple where the first element is the parsed - :class:`datetime.datetime` datetimestamp and the second element is - a tuple containing the portions of the string which were ignored: - - .. doctest:: - - >>> from dateutil.parser import parse - >>> parse("Today is January 1, 2047 at 8:21:00AM", fuzzy_with_tokens=True) - (datetime.datetime(2011, 1, 1, 8, 21), (u'Today is ', u' ', u'at ')) - - :return: - Returns a :class:`datetime.datetime` object or, if the - ``fuzzy_with_tokens`` option is ``True``, returns a tuple, the - first element being a :class:`datetime.datetime` object, the second - a tuple containing the fuzzy tokens. - - :raises ValueError: - Raised for invalid or unknown string format, if the provided - :class:`tzinfo` is not in a valid format, or if an invalid date - would be created. - - :raises OverFlowError: - Raised if the parsed date exceeds the largest valid C integer on - your system. - """ + # Python 2.x support: datetimes return their string presentation as + # bytes in 2.x and unicode in 3.x, so it's reasonable to expect that + # the parser will get both kinds. Internally we use unicode only. + if isinstance(timestr, binary_type): + timestr = timestr.decode() if parserinfo: return parser(parserinfo).parse(timestr, **kwargs) else: @@ -1239,7 +779,7 @@ def parse(self, tzstr): # BRST+3[BRDT[+2]] j = i while j < len_l and not [x for x in l[j] - if x in "0123456789:,-+"]: + if x in "0123456789:,-+"]: j += 1 if j != i: if not res.stdabbr: @@ -1249,8 +789,8 @@ def parse(self, tzstr): offattr = "dstoffset" res.dstabbr = "".join(l[i:j]) i = j - if (i < len_l and (l[i] in ('+', '-') or l[i][0] in - "0123456789")): + if (i < len_l and + (l[i] in ('+', '-') or l[i][0] in "0123456789")): if l[i] in ('+', '-'): # Yes, that's right. See the TZ variable # documentation. @@ -1261,8 +801,8 @@ def parse(self, tzstr): len_li = len(l[i]) if len_li == 4: # -0300 - setattr(res, offattr, (int(l[i][:2])*3600 + - int(l[i][2:])*60)*signal) + setattr(res, offattr, + (int(l[i][:2])*3600+int(l[i][2:])*60)*signal) elif i+1 < len_l and l[i+1] == ':': # -03:00 setattr(res, offattr, @@ -1282,8 +822,7 @@ def parse(self, tzstr): if i < len_l: for j in range(i, len_l): - if l[j] == ';': - l[j] = ',' + if l[j] == ';': l[j] = ',' assert l[i] == ',' @@ -1292,7 +831,7 @@ def parse(self, tzstr): if i >= len_l: pass elif (8 <= l.count(',') <= 9 and - not [y for x in l[i:] if x != ',' + not [y for x in l[i:] if x != ',' for y in x if y not in "0123456789"]): # GMT0BST,3,0,30,3600,10,0,26,7200[,3600] for x in (res.start, res.end): @@ -1306,7 +845,7 @@ def parse(self, tzstr): i += 2 if value: x.week = value - x.weekday = (int(l[i])-1) % 7 + x.weekday = (int(l[i])-1)%7 else: x.day = int(l[i]) i += 2 @@ -1322,7 +861,7 @@ def parse(self, tzstr): elif (l.count(',') == 2 and l[i:].count('/') <= 2 and not [y for x in l[i:] if x not in (',', '/', 'J', 'M', '.', '-', ':') - for y in x if y not in "0123456789"]): + for y in x if y not in "0123456789"]): for x in (res.start, res.end): if l[i] == 'J': # non-leap year day (1 based) @@ -1341,7 +880,7 @@ def parse(self, tzstr): i += 1 assert l[i] in ('-', '.') i += 1 - x.weekday = (int(l[i])-1) % 7 + x.weekday = (int(l[i])-1)%7 else: # year day (zero based) x.yday = int(l[i])+1 @@ -1382,8 +921,6 @@ def parse(self, tzstr): DEFAULTTZPARSER = _tzparser() - - def _parsetz(tzstr): return DEFAULTTZPARSER.parse(tzstr) diff --git a/dateutil/relativedelta.py b/dateutil/relativedelta.py index 557d521..4393bcb 100644 --- a/dateutil/relativedelta.py +++ b/dateutil/relativedelta.py @@ -1,4 +1,11 @@ -# -*- coding: utf-8 -*- +""" +Copyright (c) 2003-2010 Gustavo Niemeyer + +This module offers extensions to the standard Python +datetime module. +""" +__license__ = "Simplified BSD" + import datetime import calendar @@ -6,7 +13,6 @@ __all__ = ["relativedelta", "MO", "TU", "WE", "TH", "FR", "SA", "SU"] - class weekday(object): __slots__ = ["weekday", "n"] @@ -37,35 +43,25 @@ def __repr__(self): MO, TU, WE, TH, FR, SA, SU = weekdays = tuple([weekday(x) for x in range(7)]) - class relativedelta(object): """ -The relativedelta type is based on the specification of the excellent -work done by M.-A. Lemburg in his -`mx.DateTime `_ extension. -However, notice that this type does *NOT* implement the same algorithm as +The relativedelta type is based on the specification of the excelent +work done by M.-A. Lemburg in his mx.DateTime extension. However, +notice that this type does *NOT* implement the same algorithm as his work. Do *NOT* expect it to behave like mx.DateTime's counterpart. -There are two different ways to build a relativedelta instance. The -first one is passing it two date/datetime classes:: +There's two different ways to build a relativedelta instance. The +first one is passing it two date/datetime classes: relativedelta(datetime1, datetime2) -The second one is passing it any number of the following keyword arguments:: - - relativedelta(arg1=x,arg2=y,arg3=z...) +And the other way is to use the following keyword arguments: year, month, day, hour, minute, second, microsecond: - Absolute information (argument is singular); adding or subtracting a - relativedelta with absolute information does not perform an aritmetic - operation, but rather REPLACES the corresponding value in the - original datetime with the value(s) in relativedelta. + Absolute information. years, months, weeks, days, hours, minutes, seconds, microseconds: - Relative information, may be negative (argument is plural); adding - or subtracting a relativedelta with relative information performs - the corresponding aritmetic operation on the original datetime value - with the information in the relativedelta. + Relative information, may be negative. weekday: One of the weekday instances (MO, TU, etc). These instances may @@ -84,26 +80,26 @@ class relativedelta(object): Here is the behavior of operations with relativedelta: -1. Calculate the absolute year, using the 'year' argument, or the +1) Calculate the absolute year, using the 'year' argument, or the original datetime year, if the argument is not present. -2. Add the relative 'years' argument to the absolute year. +2) Add the relative 'years' argument to the absolute year. -3. Do steps 1 and 2 for month/months. +3) Do steps 1 and 2 for month/months. -4. Calculate the absolute day, using the 'day' argument, or the +4) Calculate the absolute day, using the 'day' argument, or the original datetime day, if the argument is not present. Then, subtract from the day until it fits in the year and month found after their operations. -5. Add the relative 'days' argument to the absolute day. Notice +5) Add the relative 'days' argument to the absolute day. Notice that the 'weeks' argument is multiplied by 7 and added to 'days'. -6. Do steps 1 and 2 for hour/hours, minute/minutes, second/seconds, +6) Do steps 1 and 2 for hour/hours, minute/minutes, second/seconds, microsecond/microseconds. -7. If the 'weekday' argument is present, calculate the weekday, +7) If the 'weekday' argument is present, calculate the weekday, with the given (wday, nth) tuple. wday is the index of the weekday (0-6, 0=Mon), and nth is the number of weeks to add forward or backward, depending on its signal. Notice that if @@ -118,14 +114,9 @@ def __init__(self, dt1=None, dt2=None, yearday=None, nlyearday=None, hour=None, minute=None, second=None, microsecond=None): if dt1 and dt2: - # datetime is a subclass of date. So both must be date - if not (isinstance(dt1, datetime.date) and - isinstance(dt2, datetime.date)): + if (not isinstance(dt1, datetime.date)) or (not isinstance(dt2, datetime.date)): raise TypeError("relativedelta only diffs datetime/date") - # We allow two dates, or two datetimes, so we coerce them to be - # of the same type - if (isinstance(dt1, datetime.datetime) != - isinstance(dt2, datetime.datetime)): + if not type(dt1) == type(dt2): #isinstance(dt1, type(dt2)): if not isinstance(dt1, datetime.datetime): dt1 = datetime.datetime.fromordinal(dt1.toordinal()) elif not isinstance(dt2, datetime.datetime): @@ -167,7 +158,7 @@ def __init__(self, dt1=None, dt2=None, else: self.years = years self.months = months - self.days = days + weeks * 7 + self.days = days+weeks*7 self.leapdays = leapdays self.hours = hours self.minutes = minutes @@ -194,8 +185,7 @@ def __init__(self, dt1=None, dt2=None, if yearday > 59: self.leapdays = -1 if yday: - ydayidx = [31, 59, 90, 120, 151, 181, 212, - 243, 273, 304, 334, 366] + ydayidx = [31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 366] for idx, ydays in enumerate(ydayidx): if yday <= ydays: self.month = idx+1 @@ -235,20 +225,13 @@ def _fix(self): div, mod = divmod(self.months*s, 12) self.months = mod*s self.years += div*s - if (self.hours or self.minutes or self.seconds or self.microseconds - or self.hour is not None or self.minute is not None or - self.second is not None or self.microsecond is not None): + if (self.hours or self.minutes or self.seconds or self.microseconds or + self.hour is not None or self.minute is not None or + self.second is not None or self.microsecond is not None): self._has_time = 1 else: self._has_time = 0 - @property - def weeks(self): - return self.days // 7 - @weeks.setter - def weeks(self, value): - self.days = self.days - (self.weeks * 7) + value*7 - def _set_months(self, months): self.months = months if abs(self.months) > 11: @@ -261,24 +244,22 @@ def _set_months(self, months): def __add__(self, other): if isinstance(other, relativedelta): - return self.__class__(years=other.years+self.years, - months=other.months+self.months, - days=other.days+self.days, - hours=other.hours+self.hours, - minutes=other.minutes+self.minutes, - seconds=other.seconds+self.seconds, - microseconds=(other.microseconds + - self.microseconds), - leapdays=other.leapdays or self.leapdays, - year=other.year or self.year, - month=other.month or self.month, - day=other.day or self.day, - weekday=other.weekday or self.weekday, - hour=other.hour or self.hour, - minute=other.minute or self.minute, - second=other.second or self.second, - microsecond=(other.microsecond or - self.microsecond)) + return relativedelta(years=other.years+self.years, + months=other.months+self.months, + days=other.days+self.days, + hours=other.hours+self.hours, + minutes=other.minutes+self.minutes, + seconds=other.seconds+self.seconds, + microseconds=other.microseconds+self.microseconds, + leapdays=other.leapdays or self.leapdays, + year=other.year or self.year, + month=other.month or self.month, + day=other.day or self.day, + weekday=other.weekday or self.weekday, + hour=other.hour or self.hour, + minute=other.minute or self.minute, + second=other.second or self.second, + microsecond=other.microsecond or self.microsecond) if not isinstance(other, datetime.date): raise TypeError("unsupported type for add operation") elif self._has_time and not isinstance(other, datetime.datetime): @@ -314,9 +295,9 @@ def __add__(self, other): weekday, nth = self.weekday.weekday, self.weekday.n or 1 jumpdays = (abs(nth)-1)*7 if nth > 0: - jumpdays += (7-ret.weekday()+weekday) % 7 + jumpdays += (7-ret.weekday()+weekday)%7 else: - jumpdays += (ret.weekday()-weekday) % 7 + jumpdays += (ret.weekday()-weekday)%7 jumpdays *= -1 ret += datetime.timedelta(days=jumpdays) return ret @@ -330,7 +311,7 @@ def __rsub__(self, other): def __sub__(self, other): if not isinstance(other, relativedelta): raise TypeError("unsupported type for sub operation") - return self.__class__(years=self.years-other.years, + return relativedelta(years=self.years-other.years, months=self.months-other.months, days=self.days-other.days, hours=self.hours-other.hours, @@ -348,7 +329,7 @@ def __sub__(self, other): microsecond=self.microsecond or other.microsecond) def __neg__(self): - return self.__class__(years=-self.years, + return relativedelta(years=-self.years, months=-self.months, days=-self.days, hours=-self.hours, @@ -382,12 +363,10 @@ def __bool__(self): self.minute is None and self.second is None and self.microsecond is None) - # Compatibility with Python 2.x - __nonzero__ = __bool__ def __mul__(self, other): f = float(other) - return self.__class__(years=int(self.years*f), + return relativedelta(years=int(self.years*f), months=int(self.months*f), days=int(self.days*f), hours=int(self.hours*f), diff --git a/dateutil/rrule.py b/dateutil/rrule.py index 57b034a..ad4d3ba 100644 --- a/dateutil/rrule.py +++ b/dateutil/rrule.py @@ -1,19 +1,21 @@ -# -*- coding: utf-8 -*- """ -The rrule module offers a small, complete, and very fast, implementation of -the recurrence rules documented in the -`iCalendar RFC `_, -including support for caching of results. +Copyright (c) 2003-2010 Gustavo Niemeyer + +This module offers extensions to the standard Python +datetime module. """ +__license__ = "Simplified BSD" + import itertools import datetime import calendar +try: + import _thread +except ImportError: + import thread as _thread import sys -from fractions import gcd - from six import advance_iterator, integer_types -from six.moves import _thread __all__ = ["rrule", "rruleset", "rrulestr", "YEARLY", "MONTHLY", "WEEKLY", "DAILY", @@ -21,7 +23,7 @@ "MO", "TU", "WE", "TH", "FR", "SA", "SU"] # Every mask is 7 days longer to handle cross-year weekly periods. -M366MASK = tuple([1]*31+[2]*29+[3]*31+[4]*30+[5]*31+[6]*30 + +M366MASK = tuple([1]*31+[2]*29+[3]*31+[4]*30+[5]*31+[6]*30+ [7]*31+[8]*31+[9]*30+[10]*31+[11]*30+[12]*31+[1]*7) M365MASK = list(M366MASK) M29, M30, M31 = list(range(1, 30)), list(range(1, 31)), list(range(1, 32)) @@ -37,8 +39,6 @@ MDAY365MASK = tuple(MDAY365MASK) M365MASK = tuple(M365MASK) -FREQNAMES = ['YEARLY','MONTHLY','WEEKLY','DAILY','HOURLY','MINUTELY','SECONDLY'] - (YEARLY, MONTHLY, WEEKLY, @@ -51,7 +51,6 @@ easter = None parser = None - class weekday(object): __slots__ = ["weekday", "n"] @@ -84,13 +83,12 @@ def __repr__(self): MO, TU, WE, TH, FR, SA, SU = weekdays = tuple([weekday(x) for x in range(7)]) - class rrulebase(object): def __init__(self, cache=False): if cache: self._cache = [] self._cache_lock = _thread.allocate_lock() - self._cache_gen = self._iter() + self._cache_gen = self._iter() self._cache_complete = False else: self._cache = None @@ -165,17 +163,11 @@ def __contains__(self, item): # __len__() introduces a large performance penality. def count(self): - """ Returns the number of recurrences in this set. It will have go - trough the whole recurrence, if this hasn't been done before. """ if self._len is None: - for x in self: - pass + for x in self: pass return self._len def before(self, dt, inc=False): - """ Returns the last recurrence before the given datetime instance. The - inc keyword defines what happens if dt is an occurrence. With - inc=True, if dt itself is an occurrence, it will be returned. """ if self._cache_complete: gen = self._cache else: @@ -194,9 +186,6 @@ def before(self, dt, inc=False): return last def after(self, dt, inc=False): - """ Returns the first recurrence after the given datetime instance. The - inc keyword defines what happens if dt is an occurrence. With - inc=True, if dt itself is an occurrence, it will be returned. """ if self._cache_complete: gen = self._cache else: @@ -211,52 +200,7 @@ def after(self, dt, inc=False): return i return None - def xafter(self, dt, count=None, inc=False): - """ - Generator which yields up to `count` recurrences after the given - datetime instance, equivalent to `after`. - - :param dt: - The datetime at which to start generating recurrences. - - :param count: - The maximum number of recurrences to generate. If `None` (default), - dates are generated until the recurrence rule is exhausted. - - :param inc: - If `dt` is an instance of the rule and `inc` is `True`, it is - included in the output. - - :yields: Yields a sequence of `datetime` objects. - """ - - if self._cache_complete: - gen = self._cache - else: - gen = self - - # Select the comparison function - if inc: - comp = lambda dc, dtc: dc >= dtc - else: - comp = lambda dc, dtc: dc > dtc - - # Generate dates - n = 0 - for d in gen: - if comp(d, dt): - yield d - - if count is not None: - n += 1 - if n >= count: - break - - def between(self, after, before, inc=False, count=1): - """ Returns all the occurrences of the rrule between after and before. - The inc keyword defines what happens if after and/or before are - themselves occurrences. With inc=True, they will be included in the - list, if they are found in the recurrence set. """ + def between(self, after, before, inc=False): if self._cache_complete: gen = self._cache else: @@ -285,93 +229,7 @@ def between(self, after, before, inc=False, count=1): l.append(i) return l - class rrule(rrulebase): - """ - That's the base of the rrule operation. It accepts all the keywords - defined in the RFC as its constructor parameters (except byday, - which was renamed to byweekday) and more. The constructor prototype is:: - - rrule(freq) - - Where freq must be one of YEARLY, MONTHLY, WEEKLY, DAILY, HOURLY, MINUTELY, - or SECONDLY. - - Additionally, it supports the following keyword arguments: - - :param cache: - If given, it must be a boolean value specifying to enable or disable - caching of results. If you will use the same rrule instance multiple - times, enabling caching will improve the performance considerably. - :param dtstart: - The recurrence start. Besides being the base for the recurrence, - missing parameters in the final recurrence instances will also be - extracted from this date. If not given, datetime.now() will be used - instead. - :param interval: - The interval between each freq iteration. For example, when using - YEARLY, an interval of 2 means once every two years, but with HOURLY, - it means once every two hours. The default interval is 1. - :param wkst: - The week start day. Must be one of the MO, TU, WE constants, or an - integer, specifying the first day of the week. This will affect - recurrences based on weekly periods. The default week start is got - from calendar.firstweekday(), and may be modified by - calendar.setfirstweekday(). - :param count: - How many occurrences will be generated. - :param until: - If given, this must be a datetime instance, that will specify the - limit of the recurrence. If a recurrence instance happens to be the - same as the datetime instance given in the until keyword, this will - be the last occurrence. - :param bysetpos: - If given, it must be either an integer, or a sequence of integers, - positive or negative. Each given integer will specify an occurrence - number, corresponding to the nth occurrence of the rule inside the - frequency period. For example, a bysetpos of -1 if combined with a - MONTHLY frequency, and a byweekday of (MO, TU, WE, TH, FR), will - result in the last work day of every month. - :param bymonth: - If given, it must be either an integer, or a sequence of integers, - meaning the months to apply the recurrence to. - :param bymonthday: - If given, it must be either an integer, or a sequence of integers, - meaning the month days to apply the recurrence to. - :param byyearday: - If given, it must be either an integer, or a sequence of integers, - meaning the year days to apply the recurrence to. - :param byweekno: - If given, it must be either an integer, or a sequence of integers, - meaning the week numbers to apply the recurrence to. Week numbers - have the meaning described in ISO8601, that is, the first week of - the year is that containing at least four days of the new year. - :param byweekday: - If given, it must be either an integer (0 == MO), a sequence of - integers, one of the weekday constants (MO, TU, etc), or a sequence - of these constants. When given, these variables will define the - weekdays where the recurrence will be applied. It's also possible to - use an argument n for the weekday instances, which will mean the nth - occurrence of this weekday in the period. For example, with MONTHLY, - or with YEARLY and BYMONTH, using FR(+1) in byweekday will specify the - first friday of the month where the recurrence happens. Notice that in - the RFC documentation, this is specified as BYDAY, but was renamed to - avoid the ambiguity of that keyword. - :param byhour: - If given, it must be either an integer, or a sequence of integers, - meaning the hours to apply the recurrence to. - :param byminute: - If given, it must be either an integer, or a sequence of integers, - meaning the minutes to apply the recurrence to. - :param bysecond: - If given, it must be either an integer, or a sequence of integers, - meaning the seconds to apply the recurrence to. - :param byeaster: - If given, it must be either an integer, or a sequence of integers, - positive or negative. Each integer will define an offset from the - Easter Sunday. Passing the offset 0 to byeaster will yield the Easter - Sunday itself. This is an extension to the RFC specification. - """ def __init__(self, freq, dtstart=None, interval=1, wkst=None, count=None, until=None, bysetpos=None, bymonth=None, bymonthday=None, byyearday=None, byeaster=None, @@ -391,24 +249,15 @@ def __init__(self, freq, dtstart=None, self._freq = freq self._interval = interval self._count = count - - # Cache the original byxxx rules, if they are provided, as the _byxxx - # attributes do not necessarily map to the inputs, and this can be - # a problem in generating the strings. Only store things if they've - # been supplied (the string retrieval will just use .get()) - self._original_rule = {} - if until and not isinstance(until, datetime.datetime): until = datetime.datetime.fromordinal(until.toordinal()) self._until = until - if wkst is None: self._wkst = calendar.firstweekday() elif isinstance(wkst, integer_types): self._wkst = wkst else: self._wkst = wkst.weekday - if bysetpos is None: self._bysetpos = None elif isinstance(bysetpos, integer_types): @@ -422,47 +271,30 @@ def __init__(self, freq, dtstart=None, if pos == 0 or not (-366 <= pos <= 366): raise ValueError("bysetpos must be between 1 and 366, " "or between -366 and -1") - - if self._bysetpos: - self._original_rule['bysetpos'] = self._bysetpos - - if (byweekno is None and byyearday is None and bymonthday is None and - byweekday is None and byeaster is None): + if not (byweekno or byyearday or bymonthday or + byweekday is not None or byeaster is not None): if freq == YEARLY: - if bymonth is None: + if not bymonth: bymonth = dtstart.month - self._original_rule['bymonth'] = None bymonthday = dtstart.day - self._original_rule['bymonthday'] = None elif freq == MONTHLY: bymonthday = dtstart.day - self._original_rule['bymonthday'] = None elif freq == WEEKLY: byweekday = dtstart.weekday() - self._original_rule['byweekday'] = None - # bymonth - if bymonth is None: + if not bymonth: self._bymonth = None + elif isinstance(bymonth, integer_types): + self._bymonth = (bymonth,) else: - if isinstance(bymonth, integer_types): - bymonth = (bymonth,) - - self._bymonth = tuple(sorted(set(bymonth))) - - if 'bymonth' not in self._original_rule: - self._original_rule['bymonth'] = self._bymonth - + self._bymonth = tuple(bymonth) # byyearday - if byyearday is None: + if not byyearday: self._byyearday = None + elif isinstance(byyearday, integer_types): + self._byyearday = (byyearday,) else: - if isinstance(byyearday, integer_types): - byyearday = (byyearday,) - - self._byyearday = tuple(sorted(set(byyearday))) - self._original_rule['byyearday'] = self._byyearday - + self._byyearday = tuple(byyearday) # byeaster if byeaster is not None: if not easter: @@ -470,144 +302,90 @@ def __init__(self, freq, dtstart=None, if isinstance(byeaster, integer_types): self._byeaster = (byeaster,) else: - self._byeaster = tuple(sorted(byeaster)) - - self._original_rule['byeaster'] = self._byeaster + self._byeaster = tuple(byeaster) else: self._byeaster = None - - # bymonthday - if bymonthday is None: + # bymonthay + if not bymonthday: self._bymonthday = () self._bynmonthday = () + elif isinstance(bymonthday, integer_types): + if bymonthday < 0: + self._bynmonthday = (bymonthday,) + self._bymonthday = () + else: + self._bymonthday = (bymonthday,) + self._bynmonthday = () else: - if isinstance(bymonthday, integer_types): - bymonthday = (bymonthday,) - - bymonthday = set(bymonthday) # Ensure it's unique - - self._bymonthday = tuple(sorted([x for x in bymonthday if x > 0])) - self._bynmonthday = tuple(sorted([x for x in bymonthday if x < 0])) - - # Storing positive numbers first, then negative numbers - if 'bymonthday' not in self._original_rule: - self._original_rule['bymonthday'] = tuple( - itertools.chain(self._bymonthday, self._bynmonthday)) - + self._bymonthday = tuple([x for x in bymonthday if x > 0]) + self._bynmonthday = tuple([x for x in bymonthday if x < 0]) # byweekno if byweekno is None: self._byweekno = None + elif isinstance(byweekno, integer_types): + self._byweekno = (byweekno,) else: - if isinstance(byweekno, integer_types): - byweekno = (byweekno,) - - self._byweekno = tuple(sorted(set(byweekno))) - - self._original_rule['byweekno'] = self._byweekno - + self._byweekno = tuple(byweekno) # byweekday / bynweekday if byweekday is None: self._byweekday = None self._bynweekday = None + elif isinstance(byweekday, integer_types): + self._byweekday = (byweekday,) + self._bynweekday = None + elif hasattr(byweekday, "n"): + if not byweekday.n or freq > MONTHLY: + self._byweekday = (byweekday.weekday,) + self._bynweekday = None + else: + self._bynweekday = ((byweekday.weekday, byweekday.n),) + self._byweekday = None else: - # If it's one of the valid non-sequence types, convert to a - # single-element sequence before the iterator that builds the - # byweekday set. - if isinstance(byweekday, integer_types) or hasattr(byweekday, "n"): - byweekday = (byweekday,) - - self._byweekday = set() - self._bynweekday = set() + self._byweekday = [] + self._bynweekday = [] for wday in byweekday: if isinstance(wday, integer_types): - self._byweekday.add(wday) + self._byweekday.append(wday) elif not wday.n or freq > MONTHLY: - self._byweekday.add(wday.weekday) + self._byweekday.append(wday.weekday) else: - self._bynweekday.add((wday.weekday, wday.n)) - + self._bynweekday.append((wday.weekday, wday.n)) + self._byweekday = tuple(self._byweekday) + self._bynweekday = tuple(self._bynweekday) if not self._byweekday: self._byweekday = None elif not self._bynweekday: self._bynweekday = None - - if self._byweekday is not None: - self._byweekday = tuple(sorted(self._byweekday)) - orig_byweekday = [weekday(x) for x in self._byweekday] - else: - orig_byweekday = tuple() - - if self._bynweekday is not None: - self._bynweekday = tuple(sorted(self._bynweekday)) - orig_bynweekday = [weekday(*x) for x in self._bynweekday] - else: - orig_bynweekday = tuple() - - if 'byweekday' not in self._original_rule: - self._original_rule['byweekday'] = tuple(itertools.chain( - orig_byweekday, orig_bynweekday)) - # byhour if byhour is None: if freq < HOURLY: - self._byhour = set((dtstart.hour,)) + self._byhour = (dtstart.hour,) else: self._byhour = None + elif isinstance(byhour, integer_types): + self._byhour = (byhour,) else: - if isinstance(byhour, integer_types): - byhour = (byhour,) - - if freq == HOURLY: - self._byhour = self.__construct_byset(start=dtstart.hour, - byxxx=byhour, - base=24) - else: - self._byhour = set(byhour) - - self._byhour = tuple(sorted(self._byhour)) - self._original_rule['byhour'] = self._byhour - + self._byhour = tuple(byhour) # byminute if byminute is None: if freq < MINUTELY: - self._byminute = set((dtstart.minute,)) + self._byminute = (dtstart.minute,) else: self._byminute = None + elif isinstance(byminute, integer_types): + self._byminute = (byminute,) else: - if isinstance(byminute, integer_types): - byminute = (byminute,) - - if freq == MINUTELY: - self._byminute = self.__construct_byset(start=dtstart.minute, - byxxx=byminute, - base=60) - else: - self._byminute = set(byminute) - - self._byminute = tuple(sorted(self._byminute)) - self._original_rule['byminute'] = self._byminute - + self._byminute = tuple(byminute) # bysecond if bysecond is None: if freq < SECONDLY: - self._bysecond = ((dtstart.second,)) + self._bysecond = (dtstart.second,) else: self._bysecond = None + elif isinstance(bysecond, integer_types): + self._bysecond = (bysecond,) else: - if isinstance(bysecond, integer_types): - bysecond = (bysecond,) - - self._bysecond = set(bysecond) - - if freq == SECONDLY: - self._bysecond = self.__construct_byset(start=dtstart.second, - byxxx=bysecond, - base=60) - else: - self._bysecond = set(bysecond) - - self._bysecond = tuple(sorted(self._bysecond)) - self._original_rule['bysecond'] = self._bysecond + self._bysecond = tuple(bysecond) if self._freq >= HOURLY: self._timeset = None @@ -617,70 +395,11 @@ def __init__(self, freq, dtstart=None, for minute in self._byminute: for second in self._bysecond: self._timeset.append( - datetime.time(hour, minute, second, - tzinfo=self._tzinfo)) + datetime.time(hour, minute, second, + tzinfo=self._tzinfo)) self._timeset.sort() self._timeset = tuple(self._timeset) - def __str__(self): - """ - Output a string that would generate this RRULE if passed to rrulestr. - This is mostly compatible with RFC2445, except for the - dateutil-specific extension BYEASTER. - """ - - output = [] - h, m, s = [None] * 3 - if self._dtstart: - output.append(self._dtstart.strftime('DTSTART:%Y%m%dT%H%M%S')) - h, m, s = self._dtstart.timetuple()[3:6] - - parts = ['FREQ=' + FREQNAMES[self._freq]] - if self._interval != 1: - parts.append('INTERVAL=' + str(self._interval)) - - if self._wkst: - parts.append('WKST=' + str(self._wkst)) - - if self._count: - parts.append('COUNT=' + str(self._count)) - - if self._original_rule.get('byweekday') is not None: - # The str() method on weekday objects doesn't generate - # RFC2445-compliant strings, so we should modify that. - original_rule = dict(self._original_rule) - wday_strings = [] - for wday in original_rule['byweekday']: - if wday.n: - wday_strings.append('{n:+d}{wday}'.format( - n=wday.n, - wday=repr(wday)[0:2])) - else: - wday_strings.append(repr(wday)) - - original_rule['byweekday'] = wday_strings - else: - original_rule = self._original_rule - - partfmt = '{name}={vals}' - for name, key in [('BYSETPOS', 'bysetpos'), - ('BYMONTH', 'bymonth'), - ('BYMONTHDAY', 'bymonthday'), - ('BYYEARDAY', 'byyearday'), - ('BYWEEKNO', 'byweekno'), - ('BYDAY', 'byweekday'), - ('BYHOUR', 'byhour'), - ('BYMINUTE', 'byminute'), - ('BYSECOND', 'bysecond'), - ('BYEASTER', 'byeaster')]: - value = original_rule.get(key) - if value: - parts.append(partfmt.format(name=name, vals=(','.join(str(v) - for v in value)))) - - output.append(';'.join(parts)) - return '\n'.join(output) - def _iter(self): year, month, day, hour, minute, second, weekday, yearday, _ = \ self._dtstart.timetuple() @@ -705,20 +424,20 @@ def _iter(self): ii = _iterinfo(self) ii.rebuild(year, month) - getdayset = {YEARLY: ii.ydayset, - MONTHLY: ii.mdayset, - WEEKLY: ii.wdayset, - DAILY: ii.ddayset, - HOURLY: ii.ddayset, - MINUTELY: ii.ddayset, - SECONDLY: ii.ddayset}[freq] - + getdayset = {YEARLY:ii.ydayset, + MONTHLY:ii.mdayset, + WEEKLY:ii.wdayset, + DAILY:ii.ddayset, + HOURLY:ii.ddayset, + MINUTELY:ii.ddayset, + SECONDLY:ii.ddayset}[freq] + if freq < HOURLY: timeset = self._timeset else: - gettimeset = {HOURLY: ii.htimeset, - MINUTELY: ii.mtimeset, - SECONDLY: ii.stimeset}[freq] + gettimeset = {HOURLY:ii.htimeset, + MINUTELY:ii.mtimeset, + SECONDLY:ii.stimeset}[freq] if ((freq >= HOURLY and self._byhour and hour not in self._byhour) or (freq >= MINUTELY and @@ -747,10 +466,11 @@ def _iter(self): ii.mdaymask[i] not in bymonthday and ii.nmdaymask[i] not in bynmonthday) or (byyearday and - ((i < ii.yearlen and i+1 not in byyearday and - -ii.yearlen+i not in byyearday) or - (i >= ii.yearlen and i+1-ii.yearlen not in byyearday and - -ii.nextyearlen+i-ii.yearlen not in byyearday)))): + ((i < ii.yearlen and i+1 not in byyearday + and -ii.yearlen+i not in byyearday) or + (i >= ii.yearlen and i+1-ii.yearlen not in byyearday + and -ii.nextyearlen+i-ii.yearlen + not in byyearday)))): dayset[i] = None filtered = True @@ -764,7 +484,7 @@ def _iter(self): daypos, timepos = divmod(pos-1, len(timeset)) try: i = [x for x in dayset[start:end] - if x is not None][daypos] + if x is not None][daypos] time = timeset[timepos] except IndexError: pass @@ -789,7 +509,7 @@ def _iter(self): else: for i in dayset[start:end]: if i is not None: - date = datetime.date.fromordinal(ii.yearordinal + i) + date = datetime.date.fromordinal(ii.yearordinal+i) for time in timeset: res = datetime.datetime.combine(date, time) if until and res > until: @@ -839,86 +559,60 @@ def _iter(self): if filtered: # Jump to one iteration before next day hour += ((23-hour)//interval)*interval - - if byhour: - ndays, hour = self.__mod_distance(value=hour, - byxxx=self._byhour, - base=24) - else: - ndays, hour = divmod(hour+interval, 24) - - if ndays: - day += ndays - fixday = True - - timeset = gettimeset(hour, minute, second) - elif freq == MINUTELY: - if filtered: - # Jump to one iteration before next day - minute += ((1439-(hour*60+minute))//interval)*interval - - valid = False - rep_rate = (24*60) - for j in range(rep_rate // gcd(interval, rep_rate)): - if byminute: - nhours, minute = \ - self.__mod_distance(value=minute, - byxxx=self._byminute, - base=60) - else: - nhours, minute = divmod(minute+interval, 60) - - div, hour = divmod(hour+nhours, 24) + while True: + hour += interval + div, mod = divmod(hour, 24) if div: + hour = mod day += div fixday = True - filtered = False - if not byhour or hour in byhour: - valid = True break - - if not valid: - raise ValueError('Invalid combination of interval and ' + - 'byhour resulting in empty rule.') - timeset = gettimeset(hour, minute, second) - elif freq == SECONDLY: + elif freq == MINUTELY: if filtered: # Jump to one iteration before next day - second += (((86399 - (hour * 3600 + minute * 60 + second)) - // interval) * interval) - - rep_rate = (24 * 3600) - valid = False - for j in range(0, rep_rate // gcd(interval, rep_rate)): - if bysecond: - nminutes, second = \ - self.__mod_distance(value=second, - byxxx=self._bysecond, - base=60) - else: - nminutes, second = divmod(second+interval, 60) - - div, minute = divmod(minute+nminutes, 60) + minute += ((1439-(hour*60+minute))//interval)*interval + while True: + minute += interval + div, mod = divmod(minute, 60) if div: + minute = mod hour += div - div, hour = divmod(hour, 24) + div, mod = divmod(hour, 24) if div: + hour = mod day += div fixday = True - + filtered = False if ((not byhour or hour in byhour) and - (not byminute or minute in byminute) and - (not bysecond or second in bysecond)): - valid = True + (not byminute or minute in byminute)): + break + timeset = gettimeset(hour, minute, second) + elif freq == SECONDLY: + if filtered: + # Jump to one iteration before next day + second += (((86399-(hour*3600+minute*60+second)) + //interval)*interval) + while True: + second += self._interval + div, mod = divmod(second, 60) + if div: + second = mod + minute += div + div, mod = divmod(minute, 60) + if div: + minute = mod + hour += div + div, mod = divmod(hour, 24) + if div: + hour = mod + day += div + fixday = True + if ((not byhour or hour in byhour) and + (not byminute or minute in byminute) and + (not bysecond or second in bysecond)): break - - if not valid: - raise ValueError('Invalid combination of interval, ' + - 'byhour and byminute resulting in empty' + - ' rule.') - timeset = gettimeset(hour, minute, second) if fixday and day > 28: @@ -936,86 +630,6 @@ def _iter(self): daysinmonth = calendar.monthrange(year, month)[1] ii.rebuild(year, month) - def __construct_byset(self, start, byxxx, base): - """ - If a `BYXXX` sequence is passed to the constructor at the same level as - `FREQ` (e.g. `FREQ=HOURLY,BYHOUR={2,4,7},INTERVAL=3`), there are some - specifications which cannot be reached given some starting conditions. - - This occurs whenever the interval is not coprime with the base of a - given unit and the difference between the starting position and the - ending position is not coprime with the greatest common denominator - between the interval and the base. For example, with a FREQ of hourly - starting at 17:00 and an interval of 4, the only valid values for - BYHOUR would be {21, 1, 5, 9, 13, 17}, because 4 and 24 are not - coprime. - - :param start: - Specifies the starting position. - :param byxxx: - An iterable containing the list of allowed values. - :param base: - The largest allowable value for the specified frequency (e.g. - 24 hours, 60 minutes). - - This does not preserve the type of the iterable, returning a set, since - the values should be unique and the order is irrelevant, this will - speed up later lookups. - - In the event of an empty set, raises a :exception:`ValueError`, as this - results in an empty rrule. - """ - - cset = set() - - # Support a single byxxx value. - if isinstance(byxxx, integer_types): - byxxx = (byxxx, ) - - for num in byxxx: - i_gcd = gcd(self._interval, base) - # Use divmod rather than % because we need to wrap negative nums. - if i_gcd == 1 or divmod(num - start, i_gcd)[1] == 0: - cset.add(num) - - if len(cset) == 0: - raise ValueError("Invalid rrule byxxx generates an empty set.") - - return cset - - def __mod_distance(self, value, byxxx, base): - """ - Calculates the next value in a sequence where the `FREQ` parameter is - specified along with a `BYXXX` parameter at the same "level" - (e.g. `HOURLY` specified with `BYHOUR`). - - :param value: - The old value of the component. - :param byxxx: - The `BYXXX` set, which should have been generated by - `rrule._construct_byset`, or something else which checks that a - valid rule is present. - :param base: - The largest allowable value for the specified frequency (e.g. - 24 hours, 60 minutes). - - If a valid value is not found after `base` iterations (the maximum - number before the sequence would start to repeat), this raises a - :exception:`ValueError`, as no valid values were found. - - This returns a tuple of `divmod(n*interval, base)`, where `n` is the - smallest number of `interval` repetitions until the next specified - value in `byxxx` is found. - """ - accumulator = 0 - for ii in range(1, base + 1): - # Using divmod() over % to account for negative intervals - div, value = divmod(value + self._interval, base) - accumulator += div - if value in byxxx: - return (accumulator, value) - - class _iterinfo(object): __slots__ = ["rrule", "lastyear", "lastmonth", "yearlen", "nextyearlen", "yearordinal", "yearweekday", @@ -1031,8 +645,8 @@ def rebuild(self, year, month): # Every mask is 7 days longer to handle cross-year weekly periods. rr = self.rrule if year != self.lastyear: - self.yearlen = 365 + calendar.isleap(year) - self.nextyearlen = 365 + calendar.isleap(year + 1) + self.yearlen = 365+calendar.isleap(year) + self.nextyearlen = 365+calendar.isleap(year+1) firstyday = datetime.date(year, 1, 1) self.yearordinal = firstyday.toordinal() self.yearweekday = firstyday.weekday() @@ -1055,13 +669,13 @@ def rebuild(self, year, month): self.wnomask = None else: self.wnomask = [0]*(self.yearlen+7) - # no1wkst = firstwkst = self.wdaymask.index(rr._wkst) - no1wkst = firstwkst = (7-self.yearweekday+rr._wkst) % 7 + #no1wkst = firstwkst = self.wdaymask.index(rr._wkst) + no1wkst = firstwkst = (7-self.yearweekday+rr._wkst)%7 if no1wkst >= 4: no1wkst = 0 # Number of days in the year, plus the days we got # from last year. - wyearlen = self.yearlen+(self.yearweekday-rr._wkst) % 7 + wyearlen = self.yearlen+(self.yearweekday-rr._wkst)%7 else: # Number of days in the year, minus the days we # left in last year. @@ -1107,22 +721,22 @@ def rebuild(self, year, month): # this year. if -1 not in rr._byweekno: lyearweekday = datetime.date(year-1, 1, 1).weekday() - lno1wkst = (7-lyearweekday+rr._wkst) % 7 + lno1wkst = (7-lyearweekday+rr._wkst)%7 lyearlen = 365+calendar.isleap(year-1) if lno1wkst >= 4: lno1wkst = 0 - lnumweeks = 52+(lyearlen + - (lyearweekday-rr._wkst) % 7) % 7//4 + lnumweeks = 52+(lyearlen+ + (lyearweekday-rr._wkst)%7)%7//4 else: - lnumweeks = 52+(self.yearlen-no1wkst) % 7//4 + lnumweeks = 52+(self.yearlen-no1wkst)%7//4 else: lnumweeks = -1 if lnumweeks in rr._byweekno: for i in range(no1wkst): self.wnomask[i] = 1 - if (rr._bynweekday and (month != self.lastmonth or - year != self.lastyear)): + if (rr._bynweekday and + (month != self.lastmonth or year != self.lastyear)): ranges = [] if rr._freq == YEARLY: if rr._bymonth: @@ -1141,10 +755,10 @@ def rebuild(self, year, month): for wday, n in rr._bynweekday: if n < 0: i = last+(n+1)*7 - i -= (self.wdaymask[i]-wday) % 7 + i -= (self.wdaymask[i]-wday)%7 else: i = first+(n-1)*7 - i += (7-self.wdaymask[i]+wday) % 7 + i += (7-self.wdaymask[i]+wday)%7 if first <= i <= last: self.nwdaymask[i] = 1 @@ -1161,50 +775,50 @@ def ydayset(self, year, month, day): return list(range(self.yearlen)), 0, self.yearlen def mdayset(self, year, month, day): - dset = [None]*self.yearlen + set = [None]*self.yearlen start, end = self.mrange[month-1:month+1] for i in range(start, end): - dset[i] = i - return dset, start, end + set[i] = i + return set, start, end def wdayset(self, year, month, day): # We need to handle cross-year weeks here. - dset = [None]*(self.yearlen+7) + set = [None]*(self.yearlen+7) i = datetime.date(year, month, day).toordinal()-self.yearordinal start = i for j in range(7): - dset[i] = i + set[i] = i i += 1 - # if (not (0 <= i < self.yearlen) or + #if (not (0 <= i < self.yearlen) or # self.wdaymask[i] == self.rrule._wkst): # This will cross the year boundary, if necessary. if self.wdaymask[i] == self.rrule._wkst: break - return dset, start, i + return set, start, i def ddayset(self, year, month, day): - dset = [None] * self.yearlen - i = datetime.date(year, month, day).toordinal() - self.yearordinal - dset[i] = i - return dset, i, i + 1 + set = [None]*self.yearlen + i = datetime.date(year, month, day).toordinal()-self.yearordinal + set[i] = i + return set, i, i+1 def htimeset(self, hour, minute, second): - tset = [] + set = [] rr = self.rrule for minute in rr._byminute: for second in rr._bysecond: - tset.append(datetime.time(hour, minute, second, - tzinfo=rr._tzinfo)) - tset.sort() - return tset + set.append(datetime.time(hour, minute, second, + tzinfo=rr._tzinfo)) + set.sort() + return set def mtimeset(self, hour, minute, second): - tset = [] + set = [] rr = self.rrule for second in rr._bysecond: - tset.append(datetime.time(hour, minute, second, tzinfo=rr._tzinfo)) - tset.sort() - return tset + set.append(datetime.time(hour, minute, second, tzinfo=rr._tzinfo)) + set.sort() + return set def stimeset(self, hour, minute, second): return (datetime.time(hour, minute, second, @@ -1212,12 +826,6 @@ def stimeset(self, hour, minute, second): class rruleset(rrulebase): - """ The rruleset type allows more complex recurrence setups, mixing - multiple rules, dates, exclusion rules, and exclusion dates. The type - constructor takes the following keyword arguments: - - :param cache: If True, caching of results will be enabled, improving - performance of multiple queries considerably. """ class _genitem(object): def __init__(self, genlist, gen): @@ -1257,26 +865,15 @@ def __init__(self, cache=False): self._exdate = [] def rrule(self, rrule): - """ Include the given :py:class:`rrule` instance in the recurrence set - generation. """ self._rrule.append(rrule) def rdate(self, rdate): - """ Include the given :py:class:`datetime` instance in the recurrence - set generation. """ self._rdate.append(rdate) def exrule(self, exrule): - """ Include the given rrule instance in the recurrence set exclusion - list. Dates which are part of the given recurrence rules will not - be generated, even if some inclusive rrule or rdate matches them. - """ self._exrule.append(exrule) def exdate(self, exdate): - """ Include the given datetime instance in the recurrence set - exclusion list. Dates included that way will not be generated, - even if some inclusive rrule or rdate matches them. """ self._exdate.append(exdate) def _iter(self): @@ -1308,7 +905,6 @@ def _iter(self): rlist.sort() self._len = total - class _rrulestr(object): _freq_map = {"YEARLY": YEARLY, @@ -1319,8 +915,7 @@ class _rrulestr(object): "MINUTELY": MINUTELY, "SECONDLY": SECONDLY} - _weekday_map = {"MO": 0, "TU": 1, "WE": 2, "TH": 3, - "FR": 4, "SA": 5, "SU": 6} + _weekday_map = {"MO":0,"TU":1,"WE":2,"TH":3,"FR":4,"SA":5,"SU":6} def _handle_int(self, rrkwargs, name, value, **kwargs): rrkwargs[name.lower()] = int(value) @@ -1328,17 +923,17 @@ def _handle_int(self, rrkwargs, name, value, **kwargs): def _handle_int_list(self, rrkwargs, name, value, **kwargs): rrkwargs[name.lower()] = [int(x) for x in value.split(',')] - _handle_INTERVAL = _handle_int - _handle_COUNT = _handle_int - _handle_BYSETPOS = _handle_int_list - _handle_BYMONTH = _handle_int_list + _handle_INTERVAL = _handle_int + _handle_COUNT = _handle_int + _handle_BYSETPOS = _handle_int_list + _handle_BYMONTH = _handle_int_list _handle_BYMONTHDAY = _handle_int_list - _handle_BYYEARDAY = _handle_int_list - _handle_BYEASTER = _handle_int_list - _handle_BYWEEKNO = _handle_int_list - _handle_BYHOUR = _handle_int_list - _handle_BYMINUTE = _handle_int_list - _handle_BYSECOND = _handle_int_list + _handle_BYYEARDAY = _handle_int_list + _handle_BYEASTER = _handle_int_list + _handle_BYWEEKNO = _handle_int_list + _handle_BYHOUR = _handle_int_list + _handle_BYMINUTE = _handle_int_list + _handle_BYSECOND = _handle_int_list def _handle_FREQ(self, rrkwargs, name, value, **kwargs): rrkwargs["freq"] = self._freq_map[value] @@ -1349,34 +944,23 @@ def _handle_UNTIL(self, rrkwargs, name, value, **kwargs): from dateutil import parser try: rrkwargs["until"] = parser.parse(value, - ignoretz=kwargs.get("ignoretz"), - tzinfos=kwargs.get("tzinfos")) + ignoretz=kwargs.get("ignoretz"), + tzinfos=kwargs.get("tzinfos")) except ValueError: raise ValueError("invalid until date") def _handle_WKST(self, rrkwargs, name, value, **kwargs): rrkwargs["wkst"] = self._weekday_map[value] - def _handle_BYWEEKDAY(self, rrkwargs, name, value, **kwargs): - """ - Two ways to specify this: +1MO or MO(+1) - """ + def _handle_BYWEEKDAY(self, rrkwargs, name, value, **kwarsg): l = [] for wday in value.split(','): - if '(' in wday: - # If it's of the form TH(+1), etc. - splt = wday.split('(') - w = splt[0] - n = int(splt[1][:-1]) - else: - # If it's of the form +1MO - for i in range(len(wday)): - if wday[i] not in '+-0123456789': - break - n = wday[:i] or None - w = wday[i:] - if n: - n = int(n) + for i in range(len(wday)): + if wday[i] not in '+-0123456789': + break + n = wday[:i] or None + w = wday[i:] + if n: n = int(n) l.append(weekdays[self._weekday_map[w]](n)) rrkwargs["byweekday"] = l @@ -1437,8 +1021,8 @@ def _parse_rfc(self, s, i += 1 else: lines = s.split() - if (not forceset and len(lines) == 1 and (s.find(':') == -1 or - s.startswith('RRULE:'))): + if (not forceset and len(lines) == 1 and + (s.find(':') == -1 or s.startswith('RRULE:'))): return self._parse_rfc_rrule(lines[0], cache=cache, dtstart=dtstart, ignoretz=ignoretz, tzinfos=tzinfos) @@ -1487,32 +1071,32 @@ def _parse_rfc(self, s, tzinfos=tzinfos) else: raise ValueError("unsupported property: "+name) - if (forceset or len(rrulevals) > 1 or rdatevals - or exrulevals or exdatevals): + if (forceset or len(rrulevals) > 1 or + rdatevals or exrulevals or exdatevals): if not parser and (rdatevals or exdatevals): from dateutil import parser - rset = rruleset(cache=cache) + set = rruleset(cache=cache) for value in rrulevals: - rset.rrule(self._parse_rfc_rrule(value, dtstart=dtstart, - ignoretz=ignoretz, - tzinfos=tzinfos)) + set.rrule(self._parse_rfc_rrule(value, dtstart=dtstart, + ignoretz=ignoretz, + tzinfos=tzinfos)) for value in rdatevals: for datestr in value.split(','): - rset.rdate(parser.parse(datestr, - ignoretz=ignoretz, - tzinfos=tzinfos)) + set.rdate(parser.parse(datestr, + ignoretz=ignoretz, + tzinfos=tzinfos)) for value in exrulevals: - rset.exrule(self._parse_rfc_rrule(value, dtstart=dtstart, - ignoretz=ignoretz, - tzinfos=tzinfos)) + set.exrule(self._parse_rfc_rrule(value, dtstart=dtstart, + ignoretz=ignoretz, + tzinfos=tzinfos)) for value in exdatevals: for datestr in value.split(','): - rset.exdate(parser.parse(datestr, - ignoretz=ignoretz, - tzinfos=tzinfos)) + set.exdate(parser.parse(datestr, + ignoretz=ignoretz, + tzinfos=tzinfos)) if compatible and dtstart: - rset.rdate(dtstart) - return rset + set.rdate(dtstart) + return set else: return self._parse_rfc_rrule(rrulevals[0], dtstart=dtstart, diff --git a/dateutil/test/__init__.py b/dateutil/test/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/dateutil/tz.py b/dateutil/tz.py index 6625d98..e849fc2 100644 --- a/dateutil/tz.py +++ b/dateutil/tz.py @@ -1,25 +1,19 @@ -# -*- coding: utf-8 -*- """ -This module offers timezone implementations subclassing the abstract -:py:`datetime.tzinfo` type. There are classes to handle tzfile format files -(usually are in :file:`/etc/localtime`, :file:`/usr/share/zoneinfo`, etc), TZ -environment string (in all known formats), given ranges (with help from -relative deltas), local machine timezone, fixed offset timezone, and UTC -timezone. +Copyright (c) 2003-2007 Gustavo Niemeyer + +This module offers extensions to the standard Python +datetime module. """ +__license__ = "Simplified BSD" + +from six import string_types, PY3 + import datetime import struct import time import sys import os -from six import string_types, PY3 - -try: - from dateutil.tzwin import tzwin, tzwinlocal -except ImportError: - tzwin = tzwinlocal = None - relativedelta = None parser = None rrule = None @@ -27,31 +21,32 @@ __all__ = ["tzutc", "tzoffset", "tzlocal", "tzfile", "tzrange", "tzstr", "tzical", "tzwin", "tzwinlocal", "gettz"] +try: + from dateutil.tzwin import tzwin, tzwinlocal +except (ImportError, OSError): + tzwin, tzwinlocal = None, None -def tzname_in_python2(namefunc): +def tzname_in_python2(myfunc): """Change unicode output into bytestrings in Python 2 tzname() API changed in Python 3. It used to return bytes, but was changed to unicode strings """ - def adjust_encoding(*args, **kwargs): - name = namefunc(*args, **kwargs) - if name is not None and not PY3: - name = name.encode() - - return name - - return adjust_encoding + def inner_func(*args, **kwargs): + if PY3: + return myfunc(*args, **kwargs) + else: + return myfunc(*args, **kwargs).encode() + return inner_func ZERO = datetime.timedelta(0) EPOCHORDINAL = datetime.datetime.utcfromtimestamp(0).toordinal() - class tzutc(datetime.tzinfo): def utcoffset(self, dt): return ZERO - + def dst(self, dt): return ZERO @@ -71,7 +66,6 @@ def __repr__(self): __reduce__ = object.__reduce__ - class tzoffset(datetime.tzinfo): def __init__(self, name, offset): @@ -102,14 +96,13 @@ def __repr__(self): __reduce__ = object.__reduce__ - class tzlocal(datetime.tzinfo): - def __init__(self): - self._std_offset = datetime.timedelta(seconds=-time.timezone) - if time.daylight: - self._dst_offset = datetime.timedelta(seconds=-time.altzone) - else: - self._dst_offset = self._std_offset + + _std_offset = datetime.timedelta(seconds=-time.timezone) + if time.daylight: + _dst_offset = datetime.timedelta(seconds=-time.altzone) + else: + _dst_offset = _std_offset def utcoffset(self, dt): if self._isdst(dt): @@ -130,25 +123,25 @@ def tzname(self, dt): def _isdst(self, dt): # We can't use mktime here. It is unstable when deciding if # the hour near to a change is DST or not. - # + # # timestamp = time.mktime((dt.year, dt.month, dt.day, dt.hour, # dt.minute, dt.second, dt.weekday(), 0, -1)) # return time.localtime(timestamp).tm_isdst # # The code above yields the following result: # - # >>> import tz, datetime - # >>> t = tz.tzlocal() - # >>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname() - # 'BRDT' - # >>> datetime.datetime(2003,2,16,0,tzinfo=t).tzname() - # 'BRST' - # >>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname() - # 'BRST' - # >>> datetime.datetime(2003,2,15,22,tzinfo=t).tzname() - # 'BRDT' - # >>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname() - # 'BRDT' + #>>> import tz, datetime + #>>> t = tz.tzlocal() + #>>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname() + #'BRDT' + #>>> datetime.datetime(2003,2,16,0,tzinfo=t).tzname() + #'BRST' + #>>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname() + #'BRST' + #>>> datetime.datetime(2003,2,15,22,tzinfo=t).tzname() + #'BRDT' + #>>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname() + #'BRDT' # # Here is a more stable implementation: # @@ -173,7 +166,6 @@ def __repr__(self): __reduce__ = object.__reduce__ - class _ttinfo(object): __slots__ = ["offset", "delta", "isdst", "abbr", "isstd", "isgmt"] @@ -213,20 +205,15 @@ def __setstate__(self, state): if name in state: setattr(self, name, state[name]) - class tzfile(datetime.tzinfo): # http://www.twinsun.com/tz/tz-link.htm # ftp://ftp.iana.org/tz/tz*.tar.gz - - def __init__(self, fileobj, filename=None): - file_opened_here = False + + def __init__(self, fileobj): if isinstance(fileobj, string_types): self._filename = fileobj fileobj = open(fileobj, 'rb') - file_opened_here = True - elif filename is not None: - self._filename = filename elif hasattr(fileobj, "name"): self._filename = fileobj.name else: @@ -241,128 +228,125 @@ def __init__(self, fileobj, filename=None): # six four-byte values of type long, written in a # ``standard'' byte order (the high-order byte # of the value is written first). - try: - if fileobj.read(4).decode() != "TZif": - raise ValueError("magic not found") - fileobj.read(16) + if fileobj.read(4).decode() != "TZif": + raise ValueError("magic not found") - ( - # The number of UTC/local indicators stored in the file. - ttisgmtcnt, + fileobj.read(16) - # The number of standard/wall indicators stored in the file. - ttisstdcnt, + ( + # The number of UTC/local indicators stored in the file. + ttisgmtcnt, - # The number of leap seconds for which data is - # stored in the file. - leapcnt, + # The number of standard/wall indicators stored in the file. + ttisstdcnt, + + # The number of leap seconds for which data is + # stored in the file. + leapcnt, - # The number of "transition times" for which data - # is stored in the file. - timecnt, + # The number of "transition times" for which data + # is stored in the file. + timecnt, - # The number of "local time types" for which data - # is stored in the file (must not be zero). - typecnt, + # The number of "local time types" for which data + # is stored in the file (must not be zero). + typecnt, - # The number of characters of "time zone - # abbreviation strings" stored in the file. - charcnt, + # The number of characters of "time zone + # abbreviation strings" stored in the file. + charcnt, - ) = struct.unpack(">6l", fileobj.read(24)) + ) = struct.unpack(">6l", fileobj.read(24)) - # The above header is followed by tzh_timecnt four-byte - # values of type long, sorted in ascending order. - # These values are written in ``standard'' byte order. - # Each is used as a transition time (as returned by - # time(2)) at which the rules for computing local time - # change. + # The above header is followed by tzh_timecnt four-byte + # values of type long, sorted in ascending order. + # These values are written in ``standard'' byte order. + # Each is used as a transition time (as returned by + # time(2)) at which the rules for computing local time + # change. - if timecnt: - self._trans_list = struct.unpack(">%dl" % timecnt, - fileobj.read(timecnt*4)) - else: - self._trans_list = [] - - # Next come tzh_timecnt one-byte values of type unsigned - # char; each one tells which of the different types of - # ``local time'' types described in the file is associated - # with the same-indexed transition time. These values - # serve as indices into an array of ttinfo structures that - # appears next in the file. - - if timecnt: - self._trans_idx = struct.unpack(">%dB" % timecnt, - fileobj.read(timecnt)) - else: - self._trans_idx = [] - - # Each ttinfo structure is written as a four-byte value - # for tt_gmtoff of type long, in a standard byte - # order, followed by a one-byte value for tt_isdst - # and a one-byte value for tt_abbrind. In each - # structure, tt_gmtoff gives the number of - # seconds to be added to UTC, tt_isdst tells whether - # tm_isdst should be set by localtime(3), and - # tt_abbrind serves as an index into the array of - # time zone abbreviation characters that follow the - # ttinfo structure(s) in the file. - - ttinfo = [] - - for i in range(typecnt): - ttinfo.append(struct.unpack(">lbb", fileobj.read(6))) - - abbr = fileobj.read(charcnt).decode() - - # Then there are tzh_leapcnt pairs of four-byte - # values, written in standard byte order; the - # first value of each pair gives the time (as - # returned by time(2)) at which a leap second - # occurs; the second gives the total number of - # leap seconds to be applied after the given time. - # The pairs of values are sorted in ascending order - # by time. - - # Not used, for now - # if leapcnt: - # leap = struct.unpack(">%dl" % (leapcnt*2), - # fileobj.read(leapcnt*8)) - - # Then there are tzh_ttisstdcnt standard/wall - # indicators, each stored as a one-byte value; - # they tell whether the transition times associated - # with local time types were specified as standard - # time or wall clock time, and are used when - # a time zone file is used in handling POSIX-style - # time zone environment variables. - - if ttisstdcnt: - isstd = struct.unpack(">%db" % ttisstdcnt, - fileobj.read(ttisstdcnt)) - - # Finally, there are tzh_ttisgmtcnt UTC/local - # indicators, each stored as a one-byte value; - # they tell whether the transition times associated - # with local time types were specified as UTC or - # local time, and are used when a time zone file - # is used in handling POSIX-style time zone envi- - # ronment variables. - - if ttisgmtcnt: - isgmt = struct.unpack(">%db" % ttisgmtcnt, - fileobj.read(ttisgmtcnt)) - - # ** Everything has been read ** - finally: - if file_opened_here: - fileobj.close() + if timecnt: + self._trans_list = struct.unpack(">%dl" % timecnt, + fileobj.read(timecnt*4)) + else: + self._trans_list = [] + + # Next come tzh_timecnt one-byte values of type unsigned + # char; each one tells which of the different types of + # ``local time'' types described in the file is associated + # with the same-indexed transition time. These values + # serve as indices into an array of ttinfo structures that + # appears next in the file. + + if timecnt: + self._trans_idx = struct.unpack(">%dB" % timecnt, + fileobj.read(timecnt)) + else: + self._trans_idx = [] + + # Each ttinfo structure is written as a four-byte value + # for tt_gmtoff of type long, in a standard byte + # order, followed by a one-byte value for tt_isdst + # and a one-byte value for tt_abbrind. In each + # structure, tt_gmtoff gives the number of + # seconds to be added to UTC, tt_isdst tells whether + # tm_isdst should be set by localtime(3), and + # tt_abbrind serves as an index into the array of + # time zone abbreviation characters that follow the + # ttinfo structure(s) in the file. + + ttinfo = [] + + for i in range(typecnt): + ttinfo.append(struct.unpack(">lbb", fileobj.read(6))) + + abbr = fileobj.read(charcnt).decode() + + # Then there are tzh_leapcnt pairs of four-byte + # values, written in standard byte order; the + # first value of each pair gives the time (as + # returned by time(2)) at which a leap second + # occurs; the second gives the total number of + # leap seconds to be applied after the given time. + # The pairs of values are sorted in ascending order + # by time. + + # Not used, for now + if leapcnt: + leap = struct.unpack(">%dl" % (leapcnt*2), + fileobj.read(leapcnt*8)) + + # Then there are tzh_ttisstdcnt standard/wall + # indicators, each stored as a one-byte value; + # they tell whether the transition times associated + # with local time types were specified as standard + # time or wall clock time, and are used when + # a time zone file is used in handling POSIX-style + # time zone environment variables. + + if ttisstdcnt: + isstd = struct.unpack(">%db" % ttisstdcnt, + fileobj.read(ttisstdcnt)) + + # Finally, there are tzh_ttisgmtcnt UTC/local + # indicators, each stored as a one-byte value; + # they tell whether the transition times associated + # with local time types were specified as UTC or + # local time, and are used when a time zone file + # is used in handling POSIX-style time zone envi- + # ronment variables. + + if ttisgmtcnt: + isgmt = struct.unpack(">%db" % ttisgmtcnt, + fileobj.read(ttisgmtcnt)) + + # ** Everything has been read ** # Build ttinfo list self._ttinfo_list = [] for i in range(typecnt): - gmtoff, isdst, abbrind = ttinfo[i] + gmtoff, isdst, abbrind = ttinfo[i] # Round to full-minutes if that's not the case. Python's # datetime doesn't accept sub-minute timezones. Check # http://python.org/sf/1447945 for some information. @@ -480,7 +464,7 @@ def dst(self, dt): # However, this class stores historical changes in the # dst offset, so I belive that this wouldn't be the right # way to implement this. - + @tzname_in_python2 def tzname(self, dt): if not self._ttinfo_std: @@ -497,6 +481,7 @@ def __eq__(self, other): def __ne__(self, other): return not self.__eq__(other) + def __repr__(self): return "%s(%s)" % (self.__class__.__name__, repr(self._filename)) @@ -505,8 +490,8 @@ def __reduce__(self): raise ValueError("Unpickable %s class" % self.__class__.__name__) return (self.__class__, (self._filename,)) - class tzrange(datetime.tzinfo): + def __init__(self, stdabbr, stdoffset=None, dstabbr=None, dstoffset=None, start=None, end=None): @@ -527,12 +512,12 @@ def __init__(self, stdabbr, stdoffset=None, self._dst_offset = ZERO if dstabbr and start is None: self._start_delta = relativedelta.relativedelta( - hours=+2, month=4, day=1, weekday=relativedelta.SU(+1)) + hours=+2, month=4, day=1, weekday=relativedelta.SU(+1)) else: self._start_delta = start if dstabbr and end is None: self._end_delta = relativedelta.relativedelta( - hours=+1, month=10, day=31, weekday=relativedelta.SU(-1)) + hours=+1, month=10, day=31, weekday=relativedelta.SU(-1)) else: self._end_delta = end @@ -585,9 +570,8 @@ def __repr__(self): __reduce__ = object.__reduce__ - class tzstr(tzrange): - + def __init__(self, s): global parser if not parser: @@ -661,10 +645,9 @@ def _delta(self, x, isend=0): def __repr__(self): return "%s(%s)" % (self.__class__.__name__, repr(self._s)) - class _tzicalvtzcomp(object): def __init__(self, tzoffsetfrom, tzoffsetto, isdst, - tzname=None, rrule=None): + tzname=None, rrule=None): self.tzoffsetfrom = datetime.timedelta(seconds=tzoffsetfrom) self.tzoffsetto = datetime.timedelta(seconds=tzoffsetto) self.tzoffsetdiff = self.tzoffsetto-self.tzoffsetfrom @@ -672,7 +655,6 @@ def __init__(self, tzoffsetfrom, tzoffsetto, isdst, self.tzname = tzname self.rrule = rrule - class _tzicalvtz(datetime.tzinfo): def __init__(self, tzid, comps=[]): self._tzid = tzid @@ -736,7 +718,6 @@ def __repr__(self): __reduce__ = object.__reduce__ - class tzical(object): def __init__(self, fileobj): global rrule @@ -745,8 +726,7 @@ def __init__(self, fileobj): if isinstance(fileobj, string_types): self._s = fileobj - # ical should be encoded in UTF-8 with CRLF - fileobj = open(fileobj, 'r') + fileobj = open(fileobj, 'r') # ical should be encoded in UTF-8 with CRLF elif hasattr(fileobj, "name"): self._s = fileobj.name else: @@ -774,7 +754,7 @@ def _parse_offset(self, s): if not s: raise ValueError("empty offset") if s[0] in ('+', '-'): - signal = (-1, +1)[s[0] == '+'] + signal = (-1, +1)[s[0]=='+'] s = s[1:] else: signal = +1 @@ -835,8 +815,7 @@ def _parse_rfc(self, s): if not tzid: raise ValueError("mandatory TZID not found") if not comps: - raise ValueError( - "at least one component is needed") + raise ValueError("at least one component is needed") # Process vtimezone self._vtz[tzid] = _tzicalvtz(tzid, comps) invtz = False @@ -844,11 +823,9 @@ def _parse_rfc(self, s): if not founddtstart: raise ValueError("mandatory DTSTART not found") if tzoffsetfrom is None: - raise ValueError( - "mandatory TZOFFSETFROM not found") + raise ValueError("mandatory TZOFFSETFROM not found") if tzoffsetto is None: - raise ValueError( - "mandatory TZOFFSETFROM not found") + raise ValueError("mandatory TZOFFSETFROM not found") # Process component rr = None if rrulelines: @@ -871,18 +848,15 @@ def _parse_rfc(self, s): rrulelines.append(line) elif name == "TZOFFSETFROM": if parms: - raise ValueError( - "unsupported %s parm: %s " % (name, parms[0])) + raise ValueError("unsupported %s parm: %s "%(name, parms[0])) tzoffsetfrom = self._parse_offset(value) elif name == "TZOFFSETTO": if parms: - raise ValueError( - "unsupported TZOFFSETTO parm: "+parms[0]) + raise ValueError("unsupported TZOFFSETTO parm: "+parms[0]) tzoffsetto = self._parse_offset(value) elif name == "TZNAME": if parms: - raise ValueError( - "unsupported TZNAME parm: "+parms[0]) + raise ValueError("unsupported TZNAME parm: "+parms[0]) tzname = value elif name == "COMMENT": pass @@ -891,8 +865,7 @@ def _parse_rfc(self, s): else: if name == "TZID": if parms: - raise ValueError( - "unsupported TZID parm: "+parms[0]) + raise ValueError("unsupported TZID parm: "+parms[0]) tzid = value elif name in ("TZURL", "LAST-MODIFIED", "COMMENT"): pass @@ -913,7 +886,6 @@ def __repr__(self): TZFILES = [] TZPATHS = [] - def gettz(name=None): tz = None if not name: @@ -961,11 +933,11 @@ def gettz(name=None): pass else: tz = None - if tzwin is not None: + if tzwin: try: tz = tzwin(name) - except WindowsError: - tz = None + except OSError: + pass if not tz: from dateutil.zoneinfo import gettz tz = gettz(name) diff --git a/dateutil/tzwin.py b/dateutil/tzwin.py index f4e0e24..041c6cc 100644 --- a/dateutil/tzwin.py +++ b/dateutil/tzwin.py @@ -1,10 +1,8 @@ # This code was originally contributed by Jeffrey Harris. import datetime import struct +import winreg -from six.moves import winreg - -from .tz import tzname_in_python2 __all__ = ["tzwin", "tzwinlocal"] @@ -14,8 +12,8 @@ TZKEYNAME9X = r"SOFTWARE\Microsoft\Windows\CurrentVersion\Time Zones" TZLOCALKEYNAME = r"SYSTEM\CurrentControlSet\Control\TimeZoneInformation" - def _settzkeyname(): + global TZKEYNAME handle = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) try: winreg.OpenKey(handle, TZKEYNAMENT).Close() @@ -23,10 +21,8 @@ def _settzkeyname(): except WindowsError: TZKEYNAME = TZKEYNAME9X handle.Close() - return TZKEYNAME - -TZKEYNAME = _settzkeyname() +_settzkeyname() class tzwinbase(datetime.tzinfo): """tzinfo class based on win32's timezones available in the registry.""" @@ -43,8 +39,7 @@ def dst(self, dt): return datetime.timedelta(minutes=minutes) else: return datetime.timedelta(0) - - @tzname_in_python2 + def tzname(self, dt): if self._isdst(dt): return self._dstname @@ -64,11 +59,8 @@ def list(): def display(self): return self._display - + def _isdst(self, dt): - if not self._dstmonth: - # dstmonth == 0 signals the zone has no daylight saving time - return False dston = picknthweekday(dt.year, self._dstmonth, self._dstdayofweek, self._dsthour, self._dstminute, self._dstweeknumber) @@ -86,33 +78,31 @@ class tzwin(tzwinbase): def __init__(self, name): self._name = name - # multiple contexts only possible in 2.7 and 3.1, we still support 2.6 - with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle: - with winreg.OpenKey(handle, - "%s\%s" % (TZKEYNAME, name)) as tzkey: - keydict = valuestodict(tzkey) + handle = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) + tzkey = winreg.OpenKey(handle, "%s\%s" % (TZKEYNAME, name)) + keydict = valuestodict(tzkey) + tzkey.Close() + handle.Close() - self._stdname = keydict["Std"] - self._dstname = keydict["Dlt"] + self._stdname = keydict["Std"].encode("iso-8859-1") + self._dstname = keydict["Dlt"].encode("iso-8859-1") self._display = keydict["Display"] - + # See http://ww_winreg.jsiinc.com/SUBA/tip0300/rh0398.htm tup = struct.unpack("=3l16h", keydict["TZI"]) - self._stdoffset = -tup[0]-tup[1] # Bias + StandardBias * -1 - self._dstoffset = self._stdoffset-tup[2] # + DaylightBias * -1 - - # for the meaning see the win32 TIME_ZONE_INFORMATION structure docs - # http://msdn.microsoft.com/en-us/library/windows/desktop/ms725481(v=vs.85).aspx + self._stdoffset = -tup[0]-tup[1] # Bias + StandardBias * -1 + self._dstoffset = self._stdoffset-tup[2] # + DaylightBias * -1 + (self._stdmonth, - self._stddayofweek, # Sunday = 0 - self._stdweeknumber, # Last = 5 + self._stddayofweek, # Sunday = 0 + self._stdweeknumber, # Last = 5 self._stdhour, self._stdminute) = tup[4:9] (self._dstmonth, - self._dstdayofweek, # Sunday = 0 - self._dstweeknumber, # Last = 5 + self._dstdayofweek, # Sunday = 0 + self._dstweeknumber, # Last = 5 self._dsthour, self._dstminute) = tup[12:17] @@ -124,59 +114,61 @@ def __reduce__(self): class tzwinlocal(tzwinbase): - + def __init__(self): - with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle: + handle = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) - with winreg.OpenKey(handle, TZLOCALKEYNAME) as tzlocalkey: - keydict = valuestodict(tzlocalkey) + tzlocalkey = winreg.OpenKey(handle, TZLOCALKEYNAME) + keydict = valuestodict(tzlocalkey) + tzlocalkey.Close() - self._stdname = keydict["StandardName"] - self._dstname = keydict["DaylightName"] + self._stdname = keydict["StandardName"].encode("iso-8859-1") + self._dstname = keydict["DaylightName"].encode("iso-8859-1") - try: - with winreg.OpenKey( - handle, "%s\%s" % (TZKEYNAME, self._stdname)) as tzkey: - _keydict = valuestodict(tzkey) - self._display = _keydict["Display"] - except OSError: - self._display = None + try: + tzkey = winreg.OpenKey(handle, "%s\%s"%(TZKEYNAME, self._stdname)) + _keydict = valuestodict(tzkey) + self._display = _keydict["Display"] + tzkey.Close() + except OSError: + self._display = None + handle.Close() + self._stdoffset = -keydict["Bias"]-keydict["StandardBias"] self._dstoffset = self._stdoffset-keydict["DaylightBias"] + # See http://ww_winreg.jsiinc.com/SUBA/tip0300/rh0398.htm tup = struct.unpack("=8h", keydict["StandardStart"]) (self._stdmonth, - self._stddayofweek, # Sunday = 0 - self._stdweeknumber, # Last = 5 + self._stddayofweek, # Sunday = 0 + self._stdweeknumber, # Last = 5 self._stdhour, self._stdminute) = tup[1:6] tup = struct.unpack("=8h", keydict["DaylightStart"]) (self._dstmonth, - self._dstdayofweek, # Sunday = 0 - self._dstweeknumber, # Last = 5 + self._dstdayofweek, # Sunday = 0 + self._dstweeknumber, # Last = 5 self._dsthour, self._dstminute) = tup[1:6] def __reduce__(self): return (self.__class__, ()) - def picknthweekday(year, month, dayofweek, hour, minute, whichweek): """dayofweek == 0 means Sunday, whichweek 5 means last instance""" first = datetime.datetime(year, month, 1, hour, minute) - weekdayone = first.replace(day=((dayofweek-first.isoweekday()) % 7+1)) + weekdayone = first.replace(day=((dayofweek-first.isoweekday())%7+1)) for n in range(whichweek): dt = weekdayone+(whichweek-n)*ONEWEEK if dt.month == month: return dt - def valuestodict(key): """Convert a registry key's values to a dictionary.""" dict = {} diff --git a/dateutil/zoneinfo/__init__.py b/dateutil/zoneinfo/__init__.py index 8156092..81db140 100644 --- a/dateutil/zoneinfo/__init__.py +++ b/dateutil/zoneinfo/__init__.py @@ -1,102 +1,109 @@ # -*- coding: utf-8 -*- +""" +Copyright (c) 2003-2005 Gustavo Niemeyer + +This module offers extensions to the standard Python +datetime module. +""" import logging import os -import warnings -import tempfile -import shutil -import json - -from subprocess import check_call +from subprocess import call from tarfile import TarFile -from pkgutil import get_data -from io import BytesIO -from contextlib import closing from dateutil.tz import tzfile -__all__ = ["gettz", "gettz_db_metadata", "rebuild"] - -ZONEFILENAME = "dateutil-zoneinfo.tar.gz" -METADATA_FN = 'METADATA' +__author__ = "Tomi Pieviläinen " +__license__ = "Simplified BSD" -# python2.6 compatability. Note that TarFile.__exit__ != TarFile.close, but -# it's close enough for python2.6 -tar_open = TarFile.open -if not hasattr(TarFile, '__exit__'): - def tar_open(*args, **kwargs): - return closing(TarFile.open(*args, **kwargs)) +__all__ = ["setcachesize", "gettz", "rebuild"] +CACHE = [] +CACHESIZE = 10 class tzfile(tzfile): def __reduce__(self): return (gettz, (self._filename,)) +def getzoneinfofile(): + filenames = sorted(os.listdir(os.path.join(os.path.dirname(__file__)))) + filenames.reverse() + for entry in filenames: + if entry.startswith("zoneinfo") and ".tar." in entry: + return os.path.join(os.path.dirname(__file__), entry) + return None -def getzoneinfofile_stream(): - try: - return BytesIO(get_data(__name__, ZONEFILENAME)) - except IOError as e: # TODO switch to FileNotFoundError? - warnings.warn("I/O error({0}): {1}".format(e.errno, e.strerror)) - return None - - -class ZoneInfoFile(object): - def __init__(self, zonefile_stream=None): - if zonefile_stream is not None: - with tar_open(fileobj=zonefile_stream, mode='r') as tf: - # dict comprehension does not work on python2.6 - # TODO: get back to the nicer syntax when we ditch python2.6 - # self.zones = {zf.name: tzfile(tf.extractfile(zf), - # filename = zf.name) - # for zf in tf.getmembers() if zf.isfile()} - self.zones = dict((zf.name, tzfile(tf.extractfile(zf), - filename=zf.name)) - for zf in tf.getmembers() - if zf.isfile() and zf.name != METADATA_FN) - # deal with links: They'll point to their parent object. Less - # waste of memory - # links = {zl.name: self.zones[zl.linkname] - # for zl in tf.getmembers() if zl.islnk() or zl.issym()} - links = dict((zl.name, self.zones[zl.linkname]) - for zl in tf.getmembers() if - zl.islnk() or zl.issym()) - self.zones.update(links) - try: - metadata_json = tf.extractfile(tf.getmember(METADATA_FN)) - metadata_str = metadata_json.read().decode('UTF-8') - self.metadata = json.loads(metadata_str) - except KeyError: - # no metadata in tar file - self.metadata = None - else: - self.zones = dict() - self.metadata = None - +ZONEINFOFILE = getzoneinfofile() -# The current API has gettz as a module function, although in fact it taps into -# a stateful class. So as a workaround for now, without changing the API, we -# will create a new "global" class instance the first time a user requests a -# timezone. Ugly, but adheres to the api. -# -# TODO: deprecate this. -_CLASS_ZONE_INSTANCE = list() +del getzoneinfofile +def setcachesize(size): + global CACHESIZE, CACHE + CACHESIZE = size + del CACHE[size:] def gettz(name): - if len(_CLASS_ZONE_INSTANCE) == 0: - _CLASS_ZONE_INSTANCE.append(ZoneInfoFile(getzoneinfofile_stream())) - return _CLASS_ZONE_INSTANCE[0].zones.get(name) - - -def gettz_db_metadata(): - """ Get the zonefile metadata - - See `zonefile_metadata`_ + tzinfo = None + if ZONEINFOFILE: + for cachedname, tzinfo in CACHE: + if cachedname == name: + break + else: + tf = TarFile.open(ZONEINFOFILE) + try: + zonefile = tf.extractfile(name) + except KeyError: + tzinfo = None + else: + tzinfo = tzfile(zonefile) + tf.close() + CACHE.insert(0, (name, tzinfo)) + del CACHE[CACHESIZE:] + return tzinfo + +def rebuild(filename, tag=None, format="gz"): + """Rebuild the internal timezone info in dateutil/zoneinfo/zoneinfo*tar* + + filename is the timezone tarball from ftp.iana.org/tz. - :returns: A dictionary with the database metadata """ - if len(_CLASS_ZONE_INSTANCE) == 0: - _CLASS_ZONE_INSTANCE.append(ZoneInfoFile(getzoneinfofile_stream())) - return _CLASS_ZONE_INSTANCE[0].metadata - - + import tempfile, shutil + tmpdir = tempfile.mkdtemp() + zonedir = os.path.join(tmpdir, "zoneinfo") + moduledir = os.path.dirname(__file__) + if tag: tag = "-"+tag + targetname = "zoneinfo%s.tar.%s" % (tag, format) + try: + tf = TarFile.open(filename) + # The "backwards" zone file contains links to other files, so must be + # processed as last + for name in sorted(tf.getnames(), + key=lambda k: k != "backward" and k or "z"): + if not (name.endswith(".sh") or + name.endswith(".tab") or + name == "leapseconds"): + tf.extract(name, tmpdir) + filepath = os.path.join(tmpdir, name) + try: + # zic will return errors for nontz files in the package + # such as the Makefile or README, so check_call cannot + # be used (or at least extra checks would be needed) + call(["zic", "-d", zonedir, filepath]) + except OSError as e: + if e.errno == 2: + logging.error( + "Could not find zic. Perhaps you need to install " + "libc-bin or some other package that provides it, " + "or it's not in your PATH?") + raise + tf.close() + target = os.path.join(moduledir, targetname) + for entry in os.listdir(moduledir): + if entry.startswith("zoneinfo") and ".tar." in entry: + os.unlink(os.path.join(moduledir, entry)) + tf = TarFile.open(target, "w:%s" % format) + for entry in os.listdir(zonedir): + entrypath = os.path.join(zonedir, entry) + tf.add(entrypath, entry) + tf.close() + finally: + shutil.rmtree(tmpdir) diff --git a/dateutil/zoneinfo/dateutil-zoneinfo.tar.gz b/dateutil/zoneinfo/dateutil-zoneinfo.tar.gz deleted file mode 100644 index 6d5c7a5..0000000 Binary files a/dateutil/zoneinfo/dateutil-zoneinfo.tar.gz and /dev/null differ diff --git a/dateutil/zoneinfo/rebuild.py b/dateutil/zoneinfo/rebuild.py deleted file mode 100644 index e646148..0000000 --- a/dateutil/zoneinfo/rebuild.py +++ /dev/null @@ -1,43 +0,0 @@ -import logging -import os -import tempfile -import shutil -import json -from subprocess import check_call - -from dateutil.zoneinfo import tar_open, METADATA_FN, ZONEFILENAME - - -def rebuild(filename, tag=None, format="gz", zonegroups=[], metadata=None): - """Rebuild the internal timezone info in dateutil/zoneinfo/zoneinfo*tar* - - filename is the timezone tarball from ftp.iana.org/tz. - - """ - tmpdir = tempfile.mkdtemp() - zonedir = os.path.join(tmpdir, "zoneinfo") - moduledir = os.path.dirname(__file__) - try: - with tar_open(filename) as tf: - for name in zonegroups: - tf.extract(name, tmpdir) - filepaths = [os.path.join(tmpdir, n) for n in zonegroups] - try: - check_call(["zic", "-d", zonedir] + filepaths) - except OSError as e: - if e.errno == 2: - logging.error( - "Could not find zic. Perhaps you need to install " - "libc-bin or some other package that provides it, " - "or it's not in your PATH?") - raise - # write metadata file - with open(os.path.join(zonedir, METADATA_FN), 'w') as f: - json.dump(metadata, f, indent=4, sort_keys=True) - target = os.path.join(moduledir, ZONEFILENAME) - with tar_open(target, "w:%s" % format) as tf: - for entry in os.listdir(zonedir): - entrypath = os.path.join(zonedir, entry) - tf.add(entrypath, entry) - finally: - shutil.rmtree(tmpdir) diff --git a/docs/Makefile b/docs/Makefile deleted file mode 100644 index e5749ea..0000000 --- a/docs/Makefile +++ /dev/null @@ -1,177 +0,0 @@ -# Makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -PAPER = -BUILDDIR = _build - -# User-friendly check for sphinx-build -ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) -$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) -endif - -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . -# the i18n builder cannot share the environment and doctrees with the others -I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . - -.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext - -help: - @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " singlehtml to make a single large HTML file" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " devhelp to make HTML files and a Devhelp project" - @echo " epub to make an epub" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " latexpdf to make LaTeX files and run them through pdflatex" - @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" - @echo " text to make text files" - @echo " man to make manual pages" - @echo " texinfo to make Texinfo files" - @echo " info to make Texinfo files and run them through makeinfo" - @echo " gettext to make PO message catalogs" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " xml to make Docutils-native XML files" - @echo " pseudoxml to make pseudoxml-XML files for display purposes" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - -clean: - rm -rf $(BUILDDIR)/* - -html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." - -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." - -singlehtml: - $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml - @echo - @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." - -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json - @echo - @echo "Build finished; now you can process the JSON files." - -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in $(BUILDDIR)/htmlhelp." - -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp - @echo - @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/dateutil.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/dateutil.qhc" - -devhelp: - $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp - @echo - @echo "Build finished." - @echo "To view the help file:" - @echo "# mkdir -p $$HOME/.local/share/devhelp/dateutil" - @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/dateutil" - @echo "# devhelp" - -epub: - $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub - @echo - @echo "Build finished. The epub file is in $(BUILDDIR)/epub." - -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo - @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." - @echo "Run \`make' in that directory to run these through (pdf)latex" \ - "(use \`make latexpdf' here to do that automatically)." - -latexpdf: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through pdflatex..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -latexpdfja: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through platex and dvipdfmx..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -text: - $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text - @echo - @echo "Build finished. The text files are in $(BUILDDIR)/text." - -man: - $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man - @echo - @echo "Build finished. The manual pages are in $(BUILDDIR)/man." - -texinfo: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo - @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." - @echo "Run \`make' in that directory to run these through makeinfo" \ - "(use \`make info' here to do that automatically)." - -info: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo "Running Texinfo files through makeinfo..." - make -C $(BUILDDIR)/texinfo info - @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." - -gettext: - $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale - @echo - @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." - -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes - @echo - @echo "The overview file is in $(BUILDDIR)/changes." - -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in $(BUILDDIR)/linkcheck/output.txt." - -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in $(BUILDDIR)/doctest/output.txt." - -xml: - $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml - @echo - @echo "Build finished. The XML files are in $(BUILDDIR)/xml." - -pseudoxml: - $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml - @echo - @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/docs/conf.py b/docs/conf.py deleted file mode 100644 index cb82c5b..0000000 --- a/docs/conf.py +++ /dev/null @@ -1,264 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# -# dateutil documentation build configuration file, created by -# sphinx-quickstart on Thu Nov 20 23:18:41 2014. -# -# This file is execfile()d with the current directory set to its -# containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -import sys -import os - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.insert(0, os.path.abspath('.')) -sys.path.insert(0, os.path.abspath('../')) - -# -- General configuration ------------------------------------------------ - -# If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.doctest', - 'sphinx.ext.viewcode', -] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix of source filenames. -source_suffix = '.rst' - -# The encoding of source files. -#source_encoding = 'utf-8-sig' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = 'dateutil' -copyright = '2015, dateutil' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -version = '2.4.2' -# The full version, including alpha/beta/rc tags. -release = '2.4.2' - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -#language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -#today = '' -# Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -exclude_patterns = ['_build'] - -# The reST default role (used for this markup: `text`) to use for all -# documents. -#default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -#add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -#show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] - -# If true, keep warnings as "system message" paragraphs in the built documents. -#keep_warnings = False - - -# -- Options for HTML output ---------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -html_theme = 'default' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -#html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -#html_title = None - -# A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -#html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -#html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -# Add any extra paths that contain custom files (such as robots.txt or -# .htaccess) here, relative to this directory. These files are copied -# directly to the root of the documentation. -#html_extra_path = [] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -#html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -#html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -#html_additional_pages = {} - -# If false, no module index is generated. -#html_domain_indices = True - -# If false, no index is generated. -#html_use_index = True - -# If true, the index is split into individual pages for each letter. -#html_split_index = False - -# If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -#html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None - -# Output file base name for HTML help builder. -htmlhelp_basename = 'dateutildoc' - - -# -- Options for LaTeX output --------------------------------------------- - -latex_elements = { -# The paper size ('letterpaper' or 'a4paper'). -#'papersize': 'letterpaper', - -# The font size ('10pt', '11pt' or '12pt'). -#'pointsize': '10pt', - -# Additional stuff for the LaTeX preamble. -#'preamble': '', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). -latex_documents = [ - ('index', 'dateutil.tex', 'dateutil Documentation', - 'dateutil', 'manual'), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -#latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -#latex_use_parts = False - -# If true, show page references after internal links. -#latex_show_pagerefs = False - -# If true, show URL addresses after external links. -#latex_show_urls = False - -# Documents to append as an appendix to all manuals. -#latex_appendices = [] - -# If false, no module index is generated. -#latex_domain_indices = True - - -# -- Options for manual page output --------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - ('index', 'dateutil', 'dateutil Documentation', - ['dateutil'], 1) -] - -# If true, show URL addresses after external links. -#man_show_urls = False - - -# -- Options for Texinfo output ------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - ('index', 'dateutil', 'dateutil Documentation', - 'dateutil', 'dateutil', 'One line description of project.', - 'Miscellaneous'), -] - -# Documents to append as an appendix to all manuals. -#texinfo_appendices = [] - -# If false, no module index is generated. -#texinfo_domain_indices = True - -# How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' - -# If true, do not generate a @detailmenu in the "Top" node's menu. -#texinfo_no_detailmenu = False diff --git a/docs/easter.rst b/docs/easter.rst deleted file mode 100644 index 13b5903..0000000 --- a/docs/easter.rst +++ /dev/null @@ -1,6 +0,0 @@ -====== -easter -====== -.. automodule:: dateutil.easter - :members: - :undoc-members: diff --git a/docs/examples.rst b/docs/examples.rst deleted file mode 100644 index ce52c63..0000000 --- a/docs/examples.rst +++ /dev/null @@ -1,1402 +0,0 @@ -dateutil examples -================= - -.. contents:: - -relativedelta examples ----------------------- - -.. testsetup:: relativedelta - - from datetime import *; from dateutil.relativedelta import * - import calendar - NOW = datetime(2003, 9, 17, 20, 54, 47, 282310) - TODAY = date(2003, 9, 17) - -Let's begin our trip:: - - >>> from datetime import *; from dateutil.relativedelta import * - >>> import calendar - -Store some values:: - - >>> NOW = datetime.now() - >>> TODAY = date.today() - >>> NOW - datetime.datetime(2003, 9, 17, 20, 54, 47, 282310) - >>> TODAY - datetime.date(2003, 9, 17) - -Next month - -.. doctest:: relativedelta - - >>> NOW+relativedelta(months=+1) - datetime.datetime(2003, 10, 17, 20, 54, 47, 282310) - -Next month, plus one week. - -.. doctest:: relativedelta - - >>> NOW+relativedelta(months=+1, weeks=+1) - datetime.datetime(2003, 10, 24, 20, 54, 47, 282310) - -Next month, plus one week, at 10am. - -.. doctest:: relativedelta - - >>> TODAY+relativedelta(months=+1, weeks=+1, hour=10) - datetime.datetime(2003, 10, 24, 10, 0) - -Here is another example using an absolute relativedelta. Notice the use of -year and month (both singular) which causes the values to be *replaced* in the -original datetime rather than performing an arithmetic operation on them. - -.. doctest:: relativedelta - - >>> NOW+relativedelta(year=1, month=1) - datetime(1, 1, 17, 20, 54, 47, 282310) - -Let's try the other way around. Notice that the -hour setting we get in the relativedelta is relative, -since it's a difference, and the weeks parameter -has gone. - -.. doctest:: relativedelta - - >>> relativedelta(datetime(2003, 10, 24, 10, 0), TODAY) - relativedelta(months=+1, days=+7, hours=+10) - -One month before one year. - -.. doctest:: relativedelta - - >>> NOW+relativedelta(years=+1, months=-1) - datetime.datetime(2004, 8, 17, 20, 54, 47, 282310) - -How does it handle months with different numbers of days? -Notice that adding one month will never cross the month -boundary. - -.. doctest:: relativedelta - - >>> date(2003,1,27)+relativedelta(months=+1) - datetime.date(2003, 2, 27) - >>> date(2003,1,31)+relativedelta(months=+1) - datetime.date(2003, 2, 28) - >>> date(2003,1,31)+relativedelta(months=+2) - datetime.date(2003, 3, 31) - -The logic for years is the same, even on leap years. - -.. doctest:: relativedelta - - >>> date(2000,2,28)+relativedelta(years=+1) - datetime.date(2001, 2, 28) - >>> date(2000,2,29)+relativedelta(years=+1) - datetime.date(2001, 2, 28) - - >>> date(1999,2,28)+relativedelta(years=+1) - datetime.date(2000, 2, 28) - >>> date(1999,3,1)+relativedelta(years=+1) - datetime.date(2000, 3, 1) - - >>> date(2001,2,28)+relativedelta(years=-1) - datetime.date(2000, 2, 28) - >>> date(2001,3,1)+relativedelta(years=-1) - datetime.date(2000, 3, 1) - -Next friday - -.. doctest:: relativedelta - - >>> TODAY+relativedelta(weekday=FR) - datetime.date(2003, 9, 19) - - >>> TODAY+relativedelta(weekday=calendar.FRIDAY) - datetime.date(2003, 9, 19) - -Last friday in this month. - -.. doctest:: relativedelta - - >>> TODAY+relativedelta(day=31, weekday=FR(-1)) - datetime.date(2003, 9, 26) - -Next wednesday (it's today!). - -.. doctest:: relativedelta - - >>> TODAY+relativedelta(weekday=WE(+1)) - datetime.date(2003, 9, 17) - -Next wednesday, but not today. - -.. doctest:: relativedelta - - >>> TODAY+relativedelta(days=+1, weekday=WE(+1)) - datetime.date(2003, 9, 24) - -Following -[http://www.cl.cam.ac.uk/~mgk25/iso-time.html ISO year week number notation] -find the first day of the 15th week of 1997. - -.. doctest:: relativedelta - - >>> datetime(1997,1,1)+relativedelta(day=4, weekday=MO(-1), weeks=+14) - datetime.datetime(1997, 4, 7, 0, 0) - -How long ago has the millennium changed? - -.. doctest:: relativedelta - - >>> relativedelta(NOW, date(2001,1,1)) - relativedelta(years=+2, months=+8, days=+16, - hours=+20, minutes=+54, seconds=+47, microseconds=+282310) - -How old is John? - -.. doctest:: relativedelta - - >>> johnbirthday = datetime(1978, 4, 5, 12, 0) - >>> relativedelta(NOW, johnbirthday) - relativedelta(years=+25, months=+5, days=+12, - hours=+8, minutes=+54, seconds=+47, microseconds=+282310) - -It works with dates too. - -.. doctest:: relativedelta - - >>> relativedelta(TODAY, johnbirthday) - relativedelta(years=+25, months=+5, days=+11, hours=+12) - -Obtain today's date using the yearday: - -.. doctest:: relativedelta - - >>> date(2003, 1, 1)+relativedelta(yearday=260) - datetime.date(2003, 9, 17) - -We can use today's date, since yearday should be absolute -in the given year: - -.. doctest:: relativedelta - - >>> TODAY+relativedelta(yearday=260) - datetime.date(2003, 9, 17) - -Last year it should be in the same day: - -.. doctest:: relativedelta - - >>> date(2002, 1, 1)+relativedelta(yearday=260) - datetime.date(2002, 9, 17) - -But not in a leap year: - -.. doctest:: relativedelta - - >>> date(2000, 1, 1)+relativedelta(yearday=260) - datetime.date(2000, 9, 16) - -We can use the non-leap year day to ignore this: - -.. doctest:: relativedelta - - >>> date(2000, 1, 1)+relativedelta(nlyearday=260) - datetime.date(2000, 9, 17) - -rrule examples --------------- -These examples were converted from the RFC. - -Prepare the environment. - -.. testsetup:: rrule - - from dateutil.rrule import * - from dateutil.parser import * - from datetime import * - import pprint - import sys - sys.displayhook = pprint.pprint - -.. doctest:: rrule - - >>> from dateutil.rrule import * - >>> from dateutil.parser import * - >>> from datetime import * - - >>> import pprint - >>> import sys - >>> sys.displayhook = pprint.pprint - -Daily, for 10 occurrences. - -.. doctest:: rrule - - >>> list(rrule(DAILY, count=10, - dtstart=parse("19970902T090000"))) - [datetime.datetime(1997, 9, 2, 9, 0), - datetime.datetime(1997, 9, 3, 9, 0), - datetime.datetime(1997, 9, 4, 9, 0), - datetime.datetime(1997, 9, 5, 9, 0), - datetime.datetime(1997, 9, 6, 9, 0), - datetime.datetime(1997, 9, 7, 9, 0), - datetime.datetime(1997, 9, 8, 9, 0), - datetime.datetime(1997, 9, 9, 9, 0), - datetime.datetime(1997, 9, 10, 9, 0), - datetime.datetime(1997, 9, 11, 9, 0)] - -Daily until December 24, 1997 - -.. doctest:: rrule - - >>> list(rrule(DAILY, - dtstart=parse("19970902T090000"), - until=parse("19971224T000000"))) - [datetime.datetime(1997, 9, 2, 9, 0), - datetime.datetime(1997, 9, 3, 9, 0), - datetime.datetime(1997, 9, 4, 9, 0), - (...) - datetime.datetime(1997, 12, 21, 9, 0), - datetime.datetime(1997, 12, 22, 9, 0), - datetime.datetime(1997, 12, 23, 9, 0)] - -Every other day, 5 occurrences. - -.. doctest:: rrule - - >>> list(rrule(DAILY, interval=2, count=5, - dtstart=parse("19970902T090000"))) - [datetime.datetime(1997, 9, 2, 9, 0), - datetime.datetime(1997, 9, 4, 9, 0), - datetime.datetime(1997, 9, 6, 9, 0), - datetime.datetime(1997, 9, 8, 9, 0), - datetime.datetime(1997, 9, 10, 9, 0)] - -Every 10 days, 5 occurrences. - -.. doctest:: rrule - - >>> list(rrule(DAILY, interval=10, count=5, - dtstart=parse("19970902T090000"))) - [datetime.datetime(1997, 9, 2, 9, 0), - datetime.datetime(1997, 9, 12, 9, 0), - datetime.datetime(1997, 9, 22, 9, 0), - datetime.datetime(1997, 10, 2, 9, 0), - datetime.datetime(1997, 10, 12, 9, 0)] - -Everyday in January, for 3 years. - -.. doctest:: rrule - - >>> list(rrule(YEARLY, bymonth=1, byweekday=range(7), - dtstart=parse("19980101T090000"), - until=parse("20000131T090000"))) - [datetime.datetime(1998, 1, 1, 9, 0), - datetime.datetime(1998, 1, 2, 9, 0), - (...) - datetime.datetime(1998, 1, 30, 9, 0), - datetime.datetime(1998, 1, 31, 9, 0), - datetime.datetime(1999, 1, 1, 9, 0), - datetime.datetime(1999, 1, 2, 9, 0), - (...) - datetime.datetime(1999, 1, 30, 9, 0), - datetime.datetime(1999, 1, 31, 9, 0), - datetime.datetime(2000, 1, 1, 9, 0), - datetime.datetime(2000, 1, 2, 9, 0), - (...) - datetime.datetime(2000, 1, 29, 9, 0), - datetime.datetime(2000, 1, 31, 9, 0)] - -Same thing, in another way. - -.. doctest:: rrule - - >>> list(rrule(DAILY, bymonth=1, - dtstart=parse("19980101T090000"), - until=parse("20000131T090000"))) - (...) - -Weekly for 10 occurrences. - -.. doctest:: rrule - - >>> list(rrule(WEEKLY, count=10, - dtstart=parse("19970902T090000"))) - [datetime.datetime(1997, 9, 2, 9, 0), - datetime.datetime(1997, 9, 9, 9, 0), - datetime.datetime(1997, 9, 16, 9, 0), - datetime.datetime(1997, 9, 23, 9, 0), - datetime.datetime(1997, 9, 30, 9, 0), - datetime.datetime(1997, 10, 7, 9, 0), - datetime.datetime(1997, 10, 14, 9, 0), - datetime.datetime(1997, 10, 21, 9, 0), - datetime.datetime(1997, 10, 28, 9, 0), - datetime.datetime(1997, 11, 4, 9, 0)] - -Every other week, 6 occurrences. - -.. doctest:: rrule - - >>> list(rrule(WEEKLY, interval=2, count=6, - dtstart=parse("19970902T090000"))) - [datetime.datetime(1997, 9, 2, 9, 0), - datetime.datetime(1997, 9, 16, 9, 0), - datetime.datetime(1997, 9, 30, 9, 0), - datetime.datetime(1997, 10, 14, 9, 0), - datetime.datetime(1997, 10, 28, 9, 0), - datetime.datetime(1997, 11, 11, 9, 0)] - -Weekly on Tuesday and Thursday for 5 weeks. - -.. doctest:: rrule - - >>> list(rrule(WEEKLY, count=10, wkst=SU, byweekday=(TU,TH), - dtstart=parse("19970902T090000"))) - [datetime.datetime(1997, 9, 2, 9, 0), - datetime.datetime(1997, 9, 4, 9, 0), - datetime.datetime(1997, 9, 9, 9, 0), - datetime.datetime(1997, 9, 11, 9, 0), - datetime.datetime(1997, 9, 16, 9, 0), - datetime.datetime(1997, 9, 18, 9, 0), - datetime.datetime(1997, 9, 23, 9, 0), - datetime.datetime(1997, 9, 25, 9, 0), - datetime.datetime(1997, 9, 30, 9, 0), - datetime.datetime(1997, 10, 2, 9, 0)] - -Every other week on Tuesday and Thursday, for 8 occurrences. - -.. doctest:: rrule - - >>> list(rrule(WEEKLY, interval=2, count=8, - wkst=SU, byweekday=(TU,TH), - dtstart=parse("19970902T090000"))) - [datetime.datetime(1997, 9, 2, 9, 0), - datetime.datetime(1997, 9, 4, 9, 0), - datetime.datetime(1997, 9, 16, 9, 0), - datetime.datetime(1997, 9, 18, 9, 0), - datetime.datetime(1997, 9, 30, 9, 0), - datetime.datetime(1997, 10, 2, 9, 0), - datetime.datetime(1997, 10, 14, 9, 0), - datetime.datetime(1997, 10, 16, 9, 0)] - -Monthly on the 1st Friday for ten occurrences. - -.. doctest:: rrule - - >>> list(rrule(MONTHLY, count=10, byweekday=FR(1), - dtstart=parse("19970905T090000"))) - [datetime.datetime(1997, 9, 5, 9, 0), - datetime.datetime(1997, 10, 3, 9, 0), - datetime.datetime(1997, 11, 7, 9, 0), - datetime.datetime(1997, 12, 5, 9, 0), - datetime.datetime(1998, 1, 2, 9, 0), - datetime.datetime(1998, 2, 6, 9, 0), - datetime.datetime(1998, 3, 6, 9, 0), - datetime.datetime(1998, 4, 3, 9, 0), - datetime.datetime(1998, 5, 1, 9, 0), - datetime.datetime(1998, 6, 5, 9, 0)] - -Every other month on the 1st and last Sunday of the month for 10 occurrences. - -.. doctest:: rrule - - >>> list(rrule(MONTHLY, interval=2, count=10, - byweekday=(SU(1), SU(-1)), - dtstart=parse("19970907T090000"))) - [datetime.datetime(1997, 9, 7, 9, 0), - datetime.datetime(1997, 9, 28, 9, 0), - datetime.datetime(1997, 11, 2, 9, 0), - datetime.datetime(1997, 11, 30, 9, 0), - datetime.datetime(1998, 1, 4, 9, 0), - datetime.datetime(1998, 1, 25, 9, 0), - datetime.datetime(1998, 3, 1, 9, 0), - datetime.datetime(1998, 3, 29, 9, 0), - datetime.datetime(1998, 5, 3, 9, 0), - datetime.datetime(1998, 5, 31, 9, 0)] - -Monthly on the second to last Monday of the month for 6 months. - -.. doctest:: rrule - - >>> list(rrule(MONTHLY, count=6, byweekday=MO(-2), - dtstart=parse("19970922T090000"))) - [datetime.datetime(1997, 9, 22, 9, 0), - datetime.datetime(1997, 10, 20, 9, 0), - datetime.datetime(1997, 11, 17, 9, 0), - datetime.datetime(1997, 12, 22, 9, 0), - datetime.datetime(1998, 1, 19, 9, 0), - datetime.datetime(1998, 2, 16, 9, 0)] - - -Monthly on the third to the last day of the month, for 6 months. - -.. doctest:: rrule - - >>> list(rrule(MONTHLY, count=6, bymonthday=-3, - dtstart=parse("19970928T090000"))) - [datetime.datetime(1997, 9, 28, 9, 0), - datetime.datetime(1997, 10, 29, 9, 0), - datetime.datetime(1997, 11, 28, 9, 0), - datetime.datetime(1997, 12, 29, 9, 0), - datetime.datetime(1998, 1, 29, 9, 0), - datetime.datetime(1998, 2, 26, 9, 0)] - - -Monthly on the 2nd and 15th of the month for 5 occurrences. - -.. doctest:: rrule - - >>> list(rrule(MONTHLY, count=5, bymonthday=(2,15), - dtstart=parse("19970902T090000"))) - [datetime.datetime(1997, 9, 2, 9, 0), - datetime.datetime(1997, 9, 15, 9, 0), - datetime.datetime(1997, 10, 2, 9, 0), - datetime.datetime(1997, 10, 15, 9, 0), - datetime.datetime(1997, 11, 2, 9, 0)] - - -Monthly on the first and last day of the month for 3 occurrences. - -.. doctest:: rrule - - >>> list(rrule(MONTHLY, count=5, bymonthday=(-1,1,), - dtstart=parse("1997090 - 2T090000"))) - [datetime.datetime(1997, 9, 30, 9, 0), - datetime.datetime(1997, 10, 1, 9, 0), - datetime.datetime(1997, 10, 31, 9, 0), - datetime.datetime(1997, 11, 1, 9, 0), - datetime.datetime(1997, 11, 30, 9, 0)] - - -Every 18 months on the 10th thru 15th of the month for 10 occurrences. - -.. doctest:: rrule - - >>> list(rrule(MONTHLY, interval=18, count=10, - bymonthday=range(10,16), - dtstart=parse("19970910T090000"))) - [datetime.datetime(1997, 9, 10, 9, 0), - datetime.datetime(1997, 9, 11, 9, 0), - datetime.datetime(1997, 9, 12, 9, 0), - datetime.datetime(1997, 9, 13, 9, 0), - datetime.datetime(1997, 9, 14, 9, 0), - datetime.datetime(1997, 9, 15, 9, 0), - datetime.datetime(1999, 3, 10, 9, 0), - datetime.datetime(1999, 3, 11, 9, 0), - datetime.datetime(1999, 3, 12, 9, 0), - datetime.datetime(1999, 3, 13, 9, 0)] - - -Every Tuesday, every other month, 6 occurences. - -.. doctest:: rrule - - >>> list(rrule(MONTHLY, interval=2, count=6, byweekday=TU, - dtstart=parse("19970902T090000"))) - [datetime.datetime(1997, 9, 2, 9, 0), - datetime.datetime(1997, 9, 9, 9, 0), - datetime.datetime(1997, 9, 16, 9, 0), - datetime.datetime(1997, 9, 23, 9, 0), - datetime.datetime(1997, 9, 30, 9, 0), - datetime.datetime(1997, 11, 4, 9, 0)] - - -Yearly in June and July for 10 occurrences. - -.. doctest:: rrule - - >>> list(rrule(YEARLY, count=4, bymonth=(6,7), - dtstart=parse("19970610T0900 - 00"))) - [datetime.datetime(1997, 6, 10, 9, 0), - datetime.datetime(1997, 7, 10, 9, 0), - datetime.datetime(1998, 6, 10, 9, 0), - datetime.datetime(1998, 7, 10, 9, 0)] - - -Every 3rd year on the 1st, 100th and 200th day for 4 occurrences. - -.. doctest:: rrule - - >>> list(rrule(YEARLY, count=4, interval=3, byyearday=(1,100,200), - dtstart=parse("19970101T090000"))) - [datetime.datetime(1997, 1, 1, 9, 0), - datetime.datetime(1997, 4, 10, 9, 0), - datetime.datetime(1997, 7, 19, 9, 0), - datetime.datetime(2000, 1, 1, 9, 0)] - - -Every 20th Monday of the year, 3 occurrences. - -.. doctest:: rrule - - >>> list(rrule(YEARLY, count=3, byweekday=MO(20), - dtstart=parse("19970519T090000"))) - [datetime.datetime(1997, 5, 19, 9, 0), - datetime.datetime(1998, 5, 18, 9, 0), - datetime.datetime(1999, 5, 17, 9, 0)] - - -Monday of week number 20 (where the default start of the week is Monday), -3 occurrences. - -.. doctest:: rrule - - >>> list(rrule(YEARLY, count=3, byweekno=20, byweekday=MO, - dtstart=parse("19970512T090000"))) - [datetime.datetime(1997, 5, 12, 9, 0), - datetime.datetime(1998, 5, 11, 9, 0), - datetime.datetime(1999, 5, 17, 9, 0)] - - -The week number 1 may be in the last year. - -.. doctest:: rrule - - >>> list(rrule(WEEKLY, count=3, byweekno=1, byweekday=MO, - dtstart=parse("19970902T090000"))) - [datetime.datetime(1997, 12, 29, 9, 0), - datetime.datetime(1999, 1, 4, 9, 0), - datetime.datetime(2000, 1, 3, 9, 0)] - - -And the week numbers greater than 51 may be in the next year. - -.. doctest:: rrule - - >>> list(rrule(WEEKLY, count=3, byweekno=52, byweekday=SU, - dtstart=parse("19970902T090000"))) - [datetime.datetime(1997, 12, 28, 9, 0), - datetime.datetime(1998, 12, 27, 9, 0), - datetime.datetime(2000, 1, 2, 9, 0)] - - -Only some years have week number 53: - -.. doctest:: rrule - - >>> list(rrule(WEEKLY, count=3, byweekno=53, byweekday=MO, - dtstart=parse("19970902T090000"))) - [datetime.datetime(1998, 12, 28, 9, 0), - datetime.datetime(2004, 12, 27, 9, 0), - datetime.datetime(2009, 12, 28, 9, 0)] - - -Every Friday the 13th, 4 occurrences. - -.. doctest:: rrule - - >>> list(rrule(YEARLY, count=4, byweekday=FR, bymonthday=13, - dtstart=parse("19970902T090000"))) - [datetime.datetime(1998, 2, 13, 9, 0), - datetime.datetime(1998, 3, 13, 9, 0), - datetime.datetime(1998, 11, 13, 9, 0), - datetime.datetime(1999, 8, 13, 9, 0)] - - -Every four years, the first Tuesday after a Monday in November, -3 occurrences (U.S. Presidential Election day): - -.. doctest:: rrule - - >>> list(rrule(YEARLY, interval=4, count=3, bymonth=11, - byweekday=TU, bymonthday=(2,3,4,5,6,7,8), - dtstart=parse("19961105T090000"))) - [datetime.datetime(1996, 11, 5, 9, 0), - datetime.datetime(2000, 11, 7, 9, 0), - datetime.datetime(2004, 11, 2, 9, 0)] - - -The 3rd instance into the month of one of Tuesday, Wednesday or -Thursday, for the next 3 months: - -.. doctest:: rrule - - >>> list(rrule(MONTHLY, count=3, byweekday=(TU,WE,TH), - bysetpos=3, dtstart=parse("19970904T090000"))) - [datetime.datetime(1997, 9, 4, 9, 0), - datetime.datetime(1997, 10, 7, 9, 0), - datetime.datetime(1997, 11, 6, 9, 0)] - - -The 2nd to last weekday of the month, 3 occurrences. - -.. doctest:: rrule - - >>> list(rrule(MONTHLY, count=3, byweekday=(MO,TU,WE,TH,FR), - bysetpos=-2, dtstart=parse("19970929T090000"))) - [datetime.datetime(1997, 9, 29, 9, 0), - datetime.datetime(1997, 10, 30, 9, 0), - datetime.datetime(1997, 11, 27, 9, 0)] - - -Every 3 hours from 9:00 AM to 5:00 PM on a specific day. - -.. doctest:: rrule - - >>> list(rrule(HOURLY, interval=3, - dtstart=parse("19970902T090000"), - until=parse("19970902T170000"))) - [datetime.datetime(1997, 9, 2, 9, 0), - datetime.datetime(1997, 9, 2, 12, 0), - datetime.datetime(1997, 9, 2, 15, 0)] - - -Every 15 minutes for 6 occurrences. - -.. doctest:: rrule - - >>> list(rrule(MINUTELY, interval=15, count=6, - dtstart=parse("19970902T090000"))) - [datetime.datetime(1997, 9, 2, 9, 0), - datetime.datetime(1997, 9, 2, 9, 15), - datetime.datetime(1997, 9, 2, 9, 30), - datetime.datetime(1997, 9, 2, 9, 45), - datetime.datetime(1997, 9, 2, 10, 0), - datetime.datetime(1997, 9, 2, 10, 15)] - - -Every hour and a half for 4 occurrences. - -.. doctest:: rrule - - >>> list(rrule(MINUTELY, interval=90, count=4, - dtstart=parse("19970902T090000"))) - [datetime.datetime(1997, 9, 2, 9, 0), - datetime.datetime(1997, 9, 2, 10, 30), - datetime.datetime(1997, 9, 2, 12, 0), - datetime.datetime(1997, 9, 2, 13, 30)] - - -Every 20 minutes from 9:00 AM to 4:40 PM for two days. - -.. doctest:: rrule - - >>> list(rrule(MINUTELY, interval=20, count=48, - byhour=range(9,17), byminute=(0,20,40), - dtstart=parse("19970902T090000"))) - [datetime.datetime(1997, 9, 2, 9, 0), - datetime.datetime(1997, 9, 2, 9, 20), - (...) - datetime.datetime(1997, 9, 2, 16, 20), - datetime.datetime(1997, 9, 2, 16, 40), - datetime.datetime(1997, 9, 3, 9, 0), - datetime.datetime(1997, 9, 3, 9, 20), - (...) - datetime.datetime(1997, 9, 3, 16, 20), - datetime.datetime(1997, 9, 3, 16, 40)] - - -An example where the days generated makes a difference because of `wkst`. - -.. doctest:: rrule - - >>> list(rrule(WEEKLY, interval=2, count=4, - byweekday=(TU,SU), wkst=MO, - dtstart=parse("19970805T090000"))) - [datetime.datetime(1997, 8, 5, 9, 0), - datetime.datetime(1997, 8, 10, 9, 0), - datetime.datetime(1997, 8, 19, 9, 0), - datetime.datetime(1997, 8, 24, 9, 0)] - - >>> list(rrule(WEEKLY, interval=2, count=4, - byweekday=(TU,SU), wkst=SU, - dtstart=parse("19970805T090000"))) - [datetime.datetime(1997, 8, 5, 9, 0), - datetime.datetime(1997, 8, 17, 9, 0), - datetime.datetime(1997, 8, 19, 9, 0), - datetime.datetime(1997, 8, 31, 9, 0)] - - -rruleset examples ------------------ -Daily, for 7 days, jumping Saturday and Sunday occurrences. - -.. doctest:: rruleset - - >>> set = rruleset() - >>> set.rrule(rrule(DAILY, count=7, - dtstart=parse("19970902T090000"))) - >>> set.exrule(rrule(YEARLY, byweekday=(SA,SU), - dtstart=parse("19970902T090000"))) - >>> list(set) - [datetime.datetime(1997, 9, 2, 9, 0), - datetime.datetime(1997, 9, 3, 9, 0), - datetime.datetime(1997, 9, 4, 9, 0), - datetime.datetime(1997, 9, 5, 9, 0), - datetime.datetime(1997, 9, 8, 9, 0)] - - -Weekly, for 4 weeks, plus one time on day 7, and not on day 16. - -.. doctest:: rruleset - - >>> set = rruleset() - >>> set.rrule(rrule(WEEKLY, count=4, - dtstart=parse("19970902T090000"))) - >>> set.rdate(datetime.datetime(1997, 9, 7, 9, 0)) - >>> set.exdate(datetime.datetime(1997, 9, 16, 9, 0)) - >>> list(set) - [datetime.datetime(1997, 9, 2, 9, 0), - datetime.datetime(1997, 9, 7, 9, 0), - datetime.datetime(1997, 9, 9, 9, 0), - datetime.datetime(1997, 9, 23, 9, 0)] - - -rrulestr() examples -------------------- - -Every 10 days, 5 occurrences. - -.. doctest:: rrulestr - - >>> list(rrulestr(""" - ... DTSTART:19970902T090000 - ... RRULE:FREQ=DAILY;INTERVAL=10;COUNT=5 - ... """)) - [datetime.datetime(1997, 9, 2, 9, 0), - datetime.datetime(1997, 9, 12, 9, 0), - datetime.datetime(1997, 9, 22, 9, 0), - datetime.datetime(1997, 10, 2, 9, 0), - datetime.datetime(1997, 10, 12, 9, 0)] - - -Same thing, but passing only the `RRULE` value. - -.. doctest:: rrulestr - - >>> list(rrulestr("FREQ=DAILY;INTERVAL=10;COUNT=5", - dtstart=parse("19970902T090000"))) - [datetime.datetime(1997, 9, 2, 9, 0), - datetime.datetime(1997, 9, 12, 9, 0), - datetime.datetime(1997, 9, 22, 9, 0), - datetime.datetime(1997, 10, 2, 9, 0), - datetime.datetime(1997, 10, 12, 9, 0)] - - -Notice that when using a single rule, it returns an -`rrule` instance, unless `forceset` was used. - -.. doctest:: rrulestr - - >>> rrulestr("FREQ=DAILY;INTERVAL=10;COUNT=5") - - - >>> rrulestr(""" - ... DTSTART:19970902T090000 - ... RRULE:FREQ=DAILY;INTERVAL=10;COUNT=5 - ... """) - - - >>> rrulestr("FREQ=DAILY;INTERVAL=10;COUNT=5", forceset=True) - - - -But when an `rruleset` is needed, it is automatically used. - -.. doctest:: rrulestr - - >>> rrulestr(""" - ... DTSTART:19970902T090000 - ... RRULE:FREQ=DAILY;INTERVAL=10;COUNT=5 - ... RRULE:FREQ=DAILY;INTERVAL=5;COUNT=3 - ... """) - - - -parse examples ------------ -The following code will prepare the environment: - -.. doctest:: tz - - >>> from dateutil.parser import * - >>> from dateutil.tz import * - >>> from datetime import * - >>> TZOFFSETS = {"BRST": -10800} - >>> BRSTTZ = tzoffset(-10800, "BRST") - >>> DEFAULT = datetime(2003, 9, 25) - - -Some simple examples based on the `date` command, using the -`ZOFFSET` dictionary to provide the BRST timezone offset. - -.. doctest:: tz - - >>> parse("Thu Sep 25 10:36:28 BRST 2003", tzinfos=TZOFFSETS) - datetime.datetime(2003, 9, 25, 10, 36, 28, - tzinfo=tzoffset('BRST', -10800)) - - >>> parse("2003 10:36:28 BRST 25 Sep Thu", tzinfos=TZOFFSETS) - datetime.datetime(2003, 9, 25, 10, 36, 28, - tzinfo=tzoffset('BRST', -10800)) - - -Notice that since BRST is my local timezone, parsing it without -further timezone settings will yield a `tzlocal` timezone. - -.. doctest:: tz - - >>> parse("Thu Sep 25 10:36:28 BRST 2003") - datetime.datetime(2003, 9, 25, 10, 36, 28, tzinfo=tzlocal()) - - -We can also ask to ignore the timezone explicitly: - -.. doctest:: tz - - >>> parse("Thu Sep 25 10:36:28 BRST 2003", ignoretz=True) - datetime.datetime(2003, 9, 25, 10, 36, 28) - - -That's the same as processing a string without timezone: - -.. doctest:: tz - - >>> parse("Thu Sep 25 10:36:28 2003") - datetime.datetime(2003, 9, 25, 10, 36, 28) - - -Without the year, but passing our `DEFAULT` datetime to return -the same year, no mattering what year we currently are in: - -.. doctest:: tz - - >>> parse("Thu Sep 25 10:36:28", default=DEFAULT) - datetime.datetime(2003, 9, 25, 10, 36, 28) - - -Strip it further: - -.. doctest:: tz - - >>> parse("Thu Sep 10:36:28", default=DEFAULT) - datetime.datetime(2003, 9, 25, 10, 36, 28) - - >>> parse("Thu 10:36:28", default=DEFAULT) - datetime.datetime(2003, 9, 25, 10, 36, 28) - - >>> parse("Thu 10:36", default=DEFAULT) - datetime.datetime(2003, 9, 25, 10, 36) - - >>> parse("10:36", default=DEFAULT) - datetime.datetime(2003, 9, 25, 10, 36) - >>> - - -Strip in a different way: - -.. doctest:: tz - - >>> parse("Thu Sep 25 2003") - datetime.datetime(2003, 9, 25, 0, 0) - - >>> parse("Sep 25 2003") - datetime.datetime(2003, 9, 25, 0, 0) - - >>> parse("Sep 2003", default=DEFAULT) - datetime.datetime(2003, 9, 25, 0, 0) - - >>> parse("Sep", default=DEFAULT) - datetime.datetime(2003, 9, 25, 0, 0) - - >>> parse("2003", default=DEFAULT) - datetime.datetime(2003, 9, 25, 0, 0) - - -Another format, based on `date -R` (RFC822): - -.. doctest:: tz - - >>> parse("Thu, 25 Sep 2003 10:49:41 -0300") - datetime.datetime(2003, 9, 25, 10, 49, 41, - tzinfo=tzoffset(None, -10800)) - - -ISO format: - -.. doctest:: tz - - >>> parse("2003-09-25T10:49:41.5-03:00") - datetime.datetime(2003, 9, 25, 10, 49, 41, 500000, - tzinfo=tzoffset(None, -10800)) - - -Some variations: - -.. doctest:: tz - - >>> parse("2003-09-25T10:49:41") - datetime.datetime(2003, 9, 25, 10, 49, 41) - - >>> parse("2003-09-25T10:49") - datetime.datetime(2003, 9, 25, 10, 49) - - >>> parse("2003-09-25T10") - datetime.datetime(2003, 9, 25, 10, 0) - - >>> parse("2003-09-25") - datetime.datetime(2003, 9, 25, 0, 0) - - -ISO format, without separators: - -.. doctest:: tz - - >>> parse("20030925T104941.5-0300") - datetime.datetime(2003, 9, 25, 10, 49, 41, 500000, - tzinfo=tzinfo=tzoffset(None, -10800)) - - >>> parse("20030925T104941-0300") - datetime.datetime(2003, 9, 25, 10, 49, 41, - tzinfo=tzoffset(None, -10800)) - - >>> parse("20030925T104941") - datetime.datetime(2003, 9, 25, 10, 49, 41) - - >>> parse("20030925T1049") - datetime.datetime(2003, 9, 25, 10, 49) - - >>> parse("20030925T10") - datetime.datetime(2003, 9, 25, 10, 0) - - >>> parse("20030925") - datetime.datetime(2003, 9, 25, 0, 0) - - -Everything together. - -.. doctest:: tz - - >>> parse("199709020900") - datetime.datetime(1997, 9, 2, 9, 0) - >>> parse("19970902090059") - datetime.datetime(1997, 9, 2, 9, 0, 59) - - -Different date orderings: - -.. doctest:: tz - - >>> parse("2003-09-25") - datetime.datetime(2003, 9, 25, 0, 0) - - >>> parse("2003-Sep-25") - datetime.datetime(2003, 9, 25, 0, 0) - - >>> parse("25-Sep-2003") - datetime.datetime(2003, 9, 25, 0, 0) - - >>> parse("Sep-25-2003") - datetime.datetime(2003, 9, 25, 0, 0) - - >>> parse("09-25-2003") - datetime.datetime(2003, 9, 25, 0, 0) - - >>> parse("25-09-2003") - datetime.datetime(2003, 9, 25, 0, 0) - - -Check some ambiguous dates: - -.. doctest:: tz - - >>> parse("10-09-2003") - datetime.datetime(2003, 10, 9, 0, 0) - - >>> parse("10-09-2003", dayfirst=True) - datetime.datetime(2003, 9, 10, 0, 0) - - >>> parse("10-09-03") - datetime.datetime(2003, 10, 9, 0, 0) - - >>> parse("10-09-03", yearfirst=True) - datetime.datetime(2010, 9, 3, 0, 0) - - -Other date separators are allowed: - -.. doctest:: tz - - >>> parse("2003.Sep.25") - datetime.datetime(2003, 9, 25, 0, 0) - - >>> parse("2003/09/25") - datetime.datetime(2003, 9, 25, 0, 0) - - -Even with spaces: - -.. doctest:: tz - - >>> parse("2003 Sep 25") - datetime.datetime(2003, 9, 25, 0, 0) - - >>> parse("2003 09 25") - datetime.datetime(2003, 9, 25, 0, 0) - - -Hours with letters work: - -.. doctest:: tz - - >>> parse("10h36m28.5s", default=DEFAULT) - datetime.datetime(2003, 9, 25, 10, 36, 28, 500000) - - >>> parse("01s02h03m", default=DEFAULT) - datetime.datetime(2003, 9, 25, 2, 3, 1) - - >>> parse("01h02m03", default=DEFAULT) - datetime.datetime(2003, 9, 3, 1, 2) - - >>> parse("01h02", default=DEFAULT) - datetime.datetime(2003, 9, 2, 1, 0) - - >>> parse("01h02s", default=DEFAULT) - datetime.datetime(2003, 9, 25, 1, 0, 2) - - -With AM/PM: - -.. doctest:: tz - - >>> parse("10h am", default=DEFAULT) - datetime.datetime(2003, 9, 25, 10, 0) - - >>> parse("10pm", default=DEFAULT) - datetime.datetime(2003, 9, 25, 22, 0) - - >>> parse("12:00am", default=DEFAULT) - datetime.datetime(2003, 9, 25, 0, 0) - - >>> parse("12pm", default=DEFAULT) - datetime.datetime(2003, 9, 25, 12, 0) - - -Some special treating for ''pertain'' relations: - -.. doctest:: tz - - >>> parse("Sep 03", default=DEFAULT) - datetime.datetime(2003, 9, 3, 0, 0) - - >>> parse("Sep of 03", default=DEFAULT) - datetime.datetime(2003, 9, 25, 0, 0) - - -Fuzzy parsing: - -.. doctest:: tz - - >>> s = "Today is 25 of September of 2003, exactly " \ - ... "at 10:49:41 with timezone -03:00." - >>> parse(s, fuzzy=True) - datetime.datetime(2003, 9, 25, 10, 49, 41, - tzinfo=tzoffset(None, -10800)) - - -Other random formats: - -.. doctest:: tz - - >>> parse("Wed, July 10, '96") - datetime.datetime(1996, 7, 10, 0, 0) - - >>> parse("1996.07.10 AD at 15:08:56 PDT", ignoretz=True) - datetime.datetime(1996, 7, 10, 15, 8, 56) - - >>> parse("Tuesday, April 12, 1952 AD 3:30:42pm PST", ignoretz=True) - datetime.datetime(1952, 4, 12, 15, 30, 42) - - >>> parse("November 5, 1994, 8:15:30 am EST", ignoretz=True) - datetime.datetime(1994, 11, 5, 8, 15, 30) - - >>> parse("3rd of May 2001") - datetime.datetime(2001, 5, 3, 0, 0) - - >>> parse("5:50 A.M. on June 13, 1990") - datetime.datetime(1990, 6, 13, 5, 50) - - -tzutc examples --------------- - -.. doctest:: tzutc - - >>> from datetime import * - >>> from dateutil.tz import * - - >>> datetime.now() - datetime.datetime(2003, 9, 27, 9, 40, 1, 521290) - - >>> datetime.now(tzutc()) - datetime.datetime(2003, 9, 27, 12, 40, 12, 156379, tzinfo=tzutc()) - - >>> datetime.now(tzutc()).tzname() - 'UTC' - - -tzoffset examples ------------------ - -.. doctest:: tzoffset - - >>> from datetime import * - >>> from dateutil.tz import * - - >>> datetime.now(tzoffset("BRST", -10800)) - datetime.datetime(2003, 9, 27, 9, 52, 43, 624904, - tzinfo=tzinfo=tzoffset('BRST', -10800)) - - >>> datetime.now(tzoffset("BRST", -10800)).tzname() - 'BRST' - - >>> datetime.now(tzoffset("BRST", -10800)).astimezone(tzutc()) - datetime.datetime(2003, 9, 27, 12, 53, 11, 446419, - tzinfo=tzutc()) - - -tzlocal examples ----------------- - -.. doctest:: tzlocal - - >>> from datetime import * - >>> from dateutil.tz import * - - >>> datetime.now(tzlocal()) - datetime.datetime(2003, 9, 27, 10, 1, 43, 673605, - tzinfo=tzlocal()) - - >>> datetime.now(tzlocal()).tzname() - 'BRST' - - >>> datetime.now(tzlocal()).astimezone(tzoffset(None, 0)) - datetime.datetime(2003, 9, 27, 13, 3, 0, 11493, - tzinfo=tzoffset(None, 0)) - - -tzstr examples --------------- -Here are examples of the recognized formats: - - * `EST5EDT` - * `EST5EDT,4,0,6,7200,10,0,26,7200,3600` - * `EST5EDT,4,1,0,7200,10,-1,0,7200,3600` - * `EST5EDT4,M4.1.0/02:00:00,M10-5-0/02:00` - * `EST5EDT4,95/02:00:00,298/02:00` - * `EST5EDT4,J96/02:00:00,J299/02:00` - -Notice that if daylight information is not present, but a -daylight abbreviation was provided, `tzstr` will follow the -convention of using the first sunday of April to start daylight -saving, and the last sunday of October to end it. If start or -end time is not present, 2AM will be used, and if the daylight -offset is not present, the standard offset plus one hour will -be used. This convention is the same as used in the GNU libc. - -This also means that some of the above examples are exactly -equivalent, and all of these examples are equivalent -in the year of 2003. - -Here is the example mentioned in the - -[http://www.python.org/doc/current/lib/module-time.html time module documentation]. - - -.. doctest:: tzstr - - >>> os.environ['TZ'] = 'EST+05EDT,M4.1.0,M10.5.0' - >>> time.tzset() - >>> time.strftime('%X %x %Z') - '02:07:36 05/08/03 EDT' - >>> os.environ['TZ'] = 'AEST-10AEDT-11,M10.5.0,M3.5.0' - >>> time.tzset() - >>> time.strftime('%X %x %Z') - '16:08:12 05/08/03 AEST' - - -And here is an example showing the same information using `tzstr`, -without touching system settings. - -.. doctest:: tzstr - - >>> tz1 = tzstr('EST+05EDT,M4.1.0,M10.5.0') - >>> tz2 = tzstr('AEST-10AEDT-11,M10.5.0,M3.5.0') - >>> dt = datetime(2003, 5, 8, 2, 7, 36, tzinfo=tz1) - >>> dt.strftime('%X %x %Z') - '02:07:36 05/08/03 EDT' - >>> dt.astimezone(tz2).strftime('%X %x %Z') - '16:07:36 05/08/03 AEST' - - -Are these really equivalent? - -.. doctest:: tzstr - - >>> tzstr('EST5EDT') == tzstr('EST5EDT,4,1,0,7200,10,-1,0,7200,3600') - True - - -Check the daylight limit. - -.. doctest:: tzstr - - >>> datetime(2003, 4, 6, 1, 59, tzinfo=tz).tzname() - 'EST' - >>> datetime(2003, 4, 6, 2, 00, tzinfo=tz).tzname() - 'EDT' - >>> datetime(2003, 10, 26, 0, 59, tzinfo=tz).tzname() - 'EDT' - >>> datetime(2003, 10, 26, 1, 00, tzinfo=tz).tzname() - 'EST' - - -tzrange examples ----------------- - -.. doctest:: tzrange - - >>> tzstr('EST5EDT') == tzrange("EST", -18000, "EDT") - True - - >>> from dateutil.relativedelta import * - >>> range1 = tzrange("EST", -18000, "EDT") - >>> range2 = tzrange("EST", -18000, "EDT", -14400, - ... relativedelta(hours=+2, month=4, day=1, - weekday=SU(+1)), - ... relativedelta(hours=+1, month=10, day=31, - weekday=SU(-1))) - >>> tzstr('EST5EDT') == range1 == range2 - True - - -Notice a minor detail in the last example: while the DST should end -at 2AM, the delta will catch 1AM. That's because the daylight saving -time should end at 2AM standard time (the difference between STD and -DST is 1h in the given example) instead of the DST time. That's how -the `tzinfo` subtypes should deal with the extra hour that happens -when going back to the standard time. Check - -[http://www.python.org/doc/current/lib/datetime-tzinfo.html tzinfo documentation] - -for more information. - -tzfile examples ---------------- - -.. doctest:: tzfile - - >>> tz = tzfile("/etc/localtime") - >>> datetime.now(tz) - datetime.datetime(2003, 9, 27, 12, 3, 48, 392138, - tzinfo=tzfile('/etc/localtime')) - - >>> datetime.now(tz).astimezone(tzutc()) - datetime.datetime(2003, 9, 27, 15, 3, 53, 70863, - tzinfo=tzutc()) - - >>> datetime.now(tz).tzname() - 'BRST' - >>> datetime(2003, 1, 1, tzinfo=tz).tzname() - 'BRDT' - - -Check the daylight limit. - -.. doctest:: tzfile - - >>> tz = tzfile('/usr/share/zoneinfo/EST5EDT') - >>> datetime(2003, 4, 6, 1, 59, tzinfo=tz).tzname() - 'EST' - >>> datetime(2003, 4, 6, 2, 00, tzinfo=tz).tzname() - 'EDT' - >>> datetime(2003, 10, 26, 0, 59, tzinfo=tz).tzname() - 'EDT' - >>> datetime(2003, 10, 26, 1, 00, tzinfo=tz).tzname() - 'EST' - - -tzical examples ---------------- -Here is a sample file extracted from the RFC. This file defines -the `EST5EDT` timezone, and will be used in the following example. - - BEGIN:VTIMEZONE - TZID:US-Eastern - LAST-MODIFIED:19870101T000000Z - TZURL:http://zones.stds_r_us.net/tz/US-Eastern - BEGIN:STANDARD - DTSTART:19671029T020000 - RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10 - TZOFFSETFROM:-0400 - TZOFFSETTO:-0500 - TZNAME:EST - END:STANDARD - BEGIN:DAYLIGHT - DTSTART:19870405T020000 - RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4 - TZOFFSETFROM:-0500 - TZOFFSETTO:-0400 - TZNAME:EDT - END:DAYLIGHT - END:VTIMEZONE - -And here is an example exploring a `tzical` type: - -.. doctest:: tzfile - - >>> from dateutil.tz import *; from datetime import * - - >>> tz = tzical('EST5EDT.ics') - >>> tz.keys() - ['US-Eastern'] - - >>> est = tz.get('US-Eastern') - >>> est - - - >>> datetime.now(est) - datetime.datetime(2003, 10, 6, 19, 44, 18, 667987, - tzinfo=) - - >>> est == tz.get() - True - - -Let's check the daylight ranges, as usual: - -.. doctest:: tzfile - - >>> datetime(2003, 4, 6, 1, 59, tzinfo=est).tzname() - 'EST' - >>> datetime(2003, 4, 6, 2, 00, tzinfo=est).tzname() - 'EDT' - - >>> datetime(2003, 10, 26, 0, 59, tzinfo=est).tzname() - 'EDT' - >>> datetime(2003, 10, 26, 1, 00, tzinfo=est).tzname() - 'EST' - - -tzwin examples --------------- - -.. doctest:: tzwin - - >>> tz = tzwin("E. South America Standard Time") - - -tzwinlocal examples -------------------- - - -.. doctest:: tzwinlocal - - >>> tz = tzwinlocal() - -# vim:ts=4:sw=4:et diff --git a/docs/index.rst b/docs/index.rst deleted file mode 100644 index 5d252f6..0000000 --- a/docs/index.rst +++ /dev/null @@ -1,31 +0,0 @@ -.. dateutil documentation master file, created by - sphinx-quickstart on Thu Nov 20 23:18:41 2014. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - - -.. include:: ../README.rst - -Documentation -============= -Contents: - -.. toctree:: - :maxdepth: 2 - - self - easter - parser - relativedelta - rrule - tz - zoneinfo - examples - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` - diff --git a/docs/make.bat b/docs/make.bat deleted file mode 100644 index 518406a..0000000 --- a/docs/make.bat +++ /dev/null @@ -1,242 +0,0 @@ -@ECHO OFF - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set BUILDDIR=_build -set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . -set I18NSPHINXOPTS=%SPHINXOPTS% . -if NOT "%PAPER%" == "" ( - set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% - set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% -) - -if "%1" == "" goto help - -if "%1" == "help" ( - :help - echo.Please use `make ^` where ^ is one of - echo. html to make standalone HTML files - echo. dirhtml to make HTML files named index.html in directories - echo. singlehtml to make a single large HTML file - echo. pickle to make pickle files - echo. json to make JSON files - echo. htmlhelp to make HTML files and a HTML help project - echo. qthelp to make HTML files and a qthelp project - echo. devhelp to make HTML files and a Devhelp project - echo. epub to make an epub - echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter - echo. text to make text files - echo. man to make manual pages - echo. texinfo to make Texinfo files - echo. gettext to make PO message catalogs - echo. changes to make an overview over all changed/added/deprecated items - echo. xml to make Docutils-native XML files - echo. pseudoxml to make pseudoxml-XML files for display purposes - echo. linkcheck to check all external links for integrity - echo. doctest to run all doctests embedded in the documentation if enabled - goto end -) - -if "%1" == "clean" ( - for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i - del /q /s %BUILDDIR%\* - goto end -) - - -%SPHINXBUILD% 2> nul -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ - exit /b 1 -) - -if "%1" == "html" ( - %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/html. - goto end -) - -if "%1" == "dirhtml" ( - %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. - goto end -) - -if "%1" == "singlehtml" ( - %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. - goto end -) - -if "%1" == "pickle" ( - %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the pickle files. - goto end -) - -if "%1" == "json" ( - %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the JSON files. - goto end -) - -if "%1" == "htmlhelp" ( - %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run HTML Help Workshop with the ^ -.hhp project file in %BUILDDIR%/htmlhelp. - goto end -) - -if "%1" == "qthelp" ( - %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run "qcollectiongenerator" with the ^ -.qhcp project file in %BUILDDIR%/qthelp, like this: - echo.^> qcollectiongenerator %BUILDDIR%\qthelp\dateutil.qhcp - echo.To view the help file: - echo.^> assistant -collectionFile %BUILDDIR%\qthelp\dateutil.ghc - goto end -) - -if "%1" == "devhelp" ( - %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. - goto end -) - -if "%1" == "epub" ( - %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The epub file is in %BUILDDIR%/epub. - goto end -) - -if "%1" == "latex" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "latexpdf" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - cd %BUILDDIR%/latex - make all-pdf - cd %BUILDDIR%/.. - echo. - echo.Build finished; the PDF files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "latexpdfja" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - cd %BUILDDIR%/latex - make all-pdf-ja - cd %BUILDDIR%/.. - echo. - echo.Build finished; the PDF files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "text" ( - %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The text files are in %BUILDDIR%/text. - goto end -) - -if "%1" == "man" ( - %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The manual pages are in %BUILDDIR%/man. - goto end -) - -if "%1" == "texinfo" ( - %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. - goto end -) - -if "%1" == "gettext" ( - %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The message catalogs are in %BUILDDIR%/locale. - goto end -) - -if "%1" == "changes" ( - %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes - if errorlevel 1 exit /b 1 - echo. - echo.The overview file is in %BUILDDIR%/changes. - goto end -) - -if "%1" == "linkcheck" ( - %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck - if errorlevel 1 exit /b 1 - echo. - echo.Link check complete; look for any errors in the above output ^ -or in %BUILDDIR%/linkcheck/output.txt. - goto end -) - -if "%1" == "doctest" ( - %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest - if errorlevel 1 exit /b 1 - echo. - echo.Testing of doctests in the sources finished, look at the ^ -results in %BUILDDIR%/doctest/output.txt. - goto end -) - -if "%1" == "xml" ( - %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The XML files are in %BUILDDIR%/xml. - goto end -) - -if "%1" == "pseudoxml" ( - %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. - goto end -) - -:end diff --git a/docs/parser.rst b/docs/parser.rst deleted file mode 100644 index c579974..0000000 --- a/docs/parser.rst +++ /dev/null @@ -1,6 +0,0 @@ -====== -parser -====== -.. automodule:: dateutil.parser - :members: - :undoc-members: diff --git a/docs/relativedelta.rst b/docs/relativedelta.rst deleted file mode 100644 index 23c52c7..0000000 --- a/docs/relativedelta.rst +++ /dev/null @@ -1,6 +0,0 @@ -============= -relativedelta -============= -.. automodule:: dateutil.relativedelta - :members: - :undoc-members: diff --git a/docs/rrule.rst b/docs/rrule.rst deleted file mode 100644 index 561a6fa..0000000 --- a/docs/rrule.rst +++ /dev/null @@ -1,8 +0,0 @@ -===== -rrule -===== -The rrule module offers a small, complete, and very fast, implementation of the recurrence rules documented in the iCalendar RFC, including support for caching of results. - -.. automodule:: dateutil.rrule - :members: - :undoc-members: diff --git a/docs/tz.rst b/docs/tz.rst deleted file mode 100644 index 5fd5bf5..0000000 --- a/docs/tz.rst +++ /dev/null @@ -1,6 +0,0 @@ -== -tz -== -.. automodule:: dateutil.tz - :members: - :undoc-members: diff --git a/docs/zoneinfo.rst b/docs/zoneinfo.rst deleted file mode 100644 index 99591f9..0000000 --- a/docs/zoneinfo.rst +++ /dev/null @@ -1,16 +0,0 @@ -======== -zoneinfo -======== -.. automodule:: dateutil.zoneinfo - :members: - :undoc-members: - -zonefile_metadata ------------------ -The zonefile metadata defines the version and exact location of -the timezone database to download. It is used in the :ref:`updatezinfo.py` -script. A json encoded file is included in the source-code, and -within each tar file we produce. The json file is attached here: - -.. literalinclude:: ../zonefile_metadata.json - :language: json diff --git a/example.py b/example.py new file mode 100644 index 0000000..ffa78e7 --- /dev/null +++ b/example.py @@ -0,0 +1,15 @@ +from dateutil.relativedelta import * +from dateutil.easter import * +from dateutil.rrule import * +from dateutil.parser import * +from datetime import * +import subprocess +import os +now = parse(subprocess.getoutput("date")) +today = now.date() +year = rrule(YEARLY, bymonth=8, bymonthday=13, byweekday=FR)[0].year +rdelta = relativedelta(easter(year), today) +print("Today is:", today) +print("Year with next Aug 13th on a Friday is:", year) +print("How far is the Easter of that year:", rdelta) +print("And the Easter of that year is:", today+rdelta) diff --git a/sandbox/rrulewrapper.py b/sandbox/rrulewrapper.py new file mode 100644 index 0000000..da4fa85 --- /dev/null +++ b/sandbox/rrulewrapper.py @@ -0,0 +1,16 @@ +from rrule import * + +class rrulewrapper(object): + def __init__(self, freq, **kwargs): + self._construct = kwargs.copy() + self._construct["freq"] = freq + self._rrule = rrule(**self._construct) + + def __getattr__(self, name): + if name in self.__dict__: + return self.__dict__[name] + return getattr(self._rrule, name) + + def set(self, **kwargs): + self._construct.update(kwargs) + self._rrule = rrule(**self._construct) diff --git a/sandbox/scheduler.py b/sandbox/scheduler.py new file mode 100644 index 0000000..62794d5 --- /dev/null +++ b/sandbox/scheduler.py @@ -0,0 +1,158 @@ +# -*- coding: utf-8 -*- +""" +Copyright (c) 2003-2005 Gustavo Niemeyer + +This module offers extensions to the standard Python +datetime module. +""" +__author__ = "Tomi Pieviläinen " +__license__ = "Simplified BSD" + +import datetime +import _thread +import signal +import time + +class sched(object): + + def __init__(self, rrule, + tolerance=None, last=None, + execute=None, args=None, kwargs=None): + self._rrule = rrule + if tolerance: + self._tolerance = datetime.timedelta(seconds=tolerance) + else: + self._tolerance = None + self._last = last + self._execute = execute + self._args = args or () + self._kwargs = kwargs or {} + + def last(self): + return self._last + + def next(self, now=None): + if not now: + now = datetime.datetime.now() + return self._rrule.after(now) + + def check(self, now=None, readonly=False): + if not now: + now = datetime.datetime.now() + item = self._rrule.before(now, inc=True) + if (item is None or item == self._last or + (self._tolerance and item+self._tolerance < now)): + return None + if not readonly: + self._last = item + if self._execute: + self._execute(*self._args, **self._kwargs) + return item + + +class schedset(object): + def __init__(self): + self._scheds = [] + + def add(self, sched): + self._scheds.append(sched) + + def next(self, now=None): + if not now: + now = datetime.datetime.now() + res = None + for sched in self._scheds: + next = sched.next(now) + if next and (not res or next < res): + res = next + return res + + def check(self, now=None, readonly=False): + if not now: + now = datetime.datetime.now() + res = False + for sched in self._scheds: + if sched.check(now, readonly): + res = True + return res + + +class schedthread(object): + + def __init__(self, sched, lock=None): + self._sched = sched + self._lock = lock + self._running = False + + def running(self): + return self._running + + def run(self): + self._running = True + _thread.start_new_thread(self._loop, ()) + + def stop(self): + self._running = False + + def _loop(self): + while self._running: + if self._lock: + self._lock.acquire() + now = datetime.datetime.now() + self._sched.check(now) + if self._lock: + self._lock.release() + seconds = _seconds_left(self._sched.next(now)) + if seconds is None: + self._running = False + break + if self._running: + time.sleep(seconds) + + +class schedalarm(object): + + def __init__(self, sched, lock=None): + self._sched = sched + self._lock = lock + self._running = False + + def running(self): + return self._running + + def run(self): + self._running = True + signal.signal(signal.SIGALRM, self._handler) + self._handler(None, None) + + def stop(self): + self._running = False + + def _handler(self, sig, frame): + while self._running: + if self._lock: + self._lock.acquire() + now = datetime.datetime.now() + self._sched.check(now) + if self._lock: + self._lock.release() + if self._running: + seconds = _seconds_left(self._sched.next(now)) + if seconds: + signal.alarm(seconds) + break + elif seconds is None: + self._running = False + break + + +def _seconds_left(next): + if not next: + return None + now = datetime.datetime.now() + delta = next-now + seconds = delta.days*86400+delta.seconds + if seconds < 0: + seconds = 0 + return seconds + diff --git a/setup.cfg b/setup.cfg index 3c6e79c..b3720b5 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,2 +1,2 @@ -[bdist_wheel] -universal=1 +[bdist_rpm] +doc_files = README LICENSE diff --git a/setup.py b/setup.py old mode 100644 new mode 100755 index 02db365..b6d8cdf --- a/setup.py +++ b/setup.py @@ -1,6 +1,7 @@ #!/usr/bin/python -from os.path import isfile +from os.path import isfile, join import codecs +import glob import os import re @@ -13,27 +14,28 @@ TOPDIR = os.path.dirname(__file__) or "." VERSION = re.search('__version__ = "([^"]+)"', - codecs.open(TOPDIR + "/dateutil/__init__.py", - encoding='utf-8').read()).group(1) + codecs.open(TOPDIR + "/dateutil/__init__.py", encoding='utf-8').read()).group(1) setup(name="python-dateutil", - version=VERSION, - description="Extensions to the standard Python datetime module", - author="Yaron de Leeuw", - author_email="me@jarondl.net", - url="https://dateutil.readthedocs.org", - license="Simplified BSD", - long_description=""" -The dateutil module provides powerful extensions to the + version = VERSION, + description = "Extensions to the standard Python datetime module", + author = "Tomi Pievilaeinen", + author_email = "tomi.pievilainen@iki.fi", + url = "http://labix.org/python-dateutil", + license = "Simplified BSD", + long_description = +"""\ +The dateutil module provides powerful extensions to the datetime module available in the Python standard library. """, - packages=["dateutil", "dateutil.zoneinfo"], - package_data={"dateutil.zoneinfo": ["dateutil-zoneinfo.tar.gz"]}, - zip_safe=True, - requires=["six"], - install_requires=["six >=1.5"], # XXX fix when packaging is sane again - classifiers=[ + packages = ["dateutil", "dateutil.zoneinfo"], + package_data = {"": ["*.tar.gz"]}, + include_package_data = True, + zip_safe = False, + requires = ["six"], + install_requires = ["six"], # XXX fix when packaging is sane again + classifiers = [ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'License :: OSI Approved :: BSD License', @@ -44,8 +46,6 @@ 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.2', 'Programming Language :: Python :: 3.3', - 'Programming Language :: Python :: 3.4', 'Topic :: Software Development :: Libraries', - ], - test_suite="dateutil.test.test" + ] ) diff --git a/dateutil/test/test.py b/test.py old mode 100644 new mode 100755 similarity index 60% rename from dateutil/test/test.py rename to test.py index f5ed765..0388fbc --- a/dateutil/test/test.py +++ b/test.py @@ -1,25 +1,23 @@ -# -*- coding: utf-8 -*- +#!/usr/bin/env python +# -*- encoding: utf-8 -*- from __future__ import unicode_literals +from six import StringIO, BytesIO, PY3 +import unittest import calendar import base64 -import sys import os -import time as _time - -from six import StringIO, BytesIO, PY3 - -try: - # python2.6 unittest has no skipUnless. So we use unittest2. - # if you have python >= 2.7, you don't need unittest2, but it won't harm - import unittest2 as unittest -except ImportError: - import unittest -MISSING_TARBALL = ("This test fails if you don't have the dateutil " - "timezone file installed. Please read the README") - -from datetime import * +# Add build directory to search path +if os.path.exists("build"): + from distutils.util import get_platform + import sys + if sys.version_info >= (3, 2): + s = "build/lib" + else: + s = "build/lib.%s-%.3s" % (get_platform(), sys.version) + s = os.path.join(os.getcwd(), s) + sys.path.insert(0, s) from dateutil.relativedelta import * from dateutil.parser import * @@ -28,43 +26,13 @@ from dateutil.tz import * from dateutil import zoneinfo -try: - from dateutil import tzwin -except ImportError: - pass +from datetime import * class RelativeDeltaTest(unittest.TestCase): now = datetime(2003, 9, 17, 20, 54, 47, 282310) today = date(2003, 9, 17) - def testInheritance(self): - # Ensure that relativedelta is inheritance-friendly. - class rdChildClass(relativedelta): - pass - - ccRD = rdChildClass(years=1, months=1, days=1, leapdays=1, weeks=1, - hours=1, minutes=1, seconds=1, microseconds=1) - - rd = relativedelta(years=1, months=1, days=1, leapdays=1, weeks=1, - hours=1, minutes=1, seconds=1, microseconds=1) - - self.assertEqual(type(ccRD + rd), type(ccRD), - msg='Addition does not inherit type.') - - self.assertEqual(type(ccRD - rd), type(ccRD), - msg='Subtraction does not inherit type.') - - self.assertEqual(type(-ccRD), type(ccRD), - msg='Negation does not inherit type.') - - self.assertEqual(type(ccRD * 5.0), type(ccRD), - msg='Multiplication does not inherit type.') - - self.assertEqual(type(ccRD / 5.0), type(ccRD), - msg='Division does not inherit type.') - - def testNextMonth(self): self.assertEqual(self.now+relativedelta(months=+1), datetime(2003, 10, 17, 20, 54, 47, 282310)) @@ -72,7 +40,6 @@ def testNextMonth(self): def testNextMonthPlusOneWeek(self): self.assertEqual(self.now+relativedelta(months=+1, weeks=+1), datetime(2003, 10, 24, 20, 54, 47, 282310)) - def testNextMonthPlusOneWeek10am(self): self.assertEqual(self.today + relativedelta(months=+1, weeks=+1, hour=10), @@ -129,6 +96,7 @@ def testNextWednesdayIsToday(self): self.assertEqual(self.today+relativedelta(weekday=WE), date(2003, 9, 17)) + def testNextWenesdayNotToday(self): self.assertEqual(self.today+relativedelta(days=+1, weekday=WE), date(2003, 9, 24)) @@ -222,35 +190,12 @@ def testDivision(self): self.assertEqual(datetime(2000, 1, 1) + relativedelta(days=28) / 28, datetime(2000, 1, 2)) - def testBoolean(self): - self.assertFalse(relativedelta(days=0)) - self.assertTrue(relativedelta(days=1)) - - def testWeeks(self): - # Test that the weeks property is working properly. - rd = relativedelta(years=4, months=2, weeks=8, days=6) - self.assertEqual((rd.weeks, rd.days), (8, 8 * 7 + 6)) - - rd.weeks = 3 - self.assertEqual((rd.weeks, rd.days), (3, 3 * 7 + 6)) - - class RRuleTest(unittest.TestCase): - def _rrulestr_reverse_test(self, rule): - """ - Call with an `rrule` and it will test that `str(rrule)` generates a - string which generates the same `rrule` as the input when passed to - `rrulestr()` - """ - rr_str = str(rule) - rrulestr_rrule = rrulestr(rr_str) - - self.assertEqual(list(rule), list(rrulestr_rrule)) def testYearly(self): self.assertEqual(list(rrule(YEARLY, count=3, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 9, 0), datetime(1998, 9, 2, 9, 0), datetime(1999, 9, 2, 9, 0)]) @@ -259,7 +204,7 @@ def testYearlyInterval(self): self.assertEqual(list(rrule(YEARLY, count=3, interval=2, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 9, 0), datetime(1999, 9, 2, 9, 0), datetime(2001, 9, 2, 9, 0)]) @@ -268,7 +213,7 @@ def testYearlyIntervalLarge(self): self.assertEqual(list(rrule(YEARLY, count=3, interval=100, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 9, 0), datetime(2097, 9, 2, 9, 0), datetime(2197, 9, 2, 9, 0)]) @@ -277,7 +222,7 @@ def testYearlyByMonth(self): self.assertEqual(list(rrule(YEARLY, count=3, bymonth=(1, 3), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 1, 2, 9, 0), datetime(1998, 3, 2, 9, 0), datetime(1999, 1, 2, 9, 0)]) @@ -286,7 +231,7 @@ def testYearlyByMonthDay(self): self.assertEqual(list(rrule(YEARLY, count=3, bymonthday=(1, 3), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 3, 9, 0), datetime(1997, 10, 1, 9, 0), datetime(1997, 10, 3, 9, 0)]) @@ -296,7 +241,7 @@ def testYearlyByMonthAndMonthDay(self): count=3, bymonth=(1, 3), bymonthday=(5, 7), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 1, 5, 9, 0), datetime(1998, 1, 7, 9, 0), datetime(1998, 3, 5, 9, 0)]) @@ -305,7 +250,7 @@ def testYearlyByWeekDay(self): self.assertEqual(list(rrule(YEARLY, count=3, byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 4, 9, 0), datetime(1997, 9, 9, 9, 0)]) @@ -314,7 +259,7 @@ def testYearlyByNWeekDay(self): self.assertEqual(list(rrule(YEARLY, count=3, byweekday=(TU(1), TH(-1)), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 12, 25, 9, 0), datetime(1998, 1, 6, 9, 0), datetime(1998, 12, 31, 9, 0)]) @@ -323,7 +268,7 @@ def testYearlyByNWeekDayLarge(self): self.assertEqual(list(rrule(YEARLY, count=3, byweekday=(TU(3), TH(-3)), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 12, 11, 9, 0), datetime(1998, 1, 20, 9, 0), datetime(1998, 12, 17, 9, 0)]) @@ -333,7 +278,7 @@ def testYearlyByMonthAndWeekDay(self): count=3, bymonth=(1, 3), byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 1, 1, 9, 0), datetime(1998, 1, 6, 9, 0), datetime(1998, 1, 8, 9, 0)]) @@ -343,7 +288,7 @@ def testYearlyByMonthAndNWeekDay(self): count=3, bymonth=(1, 3), byweekday=(TU(1), TH(-1)), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 1, 6, 9, 0), datetime(1998, 1, 29, 9, 0), datetime(1998, 3, 3, 9, 0)]) @@ -355,7 +300,7 @@ def testYearlyByMonthAndNWeekDayLarge(self): count=3, bymonth=(1, 3), byweekday=(TU(3), TH(-3)), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 1, 15, 9, 0), datetime(1998, 1, 20, 9, 0), datetime(1998, 3, 12, 9, 0)]) @@ -365,7 +310,7 @@ def testYearlyByMonthDayAndWeekDay(self): count=3, bymonthday=(1, 3), byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 1, 1, 9, 0), datetime(1998, 2, 3, 9, 0), datetime(1998, 3, 3, 9, 0)]) @@ -376,7 +321,7 @@ def testYearlyByMonthAndMonthDayAndWeekDay(self): bymonth=(1, 3), bymonthday=(1, 3), byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 1, 1, 9, 0), datetime(1998, 3, 3, 9, 0), datetime(2001, 3, 1, 9, 0)]) @@ -385,7 +330,7 @@ def testYearlyByYearDay(self): self.assertEqual(list(rrule(YEARLY, count=4, byyearday=(1, 100, 200, 365), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 12, 31, 9, 0), datetime(1998, 1, 1, 9, 0), datetime(1998, 4, 10, 9, 0), @@ -395,7 +340,7 @@ def testYearlyByYearDayNeg(self): self.assertEqual(list(rrule(YEARLY, count=4, byyearday=(-365, -266, -166, -1), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 12, 31, 9, 0), datetime(1998, 1, 1, 9, 0), datetime(1998, 4, 10, 9, 0), @@ -406,7 +351,7 @@ def testYearlyByMonthAndYearDay(self): count=4, bymonth=(4, 7), byyearday=(1, 100, 200, 365), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 4, 10, 9, 0), datetime(1998, 7, 19, 9, 0), datetime(1999, 4, 10, 9, 0), @@ -417,7 +362,7 @@ def testYearlyByMonthAndYearDayNeg(self): count=4, bymonth=(4, 7), byyearday=(-365, -266, -166, -1), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 4, 10, 9, 0), datetime(1998, 7, 19, 9, 0), datetime(1999, 4, 10, 9, 0), @@ -427,7 +372,7 @@ def testYearlyByWeekNo(self): self.assertEqual(list(rrule(YEARLY, count=3, byweekno=20, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 5, 11, 9, 0), datetime(1998, 5, 12, 9, 0), datetime(1998, 5, 13, 9, 0)]) @@ -439,7 +384,7 @@ def testYearlyByWeekNoAndWeekDay(self): count=3, byweekno=1, byweekday=MO, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 12, 29, 9, 0), datetime(1999, 1, 4, 9, 0), datetime(2000, 1, 3, 9, 0)]) @@ -451,7 +396,7 @@ def testYearlyByWeekNoAndWeekDayLarge(self): count=3, byweekno=52, byweekday=SU, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 12, 28, 9, 0), datetime(1998, 12, 27, 9, 0), datetime(2000, 1, 2, 9, 0)]) @@ -461,7 +406,7 @@ def testYearlyByWeekNoAndWeekDayLast(self): count=3, byweekno=-1, byweekday=SU, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 12, 28, 9, 0), datetime(1999, 1, 3, 9, 0), datetime(2000, 1, 2, 9, 0)]) @@ -470,7 +415,7 @@ def testYearlyByEaster(self): self.assertEqual(list(rrule(YEARLY, count=3, byeaster=0, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 4, 12, 9, 0), datetime(1999, 4, 4, 9, 0), datetime(2000, 4, 23, 9, 0)]) @@ -479,7 +424,7 @@ def testYearlyByEasterPos(self): self.assertEqual(list(rrule(YEARLY, count=3, byeaster=1, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 4, 13, 9, 0), datetime(1999, 4, 5, 9, 0), datetime(2000, 4, 24, 9, 0)]) @@ -488,7 +433,7 @@ def testYearlyByEasterNeg(self): self.assertEqual(list(rrule(YEARLY, count=3, byeaster=-1, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 4, 11, 9, 0), datetime(1999, 4, 3, 9, 0), datetime(2000, 4, 22, 9, 0)]) @@ -498,7 +443,7 @@ def testYearlyByWeekNoAndWeekDay53(self): count=3, byweekno=53, byweekday=MO, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 12, 28, 9, 0), datetime(2004, 12, 27, 9, 0), datetime(2009, 12, 28, 9, 0)]) @@ -507,7 +452,7 @@ def testYearlyByHour(self): self.assertEqual(list(rrule(YEARLY, count=3, byhour=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 18, 0), datetime(1998, 9, 2, 6, 0), datetime(1998, 9, 2, 18, 0)]) @@ -516,7 +461,7 @@ def testYearlyByMinute(self): self.assertEqual(list(rrule(YEARLY, count=3, byminute=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 9, 6), datetime(1997, 9, 2, 9, 18), datetime(1998, 9, 2, 9, 6)]) @@ -525,7 +470,7 @@ def testYearlyBySecond(self): self.assertEqual(list(rrule(YEARLY, count=3, bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 9, 0, 6), datetime(1997, 9, 2, 9, 0, 18), datetime(1998, 9, 2, 9, 0, 6)]) @@ -535,7 +480,7 @@ def testYearlyByHourAndMinute(self): count=3, byhour=(6, 18), byminute=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 18, 6), datetime(1997, 9, 2, 18, 18), datetime(1998, 9, 2, 6, 6)]) @@ -545,7 +490,7 @@ def testYearlyByHourAndSecond(self): count=3, byhour=(6, 18), bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 18, 0, 6), datetime(1997, 9, 2, 18, 0, 18), datetime(1998, 9, 2, 6, 0, 6)]) @@ -555,7 +500,7 @@ def testYearlyByMinuteAndSecond(self): count=3, byminute=(6, 18), bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 9, 6, 6), datetime(1997, 9, 2, 9, 6, 18), datetime(1997, 9, 2, 9, 18, 6)]) @@ -566,7 +511,7 @@ def testYearlyByHourAndMinuteAndSecond(self): byhour=(6, 18), byminute=(6, 18), bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 18, 6, 6), datetime(1997, 9, 2, 18, 6, 18), datetime(1997, 9, 2, 18, 18, 6)]) @@ -577,7 +522,7 @@ def testYearlyBySetPos(self): bymonthday=15, byhour=(6, 18), bysetpos=(3, -3), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 11, 15, 18, 0), datetime(1998, 2, 15, 6, 0), datetime(1998, 11, 15, 18, 0)]) @@ -585,7 +530,7 @@ def testYearlyBySetPos(self): def testMonthly(self): self.assertEqual(list(rrule(MONTHLY, count=3, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 9, 0), datetime(1997, 10, 2, 9, 0), datetime(1997, 11, 2, 9, 0)]) @@ -594,7 +539,7 @@ def testMonthlyInterval(self): self.assertEqual(list(rrule(MONTHLY, count=3, interval=2, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 9, 0), datetime(1997, 11, 2, 9, 0), datetime(1998, 1, 2, 9, 0)]) @@ -603,7 +548,7 @@ def testMonthlyIntervalLarge(self): self.assertEqual(list(rrule(MONTHLY, count=3, interval=18, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 9, 0), datetime(1999, 3, 2, 9, 0), datetime(2000, 9, 2, 9, 0)]) @@ -612,16 +557,17 @@ def testMonthlyByMonth(self): self.assertEqual(list(rrule(MONTHLY, count=3, bymonth=(1, 3), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 1, 2, 9, 0), datetime(1998, 3, 2, 9, 0), datetime(1999, 1, 2, 9, 0)]) + def testMonthlyByMonthDay(self): self.assertEqual(list(rrule(MONTHLY, count=3, bymonthday=(1, 3), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 3, 9, 0), datetime(1997, 10, 1, 9, 0), datetime(1997, 10, 3, 9, 0)]) @@ -631,7 +577,7 @@ def testMonthlyByMonthAndMonthDay(self): count=3, bymonth=(1, 3), bymonthday=(5, 7), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 1, 5, 9, 0), datetime(1998, 1, 7, 9, 0), datetime(1998, 3, 5, 9, 0)]) @@ -640,25 +586,16 @@ def testMonthlyByWeekDay(self): self.assertEqual(list(rrule(MONTHLY, count=3, byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 4, 9, 0), datetime(1997, 9, 9, 9, 0)]) - # Third Monday of the month - self.assertEqual(rrule(MONTHLY, - byweekday=(MO(+3)), - dtstart=datetime(1997, 9, 1)).between(datetime(1997, 9, 1), - datetime(1997, 12, 1)), - [datetime(1997, 9, 15, 0, 0), - datetime(1997, 10, 20, 0, 0), - datetime(1997, 11, 17, 0, 0)]) - def testMonthlyByNWeekDay(self): self.assertEqual(list(rrule(MONTHLY, count=3, byweekday=(TU(1), TH(-1)), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 25, 9, 0), datetime(1997, 10, 7, 9, 0)]) @@ -667,7 +604,7 @@ def testMonthlyByNWeekDayLarge(self): self.assertEqual(list(rrule(MONTHLY, count=3, byweekday=(TU(3), TH(-3)), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 11, 9, 0), datetime(1997, 9, 16, 9, 0), datetime(1997, 10, 16, 9, 0)]) @@ -677,7 +614,7 @@ def testMonthlyByMonthAndWeekDay(self): count=3, bymonth=(1, 3), byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 1, 1, 9, 0), datetime(1998, 1, 6, 9, 0), datetime(1998, 1, 8, 9, 0)]) @@ -687,7 +624,7 @@ def testMonthlyByMonthAndNWeekDay(self): count=3, bymonth=(1, 3), byweekday=(TU(1), TH(-1)), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 1, 6, 9, 0), datetime(1998, 1, 29, 9, 0), datetime(1998, 3, 3, 9, 0)]) @@ -697,7 +634,7 @@ def testMonthlyByMonthAndNWeekDayLarge(self): count=3, bymonth=(1, 3), byweekday=(TU(3), TH(-3)), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 1, 15, 9, 0), datetime(1998, 1, 20, 9, 0), datetime(1998, 3, 12, 9, 0)]) @@ -707,7 +644,7 @@ def testMonthlyByMonthDayAndWeekDay(self): count=3, bymonthday=(1, 3), byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 1, 1, 9, 0), datetime(1998, 2, 3, 9, 0), datetime(1998, 3, 3, 9, 0)]) @@ -718,7 +655,7 @@ def testMonthlyByMonthAndMonthDayAndWeekDay(self): bymonth=(1, 3), bymonthday=(1, 3), byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 1, 1, 9, 0), datetime(1998, 3, 3, 9, 0), datetime(2001, 3, 1, 9, 0)]) @@ -727,7 +664,7 @@ def testMonthlyByYearDay(self): self.assertEqual(list(rrule(MONTHLY, count=4, byyearday=(1, 100, 200, 365), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 12, 31, 9, 0), datetime(1998, 1, 1, 9, 0), datetime(1998, 4, 10, 9, 0), @@ -737,7 +674,7 @@ def testMonthlyByYearDayNeg(self): self.assertEqual(list(rrule(MONTHLY, count=4, byyearday=(-365, -266, -166, -1), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 12, 31, 9, 0), datetime(1998, 1, 1, 9, 0), datetime(1998, 4, 10, 9, 0), @@ -748,7 +685,7 @@ def testMonthlyByMonthAndYearDay(self): count=4, bymonth=(4, 7), byyearday=(1, 100, 200, 365), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 4, 10, 9, 0), datetime(1998, 7, 19, 9, 0), datetime(1999, 4, 10, 9, 0), @@ -759,17 +696,18 @@ def testMonthlyByMonthAndYearDayNeg(self): count=4, bymonth=(4, 7), byyearday=(-365, -266, -166, -1), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 4, 10, 9, 0), datetime(1998, 7, 19, 9, 0), datetime(1999, 4, 10, 9, 0), datetime(1999, 7, 19, 9, 0)]) + def testMonthlyByWeekNo(self): self.assertEqual(list(rrule(MONTHLY, count=3, byweekno=20, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 5, 11, 9, 0), datetime(1998, 5, 12, 9, 0), datetime(1998, 5, 13, 9, 0)]) @@ -781,7 +719,7 @@ def testMonthlyByWeekNoAndWeekDay(self): count=3, byweekno=1, byweekday=MO, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 12, 29, 9, 0), datetime(1999, 1, 4, 9, 0), datetime(2000, 1, 3, 9, 0)]) @@ -793,7 +731,7 @@ def testMonthlyByWeekNoAndWeekDayLarge(self): count=3, byweekno=52, byweekday=SU, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 12, 28, 9, 0), datetime(1998, 12, 27, 9, 0), datetime(2000, 1, 2, 9, 0)]) @@ -803,7 +741,7 @@ def testMonthlyByWeekNoAndWeekDayLast(self): count=3, byweekno=-1, byweekday=SU, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 12, 28, 9, 0), datetime(1999, 1, 3, 9, 0), datetime(2000, 1, 2, 9, 0)]) @@ -813,7 +751,7 @@ def testMonthlyByWeekNoAndWeekDay53(self): count=3, byweekno=53, byweekday=MO, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 12, 28, 9, 0), datetime(2004, 12, 27, 9, 0), datetime(2009, 12, 28, 9, 0)]) @@ -822,7 +760,7 @@ def testMonthlyByEaster(self): self.assertEqual(list(rrule(MONTHLY, count=3, byeaster=0, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 4, 12, 9, 0), datetime(1999, 4, 4, 9, 0), datetime(2000, 4, 23, 9, 0)]) @@ -831,7 +769,7 @@ def testMonthlyByEasterPos(self): self.assertEqual(list(rrule(MONTHLY, count=3, byeaster=1, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 4, 13, 9, 0), datetime(1999, 4, 5, 9, 0), datetime(2000, 4, 24, 9, 0)]) @@ -840,7 +778,7 @@ def testMonthlyByEasterNeg(self): self.assertEqual(list(rrule(MONTHLY, count=3, byeaster=-1, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 4, 11, 9, 0), datetime(1999, 4, 3, 9, 0), datetime(2000, 4, 22, 9, 0)]) @@ -849,7 +787,7 @@ def testMonthlyByHour(self): self.assertEqual(list(rrule(MONTHLY, count=3, byhour=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 18, 0), datetime(1997, 10, 2, 6, 0), datetime(1997, 10, 2, 18, 0)]) @@ -858,7 +796,7 @@ def testMonthlyByMinute(self): self.assertEqual(list(rrule(MONTHLY, count=3, byminute=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 9, 6), datetime(1997, 9, 2, 9, 18), datetime(1997, 10, 2, 9, 6)]) @@ -867,7 +805,7 @@ def testMonthlyBySecond(self): self.assertEqual(list(rrule(MONTHLY, count=3, bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 9, 0, 6), datetime(1997, 9, 2, 9, 0, 18), datetime(1997, 10, 2, 9, 0, 6)]) @@ -877,7 +815,7 @@ def testMonthlyByHourAndMinute(self): count=3, byhour=(6, 18), byminute=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 18, 6), datetime(1997, 9, 2, 18, 18), datetime(1997, 10, 2, 6, 6)]) @@ -887,7 +825,7 @@ def testMonthlyByHourAndSecond(self): count=3, byhour=(6, 18), bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 18, 0, 6), datetime(1997, 9, 2, 18, 0, 18), datetime(1997, 10, 2, 6, 0, 6)]) @@ -897,7 +835,7 @@ def testMonthlyByMinuteAndSecond(self): count=3, byminute=(6, 18), bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 9, 6, 6), datetime(1997, 9, 2, 9, 6, 18), datetime(1997, 9, 2, 9, 18, 6)]) @@ -908,7 +846,7 @@ def testMonthlyByHourAndMinuteAndSecond(self): byhour=(6, 18), byminute=(6, 18), bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 18, 6, 6), datetime(1997, 9, 2, 18, 6, 18), datetime(1997, 9, 2, 18, 18, 6)]) @@ -919,7 +857,7 @@ def testMonthlyBySetPos(self): bymonthday=(13, 17), byhour=(6, 18), bysetpos=(3, -3), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 13, 18, 0), datetime(1997, 9, 17, 6, 0), datetime(1997, 10, 13, 18, 0)]) @@ -927,7 +865,7 @@ def testMonthlyBySetPos(self): def testWeekly(self): self.assertEqual(list(rrule(WEEKLY, count=3, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 9, 9, 0), datetime(1997, 9, 16, 9, 0)]) @@ -936,7 +874,7 @@ def testWeeklyInterval(self): self.assertEqual(list(rrule(WEEKLY, count=3, interval=2, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 16, 9, 0), datetime(1997, 9, 30, 9, 0)]) @@ -945,7 +883,7 @@ def testWeeklyIntervalLarge(self): self.assertEqual(list(rrule(WEEKLY, count=3, interval=20, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 9, 0), datetime(1998, 1, 20, 9, 0), datetime(1998, 6, 9, 9, 0)]) @@ -954,7 +892,7 @@ def testWeeklyByMonth(self): self.assertEqual(list(rrule(WEEKLY, count=3, bymonth=(1, 3), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 1, 6, 9, 0), datetime(1998, 1, 13, 9, 0), datetime(1998, 1, 20, 9, 0)]) @@ -963,7 +901,7 @@ def testWeeklyByMonthDay(self): self.assertEqual(list(rrule(WEEKLY, count=3, bymonthday=(1, 3), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 3, 9, 0), datetime(1997, 10, 1, 9, 0), datetime(1997, 10, 3, 9, 0)]) @@ -973,7 +911,7 @@ def testWeeklyByMonthAndMonthDay(self): count=3, bymonth=(1, 3), bymonthday=(5, 7), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 1, 5, 9, 0), datetime(1998, 1, 7, 9, 0), datetime(1998, 3, 5, 9, 0)]) @@ -982,7 +920,7 @@ def testWeeklyByWeekDay(self): self.assertEqual(list(rrule(WEEKLY, count=3, byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 4, 9, 0), datetime(1997, 9, 9, 9, 0)]) @@ -991,7 +929,7 @@ def testWeeklyByNWeekDay(self): self.assertEqual(list(rrule(WEEKLY, count=3, byweekday=(TU(1), TH(-1)), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 4, 9, 0), datetime(1997, 9, 9, 9, 0)]) @@ -1004,7 +942,7 @@ def testWeeklyByMonthAndWeekDay(self): count=3, bymonth=(1, 3), byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 1, 1, 9, 0), datetime(1998, 1, 6, 9, 0), datetime(1998, 1, 8, 9, 0)]) @@ -1014,7 +952,7 @@ def testWeeklyByMonthAndNWeekDay(self): count=3, bymonth=(1, 3), byweekday=(TU(1), TH(-1)), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 1, 1, 9, 0), datetime(1998, 1, 6, 9, 0), datetime(1998, 1, 8, 9, 0)]) @@ -1024,7 +962,7 @@ def testWeeklyByMonthDayAndWeekDay(self): count=3, bymonthday=(1, 3), byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 1, 1, 9, 0), datetime(1998, 2, 3, 9, 0), datetime(1998, 3, 3, 9, 0)]) @@ -1035,7 +973,7 @@ def testWeeklyByMonthAndMonthDayAndWeekDay(self): bymonth=(1, 3), bymonthday=(1, 3), byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 1, 1, 9, 0), datetime(1998, 3, 3, 9, 0), datetime(2001, 3, 1, 9, 0)]) @@ -1044,7 +982,7 @@ def testWeeklyByYearDay(self): self.assertEqual(list(rrule(WEEKLY, count=4, byyearday=(1, 100, 200, 365), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 12, 31, 9, 0), datetime(1998, 1, 1, 9, 0), datetime(1998, 4, 10, 9, 0), @@ -1054,7 +992,7 @@ def testWeeklyByYearDayNeg(self): self.assertEqual(list(rrule(WEEKLY, count=4, byyearday=(-365, -266, -166, -1), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 12, 31, 9, 0), datetime(1998, 1, 1, 9, 0), datetime(1998, 4, 10, 9, 0), @@ -1065,7 +1003,7 @@ def testWeeklyByMonthAndYearDay(self): count=4, bymonth=(1, 7), byyearday=(1, 100, 200, 365), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 1, 1, 9, 0), datetime(1998, 7, 19, 9, 0), datetime(1999, 1, 1, 9, 0), @@ -1076,7 +1014,7 @@ def testWeeklyByMonthAndYearDayNeg(self): count=4, bymonth=(1, 7), byyearday=(-365, -266, -166, -1), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 1, 1, 9, 0), datetime(1998, 7, 19, 9, 0), datetime(1999, 1, 1, 9, 0), @@ -1086,7 +1024,7 @@ def testWeeklyByWeekNo(self): self.assertEqual(list(rrule(WEEKLY, count=3, byweekno=20, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 5, 11, 9, 0), datetime(1998, 5, 12, 9, 0), datetime(1998, 5, 13, 9, 0)]) @@ -1098,7 +1036,7 @@ def testWeeklyByWeekNoAndWeekDay(self): count=3, byweekno=1, byweekday=MO, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 12, 29, 9, 0), datetime(1999, 1, 4, 9, 0), datetime(2000, 1, 3, 9, 0)]) @@ -1110,7 +1048,7 @@ def testWeeklyByWeekNoAndWeekDayLarge(self): count=3, byweekno=52, byweekday=SU, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 12, 28, 9, 0), datetime(1998, 12, 27, 9, 0), datetime(2000, 1, 2, 9, 0)]) @@ -1120,7 +1058,7 @@ def testWeeklyByWeekNoAndWeekDayLast(self): count=3, byweekno=-1, byweekday=SU, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 12, 28, 9, 0), datetime(1999, 1, 3, 9, 0), datetime(2000, 1, 2, 9, 0)]) @@ -1130,7 +1068,7 @@ def testWeeklyByWeekNoAndWeekDay53(self): count=3, byweekno=53, byweekday=MO, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 12, 28, 9, 0), datetime(2004, 12, 27, 9, 0), datetime(2009, 12, 28, 9, 0)]) @@ -1139,7 +1077,7 @@ def testWeeklyByEaster(self): self.assertEqual(list(rrule(WEEKLY, count=3, byeaster=0, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 4, 12, 9, 0), datetime(1999, 4, 4, 9, 0), datetime(2000, 4, 23, 9, 0)]) @@ -1148,7 +1086,7 @@ def testWeeklyByEasterPos(self): self.assertEqual(list(rrule(WEEKLY, count=3, byeaster=1, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 4, 13, 9, 0), datetime(1999, 4, 5, 9, 0), datetime(2000, 4, 24, 9, 0)]) @@ -1157,7 +1095,7 @@ def testWeeklyByEasterNeg(self): self.assertEqual(list(rrule(WEEKLY, count=3, byeaster=-1, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 4, 11, 9, 0), datetime(1999, 4, 3, 9, 0), datetime(2000, 4, 22, 9, 0)]) @@ -1166,7 +1104,7 @@ def testWeeklyByHour(self): self.assertEqual(list(rrule(WEEKLY, count=3, byhour=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 18, 0), datetime(1997, 9, 9, 6, 0), datetime(1997, 9, 9, 18, 0)]) @@ -1175,7 +1113,7 @@ def testWeeklyByMinute(self): self.assertEqual(list(rrule(WEEKLY, count=3, byminute=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 9, 6), datetime(1997, 9, 2, 9, 18), datetime(1997, 9, 9, 9, 6)]) @@ -1184,7 +1122,7 @@ def testWeeklyBySecond(self): self.assertEqual(list(rrule(WEEKLY, count=3, bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 9, 0, 6), datetime(1997, 9, 2, 9, 0, 18), datetime(1997, 9, 9, 9, 0, 6)]) @@ -1194,7 +1132,7 @@ def testWeeklyByHourAndMinute(self): count=3, byhour=(6, 18), byminute=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 18, 6), datetime(1997, 9, 2, 18, 18), datetime(1997, 9, 9, 6, 6)]) @@ -1204,7 +1142,7 @@ def testWeeklyByHourAndSecond(self): count=3, byhour=(6, 18), bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 18, 0, 6), datetime(1997, 9, 2, 18, 0, 18), datetime(1997, 9, 9, 6, 0, 6)]) @@ -1214,7 +1152,7 @@ def testWeeklyByMinuteAndSecond(self): count=3, byminute=(6, 18), bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 9, 6, 6), datetime(1997, 9, 2, 9, 6, 18), datetime(1997, 9, 2, 9, 18, 6)]) @@ -1225,7 +1163,7 @@ def testWeeklyByHourAndMinuteAndSecond(self): byhour=(6, 18), byminute=(6, 18), bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 18, 6, 6), datetime(1997, 9, 2, 18, 6, 18), datetime(1997, 9, 2, 18, 18, 6)]) @@ -1236,7 +1174,7 @@ def testWeeklyBySetPos(self): byweekday=(TU, TH), byhour=(6, 18), bysetpos=(3, -3), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 18, 0), datetime(1997, 9, 4, 6, 0), datetime(1997, 9, 9, 18, 0)]) @@ -1244,7 +1182,7 @@ def testWeeklyBySetPos(self): def testDaily(self): self.assertEqual(list(rrule(DAILY, count=3, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 3, 9, 0), datetime(1997, 9, 4, 9, 0)]) @@ -1253,7 +1191,7 @@ def testDailyInterval(self): self.assertEqual(list(rrule(DAILY, count=3, interval=2, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 4, 9, 0), datetime(1997, 9, 6, 9, 0)]) @@ -1262,7 +1200,7 @@ def testDailyIntervalLarge(self): self.assertEqual(list(rrule(DAILY, count=3, interval=92, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 9, 0), datetime(1997, 12, 3, 9, 0), datetime(1998, 3, 5, 9, 0)]) @@ -1271,7 +1209,7 @@ def testDailyByMonth(self): self.assertEqual(list(rrule(DAILY, count=3, bymonth=(1, 3), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 1, 1, 9, 0), datetime(1998, 1, 2, 9, 0), datetime(1998, 1, 3, 9, 0)]) @@ -1280,7 +1218,7 @@ def testDailyByMonthDay(self): self.assertEqual(list(rrule(DAILY, count=3, bymonthday=(1, 3), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 3, 9, 0), datetime(1997, 10, 1, 9, 0), datetime(1997, 10, 3, 9, 0)]) @@ -1290,7 +1228,7 @@ def testDailyByMonthAndMonthDay(self): count=3, bymonth=(1, 3), bymonthday=(5, 7), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 1, 5, 9, 0), datetime(1998, 1, 7, 9, 0), datetime(1998, 3, 5, 9, 0)]) @@ -1299,7 +1237,7 @@ def testDailyByWeekDay(self): self.assertEqual(list(rrule(DAILY, count=3, byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 4, 9, 0), datetime(1997, 9, 9, 9, 0)]) @@ -1308,7 +1246,7 @@ def testDailyByNWeekDay(self): self.assertEqual(list(rrule(DAILY, count=3, byweekday=(TU(1), TH(-1)), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 4, 9, 0), datetime(1997, 9, 9, 9, 0)]) @@ -1318,7 +1256,7 @@ def testDailyByMonthAndWeekDay(self): count=3, bymonth=(1, 3), byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 1, 1, 9, 0), datetime(1998, 1, 6, 9, 0), datetime(1998, 1, 8, 9, 0)]) @@ -1328,7 +1266,7 @@ def testDailyByMonthAndNWeekDay(self): count=3, bymonth=(1, 3), byweekday=(TU(1), TH(-1)), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 1, 1, 9, 0), datetime(1998, 1, 6, 9, 0), datetime(1998, 1, 8, 9, 0)]) @@ -1338,7 +1276,7 @@ def testDailyByMonthDayAndWeekDay(self): count=3, bymonthday=(1, 3), byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 1, 1, 9, 0), datetime(1998, 2, 3, 9, 0), datetime(1998, 3, 3, 9, 0)]) @@ -1349,7 +1287,7 @@ def testDailyByMonthAndMonthDayAndWeekDay(self): bymonth=(1, 3), bymonthday=(1, 3), byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 1, 1, 9, 0), datetime(1998, 3, 3, 9, 0), datetime(2001, 3, 1, 9, 0)]) @@ -1358,7 +1296,7 @@ def testDailyByYearDay(self): self.assertEqual(list(rrule(DAILY, count=4, byyearday=(1, 100, 200, 365), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 12, 31, 9, 0), datetime(1998, 1, 1, 9, 0), datetime(1998, 4, 10, 9, 0), @@ -1368,7 +1306,7 @@ def testDailyByYearDayNeg(self): self.assertEqual(list(rrule(DAILY, count=4, byyearday=(-365, -266, -166, -1), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 12, 31, 9, 0), datetime(1998, 1, 1, 9, 0), datetime(1998, 4, 10, 9, 0), @@ -1379,7 +1317,7 @@ def testDailyByMonthAndYearDay(self): count=4, bymonth=(1, 7), byyearday=(1, 100, 200, 365), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 1, 1, 9, 0), datetime(1998, 7, 19, 9, 0), datetime(1999, 1, 1, 9, 0), @@ -1390,7 +1328,7 @@ def testDailyByMonthAndYearDayNeg(self): count=4, bymonth=(1, 7), byyearday=(-365, -266, -166, -1), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 1, 1, 9, 0), datetime(1998, 7, 19, 9, 0), datetime(1999, 1, 1, 9, 0), @@ -1400,7 +1338,7 @@ def testDailyByWeekNo(self): self.assertEqual(list(rrule(DAILY, count=3, byweekno=20, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 5, 11, 9, 0), datetime(1998, 5, 12, 9, 0), datetime(1998, 5, 13, 9, 0)]) @@ -1412,7 +1350,7 @@ def testDailyByWeekNoAndWeekDay(self): count=3, byweekno=1, byweekday=MO, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 12, 29, 9, 0), datetime(1999, 1, 4, 9, 0), datetime(2000, 1, 3, 9, 0)]) @@ -1424,7 +1362,7 @@ def testDailyByWeekNoAndWeekDayLarge(self): count=3, byweekno=52, byweekday=SU, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 12, 28, 9, 0), datetime(1998, 12, 27, 9, 0), datetime(2000, 1, 2, 9, 0)]) @@ -1434,7 +1372,7 @@ def testDailyByWeekNoAndWeekDayLast(self): count=3, byweekno=-1, byweekday=SU, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 12, 28, 9, 0), datetime(1999, 1, 3, 9, 0), datetime(2000, 1, 2, 9, 0)]) @@ -1444,7 +1382,7 @@ def testDailyByWeekNoAndWeekDay53(self): count=3, byweekno=53, byweekday=MO, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 12, 28, 9, 0), datetime(2004, 12, 27, 9, 0), datetime(2009, 12, 28, 9, 0)]) @@ -1453,7 +1391,7 @@ def testDailyByEaster(self): self.assertEqual(list(rrule(DAILY, count=3, byeaster=0, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 4, 12, 9, 0), datetime(1999, 4, 4, 9, 0), datetime(2000, 4, 23, 9, 0)]) @@ -1462,7 +1400,7 @@ def testDailyByEasterPos(self): self.assertEqual(list(rrule(DAILY, count=3, byeaster=1, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 4, 13, 9, 0), datetime(1999, 4, 5, 9, 0), datetime(2000, 4, 24, 9, 0)]) @@ -1471,7 +1409,7 @@ def testDailyByEasterNeg(self): self.assertEqual(list(rrule(DAILY, count=3, byeaster=-1, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 4, 11, 9, 0), datetime(1999, 4, 3, 9, 0), datetime(2000, 4, 22, 9, 0)]) @@ -1480,7 +1418,7 @@ def testDailyByHour(self): self.assertEqual(list(rrule(DAILY, count=3, byhour=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 18, 0), datetime(1997, 9, 3, 6, 0), datetime(1997, 9, 3, 18, 0)]) @@ -1489,7 +1427,7 @@ def testDailyByMinute(self): self.assertEqual(list(rrule(DAILY, count=3, byminute=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 9, 6), datetime(1997, 9, 2, 9, 18), datetime(1997, 9, 3, 9, 6)]) @@ -1498,7 +1436,7 @@ def testDailyBySecond(self): self.assertEqual(list(rrule(DAILY, count=3, bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 9, 0, 6), datetime(1997, 9, 2, 9, 0, 18), datetime(1997, 9, 3, 9, 0, 6)]) @@ -1508,7 +1446,7 @@ def testDailyByHourAndMinute(self): count=3, byhour=(6, 18), byminute=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 18, 6), datetime(1997, 9, 2, 18, 18), datetime(1997, 9, 3, 6, 6)]) @@ -1518,7 +1456,7 @@ def testDailyByHourAndSecond(self): count=3, byhour=(6, 18), bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 18, 0, 6), datetime(1997, 9, 2, 18, 0, 18), datetime(1997, 9, 3, 6, 0, 6)]) @@ -1528,7 +1466,7 @@ def testDailyByMinuteAndSecond(self): count=3, byminute=(6, 18), bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 9, 6, 6), datetime(1997, 9, 2, 9, 6, 18), datetime(1997, 9, 2, 9, 18, 6)]) @@ -1539,7 +1477,7 @@ def testDailyByHourAndMinuteAndSecond(self): byhour=(6, 18), byminute=(6, 18), bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 18, 6, 6), datetime(1997, 9, 2, 18, 6, 18), datetime(1997, 9, 2, 18, 18, 6)]) @@ -1550,7 +1488,7 @@ def testDailyBySetPos(self): byhour=(6, 18), byminute=(15, 45), bysetpos=(3, -3), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 18, 15), datetime(1997, 9, 3, 6, 45), datetime(1997, 9, 3, 18, 15)]) @@ -1558,7 +1496,7 @@ def testDailyBySetPos(self): def testHourly(self): self.assertEqual(list(rrule(HOURLY, count=3, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 2, 10, 0), datetime(1997, 9, 2, 11, 0)]) @@ -1567,7 +1505,7 @@ def testHourlyInterval(self): self.assertEqual(list(rrule(HOURLY, count=3, interval=2, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 2, 11, 0), datetime(1997, 9, 2, 13, 0)]) @@ -1576,7 +1514,7 @@ def testHourlyIntervalLarge(self): self.assertEqual(list(rrule(HOURLY, count=3, interval=769, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 9, 0), datetime(1997, 10, 4, 10, 0), datetime(1997, 11, 5, 11, 0)]) @@ -1585,7 +1523,7 @@ def testHourlyByMonth(self): self.assertEqual(list(rrule(HOURLY, count=3, bymonth=(1, 3), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 1, 1, 0, 0), datetime(1998, 1, 1, 1, 0), datetime(1998, 1, 1, 2, 0)]) @@ -1594,7 +1532,7 @@ def testHourlyByMonthDay(self): self.assertEqual(list(rrule(HOURLY, count=3, bymonthday=(1, 3), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 3, 0, 0), datetime(1997, 9, 3, 1, 0), datetime(1997, 9, 3, 2, 0)]) @@ -1604,7 +1542,7 @@ def testHourlyByMonthAndMonthDay(self): count=3, bymonth=(1, 3), bymonthday=(5, 7), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 1, 5, 0, 0), datetime(1998, 1, 5, 1, 0), datetime(1998, 1, 5, 2, 0)]) @@ -1613,7 +1551,7 @@ def testHourlyByWeekDay(self): self.assertEqual(list(rrule(HOURLY, count=3, byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 2, 10, 0), datetime(1997, 9, 2, 11, 0)]) @@ -1622,7 +1560,7 @@ def testHourlyByNWeekDay(self): self.assertEqual(list(rrule(HOURLY, count=3, byweekday=(TU(1), TH(-1)), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 2, 10, 0), datetime(1997, 9, 2, 11, 0)]) @@ -1632,7 +1570,7 @@ def testHourlyByMonthAndWeekDay(self): count=3, bymonth=(1, 3), byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 1, 1, 0, 0), datetime(1998, 1, 1, 1, 0), datetime(1998, 1, 1, 2, 0)]) @@ -1642,7 +1580,7 @@ def testHourlyByMonthAndNWeekDay(self): count=3, bymonth=(1, 3), byweekday=(TU(1), TH(-1)), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 1, 1, 0, 0), datetime(1998, 1, 1, 1, 0), datetime(1998, 1, 1, 2, 0)]) @@ -1652,7 +1590,7 @@ def testHourlyByMonthDayAndWeekDay(self): count=3, bymonthday=(1, 3), byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 1, 1, 0, 0), datetime(1998, 1, 1, 1, 0), datetime(1998, 1, 1, 2, 0)]) @@ -1663,7 +1601,7 @@ def testHourlyByMonthAndMonthDayAndWeekDay(self): bymonth=(1, 3), bymonthday=(1, 3), byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 1, 1, 0, 0), datetime(1998, 1, 1, 1, 0), datetime(1998, 1, 1, 2, 0)]) @@ -1672,7 +1610,7 @@ def testHourlyByYearDay(self): self.assertEqual(list(rrule(HOURLY, count=4, byyearday=(1, 100, 200, 365), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 12, 31, 0, 0), datetime(1997, 12, 31, 1, 0), datetime(1997, 12, 31, 2, 0), @@ -1682,7 +1620,7 @@ def testHourlyByYearDayNeg(self): self.assertEqual(list(rrule(HOURLY, count=4, byyearday=(-365, -266, -166, -1), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 12, 31, 0, 0), datetime(1997, 12, 31, 1, 0), datetime(1997, 12, 31, 2, 0), @@ -1693,7 +1631,7 @@ def testHourlyByMonthAndYearDay(self): count=4, bymonth=(4, 7), byyearday=(1, 100, 200, 365), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 4, 10, 0, 0), datetime(1998, 4, 10, 1, 0), datetime(1998, 4, 10, 2, 0), @@ -1704,7 +1642,7 @@ def testHourlyByMonthAndYearDayNeg(self): count=4, bymonth=(4, 7), byyearday=(-365, -266, -166, -1), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 4, 10, 0, 0), datetime(1998, 4, 10, 1, 0), datetime(1998, 4, 10, 2, 0), @@ -1714,7 +1652,7 @@ def testHourlyByWeekNo(self): self.assertEqual(list(rrule(HOURLY, count=3, byweekno=20, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 5, 11, 0, 0), datetime(1998, 5, 11, 1, 0), datetime(1998, 5, 11, 2, 0)]) @@ -1724,7 +1662,7 @@ def testHourlyByWeekNoAndWeekDay(self): count=3, byweekno=1, byweekday=MO, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 12, 29, 0, 0), datetime(1997, 12, 29, 1, 0), datetime(1997, 12, 29, 2, 0)]) @@ -1734,7 +1672,7 @@ def testHourlyByWeekNoAndWeekDayLarge(self): count=3, byweekno=52, byweekday=SU, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 12, 28, 0, 0), datetime(1997, 12, 28, 1, 0), datetime(1997, 12, 28, 2, 0)]) @@ -1744,7 +1682,7 @@ def testHourlyByWeekNoAndWeekDayLast(self): count=3, byweekno=-1, byweekday=SU, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 12, 28, 0, 0), datetime(1997, 12, 28, 1, 0), datetime(1997, 12, 28, 2, 0)]) @@ -1754,7 +1692,7 @@ def testHourlyByWeekNoAndWeekDay53(self): count=3, byweekno=53, byweekday=MO, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 12, 28, 0, 0), datetime(1998, 12, 28, 1, 0), datetime(1998, 12, 28, 2, 0)]) @@ -1763,7 +1701,7 @@ def testHourlyByEaster(self): self.assertEqual(list(rrule(HOURLY, count=3, byeaster=0, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 4, 12, 0, 0), datetime(1998, 4, 12, 1, 0), datetime(1998, 4, 12, 2, 0)]) @@ -1772,7 +1710,7 @@ def testHourlyByEasterPos(self): self.assertEqual(list(rrule(HOURLY, count=3, byeaster=1, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 4, 13, 0, 0), datetime(1998, 4, 13, 1, 0), datetime(1998, 4, 13, 2, 0)]) @@ -1781,7 +1719,7 @@ def testHourlyByEasterNeg(self): self.assertEqual(list(rrule(HOURLY, count=3, byeaster=-1, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 4, 11, 0, 0), datetime(1998, 4, 11, 1, 0), datetime(1998, 4, 11, 2, 0)]) @@ -1790,7 +1728,7 @@ def testHourlyByHour(self): self.assertEqual(list(rrule(HOURLY, count=3, byhour=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 18, 0), datetime(1997, 9, 3, 6, 0), datetime(1997, 9, 3, 18, 0)]) @@ -1799,7 +1737,7 @@ def testHourlyByMinute(self): self.assertEqual(list(rrule(HOURLY, count=3, byminute=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 9, 6), datetime(1997, 9, 2, 9, 18), datetime(1997, 9, 2, 10, 6)]) @@ -1808,7 +1746,7 @@ def testHourlyBySecond(self): self.assertEqual(list(rrule(HOURLY, count=3, bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 9, 0, 6), datetime(1997, 9, 2, 9, 0, 18), datetime(1997, 9, 2, 10, 0, 6)]) @@ -1818,7 +1756,7 @@ def testHourlyByHourAndMinute(self): count=3, byhour=(6, 18), byminute=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 18, 6), datetime(1997, 9, 2, 18, 18), datetime(1997, 9, 3, 6, 6)]) @@ -1828,7 +1766,7 @@ def testHourlyByHourAndSecond(self): count=3, byhour=(6, 18), bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 18, 0, 6), datetime(1997, 9, 2, 18, 0, 18), datetime(1997, 9, 3, 6, 0, 6)]) @@ -1838,7 +1776,7 @@ def testHourlyByMinuteAndSecond(self): count=3, byminute=(6, 18), bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 9, 6, 6), datetime(1997, 9, 2, 9, 6, 18), datetime(1997, 9, 2, 9, 18, 6)]) @@ -1849,7 +1787,7 @@ def testHourlyByHourAndMinuteAndSecond(self): byhour=(6, 18), byminute=(6, 18), bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 18, 6, 6), datetime(1997, 9, 2, 18, 6, 18), datetime(1997, 9, 2, 18, 18, 6)]) @@ -1860,7 +1798,7 @@ def testHourlyBySetPos(self): byminute=(15, 45), bysecond=(15, 45), bysetpos=(3, -3), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 9, 15, 45), datetime(1997, 9, 2, 9, 45, 15), datetime(1997, 9, 2, 10, 15, 45)]) @@ -1868,7 +1806,7 @@ def testHourlyBySetPos(self): def testMinutely(self): self.assertEqual(list(rrule(MINUTELY, count=3, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 2, 9, 1), datetime(1997, 9, 2, 9, 2)]) @@ -1877,7 +1815,7 @@ def testMinutelyInterval(self): self.assertEqual(list(rrule(MINUTELY, count=3, interval=2, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 2, 9, 2), datetime(1997, 9, 2, 9, 4)]) @@ -1886,7 +1824,7 @@ def testMinutelyIntervalLarge(self): self.assertEqual(list(rrule(MINUTELY, count=3, interval=1501, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 3, 10, 1), datetime(1997, 9, 4, 11, 2)]) @@ -1895,7 +1833,7 @@ def testMinutelyByMonth(self): self.assertEqual(list(rrule(MINUTELY, count=3, bymonth=(1, 3), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 1, 1, 0, 0), datetime(1998, 1, 1, 0, 1), datetime(1998, 1, 1, 0, 2)]) @@ -1904,7 +1842,7 @@ def testMinutelyByMonthDay(self): self.assertEqual(list(rrule(MINUTELY, count=3, bymonthday=(1, 3), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 3, 0, 0), datetime(1997, 9, 3, 0, 1), datetime(1997, 9, 3, 0, 2)]) @@ -1914,7 +1852,7 @@ def testMinutelyByMonthAndMonthDay(self): count=3, bymonth=(1, 3), bymonthday=(5, 7), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 1, 5, 0, 0), datetime(1998, 1, 5, 0, 1), datetime(1998, 1, 5, 0, 2)]) @@ -1923,7 +1861,7 @@ def testMinutelyByWeekDay(self): self.assertEqual(list(rrule(MINUTELY, count=3, byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 2, 9, 1), datetime(1997, 9, 2, 9, 2)]) @@ -1932,7 +1870,7 @@ def testMinutelyByNWeekDay(self): self.assertEqual(list(rrule(MINUTELY, count=3, byweekday=(TU(1), TH(-1)), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 2, 9, 1), datetime(1997, 9, 2, 9, 2)]) @@ -1942,7 +1880,7 @@ def testMinutelyByMonthAndWeekDay(self): count=3, bymonth=(1, 3), byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 1, 1, 0, 0), datetime(1998, 1, 1, 0, 1), datetime(1998, 1, 1, 0, 2)]) @@ -1952,7 +1890,7 @@ def testMinutelyByMonthAndNWeekDay(self): count=3, bymonth=(1, 3), byweekday=(TU(1), TH(-1)), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 1, 1, 0, 0), datetime(1998, 1, 1, 0, 1), datetime(1998, 1, 1, 0, 2)]) @@ -1962,7 +1900,7 @@ def testMinutelyByMonthDayAndWeekDay(self): count=3, bymonthday=(1, 3), byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 1, 1, 0, 0), datetime(1998, 1, 1, 0, 1), datetime(1998, 1, 1, 0, 2)]) @@ -1973,7 +1911,7 @@ def testMinutelyByMonthAndMonthDayAndWeekDay(self): bymonth=(1, 3), bymonthday=(1, 3), byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 1, 1, 0, 0), datetime(1998, 1, 1, 0, 1), datetime(1998, 1, 1, 0, 2)]) @@ -1982,7 +1920,7 @@ def testMinutelyByYearDay(self): self.assertEqual(list(rrule(MINUTELY, count=4, byyearday=(1, 100, 200, 365), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 12, 31, 0, 0), datetime(1997, 12, 31, 0, 1), datetime(1997, 12, 31, 0, 2), @@ -1992,7 +1930,7 @@ def testMinutelyByYearDayNeg(self): self.assertEqual(list(rrule(MINUTELY, count=4, byyearday=(-365, -266, -166, -1), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 12, 31, 0, 0), datetime(1997, 12, 31, 0, 1), datetime(1997, 12, 31, 0, 2), @@ -2003,7 +1941,7 @@ def testMinutelyByMonthAndYearDay(self): count=4, bymonth=(4, 7), byyearday=(1, 100, 200, 365), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 4, 10, 0, 0), datetime(1998, 4, 10, 0, 1), datetime(1998, 4, 10, 0, 2), @@ -2014,7 +1952,7 @@ def testMinutelyByMonthAndYearDayNeg(self): count=4, bymonth=(4, 7), byyearday=(-365, -266, -166, -1), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 4, 10, 0, 0), datetime(1998, 4, 10, 0, 1), datetime(1998, 4, 10, 0, 2), @@ -2024,7 +1962,7 @@ def testMinutelyByWeekNo(self): self.assertEqual(list(rrule(MINUTELY, count=3, byweekno=20, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 5, 11, 0, 0), datetime(1998, 5, 11, 0, 1), datetime(1998, 5, 11, 0, 2)]) @@ -2034,7 +1972,7 @@ def testMinutelyByWeekNoAndWeekDay(self): count=3, byweekno=1, byweekday=MO, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 12, 29, 0, 0), datetime(1997, 12, 29, 0, 1), datetime(1997, 12, 29, 0, 2)]) @@ -2044,7 +1982,7 @@ def testMinutelyByWeekNoAndWeekDayLarge(self): count=3, byweekno=52, byweekday=SU, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 12, 28, 0, 0), datetime(1997, 12, 28, 0, 1), datetime(1997, 12, 28, 0, 2)]) @@ -2054,7 +1992,7 @@ def testMinutelyByWeekNoAndWeekDayLast(self): count=3, byweekno=-1, byweekday=SU, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 12, 28, 0, 0), datetime(1997, 12, 28, 0, 1), datetime(1997, 12, 28, 0, 2)]) @@ -2064,7 +2002,7 @@ def testMinutelyByWeekNoAndWeekDay53(self): count=3, byweekno=53, byweekday=MO, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 12, 28, 0, 0), datetime(1998, 12, 28, 0, 1), datetime(1998, 12, 28, 0, 2)]) @@ -2073,7 +2011,7 @@ def testMinutelyByEaster(self): self.assertEqual(list(rrule(MINUTELY, count=3, byeaster=0, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 4, 12, 0, 0), datetime(1998, 4, 12, 0, 1), datetime(1998, 4, 12, 0, 2)]) @@ -2082,7 +2020,7 @@ def testMinutelyByEasterPos(self): self.assertEqual(list(rrule(MINUTELY, count=3, byeaster=1, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 4, 13, 0, 0), datetime(1998, 4, 13, 0, 1), datetime(1998, 4, 13, 0, 2)]) @@ -2091,7 +2029,7 @@ def testMinutelyByEasterNeg(self): self.assertEqual(list(rrule(MINUTELY, count=3, byeaster=-1, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 4, 11, 0, 0), datetime(1998, 4, 11, 0, 1), datetime(1998, 4, 11, 0, 2)]) @@ -2100,7 +2038,7 @@ def testMinutelyByHour(self): self.assertEqual(list(rrule(MINUTELY, count=3, byhour=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 18, 0), datetime(1997, 9, 2, 18, 1), datetime(1997, 9, 2, 18, 2)]) @@ -2109,7 +2047,7 @@ def testMinutelyByMinute(self): self.assertEqual(list(rrule(MINUTELY, count=3, byminute=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 9, 6), datetime(1997, 9, 2, 9, 18), datetime(1997, 9, 2, 10, 6)]) @@ -2118,7 +2056,7 @@ def testMinutelyBySecond(self): self.assertEqual(list(rrule(MINUTELY, count=3, bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 9, 0, 6), datetime(1997, 9, 2, 9, 0, 18), datetime(1997, 9, 2, 9, 1, 6)]) @@ -2128,7 +2066,7 @@ def testMinutelyByHourAndMinute(self): count=3, byhour=(6, 18), byminute=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 18, 6), datetime(1997, 9, 2, 18, 18), datetime(1997, 9, 3, 6, 6)]) @@ -2138,7 +2076,7 @@ def testMinutelyByHourAndSecond(self): count=3, byhour=(6, 18), bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 18, 0, 6), datetime(1997, 9, 2, 18, 0, 18), datetime(1997, 9, 2, 18, 1, 6)]) @@ -2148,7 +2086,7 @@ def testMinutelyByMinuteAndSecond(self): count=3, byminute=(6, 18), bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 9, 6, 6), datetime(1997, 9, 2, 9, 6, 18), datetime(1997, 9, 2, 9, 18, 6)]) @@ -2159,7 +2097,7 @@ def testMinutelyByHourAndMinuteAndSecond(self): byhour=(6, 18), byminute=(6, 18), bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 18, 6, 6), datetime(1997, 9, 2, 18, 6, 18), datetime(1997, 9, 2, 18, 18, 6)]) @@ -2169,7 +2107,7 @@ def testMinutelyBySetPos(self): count=3, bysecond=(15, 30, 45), bysetpos=(3, -3), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 9, 0, 15), datetime(1997, 9, 2, 9, 0, 45), datetime(1997, 9, 2, 9, 1, 15)]) @@ -2177,7 +2115,7 @@ def testMinutelyBySetPos(self): def testSecondly(self): self.assertEqual(list(rrule(SECONDLY, count=3, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 9, 0, 0), datetime(1997, 9, 2, 9, 0, 1), datetime(1997, 9, 2, 9, 0, 2)]) @@ -2186,7 +2124,7 @@ def testSecondlyInterval(self): self.assertEqual(list(rrule(SECONDLY, count=3, interval=2, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 9, 0, 0), datetime(1997, 9, 2, 9, 0, 2), datetime(1997, 9, 2, 9, 0, 4)]) @@ -2195,7 +2133,7 @@ def testSecondlyIntervalLarge(self): self.assertEqual(list(rrule(SECONDLY, count=3, interval=90061, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 9, 0, 0), datetime(1997, 9, 3, 10, 1, 1), datetime(1997, 9, 4, 11, 2, 2)]) @@ -2204,7 +2142,7 @@ def testSecondlyByMonth(self): self.assertEqual(list(rrule(SECONDLY, count=3, bymonth=(1, 3), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 1, 1, 0, 0, 0), datetime(1998, 1, 1, 0, 0, 1), datetime(1998, 1, 1, 0, 0, 2)]) @@ -2213,7 +2151,7 @@ def testSecondlyByMonthDay(self): self.assertEqual(list(rrule(SECONDLY, count=3, bymonthday=(1, 3), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 3, 0, 0, 0), datetime(1997, 9, 3, 0, 0, 1), datetime(1997, 9, 3, 0, 0, 2)]) @@ -2223,7 +2161,7 @@ def testSecondlyByMonthAndMonthDay(self): count=3, bymonth=(1, 3), bymonthday=(5, 7), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 1, 5, 0, 0, 0), datetime(1998, 1, 5, 0, 0, 1), datetime(1998, 1, 5, 0, 0, 2)]) @@ -2232,7 +2170,7 @@ def testSecondlyByWeekDay(self): self.assertEqual(list(rrule(SECONDLY, count=3, byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 9, 0, 0), datetime(1997, 9, 2, 9, 0, 1), datetime(1997, 9, 2, 9, 0, 2)]) @@ -2241,7 +2179,7 @@ def testSecondlyByNWeekDay(self): self.assertEqual(list(rrule(SECONDLY, count=3, byweekday=(TU(1), TH(-1)), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 9, 0, 0), datetime(1997, 9, 2, 9, 0, 1), datetime(1997, 9, 2, 9, 0, 2)]) @@ -2251,7 +2189,7 @@ def testSecondlyByMonthAndWeekDay(self): count=3, bymonth=(1, 3), byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 1, 1, 0, 0, 0), datetime(1998, 1, 1, 0, 0, 1), datetime(1998, 1, 1, 0, 0, 2)]) @@ -2261,7 +2199,7 @@ def testSecondlyByMonthAndNWeekDay(self): count=3, bymonth=(1, 3), byweekday=(TU(1), TH(-1)), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 1, 1, 0, 0, 0), datetime(1998, 1, 1, 0, 0, 1), datetime(1998, 1, 1, 0, 0, 2)]) @@ -2271,7 +2209,7 @@ def testSecondlyByMonthDayAndWeekDay(self): count=3, bymonthday=(1, 3), byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 1, 1, 0, 0, 0), datetime(1998, 1, 1, 0, 0, 1), datetime(1998, 1, 1, 0, 0, 2)]) @@ -2282,7 +2220,7 @@ def testSecondlyByMonthAndMonthDayAndWeekDay(self): bymonth=(1, 3), bymonthday=(1, 3), byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 1, 1, 0, 0, 0), datetime(1998, 1, 1, 0, 0, 1), datetime(1998, 1, 1, 0, 0, 2)]) @@ -2291,7 +2229,7 @@ def testSecondlyByYearDay(self): self.assertEqual(list(rrule(SECONDLY, count=4, byyearday=(1, 100, 200, 365), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 12, 31, 0, 0, 0), datetime(1997, 12, 31, 0, 0, 1), datetime(1997, 12, 31, 0, 0, 2), @@ -2301,7 +2239,7 @@ def testSecondlyByYearDayNeg(self): self.assertEqual(list(rrule(SECONDLY, count=4, byyearday=(-365, -266, -166, -1), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 12, 31, 0, 0, 0), datetime(1997, 12, 31, 0, 0, 1), datetime(1997, 12, 31, 0, 0, 2), @@ -2312,7 +2250,7 @@ def testSecondlyByMonthAndYearDay(self): count=4, bymonth=(4, 7), byyearday=(1, 100, 200, 365), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 4, 10, 0, 0, 0), datetime(1998, 4, 10, 0, 0, 1), datetime(1998, 4, 10, 0, 0, 2), @@ -2323,7 +2261,7 @@ def testSecondlyByMonthAndYearDayNeg(self): count=4, bymonth=(4, 7), byyearday=(-365, -266, -166, -1), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 4, 10, 0, 0, 0), datetime(1998, 4, 10, 0, 0, 1), datetime(1998, 4, 10, 0, 0, 2), @@ -2333,7 +2271,7 @@ def testSecondlyByWeekNo(self): self.assertEqual(list(rrule(SECONDLY, count=3, byweekno=20, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 5, 11, 0, 0, 0), datetime(1998, 5, 11, 0, 0, 1), datetime(1998, 5, 11, 0, 0, 2)]) @@ -2343,7 +2281,7 @@ def testSecondlyByWeekNoAndWeekDay(self): count=3, byweekno=1, byweekday=MO, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 12, 29, 0, 0, 0), datetime(1997, 12, 29, 0, 0, 1), datetime(1997, 12, 29, 0, 0, 2)]) @@ -2353,7 +2291,7 @@ def testSecondlyByWeekNoAndWeekDayLarge(self): count=3, byweekno=52, byweekday=SU, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 12, 28, 0, 0, 0), datetime(1997, 12, 28, 0, 0, 1), datetime(1997, 12, 28, 0, 0, 2)]) @@ -2363,7 +2301,7 @@ def testSecondlyByWeekNoAndWeekDayLast(self): count=3, byweekno=-1, byweekday=SU, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 12, 28, 0, 0, 0), datetime(1997, 12, 28, 0, 0, 1), datetime(1997, 12, 28, 0, 0, 2)]) @@ -2373,7 +2311,7 @@ def testSecondlyByWeekNoAndWeekDay53(self): count=3, byweekno=53, byweekday=MO, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 12, 28, 0, 0, 0), datetime(1998, 12, 28, 0, 0, 1), datetime(1998, 12, 28, 0, 0, 2)]) @@ -2382,7 +2320,7 @@ def testSecondlyByEaster(self): self.assertEqual(list(rrule(SECONDLY, count=3, byeaster=0, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 4, 12, 0, 0, 0), datetime(1998, 4, 12, 0, 0, 1), datetime(1998, 4, 12, 0, 0, 2)]) @@ -2391,7 +2329,7 @@ def testSecondlyByEasterPos(self): self.assertEqual(list(rrule(SECONDLY, count=3, byeaster=1, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 4, 13, 0, 0, 0), datetime(1998, 4, 13, 0, 0, 1), datetime(1998, 4, 13, 0, 0, 2)]) @@ -2400,7 +2338,7 @@ def testSecondlyByEasterNeg(self): self.assertEqual(list(rrule(SECONDLY, count=3, byeaster=-1, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 4, 11, 0, 0, 0), datetime(1998, 4, 11, 0, 0, 1), datetime(1998, 4, 11, 0, 0, 2)]) @@ -2409,7 +2347,7 @@ def testSecondlyByHour(self): self.assertEqual(list(rrule(SECONDLY, count=3, byhour=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 18, 0, 0), datetime(1997, 9, 2, 18, 0, 1), datetime(1997, 9, 2, 18, 0, 2)]) @@ -2418,7 +2356,7 @@ def testSecondlyByMinute(self): self.assertEqual(list(rrule(SECONDLY, count=3, byminute=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 9, 6, 0), datetime(1997, 9, 2, 9, 6, 1), datetime(1997, 9, 2, 9, 6, 2)]) @@ -2427,7 +2365,7 @@ def testSecondlyBySecond(self): self.assertEqual(list(rrule(SECONDLY, count=3, bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 9, 0, 6), datetime(1997, 9, 2, 9, 0, 18), datetime(1997, 9, 2, 9, 1, 6)]) @@ -2437,7 +2375,7 @@ def testSecondlyByHourAndMinute(self): count=3, byhour=(6, 18), byminute=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 18, 6, 0), datetime(1997, 9, 2, 18, 6, 1), datetime(1997, 9, 2, 18, 6, 2)]) @@ -2447,7 +2385,7 @@ def testSecondlyByHourAndSecond(self): count=3, byhour=(6, 18), bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 18, 0, 6), datetime(1997, 9, 2, 18, 0, 18), datetime(1997, 9, 2, 18, 1, 6)]) @@ -2457,7 +2395,7 @@ def testSecondlyByMinuteAndSecond(self): count=3, byminute=(6, 18), bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 9, 6, 6), datetime(1997, 9, 2, 9, 6, 18), datetime(1997, 9, 2, 9, 18, 6)]) @@ -2468,7 +2406,7 @@ def testSecondlyByHourAndMinuteAndSecond(self): byhour=(6, 18), byminute=(6, 18), bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 18, 6, 6), datetime(1997, 9, 2, 18, 6, 18), datetime(1997, 9, 2, 18, 18, 6)]) @@ -2479,7 +2417,7 @@ def testSecondlyByHourAndMinuteAndSecondBug(self): count=3, bysecond=(0,), byminute=(1,), - dtstart=datetime(2010, 3, 22, 12, 1))), + dtstart=parse("20100322120100"))), [datetime(2010, 3, 22, 12, 1), datetime(2010, 3, 22, 13, 1), datetime(2010, 3, 22, 14, 1)]) @@ -2494,85 +2432,22 @@ def testLongIntegers(self): byhour=long(6), byminute=long(6), bysecond=long(6), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 2, 5, 6, 6, 6), datetime(1998, 2, 12, 6, 6, 6)]) self.assertEqual(list(rrule(YEARLY, count=long(2), bymonthday=long(5), byweekno=long(2), - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1998, 1, 5, 9, 0), datetime(2004, 1, 5, 9, 0)]) - def testHourlyBadRRule(self): - """ - When `byhour` is specified with `freq=HOURLY`, there are certain - combinations of `dtstart` and `byhour` which result in an rrule with no - valid values. - - See https://github.com/dateutil/dateutil/issues/4 - """ - - self.assertRaises(ValueError, rrule, HOURLY, - **dict(interval=4, byhour=(7, 11, 15, 19), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testMinutelyBadRRule(self): - """ - See :func:`testHourlyBadRRule` for details. - """ - - self.assertRaises(ValueError, rrule, MINUTELY, - **dict(interval=12, byminute=(10, 11, 25, 39, 50), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testSecondlyBadRRule(self): - """ - See :func:`testHourlyBadRRule` for details. - """ - - self.assertRaises(ValueError, rrule, SECONDLY, - **dict(interval=10, bysecond=(2, 15, 37, 42, 59), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testMinutelyBadComboRRule(self): - """ - Certain values of :param:`interval` in :class:`rrule`, when combined - with certain values of :param:`byhour` create rules which apply to no - valid dates. The library should detect this case in the iterator and - raise a :exception:`ValueError`. - """ - - # In Python 2.7 you can use a context manager for this. - def make_bad_rrule(): - list(rrule(MINUTELY, interval=120, byhour=(10, 12, 14, 16), - count=2, dtstart=datetime(1997, 9, 2, 9, 0))) - - self.assertRaises(ValueError, make_bad_rrule) - - def testSecondlyBadComboRRule(self): - """ - See :func:`testMinutelyBadComboRRule' for details. - """ - - # In Python 2.7 you can use a context manager for this. - def make_bad_minute_rrule(): - list(rrule(SECONDLY, interval=360, byminute=(10, 28, 49), - count=4, dtstart=datetime(1997, 9, 2, 9, 0))) - - def make_bad_hour_rrule(): - list(rrule(SECONDLY, interval=43200, byhour=(2, 10, 18, 23), - count=4, dtstart=datetime(1997, 9, 2, 9, 0))) - - self.assertRaises(ValueError, make_bad_minute_rrule) - self.assertRaises(ValueError, make_bad_hour_rrule) - def testUntilNotMatching(self): self.assertEqual(list(rrule(DAILY, count=3, - dtstart=datetime(1997, 9, 2, 9, 0), - until=datetime(1997, 9, 5, 8, 0))), + dtstart=parse("19970902T090000"), + until=parse("19970905T080000"))), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 3, 9, 0), datetime(1997, 9, 4, 9, 0)]) @@ -2580,8 +2455,8 @@ def testUntilNotMatching(self): def testUntilMatching(self): self.assertEqual(list(rrule(DAILY, count=3, - dtstart=datetime(1997, 9, 2, 9, 0), - until=datetime(1997, 9, 4, 9, 0))), + dtstart=parse("19970902T090000"), + until=parse("19970904T090000"))), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 3, 9, 0), datetime(1997, 9, 4, 9, 0)]) @@ -2589,21 +2464,21 @@ def testUntilMatching(self): def testUntilSingle(self): self.assertEqual(list(rrule(DAILY, count=3, - dtstart=datetime(1997, 9, 2, 9, 0), - until=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"), + until=parse("19970902T090000"))), [datetime(1997, 9, 2, 9, 0)]) def testUntilEmpty(self): self.assertEqual(list(rrule(DAILY, count=3, - dtstart=datetime(1997, 9, 2, 9, 0), - until=datetime(1997, 9, 1, 9, 0))), + dtstart=parse("19970902T090000"), + until=parse("19970901T090000"))), []) def testUntilWithDate(self): self.assertEqual(list(rrule(DAILY, count=3, - dtstart=datetime(1997, 9, 2, 9, 0), + dtstart=parse("19970902T090000"), until=date(1997, 9, 5))), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 3, 9, 0), @@ -2615,7 +2490,7 @@ def testWkStIntervalMO(self): interval=2, byweekday=(TU, SU), wkst=MO, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 7, 9, 0), datetime(1997, 9, 16, 9, 0)]) @@ -2626,7 +2501,7 @@ def testWkStIntervalSU(self): interval=2, byweekday=(TU, SU), wkst=SU, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 14, 9, 0), datetime(1997, 9, 16, 9, 0)]) @@ -2642,7 +2517,7 @@ def testDTStartIsDate(self): def testDTStartWithMicroseconds(self): self.assertEqual(list(rrule(DAILY, count=3, - dtstart=datetime(1997, 9, 2, 9, 0, 0, 500000))), + dtstart=parse("19970902T090000.5"))), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 3, 9, 0), datetime(1997, 9, 4, 9, 0)]) @@ -2652,31 +2527,31 @@ def testMaxYear(self): count=3, bymonth=2, bymonthday=31, - dtstart=datetime(9997, 9, 2, 9, 0, 0))), + dtstart=parse("99970902T090000"))), []) def testGetItem(self): self.assertEqual(rrule(DAILY, count=3, - dtstart=datetime(1997, 9, 2, 9, 0))[0], + dtstart=parse("19970902T090000"))[0], datetime(1997, 9, 2, 9, 0)) def testGetItemNeg(self): self.assertEqual(rrule(DAILY, count=3, - dtstart=datetime(1997, 9, 2, 9, 0))[-1], + dtstart=parse("19970902T090000"))[-1], datetime(1997, 9, 4, 9, 0)) def testGetItemSlice(self): self.assertEqual(rrule(DAILY, - # count=3, - dtstart=datetime(1997, 9, 2, 9, 0))[1:2], + #count=3, + dtstart=parse("19970902T090000"))[1:2], [datetime(1997, 9, 3, 9, 0)]) def testGetItemSliceEmpty(self): self.assertEqual(rrule(DAILY, count=3, - dtstart=datetime(1997, 9, 2, 9, 0))[:], + dtstart=parse("19970902T090000"))[:], [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 3, 9, 0), datetime(1997, 9, 4, 9, 0)]) @@ -2684,90 +2559,58 @@ def testGetItemSliceEmpty(self): def testGetItemSliceStep(self): self.assertEqual(rrule(DAILY, count=3, - dtstart=datetime(1997, 9, 2, 9, 0))[::-2], + dtstart=parse("19970902T090000"))[::-2], [datetime(1997, 9, 4, 9, 0), datetime(1997, 9, 2, 9, 0)]) def testCount(self): self.assertEqual(rrule(DAILY, count=3, - dtstart=datetime(1997, 9, 2, 9, 0)).count(), + dtstart=parse("19970902T090000")).count(), 3) def testContains(self): - rr = rrule(DAILY, count=3, dtstart=datetime(1997, 9, 2, 9, 0)) + rr = rrule(DAILY, count=3, dtstart=parse("19970902T090000")) self.assertEqual(datetime(1997, 9, 3, 9, 0) in rr, True) def testContainsNot(self): - rr = rrule(DAILY, count=3, dtstart=datetime(1997, 9, 2, 9, 0)) + rr = rrule(DAILY, count=3, dtstart=parse("19970902T090000")) self.assertEqual(datetime(1997, 9, 3, 9, 0) not in rr, False) def testBefore(self): - self.assertEqual(rrule(DAILY, # count=5 - dtstart=datetime(1997, 9, 2, 9, 0)).before(datetime(1997, 9, 5, 9, 0)), + self.assertEqual(rrule(DAILY, + #count=5, + dtstart=parse("19970902T090000")) + .before(parse("19970905T090000")), datetime(1997, 9, 4, 9, 0)) def testBeforeInc(self): self.assertEqual(rrule(DAILY, #count=5, - dtstart=datetime(1997, 9, 2, 9, 0)) - .before(datetime(1997, 9, 5, 9, 0), inc=True), + dtstart=parse("19970902T090000")) + .before(parse("19970905T090000"), inc=True), datetime(1997, 9, 5, 9, 0)) def testAfter(self): self.assertEqual(rrule(DAILY, #count=5, - dtstart=datetime(1997, 9, 2, 9, 0)) - .after(datetime(1997, 9, 4, 9, 0)), + dtstart=parse("19970902T090000")) + .after(parse("19970904T090000")), datetime(1997, 9, 5, 9, 0)) def testAfterInc(self): self.assertEqual(rrule(DAILY, #count=5, - dtstart=datetime(1997, 9, 2, 9, 0)) - .after(datetime(1997, 9, 4, 9, 0), inc=True), + dtstart=parse("19970902T090000")) + .after(parse("19970904T090000"), inc=True), datetime(1997, 9, 4, 9, 0)) - def testXAfter(self): - self.assertEqual(list(rrule(DAILY, - dtstart=datetime(1997, 9, 2, 9, 0)) - .xafter(datetime(1997, 9, 8, 9, 0), count=12)), - [datetime(1997, 9, 9, 9, 0), - datetime(1997, 9, 10, 9, 0), - datetime(1997, 9, 11, 9, 0), - datetime(1997, 9, 12, 9, 0), - datetime(1997, 9, 13, 9, 0), - datetime(1997, 9, 14, 9, 0), - datetime(1997, 9, 15, 9, 0), - datetime(1997, 9, 16, 9, 0), - datetime(1997, 9, 17, 9, 0), - datetime(1997, 9, 18, 9, 0), - datetime(1997, 9, 19, 9, 0), - datetime(1997, 9, 20, 9, 0)]) - - def testXAfterInc(self): - self.assertEqual(list(rrule(DAILY, - dtstart=datetime(1997, 9, 2, 9, 0)) - .xafter(datetime(1997, 9, 8, 9, 0), count=12, inc=True)), - [datetime(1997, 9, 8, 9, 0), - datetime(1997, 9, 9, 9, 0), - datetime(1997, 9, 10, 9, 0), - datetime(1997, 9, 11, 9, 0), - datetime(1997, 9, 12, 9, 0), - datetime(1997, 9, 13, 9, 0), - datetime(1997, 9, 14, 9, 0), - datetime(1997, 9, 15, 9, 0), - datetime(1997, 9, 16, 9, 0), - datetime(1997, 9, 17, 9, 0), - datetime(1997, 9, 18, 9, 0), - datetime(1997, 9, 19, 9, 0)]) - def testBetween(self): self.assertEqual(rrule(DAILY, #count=5, - dtstart=datetime(1997, 9, 2, 9, 0)) - .between(datetime(1997, 9, 2, 9, 0), - datetime(1997, 9, 6, 9, 0)), + dtstart=parse("19970902T090000")) + .between(parse("19970902T090000"), + parse("19970906T090000")), [datetime(1997, 9, 3, 9, 0), datetime(1997, 9, 4, 9, 0), datetime(1997, 9, 5, 9, 0)]) @@ -2775,9 +2618,9 @@ def testBetween(self): def testBetweenInc(self): self.assertEqual(rrule(DAILY, #count=5, - dtstart=datetime(1997, 9, 2, 9, 0)) - .between(datetime(1997, 9, 2, 9, 0), - datetime(1997, 9, 6, 9, 0), inc=True), + dtstart=parse("19970902T090000")) + .between(parse("19970902T090000"), + parse("19970906T090000"), inc=True), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 3, 9, 0), datetime(1997, 9, 4, 9, 0), @@ -2786,7 +2629,7 @@ def testBetweenInc(self): def testCachePre(self): rr = rrule(DAILY, count=15, cache=True, - dtstart=datetime(1997, 9, 2, 9, 0)) + dtstart=parse("19970902T090000")) self.assertEqual(list(rr), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 3, 9, 0), @@ -2806,7 +2649,7 @@ def testCachePre(self): def testCachePost(self): rr = rrule(DAILY, count=15, cache=True, - dtstart=datetime(1997, 9, 2, 9, 0)) + dtstart=parse("19970902T090000")) for x in rr: pass self.assertEqual(list(rr), [datetime(1997, 9, 2, 9, 0), @@ -2827,7 +2670,7 @@ def testCachePost(self): def testCachePostInternal(self): rr = rrule(DAILY, count=15, cache=True, - dtstart=datetime(1997, 9, 2, 9, 0)) + dtstart=parse("19970902T090000")) for x in rr: pass self.assertEqual(rr._cache, [datetime(1997, 9, 2, 9, 0), @@ -2848,21 +2691,21 @@ def testCachePostInternal(self): def testCachePreContains(self): rr = rrule(DAILY, count=3, cache=True, - dtstart=datetime(1997, 9, 2, 9, 0)) + dtstart=parse("19970902T090000")) self.assertEqual(datetime(1997, 9, 3, 9, 0) in rr, True) def testCachePostContains(self): rr = rrule(DAILY, count=3, cache=True, - dtstart=datetime(1997, 9, 2, 9, 0)) + dtstart=parse("19970902T090000")) for x in rr: pass self.assertEqual(datetime(1997, 9, 3, 9, 0) in rr, True) def testSet(self): set = rruleset() set.rrule(rrule(YEARLY, count=2, byweekday=TU, - dtstart=datetime(1997, 9, 2, 9, 0))) + dtstart=parse("19970902T090000"))) set.rrule(rrule(YEARLY, count=1, byweekday=TH, - dtstart=datetime(1997, 9, 2, 9, 0))) + dtstart=parse("19970902T090000"))) self.assertEqual(list(set), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 4, 9, 0), @@ -2871,7 +2714,7 @@ def testSet(self): def testSetDate(self): set = rruleset() set.rrule(rrule(YEARLY, count=1, byweekday=TU, - dtstart=datetime(1997, 9, 2, 9, 0))) + dtstart=parse("19970902T090000"))) set.rdate(datetime(1997, 9, 4, 9)) set.rdate(datetime(1997, 9, 9, 9)) self.assertEqual(list(set), @@ -2882,9 +2725,9 @@ def testSetDate(self): def testSetExRule(self): set = rruleset() set.rrule(rrule(YEARLY, count=6, byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))) + dtstart=parse("19970902T090000"))) set.exrule(rrule(YEARLY, count=3, byweekday=TH, - dtstart=datetime(1997, 9, 2, 9, 0))) + dtstart=parse("19970902T090000"))) self.assertEqual(list(set), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 9, 9, 0), @@ -2893,7 +2736,7 @@ def testSetExRule(self): def testSetExDate(self): set = rruleset() set.rrule(rrule(YEARLY, count=6, byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))) + dtstart=parse("19970902T090000"))) set.exdate(datetime(1997, 9, 4, 9)) set.exdate(datetime(1997, 9, 11, 9)) set.exdate(datetime(1997, 9, 18, 9)) @@ -2905,7 +2748,7 @@ def testSetExDate(self): def testSetExDateRevOrder(self): set = rruleset() set.rrule(rrule(MONTHLY, count=5, bymonthday=10, - dtstart=datetime(2004, 1, 1, 9, 0))) + dtstart=parse("20040101T090000"))) set.exdate(datetime(2004, 4, 10, 9, 0)) set.exdate(datetime(2004, 2, 10, 9, 0)) self.assertEqual(list(set), @@ -2938,7 +2781,7 @@ def testSetDateAndExRule(self): set.rdate(datetime(1997, 9, 16, 9)) set.rdate(datetime(1997, 9, 18, 9)) set.exrule(rrule(YEARLY, count=3, byweekday=TH, - dtstart=datetime(1997, 9, 2, 9, 0))) + dtstart=parse("19970902T090000"))) self.assertEqual(list(set), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 9, 9, 0), @@ -2947,17 +2790,17 @@ def testSetDateAndExRule(self): def testSetCount(self): set = rruleset() set.rrule(rrule(YEARLY, count=6, byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))) + dtstart=parse("19970902T090000"))) set.exrule(rrule(YEARLY, count=3, byweekday=TH, - dtstart=datetime(1997, 9, 2, 9, 0))) + dtstart=parse("19970902T090000"))) self.assertEqual(set.count(), 3) def testSetCachePre(self): set = rruleset() set.rrule(rrule(YEARLY, count=2, byweekday=TU, - dtstart=datetime(1997, 9, 2, 9, 0))) + dtstart=parse("19970902T090000"))) set.rrule(rrule(YEARLY, count=1, byweekday=TH, - dtstart=datetime(1997, 9, 2, 9, 0))) + dtstart=parse("19970902T090000"))) self.assertEqual(list(set), [datetime(1997, 9, 2, 9, 0), datetime(1997, 9, 4, 9, 0), @@ -2966,9 +2809,9 @@ def testSetCachePre(self): def testSetCachePost(self): set = rruleset(cache=True) set.rrule(rrule(YEARLY, count=2, byweekday=TU, - dtstart=datetime(1997, 9, 2, 9, 0))) + dtstart=parse("19970902T090000"))) set.rrule(rrule(YEARLY, count=1, byweekday=TH, - dtstart=datetime(1997, 9, 2, 9, 0))) + dtstart=parse("19970902T090000"))) for x in set: pass self.assertEqual(list(set), [datetime(1997, 9, 2, 9, 0), @@ -2978,9 +2821,9 @@ def testSetCachePost(self): def testSetCachePostInternal(self): set = rruleset(cache=True) set.rrule(rrule(YEARLY, count=2, byweekday=TU, - dtstart=datetime(1997, 9, 2, 9, 0))) + dtstart=parse("19970902T090000"))) set.rrule(rrule(YEARLY, count=1, byweekday=TH, - dtstart=datetime(1997, 9, 2, 9, 0))) + dtstart=parse("19970902T090000"))) for x in set: pass self.assertEqual(list(set._cache), [datetime(1997, 9, 2, 9, 0), @@ -3046,7 +2889,7 @@ def testStrSpacesAndLines(self): def testStrNoDTStart(self): self.assertEqual(list(rrulestr( "RRULE:FREQ=YEARLY;COUNT=3\n" - , dtstart=datetime(1997, 9, 2, 9, 0))), + , dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 9, 0), datetime(1998, 9, 2, 9, 0), datetime(1999, 9, 2, 9, 0)]) @@ -3054,7 +2897,7 @@ def testStrNoDTStart(self): def testStrValueOnly(self): self.assertEqual(list(rrulestr( "FREQ=YEARLY;COUNT=3\n" - , dtstart=datetime(1997, 9, 2, 9, 0))), + , dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 9, 0), datetime(1998, 9, 2, 9, 0), datetime(1999, 9, 2, 9, 0)]) @@ -3062,7 +2905,7 @@ def testStrValueOnly(self): def testStrUnfold(self): self.assertEqual(list(rrulestr( "FREQ=YEA\n RLY;COUNT=3\n", unfold=True, - dtstart=datetime(1997, 9, 2, 9, 0))), + dtstart=parse("19970902T090000"))), [datetime(1997, 9, 2, 9, 0), datetime(1998, 9, 2, 9, 0), datetime(1999, 9, 2, 9, 0)]) @@ -3167,1729 +3010,169 @@ def testBadBySetPos(self): rrule, MONTHLY, count=1, bysetpos=0, - dtstart=datetime(1997, 9, 2, 9, 0)) + dtstart=parse("19970902T090000")) def testBadBySetPosMany(self): self.assertRaises(ValueError, rrule, MONTHLY, count=1, bysetpos=(-1, 0, 1), - dtstart=datetime(1997, 9, 2, 9, 0)) - - # Tests to ensure that str(rrule) works - def testToStrYearly(self): - rule = rrule(YEARLY, count=3, dtstart=datetime(1997, 9, 2, 9, 0)) - self._rrulestr_reverse_test(rule) - - def testToStrYearlyInterval(self): - rule = rrule(YEARLY, count=3, interval=2, - dtstart=datetime(1997, 9, 2, 9, 0)) - self._rrulestr_reverse_test(rule) - - def testToStrYearlyByMonth(self): - rule = rrule(YEARLY, count=3, bymonth=(1, 3), - dtstart=datetime(1997, 9, 2, 9, 0)) - - self._rrulestr_reverse_test(rule) - - def testToStrYearlyByMonth(self): - self._rrulestr_reverse_test(rrule(YEARLY, - count=3, - bymonth=(1, 3), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrYearlyByMonthDay(self): - self._rrulestr_reverse_test(rrule(YEARLY, - count=3, - bymonthday=(1, 3), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrYearlyByMonthAndMonthDay(self): - self._rrulestr_reverse_test(rrule(YEARLY, - count=3, - bymonth=(1, 3), - bymonthday=(5, 7), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrYearlyByWeekDay(self): - self._rrulestr_reverse_test(rrule(YEARLY, - count=3, - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrYearlyByNWeekDay(self): - self._rrulestr_reverse_test(rrule(YEARLY, - count=3, - byweekday=(TU(1), TH(-1)), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrYearlyByNWeekDayLarge(self): - self._rrulestr_reverse_test(rrule(YEARLY, - count=3, - byweekday=(TU(3), TH(-3)), - dtstart=datetime(1997, 9, 2, 9, 0))) + dtstart=parse("19970902T090000")) - def testToStrYearlyByMonthAndWeekDay(self): - self._rrulestr_reverse_test(rrule(YEARLY, - count=3, - bymonth=(1, 3), - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))) - def testToStrYearlyByMonthAndNWeekDay(self): - self._rrulestr_reverse_test(rrule(YEARLY, - count=3, - bymonth=(1, 3), - byweekday=(TU(1), TH(-1)), - dtstart=datetime(1997, 9, 2, 9, 0))) +class ParserTest(unittest.TestCase): - def testToStrYearlyByMonthAndNWeekDayLarge(self): - # This is interesting because the TH(-3) ends up before - # the TU(3). - self._rrulestr_reverse_test(rrule(YEARLY, - count=3, - bymonth=(1, 3), - byweekday=(TU(3), TH(-3)), - dtstart=datetime(1997, 9, 2, 9, 0))) + def setUp(self): + self.tzinfos = {"BRST": -10800} + self.brsttz = tzoffset("BRST", -10800) + self.default = datetime(2003, 9, 25) - def testToStrYearlyByMonthDayAndWeekDay(self): - self._rrulestr_reverse_test(rrule(YEARLY, - count=3, - bymonthday=(1, 3), - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))) + def testDateCommandFormat(self): + self.assertEqual(parse("Thu Sep 25 10:36:28 BRST 2003", + tzinfos=self.tzinfos), + datetime(2003, 9, 25, 10, 36, 28, + tzinfo=self.brsttz)) - def testToStrYearlyByMonthAndMonthDayAndWeekDay(self): - self._rrulestr_reverse_test(rrule(YEARLY, - count=3, - bymonth=(1, 3), - bymonthday=(1, 3), - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))) + def testDateCommandFormatUnicode(self): + self.assertEqual(parse("Thu Sep 25 10:36:28 BRST 2003", + tzinfos=self.tzinfos), + datetime(2003, 9, 25, 10, 36, 28, + tzinfo=self.brsttz)) - def testToStrYearlyByYearDay(self): - self._rrulestr_reverse_test(rrule(YEARLY, - count=4, - byyearday=(1, 100, 200, 365), - dtstart=datetime(1997, 9, 2, 9, 0))) - def testToStrYearlyByYearDayNeg(self): - self._rrulestr_reverse_test(rrule(YEARLY, - count=4, - byyearday=(-365, -266, -166, -1), - dtstart=datetime(1997, 9, 2, 9, 0))) + def testDateCommandFormatReversed(self): + self.assertEqual(parse("2003 10:36:28 BRST 25 Sep Thu", + tzinfos=self.tzinfos), + datetime(2003, 9, 25, 10, 36, 28, + tzinfo=self.brsttz)) - def testToStrYearlyByMonthAndYearDay(self): - self._rrulestr_reverse_test(rrule(YEARLY, - count=4, - bymonth=(4, 7), - byyearday=(1, 100, 200, 365), - dtstart=datetime(1997, 9, 2, 9, 0))) + def testDateCommandFormatWithLong(self): + if not PY3: + self.assertEqual(parse("Thu Sep 25 10:36:28 BRST 2003", + tzinfos={"BRST": long(-10800)}), + datetime(2003, 9, 25, 10, 36, 28, + tzinfo=self.brsttz)) + def testDateCommandFormatIgnoreTz(self): + self.assertEqual(parse("Thu Sep 25 10:36:28 BRST 2003", + ignoretz=True), + datetime(2003, 9, 25, 10, 36, 28)) - def testToStrYearlyByMonthAndYearDayNeg(self): - self._rrulestr_reverse_test(rrule(YEARLY, - count=4, - bymonth=(4, 7), - byyearday=(-365, -266, -166, -1), - dtstart=datetime(1997, 9, 2, 9, 0))) + def testDateCommandFormatStrip1(self): + self.assertEqual(parse("Thu Sep 25 10:36:28 2003"), + datetime(2003, 9, 25, 10, 36, 28)) - def testToStrYearlyByWeekNo(self): - self._rrulestr_reverse_test(rrule(YEARLY, - count=3, - byweekno=20, - dtstart=datetime(1997, 9, 2, 9, 0))) + def testDateCommandFormatStrip2(self): + self.assertEqual(parse("Thu Sep 25 10:36:28", default=self.default), + datetime(2003, 9, 25, 10, 36, 28)) - def testToStrYearlyByWeekNoAndWeekDay(self): - # That's a nice one. The first days of week number one - # may be in the last year. - self._rrulestr_reverse_test(rrule(YEARLY, - count=3, - byweekno=1, - byweekday=MO, - dtstart=datetime(1997, 9, 2, 9, 0))) + def testDateCommandFormatStrip3(self): + self.assertEqual(parse("Thu Sep 10:36:28", default=self.default), + datetime(2003, 9, 25, 10, 36, 28)) - def testToStrYearlyByWeekNoAndWeekDayLarge(self): - # Another nice test. The last days of week number 52/53 - # may be in the next year. - self._rrulestr_reverse_test(rrule(YEARLY, - count=3, - byweekno=52, - byweekday=SU, - dtstart=datetime(1997, 9, 2, 9, 0))) + def testDateCommandFormatStrip4(self): + self.assertEqual(parse("Thu 10:36:28", default=self.default), + datetime(2003, 9, 25, 10, 36, 28)) - def testToStrYearlyByWeekNoAndWeekDayLast(self): - self._rrulestr_reverse_test(rrule(YEARLY, - count=3, - byweekno=-1, - byweekday=SU, - dtstart=datetime(1997, 9, 2, 9, 0))) + def testDateCommandFormatStrip5(self): + self.assertEqual(parse("Sep 10:36:28", default=self.default), + datetime(2003, 9, 25, 10, 36, 28)) - def testToStrYearlyByEaster(self): - self._rrulestr_reverse_test(rrule(YEARLY, - count=3, - byeaster=0, - dtstart=datetime(1997, 9, 2, 9, 0))) + def testDateCommandFormatStrip6(self): + self.assertEqual(parse("10:36:28", default=self.default), + datetime(2003, 9, 25, 10, 36, 28)) - def testToStrYearlyByEasterPos(self): - self._rrulestr_reverse_test(rrule(YEARLY, - count=3, - byeaster=1, - dtstart=datetime(1997, 9, 2, 9, 0))) + def testDateCommandFormatStrip7(self): + self.assertEqual(parse("10:36", default=self.default), + datetime(2003, 9, 25, 10, 36)) - def testToStrYearlyByEasterNeg(self): - self._rrulestr_reverse_test(rrule(YEARLY, - count=3, - byeaster=-1, - dtstart=datetime(1997, 9, 2, 9, 0))) + def testDateCommandFormatStrip8(self): + self.assertEqual(parse("Thu Sep 25 2003"), + datetime(2003, 9, 25)) - def testToStrYearlyByWeekNoAndWeekDay53(self): - self._rrulestr_reverse_test(rrule(YEARLY, - count=3, - byweekno=53, - byweekday=MO, - dtstart=datetime(1997, 9, 2, 9, 0))) + def testDateCommandFormatStrip9(self): + self.assertEqual(parse("Sep 25 2003"), + datetime(2003, 9, 25)) - def testToStrYearlyByHour(self): - self._rrulestr_reverse_test(rrule(YEARLY, - count=3, - byhour=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) + def testDateCommandFormatStrip10(self): + self.assertEqual(parse("Sep 2003", default=self.default), + datetime(2003, 9, 25)) - def testToStrYearlyByMinute(self): - self._rrulestr_reverse_test(rrule(YEARLY, - count=3, - byminute=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) + def testDateCommandFormatStrip11(self): + self.assertEqual(parse("Sep", default=self.default), + datetime(2003, 9, 25)) - def testToStrYearlyBySecond(self): - self._rrulestr_reverse_test(rrule(YEARLY, - count=3, - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) + def testDateCommandFormatStrip12(self): + self.assertEqual(parse("2003", default=self.default), + datetime(2003, 9, 25)) - def testToStrYearlyByHourAndMinute(self): - self._rrulestr_reverse_test(rrule(YEARLY, - count=3, - byhour=(6, 18), - byminute=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) + def testDateRCommandFormat(self): + self.assertEqual(parse("Thu, 25 Sep 2003 10:49:41 -0300"), + datetime(2003, 9, 25, 10, 49, 41, + tzinfo=self.brsttz)) - def testToStrYearlyByHourAndSecond(self): - self._rrulestr_reverse_test(rrule(YEARLY, - count=3, - byhour=(6, 18), - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) + def testISOFormat(self): + self.assertEqual(parse("2003-09-25T10:49:41.5-03:00"), + datetime(2003, 9, 25, 10, 49, 41, 500000, + tzinfo=self.brsttz)) - def testToStrYearlyByMinuteAndSecond(self): - self._rrulestr_reverse_test(rrule(YEARLY, - count=3, - byminute=(6, 18), - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) + def testISOFormatStrip1(self): + self.assertEqual(parse("2003-09-25T10:49:41-03:00"), + datetime(2003, 9, 25, 10, 49, 41, + tzinfo=self.brsttz)) - def testToStrYearlyByHourAndMinuteAndSecond(self): - self._rrulestr_reverse_test(rrule(YEARLY, - count=3, - byhour=(6, 18), - byminute=(6, 18), - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) + def testISOFormatStrip2(self): + self.assertEqual(parse("2003-09-25T10:49:41"), + datetime(2003, 9, 25, 10, 49, 41)) - def testToStrYearlyBySetPos(self): - self._rrulestr_reverse_test(rrule(YEARLY, - count=3, - bymonthday=15, - byhour=(6, 18), - bysetpos=(3, -3), - dtstart=datetime(1997, 9, 2, 9, 0))) + def testISOFormatStrip3(self): + self.assertEqual(parse("2003-09-25T10:49"), + datetime(2003, 9, 25, 10, 49)) - def testToStrMonthly(self): - self._rrulestr_reverse_test(rrule(MONTHLY, - count=3, - dtstart=datetime(1997, 9, 2, 9, 0))) + def testISOFormatStrip4(self): + self.assertEqual(parse("2003-09-25T10"), + datetime(2003, 9, 25, 10)) - def testToStrMonthlyInterval(self): - self._rrulestr_reverse_test(rrule(MONTHLY, - count=3, - interval=2, - dtstart=datetime(1997, 9, 2, 9, 0))) + def testISOFormatStrip5(self): + self.assertEqual(parse("2003-09-25"), + datetime(2003, 9, 25)) - def testToStrMonthlyIntervalLarge(self): - self._rrulestr_reverse_test(rrule(MONTHLY, - count=3, - interval=18, - dtstart=datetime(1997, 9, 2, 9, 0))) + def testISOStrippedFormat(self): + self.assertEqual(parse("20030925T104941.5-0300"), + datetime(2003, 9, 25, 10, 49, 41, 500000, + tzinfo=self.brsttz)) - def testToStrMonthlyByMonth(self): - self._rrulestr_reverse_test(rrule(MONTHLY, - count=3, - bymonth=(1, 3), - dtstart=datetime(1997, 9, 2, 9, 0))) + def testISOStrippedFormatStrip1(self): + self.assertEqual(parse("20030925T104941-0300"), + datetime(2003, 9, 25, 10, 49, 41, + tzinfo=self.brsttz)) - def testToStrMonthlyByMonthDay(self): - self._rrulestr_reverse_test(rrule(MONTHLY, - count=3, - bymonthday=(1, 3), - dtstart=datetime(1997, 9, 2, 9, 0))) + def testISOStrippedFormatStrip2(self): + self.assertEqual(parse("20030925T104941"), + datetime(2003, 9, 25, 10, 49, 41)) - def testToStrMonthlyByMonthAndMonthDay(self): - self._rrulestr_reverse_test(rrule(MONTHLY, - count=3, - bymonth=(1, 3), - bymonthday=(5, 7), - dtstart=datetime(1997, 9, 2, 9, 0))) + def testISOStrippedFormatStrip3(self): + self.assertEqual(parse("20030925T1049"), + datetime(2003, 9, 25, 10, 49, 0)) - def testToStrMonthlyByWeekDay(self): - self._rrulestr_reverse_test(rrule(MONTHLY, - count=3, - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))) - - # Third Monday of the month - self.assertEqual(rrule(MONTHLY, - byweekday=(MO(+3)), - dtstart=datetime(1997, 9, 1)).between(datetime(1997, - 9, - 1), - datetime(1997, - 12, - 1)), - [datetime(1997, 9, 15, 0, 0), - datetime(1997, 10, 20, 0, 0), - datetime(1997, 11, 17, 0, 0)]) - - def testToStrMonthlyByNWeekDay(self): - self._rrulestr_reverse_test(rrule(MONTHLY, - count=3, - byweekday=(TU(1), TH(-1)), - dtstart=datetime(1997, 9, 2, 9, 0))) + def testISOStrippedFormatStrip4(self): + self.assertEqual(parse("20030925T10"), + datetime(2003, 9, 25, 10)) - def testToStrMonthlyByNWeekDayLarge(self): - self._rrulestr_reverse_test(rrule(MONTHLY, - count=3, - byweekday=(TU(3), TH(-3)), - dtstart=datetime(1997, 9, 2, 9, 0))) + def testISOStrippedFormatStrip5(self): + self.assertEqual(parse("20030925"), + datetime(2003, 9, 25)) - def testToStrMonthlyByMonthAndWeekDay(self): - self._rrulestr_reverse_test(rrule(MONTHLY, - count=3, - bymonth=(1, 3), - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))) + def testNoSeparator1(self): + self.assertEqual(parse("199709020908"), + datetime(1997, 9, 2, 9, 8)) - def testToStrMonthlyByMonthAndNWeekDay(self): - self._rrulestr_reverse_test(rrule(MONTHLY, - count=3, - bymonth=(1, 3), - byweekday=(TU(1), TH(-1)), - dtstart=datetime(1997, 9, 2, 9, 0))) + def testNoSeparator2(self): + self.assertEqual(parse("19970902090807"), + datetime(1997, 9, 2, 9, 8, 7)) - def testToStrMonthlyByMonthAndNWeekDayLarge(self): - self._rrulestr_reverse_test(rrule(MONTHLY, - count=3, - bymonth=(1, 3), - byweekday=(TU(3), TH(-3)), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMonthlyByMonthDayAndWeekDay(self): - self._rrulestr_reverse_test(rrule(MONTHLY, - count=3, - bymonthday=(1, 3), - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMonthlyByMonthAndMonthDayAndWeekDay(self): - self._rrulestr_reverse_test(rrule(MONTHLY, - count=3, - bymonth=(1, 3), - bymonthday=(1, 3), - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMonthlyByYearDay(self): - self._rrulestr_reverse_test(rrule(MONTHLY, - count=4, - byyearday=(1, 100, 200, 365), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMonthlyByYearDayNeg(self): - self._rrulestr_reverse_test(rrule(MONTHLY, - count=4, - byyearday=(-365, -266, -166, -1), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMonthlyByMonthAndYearDay(self): - self._rrulestr_reverse_test(rrule(MONTHLY, - count=4, - bymonth=(4, 7), - byyearday=(1, 100, 200, 365), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMonthlyByMonthAndYearDayNeg(self): - self._rrulestr_reverse_test(rrule(MONTHLY, - count=4, - bymonth=(4, 7), - byyearday=(-365, -266, -166, -1), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMonthlyByWeekNo(self): - self._rrulestr_reverse_test(rrule(MONTHLY, - count=3, - byweekno=20, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMonthlyByWeekNoAndWeekDay(self): - # That's a nice one. The first days of week number one - # may be in the last year. - self._rrulestr_reverse_test(rrule(MONTHLY, - count=3, - byweekno=1, - byweekday=MO, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMonthlyByWeekNoAndWeekDayLarge(self): - # Another nice test. The last days of week number 52/53 - # may be in the next year. - self._rrulestr_reverse_test(rrule(MONTHLY, - count=3, - byweekno=52, - byweekday=SU, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMonthlyByWeekNoAndWeekDayLast(self): - self._rrulestr_reverse_test(rrule(MONTHLY, - count=3, - byweekno=-1, - byweekday=SU, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMonthlyByWeekNoAndWeekDay53(self): - self._rrulestr_reverse_test(rrule(MONTHLY, - count=3, - byweekno=53, - byweekday=MO, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMonthlyByEaster(self): - self._rrulestr_reverse_test(rrule(MONTHLY, - count=3, - byeaster=0, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMonthlyByEasterPos(self): - self._rrulestr_reverse_test(rrule(MONTHLY, - count=3, - byeaster=1, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMonthlyByEasterNeg(self): - self._rrulestr_reverse_test(rrule(MONTHLY, - count=3, - byeaster=-1, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMonthlyByHour(self): - self._rrulestr_reverse_test(rrule(MONTHLY, - count=3, - byhour=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMonthlyByMinute(self): - self._rrulestr_reverse_test(rrule(MONTHLY, - count=3, - byminute=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMonthlyBySecond(self): - self._rrulestr_reverse_test(rrule(MONTHLY, - count=3, - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMonthlyByHourAndMinute(self): - self._rrulestr_reverse_test(rrule(MONTHLY, - count=3, - byhour=(6, 18), - byminute=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMonthlyByHourAndSecond(self): - self._rrulestr_reverse_test(rrule(MONTHLY, - count=3, - byhour=(6, 18), - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMonthlyByMinuteAndSecond(self): - self._rrulestr_reverse_test(rrule(MONTHLY, - count=3, - byminute=(6, 18), - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMonthlyByHourAndMinuteAndSecond(self): - self._rrulestr_reverse_test(rrule(MONTHLY, - count=3, - byhour=(6, 18), - byminute=(6, 18), - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMonthlyBySetPos(self): - self._rrulestr_reverse_test(rrule(MONTHLY, - count=3, - bymonthday=(13, 17), - byhour=(6, 18), - bysetpos=(3, -3), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrWeekly(self): - self._rrulestr_reverse_test(rrule(WEEKLY, - count=3, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrWeeklyInterval(self): - self._rrulestr_reverse_test(rrule(WEEKLY, - count=3, - interval=2, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrWeeklyIntervalLarge(self): - self._rrulestr_reverse_test(rrule(WEEKLY, - count=3, - interval=20, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrWeeklyByMonth(self): - self._rrulestr_reverse_test(rrule(WEEKLY, - count=3, - bymonth=(1, 3), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrWeeklyByMonthDay(self): - self._rrulestr_reverse_test(rrule(WEEKLY, - count=3, - bymonthday=(1, 3), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrWeeklyByMonthAndMonthDay(self): - self._rrulestr_reverse_test(rrule(WEEKLY, - count=3, - bymonth=(1, 3), - bymonthday=(5, 7), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrWeeklyByWeekDay(self): - self._rrulestr_reverse_test(rrule(WEEKLY, - count=3, - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrWeeklyByNWeekDay(self): - self._rrulestr_reverse_test(rrule(WEEKLY, - count=3, - byweekday=(TU(1), TH(-1)), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrWeeklyByMonthAndWeekDay(self): - # This test is interesting, because it crosses the year - # boundary in a weekly period to find day '1' as a - # valid recurrence. - self._rrulestr_reverse_test(rrule(WEEKLY, - count=3, - bymonth=(1, 3), - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrWeeklyByMonthAndNWeekDay(self): - self._rrulestr_reverse_test(rrule(WEEKLY, - count=3, - bymonth=(1, 3), - byweekday=(TU(1), TH(-1)), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrWeeklyByMonthDayAndWeekDay(self): - self._rrulestr_reverse_test(rrule(WEEKLY, - count=3, - bymonthday=(1, 3), - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrWeeklyByMonthAndMonthDayAndWeekDay(self): - self._rrulestr_reverse_test(rrule(WEEKLY, - count=3, - bymonth=(1, 3), - bymonthday=(1, 3), - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrWeeklyByYearDay(self): - self._rrulestr_reverse_test(rrule(WEEKLY, - count=4, - byyearday=(1, 100, 200, 365), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrWeeklyByYearDayNeg(self): - self._rrulestr_reverse_test(rrule(WEEKLY, - count=4, - byyearday=(-365, -266, -166, -1), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrWeeklyByMonthAndYearDay(self): - self._rrulestr_reverse_test(rrule(WEEKLY, - count=4, - bymonth=(1, 7), - byyearday=(1, 100, 200, 365), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrWeeklyByMonthAndYearDayNeg(self): - self._rrulestr_reverse_test(rrule(WEEKLY, - count=4, - bymonth=(1, 7), - byyearday=(-365, -266, -166, -1), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrWeeklyByWeekNo(self): - self._rrulestr_reverse_test(rrule(WEEKLY, - count=3, - byweekno=20, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrWeeklyByWeekNoAndWeekDay(self): - # That's a nice one. The first days of week number one - # may be in the last year. - self._rrulestr_reverse_test(rrule(WEEKLY, - count=3, - byweekno=1, - byweekday=MO, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrWeeklyByWeekNoAndWeekDayLarge(self): - # Another nice test. The last days of week number 52/53 - # may be in the next year. - self._rrulestr_reverse_test(rrule(WEEKLY, - count=3, - byweekno=52, - byweekday=SU, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrWeeklyByWeekNoAndWeekDayLast(self): - self._rrulestr_reverse_test(rrule(WEEKLY, - count=3, - byweekno=-1, - byweekday=SU, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrWeeklyByWeekNoAndWeekDay53(self): - self._rrulestr_reverse_test(rrule(WEEKLY, - count=3, - byweekno=53, - byweekday=MO, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrWeeklyByEaster(self): - self._rrulestr_reverse_test(rrule(WEEKLY, - count=3, - byeaster=0, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrWeeklyByEasterPos(self): - self._rrulestr_reverse_test(rrule(WEEKLY, - count=3, - byeaster=1, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrWeeklyByEasterNeg(self): - self._rrulestr_reverse_test(rrule(WEEKLY, - count=3, - byeaster=-1, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrWeeklyByHour(self): - self._rrulestr_reverse_test(rrule(WEEKLY, - count=3, - byhour=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrWeeklyByMinute(self): - self._rrulestr_reverse_test(rrule(WEEKLY, - count=3, - byminute=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrWeeklyBySecond(self): - self._rrulestr_reverse_test(rrule(WEEKLY, - count=3, - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrWeeklyByHourAndMinute(self): - self._rrulestr_reverse_test(rrule(WEEKLY, - count=3, - byhour=(6, 18), - byminute=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrWeeklyByHourAndSecond(self): - self._rrulestr_reverse_test(rrule(WEEKLY, - count=3, - byhour=(6, 18), - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrWeeklyByMinuteAndSecond(self): - self._rrulestr_reverse_test(rrule(WEEKLY, - count=3, - byminute=(6, 18), - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrWeeklyByHourAndMinuteAndSecond(self): - self._rrulestr_reverse_test(rrule(WEEKLY, - count=3, - byhour=(6, 18), - byminute=(6, 18), - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrWeeklyBySetPos(self): - self._rrulestr_reverse_test(rrule(WEEKLY, - count=3, - byweekday=(TU, TH), - byhour=(6, 18), - bysetpos=(3, -3), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrDaily(self): - self._rrulestr_reverse_test(rrule(DAILY, - count=3, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrDailyInterval(self): - self._rrulestr_reverse_test(rrule(DAILY, - count=3, - interval=2, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrDailyIntervalLarge(self): - self._rrulestr_reverse_test(rrule(DAILY, - count=3, - interval=92, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrDailyByMonth(self): - self._rrulestr_reverse_test(rrule(DAILY, - count=3, - bymonth=(1, 3), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrDailyByMonthDay(self): - self._rrulestr_reverse_test(rrule(DAILY, - count=3, - bymonthday=(1, 3), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrDailyByMonthAndMonthDay(self): - self._rrulestr_reverse_test(rrule(DAILY, - count=3, - bymonth=(1, 3), - bymonthday=(5, 7), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrDailyByWeekDay(self): - self._rrulestr_reverse_test(rrule(DAILY, - count=3, - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrDailyByNWeekDay(self): - self._rrulestr_reverse_test(rrule(DAILY, - count=3, - byweekday=(TU(1), TH(-1)), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrDailyByMonthAndWeekDay(self): - self._rrulestr_reverse_test(rrule(DAILY, - count=3, - bymonth=(1, 3), - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrDailyByMonthAndNWeekDay(self): - self._rrulestr_reverse_test(rrule(DAILY, - count=3, - bymonth=(1, 3), - byweekday=(TU(1), TH(-1)), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrDailyByMonthDayAndWeekDay(self): - self._rrulestr_reverse_test(rrule(DAILY, - count=3, - bymonthday=(1, 3), - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrDailyByMonthAndMonthDayAndWeekDay(self): - self._rrulestr_reverse_test(rrule(DAILY, - count=3, - bymonth=(1, 3), - bymonthday=(1, 3), - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrDailyByYearDay(self): - self._rrulestr_reverse_test(rrule(DAILY, - count=4, - byyearday=(1, 100, 200, 365), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrDailyByYearDayNeg(self): - self._rrulestr_reverse_test(rrule(DAILY, - count=4, - byyearday=(-365, -266, -166, -1), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrDailyByMonthAndYearDay(self): - self._rrulestr_reverse_test(rrule(DAILY, - count=4, - bymonth=(1, 7), - byyearday=(1, 100, 200, 365), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrDailyByMonthAndYearDayNeg(self): - self._rrulestr_reverse_test(rrule(DAILY, - count=4, - bymonth=(1, 7), - byyearday=(-365, -266, -166, -1), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrDailyByWeekNo(self): - self._rrulestr_reverse_test(rrule(DAILY, - count=3, - byweekno=20, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrDailyByWeekNoAndWeekDay(self): - # That's a nice one. The first days of week number one - # may be in the last year. - self._rrulestr_reverse_test(rrule(DAILY, - count=3, - byweekno=1, - byweekday=MO, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrDailyByWeekNoAndWeekDayLarge(self): - # Another nice test. The last days of week number 52/53 - # may be in the next year. - self._rrulestr_reverse_test(rrule(DAILY, - count=3, - byweekno=52, - byweekday=SU, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrDailyByWeekNoAndWeekDayLast(self): - self._rrulestr_reverse_test(rrule(DAILY, - count=3, - byweekno=-1, - byweekday=SU, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrDailyByWeekNoAndWeekDay53(self): - self._rrulestr_reverse_test(rrule(DAILY, - count=3, - byweekno=53, - byweekday=MO, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrDailyByEaster(self): - self._rrulestr_reverse_test(rrule(DAILY, - count=3, - byeaster=0, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrDailyByEasterPos(self): - self._rrulestr_reverse_test(rrule(DAILY, - count=3, - byeaster=1, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrDailyByEasterNeg(self): - self._rrulestr_reverse_test(rrule(DAILY, - count=3, - byeaster=-1, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrDailyByHour(self): - self._rrulestr_reverse_test(rrule(DAILY, - count=3, - byhour=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrDailyByMinute(self): - self._rrulestr_reverse_test(rrule(DAILY, - count=3, - byminute=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrDailyBySecond(self): - self._rrulestr_reverse_test(rrule(DAILY, - count=3, - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrDailyByHourAndMinute(self): - self._rrulestr_reverse_test(rrule(DAILY, - count=3, - byhour=(6, 18), - byminute=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrDailyByHourAndSecond(self): - self._rrulestr_reverse_test(rrule(DAILY, - count=3, - byhour=(6, 18), - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrDailyByMinuteAndSecond(self): - self._rrulestr_reverse_test(rrule(DAILY, - count=3, - byminute=(6, 18), - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrDailyByHourAndMinuteAndSecond(self): - self._rrulestr_reverse_test(rrule(DAILY, - count=3, - byhour=(6, 18), - byminute=(6, 18), - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrDailyBySetPos(self): - self._rrulestr_reverse_test(rrule(DAILY, - count=3, - byhour=(6, 18), - byminute=(15, 45), - bysetpos=(3, -3), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrHourly(self): - self._rrulestr_reverse_test(rrule(HOURLY, - count=3, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrHourlyInterval(self): - self._rrulestr_reverse_test(rrule(HOURLY, - count=3, - interval=2, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrHourlyIntervalLarge(self): - self._rrulestr_reverse_test(rrule(HOURLY, - count=3, - interval=769, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrHourlyByMonth(self): - self._rrulestr_reverse_test(rrule(HOURLY, - count=3, - bymonth=(1, 3), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrHourlyByMonthDay(self): - self._rrulestr_reverse_test(rrule(HOURLY, - count=3, - bymonthday=(1, 3), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrHourlyByMonthAndMonthDay(self): - self._rrulestr_reverse_test(rrule(HOURLY, - count=3, - bymonth=(1, 3), - bymonthday=(5, 7), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrHourlyByWeekDay(self): - self._rrulestr_reverse_test(rrule(HOURLY, - count=3, - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrHourlyByNWeekDay(self): - self._rrulestr_reverse_test(rrule(HOURLY, - count=3, - byweekday=(TU(1), TH(-1)), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrHourlyByMonthAndWeekDay(self): - self._rrulestr_reverse_test(rrule(HOURLY, - count=3, - bymonth=(1, 3), - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrHourlyByMonthAndNWeekDay(self): - self._rrulestr_reverse_test(rrule(HOURLY, - count=3, - bymonth=(1, 3), - byweekday=(TU(1), TH(-1)), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrHourlyByMonthDayAndWeekDay(self): - self._rrulestr_reverse_test(rrule(HOURLY, - count=3, - bymonthday=(1, 3), - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrHourlyByMonthAndMonthDayAndWeekDay(self): - self._rrulestr_reverse_test(rrule(HOURLY, - count=3, - bymonth=(1, 3), - bymonthday=(1, 3), - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrHourlyByYearDay(self): - self._rrulestr_reverse_test(rrule(HOURLY, - count=4, - byyearday=(1, 100, 200, 365), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrHourlyByYearDayNeg(self): - self._rrulestr_reverse_test(rrule(HOURLY, - count=4, - byyearday=(-365, -266, -166, -1), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrHourlyByMonthAndYearDay(self): - self._rrulestr_reverse_test(rrule(HOURLY, - count=4, - bymonth=(4, 7), - byyearday=(1, 100, 200, 365), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrHourlyByMonthAndYearDayNeg(self): - self._rrulestr_reverse_test(rrule(HOURLY, - count=4, - bymonth=(4, 7), - byyearday=(-365, -266, -166, -1), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrHourlyByWeekNo(self): - self._rrulestr_reverse_test(rrule(HOURLY, - count=3, - byweekno=20, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrHourlyByWeekNoAndWeekDay(self): - self._rrulestr_reverse_test(rrule(HOURLY, - count=3, - byweekno=1, - byweekday=MO, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrHourlyByWeekNoAndWeekDayLarge(self): - self._rrulestr_reverse_test(rrule(HOURLY, - count=3, - byweekno=52, - byweekday=SU, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrHourlyByWeekNoAndWeekDayLast(self): - self._rrulestr_reverse_test(rrule(HOURLY, - count=3, - byweekno=-1, - byweekday=SU, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrHourlyByWeekNoAndWeekDay53(self): - self._rrulestr_reverse_test(rrule(HOURLY, - count=3, - byweekno=53, - byweekday=MO, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrHourlyByEaster(self): - self._rrulestr_reverse_test(rrule(HOURLY, - count=3, - byeaster=0, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrHourlyByEasterPos(self): - self._rrulestr_reverse_test(rrule(HOURLY, - count=3, - byeaster=1, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrHourlyByEasterNeg(self): - self._rrulestr_reverse_test(rrule(HOURLY, - count=3, - byeaster=-1, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrHourlyByHour(self): - self._rrulestr_reverse_test(rrule(HOURLY, - count=3, - byhour=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrHourlyByMinute(self): - self._rrulestr_reverse_test(rrule(HOURLY, - count=3, - byminute=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrHourlyBySecond(self): - self._rrulestr_reverse_test(rrule(HOURLY, - count=3, - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrHourlyByHourAndMinute(self): - self._rrulestr_reverse_test(rrule(HOURLY, - count=3, - byhour=(6, 18), - byminute=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrHourlyByHourAndSecond(self): - self._rrulestr_reverse_test(rrule(HOURLY, - count=3, - byhour=(6, 18), - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrHourlyByMinuteAndSecond(self): - self._rrulestr_reverse_test(rrule(HOURLY, - count=3, - byminute=(6, 18), - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrHourlyByHourAndMinuteAndSecond(self): - self._rrulestr_reverse_test(rrule(HOURLY, - count=3, - byhour=(6, 18), - byminute=(6, 18), - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrHourlyBySetPos(self): - self._rrulestr_reverse_test(rrule(HOURLY, - count=3, - byminute=(15, 45), - bysecond=(15, 45), - bysetpos=(3, -3), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMinutely(self): - self._rrulestr_reverse_test(rrule(MINUTELY, - count=3, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMinutelyInterval(self): - self._rrulestr_reverse_test(rrule(MINUTELY, - count=3, - interval=2, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMinutelyIntervalLarge(self): - self._rrulestr_reverse_test(rrule(MINUTELY, - count=3, - interval=1501, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMinutelyByMonth(self): - self._rrulestr_reverse_test(rrule(MINUTELY, - count=3, - bymonth=(1, 3), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMinutelyByMonthDay(self): - self._rrulestr_reverse_test(rrule(MINUTELY, - count=3, - bymonthday=(1, 3), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMinutelyByMonthAndMonthDay(self): - self._rrulestr_reverse_test(rrule(MINUTELY, - count=3, - bymonth=(1, 3), - bymonthday=(5, 7), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMinutelyByWeekDay(self): - self._rrulestr_reverse_test(rrule(MINUTELY, - count=3, - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMinutelyByNWeekDay(self): - self._rrulestr_reverse_test(rrule(MINUTELY, - count=3, - byweekday=(TU(1), TH(-1)), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMinutelyByMonthAndWeekDay(self): - self._rrulestr_reverse_test(rrule(MINUTELY, - count=3, - bymonth=(1, 3), - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMinutelyByMonthAndNWeekDay(self): - self._rrulestr_reverse_test(rrule(MINUTELY, - count=3, - bymonth=(1, 3), - byweekday=(TU(1), TH(-1)), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMinutelyByMonthDayAndWeekDay(self): - self._rrulestr_reverse_test(rrule(MINUTELY, - count=3, - bymonthday=(1, 3), - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMinutelyByMonthAndMonthDayAndWeekDay(self): - self._rrulestr_reverse_test(rrule(MINUTELY, - count=3, - bymonth=(1, 3), - bymonthday=(1, 3), - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMinutelyByYearDay(self): - self._rrulestr_reverse_test(rrule(MINUTELY, - count=4, - byyearday=(1, 100, 200, 365), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMinutelyByYearDayNeg(self): - self._rrulestr_reverse_test(rrule(MINUTELY, - count=4, - byyearday=(-365, -266, -166, -1), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMinutelyByMonthAndYearDay(self): - self._rrulestr_reverse_test(rrule(MINUTELY, - count=4, - bymonth=(4, 7), - byyearday=(1, 100, 200, 365), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMinutelyByMonthAndYearDayNeg(self): - self._rrulestr_reverse_test(rrule(MINUTELY, - count=4, - bymonth=(4, 7), - byyearday=(-365, -266, -166, -1), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMinutelyByWeekNo(self): - self._rrulestr_reverse_test(rrule(MINUTELY, - count=3, - byweekno=20, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMinutelyByWeekNoAndWeekDay(self): - self._rrulestr_reverse_test(rrule(MINUTELY, - count=3, - byweekno=1, - byweekday=MO, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMinutelyByWeekNoAndWeekDayLarge(self): - self._rrulestr_reverse_test(rrule(MINUTELY, - count=3, - byweekno=52, - byweekday=SU, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMinutelyByWeekNoAndWeekDayLast(self): - self._rrulestr_reverse_test(rrule(MINUTELY, - count=3, - byweekno=-1, - byweekday=SU, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMinutelyByWeekNoAndWeekDay53(self): - self._rrulestr_reverse_test(rrule(MINUTELY, - count=3, - byweekno=53, - byweekday=MO, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMinutelyByEaster(self): - self._rrulestr_reverse_test(rrule(MINUTELY, - count=3, - byeaster=0, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMinutelyByEasterPos(self): - self._rrulestr_reverse_test(rrule(MINUTELY, - count=3, - byeaster=1, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMinutelyByEasterNeg(self): - self._rrulestr_reverse_test(rrule(MINUTELY, - count=3, - byeaster=-1, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMinutelyByHour(self): - self._rrulestr_reverse_test(rrule(MINUTELY, - count=3, - byhour=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMinutelyByMinute(self): - self._rrulestr_reverse_test(rrule(MINUTELY, - count=3, - byminute=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMinutelyBySecond(self): - self._rrulestr_reverse_test(rrule(MINUTELY, - count=3, - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMinutelyByHourAndMinute(self): - self._rrulestr_reverse_test(rrule(MINUTELY, - count=3, - byhour=(6, 18), - byminute=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMinutelyByHourAndSecond(self): - self._rrulestr_reverse_test(rrule(MINUTELY, - count=3, - byhour=(6, 18), - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMinutelyByMinuteAndSecond(self): - self._rrulestr_reverse_test(rrule(MINUTELY, - count=3, - byminute=(6, 18), - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMinutelyByHourAndMinuteAndSecond(self): - self._rrulestr_reverse_test(rrule(MINUTELY, - count=3, - byhour=(6, 18), - byminute=(6, 18), - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrMinutelyBySetPos(self): - self._rrulestr_reverse_test(rrule(MINUTELY, - count=3, - bysecond=(15, 30, 45), - bysetpos=(3, -3), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrSecondly(self): - self._rrulestr_reverse_test(rrule(SECONDLY, - count=3, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrSecondlyInterval(self): - self._rrulestr_reverse_test(rrule(SECONDLY, - count=3, - interval=2, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrSecondlyIntervalLarge(self): - self._rrulestr_reverse_test(rrule(SECONDLY, - count=3, - interval=90061, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrSecondlyByMonth(self): - self._rrulestr_reverse_test(rrule(SECONDLY, - count=3, - bymonth=(1, 3), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrSecondlyByMonthDay(self): - self._rrulestr_reverse_test(rrule(SECONDLY, - count=3, - bymonthday=(1, 3), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrSecondlyByMonthAndMonthDay(self): - self._rrulestr_reverse_test(rrule(SECONDLY, - count=3, - bymonth=(1, 3), - bymonthday=(5, 7), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrSecondlyByWeekDay(self): - self._rrulestr_reverse_test(rrule(SECONDLY, - count=3, - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrSecondlyByNWeekDay(self): - self._rrulestr_reverse_test(rrule(SECONDLY, - count=3, - byweekday=(TU(1), TH(-1)), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrSecondlyByMonthAndWeekDay(self): - self._rrulestr_reverse_test(rrule(SECONDLY, - count=3, - bymonth=(1, 3), - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrSecondlyByMonthAndNWeekDay(self): - self._rrulestr_reverse_test(rrule(SECONDLY, - count=3, - bymonth=(1, 3), - byweekday=(TU(1), TH(-1)), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrSecondlyByMonthDayAndWeekDay(self): - self._rrulestr_reverse_test(rrule(SECONDLY, - count=3, - bymonthday=(1, 3), - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrSecondlyByMonthAndMonthDayAndWeekDay(self): - self._rrulestr_reverse_test(rrule(SECONDLY, - count=3, - bymonth=(1, 3), - bymonthday=(1, 3), - byweekday=(TU, TH), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrSecondlyByYearDay(self): - self._rrulestr_reverse_test(rrule(SECONDLY, - count=4, - byyearday=(1, 100, 200, 365), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrSecondlyByYearDayNeg(self): - self._rrulestr_reverse_test(rrule(SECONDLY, - count=4, - byyearday=(-365, -266, -166, -1), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrSecondlyByMonthAndYearDay(self): - self._rrulestr_reverse_test(rrule(SECONDLY, - count=4, - bymonth=(4, 7), - byyearday=(1, 100, 200, 365), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrSecondlyByMonthAndYearDayNeg(self): - self._rrulestr_reverse_test(rrule(SECONDLY, - count=4, - bymonth=(4, 7), - byyearday=(-365, -266, -166, -1), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrSecondlyByWeekNo(self): - self._rrulestr_reverse_test(rrule(SECONDLY, - count=3, - byweekno=20, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrSecondlyByWeekNoAndWeekDay(self): - self._rrulestr_reverse_test(rrule(SECONDLY, - count=3, - byweekno=1, - byweekday=MO, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrSecondlyByWeekNoAndWeekDayLarge(self): - self._rrulestr_reverse_test(rrule(SECONDLY, - count=3, - byweekno=52, - byweekday=SU, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrSecondlyByWeekNoAndWeekDayLast(self): - self._rrulestr_reverse_test(rrule(SECONDLY, - count=3, - byweekno=-1, - byweekday=SU, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrSecondlyByWeekNoAndWeekDay53(self): - self._rrulestr_reverse_test(rrule(SECONDLY, - count=3, - byweekno=53, - byweekday=MO, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrSecondlyByEaster(self): - self._rrulestr_reverse_test(rrule(SECONDLY, - count=3, - byeaster=0, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrSecondlyByEasterPos(self): - self._rrulestr_reverse_test(rrule(SECONDLY, - count=3, - byeaster=1, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrSecondlyByEasterNeg(self): - self._rrulestr_reverse_test(rrule(SECONDLY, - count=3, - byeaster=-1, - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrSecondlyByHour(self): - self._rrulestr_reverse_test(rrule(SECONDLY, - count=3, - byhour=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrSecondlyByMinute(self): - self._rrulestr_reverse_test(rrule(SECONDLY, - count=3, - byminute=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrSecondlyBySecond(self): - self._rrulestr_reverse_test(rrule(SECONDLY, - count=3, - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrSecondlyByHourAndMinute(self): - self._rrulestr_reverse_test(rrule(SECONDLY, - count=3, - byhour=(6, 18), - byminute=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrSecondlyByHourAndSecond(self): - self._rrulestr_reverse_test(rrule(SECONDLY, - count=3, - byhour=(6, 18), - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrSecondlyByMinuteAndSecond(self): - self._rrulestr_reverse_test(rrule(SECONDLY, - count=3, - byminute=(6, 18), - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrSecondlyByHourAndMinuteAndSecond(self): - self._rrulestr_reverse_test(rrule(SECONDLY, - count=3, - byhour=(6, 18), - byminute=(6, 18), - bysecond=(6, 18), - dtstart=datetime(1997, 9, 2, 9, 0))) - - def testToStrSecondlyByHourAndMinuteAndSecondBug(self): - # This explores a bug found by Mathieu Bridon. - self._rrulestr_reverse_test(rrule(SECONDLY, - count=3, - bysecond=(0,), - byminute=(1,), - dtstart=datetime(2010, 3, 22, 12, 1))) - - def testToStrLongIntegers(self): - if not PY3: # There is no longs in python3 - self._rrulestr_reverse_test(rrule(MINUTELY, - count=long(2), - interval=long(2), - bymonth=long(2), - byweekday=long(3), - byhour=long(6), - byminute=long(6), - bysecond=long(6), - dtstart=datetime(1997, 9, 2, 9, 0))) - - self._rrulestr_reverse_test(rrule(YEARLY, - count=long(2), - bymonthday=long(5), - byweekno=long(2), - dtstart=datetime(1997, 9, 2, 9, 0))) - - -class ParserTest(unittest.TestCase): - - def setUp(self): - self.tzinfos = {"BRST": -10800} - self.brsttz = tzoffset("BRST", -10800) - self.default = datetime(2003, 9, 25) - - # Parser should be able to handle bytestring and unicode - base_str = '2014-05-01 08:00:00' - try: - # Python 2.x - self.uni_str = unicode(base_str) - self.str_str = str(base_str) - except NameError: - self.uni_str = str(base_str) - self.str_str = bytes(base_str.encode()) - - def testDateCommandFormat(self): - self.assertEqual(parse("Thu Sep 25 10:36:28 BRST 2003", - tzinfos=self.tzinfos), - datetime(2003, 9, 25, 10, 36, 28, - tzinfo=self.brsttz)) - - def testDateCommandFormatUnicode(self): - self.assertEqual(parse("Thu Sep 25 10:36:28 BRST 2003", - tzinfos=self.tzinfos), - datetime(2003, 9, 25, 10, 36, 28, - tzinfo=self.brsttz)) - - - def testDateCommandFormatReversed(self): - self.assertEqual(parse("2003 10:36:28 BRST 25 Sep Thu", - tzinfos=self.tzinfos), - datetime(2003, 9, 25, 10, 36, 28, - tzinfo=self.brsttz)) - - def testDateCommandFormatWithLong(self): - if not PY3: - self.assertEqual(parse("Thu Sep 25 10:36:28 BRST 2003", - tzinfos={"BRST": long(-10800)}), - datetime(2003, 9, 25, 10, 36, 28, - tzinfo=self.brsttz)) - def testDateCommandFormatIgnoreTz(self): - self.assertEqual(parse("Thu Sep 25 10:36:28 BRST 2003", - ignoretz=True), - datetime(2003, 9, 25, 10, 36, 28)) - - def testDateCommandFormatStrip1(self): - self.assertEqual(parse("Thu Sep 25 10:36:28 2003"), - datetime(2003, 9, 25, 10, 36, 28)) - - def testDateCommandFormatStrip2(self): - self.assertEqual(parse("Thu Sep 25 10:36:28", default=self.default), - datetime(2003, 9, 25, 10, 36, 28)) - - def testDateCommandFormatStrip3(self): - self.assertEqual(parse("Thu Sep 10:36:28", default=self.default), - datetime(2003, 9, 25, 10, 36, 28)) - - def testDateCommandFormatStrip4(self): - self.assertEqual(parse("Thu 10:36:28", default=self.default), - datetime(2003, 9, 25, 10, 36, 28)) - - def testDateCommandFormatStrip5(self): - self.assertEqual(parse("Sep 10:36:28", default=self.default), - datetime(2003, 9, 25, 10, 36, 28)) - - def testDateCommandFormatStrip6(self): - self.assertEqual(parse("10:36:28", default=self.default), - datetime(2003, 9, 25, 10, 36, 28)) - - def testDateCommandFormatStrip7(self): - self.assertEqual(parse("10:36", default=self.default), - datetime(2003, 9, 25, 10, 36)) - - def testDateCommandFormatStrip8(self): - self.assertEqual(parse("Thu Sep 25 2003"), - datetime(2003, 9, 25)) - - def testDateCommandFormatStrip9(self): - self.assertEqual(parse("Sep 25 2003"), - datetime(2003, 9, 25)) - - def testDateCommandFormatStrip10(self): - self.assertEqual(parse("Sep 2003", default=self.default), - datetime(2003, 9, 25)) - - def testDateCommandFormatStrip11(self): - self.assertEqual(parse("Sep", default=self.default), - datetime(2003, 9, 25)) - - def testDateCommandFormatStrip12(self): - self.assertEqual(parse("2003", default=self.default), - datetime(2003, 9, 25)) - - def testDateRCommandFormat(self): - self.assertEqual(parse("Thu, 25 Sep 2003 10:49:41 -0300"), - datetime(2003, 9, 25, 10, 49, 41, - tzinfo=self.brsttz)) - - def testISOFormat(self): - self.assertEqual(parse("2003-09-25T10:49:41.5-03:00"), - datetime(2003, 9, 25, 10, 49, 41, 500000, - tzinfo=self.brsttz)) - - def testISOFormatStrip1(self): - self.assertEqual(parse("2003-09-25T10:49:41-03:00"), - datetime(2003, 9, 25, 10, 49, 41, - tzinfo=self.brsttz)) - - def testISOFormatStrip2(self): - self.assertEqual(parse("2003-09-25T10:49:41"), - datetime(2003, 9, 25, 10, 49, 41)) - - def testISOFormatStrip3(self): - self.assertEqual(parse("2003-09-25T10:49"), - datetime(2003, 9, 25, 10, 49)) - - def testISOFormatStrip4(self): - self.assertEqual(parse("2003-09-25T10"), - datetime(2003, 9, 25, 10)) - - def testISOFormatStrip5(self): - self.assertEqual(parse("2003-09-25"), - datetime(2003, 9, 25)) - - def testISOStrippedFormat(self): - self.assertEqual(parse("20030925T104941.5-0300"), - datetime(2003, 9, 25, 10, 49, 41, 500000, - tzinfo=self.brsttz)) - - def testISOStrippedFormatStrip1(self): - self.assertEqual(parse("20030925T104941-0300"), - datetime(2003, 9, 25, 10, 49, 41, - tzinfo=self.brsttz)) - - def testISOStrippedFormatStrip2(self): - self.assertEqual(parse("20030925T104941"), - datetime(2003, 9, 25, 10, 49, 41)) - - def testISOStrippedFormatStrip3(self): - self.assertEqual(parse("20030925T1049"), - datetime(2003, 9, 25, 10, 49, 0)) - - def testISOStrippedFormatStrip4(self): - self.assertEqual(parse("20030925T10"), - datetime(2003, 9, 25, 10)) - - def testISOStrippedFormatStrip5(self): - self.assertEqual(parse("20030925"), - datetime(2003, 9, 25)) - - def testPythonLoggerFormat(self): - self.assertEqual(parse("2003-09-25 10:49:41,502"), - datetime(2003, 9, 25, 10, 49, 41, 502000)) - - def testNoSeparator1(self): - self.assertEqual(parse("199709020908"), - datetime(1997, 9, 2, 9, 8)) - - def testNoSeparator2(self): - self.assertEqual(parse("19970902090807"), - datetime(1997, 9, 2, 9, 8, 7)) - - def testDateWithDash1(self): - self.assertEqual(parse("2003-09-25"), - datetime(2003, 9, 25)) + def testDateWithDash1(self): + self.assertEqual(parse("2003-09-25"), + datetime(2003, 9, 25)) def testDateWithDash2(self): self.assertEqual(parse("2003-Sep-25"), @@ -5175,30 +3458,16 @@ def testFuzzy(self): self.assertEqual(parse(s, fuzzy=True), datetime(2003, 9, 25, 10, 49, 41, tzinfo=self.brsttz)) - def testFuzzyWithTokens(self): s = "Today is 25 of September of 2003, exactly " \ "at 10:49:41 with timezone -03:00." self.assertEqual(parse(s, fuzzy_with_tokens=True), - (datetime(2003, 9, 25, 10, 49, 41, - tzinfo=self.brsttz), - ('Today is ', 'of ', ', exactly at ', - ' with timezone ', '.'))) - - def testFuzzyAMPMProblem(self): - # Sometimes fuzzy parsing results in AM/PM flag being set without - # hours - if it's fuzzy it should ignore that. - s1 = "I have a meeting on March 1, 1974." - s2 = "On June 8th, 2020, I am going to be the first man on Mars" - - # Also don't want any erroneous AM or PMs changing the parsed time - s3 = "Meet me at the AM/PM on Sunset at 3:00 AM on December 3rd, 2003" - s4 = "Meet me at 3:00AM on December 3rd, 2003 at the AM/PM on Sunset" - - self.assertEqual(parse(s1, fuzzy=True), datetime(1974, 3, 1)) - self.assertEqual(parse(s2, fuzzy=True), datetime(2020, 6, 8)) - self.assertEqual(parse(s3, fuzzy=True), datetime(2003, 12, 3, 3)) - self.assertEqual(parse(s4, fuzzy=True), datetime(2003, 12, 3, 3)) + (datetime(2003, 9, 25, 10, 49, 41, + tzinfo=self.brsttz), + ('Today is ', 'of ', ', exactly at ', + ' with timezone ', '.') + ) + ) def testExtraSpace(self): self.assertEqual(parse(" July 4 , 1976 12:01:02 am "), @@ -5342,174 +3611,6 @@ def testRandomFormat35(self): self.assertEqual(parse("2004 10 Apr 11h30m", default=self.default), datetime(2004, 4, 10, 11, 30)) - # Test that if a year is omitted, we use the most recent matching value - def testSmartDefaultsNoYearMonthEarlier(self): - self.assertEqual(parse("August 3", default=datetime(2014, 5, 1), - smart_defaults=True), - datetime(2013, 8, 3)) - - def testSmartDefaultsNoYearDayEarlier(self): - self.assertEqual(parse("August 3", default=datetime(2014, 8, 1), - smart_defaults=True), - datetime(2013, 8, 3)) - - def testSmartDefaultsNoYearSameDay(self): - self.assertEqual(parse("August 3", default=datetime(2014, 8, 3), - smart_defaults=True), - datetime(2014, 8, 3)) - - def testSmartDefaultsNoYearDayLater(self): - self.assertEqual(parse("August 3", default=datetime(2014, 8, 4), - smart_defaults=True), - datetime(2014, 8, 3)) - - def testSmartDefaultsNoYearMonthLater(self): - self.assertEqual(parse("August 3", default=datetime(2014, 12, 19), - smart_defaults=True), - datetime(2014, 8, 3)) - - def testSmartDefaultsNoYearFeb29(self): - self.assertEqual(parse("February 29", default=datetime(2014, 12, 19), - date_in_future=False, smart_defaults=True), - datetime(2012, 2, 29)) - - def testSmartDefaultsNoYearFeb29Y2100(self): - # Year 2000 was not a leap year. - self.assertEqual(parse("February 29", default=datetime(2100, 12, 19), - smart_defaults=True), - datetime(2096, 2, 29)) - - # Test that if a year is omitted, we use the most next matching value - def testSmartDefaultsNoYearFutureDayEarlier(self): - self.assertEqual(parse("August 3", default=datetime(2014, 5, 1), - date_in_future=True, smart_defaults=True), - datetime(2014, 8, 3)) - - def testSmartDefaultsNoYearFutureMonthEarlier(self): - self.assertEqual(parse("August 3", default=datetime(2014, 8, 1), - date_in_future=True, smart_defaults=True), - datetime(2014, 8, 3)) - - def testSmartDefaultsNoYearFutureSameDay(self): - self.assertEqual(parse("August 3", default=datetime(2014, 8, 3), - date_in_future=True, smart_defaults=True), - datetime(2014, 8, 3)) - - def testSmartDefaultsNoYearFutureDayLater(self): - self.assertEqual(parse("August 3", default=datetime(2014, 8, 4), - date_in_future=True, smart_defaults=True), - datetime(2015, 8, 3)) - - def testSmartDefaultsNoYearFutureMonthLater(self): - self.assertEqual(parse("August 3", default=datetime(2014, 12, 19), - date_in_future=True, smart_defaults=True), - datetime(2015, 8, 3)) - - def testSmartDefaultsNoYearFutureFeb29Y2100(self): - self.assertEqual(parse("February 29", default=datetime(2098, 12, 19), - date_in_future=True, smart_defaults=True), - datetime(2104, 2, 29)) - - # Test that if only a month is provided, we select the beginning of the most recent - # occurrence of the specified month - def testSmartDefaultsMonthOnlyMonthEarlier(self): - self.assertEqual(parse("September", default=datetime(2014, 5, 1), - smart_defaults=True), - datetime(2013, 9, 1)) - - def testSmartDefaultsMonthOnlySameMonthFirstDay(self): - self.assertEqual(parse("September", default=datetime(2014, 9, 1), - smart_defaults=True), - datetime(2014, 9, 1)) - - def testSmartDefaultsMonthOnlySameMonthLastDay(self): - self.assertEqual(parse("September", default=datetime(2014, 9, 30), - smart_defaults=True), - datetime(2014, 9, 1)) - - def testSmartDefaultMonthOnlyMonthLater(self): - self.assertEqual(parse("September", default=datetime(2014, 11, 1), - smart_defaults=True), - datetime(2014, 9, 1)) - - # Test that if only a month is provided, we select the beginning of the most recent - # occurrence of the specified month - def testSmartDefaultsMonthOnlyFutureMonthEarlier(self): - self.assertEqual(parse("September", default=datetime(2014, 5, 1), - date_in_future=True, smart_defaults=True), - datetime(2014, 9, 1)) - - def testSmartDefaultsMonthOnlyFutureSameMonthFirstDay(self): - self.assertEqual(parse("September", default=datetime(2014, 9, 1), - date_in_future=True, smart_defaults=True), - datetime(2014, 9, 1)) - - def testSmartDefaultsMonthOnlyFutureSameMonthLastDay(self): - self.assertEqual(parse("September", default=datetime(2014, 9, 30), - date_in_future=True, smart_defaults=True), - datetime(2014, 9, 1)) - - def testSmartDefaultsMonthOnlyFutureMonthLater(self): - self.assertEqual(parse("September", default=datetime(2014, 11, 1), - date_in_future=True, smart_defaults=True), - datetime(2015, 9, 1)) - - # Test to ensure that if a year is specified, January 1st of that year is - # returned. - def testSmartDefaultsYearOnly(self): - self.assertEqual(parse("2009", smart_defaults=True), - datetime(2009, 1, 1)) - - def testSmartDefaultsYearOnlyFuture(self): - self.assertEqual(parse("2009", smart_defaults=True, - date_in_future=True), - datetime(2009, 1, 1)) - - # Tests that invalid days fall back to the end of the month if that's - # the desired behavior. - def testInvalidDayNoFallback(self): - self.assertRaises(ValueError, parse, "Feb 30, 2007", - **{'fallback_on_invalid_day':False}) - - def testInvalidDayFallbackFebNoLeapYear(self): - self.assertEqual(parse("Feb 31, 2007", fallback_on_invalid_day=True), - datetime(2007, 2, 28)) - - def testInvalidDayFallbackFebLeapYear(self): - self.assertEqual(parse("Feb 31, 2008", fallback_on_invalid_day=True), - datetime(2008, 2, 29)) - - def testUnspecifiedDayNoFallback(self): - self.assertRaises(ValueError, parse, "April 2009", - **{'fallback_on_invalid_day':False, - 'default':datetime(2010, 1, 31)}) - - def testUnspecifiedDayUnspecifiedFallback(self): - self.assertEqual(parse("April 2009", default=datetime(2010, 1, 31)), - datetime(2009, 4, 30)) - - def testUnspecifiedDayUnspecifiedFallback(self): - self.assertEqual(parse("April 2009", fallback_on_invalid_day=True, - default=datetime(2010, 1, 31)), - datetime(2009, 4, 30)) - - def testUnspecifiedDayUnspecifiedFallbackFebNoLeapYear(self): - self.assertEqual(parse("Feb 2007", default=datetime(2010, 1, 31)), - datetime(2007, 2, 28)) - - def testUnspecifiedDayUnspecifiedFallbackFebLeapYear(self): - self.assertEqual(parse("Feb 2008", default=datetime(2010, 1, 31)), - datetime(2008, 2, 29)) - - def testErrorType01(self): - self.assertRaises(ValueError, - parse, 'shouldfail') - - def testCorrectErrorOnFuzzyWithTokens(self): - self.assertRaisesRegexp(ValueError, 'Unknown string format', parse, '04/04/32/423', fuzzy_with_tokens=True) - self.assertRaisesRegexp(ValueError, 'Unknown string format', parse, '04/04/04 +32423', fuzzy_with_tokens=True) - self.assertRaisesRegexp(ValueError, 'Unknown string format', parse, '04/04/0d4', fuzzy_with_tokens=True) - def testIncreasingCTime(self): # This test will check 200 different years, every month, every day, # every hour, every minute, every second, and every weekday, using @@ -5552,7 +3653,6 @@ def testHighPrecisionSeconds(self): def testCustomParserInfo(self): # Custom parser info wasn't working, as Michael Elsdörfer discovered. from dateutil.parser import parserinfo, parser - class myparserinfo(parserinfo): MONTHS = parserinfo.MONTHS[:] MONTHS[0] = ("Foo", "Foo") @@ -5560,20 +3660,10 @@ class myparserinfo(parserinfo): dt = myparser.parse("01/Foo/2007") self.assertEqual(dt, datetime(2007, 1, 1)) - def testParseStr(self): - self.assertEqual(parse(self.str_str), - parse(self.uni_str)) - - def testParserParseStr(self): - from dateutil.parser import parser - - self.assertEqual(parser().parse(self.str_str), - parser().parse(self.uni_str)) - class EasterTest(unittest.TestCase): easterlist = [ - # WESTERN ORTHODOX + # WESTERN ORTHODOX (date(1990, 4, 15), date(1990, 4, 15)), (date(1991, 3, 31), date(1991, 4, 7)), (date(1992, 4, 19), date(1992, 4, 26)), @@ -5824,14 +3914,6 @@ def testStrEnd6(self): self.assertEqual(datetime(2003, 10, 26, 1, 00, tzinfo=tzstr(s)).tzname(), "EST") - def testStrStr(self): - # Test that tzstr() won't throw an error if given a str instead - # of a unicode literal. - self.assertEqual(datetime(2003, 4, 6, 1, 59, - tzinfo=tzstr(str("EST5EDT"))).tzname(), "EST") - self.assertEqual(datetime(2003, 4, 6, 2, 00, - tzinfo=tzstr(str("EST5EDT"))).tzname(), "EDT") - def testStrCmp1(self): self.assertEqual(tzstr("EST5EDT"), tzstr("EST5EDT4,M4.1.0/02:00:00,M10-5-0/02:00")) @@ -5861,28 +3943,23 @@ def testFileStart1(self): def testFileEnd1(self): tz = tzfile(BytesIO(base64.decodestring(self.TZFILE_EST5EDT))) - self.assertEqual(datetime(2003, 10, 26, 0, 59, tzinfo=tz).tzname(), - "EDT") - self.assertEqual(datetime(2003, 10, 26, 1, 00, tzinfo=tz).tzname(), - "EST") + self.assertEqual(datetime(2003, 10, 26, 0, 59, tzinfo=tz).tzname(), "EDT") + self.assertEqual(datetime(2003, 10, 26, 1, 00, tzinfo=tz).tzname(), "EST") def testZoneInfoFileStart1(self): tz = zoneinfo.gettz("EST5EDT") - self.assertEqual(datetime(2003, 4, 6, 1, 59, tzinfo=tz).tzname(), "EST", - MISSING_TARBALL) + self.assertEqual(datetime(2003, 4, 6, 1, 59, tzinfo=tz).tzname(), "EST") self.assertEqual(datetime(2003, 4, 6, 2, 00, tzinfo=tz).tzname(), "EDT") def testZoneInfoFileEnd1(self): tz = zoneinfo.gettz("EST5EDT") - self.assertEqual(datetime(2003, 10, 26, 0, 59, tzinfo=tz).tzname(), - "EDT", MISSING_TARBALL) - self.assertEqual(datetime(2003, 10, 26, 1, 00, tzinfo=tz).tzname(), - "EST") + self.assertEqual(datetime(2003, 10, 26, 0, 59, tzinfo=tz).tzname(), "EDT") + self.assertEqual(datetime(2003, 10, 26, 1, 00, tzinfo=tz).tzname(), "EST") def testZoneInfoOffsetSignal(self): utc = zoneinfo.gettz("UTC") nyc = zoneinfo.gettz("America/New_York") - self.assertNotEqual(utc, None, MISSING_TARBALL) + self.assertNotEqual(utc, None) self.assertNotEqual(nyc, None) t0 = datetime(2007, 11, 4, 0, 30, tzinfo=nyc) t1 = t0.astimezone(utc) @@ -5890,12 +3967,6 @@ def testZoneInfoOffsetSignal(self): self.assertEqual(t0, t2) self.assertEqual(nyc.dst(t0), timedelta(hours=1)) - def testTzNameNone(self): - gmt5 = tzoffset(None, -18000) # -5:00 - self.assertIs(datetime(2003, 10, 26, 0, 0, tzinfo=gmt5).tzname(), - None) - - def testICalStart1(self): tz = tzical(StringIO(self.TZICAL_EST5EDT)).get() self.assertEqual(datetime(2003, 4, 6, 1, 59, tzinfo=tz).tzname(), "EST") @@ -5910,7 +3981,7 @@ def testRoundNonFullMinutes(self): # This timezone has an offset of 5992 seconds in 1900-01-01. tz = tzfile(BytesIO(base64.decodestring(self.EUROPE_HELSINKI))) self.assertEqual(str(datetime(1900, 1, 1, 0, 0, tzinfo=tz)), - "1900-01-01 00:00:00+01:40") + "1900-01-01 00:00:00+01:40") def testLeapCountDecodesProperly(self): # This timezone has leapcnt, and failed to decode until @@ -5944,20 +4015,8 @@ def testGMTOffset(self): self.assertEqual(dt.astimezone(tz=gettz("UTC-2")), datetime(2007, 8, 6, 2, 10, tzinfo=tzstr("UTC-2"))) - @unittest.skipUnless(sys.platform.startswith("win"), "requires Windows") - def testIsdstZoneWithNoDaylightSaving(self): - tz = tzwin.tzwin("UTC") - dt = parse("2013-03-06 19:08:15") - self.assertFalse(tz._isdst(dt)) - - @unittest.skipIf(sys.platform.startswith("win"), "requires Unix") - def testTZSetDoesntCorrupt(self): - # if we start in non-UTC then tzset UTC make sure parse doesn't get - # confused - os.environ['TZ'] = 'UTC' - _time.tzset() - # this should parse to UTC timezone not the original timezone - dt = parse('2014-07-20T12:34:56+00:00') - self.assertEqual(str(dt), '2014-07-20 12:34:56+00:00') + +if __name__ == "__main__": + unittest.main() # vim:ts=4:sw=4 diff --git a/tox.ini b/tox.ini index 194c146..5d90c1c 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] -envlist = py26, py27, py32, py33, py34 +envlist = py26, py27, py32, py33 [testenv] -commands = python setup.py test -q +commands = python test.py deps = six diff --git a/updatezinfo.py b/updatezinfo.py old mode 100644 new mode 100755 index 591118e..627231a --- a/updatezinfo.py +++ b/updatezinfo.py @@ -1,33 +1,44 @@ #!/usr/bin/env python import os -import hashlib -import json -import io - -from six.moves.urllib import request +import re +import sys from dateutil.zoneinfo import rebuild -METADATA_FILE = "zonefile_metadata.json" - +SERVER = "ftp.iana.org" +DIR = "/tz" +NAME = re.compile("tzdata(.*).tar.gz") def main(): - with io.open(METADATA_FILE, 'r') as f: - metadata = json.load(f) - - if not os.path.isfile(metadata['tzdata_file']): - print("Downloading tz file from iana") - request.urlretrieve(os.path.join(metadata['releases_url'], - metadata['tzdata_file']), - metadata['tzdata_file']) - with open(metadata['tzdata_file'], 'rb') as tzfile: - sha_hasher = hashlib.sha512() - sha_hasher.update(tzfile.read()) - sha_512_file = sha_hasher.hexdigest() - assert metadata['tzdata_file_sha512'] == sha_512_file, "SHA failed for" + if len(sys.argv) == 2: + tzdata = sys.argv[1] + else: + from ftplib import FTP + print("Connecting to %s..." % SERVER) + ftp = FTP(SERVER) + print("Logging in...") + ftp.login() + print("Changing to %s..." % DIR) + ftp.cwd(DIR) + print("Listing files...") + for name in ftp.nlst(): + if NAME.match(name): + break + else: + sys.exit("error: file matching %s not found" % NAME.pattern) + if os.path.isfile(name): + print("Found local %s..." % name) + else: + print("Retrieving %s..." % name) + file = open(name, "w") + ftp.retrbinary("RETR "+name, file.write) + file.close() + ftp.close() + tzdata = name + if not tzdata or not NAME.match(tzdata): + sys.exit("Usage: updatezinfo.py tzdataXXXXX.tar.gz") print("Updating timezone information...") - rebuild.rebuild(metadata['tzdata_file'], zonegroups=metadata['zonegroups'], - metadata=metadata) + rebuild(tzdata, NAME.match(tzdata).group(1)) print("Done.") if __name__ == "__main__": diff --git a/zonefile_metadata.json b/zonefile_metadata.json deleted file mode 100644 index cde6c88..0000000 --- a/zonefile_metadata.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "metadata_version" : 1.0, - "releases_url" : "ftp://ftp.iana.org/tz/releases/", - "tzversion": "2015d", - "tzdata_file" : "tzdata2015d.tar.gz", - "tzdata_file_sha512" : "37b5aa3c5e0d601c8b20fac08d7267c398a836e4190ef85625d5e86a806ba1baceb2315ba81a9a6c854eae4fce40e9c8f90cf5adade3f48ad443f77c221d8983", - "zonegroups" : [ - "africa", - "antarctica", - "asia", - "australasia", - "europe", - "northamerica", - "southamerica", - "pacificnew", - "etcetera", - "systemv", - "factory", - "backzone", - "backward"] -} -