diff --git a/doc/users/whats_new.rst b/doc/users/whats_new.rst index addb2524cae4..759bf5029bac 100644 --- a/doc/users/whats_new.rst +++ b/doc/users/whats_new.rst @@ -127,6 +127,16 @@ local intensity of the vector field. .. plot:: mpl_examples/pylab_examples/streamplot_demo.py +Updated shipped dependencies +---------------------------- + +The following dependencies that ship with matplotlib and are +optionally installed alongside it have been updated: + + - `pytz ` 2012d + + - `dateutil ` 1.5 on Python 2.x, + and 2.1 on Python 3.x .. _whats-new-1-1: diff --git a/lib/dateutil/LICENSE b/lib/dateutil_py2/LICENSE similarity index 100% rename from lib/dateutil/LICENSE rename to lib/dateutil_py2/LICENSE diff --git a/lib/dateutil/NEWS b/lib/dateutil_py2/NEWS similarity index 100% rename from lib/dateutil/NEWS rename to lib/dateutil_py2/NEWS diff --git a/lib/dateutil/README b/lib/dateutil_py2/README similarity index 100% rename from lib/dateutil/README rename to lib/dateutil_py2/README diff --git a/lib/dateutil/__init__.py b/lib/dateutil_py2/__init__.py similarity index 100% rename from lib/dateutil/__init__.py rename to lib/dateutil_py2/__init__.py diff --git a/lib/dateutil/easter.py b/lib/dateutil_py2/easter.py similarity index 100% rename from lib/dateutil/easter.py rename to lib/dateutil_py2/easter.py diff --git a/lib/dateutil/parser.py b/lib/dateutil_py2/parser.py similarity index 100% rename from lib/dateutil/parser.py rename to lib/dateutil_py2/parser.py diff --git a/lib/dateutil/relativedelta.py b/lib/dateutil_py2/relativedelta.py similarity index 100% rename from lib/dateutil/relativedelta.py rename to lib/dateutil_py2/relativedelta.py diff --git a/lib/dateutil/rrule.py b/lib/dateutil_py2/rrule.py similarity index 100% rename from lib/dateutil/rrule.py rename to lib/dateutil_py2/rrule.py diff --git a/lib/dateutil/tz.py b/lib/dateutil_py2/tz.py similarity index 100% rename from lib/dateutil/tz.py rename to lib/dateutil_py2/tz.py diff --git a/lib/dateutil/tzwin.py b/lib/dateutil_py2/tzwin.py similarity index 100% rename from lib/dateutil/tzwin.py rename to lib/dateutil_py2/tzwin.py diff --git a/lib/dateutil/zoneinfo/__init__.py b/lib/dateutil_py2/zoneinfo/__init__.py similarity index 100% rename from lib/dateutil/zoneinfo/__init__.py rename to lib/dateutil_py2/zoneinfo/__init__.py diff --git a/lib/dateutil/zoneinfo/zoneinfo-2010g.tar.gz b/lib/dateutil_py2/zoneinfo/zoneinfo-2010g.tar.gz similarity index 100% rename from lib/dateutil/zoneinfo/zoneinfo-2010g.tar.gz rename to lib/dateutil_py2/zoneinfo/zoneinfo-2010g.tar.gz diff --git a/lib/dateutil_py3/LICENSE b/lib/dateutil_py3/LICENSE new file mode 100644 index 000000000000..5834335bd9da --- /dev/null +++ b/lib/dateutil_py3/LICENSE @@ -0,0 +1,30 @@ +dateutil - Extensions to the standard Python datetime module. + +Copyright (c) 2003-2011 - Gustavo Niemeyer +Copyright (c) 2012 - Tomi Pieviläinen + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/lib/dateutil_py3/NEWS b/lib/dateutil_py3/NEWS new file mode 100644 index 000000000000..3a0a8ed12aae --- /dev/null +++ b/lib/dateutil_py3/NEWS @@ -0,0 +1,164 @@ +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/lib/dateutil_py3/README b/lib/dateutil_py3/README new file mode 100644 index 000000000000..9453699e7d54 --- /dev/null +++ b/lib/dateutil_py3/README @@ -0,0 +1,1970 @@ +## 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 GustavoNiemeyer . + +== 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. + + 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') +}}} + +## vim:ft=moin diff --git a/lib/dateutil_py3/__init__.py b/lib/dateutil_py3/__init__.py new file mode 100644 index 000000000000..0f91a31f6eb2 --- /dev/null +++ b/lib/dateutil_py3/__init__.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +""" +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.1" diff --git a/lib/dateutil_py3/easter.py b/lib/dateutil_py3/easter.py new file mode 100644 index 000000000000..d8a38844f9e3 --- /dev/null +++ b/lib/dateutil_py3/easter.py @@ -0,0 +1,91 @@ +""" +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_ORTHODOX = 2 +EASTER_WESTERN = 3 + +def easter(year, method=EASTER_WESTERN): + """ + This method was ported from the work done by GM Arts, + on top of the algorithm by 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 + 2 - Original method, with date converted to Gregorian + calendar, valid in years 1583 to 4099 + 3 - 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. + + More about the algorithm may be found at: + + http://users.chariot.net.au/~gmarts/eastalg.htm + + and + + http://www.tondering.dk/claus/calendar.html + + """ + + if not (1 <= method <= 3): + raise ValueError("invalid method") + + # g - Golden year - 1 + # c - Century + # h - (23 - Epact) mod 30 + # i - Number of days from March 21 to Paschal Full Moon + # j - Weekday for PFM (0=Sunday, etc) + # p - Number of days from March 21 to Sunday on or before PFM + # (-6 to 28 methods 1 & 3, to 56 for method 2) + # e - Extra days to add for method 2 (converting Julian + # date to Gregorian date) + + y = year + g = y % 19 + e = 0 + if method < 3: + # Old method + 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 + 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 + + # 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 + return datetime.date(int(y), int(m), int(d)) + diff --git a/lib/dateutil_py3/parser.py b/lib/dateutil_py3/parser.py new file mode 100644 index 000000000000..a2604a35ba06 --- /dev/null +++ b/lib/dateutil_py3/parser.py @@ -0,0 +1,909 @@ +# -*- coding:iso-8859-1 -*- +""" +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 + +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): + + def __init__(self, instream): + if isinstance(instream, text_type): + instream = StringIO(instream) + self.instream = instream + self.wordchars = ('abcdfeghijklmnopqrstuvwxyz' + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ_' + 'ßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿ' + 'ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞ') + self.numchars = '0123456789' + self.whitespace = ' \t\r\n' + self.charstack = [] + self.tokenstack = [] + self.eof = False + + def get_token(self): + 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: + 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: + token = nextchar + if nextchar in wordchars: + state = 'a' + elif nextchar in numchars: + state = '0' + elif nextchar in whitespace: + token = ' ' + break # emit token + else: + break # emit token + elif state == 'a': + seenletters = True + if nextchar in wordchars: + token += nextchar + elif nextchar == '.': + token += nextchar + state = 'a.' + else: + self.charstack.append(nextchar) + break # emit token + elif state == '0': + if nextchar in numchars: + token += nextchar + elif nextchar == '.': + token += nextchar + state = '0.' + else: + self.charstack.append(nextchar) + break # emit token + elif state == 'a.': + seenletters = True + if nextchar == '.' or nextchar in wordchars: + token += nextchar + elif nextchar in numchars and token[-1] == '.': + token += nextchar + state = '0.' + else: + self.charstack.append(nextchar) + break # emit token + elif state == '0.': + if nextchar == '.' or nextchar in numchars: + token += nextchar + elif nextchar in wordchars and token[-1] == '.': + token += nextchar + state = 'a.' + else: + self.charstack.append(nextchar) + 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) + return token + + def __iter__(self): + return self + + def __next__(self): + token = self.get_token() + if token is None: + raise StopIteration + return token + + def next(self): + return self.__next__() # Python 2.x support + + def split(cls, s): + return list(cls(s)) + split = classmethod(split) + + +class _resultbase(object): + + def __init__(self): + for attr in self.__slots__: + setattr(self, attr, None) + + def _repr(self, classname): + l = [] + for attr in self.__slots__: + value = getattr(self, attr) + if value is not None: + l.append("%s=%s" % (attr, repr(value))) + return "%s(%s)" % (classname, ", ".join(l)) + + def __repr__(self): + return self._repr(self.__class__.__name__) + + +class parserinfo(object): + + # m from a.m/p.m, t from ISO T separator + JUMP = [" ", ".", ",", ";", "-", "/", "'", + "at", "on", "and", "ad", "m", "t", "of", + "st", "nd", "rd", "th"] + + WEEKDAYS = [("Mon", "Monday"), + ("Tue", "Tuesday"), + ("Wed", "Wednesday"), + ("Thu", "Thursday"), + ("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")] + HMS = [("h", "hour", "hours"), + ("m", "minute", "minutes"), + ("s", "second", "seconds")] + AMPM = [("am", "a"), + ("pm", "p")] + UTCZONE = ["UTC", "GMT", "Z"] + PERTAIN = ["of"] + TZOFFSET = {} + + 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) + self._hms = self._convert(self.HMS) + self._ampm = self._convert(self.AMPM) + self._utczone = self._convert(self.UTCZONE) + self._pertain = self._convert(self.PERTAIN) + + self.dayfirst = dayfirst + self.yearfirst = yearfirst + + self._year = time.localtime().tm_year + self._century = self._year//100*100 + + def _convert(self, lst): + dct = {} + for i in range(len(lst)): + v = lst[i] + if isinstance(v, tuple): + for v in v: + dct[v.lower()] = i + else: + dct[v.lower()] = i + return dct + + def jump(self, name): + return name.lower() in self._jump + + def weekday(self, name): + if len(name) >= 3: + try: + return self._weekdays[name.lower()] + except KeyError: + pass + return None + + def month(self, name): + if len(name) >= 3: + try: + return self._months[name.lower()]+1 + except KeyError: + pass + return None + + def hms(self, name): + try: + return self._hms[name.lower()] + except KeyError: + return None + + def ampm(self, name): + try: + return self._ampm[name.lower()] + except KeyError: + return None + + def pertain(self, name): + return name.lower() in self._pertain + + def utczone(self, name): + return name.lower() in self._utczone + + 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 year < self._year: + year += 100 + else: + year -= 100 + return year + + 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 + elif res.tzoffset != 0 and res.tzname and self.utczone(res.tzname): + res.tzoffset = 0 + return True + + +class parser(object): + + def __init__(self, info=None): + self.info = info or parserinfo() + + 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) + res = self._parse(timestr, **kwargs) + if res is None: + raise ValueError("unknown string format") + repl = {} + for attr in ["year", "month", "day", "hour", + "minute", "second", "microsecond"]: + value = getattr(res, attr) + if value is not None: + repl[attr] = value + 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): + tzdata = tzinfos(res.tzname, res.tzoffset) + else: + tzdata = tzinfos.get(res.tzname) + if isinstance(tzdata, datetime.tzinfo): + tzinfo = tzdata + elif isinstance(tzdata, text_type): + tzinfo = tz.tzstr(tzdata) + elif isinstance(tzdata, integer_types): + tzinfo = tz.tzoffset(res.tzname, tzdata) + else: + 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()) + elif res.tzoffset == 0: + ret = ret.replace(tzinfo=tz.tzutc()) + elif res.tzoffset: + ret = ret.replace(tzinfo=tz.tzoffset(res.tzname, res.tzoffset)) + return ret + + class _result(_resultbase): + __slots__ = ["year", "month", "day", "weekday", + "hour", "minute", "second", "microsecond", + "tzname", "tzoffset"] + + def _parse(self, timestr, dayfirst=None, yearfirst=None, fuzzy=False): + info = self.info + if dayfirst is None: + dayfirst = info.dayfirst + if yearfirst is None: + yearfirst = info.yearfirst + res = self._result() + l = _timelex.split(timestr) + try: + + # year/month/day list + ymd = [] + + # Index of the month string in ymd + mstridx = -1 + + len_l = len(l) + i = 0 + while i < len_l: + + # Check if it's a number + try: + value_repr = l[i] + value = float(value_repr) + except ValueError: + value = None + + if value is not None: + # Token is a number + len_li = len(l[i]) + i += 1 + if (len(ymd) == 3 and len_li in (2, 4) + 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])) + ymd.append(int(s[4:])) + else: + # 19990101T235959[.59] + 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] + ymd.append(int(s[:4])) + ymd.append(int(s[4:6])) + 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)) + elif idx == 1: + res.minute = int(value) + 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] + value = float(value_repr) + except ValueError: + break + 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: + # 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)) + elif idx == 2: + res.second, res.microsecond = \ + _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)) + 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] + ymd.append(int(l[i])) + 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 + 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 + else: + i += 1 + continue + + # Check weekday + value = info.weekday(l[i]) + if value is not None: + res.weekday = value + i += 1 + continue + + # Check month name + value = info.month(l[i]) + if value is not None: + ymd.append(value) + assert mstridx == -1 + mstridx = len(ymd)-1 + i += 1 + if i < len_l: + if l[i] in ('-', '/'): + # Jan-01[-99] + sep = l[i] + 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 + # In this case, 01 is clearly year + try: + value = int(l[i+3]) + except ValueError: + # Wrong guess + pass + else: + # Convert it here to become unambiguous + ymd.append(info.convertyear(value)) + i += 4 + continue + + # Check am/pm + value = info.ampm(l[i]) + if value is not None: + 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 = l[i] + res.tzoffset = info.tzoffset(res.tzname) + i += 1 + + # Check for something like GMT+3, or BRST+3. Notice + # that it doesn't mean "I am 3 hours after GMT", but + # "my time +3 is GMT". If found, we reverse the + # logic so that timezone parsing code will get it + # right. + if i < len_l and l[i] in ('+', '-'): + l[i] = ('+', '-')[l[i] == '+'] + res.tzoffset = None + if info.utczone(res.tzname): + # With something like GMT+3, the timezone + # is *not* GMT. + res.tzname = None + + continue + + # Check for a numbered timezone + if res.hour is not None and l[i] in ('+', '-'): + 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 + elif i+1 < len_l and l[i+1] == ':': + # -03:00 + res.tzoffset = int(l[i])*3600+int(l[i+2])*60 + i += 2 + elif len_li <= 2: + # -[0]3 + res.tzoffset = int(l[i][:2])*3600 + else: + return None + i += 1 + res.tzoffset *= signal + + # Look for a timezone name between parenthesis + if (i+3 < len_l and + 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]): + # -0300 (BRST) + res.tzname = l[i+2] + i += 4 + continue + + # Check jumps + if not (info.jump(l[i]) or fuzzy): + return None + + i += 1 + + # Process year/month/day + len_ymd = len(ymd) + if len_ymd > 3: + # More than three members!? + 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: + # 99-01 + res.year, res.month = ymd + elif ymd[1] > 31: + # 01-99 + res.month, res.year = ymd + elif dayfirst and ymd[1] <= 12: + # 13-01 + res.day, res.month = ymd + else: + # 01-13 + res.month, res.day = ymd + if len_ymd == 3: + # Three members + if mstridx == 0: + res.month, res.day, res.year = ymd + elif mstridx == 1: + if ymd[0] > 31 or (yearfirst and ymd[2] <= 31): + # 99-Jan-01 + res.year, res.month, res.day = ymd + else: + # 01-Jan-01 + # 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: + # 01-99-Jan + res.day, res.year, res.month = ymd + 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): + # 99-01-01 + res.year, res.month, res.day = ymd + elif ymd[0] > 12 or (dayfirst and ymd[1] <= 12): + # 13-01-01 + res.day, res.month, res.year = ymd + else: + # 01-13-01 + res.month, res.day, res.year = ymd + + except (IndexError, ValueError, AssertionError): + return None + + if not info.validate(res): + return None + return res + +DEFAULTPARSER = parser() +def parse(timestr, parserinfo=None, **kwargs): + # 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: + return DEFAULTPARSER.parse(timestr, **kwargs) + + +class _tzparser(object): + + class _result(_resultbase): + + __slots__ = ["stdabbr", "stdoffset", "dstabbr", "dstoffset", + "start", "end"] + + class _attr(_resultbase): + __slots__ = ["month", "week", "weekday", + "yday", "jyday", "day", "time"] + + def __repr__(self): + return self._repr("") + + def __init__(self): + _resultbase.__init__(self) + self.start = self._attr() + self.end = self._attr() + + def parse(self, tzstr): + res = self._result() + l = _timelex.split(tzstr) + try: + + len_l = len(l) + + i = 0 + while i < len_l: + # BRST+3[BRDT[+2]] + j = i + while j < len_l and not [x for x in l[j] + if x in "0123456789:,-+"]: + j += 1 + if j != i: + if not res.stdabbr: + offattr = "stdoffset" + res.stdabbr = "".join(l[i:j]) + else: + 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 l[i] in ('+', '-'): + # Yes, that's right. See the TZ variable + # documentation. + signal = (1, -1)[l[i] == '+'] + i += 1 + else: + signal = -1 + len_li = len(l[i]) + if len_li == 4: + # -0300 + 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, + (int(l[i])*3600+int(l[i+2])*60)*signal) + i += 2 + elif len_li <= 2: + # -[0]3 + setattr(res, offattr, + int(l[i][:2])*3600*signal) + else: + return None + i += 1 + if res.dstabbr: + break + else: + break + + if i < len_l: + for j in range(i, len_l): + if l[j] == ';': l[j] = ',' + + assert l[i] == ',' + + i += 1 + + if i >= len_l: + pass + elif (8 <= l.count(',') <= 9 and + 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): + x.month = int(l[i]) + i += 2 + if l[i] == '-': + value = int(l[i+1])*-1 + i += 1 + else: + value = int(l[i]) + i += 2 + if value: + x.week = value + x.weekday = (int(l[i])-1)%7 + else: + x.day = int(l[i]) + i += 2 + x.time = int(l[i]) + i += 2 + if i < len_l: + if l[i] in ('-', '+'): + signal = (-1, 1)[l[i] == "+"] + i += 1 + else: + signal = 1 + res.dstoffset = (res.stdoffset+int(l[i]))*signal + 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 x in (res.start, res.end): + if l[i] == 'J': + # non-leap year day (1 based) + i += 1 + x.jyday = int(l[i]) + elif l[i] == 'M': + # month[-.]week[-.]weekday + i += 1 + x.month = int(l[i]) + i += 1 + assert l[i] in ('-', '.') + i += 1 + x.week = int(l[i]) + if x.week == 5: + x.week = -1 + i += 1 + assert l[i] in ('-', '.') + i += 1 + x.weekday = (int(l[i])-1)%7 + else: + # year day (zero based) + x.yday = int(l[i])+1 + + i += 1 + + if i < len_l and l[i] == '/': + i += 1 + # start time + len_li = len(l[i]) + if len_li == 4: + # -0300 + x.time = (int(l[i][:2])*3600+int(l[i][2:])*60) + elif i+1 < len_l and l[i+1] == ':': + # -03:00 + x.time = int(l[i])*3600+int(l[i+2])*60 + i += 2 + if i+1 < len_l and l[i+1] == ':': + i += 2 + x.time += int(l[i]) + elif len_li <= 2: + # -[0]3 + x.time = (int(l[i][:2])*3600) + else: + return None + i += 1 + + assert i == len_l or l[i] == ',' + + i += 1 + + assert i >= len_l + + except (IndexError, ValueError, AssertionError): + return None + + return res + + +DEFAULTTZPARSER = _tzparser() +def _parsetz(tzstr): + return DEFAULTTZPARSER.parse(tzstr) + + +def _parsems(value): + """Parse a I[.F] seconds value into (seconds, microseconds).""" + if "." not in value: + return int(value), 0 + else: + i, f = value.split(".") + return int(i), int(f.ljust(6, "0")[:6]) + + +# vim:ts=4:sw=4:et diff --git a/lib/dateutil_py3/relativedelta.py b/lib/dateutil_py3/relativedelta.py new file mode 100644 index 000000000000..4393bcbcde22 --- /dev/null +++ b/lib/dateutil_py3/relativedelta.py @@ -0,0 +1,436 @@ +""" +Copyright (c) 2003-2010 Gustavo Niemeyer + +This module offers extensions to the standard Python +datetime module. +""" +__license__ = "Simplified BSD" + +import datetime +import calendar + +from six import integer_types + +__all__ = ["relativedelta", "MO", "TU", "WE", "TH", "FR", "SA", "SU"] + +class weekday(object): + __slots__ = ["weekday", "n"] + + def __init__(self, weekday, n=None): + self.weekday = weekday + self.n = n + + def __call__(self, n): + if n == self.n: + return self + else: + return self.__class__(self.weekday, n) + + def __eq__(self, other): + try: + if self.weekday != other.weekday or self.n != other.n: + return False + except AttributeError: + return False + return True + + def __repr__(self): + s = ("MO", "TU", "WE", "TH", "FR", "SA", "SU")[self.weekday] + if not self.n: + return s + else: + return "%s(%+d)" % (s, self.n) + +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 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's two different ways to build a relativedelta instance. The +first one is passing it two date/datetime classes: + + relativedelta(datetime1, datetime2) + +And the other way is to use 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 Nth weekday, which could + be positive or negative (like MO(+1) or MO(-2). Not specifying + it is the same as specifying +1. You can also use an integer, + where 0=MO. + + leapdays: + Will add given days to the date found, if year is a leap + year, and the date found 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. + +Here is the behavior of operations with relativedelta: + +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. + +3) Do steps 1 and 2 for month/months. + +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 + 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, + microsecond/microseconds. + +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 + the calculated date is already Monday, for example, using + (0, 1) or (0, -1) won't change the day. + """ + + def __init__(self, dt1=None, dt2=None, + years=0, months=0, days=0, leapdays=0, weeks=0, + hours=0, minutes=0, seconds=0, microseconds=0, + year=None, month=None, day=None, weekday=None, + yearday=None, nlyearday=None, + hour=None, minute=None, second=None, microsecond=None): + if dt1 and dt2: + if (not isinstance(dt1, datetime.date)) or (not isinstance(dt2, datetime.date)): + raise TypeError("relativedelta only diffs datetime/date") + 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): + dt2 = datetime.datetime.fromordinal(dt2.toordinal()) + self.years = 0 + self.months = 0 + self.days = 0 + self.leapdays = 0 + self.hours = 0 + self.minutes = 0 + self.seconds = 0 + self.microseconds = 0 + self.year = None + self.month = None + self.day = None + self.weekday = None + self.hour = None + self.minute = None + self.second = None + self.microsecond = None + self._has_time = 0 + + months = (dt1.year*12+dt1.month)-(dt2.year*12+dt2.month) + self._set_months(months) + dtm = self.__radd__(dt2) + if dt1 < dt2: + while dt1 > dtm: + months += 1 + self._set_months(months) + dtm = self.__radd__(dt2) + else: + while dt1 < dtm: + months -= 1 + self._set_months(months) + dtm = self.__radd__(dt2) + delta = dt1 - dtm + self.seconds = delta.seconds+delta.days*86400 + self.microseconds = delta.microseconds + else: + self.years = years + self.months = months + self.days = days+weeks*7 + self.leapdays = leapdays + self.hours = hours + self.minutes = minutes + self.seconds = seconds + self.microseconds = microseconds + self.year = year + self.month = month + self.day = day + self.hour = hour + self.minute = minute + self.second = second + self.microsecond = microsecond + + if isinstance(weekday, integer_types): + self.weekday = weekdays[weekday] + else: + self.weekday = weekday + + yday = 0 + if nlyearday: + yday = nlyearday + elif yearday: + yday = yearday + if yearday > 59: + self.leapdays = -1 + if yday: + 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 + if idx == 0: + self.day = yday + else: + self.day = yday-ydayidx[idx-1] + break + else: + raise ValueError("invalid year day (%d)" % yday) + + self._fix() + + def _fix(self): + if abs(self.microseconds) > 999999: + s = self.microseconds//abs(self.microseconds) + div, mod = divmod(self.microseconds*s, 1000000) + self.microseconds = mod*s + self.seconds += div*s + if abs(self.seconds) > 59: + s = self.seconds//abs(self.seconds) + div, mod = divmod(self.seconds*s, 60) + self.seconds = mod*s + self.minutes += div*s + if abs(self.minutes) > 59: + s = self.minutes//abs(self.minutes) + div, mod = divmod(self.minutes*s, 60) + self.minutes = mod*s + self.hours += div*s + if abs(self.hours) > 23: + s = self.hours//abs(self.hours) + div, mod = divmod(self.hours*s, 24) + self.hours = mod*s + self.days += div*s + if abs(self.months) > 11: + s = self.months//abs(self.months) + 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): + self._has_time = 1 + else: + self._has_time = 0 + + def _set_months(self, months): + self.months = months + if abs(self.months) > 11: + s = self.months//abs(self.months) + div, mod = divmod(self.months*s, 12) + self.months = mod*s + self.years = div*s + else: + self.years = 0 + + def __add__(self, other): + if isinstance(other, relativedelta): + 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): + other = datetime.datetime.fromordinal(other.toordinal()) + year = (self.year or other.year)+self.years + month = self.month or other.month + if self.months: + assert 1 <= abs(self.months) <= 12 + month += self.months + if month > 12: + year += 1 + month -= 12 + elif month < 1: + year -= 1 + month += 12 + day = min(calendar.monthrange(year, month)[1], + self.day or other.day) + repl = {"year": year, "month": month, "day": day} + for attr in ["hour", "minute", "second", "microsecond"]: + value = getattr(self, attr) + if value is not None: + repl[attr] = value + days = self.days + if self.leapdays and month > 2 and calendar.isleap(year): + days += self.leapdays + ret = (other.replace(**repl) + + datetime.timedelta(days=days, + hours=self.hours, + minutes=self.minutes, + seconds=self.seconds, + microseconds=self.microseconds)) + if self.weekday: + weekday, nth = self.weekday.weekday, self.weekday.n or 1 + jumpdays = (abs(nth)-1)*7 + if nth > 0: + jumpdays += (7-ret.weekday()+weekday)%7 + else: + jumpdays += (ret.weekday()-weekday)%7 + jumpdays *= -1 + ret += datetime.timedelta(days=jumpdays) + return ret + + def __radd__(self, other): + return self.__add__(other) + + def __rsub__(self, other): + return self.__neg__().__radd__(other) + + def __sub__(self, other): + if not isinstance(other, relativedelta): + raise TypeError("unsupported type for sub operation") + return relativedelta(years=self.years-other.years, + months=self.months-other.months, + days=self.days-other.days, + hours=self.hours-other.hours, + minutes=self.minutes-other.minutes, + seconds=self.seconds-other.seconds, + microseconds=self.microseconds-other.microseconds, + leapdays=self.leapdays or other.leapdays, + year=self.year or other.year, + month=self.month or other.month, + day=self.day or other.day, + weekday=self.weekday or other.weekday, + hour=self.hour or other.hour, + minute=self.minute or other.minute, + second=self.second or other.second, + microsecond=self.microsecond or other.microsecond) + + def __neg__(self): + return relativedelta(years=-self.years, + months=-self.months, + days=-self.days, + hours=-self.hours, + minutes=-self.minutes, + seconds=-self.seconds, + microseconds=-self.microseconds, + leapdays=self.leapdays, + year=self.year, + month=self.month, + day=self.day, + weekday=self.weekday, + hour=self.hour, + minute=self.minute, + second=self.second, + microsecond=self.microsecond) + + def __bool__(self): + return not (not self.years and + not self.months and + not self.days and + not self.hours and + not self.minutes and + not self.seconds and + not self.microseconds and + not self.leapdays and + self.year is None and + self.month is None and + self.day is None and + self.weekday is None and + self.hour is None and + self.minute is None and + self.second is None and + self.microsecond is None) + + def __mul__(self, other): + f = float(other) + return relativedelta(years=int(self.years*f), + months=int(self.months*f), + days=int(self.days*f), + hours=int(self.hours*f), + minutes=int(self.minutes*f), + seconds=int(self.seconds*f), + microseconds=int(self.microseconds*f), + leapdays=self.leapdays, + year=self.year, + month=self.month, + day=self.day, + weekday=self.weekday, + hour=self.hour, + minute=self.minute, + second=self.second, + microsecond=self.microsecond) + + __rmul__ = __mul__ + + def __eq__(self, other): + if not isinstance(other, relativedelta): + return False + if self.weekday or other.weekday: + if not self.weekday or not other.weekday: + return False + if self.weekday.weekday != other.weekday.weekday: + return False + n1, n2 = self.weekday.n, other.weekday.n + if n1 != n2 and not ((not n1 or n1 == 1) and (not n2 or n2 == 1)): + return False + return (self.years == other.years and + self.months == other.months and + self.days == other.days and + self.hours == other.hours and + self.minutes == other.minutes and + self.seconds == other.seconds and + self.leapdays == other.leapdays and + self.year == other.year and + self.month == other.month and + self.day == other.day and + self.hour == other.hour and + self.minute == other.minute and + self.second == other.second and + self.microsecond == other.microsecond) + + def __ne__(self, other): + return not self.__eq__(other) + + def __div__(self, other): + return self.__mul__(1/float(other)) + + __truediv__ = __div__ + + def __repr__(self): + l = [] + for attr in ["years", "months", "days", "leapdays", + "hours", "minutes", "seconds", "microseconds"]: + value = getattr(self, attr) + if value: + l.append("%s=%+d" % (attr, value)) + for attr in ["year", "month", "day", "weekday", + "hour", "minute", "second", "microsecond"]: + value = getattr(self, attr) + if value is not None: + l.append("%s=%s" % (attr, repr(value))) + return "%s(%s)" % (self.__class__.__name__, ", ".join(l)) + +# vim:ts=4:sw=4:et diff --git a/lib/dateutil_py3/rrule.py b/lib/dateutil_py3/rrule.py new file mode 100644 index 000000000000..ad4d3ba70c4e --- /dev/null +++ b/lib/dateutil_py3/rrule.py @@ -0,0 +1,1112 @@ +""" +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 six import advance_iterator, integer_types + +__all__ = ["rrule", "rruleset", "rrulestr", + "YEARLY", "MONTHLY", "WEEKLY", "DAILY", + "HOURLY", "MINUTELY", "SECONDLY", + "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+ + [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)) +MDAY366MASK = tuple(M31+M29+M31+M30+M31+M30+M31+M31+M30+M31+M30+M31+M31[:7]) +MDAY365MASK = list(MDAY366MASK) +M29, M30, M31 = list(range(-29, 0)), list(range(-30, 0)), list(range(-31, 0)) +NMDAY366MASK = tuple(M31+M29+M31+M30+M31+M30+M31+M31+M30+M31+M30+M31+M31[:7]) +NMDAY365MASK = list(NMDAY366MASK) +M366RANGE = (0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366) +M365RANGE = (0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365) +WDAYMASK = [0, 1, 2, 3, 4, 5, 6]*55 +del M29, M30, M31, M365MASK[59], MDAY365MASK[59], NMDAY365MASK[31] +MDAY365MASK = tuple(MDAY365MASK) +M365MASK = tuple(M365MASK) + +(YEARLY, + MONTHLY, + WEEKLY, + DAILY, + HOURLY, + MINUTELY, + SECONDLY) = list(range(7)) + +# Imported on demand. +easter = None +parser = None + +class weekday(object): + __slots__ = ["weekday", "n"] + + def __init__(self, weekday, n=None): + if n == 0: + raise ValueError("Can't create weekday with n == 0") + self.weekday = weekday + self.n = n + + def __call__(self, n): + if n == self.n: + return self + else: + return self.__class__(self.weekday, n) + + def __eq__(self, other): + try: + if self.weekday != other.weekday or self.n != other.n: + return False + except AttributeError: + return False + return True + + def __repr__(self): + s = ("MO", "TU", "WE", "TH", "FR", "SA", "SU")[self.weekday] + if not self.n: + return s + else: + return "%s(%+d)" % (s, self.n) + +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_complete = False + else: + self._cache = None + self._cache_complete = False + self._len = None + + def __iter__(self): + if self._cache_complete: + return iter(self._cache) + elif self._cache is None: + return self._iter() + else: + return self._iter_cached() + + def _iter_cached(self): + i = 0 + gen = self._cache_gen + cache = self._cache + acquire = self._cache_lock.acquire + release = self._cache_lock.release + while gen: + if i == len(cache): + acquire() + if self._cache_complete: + break + try: + for j in range(10): + cache.append(advance_iterator(gen)) + except StopIteration: + self._cache_gen = gen = None + self._cache_complete = True + break + release() + yield cache[i] + i += 1 + while i < self._len: + yield cache[i] + i += 1 + + def __getitem__(self, item): + if self._cache_complete: + return self._cache[item] + elif isinstance(item, slice): + if item.step and item.step < 0: + return list(iter(self))[item] + else: + return list(itertools.islice(self, + item.start or 0, + item.stop or sys.maxsize, + item.step or 1)) + elif item >= 0: + gen = iter(self) + try: + for i in range(item+1): + res = advance_iterator(gen) + except StopIteration: + raise IndexError + return res + else: + return list(iter(self))[item] + + def __contains__(self, item): + if self._cache_complete: + return item in self._cache + else: + for i in self: + if i == item: + return True + elif i > item: + return False + return False + + # __len__() introduces a large performance penality. + def count(self): + if self._len is None: + for x in self: pass + return self._len + + def before(self, dt, inc=False): + if self._cache_complete: + gen = self._cache + else: + gen = self + last = None + if inc: + for i in gen: + if i > dt: + break + last = i + else: + for i in gen: + if i >= dt: + break + last = i + return last + + def after(self, dt, inc=False): + if self._cache_complete: + gen = self._cache + else: + gen = self + if inc: + for i in gen: + if i >= dt: + return i + else: + for i in gen: + if i > dt: + return i + return None + + def between(self, after, before, inc=False): + if self._cache_complete: + gen = self._cache + else: + gen = self + started = False + l = [] + if inc: + for i in gen: + if i > before: + break + elif not started: + if i >= after: + started = True + l.append(i) + else: + l.append(i) + else: + for i in gen: + if i >= before: + break + elif not started: + if i > after: + started = True + l.append(i) + else: + l.append(i) + return l + +class rrule(rrulebase): + def __init__(self, freq, dtstart=None, + interval=1, wkst=None, count=None, until=None, bysetpos=None, + bymonth=None, bymonthday=None, byyearday=None, byeaster=None, + byweekno=None, byweekday=None, + byhour=None, byminute=None, bysecond=None, + cache=False): + super(rrule, self).__init__(cache) + global easter + if not dtstart: + dtstart = datetime.datetime.now().replace(microsecond=0) + elif not isinstance(dtstart, datetime.datetime): + dtstart = datetime.datetime.fromordinal(dtstart.toordinal()) + else: + dtstart = dtstart.replace(microsecond=0) + self._dtstart = dtstart + self._tzinfo = dtstart.tzinfo + self._freq = freq + self._interval = interval + self._count = count + 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): + if bysetpos == 0 or not (-366 <= bysetpos <= 366): + raise ValueError("bysetpos must be between 1 and 366, " + "or between -366 and -1") + self._bysetpos = (bysetpos,) + else: + self._bysetpos = tuple(bysetpos) + for pos in self._bysetpos: + if pos == 0 or not (-366 <= pos <= 366): + raise ValueError("bysetpos must be between 1 and 366, " + "or between -366 and -1") + if not (byweekno or byyearday or bymonthday or + byweekday is not None or byeaster is not None): + if freq == YEARLY: + if not bymonth: + bymonth = dtstart.month + bymonthday = dtstart.day + elif freq == MONTHLY: + bymonthday = dtstart.day + elif freq == WEEKLY: + byweekday = dtstart.weekday() + # bymonth + if not bymonth: + self._bymonth = None + elif isinstance(bymonth, integer_types): + self._bymonth = (bymonth,) + else: + self._bymonth = tuple(bymonth) + # byyearday + if not byyearday: + self._byyearday = None + elif isinstance(byyearday, integer_types): + self._byyearday = (byyearday,) + else: + self._byyearday = tuple(byyearday) + # byeaster + if byeaster is not None: + if not easter: + from dateutil import easter + if isinstance(byeaster, integer_types): + self._byeaster = (byeaster,) + else: + self._byeaster = tuple(byeaster) + else: + self._byeaster = 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: + 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: + 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: + self._byweekday = [] + self._bynweekday = [] + for wday in byweekday: + if isinstance(wday, integer_types): + self._byweekday.append(wday) + elif not wday.n or freq > MONTHLY: + self._byweekday.append(wday.weekday) + else: + 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 + # byhour + if byhour is None: + if freq < HOURLY: + self._byhour = (dtstart.hour,) + else: + self._byhour = None + elif isinstance(byhour, integer_types): + self._byhour = (byhour,) + else: + self._byhour = tuple(byhour) + # byminute + if byminute is None: + if freq < MINUTELY: + self._byminute = (dtstart.minute,) + else: + self._byminute = None + elif isinstance(byminute, integer_types): + self._byminute = (byminute,) + else: + self._byminute = tuple(byminute) + # bysecond + if bysecond is None: + if freq < SECONDLY: + self._bysecond = (dtstart.second,) + else: + self._bysecond = None + elif isinstance(bysecond, integer_types): + self._bysecond = (bysecond,) + else: + self._bysecond = tuple(bysecond) + + if self._freq >= HOURLY: + self._timeset = None + else: + self._timeset = [] + for hour in self._byhour: + for minute in self._byminute: + for second in self._bysecond: + self._timeset.append( + datetime.time(hour, minute, second, + tzinfo=self._tzinfo)) + self._timeset.sort() + self._timeset = tuple(self._timeset) + + def _iter(self): + year, month, day, hour, minute, second, weekday, yearday, _ = \ + self._dtstart.timetuple() + + # Some local variables to speed things up a bit + freq = self._freq + interval = self._interval + wkst = self._wkst + until = self._until + bymonth = self._bymonth + byweekno = self._byweekno + byyearday = self._byyearday + byweekday = self._byweekday + byeaster = self._byeaster + bymonthday = self._bymonthday + bynmonthday = self._bynmonthday + bysetpos = self._bysetpos + byhour = self._byhour + byminute = self._byminute + bysecond = self._bysecond + + 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] + + if freq < HOURLY: + timeset = self._timeset + else: + 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 + self._byminute and minute not in self._byminute) or + (freq >= SECONDLY and + self._bysecond and second not in self._bysecond)): + timeset = () + else: + timeset = gettimeset(hour, minute, second) + + total = 0 + count = self._count + while True: + # Get dayset with the right frequency + dayset, start, end = getdayset(year, month, day) + + # Do the "hard" work ;-) + filtered = False + for i in dayset[start:end]: + if ((bymonth and ii.mmask[i] not in bymonth) or + (byweekno and not ii.wnomask[i]) or + (byweekday and ii.wdaymask[i] not in byweekday) or + (ii.nwdaymask and not ii.nwdaymask[i]) or + (byeaster and not ii.eastermask[i]) or + ((bymonthday or bynmonthday) and + 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)))): + dayset[i] = None + filtered = True + + # Output results + if bysetpos and timeset: + poslist = [] + for pos in bysetpos: + if pos < 0: + daypos, timepos = divmod(pos, len(timeset)) + else: + daypos, timepos = divmod(pos-1, len(timeset)) + try: + i = [x for x in dayset[start:end] + if x is not None][daypos] + time = timeset[timepos] + except IndexError: + pass + else: + date = datetime.date.fromordinal(ii.yearordinal+i) + res = datetime.datetime.combine(date, time) + if res not in poslist: + poslist.append(res) + poslist.sort() + for res in poslist: + if until and res > until: + self._len = total + return + elif res >= self._dtstart: + total += 1 + yield res + if count: + count -= 1 + if not count: + self._len = total + return + else: + for i in dayset[start:end]: + if i is not None: + date = datetime.date.fromordinal(ii.yearordinal+i) + for time in timeset: + res = datetime.datetime.combine(date, time) + if until and res > until: + self._len = total + return + elif res >= self._dtstart: + total += 1 + yield res + if count: + count -= 1 + if not count: + self._len = total + return + + # Handle frequency and interval + fixday = False + if freq == YEARLY: + year += interval + if year > datetime.MAXYEAR: + self._len = total + return + ii.rebuild(year, month) + elif freq == MONTHLY: + month += interval + if month > 12: + div, mod = divmod(month, 12) + month = mod + year += div + if month == 0: + month = 12 + year -= 1 + if year > datetime.MAXYEAR: + self._len = total + return + ii.rebuild(year, month) + elif freq == WEEKLY: + if wkst > weekday: + day += -(weekday+1+(6-wkst))+self._interval*7 + else: + day += -(weekday-wkst)+self._interval*7 + weekday = wkst + fixday = True + elif freq == DAILY: + day += interval + fixday = True + elif freq == HOURLY: + if filtered: + # Jump to one iteration before next day + hour += ((23-hour)//interval)*interval + while True: + hour += interval + div, mod = divmod(hour, 24) + if div: + hour = mod + day += div + fixday = True + if not byhour or hour in byhour: + break + timeset = gettimeset(hour, minute, second) + elif freq == MINUTELY: + if filtered: + # Jump to one iteration before next day + minute += ((1439-(hour*60+minute))//interval)*interval + while True: + minute += interval + div, mod = divmod(minute, 60) + if div: + minute = mod + hour += div + 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)): + 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 + timeset = gettimeset(hour, minute, second) + + if fixday and day > 28: + daysinmonth = calendar.monthrange(year, month)[1] + if day > daysinmonth: + while day > daysinmonth: + day -= daysinmonth + month += 1 + if month == 13: + month = 1 + year += 1 + if year > datetime.MAXYEAR: + self._len = total + return + daysinmonth = calendar.monthrange(year, month)[1] + ii.rebuild(year, month) + +class _iterinfo(object): + __slots__ = ["rrule", "lastyear", "lastmonth", + "yearlen", "nextyearlen", "yearordinal", "yearweekday", + "mmask", "mrange", "mdaymask", "nmdaymask", + "wdaymask", "wnomask", "nwdaymask", "eastermask"] + + def __init__(self, rrule): + for attr in self.__slots__: + setattr(self, attr, None) + self.rrule = rrule + + 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) + firstyday = datetime.date(year, 1, 1) + self.yearordinal = firstyday.toordinal() + self.yearweekday = firstyday.weekday() + + wday = datetime.date(year, 1, 1).weekday() + if self.yearlen == 365: + self.mmask = M365MASK + self.mdaymask = MDAY365MASK + self.nmdaymask = NMDAY365MASK + self.wdaymask = WDAYMASK[wday:] + self.mrange = M365RANGE + else: + self.mmask = M366MASK + self.mdaymask = MDAY366MASK + self.nmdaymask = NMDAY366MASK + self.wdaymask = WDAYMASK[wday:] + self.mrange = M366RANGE + + if not rr._byweekno: + 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 + 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 + else: + # Number of days in the year, minus the days we + # left in last year. + wyearlen = self.yearlen-no1wkst + div, mod = divmod(wyearlen, 7) + numweeks = div+mod//4 + for n in rr._byweekno: + if n < 0: + n += numweeks+1 + if not (0 < n <= numweeks): + continue + if n > 1: + i = no1wkst+(n-1)*7 + if no1wkst != firstwkst: + i -= 7-firstwkst + else: + i = no1wkst + for j in range(7): + self.wnomask[i] = 1 + i += 1 + if self.wdaymask[i] == rr._wkst: + break + if 1 in rr._byweekno: + # Check week number 1 of next year as well + # TODO: Check -numweeks for next year. + i = no1wkst+numweeks*7 + if no1wkst != firstwkst: + i -= 7-firstwkst + if i < self.yearlen: + # If week starts in next year, we + # don't care about it. + for j in range(7): + self.wnomask[i] = 1 + i += 1 + if self.wdaymask[i] == rr._wkst: + break + if no1wkst: + # Check last week number of last year as + # well. If no1wkst is 0, either the year + # started on week start, or week number 1 + # got days from last year, so there are no + # days from last year's last week number in + # this year. + if -1 not in rr._byweekno: + lyearweekday = datetime.date(year-1, 1, 1).weekday() + 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 + else: + 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)): + ranges = [] + if rr._freq == YEARLY: + if rr._bymonth: + for month in rr._bymonth: + ranges.append(self.mrange[month-1:month+1]) + else: + ranges = [(0, self.yearlen)] + elif rr._freq == MONTHLY: + ranges = [self.mrange[month-1:month+1]] + if ranges: + # Weekly frequency won't get here, so we may not + # care about cross-year weekly periods. + self.nwdaymask = [0]*self.yearlen + for first, last in ranges: + last -= 1 + for wday, n in rr._bynweekday: + if n < 0: + i = last+(n+1)*7 + i -= (self.wdaymask[i]-wday)%7 + else: + i = first+(n-1)*7 + i += (7-self.wdaymask[i]+wday)%7 + if first <= i <= last: + self.nwdaymask[i] = 1 + + if rr._byeaster: + self.eastermask = [0]*(self.yearlen+7) + eyday = easter.easter(year).toordinal()-self.yearordinal + for offset in rr._byeaster: + self.eastermask[eyday+offset] = 1 + + self.lastyear = year + self.lastmonth = month + + def ydayset(self, year, month, day): + return list(range(self.yearlen)), 0, self.yearlen + + def mdayset(self, year, month, day): + set = [None]*self.yearlen + start, end = self.mrange[month-1:month+1] + for i in range(start, end): + set[i] = i + return set, start, end + + def wdayset(self, year, month, day): + # We need to handle cross-year weeks here. + set = [None]*(self.yearlen+7) + i = datetime.date(year, month, day).toordinal()-self.yearordinal + start = i + for j in range(7): + set[i] = i + i += 1 + #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 set, start, i + + def ddayset(self, year, month, day): + 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): + set = [] + rr = self.rrule + for minute in rr._byminute: + for second in rr._bysecond: + set.append(datetime.time(hour, minute, second, + tzinfo=rr._tzinfo)) + set.sort() + return set + + def mtimeset(self, hour, minute, second): + set = [] + rr = self.rrule + for second in rr._bysecond: + 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, + tzinfo=self.rrule._tzinfo),) + + +class rruleset(rrulebase): + + class _genitem(object): + def __init__(self, genlist, gen): + try: + self.dt = advance_iterator(gen) + genlist.append(self) + except StopIteration: + pass + self.genlist = genlist + self.gen = gen + + def __next__(self): + try: + self.dt = advance_iterator(self.gen) + except StopIteration: + self.genlist.remove(self) + + next = __next__ + + def __lt__(self, other): + return self.dt < other.dt + + def __gt__(self, other): + return self.dt > other.dt + + def __eq__(self, other): + return self.dt == other.dt + + def __ne__(self, other): + return self.dt != other.dt + + def __init__(self, cache=False): + super(rruleset, self).__init__(cache) + self._rrule = [] + self._rdate = [] + self._exrule = [] + self._exdate = [] + + def rrule(self, rrule): + self._rrule.append(rrule) + + def rdate(self, rdate): + self._rdate.append(rdate) + + def exrule(self, exrule): + self._exrule.append(exrule) + + def exdate(self, exdate): + self._exdate.append(exdate) + + def _iter(self): + rlist = [] + self._rdate.sort() + self._genitem(rlist, iter(self._rdate)) + for gen in [iter(x) for x in self._rrule]: + self._genitem(rlist, gen) + rlist.sort() + exlist = [] + self._exdate.sort() + self._genitem(exlist, iter(self._exdate)) + for gen in [iter(x) for x in self._exrule]: + self._genitem(exlist, gen) + exlist.sort() + lastdt = None + total = 0 + while rlist: + ritem = rlist[0] + if not lastdt or lastdt != ritem.dt: + while exlist and exlist[0] < ritem: + advance_iterator(exlist[0]) + exlist.sort() + if not exlist or ritem != exlist[0]: + total += 1 + yield ritem.dt + lastdt = ritem.dt + advance_iterator(ritem) + rlist.sort() + self._len = total + +class _rrulestr(object): + + _freq_map = {"YEARLY": YEARLY, + "MONTHLY": MONTHLY, + "WEEKLY": WEEKLY, + "DAILY": DAILY, + "HOURLY": HOURLY, + "MINUTELY": MINUTELY, + "SECONDLY": SECONDLY} + + _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) + + 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_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 + + def _handle_FREQ(self, rrkwargs, name, value, **kwargs): + rrkwargs["freq"] = self._freq_map[value] + + def _handle_UNTIL(self, rrkwargs, name, value, **kwargs): + global parser + if not parser: + from dateutil import parser + try: + rrkwargs["until"] = parser.parse(value, + 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, **kwarsg): + l = [] + for wday in value.split(','): + 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 + + _handle_BYDAY = _handle_BYWEEKDAY + + def _parse_rfc_rrule(self, line, + dtstart=None, + cache=False, + ignoretz=False, + tzinfos=None): + if line.find(':') != -1: + name, value = line.split(':') + if name != "RRULE": + raise ValueError("unknown parameter name") + else: + value = line + rrkwargs = {} + for pair in value.split(';'): + name, value = pair.split('=') + name = name.upper() + value = value.upper() + try: + getattr(self, "_handle_"+name)(rrkwargs, name, value, + ignoretz=ignoretz, + tzinfos=tzinfos) + except AttributeError: + raise ValueError("unknown parameter '%s'" % name) + except (KeyError, ValueError): + raise ValueError("invalid '%s': %s" % (name, value)) + return rrule(dtstart=dtstart, cache=cache, **rrkwargs) + + def _parse_rfc(self, s, + dtstart=None, + cache=False, + unfold=False, + forceset=False, + compatible=False, + ignoretz=False, + tzinfos=None): + global parser + if compatible: + forceset = True + unfold = True + s = s.upper() + if not s.strip(): + raise ValueError("empty string") + if unfold: + lines = s.splitlines() + i = 0 + while i < len(lines): + line = lines[i].rstrip() + if not line: + del lines[i] + elif i > 0 and line[0] == " ": + lines[i-1] += line[1:] + del lines[i] + else: + i += 1 + else: + lines = s.split() + 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) + else: + rrulevals = [] + rdatevals = [] + exrulevals = [] + exdatevals = [] + for line in lines: + if not line: + continue + if line.find(':') == -1: + name = "RRULE" + value = line + else: + name, value = line.split(':', 1) + parms = name.split(';') + if not parms: + raise ValueError("empty property name") + name = parms[0] + parms = parms[1:] + if name == "RRULE": + for parm in parms: + raise ValueError("unsupported RRULE parm: "+parm) + rrulevals.append(value) + elif name == "RDATE": + for parm in parms: + if parm != "VALUE=DATE-TIME": + raise ValueError("unsupported RDATE parm: "+parm) + rdatevals.append(value) + elif name == "EXRULE": + for parm in parms: + raise ValueError("unsupported EXRULE parm: "+parm) + exrulevals.append(value) + elif name == "EXDATE": + for parm in parms: + if parm != "VALUE=DATE-TIME": + raise ValueError("unsupported RDATE parm: "+parm) + exdatevals.append(value) + elif name == "DTSTART": + for parm in parms: + raise ValueError("unsupported DTSTART parm: "+parm) + if not parser: + from dateutil import parser + dtstart = parser.parse(value, ignoretz=ignoretz, + tzinfos=tzinfos) + else: + raise ValueError("unsupported property: "+name) + if (forceset or len(rrulevals) > 1 or + rdatevals or exrulevals or exdatevals): + if not parser and (rdatevals or exdatevals): + from dateutil import parser + set = rruleset(cache=cache) + for value in rrulevals: + set.rrule(self._parse_rfc_rrule(value, dtstart=dtstart, + ignoretz=ignoretz, + tzinfos=tzinfos)) + for value in rdatevals: + for datestr in value.split(','): + set.rdate(parser.parse(datestr, + ignoretz=ignoretz, + tzinfos=tzinfos)) + for value in exrulevals: + set.exrule(self._parse_rfc_rrule(value, dtstart=dtstart, + ignoretz=ignoretz, + tzinfos=tzinfos)) + for value in exdatevals: + for datestr in value.split(','): + set.exdate(parser.parse(datestr, + ignoretz=ignoretz, + tzinfos=tzinfos)) + if compatible and dtstart: + set.rdate(dtstart) + return set + else: + return self._parse_rfc_rrule(rrulevals[0], + dtstart=dtstart, + cache=cache, + ignoretz=ignoretz, + tzinfos=tzinfos) + + def __call__(self, s, **kwargs): + return self._parse_rfc(s, **kwargs) + +rrulestr = _rrulestr() + +# vim:ts=4:sw=4:et diff --git a/lib/dateutil_py3/six.py b/lib/dateutil_py3/six.py new file mode 100644 index 000000000000..6526d76cb148 --- /dev/null +++ b/lib/dateutil_py3/six.py @@ -0,0 +1,353 @@ +"""Utilities for writing code that runs on Python 2 and 3""" + +import operator +import sys +import types + +__author__ = "Benjamin Peterson " +__version__ = "1.1.0" + + +# True if we are running on Python 3. +PY3 = sys.version_info[0] == 3 + +if PY3: + string_types = str, + integer_types = int, + class_types = type, + text_type = str + binary_type = bytes + + MAXSIZE = sys.maxsize +else: + string_types = basestring, + integer_types = (int, long) + class_types = (type, types.ClassType) + text_type = unicode + binary_type = str + + # It's possible to have sizeof(long) != sizeof(Py_ssize_t). + class X(object): + def __len__(self): + return 1 << 31 + try: + len(X()) + except OverflowError: + # 32-bit + MAXSIZE = int((1 << 31) - 1) + else: + # 64-bit + MAXSIZE = int((1 << 63) - 1) + del X + + +def _add_doc(func, doc): + """Add documentation to a function.""" + func.__doc__ = doc + + +def _import_module(name): + """Import module, returning the module after the last dot.""" + __import__(name) + return sys.modules[name] + + +class _LazyDescr(object): + + def __init__(self, name): + self.name = name + + def __get__(self, obj, tp): + result = self._resolve() + setattr(obj, self.name, result) + # This is a bit ugly, but it avoids running this again. + delattr(tp, self.name) + return result + + +class MovedModule(_LazyDescr): + + def __init__(self, name, old, new=None): + super(MovedModule, self).__init__(name) + if PY3: + if new is None: + new = name + self.mod = new + else: + self.mod = old + + def _resolve(self): + return _import_module(self.mod) + + +class MovedAttribute(_LazyDescr): + + def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None): + super(MovedAttribute, self).__init__(name) + if PY3: + if new_mod is None: + new_mod = name + self.mod = new_mod + if new_attr is None: + if old_attr is None: + new_attr = name + else: + new_attr = old_attr + self.attr = new_attr + else: + self.mod = old_mod + if old_attr is None: + old_attr = name + self.attr = old_attr + + def _resolve(self): + module = _import_module(self.mod) + return getattr(module, self.attr) + + + +class _MovedItems(types.ModuleType): + """Lazy loading of moved objects""" + + +_moved_attributes = [ + MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"), + MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"), + MovedAttribute("map", "itertools", "builtins", "imap", "map"), + MovedAttribute("reload_module", "__builtin__", "imp", "reload"), + MovedAttribute("reduce", "__builtin__", "functools"), + MovedAttribute("StringIO", "StringIO", "io"), + MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"), + MovedAttribute("zip", "itertools", "builtins", "izip", "zip"), + + MovedModule("builtins", "__builtin__"), + MovedModule("configparser", "ConfigParser"), + MovedModule("copyreg", "copy_reg"), + MovedModule("http_cookiejar", "cookielib", "http.cookiejar"), + MovedModule("http_cookies", "Cookie", "http.cookies"), + MovedModule("html_entities", "htmlentitydefs", "html.entities"), + MovedModule("html_parser", "HTMLParser", "html.parser"), + MovedModule("http_client", "httplib", "http.client"), + MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"), + MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"), + MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"), + MovedModule("cPickle", "cPickle", "pickle"), + MovedModule("queue", "Queue"), + MovedModule("reprlib", "repr"), + MovedModule("socketserver", "SocketServer"), + MovedModule("tkinter", "Tkinter"), + MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"), + MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"), + MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"), + MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"), + MovedModule("tkinter_tix", "Tix", "tkinter.tix"), + MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"), + MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"), + MovedModule("tkinter_colorchooser", "tkColorChooser", + "tkinter.colorchooser"), + MovedModule("tkinter_commondialog", "tkCommonDialog", + "tkinter.commondialog"), + MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"), + MovedModule("tkinter_font", "tkFont", "tkinter.font"), + MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"), + MovedModule("tkinter_tksimpledialog", "tkSimpleDialog", + "tkinter.simpledialog"), + MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"), + MovedModule("winreg", "_winreg"), +] +for attr in _moved_attributes: + setattr(_MovedItems, attr.name, attr) +del attr + +moves = sys.modules["six.moves"] = _MovedItems("moves") + + +def add_move(move): + """Add an item to six.moves.""" + setattr(_MovedItems, move.name, move) + + +def remove_move(name): + """Remove item from six.moves.""" + try: + delattr(_MovedItems, name) + except AttributeError: + try: + del moves.__dict__[name] + except KeyError: + raise AttributeError("no such move, %r" % (name,)) + + +if PY3: + _meth_func = "__func__" + _meth_self = "__self__" + + _func_code = "__code__" + _func_defaults = "__defaults__" + + _iterkeys = "keys" + _itervalues = "values" + _iteritems = "items" +else: + _meth_func = "im_func" + _meth_self = "im_self" + + _func_code = "func_code" + _func_defaults = "func_defaults" + + _iterkeys = "iterkeys" + _itervalues = "itervalues" + _iteritems = "iteritems" + + +if PY3: + def get_unbound_function(unbound): + return unbound + + + advance_iterator = next + + def callable(obj): + return any("__call__" in klass.__dict__ for klass in type(obj).__mro__) +else: + def get_unbound_function(unbound): + return unbound.im_func + + + def advance_iterator(it): + return it.next() + + callable = callable +_add_doc(get_unbound_function, + """Get the function out of a possibly unbound function""") + + +get_method_function = operator.attrgetter(_meth_func) +get_method_self = operator.attrgetter(_meth_self) +get_function_code = operator.attrgetter(_func_code) +get_function_defaults = operator.attrgetter(_func_defaults) + + +def iterkeys(d): + """Return an iterator over the keys of a dictionary.""" + return getattr(d, _iterkeys)() + +def itervalues(d): + """Return an iterator over the values of a dictionary.""" + return getattr(d, _itervalues)() + +def iteritems(d): + """Return an iterator over the (key, value) pairs of a dictionary.""" + return getattr(d, _iteritems)() + + +if PY3: + def b(s): + return s.encode("latin-1") + def u(s): + return s + if sys.version_info[1] <= 1: + def int2byte(i): + return bytes((i,)) + else: + # This is about 2x faster than the implementation above on 3.2+ + int2byte = operator.methodcaller("to_bytes", 1, "big") + import io + StringIO = io.StringIO + BytesIO = io.BytesIO +else: + def b(s): + return s + def u(s): + return unicode(s, "unicode_escape") + int2byte = chr + import StringIO + StringIO = BytesIO = StringIO.StringIO +_add_doc(b, """Byte literal""") +_add_doc(u, """Text literal""") + + +if PY3: + import builtins + exec_ = getattr(builtins, "exec") + + + def reraise(tp, value, tb=None): + if value.__traceback__ is not tb: + raise value.with_traceback(tb) + raise value + + + print_ = getattr(builtins, "print") + del builtins + +else: + def exec_(code, globs=None, locs=None): + """Execute code in a namespace.""" + if globs is None: + frame = sys._getframe(1) + globs = frame.f_globals + if locs is None: + locs = frame.f_locals + del frame + elif locs is None: + locs = globs + exec("""exec code in globs, locs""") + + + exec_("""def reraise(tp, value, tb=None): + raise tp, value, tb +""") + + + def print_(*args, **kwargs): + """The new-style print function.""" + fp = kwargs.pop("file", sys.stdout) + if fp is None: + return + def write(data): + if not isinstance(data, basestring): + data = str(data) + fp.write(data) + want_unicode = False + sep = kwargs.pop("sep", None) + if sep is not None: + if isinstance(sep, unicode): + want_unicode = True + elif not isinstance(sep, str): + raise TypeError("sep must be None or a string") + end = kwargs.pop("end", None) + if end is not None: + if isinstance(end, unicode): + want_unicode = True + elif not isinstance(end, str): + raise TypeError("end must be None or a string") + if kwargs: + raise TypeError("invalid keyword arguments to print()") + if not want_unicode: + for arg in args: + if isinstance(arg, unicode): + want_unicode = True + break + if want_unicode: + newline = unicode("\n") + space = unicode(" ") + else: + newline = "\n" + space = " " + if sep is None: + sep = space + if end is None: + end = newline + for i, arg in enumerate(args): + if i: + write(sep) + write(arg) + write(end) + +_add_doc(reraise, """Reraise an exception.""") + + +def with_metaclass(meta, base=object): + """Create a base class with a metaclass.""" + return meta("NewBase", (base,), {}) diff --git a/lib/dateutil_py3/tz.py b/lib/dateutil_py3/tz.py new file mode 100644 index 000000000000..e849fc24b5e2 --- /dev/null +++ b/lib/dateutil_py3/tz.py @@ -0,0 +1,960 @@ +""" +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 + +relativedelta = None +parser = None +rrule = None + +__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(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 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 + + @tzname_in_python2 + def tzname(self, dt): + return "UTC" + + def __eq__(self, other): + return (isinstance(other, tzutc) or + (isinstance(other, tzoffset) and other._offset == ZERO)) + + def __ne__(self, other): + return not self.__eq__(other) + + def __repr__(self): + return "%s()" % self.__class__.__name__ + + __reduce__ = object.__reduce__ + +class tzoffset(datetime.tzinfo): + + def __init__(self, name, offset): + self._name = name + self._offset = datetime.timedelta(seconds=offset) + + def utcoffset(self, dt): + return self._offset + + def dst(self, dt): + return ZERO + + @tzname_in_python2 + def tzname(self, dt): + return self._name + + def __eq__(self, other): + return (isinstance(other, tzoffset) and + self._offset == other._offset) + + def __ne__(self, other): + return not self.__eq__(other) + + def __repr__(self): + return "%s(%s, %s)" % (self.__class__.__name__, + repr(self._name), + self._offset.days*86400+self._offset.seconds) + + __reduce__ = object.__reduce__ + +class tzlocal(datetime.tzinfo): + + _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): + return self._dst_offset + else: + return self._std_offset + + def dst(self, dt): + if self._isdst(dt): + return self._dst_offset-self._std_offset + else: + return ZERO + + @tzname_in_python2 + def tzname(self, dt): + return time.tzname[self._isdst(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' + # + # Here is a more stable implementation: + # + timestamp = ((dt.toordinal() - EPOCHORDINAL) * 86400 + + dt.hour * 3600 + + dt.minute * 60 + + dt.second) + return time.localtime(timestamp+time.timezone).tm_isdst + + def __eq__(self, other): + if not isinstance(other, tzlocal): + return False + return (self._std_offset == other._std_offset and + self._dst_offset == other._dst_offset) + return True + + def __ne__(self, other): + return not self.__eq__(other) + + def __repr__(self): + return "%s()" % self.__class__.__name__ + + __reduce__ = object.__reduce__ + +class _ttinfo(object): + __slots__ = ["offset", "delta", "isdst", "abbr", "isstd", "isgmt"] + + def __init__(self): + for attr in self.__slots__: + setattr(self, attr, None) + + def __repr__(self): + l = [] + for attr in self.__slots__: + value = getattr(self, attr) + if value is not None: + l.append("%s=%s" % (attr, repr(value))) + return "%s(%s)" % (self.__class__.__name__, ", ".join(l)) + + def __eq__(self, other): + if not isinstance(other, _ttinfo): + return False + return (self.offset == other.offset and + self.delta == other.delta and + self.isdst == other.isdst and + self.abbr == other.abbr and + self.isstd == other.isstd and + self.isgmt == other.isgmt) + + def __ne__(self, other): + return not self.__eq__(other) + + def __getstate__(self): + state = {} + for name in self.__slots__: + state[name] = getattr(self, name, None) + return state + + def __setstate__(self, state): + for name in self.__slots__: + 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): + if isinstance(fileobj, string_types): + self._filename = fileobj + fileobj = open(fileobj, 'rb') + elif hasattr(fileobj, "name"): + self._filename = fileobj.name + else: + self._filename = repr(fileobj) + + # From tzfile(5): + # + # The time zone information files used by tzset(3) + # begin with the magic characters "TZif" to identify + # them as time zone information files, followed by + # sixteen bytes reserved for future use, followed by + # six four-byte values of type long, written in a + # ``standard'' byte order (the high-order byte + # of the value is written first). + + if fileobj.read(4).decode() != "TZif": + raise ValueError("magic not found") + + fileobj.read(16) + + ( + # The number of UTC/local indicators stored in the file. + ttisgmtcnt, + + # 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 "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, + + ) = 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. + + 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] + # 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. + gmtoff = (gmtoff+30)//60*60 + tti = _ttinfo() + tti.offset = gmtoff + tti.delta = datetime.timedelta(seconds=gmtoff) + tti.isdst = isdst + tti.abbr = abbr[abbrind:abbr.find('\x00', abbrind)] + tti.isstd = (ttisstdcnt > i and isstd[i] != 0) + tti.isgmt = (ttisgmtcnt > i and isgmt[i] != 0) + self._ttinfo_list.append(tti) + + # Replace ttinfo indexes for ttinfo objects. + trans_idx = [] + for idx in self._trans_idx: + trans_idx.append(self._ttinfo_list[idx]) + self._trans_idx = tuple(trans_idx) + + # Set standard, dst, and before ttinfos. before will be + # used when a given time is before any transitions, + # and will be set to the first non-dst ttinfo, or to + # the first dst, if all of them are dst. + self._ttinfo_std = None + self._ttinfo_dst = None + self._ttinfo_before = None + if self._ttinfo_list: + if not self._trans_list: + self._ttinfo_std = self._ttinfo_first = self._ttinfo_list[0] + else: + for i in range(timecnt-1, -1, -1): + tti = self._trans_idx[i] + if not self._ttinfo_std and not tti.isdst: + self._ttinfo_std = tti + elif not self._ttinfo_dst and tti.isdst: + self._ttinfo_dst = tti + if self._ttinfo_std and self._ttinfo_dst: + break + else: + if self._ttinfo_dst and not self._ttinfo_std: + self._ttinfo_std = self._ttinfo_dst + + for tti in self._ttinfo_list: + if not tti.isdst: + self._ttinfo_before = tti + break + else: + self._ttinfo_before = self._ttinfo_list[0] + + # Now fix transition times to become relative to wall time. + # + # I'm not sure about this. In my tests, the tz source file + # is setup to wall time, and in the binary file isstd and + # isgmt are off, so it should be in wall time. OTOH, it's + # always in gmt time. Let me know if you have comments + # about this. + laststdoffset = 0 + self._trans_list = list(self._trans_list) + for i in range(len(self._trans_list)): + tti = self._trans_idx[i] + if not tti.isdst: + # This is std time. + self._trans_list[i] += tti.offset + laststdoffset = tti.offset + else: + # This is dst time. Convert to std. + self._trans_list[i] += laststdoffset + self._trans_list = tuple(self._trans_list) + + def _find_ttinfo(self, dt, laststd=0): + timestamp = ((dt.toordinal() - EPOCHORDINAL) * 86400 + + dt.hour * 3600 + + dt.minute * 60 + + dt.second) + idx = 0 + for trans in self._trans_list: + if timestamp < trans: + break + idx += 1 + else: + return self._ttinfo_std + if idx == 0: + return self._ttinfo_before + if laststd: + while idx > 0: + tti = self._trans_idx[idx-1] + if not tti.isdst: + return tti + idx -= 1 + else: + return self._ttinfo_std + else: + return self._trans_idx[idx-1] + + def utcoffset(self, dt): + if not self._ttinfo_std: + return ZERO + return self._find_ttinfo(dt).delta + + def dst(self, dt): + if not self._ttinfo_dst: + return ZERO + tti = self._find_ttinfo(dt) + if not tti.isdst: + return ZERO + + # The documentation says that utcoffset()-dst() must + # be constant for every dt. + return tti.delta-self._find_ttinfo(dt, laststd=1).delta + + # An alternative for that would be: + # + # return self._ttinfo_dst.offset-self._ttinfo_std.offset + # + # 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: + return None + return self._find_ttinfo(dt).abbr + + def __eq__(self, other): + if not isinstance(other, tzfile): + return False + return (self._trans_list == other._trans_list and + self._trans_idx == other._trans_idx and + self._ttinfo_list == other._ttinfo_list) + + def __ne__(self, other): + return not self.__eq__(other) + + + def __repr__(self): + return "%s(%s)" % (self.__class__.__name__, repr(self._filename)) + + def __reduce__(self): + if not os.path.isfile(self._filename): + 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): + global relativedelta + if not relativedelta: + from dateutil import relativedelta + self._std_abbr = stdabbr + self._dst_abbr = dstabbr + if stdoffset is not None: + self._std_offset = datetime.timedelta(seconds=stdoffset) + else: + self._std_offset = ZERO + if dstoffset is not None: + self._dst_offset = datetime.timedelta(seconds=dstoffset) + elif dstabbr and stdoffset is not None: + self._dst_offset = self._std_offset+datetime.timedelta(hours=+1) + else: + 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)) + 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)) + else: + self._end_delta = end + + def utcoffset(self, dt): + if self._isdst(dt): + return self._dst_offset + else: + return self._std_offset + + def dst(self, dt): + if self._isdst(dt): + return self._dst_offset-self._std_offset + else: + return ZERO + + @tzname_in_python2 + def tzname(self, dt): + if self._isdst(dt): + return self._dst_abbr + else: + return self._std_abbr + + def _isdst(self, dt): + if not self._start_delta: + return False + year = datetime.datetime(dt.year, 1, 1) + start = year+self._start_delta + end = year+self._end_delta + dt = dt.replace(tzinfo=None) + if start < end: + return dt >= start and dt < end + else: + return dt >= start or dt < end + + def __eq__(self, other): + if not isinstance(other, tzrange): + return False + return (self._std_abbr == other._std_abbr and + self._dst_abbr == other._dst_abbr and + self._std_offset == other._std_offset and + self._dst_offset == other._dst_offset and + self._start_delta == other._start_delta and + self._end_delta == other._end_delta) + + def __ne__(self, other): + return not self.__eq__(other) + + def __repr__(self): + return "%s(...)" % self.__class__.__name__ + + __reduce__ = object.__reduce__ + +class tzstr(tzrange): + + def __init__(self, s): + global parser + if not parser: + from dateutil import parser + self._s = s + + res = parser._parsetz(s) + if res is None: + raise ValueError("unknown string format") + + # Here we break the compatibility with the TZ variable handling. + # GMT-3 actually *means* the timezone -3. + if res.stdabbr in ("GMT", "UTC"): + res.stdoffset *= -1 + + # We must initialize it first, since _delta() needs + # _std_offset and _dst_offset set. Use False in start/end + # to avoid building it two times. + tzrange.__init__(self, res.stdabbr, res.stdoffset, + res.dstabbr, res.dstoffset, + start=False, end=False) + + if not res.dstabbr: + self._start_delta = None + self._end_delta = None + else: + self._start_delta = self._delta(res.start) + if self._start_delta: + self._end_delta = self._delta(res.end, isend=1) + + def _delta(self, x, isend=0): + kwargs = {} + if x.month is not None: + kwargs["month"] = x.month + if x.weekday is not None: + kwargs["weekday"] = relativedelta.weekday(x.weekday, x.week) + if x.week > 0: + kwargs["day"] = 1 + else: + kwargs["day"] = 31 + elif x.day: + kwargs["day"] = x.day + elif x.yday is not None: + kwargs["yearday"] = x.yday + elif x.jyday is not None: + kwargs["nlyearday"] = x.jyday + if not kwargs: + # Default is to start on first sunday of april, and end + # on last sunday of october. + if not isend: + kwargs["month"] = 4 + kwargs["day"] = 1 + kwargs["weekday"] = relativedelta.SU(+1) + else: + kwargs["month"] = 10 + kwargs["day"] = 31 + kwargs["weekday"] = relativedelta.SU(-1) + if x.time is not None: + kwargs["seconds"] = x.time + else: + # Default is 2AM. + kwargs["seconds"] = 7200 + if isend: + # Convert to standard time, to follow the documented way + # of working with the extra hour. See the documentation + # of the tzinfo class. + delta = self._dst_offset-self._std_offset + kwargs["seconds"] -= delta.seconds+delta.days*86400 + return relativedelta.relativedelta(**kwargs) + + 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): + self.tzoffsetfrom = datetime.timedelta(seconds=tzoffsetfrom) + self.tzoffsetto = datetime.timedelta(seconds=tzoffsetto) + self.tzoffsetdiff = self.tzoffsetto-self.tzoffsetfrom + self.isdst = isdst + self.tzname = tzname + self.rrule = rrule + +class _tzicalvtz(datetime.tzinfo): + def __init__(self, tzid, comps=[]): + self._tzid = tzid + self._comps = comps + self._cachedate = [] + self._cachecomp = [] + + def _find_comp(self, dt): + if len(self._comps) == 1: + return self._comps[0] + dt = dt.replace(tzinfo=None) + try: + return self._cachecomp[self._cachedate.index(dt)] + except ValueError: + pass + lastcomp = None + lastcompdt = None + for comp in self._comps: + if not comp.isdst: + # Handle the extra hour in DST -> STD + compdt = comp.rrule.before(dt-comp.tzoffsetdiff, inc=True) + else: + compdt = comp.rrule.before(dt, inc=True) + if compdt and (not lastcompdt or lastcompdt < compdt): + lastcompdt = compdt + lastcomp = comp + if not lastcomp: + # RFC says nothing about what to do when a given + # time is before the first onset date. We'll look for the + # first standard component, or the first component, if + # none is found. + for comp in self._comps: + if not comp.isdst: + lastcomp = comp + break + else: + lastcomp = comp[0] + self._cachedate.insert(0, dt) + self._cachecomp.insert(0, lastcomp) + if len(self._cachedate) > 10: + self._cachedate.pop() + self._cachecomp.pop() + return lastcomp + + def utcoffset(self, dt): + return self._find_comp(dt).tzoffsetto + + def dst(self, dt): + comp = self._find_comp(dt) + if comp.isdst: + return comp.tzoffsetdiff + else: + return ZERO + + @tzname_in_python2 + def tzname(self, dt): + return self._find_comp(dt).tzname + + def __repr__(self): + return "" % repr(self._tzid) + + __reduce__ = object.__reduce__ + +class tzical(object): + def __init__(self, fileobj): + global rrule + if not rrule: + from dateutil import rrule + + if isinstance(fileobj, string_types): + self._s = fileobj + fileobj = open(fileobj, 'r') # ical should be encoded in UTF-8 with CRLF + elif hasattr(fileobj, "name"): + self._s = fileobj.name + else: + self._s = repr(fileobj) + + self._vtz = {} + + self._parse_rfc(fileobj.read()) + + def keys(self): + return list(self._vtz.keys()) + + def get(self, tzid=None): + if tzid is None: + keys = list(self._vtz.keys()) + if len(keys) == 0: + raise ValueError("no timezones defined") + elif len(keys) > 1: + raise ValueError("more than one timezone available") + tzid = keys[0] + return self._vtz.get(tzid) + + def _parse_offset(self, s): + s = s.strip() + if not s: + raise ValueError("empty offset") + if s[0] in ('+', '-'): + signal = (-1, +1)[s[0]=='+'] + s = s[1:] + else: + signal = +1 + if len(s) == 4: + return (int(s[:2])*3600+int(s[2:])*60)*signal + elif len(s) == 6: + return (int(s[:2])*3600+int(s[2:4])*60+int(s[4:]))*signal + else: + raise ValueError("invalid offset: "+s) + + def _parse_rfc(self, s): + lines = s.splitlines() + if not lines: + raise ValueError("empty string") + + # Unfold + i = 0 + while i < len(lines): + line = lines[i].rstrip() + if not line: + del lines[i] + elif i > 0 and line[0] == " ": + lines[i-1] += line[1:] + del lines[i] + else: + i += 1 + + tzid = None + comps = [] + invtz = False + comptype = None + for line in lines: + if not line: + continue + name, value = line.split(':', 1) + parms = name.split(';') + if not parms: + raise ValueError("empty property name") + name = parms[0].upper() + parms = parms[1:] + if invtz: + if name == "BEGIN": + if value in ("STANDARD", "DAYLIGHT"): + # Process component + pass + else: + raise ValueError("unknown component: "+value) + comptype = value + founddtstart = False + tzoffsetfrom = None + tzoffsetto = None + rrulelines = [] + tzname = None + elif name == "END": + if value == "VTIMEZONE": + if comptype: + raise ValueError("component not closed: "+comptype) + if not tzid: + raise ValueError("mandatory TZID not found") + if not comps: + raise ValueError("at least one component is needed") + # Process vtimezone + self._vtz[tzid] = _tzicalvtz(tzid, comps) + invtz = False + elif value == comptype: + if not founddtstart: + raise ValueError("mandatory DTSTART not found") + if tzoffsetfrom is None: + raise ValueError("mandatory TZOFFSETFROM not found") + if tzoffsetto is None: + raise ValueError("mandatory TZOFFSETFROM not found") + # Process component + rr = None + if rrulelines: + rr = rrule.rrulestr("\n".join(rrulelines), + compatible=True, + ignoretz=True, + cache=True) + comp = _tzicalvtzcomp(tzoffsetfrom, tzoffsetto, + (comptype == "DAYLIGHT"), + tzname, rr) + comps.append(comp) + comptype = None + else: + raise ValueError("invalid component end: "+value) + elif comptype: + if name == "DTSTART": + rrulelines.append(line) + founddtstart = True + elif name in ("RRULE", "RDATE", "EXRULE", "EXDATE"): + rrulelines.append(line) + elif name == "TZOFFSETFROM": + if parms: + 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]) + tzoffsetto = self._parse_offset(value) + elif name == "TZNAME": + if parms: + raise ValueError("unsupported TZNAME parm: "+parms[0]) + tzname = value + elif name == "COMMENT": + pass + else: + raise ValueError("unsupported property: "+name) + else: + if name == "TZID": + if parms: + raise ValueError("unsupported TZID parm: "+parms[0]) + tzid = value + elif name in ("TZURL", "LAST-MODIFIED", "COMMENT"): + pass + else: + raise ValueError("unsupported property: "+name) + elif name == "BEGIN" and value == "VTIMEZONE": + tzid = None + comps = [] + invtz = True + + def __repr__(self): + return "%s(%s)" % (self.__class__.__name__, repr(self._s)) + +if sys.platform != "win32": + TZFILES = ["/etc/localtime", "localtime"] + TZPATHS = ["/usr/share/zoneinfo", "/usr/lib/zoneinfo", "/etc/zoneinfo"] +else: + TZFILES = [] + TZPATHS = [] + +def gettz(name=None): + tz = None + if not name: + try: + name = os.environ["TZ"] + except KeyError: + pass + if name is None or name == ":": + for filepath in TZFILES: + if not os.path.isabs(filepath): + filename = filepath + for path in TZPATHS: + filepath = os.path.join(path, filename) + if os.path.isfile(filepath): + break + else: + continue + if os.path.isfile(filepath): + try: + tz = tzfile(filepath) + break + except (IOError, OSError, ValueError): + pass + else: + tz = tzlocal() + else: + if name.startswith(":"): + name = name[:-1] + if os.path.isabs(name): + if os.path.isfile(name): + tz = tzfile(name) + else: + tz = None + else: + for path in TZPATHS: + filepath = os.path.join(path, name) + if not os.path.isfile(filepath): + filepath = filepath.replace(' ', '_') + if not os.path.isfile(filepath): + continue + try: + tz = tzfile(filepath) + break + except (IOError, OSError, ValueError): + pass + else: + tz = None + if tzwin: + try: + tz = tzwin(name) + except OSError: + pass + if not tz: + from dateutil.zoneinfo import gettz + tz = gettz(name) + if not tz: + for c in name: + # name must have at least one offset to be a tzstr + if c in "0123456789": + try: + tz = tzstr(name) + except ValueError: + pass + break + else: + if name in ("GMT", "UTC"): + tz = tzutc() + elif name in time.tzname: + tz = tzlocal() + return tz + +# vim:ts=4:sw=4:et diff --git a/lib/dateutil_py3/tzwin.py b/lib/dateutil_py3/tzwin.py new file mode 100644 index 000000000000..041c6cc3d645 --- /dev/null +++ b/lib/dateutil_py3/tzwin.py @@ -0,0 +1,179 @@ +# This code was originally contributed by Jeffrey Harris. +import datetime +import struct +import winreg + + +__all__ = ["tzwin", "tzwinlocal"] + +ONEWEEK = datetime.timedelta(7) + +TZKEYNAMENT = r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones" +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() + TZKEYNAME = TZKEYNAMENT + except WindowsError: + TZKEYNAME = TZKEYNAME9X + handle.Close() + +_settzkeyname() + +class tzwinbase(datetime.tzinfo): + """tzinfo class based on win32's timezones available in the registry.""" + + def utcoffset(self, dt): + if self._isdst(dt): + return datetime.timedelta(minutes=self._dstoffset) + else: + return datetime.timedelta(minutes=self._stdoffset) + + def dst(self, dt): + if self._isdst(dt): + minutes = self._dstoffset - self._stdoffset + return datetime.timedelta(minutes=minutes) + else: + return datetime.timedelta(0) + + def tzname(self, dt): + if self._isdst(dt): + return self._dstname + else: + return self._stdname + + def list(): + """Return a list of all time zones known to the system.""" + handle = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) + tzkey = winreg.OpenKey(handle, TZKEYNAME) + result = [winreg.EnumKey(tzkey, i) + for i in range(winreg.QueryInfoKey(tzkey)[0])] + tzkey.Close() + handle.Close() + return result + list = staticmethod(list) + + def display(self): + return self._display + + def _isdst(self, dt): + dston = picknthweekday(dt.year, self._dstmonth, self._dstdayofweek, + self._dsthour, self._dstminute, + self._dstweeknumber) + dstoff = picknthweekday(dt.year, self._stdmonth, self._stddayofweek, + self._stdhour, self._stdminute, + self._stdweeknumber) + if dston < dstoff: + return dston <= dt.replace(tzinfo=None) < dstoff + else: + return not dstoff <= dt.replace(tzinfo=None) < dston + + +class tzwin(tzwinbase): + + def __init__(self, name): + self._name = name + + 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"].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 + + (self._stdmonth, + 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._dsthour, + self._dstminute) = tup[12:17] + + def __repr__(self): + return "tzwin(%s)" % repr(self._name) + + def __reduce__(self): + return (self.__class__, (self._name,)) + + +class tzwinlocal(tzwinbase): + + def __init__(self): + + handle = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) + + tzlocalkey = winreg.OpenKey(handle, TZLOCALKEYNAME) + keydict = valuestodict(tzlocalkey) + tzlocalkey.Close() + + self._stdname = keydict["StandardName"].encode("iso-8859-1") + self._dstname = keydict["DaylightName"].encode("iso-8859-1") + + 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._stdhour, + self._stdminute) = tup[1:6] + + tup = struct.unpack("=8h", keydict["DaylightStart"]) + + (self._dstmonth, + 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)) + 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 = {} + size = winreg.QueryInfoKey(key)[1] + for i in range(size): + data = winreg.EnumValue(key, i) + dict[data[0]] = data[1] + return dict diff --git a/lib/dateutil_py3/zoneinfo/__init__.py b/lib/dateutil_py3/zoneinfo/__init__.py new file mode 100644 index 000000000000..a1b34874baa3 --- /dev/null +++ b/lib/dateutil_py3/zoneinfo/__init__.py @@ -0,0 +1,90 @@ +# -*- coding: utf-8 -*- +""" +Copyright (c) 2003-2005 Gustavo Niemeyer + +This module offers extensions to the standard Python +datetime module. +""" +from dateutil.tz import tzfile +from tarfile import TarFile +import os + +__author__ = "Tomi Pieviläinen " +__license__ = "Simplified BSD" + +__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 + +ZONEINFOFILE = getzoneinfofile() + +del getzoneinfofile + +def setcachesize(size): + global CACHESIZE, CACHE + CACHESIZE = size + del CACHE[size:] + +def gettz(name): + 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"): + 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) + os.system("zic -d %s %s" % (zonedir, filepath)) + 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/lib/dateutil_py3/zoneinfo/zoneinfo--latest.tar.gz b/lib/dateutil_py3/zoneinfo/zoneinfo--latest.tar.gz new file mode 100644 index 000000000000..12eadffb098a Binary files /dev/null and b/lib/dateutil_py3/zoneinfo/zoneinfo--latest.tar.gz differ diff --git a/lib/dateutil_py3/zoneinfo/zoneinfo-2011d.tar.gz b/lib/dateutil_py3/zoneinfo/zoneinfo-2011d.tar.gz new file mode 100644 index 000000000000..a8c96074d8a2 Binary files /dev/null and b/lib/dateutil_py3/zoneinfo/zoneinfo-2011d.tar.gz differ diff --git a/lib/pytz/README.txt b/lib/pytz/README.txt index 33342b54546c..3a174dda94a0 100644 --- a/lib/pytz/README.txt +++ b/lib/pytz/README.txt @@ -176,9 +176,8 @@ parameter to the ``utcoffset()``, ``dst()`` && ``tzname()`` methods. >>> normal = datetime(2009, 9, 1) >>> ambiguous = datetime(2009, 10, 31, 23, 30) -the ``is_dst`` parameter is ignormed for most timestamps, but -is used to resolve the ambiguity during ambiguous periods caused -to DST transitions. +The ``is_dst`` parameter is ignored for most timestamps. It is only used +during DST transition ambiguous periods to resulve that ambiguity. >>> tz.utcoffset(normal, is_dst=True) datetime.timedelta(-1, 77400) diff --git a/lib/pytz/__init__.py b/lib/pytz/__init__.py index c4a3462740ae..8887140f1e1b 100644 --- a/lib/pytz/__init__.py +++ b/lib/pytz/__init__.py @@ -9,7 +9,7 @@ ''' # The Olson database is updated several times a year. -OLSON_VERSION = '2011k' +OLSON_VERSION = '2012d' VERSION = OLSON_VERSION # Version format for a patch release - only one so far. #VERSION = OLSON_VERSION + '.2' @@ -199,12 +199,8 @@ def _unmunge_zone(zone): class UTC(datetime.tzinfo): """UTC - Identical to the reference UTC implementation given in Python docs except - that it unpickles using the single module global instance defined beneath - this class declaration. - - Also contains extra attributes and methods to match other pytz tzinfo - instances. + Optimized UTC implementation. It unpickles using the single module global + instance defined beneath this class declaration. """ zone = "UTC" @@ -212,6 +208,11 @@ class UTC(datetime.tzinfo): _dst = ZERO _tzname = zone + def fromutc(self, dt): + if dt.tzinfo is None: + return self.localize(dt) + return super(utc.__class__, self).fromutc(dt) + def utcoffset(self, dt): return ZERO @@ -232,9 +233,11 @@ def localize(self, dt, is_dst=False): def normalize(self, dt, is_dst=False): '''Correct the timezone information on the given datetime''' + if dt.tzinfo is self: + return dt if dt.tzinfo is None: raise ValueError('Naive time - no tzinfo set') - return dt.replace(tzinfo=self) + return dt.astimezone(self) def __repr__(self): return "" @@ -616,6 +619,7 @@ def _test(): 'America/Coral_Harbour', 'America/Cordoba', 'America/Costa_Rica', + 'America/Creston', 'America/Cuiaba', 'America/Curacao', 'America/Danmarkshavn', @@ -1187,6 +1191,7 @@ def _test(): 'America/Chicago', 'America/Chihuahua', 'America/Costa_Rica', + 'America/Creston', 'America/Cuiaba', 'America/Curacao', 'America/Danmarkshavn', diff --git a/lib/pytz/tests/test_tzinfo.py b/lib/pytz/tests/test_tzinfo.py index d8e5ddf33397..24abb91ee844 100644 --- a/lib/pytz/tests/test_tzinfo.py +++ b/lib/pytz/tests/test_tzinfo.py @@ -17,11 +17,11 @@ import pytz from pytz import reference from pytz.tzfile import _byte_string -from pytz.tzinfo import StaticTzInfo +from pytz.tzinfo import DstTzInfo, StaticTzInfo # I test for expected version to ensure the correct version of pytz is # actually being tested. -EXPECTED_VERSION='2011k' +EXPECTED_VERSION='2012d' fmt = '%Y-%m-%d %H:%M:%S %Z%z' @@ -533,6 +533,24 @@ class TahitiTestCase(USEasternDSTStartTestCase): } +class SamoaInternationalDateLineChange(USEasternDSTStartTestCase): + # At the end of 2011, Samoa will switch from being east of the + # international dateline to the west. There will be no Dec 30th + # 2011 and it will switch from UTC-10 to UTC+14. + tzinfo = pytz.timezone('Pacific/Apia') + transition_time = datetime(2011, 12, 30, 10, 0, 0, tzinfo=UTC) + before = { + 'tzname': 'WSDT', + 'utcoffset': timedelta(hours=-10), + 'dst': timedelta(hours=1), + } + after = { + 'tzname': 'WSDT', + 'utcoffset': timedelta(hours=14), + 'dst': timedelta(hours=1), + } + + class ReferenceUSEasternDSTStartTestCase(USEasternDSTStartTestCase): tzinfo = reference.Eastern def test_arithmetic(self): @@ -701,6 +719,85 @@ def test_belfast(self): self.assertFalse('Europe/Belfast' in pytz.common_timezones_set) +class BaseTzInfoTestCase: + '''Ensure UTC, StaticTzInfo and DstTzInfo work consistently. + + These tests are run for each type of tzinfo. + ''' + tz = None # override + tz_class = None # override + + def test_expectedclass(self): + self.assertTrue(isinstance(self.tz, self.tz_class)) + + def test_fromutc(self): + # naive datetime. + dt1 = datetime(2011, 10, 31) + + # localized datetime, same timezone. + dt2 = self.tz.localize(dt1) + + # Both should give the same results. Note that the standard + # Python tzinfo.fromutc() only supports the second. + for dt in [dt1, dt2]: + loc_dt = self.tz.fromutc(dt) + loc_dt2 = pytz.utc.localize(dt1).astimezone(self.tz) + self.assertEqual(loc_dt, loc_dt2) + + # localized datetime, different timezone. + new_tz = pytz.timezone('Europe/Paris') + self.assertTrue(self.tz is not new_tz) + dt3 = new_tz.localize(dt1) + self.assertRaises(ValueError, self.tz.fromutc, dt3) + + def test_normalize(self): + other_tz = pytz.timezone('Europe/Paris') + self.assertTrue(self.tz is not other_tz) + + dt = datetime(2012, 3, 26, 12, 0) + other_dt = other_tz.localize(dt) + + local_dt = self.tz.normalize(other_dt) + + self.assertTrue(local_dt.tzinfo is not other_dt.tzinfo) + self.assertNotEqual( + local_dt.replace(tzinfo=None), other_dt.replace(tzinfo=None)) + + def test_astimezone(self): + other_tz = pytz.timezone('Europe/Paris') + self.assertTrue(self.tz is not other_tz) + + dt = datetime(2012, 3, 26, 12, 0) + other_dt = other_tz.localize(dt) + + local_dt = other_dt.astimezone(self.tz) + + self.assertTrue(local_dt.tzinfo is not other_dt.tzinfo) + self.assertNotEqual( + local_dt.replace(tzinfo=None), other_dt.replace(tzinfo=None)) + + +class OptimizedUTCTestCase(unittest.TestCase, BaseTzInfoTestCase): + tz = pytz.utc + tz_class = tz.__class__ + + +class LegacyUTCTestCase(unittest.TestCase, BaseTzInfoTestCase): + # Deprecated timezone, but useful for comparison tests. + tz = pytz.timezone('Etc/UTC') + tz_class = StaticTzInfo + + +class StaticTzInfoTestCase(unittest.TestCase, BaseTzInfoTestCase): + tz = pytz.timezone('GMT') + tz_class = StaticTzInfo + + +class DstTzInfoTestCase(unittest.TestCase, BaseTzInfoTestCase): + tz = pytz.timezone('Australia/Melbourne') + tz_class = DstTzInfo + + def test_suite(): suite = unittest.TestSuite() suite.addTest(doctest.DocTestSuite('pytz')) diff --git a/lib/pytz/tzfile.py b/lib/pytz/tzfile.py index 05944daf22e0..9c007c80995a 100644 --- a/lib/pytz/tzfile.py +++ b/lib/pytz/tzfile.py @@ -97,7 +97,9 @@ def build_tzinfo(zone, fp): break dst = inf[0] - prev_inf[0] # dst offset - if dst <= 0: # Bad dst? Look further. + # Bad dst? Look further. DST > 24 hours happens when + # a timzone has moved across the international dateline. + if dst <= 0 or dst > 3600*3: for j in range(i+1, len(transitions)): stdinf = ttinfo[lindexes[j]] if not stdinf[1]: diff --git a/lib/pytz/tzinfo.py b/lib/pytz/tzinfo.py index 1bb57c250039..a1e43cdf0c01 100644 --- a/lib/pytz/tzinfo.py +++ b/lib/pytz/tzinfo.py @@ -74,6 +74,8 @@ class StaticTzInfo(BaseTzInfo): ''' def fromutc(self, dt): '''See datetime.tzinfo.fromutc''' + if dt.tzinfo is not None and dt.tzinfo is not self: + raise ValueError('fromutc: dt.tzinfo is not self') return (dt + self._utcoffset).replace(tzinfo=self) def utcoffset(self, dt, is_dst=None): @@ -176,6 +178,9 @@ def __init__(self, _inf=None, _tzinfos=None): def fromutc(self, dt): '''See datetime.tzinfo.fromutc''' + if (dt.tzinfo is not None + and getattr(dt.tzinfo, '_tzinfos', None) is not self._tzinfos): + raise ValueError('fromutc: dt.tzinfo is not self') dt = dt.replace(tzinfo=None) idx = max(0, bisect_right(self._utc_transition_times, dt) - 1) inf = self._transition_info[idx] diff --git a/lib/pytz/zoneinfo/Africa/Casablanca b/lib/pytz/zoneinfo/Africa/Casablanca index 77b88cffa389..d3d6da917d16 100644 Binary files a/lib/pytz/zoneinfo/Africa/Casablanca and b/lib/pytz/zoneinfo/Africa/Casablanca differ diff --git a/lib/pytz/zoneinfo/America/Atikokan b/lib/pytz/zoneinfo/America/Atikokan index c63a32a2a171..1b49e37c943b 100644 Binary files a/lib/pytz/zoneinfo/America/Atikokan and b/lib/pytz/zoneinfo/America/Atikokan differ diff --git a/lib/pytz/zoneinfo/America/Bahia b/lib/pytz/zoneinfo/America/Bahia index 34b2d7a304b9..9735a14a6eab 100644 Binary files a/lib/pytz/zoneinfo/America/Bahia and b/lib/pytz/zoneinfo/America/Bahia differ diff --git a/lib/pytz/zoneinfo/America/Blanc-Sablon b/lib/pytz/zoneinfo/America/Blanc-Sablon index 8526259c7a5a..8a33789afcbb 100644 Binary files a/lib/pytz/zoneinfo/America/Blanc-Sablon and b/lib/pytz/zoneinfo/America/Blanc-Sablon differ diff --git a/lib/pytz/zoneinfo/America/Coral_Harbour b/lib/pytz/zoneinfo/America/Coral_Harbour index c63a32a2a171..1b49e37c943b 100644 Binary files a/lib/pytz/zoneinfo/America/Coral_Harbour and b/lib/pytz/zoneinfo/America/Coral_Harbour differ diff --git a/lib/pytz/zoneinfo/America/Creston b/lib/pytz/zoneinfo/America/Creston new file mode 100644 index 000000000000..1cf719ae8374 Binary files /dev/null and b/lib/pytz/zoneinfo/America/Creston differ diff --git a/lib/pytz/zoneinfo/America/Dawson_Creek b/lib/pytz/zoneinfo/America/Dawson_Creek index 01ed5354f840..c3fb166b0887 100644 Binary files a/lib/pytz/zoneinfo/America/Dawson_Creek and b/lib/pytz/zoneinfo/America/Dawson_Creek differ diff --git a/lib/pytz/zoneinfo/America/Edmonton b/lib/pytz/zoneinfo/America/Edmonton index ba6dbb46cfab..3fa0579891a9 100644 Binary files a/lib/pytz/zoneinfo/America/Edmonton and b/lib/pytz/zoneinfo/America/Edmonton differ diff --git a/lib/pytz/zoneinfo/America/Glace_Bay b/lib/pytz/zoneinfo/America/Glace_Bay index 199f0b030447..48412a4cbf92 100644 Binary files a/lib/pytz/zoneinfo/America/Glace_Bay and b/lib/pytz/zoneinfo/America/Glace_Bay differ diff --git a/lib/pytz/zoneinfo/America/Goose_Bay b/lib/pytz/zoneinfo/America/Goose_Bay index f2a981849d1d..83e5a9b398fd 100644 Binary files a/lib/pytz/zoneinfo/America/Goose_Bay and b/lib/pytz/zoneinfo/America/Goose_Bay differ diff --git a/lib/pytz/zoneinfo/America/Halifax b/lib/pytz/zoneinfo/America/Halifax index b98c3a77340f..756099abe6ce 100644 Binary files a/lib/pytz/zoneinfo/America/Halifax and b/lib/pytz/zoneinfo/America/Halifax differ diff --git a/lib/pytz/zoneinfo/America/Havana b/lib/pytz/zoneinfo/America/Havana index b519c0947008..96eaf81ee9e4 100644 Binary files a/lib/pytz/zoneinfo/America/Havana and b/lib/pytz/zoneinfo/America/Havana differ diff --git a/lib/pytz/zoneinfo/America/Moncton b/lib/pytz/zoneinfo/America/Moncton index 33125756544b..b51125ebf112 100644 Binary files a/lib/pytz/zoneinfo/America/Moncton and b/lib/pytz/zoneinfo/America/Moncton differ diff --git a/lib/pytz/zoneinfo/America/Montreal b/lib/pytz/zoneinfo/America/Montreal index 0ddffb2f299a..47633bd49f89 100644 Binary files a/lib/pytz/zoneinfo/America/Montreal and b/lib/pytz/zoneinfo/America/Montreal differ diff --git a/lib/pytz/zoneinfo/America/Nipigon b/lib/pytz/zoneinfo/America/Nipigon index fced633dcae5..619f1f75905e 100644 Binary files a/lib/pytz/zoneinfo/America/Nipigon and b/lib/pytz/zoneinfo/America/Nipigon differ diff --git a/lib/pytz/zoneinfo/America/Port-au-Prince b/lib/pytz/zoneinfo/America/Port-au-Prince index cd104790f9cd..189fd5252cf4 100644 Binary files a/lib/pytz/zoneinfo/America/Port-au-Prince and b/lib/pytz/zoneinfo/America/Port-au-Prince differ diff --git a/lib/pytz/zoneinfo/America/Rainy_River b/lib/pytz/zoneinfo/America/Rainy_River index a5288ed9e075..e006a30dbf35 100644 Binary files a/lib/pytz/zoneinfo/America/Rainy_River and b/lib/pytz/zoneinfo/America/Rainy_River differ diff --git a/lib/pytz/zoneinfo/America/Regina b/lib/pytz/zoneinfo/America/Regina index fa9c6eec2fcd..20c9c84df491 100644 Binary files a/lib/pytz/zoneinfo/America/Regina and b/lib/pytz/zoneinfo/America/Regina differ diff --git a/lib/pytz/zoneinfo/America/Santiago b/lib/pytz/zoneinfo/America/Santiago index a8cafe6a8899..de74ddf1a095 100644 Binary files a/lib/pytz/zoneinfo/America/Santiago and b/lib/pytz/zoneinfo/America/Santiago differ diff --git a/lib/pytz/zoneinfo/America/Sitka b/lib/pytz/zoneinfo/America/Sitka index d2f43a2fd50b..f2ae47a323e7 100644 Binary files a/lib/pytz/zoneinfo/America/Sitka and b/lib/pytz/zoneinfo/America/Sitka differ diff --git a/lib/pytz/zoneinfo/America/St_Johns b/lib/pytz/zoneinfo/America/St_Johns index 27416c379732..e7a18d601d02 100644 Binary files a/lib/pytz/zoneinfo/America/St_Johns and b/lib/pytz/zoneinfo/America/St_Johns differ diff --git a/lib/pytz/zoneinfo/America/Swift_Current b/lib/pytz/zoneinfo/America/Swift_Current index 8224028413ef..8e9ef255eeb1 100644 Binary files a/lib/pytz/zoneinfo/America/Swift_Current and b/lib/pytz/zoneinfo/America/Swift_Current differ diff --git a/lib/pytz/zoneinfo/America/Toronto b/lib/pytz/zoneinfo/America/Toronto index b126bc641119..1698477a4877 100644 Binary files a/lib/pytz/zoneinfo/America/Toronto and b/lib/pytz/zoneinfo/America/Toronto differ diff --git a/lib/pytz/zoneinfo/America/Vancouver b/lib/pytz/zoneinfo/America/Vancouver index b69358c4502e..0c1fa5269049 100644 Binary files a/lib/pytz/zoneinfo/America/Vancouver and b/lib/pytz/zoneinfo/America/Vancouver differ diff --git a/lib/pytz/zoneinfo/America/Winnipeg b/lib/pytz/zoneinfo/America/Winnipeg index 5f9e35c391b5..2d22791686e8 100644 Binary files a/lib/pytz/zoneinfo/America/Winnipeg and b/lib/pytz/zoneinfo/America/Winnipeg differ diff --git a/lib/pytz/zoneinfo/Antarctica/Casey b/lib/pytz/zoneinfo/Antarctica/Casey index 76d0794e9565..8ebf0598ff90 100644 Binary files a/lib/pytz/zoneinfo/Antarctica/Casey and b/lib/pytz/zoneinfo/Antarctica/Casey differ diff --git a/lib/pytz/zoneinfo/Antarctica/Davis b/lib/pytz/zoneinfo/Antarctica/Davis index d773c541275a..cd7acad690b6 100644 Binary files a/lib/pytz/zoneinfo/Antarctica/Davis and b/lib/pytz/zoneinfo/Antarctica/Davis differ diff --git a/lib/pytz/zoneinfo/Antarctica/Palmer b/lib/pytz/zoneinfo/Antarctica/Palmer index 614c9b97b40d..c51d91e64ccf 100644 Binary files a/lib/pytz/zoneinfo/Antarctica/Palmer and b/lib/pytz/zoneinfo/Antarctica/Palmer differ diff --git a/lib/pytz/zoneinfo/Asia/Damascus b/lib/pytz/zoneinfo/Asia/Damascus index d272a351040b..4b610b5a0836 100644 Binary files a/lib/pytz/zoneinfo/Asia/Damascus and b/lib/pytz/zoneinfo/Asia/Damascus differ diff --git a/lib/pytz/zoneinfo/Asia/Gaza b/lib/pytz/zoneinfo/Asia/Gaza index 063ed39e8d8c..6f1cabc29150 100644 Binary files a/lib/pytz/zoneinfo/Asia/Gaza and b/lib/pytz/zoneinfo/Asia/Gaza differ diff --git a/lib/pytz/zoneinfo/Asia/Hebron b/lib/pytz/zoneinfo/Asia/Hebron index 841779271fe0..a073e06ef72e 100644 Binary files a/lib/pytz/zoneinfo/Asia/Hebron and b/lib/pytz/zoneinfo/Asia/Hebron differ diff --git a/lib/pytz/zoneinfo/Asia/Yerevan b/lib/pytz/zoneinfo/Asia/Yerevan index 3b4fb193e331..c4ab2197f803 100644 Binary files a/lib/pytz/zoneinfo/Asia/Yerevan and b/lib/pytz/zoneinfo/Asia/Yerevan differ diff --git a/lib/pytz/zoneinfo/Atlantic/Stanley b/lib/pytz/zoneinfo/Atlantic/Stanley index d7da723f7ec8..34f9d022cd5a 100644 Binary files a/lib/pytz/zoneinfo/Atlantic/Stanley and b/lib/pytz/zoneinfo/Atlantic/Stanley differ diff --git a/lib/pytz/zoneinfo/Canada/Atlantic b/lib/pytz/zoneinfo/Canada/Atlantic index b98c3a77340f..756099abe6ce 100644 Binary files a/lib/pytz/zoneinfo/Canada/Atlantic and b/lib/pytz/zoneinfo/Canada/Atlantic differ diff --git a/lib/pytz/zoneinfo/Canada/Central b/lib/pytz/zoneinfo/Canada/Central index 5f9e35c391b5..2d22791686e8 100644 Binary files a/lib/pytz/zoneinfo/Canada/Central and b/lib/pytz/zoneinfo/Canada/Central differ diff --git a/lib/pytz/zoneinfo/Canada/East-Saskatchewan b/lib/pytz/zoneinfo/Canada/East-Saskatchewan index fa9c6eec2fcd..20c9c84df491 100644 Binary files a/lib/pytz/zoneinfo/Canada/East-Saskatchewan and b/lib/pytz/zoneinfo/Canada/East-Saskatchewan differ diff --git a/lib/pytz/zoneinfo/Canada/Eastern b/lib/pytz/zoneinfo/Canada/Eastern index b126bc641119..1698477a4877 100644 Binary files a/lib/pytz/zoneinfo/Canada/Eastern and b/lib/pytz/zoneinfo/Canada/Eastern differ diff --git a/lib/pytz/zoneinfo/Canada/Mountain b/lib/pytz/zoneinfo/Canada/Mountain index ba6dbb46cfab..3fa0579891a9 100644 Binary files a/lib/pytz/zoneinfo/Canada/Mountain and b/lib/pytz/zoneinfo/Canada/Mountain differ diff --git a/lib/pytz/zoneinfo/Canada/Newfoundland b/lib/pytz/zoneinfo/Canada/Newfoundland index 27416c379732..e7a18d601d02 100644 Binary files a/lib/pytz/zoneinfo/Canada/Newfoundland and b/lib/pytz/zoneinfo/Canada/Newfoundland differ diff --git a/lib/pytz/zoneinfo/Canada/Pacific b/lib/pytz/zoneinfo/Canada/Pacific index b69358c4502e..0c1fa5269049 100644 Binary files a/lib/pytz/zoneinfo/Canada/Pacific and b/lib/pytz/zoneinfo/Canada/Pacific differ diff --git a/lib/pytz/zoneinfo/Canada/Saskatchewan b/lib/pytz/zoneinfo/Canada/Saskatchewan index fa9c6eec2fcd..20c9c84df491 100644 Binary files a/lib/pytz/zoneinfo/Canada/Saskatchewan and b/lib/pytz/zoneinfo/Canada/Saskatchewan differ diff --git a/lib/pytz/zoneinfo/Chile/Continental b/lib/pytz/zoneinfo/Chile/Continental index a8cafe6a8899..de74ddf1a095 100644 Binary files a/lib/pytz/zoneinfo/Chile/Continental and b/lib/pytz/zoneinfo/Chile/Continental differ diff --git a/lib/pytz/zoneinfo/Chile/EasterIsland b/lib/pytz/zoneinfo/Chile/EasterIsland index 1f7bae5373b9..de6b8ff5839f 100644 Binary files a/lib/pytz/zoneinfo/Chile/EasterIsland and b/lib/pytz/zoneinfo/Chile/EasterIsland differ diff --git a/lib/pytz/zoneinfo/Cuba b/lib/pytz/zoneinfo/Cuba index b519c0947008..96eaf81ee9e4 100644 Binary files a/lib/pytz/zoneinfo/Cuba and b/lib/pytz/zoneinfo/Cuba differ diff --git a/lib/pytz/zoneinfo/Europe/Kiev b/lib/pytz/zoneinfo/Europe/Kiev index 1255e67fb6d1..075cc02fd893 100644 Binary files a/lib/pytz/zoneinfo/Europe/Kiev and b/lib/pytz/zoneinfo/Europe/Kiev differ diff --git a/lib/pytz/zoneinfo/Europe/Simferopol b/lib/pytz/zoneinfo/Europe/Simferopol index 08efa510178f..ebb63b4450b0 100644 Binary files a/lib/pytz/zoneinfo/Europe/Simferopol and b/lib/pytz/zoneinfo/Europe/Simferopol differ diff --git a/lib/pytz/zoneinfo/Europe/Uzhgorod b/lib/pytz/zoneinfo/Europe/Uzhgorod index 00584c1fca22..7032ab9b34f9 100644 Binary files a/lib/pytz/zoneinfo/Europe/Uzhgorod and b/lib/pytz/zoneinfo/Europe/Uzhgorod differ diff --git a/lib/pytz/zoneinfo/Europe/Zaporozhye b/lib/pytz/zoneinfo/Europe/Zaporozhye index 899fa88b6311..2ccf8998b24f 100644 Binary files a/lib/pytz/zoneinfo/Europe/Zaporozhye and b/lib/pytz/zoneinfo/Europe/Zaporozhye differ diff --git a/lib/pytz/zoneinfo/Pacific/Easter b/lib/pytz/zoneinfo/Pacific/Easter index 1f7bae5373b9..de6b8ff5839f 100644 Binary files a/lib/pytz/zoneinfo/Pacific/Easter and b/lib/pytz/zoneinfo/Pacific/Easter differ diff --git a/lib/pytz/zoneinfo/Pacific/Fakaofo b/lib/pytz/zoneinfo/Pacific/Fakaofo index b3991ee56dee..2a4e7afa5ea5 100644 Binary files a/lib/pytz/zoneinfo/Pacific/Fakaofo and b/lib/pytz/zoneinfo/Pacific/Fakaofo differ diff --git a/lib/pytz/zoneinfo/Pacific/Fiji b/lib/pytz/zoneinfo/Pacific/Fiji index b2f6cac9c242..797842aa3e1e 100644 Binary files a/lib/pytz/zoneinfo/Pacific/Fiji and b/lib/pytz/zoneinfo/Pacific/Fiji differ diff --git a/lib/pytz/zoneinfo/iso3166.tab b/lib/pytz/zoneinfo/iso3166.tab index 950c40f1db66..b952ca1c5900 100644 --- a/lib/pytz/zoneinfo/iso3166.tab +++ b/lib/pytz/zoneinfo/iso3166.tab @@ -1,5 +1,4 @@ #
-# @(#)iso3166.tab	8.11
 # This file is in the public domain, so clarified as of
 # 2009-05-17 by Arthur David Olson.
 # ISO 3166 alpha-2 country codes
diff --git a/lib/pytz/zoneinfo/zone.tab b/lib/pytz/zoneinfo/zone.tab
index f197b948dd97..6bda8266ba97 100644
--- a/lib/pytz/zoneinfo/zone.tab
+++ b/lib/pytz/zoneinfo/zone.tab
@@ -1,5 +1,4 @@
 # 
-# @(#)zone.tab	8.49
 # This file is in the public domain, so clarified as of
 # 2009-05-17 by Arthur David Olson.
 #
@@ -131,6 +130,7 @@ CA	+5333-11328	America/Edmonton	Mountain Time - Alberta, east British Columbia &
 CA	+690650-1050310	America/Cambridge_Bay	Mountain Time - west Nunavut
 CA	+6227-11421	America/Yellowknife	Mountain Time - central Northwest Territories
 CA	+682059-1334300	America/Inuvik	Mountain Time - west Northwest Territories
+CA	+4906-11631	America/Creston	Mountain Standard Time - Creston, British Columbia
 CA	+5946-12014	America/Dawson_Creek	Mountain Standard Time - Dawson Creek & Fort Saint John, British Columbia
 CA	+4916-12307	America/Vancouver	Pacific Time - west British Columbia
 CA	+6043-13503	America/Whitehorse	Pacific Time - south Yukon
@@ -333,7 +333,7 @@ RS	+4450+02030	Europe/Belgrade
 RU	+5443+02030	Europe/Kaliningrad	Moscow-01 - Kaliningrad
 RU	+5545+03735	Europe/Moscow	Moscow+00 - west Russia
 RU	+4844+04425	Europe/Volgograd	Moscow+00 - Caspian Sea
-RU	+5312+05009	Europe/Samara	Moscow - Samara, Udmurtia
+RU	+5312+05009	Europe/Samara	Moscow+00 - Samara, Udmurtia
 RU	+5651+06036	Asia/Yekaterinburg	Moscow+02 - Urals
 RU	+5500+07324	Asia/Omsk	Moscow+03 - west Siberia
 RU	+5502+08255	Asia/Novosibirsk	Moscow+03 - Novosibirsk
diff --git a/setup.py b/setup.py
index 62a8888d4fab..6340514b5619 100644
--- a/setup.py
+++ b/setup.py
@@ -48,7 +48,7 @@
      check_for_qt, check_for_qt4, check_for_pyside, check_for_cairo, \
      check_provide_pytz, check_provide_dateutil,\
      check_for_dvipng, check_for_ghostscript, check_for_latex, \
-     check_for_pdftops, check_for_datetime, options, build_png, build_tri
+     check_for_pdftops, options, build_png, build_tri
 
 # jdh
 packages = [
@@ -108,6 +108,8 @@
                               'backends/Matplotlib.nib/*',
                               ]}
 
+package_dir = {'': 'lib'}
+
 if 1:
     # TODO: exclude these when making release?
     baseline_images = glob.glob(os.path.join('lib','matplotlib','tests',
@@ -187,46 +189,46 @@ def chop_package(fname):
 print_raw("")
 print_raw("OPTIONAL DATE/TIMEZONE DEPENDENCIES")
 
-hasdatetime = check_for_datetime()
-provide_dateutil = check_provide_dateutil(hasdatetime)
-provide_pytz = check_provide_pytz(hasdatetime)
-
-if hasdatetime: # dates require python23 datetime
-    # only install pytz and dateutil if the user hasn't got them
-
-    def add_pytz():
-        packages.append('pytz')
-
-        resources = ['zone.tab', 'locales/pytz.pot']
-        for dirpath, dirnames, filenames in os.walk(
-            os.path.join('lib', 'pytz', 'zoneinfo')
-            ):
-
-            # remove the 'pytz' part of the path
-            basepath = os.path.join(*dirpath.split(os.path.sep)[2:])
-            #print dirpath, basepath
-            resources.extend([os.path.join(basepath, filename)
-                              for filename in filenames])
-        package_data['pytz'] = resources
-        #print resources
-        assert len(resources) > 10, 'zoneinfo files not found!'
-
-
-    def add_dateutil():
-        packages.append('dateutil')
-        packages.append('dateutil.zoneinfo')
-        package_data['dateutil'] = ['zoneinfo/zoneinfo*.tar.*']
+provide_dateutil = check_provide_dateutil()
+provide_pytz = check_provide_pytz()
+
+def add_pytz():
+    packages.append('pytz')
+
+    resources = ['zone.tab', 'locales/pytz.pot']
+    for dirpath, dirnames, filenames in os.walk(
+        os.path.join('lib', 'pytz', 'zoneinfo')
+        ):
+
+        # remove the 'pytz' part of the path
+        basepath = os.path.join(*dirpath.split(os.path.sep)[2:])
+        #print dirpath, basepath
+        resources.extend([os.path.join(basepath, filename)
+                          for filename in filenames])
+    package_data['pytz'] = resources
+    #print resources
+    assert len(resources) > 10, 'zoneinfo files not found!'
+
+def add_dateutil():
+    packages.append('dateutil')
+    packages.append('dateutil.zoneinfo')
+    package_data['dateutil'] = ['zoneinfo/zoneinfo*.tar.*']
+    if sys.version_info[0] >= 3:
+        package_dir['dateutil'] = 'lib/dateutil_py3'
+    else:
+        package_dir['dateutil'] = 'lib/dateutil_py2'
 
-    if sys.platform=='win32':
-        # always add these to the win32 installer
+if sys.platform=='win32':
+    # always add these to the win32 installer
+    add_pytz()
+    add_dateutil()
+else:
+    # only add them if we need them
+    if provide_pytz:
         add_pytz()
+        print_raw("adding pytz")
+    if provide_dateutil:
         add_dateutil()
-    else:
-        # only add them if we need them
-        if provide_pytz:
-            add_pytz()
-            print_raw("adding pytz")
-        if provide_dateutil: add_dateutil()
 
 print_raw("")
 print_raw("OPTIONAL USETEX DEPENDENCIES")
@@ -259,8 +261,15 @@ def add_dateutil():
         mod.extra_compile_args.append('-DVERBOSE')
 
 if sys.version_info[0] >= 3:
+    def should_2to3(file, root):
+        file = os.path.abspath(file)[len(os.path.abspath(root)):]
+        if ('py3' in file or
+            file.startswith('pytz') or
+            file.startswith('dateutil')):
+            return False
+        return True
+
     import multiprocessing
-    from distutils import util
     def refactor(x):
         from lib2to3.refactor import RefactoringTool, get_fixers_from_package
         class DistutilsRefactoringTool(RefactoringTool):
@@ -276,7 +285,7 @@ class build_py(original_build_py):
         def run_2to3(self, files):
             # We need to skip certain files that have already been
             # converted to Python 3.x
-            filtered = [x for x in files if 'py3' not in x]
+            filtered = [x for x in files if should_2to3(x, self.build_lib)]
             if sys.platform.startswith('win'):
                 # doing this in parallel on windows may crash your computer
                 [refactor(f) for f in filtered]
@@ -306,7 +315,7 @@ def run_2to3(self, files):
       platforms='any',
       py_modules = py_modules,
       ext_modules = ext_modules,
-      package_dir = {'': 'lib'},
+      package_dir = package_dir,
       package_data = package_data,
       cmdclass = {'build_py': build_py},
       **additional_params
diff --git a/setupext.py b/setupext.py
index 7dc1e2a9285b..b5d8c0c7d200 100644
--- a/setupext.py
+++ b/setupext.py
@@ -431,24 +431,14 @@ def check_for_cairo():
         print_status("Cairo", cairo.version)
         return True
 
-def check_for_datetime():
-    try:
-        import datetime
-    except ImportError:
-        print_status("datetime", "no")
-        return False
-    else:
-        print_status("datetime", "present, version unknown")
-        return True
-
-def check_provide_pytz(hasdatetime=True):
-    if hasdatetime and (options['provide_pytz'] is True):
+def check_provide_pytz():
+    if options['provide_pytz'] is True:
         print_status("pytz", "matplotlib will provide")
         return True
     try:
         import pytz
     except ImportError:
-        if hasdatetime and options['provide_pytz']:
+        if options['provide_pytz']:
             print_status("pytz", "matplotlib will provide")
             return True
         else:
@@ -462,14 +452,14 @@ def check_provide_pytz(hasdatetime=True):
             print_status("pytz", pytz.__version__)
             return False
 
-def check_provide_dateutil(hasdatetime=True):
-    if hasdatetime and (options['provide_dateutil'] is True):
+def check_provide_dateutil():
+    if options['provide_dateutil'] is True:
         print_status("dateutil", "matplotlib will provide")
         return True
     try:
         import dateutil
     except ImportError:
-        if hasdatetime and options['provide_dateutil']:
+        if options['provide_dateutil']:
             print_status("dateutil", "matplotlib will provide")
             return True
         else: