├── .gitignore ├── LICENSE ├── MANIFEST.in ├── Makefile ├── NEWS ├── PKG-INFO ├── README ├── dateutil ├── __init__.py ├── easter.py ├── parser.py ├── relativedelta.py ├── rrule.py ├── tz.py ├── tzwin.py └── zoneinfo │ ├── __init__.py │ └── zoneinfo-2012c.tar.gz ├── example.py ├── python_dateutil.egg-info ├── PKG-INFO ├── SOURCES.txt ├── dependency_links.txt ├── not-zip-safe └── top_level.txt ├── sandbox ├── rrulewrapper.py └── scheduler.py ├── setup.cfg ├── setup.py ├── test.py └── updatezinfo.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .DS_Store 3 | build/ 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | A. HISTORY OF THE SOFTWARE 2 | ========================== 3 | 4 | Python was created in the early 1990s by Guido van Rossum at Stichting 5 | Mathematisch Centrum (CWI, see http://www.cwi.nl) in the Netherlands 6 | as a successor of a language called ABC. Guido remains Python's 7 | principal author, although it includes many contributions from others. 8 | 9 | In 1995, Guido continued his work on Python at the Corporation for 10 | National Research Initiatives (CNRI, see http://www.cnri.reston.va.us) 11 | in Reston, Virginia where he released several versions of the 12 | software. 13 | 14 | In May 2000, Guido and the Python core development team moved to 15 | BeOpen.com to form the BeOpen PythonLabs team. In October of the same 16 | year, the PythonLabs team moved to Digital Creations (now Zope 17 | Corporation, see http://www.zope.com). In 2001, the Python Software 18 | Foundation (PSF, see http://www.python.org/psf/) was formed, a 19 | non-profit organization created specifically to own Python-related 20 | Intellectual Property. Zope Corporation is a sponsoring member of 21 | the PSF. 22 | 23 | All Python releases are Open Source (see http://www.opensource.org for 24 | the Open Source Definition). Historically, most, but not all, Python 25 | releases have also been GPL-compatible; the table below summarizes 26 | the various releases. 27 | 28 | Release Derived Year Owner GPL- 29 | from compatible? (1) 30 | 31 | 0.9.0 thru 1.2 1991-1995 CWI yes 32 | 1.3 thru 1.5.2 1.2 1995-1999 CNRI yes 33 | 1.6 1.5.2 2000 CNRI no 34 | 2.0 1.6 2000 BeOpen.com no 35 | 1.6.1 1.6 2001 CNRI yes (2) 36 | 2.1 2.0+1.6.1 2001 PSF no 37 | 2.0.1 2.0+1.6.1 2001 PSF yes 38 | 2.1.1 2.1+2.0.1 2001 PSF yes 39 | 2.2 2.1.1 2001 PSF yes 40 | 2.1.2 2.1.1 2002 PSF yes 41 | 2.1.3 2.1.2 2002 PSF yes 42 | 2.2.1 2.2 2002 PSF yes 43 | 2.2.2 2.2.1 2002 PSF yes 44 | 2.2.3 2.2.2 2003 PSF yes 45 | 2.3 2.2.2 2002-2003 PSF yes 46 | 47 | Footnotes: 48 | 49 | (1) GPL-compatible doesn't mean that we're distributing Python under 50 | the GPL. All Python licenses, unlike the GPL, let you distribute 51 | a modified version without making your changes open source. The 52 | GPL-compatible licenses make it possible to combine Python with 53 | other software that is released under the GPL; the others don't. 54 | 55 | (2) According to Richard Stallman, 1.6.1 is not GPL-compatible, 56 | because its license has a choice of law clause. According to 57 | CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1 58 | is "not incompatible" with the GPL. 59 | 60 | Thanks to the many outside volunteers who have worked under Guido's 61 | direction to make these releases possible. 62 | 63 | 64 | B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON 65 | =============================================================== 66 | 67 | PSF LICENSE AGREEMENT FOR PYTHON 2.3 68 | ------------------------------------ 69 | 70 | 1. This LICENSE AGREEMENT is between the Python Software Foundation 71 | ("PSF"), and the Individual or Organization ("Licensee") accessing and 72 | otherwise using Python 2.3 software in source or binary form and its 73 | associated documentation. 74 | 75 | 2. Subject to the terms and conditions of this License Agreement, PSF 76 | hereby grants Licensee a nonexclusive, royalty-free, world-wide 77 | license to reproduce, analyze, test, perform and/or display publicly, 78 | prepare derivative works, distribute, and otherwise use Python 2.3 79 | alone or in any derivative version, provided, however, that PSF's 80 | License Agreement and PSF's notice of copyright, i.e., "Copyright (c) 81 | 2001, 2002, 2003 Python Software Foundation; All Rights Reserved" are 82 | retained in Python 2.3 alone or in any derivative version prepared by 83 | Licensee. 84 | 85 | 3. In the event Licensee prepares a derivative work that is based on 86 | or incorporates Python 2.3 or any part thereof, and wants to make 87 | the derivative work available to others as provided herein, then 88 | Licensee hereby agrees to include in any such work a brief summary of 89 | the changes made to Python 2.3. 90 | 91 | 4. PSF is making Python 2.3 available to Licensee on an "AS IS" 92 | basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR 93 | IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND 94 | DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS 95 | FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 2.3 WILL NOT 96 | INFRINGE ANY THIRD PARTY RIGHTS. 97 | 98 | 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON 99 | 2.3 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS 100 | A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 2.3, 101 | OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. 102 | 103 | 6. This License Agreement will automatically terminate upon a material 104 | breach of its terms and conditions. 105 | 106 | 7. Nothing in this License Agreement shall be deemed to create any 107 | relationship of agency, partnership, or joint venture between PSF and 108 | Licensee. This License Agreement does not grant permission to use PSF 109 | trademarks or trade name in a trademark sense to endorse or promote 110 | products or services of Licensee, or any third party. 111 | 112 | 8. By copying, installing or otherwise using Python 2.3, Licensee 113 | agrees to be bound by the terms and conditions of this License 114 | Agreement. 115 | 116 | 117 | BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0 118 | ------------------------------------------- 119 | 120 | BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1 121 | 122 | 1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an 123 | office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the 124 | Individual or Organization ("Licensee") accessing and otherwise using 125 | this software in source or binary form and its associated 126 | documentation ("the Software"). 127 | 128 | 2. Subject to the terms and conditions of this BeOpen Python License 129 | Agreement, BeOpen hereby grants Licensee a non-exclusive, 130 | royalty-free, world-wide license to reproduce, analyze, test, perform 131 | and/or display publicly, prepare derivative works, distribute, and 132 | otherwise use the Software alone or in any derivative version, 133 | provided, however, that the BeOpen Python License is retained in the 134 | Software, alone or in any derivative version prepared by Licensee. 135 | 136 | 3. BeOpen is making the Software available to Licensee on an "AS IS" 137 | basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR 138 | IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND 139 | DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS 140 | FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT 141 | INFRINGE ANY THIRD PARTY RIGHTS. 142 | 143 | 4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE 144 | SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS 145 | AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY 146 | DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. 147 | 148 | 5. This License Agreement will automatically terminate upon a material 149 | breach of its terms and conditions. 150 | 151 | 6. This License Agreement shall be governed by and interpreted in all 152 | respects by the law of the State of California, excluding conflict of 153 | law provisions. Nothing in this License Agreement shall be deemed to 154 | create any relationship of agency, partnership, or joint venture 155 | between BeOpen and Licensee. This License Agreement does not grant 156 | permission to use BeOpen trademarks or trade names in a trademark 157 | sense to endorse or promote products or services of Licensee, or any 158 | third party. As an exception, the "BeOpen Python" logos available at 159 | http://www.pythonlabs.com/logos.html may be used according to the 160 | permissions granted on that web page. 161 | 162 | 7. By copying, installing or otherwise using the software, Licensee 163 | agrees to be bound by the terms and conditions of this License 164 | Agreement. 165 | 166 | 167 | CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1 168 | --------------------------------------- 169 | 170 | 1. This LICENSE AGREEMENT is between the Corporation for National 171 | Research Initiatives, having an office at 1895 Preston White Drive, 172 | Reston, VA 20191 ("CNRI"), and the Individual or Organization 173 | ("Licensee") accessing and otherwise using Python 1.6.1 software in 174 | source or binary form and its associated documentation. 175 | 176 | 2. Subject to the terms and conditions of this License Agreement, CNRI 177 | hereby grants Licensee a nonexclusive, royalty-free, world-wide 178 | license to reproduce, analyze, test, perform and/or display publicly, 179 | prepare derivative works, distribute, and otherwise use Python 1.6.1 180 | alone or in any derivative version, provided, however, that CNRI's 181 | License Agreement and CNRI's notice of copyright, i.e., "Copyright (c) 182 | 1995-2001 Corporation for National Research Initiatives; All Rights 183 | Reserved" are retained in Python 1.6.1 alone or in any derivative 184 | version prepared by Licensee. Alternately, in lieu of CNRI's License 185 | Agreement, Licensee may substitute the following text (omitting the 186 | quotes): "Python 1.6.1 is made available subject to the terms and 187 | conditions in CNRI's License Agreement. This Agreement together with 188 | Python 1.6.1 may be located on the Internet using the following 189 | unique, persistent identifier (known as a handle): 1895.22/1013. This 190 | Agreement may also be obtained from a proxy server on the Internet 191 | using the following URL: http://hdl.handle.net/1895.22/1013". 192 | 193 | 3. In the event Licensee prepares a derivative work that is based on 194 | or incorporates Python 1.6.1 or any part thereof, and wants to make 195 | the derivative work available to others as provided herein, then 196 | Licensee hereby agrees to include in any such work a brief summary of 197 | the changes made to Python 1.6.1. 198 | 199 | 4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS" 200 | basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR 201 | IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND 202 | DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS 203 | FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT 204 | INFRINGE ANY THIRD PARTY RIGHTS. 205 | 206 | 5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON 207 | 1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS 208 | A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1, 209 | OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. 210 | 211 | 6. This License Agreement will automatically terminate upon a material 212 | breach of its terms and conditions. 213 | 214 | 7. This License Agreement shall be governed by the federal 215 | intellectual property law of the United States, including without 216 | limitation the federal copyright law, and, to the extent such 217 | U.S. federal law does not apply, by the law of the Commonwealth of 218 | Virginia, excluding Virginia's conflict of law provisions. 219 | Notwithstanding the foregoing, with regard to derivative works based 220 | on Python 1.6.1 that incorporate non-separable material that was 221 | previously distributed under the GNU General Public License (GPL), the 222 | law of the Commonwealth of Virginia shall govern this License 223 | Agreement only as to issues arising under or with respect to 224 | Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this 225 | License Agreement shall be deemed to create any relationship of 226 | agency, partnership, or joint venture between CNRI and Licensee. This 227 | License Agreement does not grant permission to use CNRI trademarks or 228 | trade name in a trademark sense to endorse or promote products or 229 | services of Licensee, or any third party. 230 | 231 | 8. By clicking on the "ACCEPT" button where indicated, or by copying, 232 | installing or otherwise using Python 1.6.1, Licensee agrees to be 233 | bound by the terms and conditions of this License Agreement. 234 | 235 | ACCEPT 236 | 237 | 238 | CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2 239 | -------------------------------------------------- 240 | 241 | Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam, 242 | The Netherlands. All rights reserved. 243 | 244 | Permission to use, copy, modify, and distribute this software and its 245 | documentation for any purpose and without fee is hereby granted, 246 | provided that the above copyright notice appear in all copies and that 247 | both that copyright notice and this permission notice appear in 248 | supporting documentation, and that the name of Stichting Mathematisch 249 | Centrum or CWI not be used in advertising or publicity pertaining to 250 | distribution of the software without specific, written prior 251 | permission. 252 | 253 | STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO 254 | THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND 255 | FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE 256 | FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 257 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 258 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT 259 | OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 260 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include dateutil *.py *.tar.* 2 | recursive-include sandbox *.py 3 | include setup.py setup.cfg MANIFEST.in README LICENSE NEWS Makefile 4 | include test.py example.py 5 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Simple wrapper for setup.py script 3 | # 4 | 5 | DESTDIR=/ 6 | PYTHON=python 7 | 8 | prefix=/usr 9 | bindir=$(prefix)/bin 10 | 11 | all: 12 | $(PYTHON) setup.py build 13 | 14 | install: 15 | $(PYTHON) setup.py install \ 16 | --root=$(DESTDIR) \ 17 | --prefix=$(prefix) \ 18 | --install-scripts=$(bindir) 19 | 20 | dist: 21 | $(PYTHON) setup.py sdist 22 | 23 | rpm: 24 | $(PYTHON) setup.py bdist_rpm 25 | 26 | -------------------------------------------------------------------------------- /NEWS: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paxan/python-dateutil/16e385ab1bcae57d7db38a1ab525b954d3614f56/NEWS -------------------------------------------------------------------------------- /PKG-INFO: -------------------------------------------------------------------------------- 1 | Metadata-Version: 1.0 2 | Name: python-dateutil 3 | Version: 1.5.0.1 4 | Summary: Extensions to the standard python 2.3+ datetime module 5 | Home-page: http://labix.org/python-dateutil 6 | Author: Gustavo Niemeyer 7 | Author-email: gustavo@niemeyer.net 8 | License: PSF License 9 | Description: The dateutil module provides powerful extensions to the standard 10 | datetime module, available in Python 2.3+. 11 | 12 | Platform: UNKNOWN 13 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | ## This file is in the moin format. The latest version is found 2 | ## at https://moin.conectiva.com.br/DateUtil 3 | 4 | == Contents == 5 | [[TableOfContents]] 6 | 7 | == Description == 8 | The '''dateutil''' module provides powerful extensions to 9 | the standard '''datetime''' module, available in Python 2.3+. 10 | 11 | == Features == 12 | 13 | * Computing of relative deltas (next month, next year, 14 | next monday, last week of month, etc); 15 | 16 | * Computing of relative deltas between two given 17 | date and/or datetime objects; 18 | 19 | * Computing of dates based on very flexible recurrence rules, 20 | using a superset of the 21 | [ftp://ftp.rfc-editor.org/in-notes/rfc2445.txt iCalendar] 22 | specification. Parsing of RFC strings is supported as well. 23 | 24 | * Generic parsing of dates in almost any string format; 25 | 26 | * Timezone (tzinfo) implementations for tzfile(5) format 27 | files (/etc/localtime, /usr/share/zoneinfo, etc), TZ 28 | environment string (in all known formats), iCalendar 29 | format files, given ranges (with help from relative deltas), 30 | local machine timezone, fixed offset timezone, UTC timezone, 31 | and Windows registry-based time zones. 32 | 33 | * Internal up-to-date world timezone information based on 34 | Olson's database. 35 | 36 | * Computing of Easter Sunday dates for any given year, 37 | using Western, Orthodox or Julian algorithms; 38 | 39 | * More than 400 test cases. 40 | 41 | == Quick example == 42 | Here's a snapshot, just to give an idea about the power of the 43 | package. For more examples, look at the documentation below. 44 | 45 | Suppose you want to know how much time is left, in 46 | years/months/days/etc, before the next easter happening on a 47 | year with a Friday 13th in August, and you want to get today's 48 | date out of the "date" unix system command. Here is the code: 49 | {{{ 50 | from dateutil.relativedelta import * 51 | from dateutil.easter import * 52 | from dateutil.rrule import * 53 | from dateutil.parser import * 54 | from datetime import * 55 | import commands 56 | import os 57 | now = parse(commands.getoutput("date")) 58 | today = now.date() 59 | year = rrule(YEARLY,bymonth=8,bymonthday=13,byweekday=FR)[0].year 60 | rdelta = relativedelta(easter(year), today) 61 | print "Today is:", today 62 | print "Year with next Aug 13th on a Friday is:", year 63 | print "How far is the Easter of that year:", rdelta 64 | print "And the Easter of that year is:", today+rdelta 65 | }}} 66 | 67 | And here's the output: 68 | {{{ 69 | Today is: 2003-10-11 70 | Year with next Aug 13th on a Friday is: 2004 71 | How far is the Easter of that year: relativedelta(months=+6) 72 | And the Easter of that year is: 2004-04-11 73 | }}} 74 | 75 | {i} Being exactly 6 months ahead was '''really''' a coincidence :) 76 | 77 | == Download == 78 | The following files are available. 79 | * attachment:python-dateutil-1.0.tar.bz2 80 | * attachment:python-dateutil-1.0-1.noarch.rpm 81 | 82 | == Author == 83 | The dateutil module was written by GustavoNiemeyer . 84 | 85 | == Documentation == 86 | The following modules are available. 87 | 88 | === relativedelta === 89 | This module offers the '''relativedelta''' type, which is based 90 | on the specification of the excelent work done by M.-A. Lemburg in his 91 | [http://www.egenix.com/files/python/mxDateTime.html mxDateTime] 92 | extension. However, notice that this type '''does not''' implement the 93 | same algorithm as his work. Do not expect it to behave like 94 | {{{mxDateTime}}}'s counterpart. 95 | 96 | ==== relativedelta type ==== 97 | 98 | There's two different ways to build a relativedelta instance. The 99 | first one is passing it two {{{date}}}/{{{datetime}}} instances: 100 | {{{ 101 | relativedelta(datetime1, datetime2) 102 | }}} 103 | 104 | This will build the relative difference between {{{datetime1}}} and 105 | {{{datetime2}}}, so that the following constraint is always true: 106 | {{{ 107 | datetime2+relativedelta(datetime1, datetime2) == datetime1 108 | }}} 109 | 110 | Notice that instead of {{{datetime}}} instances, you may use 111 | {{{date}}} instances, or a mix of both. 112 | 113 | And the other way is to use any of the following keyword arguments: 114 | 115 | year, month, day, hour, minute, second, microsecond:: 116 | Absolute information. 117 | 118 | years, months, weeks, days, hours, minutes, seconds, microseconds:: 119 | Relative information, may be negative. 120 | 121 | weekday:: 122 | One of the weekday instances ({{{MO}}}, {{{TU}}}, etc). These 123 | instances may receive a parameter {{{n}}}, specifying the {{{n}}}th 124 | weekday, which could be positive or negative (like {{{MO(+2)}}} or 125 | {{{MO(-3)}}}. Not specifying it is the same as specifying {{{+1}}}. 126 | You can also use an integer, where {{{0=MO}}}. Notice that, 127 | for example, if the calculated date is already Monday, using 128 | {{{MO}}} or {{{MO(+1)}}} (which is the same thing in this context), 129 | won't change the day. 130 | 131 | leapdays:: 132 | Will add given days to the date found, but only if the computed 133 | year is a leap year and the computed date is post 28 of february. 134 | 135 | yearday, nlyearday:: 136 | Set the yearday or the non-leap year day (jump leap days). 137 | These are converted to {{{day}}}/{{{month}}}/{{{leapdays}}} 138 | information. 139 | 140 | ==== Behavior of operations ==== 141 | If you're curious about exactly how the relative delta will act 142 | on operations, here is a description of its behavior. 143 | 144 | 1. Calculate the absolute year, using the {{{year}}} argument, or the 145 | original datetime year, if the argument is not present. 146 | 1. Add the relative {{{years}}} argument to the absolute year. 147 | 1. Do steps 1 and 2 for {{{month}}}/{{{months}}}. 148 | 1. Calculate the absolute day, using the {{{day}}} argument, or the 149 | original datetime day, if the argument is not present. Then, subtract 150 | from the day until it fits in the year and month found after their 151 | operations. 152 | 1. Add the relative {{{days}}} argument to the absolute day. Notice 153 | that the {{{weeks}}} argument is multiplied by 7 and added to {{{days}}}. 154 | 1. If {{{leapdays}}} is present, the computed year is a leap year, and 155 | the computed month is after february, remove one day from the found date. 156 | 1. Do steps 1 and 2 for {{{hour}}}/{{{hours}}}, {{{minute}}}/{{{minutes}}}, 157 | {{{second}}}/{{{seconds}}}, {{{microsecond}}}/{{{microseconds}}}. 158 | 1. If the {{{weekday}}} argument is present, calculate the {{{n}}}th 159 | occurrence of the given weekday. 160 | 161 | ==== Examples ==== 162 | 163 | Let's begin our trip. 164 | {{{ 165 | >>> from datetime import *; from dateutil.relativedelta import * 166 | >>> import calendar 167 | }}} 168 | 169 | Store some values. 170 | {{{ 171 | >>> NOW = datetime.now() 172 | >>> TODAY = date.today() 173 | >>> NOW 174 | datetime.datetime(2003, 9, 17, 20, 54, 47, 282310) 175 | >>> TODAY 176 | datetime.date(2003, 9, 17) 177 | }}} 178 | 179 | Next month. 180 | {{{ 181 | >>> NOW+relativedelta(months=+1) 182 | datetime.datetime(2003, 10, 17, 20, 54, 47, 282310) 183 | }}} 184 | 185 | Next month, plus one week. 186 | {{{ 187 | >>> NOW+relativedelta(months=+1, weeks=+1) 188 | datetime.datetime(2003, 10, 24, 20, 54, 47, 282310) 189 | }}} 190 | 191 | Next month, plus one week, at 10am. 192 | {{{ 193 | >>> TODAY+relativedelta(months=+1, weeks=+1, hour=10) 194 | datetime.datetime(2003, 10, 24, 10, 0) 195 | }}} 196 | 197 | Let's try the other way around. Notice that the 198 | hour setting we get in the relativedelta is relative, 199 | since it's a difference, and the weeks parameter 200 | has gone. 201 | {{{ 202 | >>> relativedelta(datetime(2003, 10, 24, 10, 0), TODAY) 203 | relativedelta(months=+1, days=+7, hours=+10) 204 | }}} 205 | 206 | One month before one year. 207 | {{{ 208 | >>> NOW+relativedelta(years=+1, months=-1) 209 | datetime.datetime(2004, 8, 17, 20, 54, 47, 282310) 210 | }}} 211 | 212 | How does it handle months with different numbers of days? 213 | Notice that adding one month will never cross the month 214 | boundary. 215 | {{{ 216 | >>> date(2003,1,27)+relativedelta(months=+1) 217 | datetime.date(2003, 2, 27) 218 | >>> date(2003,1,31)+relativedelta(months=+1) 219 | datetime.date(2003, 2, 28) 220 | >>> date(2003,1,31)+relativedelta(months=+2) 221 | datetime.date(2003, 3, 31) 222 | }}} 223 | 224 | The logic for years is the same, even on leap years. 225 | {{{ 226 | >>> date(2000,2,28)+relativedelta(years=+1) 227 | datetime.date(2001, 2, 28) 228 | >>> date(2000,2,29)+relativedelta(years=+1) 229 | datetime.date(2001, 2, 28) 230 | 231 | >>> date(1999,2,28)+relativedelta(years=+1) 232 | datetime.date(2000, 2, 28) 233 | >>> date(1999,3,1)+relativedelta(years=+1) 234 | datetime.date(2000, 3, 1) 235 | 236 | >>> date(2001,2,28)+relativedelta(years=-1) 237 | datetime.date(2000, 2, 28) 238 | >>> date(2001,3,1)+relativedelta(years=-1) 239 | datetime.date(2000, 3, 1) 240 | }}} 241 | 242 | Next friday. 243 | {{{ 244 | >>> TODAY+relativedelta(weekday=FR) 245 | datetime.date(2003, 9, 19) 246 | 247 | >>> TODAY+relativedelta(weekday=calendar.FRIDAY) 248 | datetime.date(2003, 9, 19) 249 | }}} 250 | 251 | Last friday in this month. 252 | {{{ 253 | >>> TODAY+relativedelta(day=31, weekday=FR(-1)) 254 | datetime.date(2003, 9, 26) 255 | }}} 256 | 257 | Next wednesday (it's today!). 258 | {{{ 259 | >>> TODAY+relativedelta(weekday=WE(+1)) 260 | datetime.date(2003, 9, 17) 261 | }}} 262 | 263 | Next wednesday, but not today. 264 | {{{ 265 | >>> TODAY+relativedelta(days=+1, weekday=WE(+1)) 266 | datetime.date(2003, 9, 24) 267 | }}} 268 | 269 | Following 270 | [http://www.cl.cam.ac.uk/~mgk25/iso-time.html ISO year week number notation] 271 | find the first day of the 15th week of 1997. 272 | {{{ 273 | >>> datetime(1997,1,1)+relativedelta(day=4, weekday=MO(-1), weeks=+14) 274 | datetime.datetime(1997, 4, 7, 0, 0) 275 | }}} 276 | 277 | How long ago has the millennium changed? 278 | {{{ 279 | >>> relativedelta(NOW, date(2001,1,1)) 280 | relativedelta(years=+2, months=+8, days=+16, 281 | hours=+20, minutes=+54, seconds=+47, microseconds=+282310) 282 | }}} 283 | 284 | How old is John? 285 | {{{ 286 | >>> johnbirthday = datetime(1978, 4, 5, 12, 0) 287 | >>> relativedelta(NOW, johnbirthday) 288 | relativedelta(years=+25, months=+5, days=+12, 289 | hours=+8, minutes=+54, seconds=+47, microseconds=+282310) 290 | }}} 291 | 292 | It works with dates too. 293 | {{{ 294 | >>> relativedelta(TODAY, johnbirthday) 295 | relativedelta(years=+25, months=+5, days=+11, hours=+12) 296 | }}} 297 | 298 | Obtain today's date using the yearday: 299 | {{{ 300 | >>> date(2003, 1, 1)+relativedelta(yearday=260) 301 | datetime.date(2003, 9, 17) 302 | }}} 303 | 304 | We can use today's date, since yearday should be absolute 305 | in the given year: 306 | {{{ 307 | >>> TODAY+relativedelta(yearday=260) 308 | datetime.date(2003, 9, 17) 309 | }}} 310 | 311 | Last year it should be in the same day: 312 | {{{ 313 | >>> date(2002, 1, 1)+relativedelta(yearday=260) 314 | datetime.date(2002, 9, 17) 315 | }}} 316 | 317 | But not in a leap year: 318 | {{{ 319 | >>> date(2000, 1, 1)+relativedelta(yearday=260) 320 | datetime.date(2000, 9, 16) 321 | }}} 322 | 323 | We can use the non-leap year day to ignore this: 324 | {{{ 325 | >>> date(2000, 1, 1)+relativedelta(nlyearday=260) 326 | datetime.date(2000, 9, 17) 327 | }}} 328 | 329 | === rrule === 330 | The rrule module offers a small, complete, and very fast, implementation 331 | of the recurrence rules documented in the 332 | [ftp://ftp.rfc-editor.org/in-notes/rfc2445.txt iCalendar RFC], including 333 | support for caching of results. 334 | 335 | ==== rrule type ==== 336 | That's the base of the rrule operation. It accepts all the keywords 337 | defined in the RFC as its constructor parameters (except {{{byday}}}, 338 | which was renamed to {{{byweekday}}}) and more. The constructor 339 | prototype is: 340 | {{{ 341 | rrule(freq) 342 | }}} 343 | 344 | Where {{{freq}}} must be one of {{{YEARLY}}}, {{{MONTHLY}}}, 345 | {{{WEEKLY}}}, {{{DAILY}}}, {{{HOURLY}}}, {{{MINUTELY}}}, 346 | or {{{SECONDLY}}}. 347 | 348 | Additionally, it supports the following keyword arguments: 349 | 350 | cache:: 351 | If given, it must be a boolean value specifying to enable 352 | or disable caching of results. If you will use the same 353 | {{{rrule}}} instance multiple times, enabling caching will 354 | improve the performance considerably. 355 | 356 | dtstart:: 357 | The recurrence start. Besides being the base for the 358 | recurrence, missing parameters in the final recurrence 359 | instances will also be extracted from this date. If not 360 | given, {{{datetime.now()}}} will be used instead. 361 | 362 | interval:: 363 | The interval between each {{{freq}}} iteration. For example, 364 | when using {{{YEARLY}}}, an interval of {{{2}}} means 365 | once every two years, but with {{{HOURLY}}}, it means 366 | once every two hours. The default interval is {{{1}}}. 367 | 368 | wkst:: 369 | The week start day. Must be one of the {{{MO}}}, {{{TU}}}, 370 | {{{WE}}} constants, or an integer, specifying the first day 371 | of the week. This will affect recurrences based on weekly 372 | periods. The default week start is got from 373 | {{{calendar.firstweekday()}}}, and may be modified by 374 | {{{calendar.setfirstweekday()}}}. 375 | 376 | count:: 377 | How many occurrences will be generated. 378 | 379 | until:: 380 | If given, this must be a {{{datetime}}} instance, that will 381 | specify the limit of the recurrence. If a recurrence instance 382 | happens to be the same as the {{{datetime}}} instance given 383 | in the {{{until}}} keyword, this will be the last occurrence. 384 | 385 | bysetpos:: 386 | If given, it must be either an integer, or a sequence of 387 | integers, positive or negative. Each given integer will 388 | specify an occurrence number, corresponding to the nth 389 | occurrence of the rule inside the frequency period. For 390 | example, a {{{bysetpos}}} of {{{-1}}} if combined with a 391 | {{{MONTHLY}}} frequency, and a {{{byweekday}}} of 392 | {{{(MO, TU, WE, TH, FR)}}}, will result in the last work 393 | day of every month. 394 | 395 | bymonth:: 396 | If given, it must be either an integer, or a sequence of 397 | integers, meaning the months to apply the recurrence to. 398 | 399 | bymonthday:: 400 | If given, it must be either an integer, or a sequence of 401 | integers, meaning the month days to apply the recurrence to. 402 | 403 | byyearday:: 404 | If given, it must be either an integer, or a sequence of 405 | integers, meaning the year days to apply the recurrence to. 406 | 407 | byweekno:: 408 | If given, it must be either an integer, or a sequence of 409 | integers, meaning the week numbers to apply the recurrence 410 | to. Week numbers have the meaning described in ISO8601, 411 | that is, the first week of the year is that containing at 412 | least four days of the new year. 413 | 414 | byweekday:: 415 | If given, it must be either an integer ({{{0 == MO}}}), a 416 | sequence of integers, one of the weekday constants 417 | ({{{MO}}}, {{{TU}}}, etc), or a sequence of these constants. 418 | When given, these variables will define the weekdays where 419 | the recurrence will be applied. It's also possible to use 420 | an argument {{{n}}} for the weekday instances, which will 421 | mean the {{{n}}}''th'' occurrence of this weekday in the 422 | period. For example, with {{{MONTHLY}}}, or with 423 | {{{YEARLY}}} and {{{BYMONTH}}}, using {{{FR(+1)}}} 424 | in {{{byweekday}}} will specify the first friday of the 425 | month where the recurrence happens. Notice that in the RFC 426 | documentation, this is specified as {{{BYDAY}}}, but was 427 | renamed to avoid the ambiguity of that keyword. 428 | 429 | byhour:: 430 | If given, it must be either an integer, or a sequence of 431 | integers, meaning the hours to apply the recurrence to. 432 | 433 | byminute:: 434 | If given, it must be either an integer, or a sequence of 435 | integers, meaning the minutes to apply the recurrence to. 436 | 437 | bysecond:: 438 | If given, it must be either an integer, or a sequence of 439 | integers, meaning the seconds to apply the recurrence to. 440 | 441 | byeaster:: 442 | If given, it must be either an integer, or a sequence of 443 | integers, positive or negative. Each integer will define 444 | an offset from the Easter Sunday. Passing the offset 445 | {{{0}}} to {{{byeaster}}} will yield the Easter Sunday 446 | itself. This is an extension to the RFC specification. 447 | 448 | ==== rrule methods ==== 449 | The following methods are available in {{{rrule}}} instances: 450 | 451 | rrule.before(dt, inc=False):: 452 | Returns the last recurrence before the given {{{datetime}}} 453 | instance. The {{{inc}}} keyword defines what happens if 454 | {{{dt}}} '''is''' an occurrence. With {{{inc == True}}}, 455 | if {{{dt}}} itself is an occurrence, it will be returned. 456 | 457 | rrule.after(dt, inc=False):: 458 | Returns the first recurrence after the given {{{datetime}}} 459 | instance. The {{{inc}}} keyword defines what happens if 460 | {{{dt}}} '''is''' an occurrence. With {{{inc == True}}}, 461 | if {{{dt}}} itself is an occurrence, it will be returned. 462 | 463 | rrule.between(after, before, inc=False):: 464 | Returns all the occurrences of the rrule between {{{after}}} 465 | and {{{before}}}. The {{{inc}}} keyword defines what happens 466 | if {{{after}}} and/or {{{before}}} are themselves occurrences. 467 | With {{{inc == True}}}, they will be included in the list, 468 | if they are found in the recurrence set. 469 | 470 | rrule.count():: 471 | Returns the number of recurrences in this set. It will have 472 | go trough the whole recurrence, if this hasn't been done 473 | before. 474 | 475 | Besides these methods, {{{rrule}}} instances also support 476 | the {{{__getitem__()}}} and {{{__contains__()}}} special methods, 477 | meaning that these are valid expressions: 478 | {{{ 479 | rr = rrule(...) 480 | if datetime(...) in rr: 481 | ... 482 | print rr[0] 483 | print rr[-1] 484 | print rr[1:2] 485 | print rr[::-2] 486 | }}} 487 | 488 | The getitem/slicing mechanism is smart enough to avoid getting the whole 489 | recurrence set, if possible. 490 | 491 | ==== Notes ==== 492 | 493 | * The rrule type has no {{{byday}}} keyword. The equivalent keyword 494 | has been replaced by the {{{byweekday}}} keyword, to remove the 495 | ambiguity present in the original keyword. 496 | 497 | * Unlike documented in the RFC, the starting datetime ({{{dtstart}}}) 498 | is not the first recurrence instance, unless it does fit in the 499 | specified rules. In a python module context, this behavior makes more 500 | sense than otherwise. Notice that you can easily get the original 501 | behavior by using a rruleset and adding the {{{dtstart}}} as an 502 | {{{rdate}}} recurrence. 503 | 504 | * Unlike documented in the RFC, every keyword is valid on every 505 | frequency (the RFC documents that {{{byweekno}}} is only valid 506 | on yearly frequencies, for example). 507 | 508 | * In addition to the documented keywords, a {{{byeaster}}} keyword 509 | was introduced, making it easy to compute recurrent events relative 510 | to the Easter Sunday. 511 | 512 | ==== rrule examples ==== 513 | These examples were converted from the RFC. 514 | 515 | Prepare the environment. 516 | {{{ 517 | >>> from dateutil.rrule import * 518 | >>> from dateutil.parser import * 519 | >>> from datetime import * 520 | 521 | >>> import pprint 522 | >>> import sys 523 | >>> sys.displayhook = pprint.pprint 524 | }}} 525 | 526 | Daily, for 10 occurrences. 527 | {{{ 528 | >>> list(rrule(DAILY, count=10, 529 | dtstart=parse("19970902T090000"))) 530 | [datetime.datetime(1997, 9, 2, 9, 0), 531 | datetime.datetime(1997, 9, 3, 9, 0), 532 | datetime.datetime(1997, 9, 4, 9, 0), 533 | datetime.datetime(1997, 9, 5, 9, 0), 534 | datetime.datetime(1997, 9, 6, 9, 0), 535 | datetime.datetime(1997, 9, 7, 9, 0), 536 | datetime.datetime(1997, 9, 8, 9, 0), 537 | datetime.datetime(1997, 9, 9, 9, 0), 538 | datetime.datetime(1997, 9, 10, 9, 0), 539 | datetime.datetime(1997, 9, 11, 9, 0)] 540 | }}} 541 | 542 | Daily until December 24, 1997 543 | {{{ 544 | >>> list(rrule(DAILY, 545 | dtstart=parse("19970902T090000"), 546 | until=parse("19971224T000000"))) 547 | [datetime.datetime(1997, 9, 2, 9, 0), 548 | datetime.datetime(1997, 9, 3, 9, 0), 549 | datetime.datetime(1997, 9, 4, 9, 0), 550 | (...) 551 | datetime.datetime(1997, 12, 21, 9, 0), 552 | datetime.datetime(1997, 12, 22, 9, 0), 553 | datetime.datetime(1997, 12, 23, 9, 0)] 554 | }}} 555 | 556 | Every other day, 5 occurrences. 557 | {{{ 558 | >>> list(rrule(DAILY, interval=2, count=5, 559 | dtstart=parse("19970902T090000"))) 560 | [datetime.datetime(1997, 9, 2, 9, 0), 561 | datetime.datetime(1997, 9, 4, 9, 0), 562 | datetime.datetime(1997, 9, 6, 9, 0), 563 | datetime.datetime(1997, 9, 8, 9, 0), 564 | datetime.datetime(1997, 9, 10, 9, 0)] 565 | }}} 566 | 567 | Every 10 days, 5 occurrences. 568 | {{{ 569 | >>> list(rrule(DAILY, interval=10, count=5, 570 | dtstart=parse("19970902T090000"))) 571 | [datetime.datetime(1997, 9, 2, 9, 0), 572 | datetime.datetime(1997, 9, 12, 9, 0), 573 | datetime.datetime(1997, 9, 22, 9, 0), 574 | datetime.datetime(1997, 10, 2, 9, 0), 575 | datetime.datetime(1997, 10, 12, 9, 0)] 576 | }}} 577 | 578 | Everyday in January, for 3 years. 579 | {{{ 580 | >>> list(rrule(YEARLY, bymonth=1, byweekday=range(7), 581 | dtstart=parse("19980101T090000"), 582 | until=parse("20000131T090000"))) 583 | [datetime.datetime(1998, 1, 1, 9, 0), 584 | datetime.datetime(1998, 1, 2, 9, 0), 585 | (...) 586 | datetime.datetime(1998, 1, 30, 9, 0), 587 | datetime.datetime(1998, 1, 31, 9, 0), 588 | datetime.datetime(1999, 1, 1, 9, 0), 589 | datetime.datetime(1999, 1, 2, 9, 0), 590 | (...) 591 | datetime.datetime(1999, 1, 30, 9, 0), 592 | datetime.datetime(1999, 1, 31, 9, 0), 593 | datetime.datetime(2000, 1, 1, 9, 0), 594 | datetime.datetime(2000, 1, 2, 9, 0), 595 | (...) 596 | datetime.datetime(2000, 1, 29, 9, 0), 597 | datetime.datetime(2000, 1, 31, 9, 0)] 598 | }}} 599 | 600 | Same thing, in another way. 601 | {{{ 602 | >>> list(rrule(DAILY, bymonth=1, 603 | dtstart=parse("19980101T090000"), 604 | until=parse("20000131T090000"))) 605 | (...) 606 | }}} 607 | 608 | Weekly for 10 occurrences. 609 | {{{ 610 | >>> list(rrule(WEEKLY, count=10, 611 | dtstart=parse("19970902T090000"))) 612 | [datetime.datetime(1997, 9, 2, 9, 0), 613 | datetime.datetime(1997, 9, 9, 9, 0), 614 | datetime.datetime(1997, 9, 16, 9, 0), 615 | datetime.datetime(1997, 9, 23, 9, 0), 616 | datetime.datetime(1997, 9, 30, 9, 0), 617 | datetime.datetime(1997, 10, 7, 9, 0), 618 | datetime.datetime(1997, 10, 14, 9, 0), 619 | datetime.datetime(1997, 10, 21, 9, 0), 620 | datetime.datetime(1997, 10, 28, 9, 0), 621 | datetime.datetime(1997, 11, 4, 9, 0)] 622 | }}} 623 | 624 | Every other week, 6 occurrences. 625 | {{{ 626 | >>> list(rrule(WEEKLY, interval=2, count=6, 627 | dtstart=parse("19970902T090000"))) 628 | [datetime.datetime(1997, 9, 2, 9, 0), 629 | datetime.datetime(1997, 9, 16, 9, 0), 630 | datetime.datetime(1997, 9, 30, 9, 0), 631 | datetime.datetime(1997, 10, 14, 9, 0), 632 | datetime.datetime(1997, 10, 28, 9, 0), 633 | datetime.datetime(1997, 11, 11, 9, 0)] 634 | }}} 635 | 636 | Weekly on Tuesday and Thursday for 5 weeks. 637 | {{{ 638 | >>> list(rrule(WEEKLY, count=10, wkst=SU, byweekday=(TU,TH), 639 | dtstart=parse("19970902T090000"))) 640 | [datetime.datetime(1997, 9, 2, 9, 0), 641 | datetime.datetime(1997, 9, 4, 9, 0), 642 | datetime.datetime(1997, 9, 9, 9, 0), 643 | datetime.datetime(1997, 9, 11, 9, 0), 644 | datetime.datetime(1997, 9, 16, 9, 0), 645 | datetime.datetime(1997, 9, 18, 9, 0), 646 | datetime.datetime(1997, 9, 23, 9, 0), 647 | datetime.datetime(1997, 9, 25, 9, 0), 648 | datetime.datetime(1997, 9, 30, 9, 0), 649 | datetime.datetime(1997, 10, 2, 9, 0)] 650 | }}} 651 | 652 | Every other week on Tuesday and Thursday, for 8 occurrences. 653 | {{{ 654 | >>> list(rrule(WEEKLY, interval=2, count=8, 655 | wkst=SU, byweekday=(TU,TH), 656 | dtstart=parse("19970902T090000"))) 657 | [datetime.datetime(1997, 9, 2, 9, 0), 658 | datetime.datetime(1997, 9, 4, 9, 0), 659 | datetime.datetime(1997, 9, 16, 9, 0), 660 | datetime.datetime(1997, 9, 18, 9, 0), 661 | datetime.datetime(1997, 9, 30, 9, 0), 662 | datetime.datetime(1997, 10, 2, 9, 0), 663 | datetime.datetime(1997, 10, 14, 9, 0), 664 | datetime.datetime(1997, 10, 16, 9, 0)] 665 | }}} 666 | 667 | Monthly on the 1st Friday for ten occurrences. 668 | {{{ 669 | >>> list(rrule(MONTHLY, count=10, byweekday=FR(1), 670 | dtstart=parse("19970905T090000"))) 671 | [datetime.datetime(1997, 9, 5, 9, 0), 672 | datetime.datetime(1997, 10, 3, 9, 0), 673 | datetime.datetime(1997, 11, 7, 9, 0), 674 | datetime.datetime(1997, 12, 5, 9, 0), 675 | datetime.datetime(1998, 1, 2, 9, 0), 676 | datetime.datetime(1998, 2, 6, 9, 0), 677 | datetime.datetime(1998, 3, 6, 9, 0), 678 | datetime.datetime(1998, 4, 3, 9, 0), 679 | datetime.datetime(1998, 5, 1, 9, 0), 680 | datetime.datetime(1998, 6, 5, 9, 0)] 681 | }}} 682 | 683 | Every other month on the 1st and last Sunday of the month for 10 occurrences. 684 | {{{ 685 | >>> list(rrule(MONTHLY, interval=2, count=10, 686 | byweekday=(SU(1), SU(-1)), 687 | dtstart=parse("19970907T090000"))) 688 | [datetime.datetime(1997, 9, 7, 9, 0), 689 | datetime.datetime(1997, 9, 28, 9, 0), 690 | datetime.datetime(1997, 11, 2, 9, 0), 691 | datetime.datetime(1997, 11, 30, 9, 0), 692 | datetime.datetime(1998, 1, 4, 9, 0), 693 | datetime.datetime(1998, 1, 25, 9, 0), 694 | datetime.datetime(1998, 3, 1, 9, 0), 695 | datetime.datetime(1998, 3, 29, 9, 0), 696 | datetime.datetime(1998, 5, 3, 9, 0), 697 | datetime.datetime(1998, 5, 31, 9, 0)] 698 | }}} 699 | 700 | Monthly on the second to last Monday of the month for 6 months. 701 | {{{ 702 | >>> list(rrule(MONTHLY, count=6, byweekday=MO(-2), 703 | dtstart=parse("19970922T090000"))) 704 | [datetime.datetime(1997, 9, 22, 9, 0), 705 | datetime.datetime(1997, 10, 20, 9, 0), 706 | datetime.datetime(1997, 11, 17, 9, 0), 707 | datetime.datetime(1997, 12, 22, 9, 0), 708 | datetime.datetime(1998, 1, 19, 9, 0), 709 | datetime.datetime(1998, 2, 16, 9, 0)] 710 | }}} 711 | 712 | Monthly on the third to the last day of the month, for 6 months. 713 | {{{ 714 | >>> list(rrule(MONTHLY, count=6, bymonthday=-3, 715 | dtstart=parse("19970928T090000"))) 716 | [datetime.datetime(1997, 9, 28, 9, 0), 717 | datetime.datetime(1997, 10, 29, 9, 0), 718 | datetime.datetime(1997, 11, 28, 9, 0), 719 | datetime.datetime(1997, 12, 29, 9, 0), 720 | datetime.datetime(1998, 1, 29, 9, 0), 721 | datetime.datetime(1998, 2, 26, 9, 0)] 722 | }}} 723 | 724 | Monthly on the 2nd and 15th of the month for 5 occurrences. 725 | {{{ 726 | >>> list(rrule(MONTHLY, count=5, bymonthday=(2,15), 727 | dtstart=parse("19970902T090000"))) 728 | [datetime.datetime(1997, 9, 2, 9, 0), 729 | datetime.datetime(1997, 9, 15, 9, 0), 730 | datetime.datetime(1997, 10, 2, 9, 0), 731 | datetime.datetime(1997, 10, 15, 9, 0), 732 | datetime.datetime(1997, 11, 2, 9, 0)] 733 | }}} 734 | 735 | Monthly on the first and last day of the month for 3 occurrences. 736 | {{{ 737 | >>> list(rrule(MONTHLY, count=5, bymonthday=(-1,1,), 738 | dtstart=parse("1997090 739 | 2T090000"))) 740 | [datetime.datetime(1997, 9, 30, 9, 0), 741 | datetime.datetime(1997, 10, 1, 9, 0), 742 | datetime.datetime(1997, 10, 31, 9, 0), 743 | datetime.datetime(1997, 11, 1, 9, 0), 744 | datetime.datetime(1997, 11, 30, 9, 0)] 745 | }}} 746 | 747 | Every 18 months on the 10th thru 15th of the month for 10 occurrences. 748 | {{{ 749 | >>> list(rrule(MONTHLY, interval=18, count=10, 750 | bymonthday=range(10,16), 751 | dtstart=parse("19970910T090000"))) 752 | [datetime.datetime(1997, 9, 10, 9, 0), 753 | datetime.datetime(1997, 9, 11, 9, 0), 754 | datetime.datetime(1997, 9, 12, 9, 0), 755 | datetime.datetime(1997, 9, 13, 9, 0), 756 | datetime.datetime(1997, 9, 14, 9, 0), 757 | datetime.datetime(1997, 9, 15, 9, 0), 758 | datetime.datetime(1999, 3, 10, 9, 0), 759 | datetime.datetime(1999, 3, 11, 9, 0), 760 | datetime.datetime(1999, 3, 12, 9, 0), 761 | datetime.datetime(1999, 3, 13, 9, 0)] 762 | }}} 763 | 764 | Every Tuesday, every other month, 6 occurences. 765 | {{{ 766 | >>> list(rrule(MONTHLY, interval=2, count=6, byweekday=TU, 767 | dtstart=parse("19970902T090000"))) 768 | [datetime.datetime(1997, 9, 2, 9, 0), 769 | datetime.datetime(1997, 9, 9, 9, 0), 770 | datetime.datetime(1997, 9, 16, 9, 0), 771 | datetime.datetime(1997, 9, 23, 9, 0), 772 | datetime.datetime(1997, 9, 30, 9, 0), 773 | datetime.datetime(1997, 11, 4, 9, 0)] 774 | }}} 775 | 776 | Yearly in June and July for 10 occurrences. 777 | {{{ 778 | >>> list(rrule(YEARLY, count=4, bymonth=(6,7), 779 | dtstart=parse("19970610T0900 780 | 00"))) 781 | [datetime.datetime(1997, 6, 10, 9, 0), 782 | datetime.datetime(1997, 7, 10, 9, 0), 783 | datetime.datetime(1998, 6, 10, 9, 0), 784 | datetime.datetime(1998, 7, 10, 9, 0)] 785 | }}} 786 | 787 | Every 3rd year on the 1st, 100th and 200th day for 4 occurrences. 788 | {{{ 789 | >>> list(rrule(YEARLY, count=4, interval=3, byyearday=(1,100,200), 790 | dtstart=parse("19970101T090000"))) 791 | [datetime.datetime(1997, 1, 1, 9, 0), 792 | datetime.datetime(1997, 4, 10, 9, 0), 793 | datetime.datetime(1997, 7, 19, 9, 0), 794 | datetime.datetime(2000, 1, 1, 9, 0)] 795 | }}} 796 | 797 | Every 20th Monday of the year, 3 occurrences. 798 | {{{ 799 | >>> list(rrule(YEARLY, count=3, byweekday=MO(20), 800 | dtstart=parse("19970519T090000"))) 801 | [datetime.datetime(1997, 5, 19, 9, 0), 802 | datetime.datetime(1998, 5, 18, 9, 0), 803 | datetime.datetime(1999, 5, 17, 9, 0)] 804 | }}} 805 | 806 | Monday of week number 20 (where the default start of the week is Monday), 807 | 3 occurrences. 808 | {{{ 809 | >>> list(rrule(YEARLY, count=3, byweekno=20, byweekday=MO, 810 | dtstart=parse("19970512T090000"))) 811 | [datetime.datetime(1997, 5, 12, 9, 0), 812 | datetime.datetime(1998, 5, 11, 9, 0), 813 | datetime.datetime(1999, 5, 17, 9, 0)] 814 | }}} 815 | 816 | The week number 1 may be in the last year. 817 | {{{ 818 | >>> list(rrule(WEEKLY, count=3, byweekno=1, byweekday=MO, 819 | dtstart=parse("19970902T090000"))) 820 | [datetime.datetime(1997, 12, 29, 9, 0), 821 | datetime.datetime(1999, 1, 4, 9, 0), 822 | datetime.datetime(2000, 1, 3, 9, 0)] 823 | }}} 824 | 825 | And the week numbers greater than 51 may be in the next year. 826 | {{{ 827 | >>> list(rrule(WEEKLY, count=3, byweekno=52, byweekday=SU, 828 | dtstart=parse("19970902T090000"))) 829 | [datetime.datetime(1997, 12, 28, 9, 0), 830 | datetime.datetime(1998, 12, 27, 9, 0), 831 | datetime.datetime(2000, 1, 2, 9, 0)] 832 | }}} 833 | 834 | Only some years have week number 53: 835 | {{{ 836 | >>> list(rrule(WEEKLY, count=3, byweekno=53, byweekday=MO, 837 | dtstart=parse("19970902T090000"))) 838 | [datetime.datetime(1998, 12, 28, 9, 0), 839 | datetime.datetime(2004, 12, 27, 9, 0), 840 | datetime.datetime(2009, 12, 28, 9, 0)] 841 | }}} 842 | 843 | Every Friday the 13th, 4 occurrences. 844 | {{{ 845 | >>> list(rrule(YEARLY, count=4, byweekday=FR, bymonthday=13, 846 | dtstart=parse("19970902T090000"))) 847 | [datetime.datetime(1998, 2, 13, 9, 0), 848 | datetime.datetime(1998, 3, 13, 9, 0), 849 | datetime.datetime(1998, 11, 13, 9, 0), 850 | datetime.datetime(1999, 8, 13, 9, 0)] 851 | }}} 852 | 853 | Every four years, the first Tuesday after a Monday in November, 854 | 3 occurrences (U.S. Presidential Election day): 855 | {{{ 856 | >>> list(rrule(YEARLY, interval=4, count=3, bymonth=11, 857 | byweekday=TU, bymonthday=(2,3,4,5,6,7,8), 858 | dtstart=parse("19961105T090000"))) 859 | [datetime.datetime(1996, 11, 5, 9, 0), 860 | datetime.datetime(2000, 11, 7, 9, 0), 861 | datetime.datetime(2004, 11, 2, 9, 0)] 862 | }}} 863 | 864 | The 3rd instance into the month of one of Tuesday, Wednesday or 865 | Thursday, for the next 3 months: 866 | {{{ 867 | >>> list(rrule(MONTHLY, count=3, byweekday=(TU,WE,TH), 868 | bysetpos=3, dtstart=parse("19970904T090000"))) 869 | [datetime.datetime(1997, 9, 4, 9, 0), 870 | datetime.datetime(1997, 10, 7, 9, 0), 871 | datetime.datetime(1997, 11, 6, 9, 0)] 872 | }}} 873 | 874 | The 2nd to last weekday of the month, 3 occurrences. 875 | {{{ 876 | >>> list(rrule(MONTHLY, count=3, byweekday=(MO,TU,WE,TH,FR), 877 | bysetpos=-2, dtstart=parse("19970929T090000"))) 878 | [datetime.datetime(1997, 9, 29, 9, 0), 879 | datetime.datetime(1997, 10, 30, 9, 0), 880 | datetime.datetime(1997, 11, 27, 9, 0)] 881 | }}} 882 | 883 | Every 3 hours from 9:00 AM to 5:00 PM on a specific day. 884 | {{{ 885 | >>> list(rrule(HOURLY, interval=3, 886 | dtstart=parse("19970902T090000"), 887 | until=parse("19970902T170000"))) 888 | [datetime.datetime(1997, 9, 2, 9, 0), 889 | datetime.datetime(1997, 9, 2, 12, 0), 890 | datetime.datetime(1997, 9, 2, 15, 0)] 891 | }}} 892 | 893 | Every 15 minutes for 6 occurrences. 894 | {{{ 895 | >>> list(rrule(MINUTELY, interval=15, count=6, 896 | dtstart=parse("19970902T090000"))) 897 | [datetime.datetime(1997, 9, 2, 9, 0), 898 | datetime.datetime(1997, 9, 2, 9, 15), 899 | datetime.datetime(1997, 9, 2, 9, 30), 900 | datetime.datetime(1997, 9, 2, 9, 45), 901 | datetime.datetime(1997, 9, 2, 10, 0), 902 | datetime.datetime(1997, 9, 2, 10, 15)] 903 | }}} 904 | 905 | Every hour and a half for 4 occurrences. 906 | {{{ 907 | >>> list(rrule(MINUTELY, interval=90, count=4, 908 | dtstart=parse("19970902T090000"))) 909 | [datetime.datetime(1997, 9, 2, 9, 0), 910 | datetime.datetime(1997, 9, 2, 10, 30), 911 | datetime.datetime(1997, 9, 2, 12, 0), 912 | datetime.datetime(1997, 9, 2, 13, 30)] 913 | }}} 914 | 915 | Every 20 minutes from 9:00 AM to 4:40 PM for two days. 916 | {{{ 917 | >>> list(rrule(MINUTELY, interval=20, count=48, 918 | byhour=range(9,17), byminute=(0,20,40), 919 | dtstart=parse("19970902T090000"))) 920 | [datetime.datetime(1997, 9, 2, 9, 0), 921 | datetime.datetime(1997, 9, 2, 9, 20), 922 | (...) 923 | datetime.datetime(1997, 9, 2, 16, 20), 924 | datetime.datetime(1997, 9, 2, 16, 40), 925 | datetime.datetime(1997, 9, 3, 9, 0), 926 | datetime.datetime(1997, 9, 3, 9, 20), 927 | (...) 928 | datetime.datetime(1997, 9, 3, 16, 20), 929 | datetime.datetime(1997, 9, 3, 16, 40)] 930 | }}} 931 | 932 | An example where the days generated makes a difference because of {{{wkst}}}. 933 | {{{ 934 | >>> list(rrule(WEEKLY, interval=2, count=4, 935 | byweekday=(TU,SU), wkst=MO, 936 | dtstart=parse("19970805T090000"))) 937 | [datetime.datetime(1997, 8, 5, 9, 0), 938 | datetime.datetime(1997, 8, 10, 9, 0), 939 | datetime.datetime(1997, 8, 19, 9, 0), 940 | datetime.datetime(1997, 8, 24, 9, 0)] 941 | 942 | >>> list(rrule(WEEKLY, interval=2, count=4, 943 | byweekday=(TU,SU), wkst=SU, 944 | dtstart=parse("19970805T090000"))) 945 | [datetime.datetime(1997, 8, 5, 9, 0), 946 | datetime.datetime(1997, 8, 17, 9, 0), 947 | datetime.datetime(1997, 8, 19, 9, 0), 948 | datetime.datetime(1997, 8, 31, 9, 0)] 949 | }}} 950 | 951 | ==== rruleset type ==== 952 | The {{{rruleset}}} type allows more complex recurrence setups, mixing 953 | multiple rules, dates, exclusion rules, and exclusion dates. 954 | The type constructor takes the following keyword arguments: 955 | 956 | cache:: 957 | If True, caching of results will be enabled, improving performance 958 | of multiple queries considerably. 959 | 960 | ==== rruleset methods ==== 961 | The following methods are available: 962 | 963 | rruleset.rrule(rrule):: 964 | Include the given {{{rrule}}} instance in the recurrence set 965 | generation. 966 | 967 | rruleset.rdate(dt):: 968 | Include the given {{{datetime}}} instance in the recurrence 969 | set generation. 970 | 971 | rruleset.exrule(rrule):: 972 | Include the given {{{rrule}}} instance in the recurrence set 973 | exclusion list. Dates which are part of the given recurrence 974 | rules will not be generated, even if some inclusive {{{rrule}}} 975 | or {{{rdate}}} matches them. 976 | 977 | rruleset.exdate(dt):: 978 | Include the given {{{datetime}}} instance in the recurrence set 979 | exclusion list. Dates included that way will not be generated, 980 | even if some inclusive {{{rrule}}} or {{{rdate}}} matches them. 981 | 982 | rruleset.before(dt, inc=False):: 983 | Returns the last recurrence before the given {{{datetime}}} 984 | instance. The {{{inc}}} keyword defines what happens if 985 | {{{dt}}} '''is''' an occurrence. With {{{inc == True}}}, 986 | if {{{dt}}} itself is an occurrence, it will be returned. 987 | 988 | rruleset.after(dt, inc=False):: 989 | Returns the first recurrence after the given {{{datetime}}} 990 | instance. The {{{inc}}} keyword defines what happens if 991 | {{{dt}}} '''is''' an occurrence. With {{{inc == True}}}, 992 | if {{{dt}}} itself is an occurrence, it will be returned. 993 | 994 | rruleset.between(after, before, inc=False):: 995 | Returns all the occurrences of the rrule between {{{after}}} 996 | and {{{before}}}. The {{{inc}}} keyword defines what happens 997 | if {{{after}}} and/or {{{before}}} are themselves occurrences. 998 | With {{{inc == True}}}, they will be included in the list, 999 | if they are found in the recurrence set. 1000 | 1001 | rruleset.count():: 1002 | Returns the number of recurrences in this set. It will have 1003 | go trough the whole recurrence, if this hasn't been done 1004 | before. 1005 | 1006 | Besides these methods, {{{rruleset}}} instances also support 1007 | the {{{__getitem__()}}} and {{{__contains__()}}} special methods, 1008 | meaning that these are valid expressions: 1009 | {{{ 1010 | set = rruleset(...) 1011 | if datetime(...) in set: 1012 | ... 1013 | print set[0] 1014 | print set[-1] 1015 | print set[1:2] 1016 | print set[::-2] 1017 | }}} 1018 | 1019 | The getitem/slicing mechanism is smart enough to avoid getting the whole 1020 | recurrence set, if possible. 1021 | 1022 | ==== rruleset examples ==== 1023 | Daily, for 7 days, jumping Saturday and Sunday occurrences. 1024 | {{{ 1025 | >>> set = rruleset() 1026 | >>> set.rrule(rrule(DAILY, count=7, 1027 | dtstart=parse("19970902T090000"))) 1028 | >>> set.exrule(rrule(YEARLY, byweekday=(SA,SU), 1029 | dtstart=parse("19970902T090000"))) 1030 | >>> list(set) 1031 | [datetime.datetime(1997, 9, 2, 9, 0), 1032 | datetime.datetime(1997, 9, 3, 9, 0), 1033 | datetime.datetime(1997, 9, 4, 9, 0), 1034 | datetime.datetime(1997, 9, 5, 9, 0), 1035 | datetime.datetime(1997, 9, 8, 9, 0)] 1036 | }}} 1037 | 1038 | Weekly, for 4 weeks, plus one time on day 7, and not on day 16. 1039 | {{{ 1040 | >>> set = rruleset() 1041 | >>> set.rrule(rrule(WEEKLY, count=4, 1042 | dtstart=parse("19970902T090000"))) 1043 | >>> set.rdate(datetime.datetime(1997, 9, 7, 9, 0)) 1044 | >>> set.exdate(datetime.datetime(1997, 9, 16, 9, 0)) 1045 | >>> list(set) 1046 | [datetime.datetime(1997, 9, 2, 9, 0), 1047 | datetime.datetime(1997, 9, 7, 9, 0), 1048 | datetime.datetime(1997, 9, 9, 9, 0), 1049 | datetime.datetime(1997, 9, 23, 9, 0)] 1050 | }}} 1051 | 1052 | ==== rrulestr() function ==== 1053 | The {{{rrulestr()}}} function is a parser for ''RFC-like'' syntaxes. 1054 | The function prototype is: 1055 | {{{ 1056 | rrulestr(str) 1057 | }}} 1058 | 1059 | The string passed as parameter may be a multiple line string, a 1060 | single line string, or just the {{{RRULE}}} property value. 1061 | 1062 | Additionally, it accepts the following keyword arguments: 1063 | 1064 | cache:: 1065 | If {{{True}}}, the {{{rruleset}}} or {{{rrule}}} created instance 1066 | will cache its results. Default is not to cache. 1067 | 1068 | dtstart:: 1069 | If given, it must be a {{{datetime}}} instance that will be used 1070 | when no {{{DTSTART}}} property is found in the parsed string. If 1071 | it is not given, and the property is not found, {{{datetime.now()}}} 1072 | will be used instead. 1073 | 1074 | unfold:: 1075 | If set to {{{True}}}, lines will be unfolded following the RFC 1076 | specification. It defaults to {{{False}}}, meaning that spaces 1077 | before every line will be stripped. 1078 | 1079 | forceset:: 1080 | If set to {{{True}}} a {{{rruleset}}} instance will be returned, 1081 | even if only a single rule is found. The default is to return an 1082 | {{{rrule}}} if possible, and an {{{rruleset}}} if necessary. 1083 | 1084 | compatible:: 1085 | If set to {{{True}}}, the parser will operate in RFC-compatible 1086 | mode. Right now it means that {{{unfold}}} will be turned on, 1087 | and if a {{{DTSTART}}} is found, it will be considered the first 1088 | recurrence instance, as documented in the RFC. 1089 | 1090 | ignoretz:: 1091 | If set to {{{True}}}, the date parser will ignore timezone 1092 | information available in the {{{DTSTART}}} property, or the 1093 | {{{UNTIL}}} attribute. 1094 | 1095 | tzinfos:: 1096 | If set, it will be passed to the datetime string parser to 1097 | resolve unknown timezone settings. For more information about 1098 | what could be used here, check the parser documentation. 1099 | 1100 | ==== rrulestr() examples ==== 1101 | 1102 | Every 10 days, 5 occurrences. 1103 | {{{ 1104 | >>> list(rrulestr(""" 1105 | ... DTSTART:19970902T090000 1106 | ... RRULE:FREQ=DAILY;INTERVAL=10;COUNT=5 1107 | ... """)) 1108 | [datetime.datetime(1997, 9, 2, 9, 0), 1109 | datetime.datetime(1997, 9, 12, 9, 0), 1110 | datetime.datetime(1997, 9, 22, 9, 0), 1111 | datetime.datetime(1997, 10, 2, 9, 0), 1112 | datetime.datetime(1997, 10, 12, 9, 0)] 1113 | }}} 1114 | 1115 | Same thing, but passing only the {{{RRULE}}} value. 1116 | {{{ 1117 | >>> list(rrulestr("FREQ=DAILY;INTERVAL=10;COUNT=5", 1118 | dtstart=parse("19970902T090000"))) 1119 | [datetime.datetime(1997, 9, 2, 9, 0), 1120 | datetime.datetime(1997, 9, 12, 9, 0), 1121 | datetime.datetime(1997, 9, 22, 9, 0), 1122 | datetime.datetime(1997, 10, 2, 9, 0), 1123 | datetime.datetime(1997, 10, 12, 9, 0)] 1124 | }}} 1125 | 1126 | Notice that when using a single rule, it returns an 1127 | {{{rrule}}} instance, unless {{{forceset}}} was used. 1128 | {{{ 1129 | >>> rrulestr("FREQ=DAILY;INTERVAL=10;COUNT=5") 1130 | 1131 | 1132 | >>> rrulestr(""" 1133 | ... DTSTART:19970902T090000 1134 | ... RRULE:FREQ=DAILY;INTERVAL=10;COUNT=5 1135 | ... """) 1136 | 1137 | 1138 | >>> rrulestr("FREQ=DAILY;INTERVAL=10;COUNT=5", forceset=True) 1139 | 1140 | }}} 1141 | 1142 | But when an {{{rruleset}}} is needed, it is automatically used. 1143 | {{{ 1144 | >>> rrulestr(""" 1145 | ... DTSTART:19970902T090000 1146 | ... RRULE:FREQ=DAILY;INTERVAL=10;COUNT=5 1147 | ... RRULE:FREQ=DAILY;INTERVAL=5;COUNT=3 1148 | ... """) 1149 | 1150 | }}} 1151 | 1152 | === parser === 1153 | This module offers a generic date/time string parser which is 1154 | able to parse most known formats to represent a date and/or 1155 | time. 1156 | 1157 | ==== parse() function ==== 1158 | That's probably the only function you'll need from this module. 1159 | It offers you an interface to access the parser functionality and 1160 | extract a {{{datetime}}} type out of a string. 1161 | 1162 | The prototype of this function is: 1163 | {{{ 1164 | parse(timestr) 1165 | }}} 1166 | 1167 | Additionally, the following keyword arguments are available: 1168 | 1169 | default:: 1170 | If given, this must be a {{{datetime}}} instance. Any fields 1171 | missing in the parsed date will be copied from this instance. 1172 | The default value is the current date, at 00:00:00am. 1173 | 1174 | ignoretz:: 1175 | If this is true, even if a timezone is found in the string, 1176 | the parser will not use it. 1177 | 1178 | tzinfos:: 1179 | Using this keyword argument you may provide custom timezones 1180 | to the parser. If given, it must be either a dictionary with 1181 | the timezone abbreviation as key, or a function accepting a 1182 | timezone abbreviation and offset as argument. The dictionary 1183 | values and the function return must be a timezone offset 1184 | in seconds, a tzinfo subclass, or a string defining the 1185 | timezone (in the TZ environment variable format). 1186 | 1187 | dayfirst:: 1188 | This option allow one to change the precedence in which 1189 | days are parsed in date strings. The default is given in the 1190 | parserinfo instance (the default parserinfo has it set to 1191 | False). If {{{dayfirst}}} is False, the {{{MM-DD-YYYY}}} 1192 | format will have precedence over {{{DD-MM-YYYY}}} in an 1193 | ambiguous date. 1194 | 1195 | yearfirst:: 1196 | This option allow one to change the precedence in which 1197 | years are parsed in date strings. The default is given in 1198 | the parserinfo instance (the default parserinfo has it set 1199 | to False). If {{{yearfirst}}} is false, the {{{MM-DD-YY}}} 1200 | format will have precedence over {{{YY-MM-DD}}} in an 1201 | ambiguous date. 1202 | 1203 | fuzzy:: 1204 | If {{{fuzzy}}} is set to True, unknown tokens in the string 1205 | will be ignored. 1206 | 1207 | parserinfo:: 1208 | This parameter allows one to change how the string is parsed, 1209 | by using a different parserinfo class instance. Using it you 1210 | may, for example, intenationalize the parser strings, or make 1211 | it ignore additional words. 1212 | 1213 | ==== Format precedence ==== 1214 | Whenever an ambiguous date is found, the {{{dayfirst}}} and 1215 | {{{yearfirst}}} parameters will control how the information 1216 | is processed. Here is the precedence in each case: 1217 | 1218 | If {{{dayfirst}}} is {{{False}}} and {{{yearfirst}}} is {{{False}}}, 1219 | (default, if no parameter is given): 1220 | 1221 | * {{{MM-DD-YY}}} 1222 | * {{{DD-MM-YY}}} 1223 | * {{{YY-MM-DD}}} 1224 | 1225 | If {{{dayfirst}}} is {{{True}}} and {{{yearfirst}}} is {{{False}}}: 1226 | 1227 | * {{{DD-MM-YY}}} 1228 | * {{{MM-DD-YY}}} 1229 | * {{{YY-MM-DD}}} 1230 | 1231 | If {{{dayfirst}}} is {{{False}}} and {{{yearfirst}}} is {{{True}}}: 1232 | 1233 | * {{{YY-MM-DD}}} 1234 | * {{{MM-DD-YY}}} 1235 | * {{{DD-MM-YY}}} 1236 | 1237 | If {{{dayfirst}}} is {{{True}}} and {{{yearfirst}}} is {{{True}}}: 1238 | 1239 | * {{{YY-MM-DD}}} 1240 | * {{{DD-MM-YY}}} 1241 | * {{{MM-DD-YY}}} 1242 | 1243 | ==== Converting two digit years ==== 1244 | When a two digit year is found, it is processed considering 1245 | the current year, so that the computed year is never more 1246 | than 49 years after the current year, nor 50 years before the 1247 | current year. In other words, if we are in year 2003, and the 1248 | year 30 is found, it will be considered as 2030, but if the 1249 | year 60 is found, it will be considered 1960. 1250 | 1251 | ==== Examples ==== 1252 | The following code will prepare the environment: 1253 | {{{ 1254 | >>> from dateutil.parser import * 1255 | >>> from dateutil.tz import * 1256 | >>> from datetime import * 1257 | >>> TZOFFSETS = {"BRST": -10800} 1258 | >>> BRSTTZ = tzoffset(-10800, "BRST") 1259 | >>> DEFAULT = datetime(2003, 9, 25) 1260 | }}} 1261 | 1262 | Some simple examples based on the {{{date}}} command, using the 1263 | {{{TZOFFSET}}} dictionary to provide the BRST timezone offset. 1264 | {{{ 1265 | >>> parse("Thu Sep 25 10:36:28 BRST 2003", tzinfos=TZOFFSETS) 1266 | datetime.datetime(2003, 9, 25, 10, 36, 28, 1267 | tzinfo=tzoffset('BRST', -10800)) 1268 | 1269 | >>> parse("2003 10:36:28 BRST 25 Sep Thu", tzinfos=TZOFFSETS) 1270 | datetime.datetime(2003, 9, 25, 10, 36, 28, 1271 | tzinfo=tzoffset('BRST', -10800)) 1272 | }}} 1273 | 1274 | Notice that since BRST is my local timezone, parsing it without 1275 | further timezone settings will yield a {{{tzlocal}}} timezone. 1276 | {{{ 1277 | >>> parse("Thu Sep 25 10:36:28 BRST 2003") 1278 | datetime.datetime(2003, 9, 25, 10, 36, 28, tzinfo=tzlocal()) 1279 | }}} 1280 | 1281 | We can also ask to ignore the timezone explicitly: 1282 | {{{ 1283 | >>> parse("Thu Sep 25 10:36:28 BRST 2003", ignoretz=True) 1284 | datetime.datetime(2003, 9, 25, 10, 36, 28) 1285 | }}} 1286 | 1287 | That's the same as processing a string without timezone: 1288 | {{{ 1289 | >>> parse("Thu Sep 25 10:36:28 2003") 1290 | datetime.datetime(2003, 9, 25, 10, 36, 28) 1291 | }}} 1292 | 1293 | Without the year, but passing our {{{DEFAULT}}} datetime to return 1294 | the same year, no mattering what year we currently are in: 1295 | {{{ 1296 | >>> parse("Thu Sep 25 10:36:28", default=DEFAULT) 1297 | datetime.datetime(2003, 9, 25, 10, 36, 28) 1298 | }}} 1299 | 1300 | Strip it further: 1301 | {{{ 1302 | >>> parse("Thu Sep 10:36:28", default=DEFAULT) 1303 | datetime.datetime(2003, 9, 25, 10, 36, 28) 1304 | 1305 | >>> parse("Thu 10:36:28", default=DEFAULT) 1306 | datetime.datetime(2003, 9, 25, 10, 36, 28) 1307 | 1308 | >>> parse("Thu 10:36", default=DEFAULT) 1309 | datetime.datetime(2003, 9, 25, 10, 36) 1310 | 1311 | >>> parse("10:36", default=DEFAULT) 1312 | datetime.datetime(2003, 9, 25, 10, 36) 1313 | >>> 1314 | }}} 1315 | 1316 | Strip in a different way: 1317 | {{{ 1318 | >>> parse("Thu Sep 25 2003") 1319 | datetime.datetime(2003, 9, 25, 0, 0) 1320 | 1321 | >>> parse("Sep 25 2003") 1322 | datetime.datetime(2003, 9, 25, 0, 0) 1323 | 1324 | >>> parse("Sep 2003", default=DEFAULT) 1325 | datetime.datetime(2003, 9, 25, 0, 0) 1326 | 1327 | >>> parse("Sep", default=DEFAULT) 1328 | datetime.datetime(2003, 9, 25, 0, 0) 1329 | 1330 | >>> parse("2003", default=DEFAULT) 1331 | datetime.datetime(2003, 9, 25, 0, 0) 1332 | }}} 1333 | 1334 | Another format, based on {{{date -R}}} (RFC822): 1335 | {{{ 1336 | >>> parse("Thu, 25 Sep 2003 10:49:41 -0300") 1337 | datetime.datetime(2003, 9, 25, 10, 49, 41, 1338 | tzinfo=tzoffset(None, -10800)) 1339 | }}} 1340 | 1341 | ISO format: 1342 | {{{ 1343 | >>> parse("2003-09-25T10:49:41.5-03:00") 1344 | datetime.datetime(2003, 9, 25, 10, 49, 41, 500000, 1345 | tzinfo=tzoffset(None, -10800)) 1346 | }}} 1347 | 1348 | Some variations: 1349 | {{{ 1350 | >>> parse("2003-09-25T10:49:41") 1351 | datetime.datetime(2003, 9, 25, 10, 49, 41) 1352 | 1353 | >>> parse("2003-09-25T10:49") 1354 | datetime.datetime(2003, 9, 25, 10, 49) 1355 | 1356 | >>> parse("2003-09-25T10") 1357 | datetime.datetime(2003, 9, 25, 10, 0) 1358 | 1359 | >>> parse("2003-09-25") 1360 | datetime.datetime(2003, 9, 25, 0, 0) 1361 | }}} 1362 | 1363 | ISO format, without separators: 1364 | {{{ 1365 | >>> parse("20030925T104941.5-0300") 1366 | datetime.datetime(2003, 9, 25, 10, 49, 41, 500000, 1367 | tzinfo=tzinfo=tzoffset(None, -10800)) 1368 | 1369 | >>> parse("20030925T104941-0300") 1370 | datetime.datetime(2003, 9, 25, 10, 49, 41, 1371 | tzinfo=tzoffset(None, -10800)) 1372 | 1373 | >>> parse("20030925T104941") 1374 | datetime.datetime(2003, 9, 25, 10, 49, 41) 1375 | 1376 | >>> parse("20030925T1049") 1377 | datetime.datetime(2003, 9, 25, 10, 49) 1378 | 1379 | >>> parse("20030925T10") 1380 | datetime.datetime(2003, 9, 25, 10, 0) 1381 | 1382 | >>> parse("20030925") 1383 | datetime.datetime(2003, 9, 25, 0, 0) 1384 | }}} 1385 | 1386 | Everything together. 1387 | {{{ 1388 | >>> parse("199709020900") 1389 | datetime.datetime(1997, 9, 2, 9, 0) 1390 | >>> parse("19970902090059") 1391 | datetime.datetime(1997, 9, 2, 9, 0, 59) 1392 | }}} 1393 | 1394 | Different date orderings: 1395 | {{{ 1396 | >>> parse("2003-09-25") 1397 | datetime.datetime(2003, 9, 25, 0, 0) 1398 | 1399 | >>> parse("2003-Sep-25") 1400 | datetime.datetime(2003, 9, 25, 0, 0) 1401 | 1402 | >>> parse("25-Sep-2003") 1403 | datetime.datetime(2003, 9, 25, 0, 0) 1404 | 1405 | >>> parse("Sep-25-2003") 1406 | datetime.datetime(2003, 9, 25, 0, 0) 1407 | 1408 | >>> parse("09-25-2003") 1409 | datetime.datetime(2003, 9, 25, 0, 0) 1410 | 1411 | >>> parse("25-09-2003") 1412 | datetime.datetime(2003, 9, 25, 0, 0) 1413 | }}} 1414 | 1415 | Check some ambiguous dates: 1416 | {{{ 1417 | >>> parse("10-09-2003") 1418 | datetime.datetime(2003, 10, 9, 0, 0) 1419 | 1420 | >>> parse("10-09-2003", dayfirst=True) 1421 | datetime.datetime(2003, 9, 10, 0, 0) 1422 | 1423 | >>> parse("10-09-03") 1424 | datetime.datetime(2003, 10, 9, 0, 0) 1425 | 1426 | >>> parse("10-09-03", yearfirst=True) 1427 | datetime.datetime(2010, 9, 3, 0, 0) 1428 | }}} 1429 | 1430 | Other date separators are allowed: 1431 | {{{ 1432 | >>> parse("2003.Sep.25") 1433 | datetime.datetime(2003, 9, 25, 0, 0) 1434 | 1435 | >>> parse("2003/09/25") 1436 | datetime.datetime(2003, 9, 25, 0, 0) 1437 | }}} 1438 | 1439 | Even with spaces: 1440 | {{{ 1441 | >>> parse("2003 Sep 25") 1442 | datetime.datetime(2003, 9, 25, 0, 0) 1443 | 1444 | >>> parse("2003 09 25") 1445 | datetime.datetime(2003, 9, 25, 0, 0) 1446 | }}} 1447 | 1448 | Hours with letters work: 1449 | {{{ 1450 | >>> parse("10h36m28.5s", default=DEFAULT) 1451 | datetime.datetime(2003, 9, 25, 10, 36, 28, 500000) 1452 | 1453 | >>> parse("01s02h03m", default=DEFAULT) 1454 | datetime.datetime(2003, 9, 25, 2, 3, 1) 1455 | 1456 | >>> parse("01h02m03", default=DEFAULT) 1457 | datetime.datetime(2003, 9, 3, 1, 2) 1458 | 1459 | >>> parse("01h02", default=DEFAULT) 1460 | datetime.datetime(2003, 9, 2, 1, 0) 1461 | 1462 | >>> parse("01h02s", default=DEFAULT) 1463 | datetime.datetime(2003, 9, 25, 1, 0, 2) 1464 | }}} 1465 | 1466 | With AM/PM: 1467 | {{{ 1468 | >>> parse("10h am", default=DEFAULT) 1469 | datetime.datetime(2003, 9, 25, 10, 0) 1470 | 1471 | >>> parse("10pm", default=DEFAULT) 1472 | datetime.datetime(2003, 9, 25, 22, 0) 1473 | 1474 | >>> parse("12:00am", default=DEFAULT) 1475 | datetime.datetime(2003, 9, 25, 0, 0) 1476 | 1477 | >>> parse("12pm", default=DEFAULT) 1478 | datetime.datetime(2003, 9, 25, 12, 0) 1479 | }}} 1480 | 1481 | Some special treating for ''pertain'' relations: 1482 | {{{ 1483 | >>> parse("Sep 03", default=DEFAULT) 1484 | datetime.datetime(2003, 9, 3, 0, 0) 1485 | 1486 | >>> parse("Sep of 03", default=DEFAULT) 1487 | datetime.datetime(2003, 9, 25, 0, 0) 1488 | }}} 1489 | 1490 | Fuzzy parsing: 1491 | {{{ 1492 | >>> s = "Today is 25 of September of 2003, exactly " \ 1493 | ... "at 10:49:41 with timezone -03:00." 1494 | >>> parse(s, fuzzy=True) 1495 | datetime.datetime(2003, 9, 25, 10, 49, 41, 1496 | tzinfo=tzoffset(None, -10800)) 1497 | }}} 1498 | 1499 | Other random formats: 1500 | {{{ 1501 | >>> parse("Wed, July 10, '96") 1502 | datetime.datetime(1996, 7, 10, 0, 0) 1503 | 1504 | >>> parse("1996.07.10 AD at 15:08:56 PDT", ignoretz=True) 1505 | datetime.datetime(1996, 7, 10, 15, 8, 56) 1506 | 1507 | >>> parse("Tuesday, April 12, 1952 AD 3:30:42pm PST", ignoretz=True) 1508 | datetime.datetime(1952, 4, 12, 15, 30, 42) 1509 | 1510 | >>> parse("November 5, 1994, 8:15:30 am EST", ignoretz=True) 1511 | datetime.datetime(1994, 11, 5, 8, 15, 30) 1512 | 1513 | >>> parse("3rd of May 2001") 1514 | datetime.datetime(2001, 5, 3, 0, 0) 1515 | 1516 | >>> parse("5:50 A.M. on June 13, 1990") 1517 | datetime.datetime(1990, 6, 13, 5, 50) 1518 | }}} 1519 | 1520 | === easter === 1521 | This module offers a generic easter computing method for 1522 | any given year, using Western, Orthodox or Julian algorithms. 1523 | 1524 | ==== easter() function ==== 1525 | This method was ported from the work done by 1526 | [http://users.chariot.net.au/~gmarts/eastalg.htm GM Arts], 1527 | on top of the algorithm by 1528 | [http://www.tondering.dk/claus/calendar.html Claus Tondering], 1529 | which was based in part on the algorithm of Ouding (1940), 1530 | as quoted in "Explanatory Supplement to the Astronomical 1531 | Almanac", P. Kenneth Seidelmann, editor. 1532 | 1533 | This algorithm implements three different easter 1534 | calculation methods: 1535 | 1536 | 1. Original calculation in Julian calendar, valid in 1537 | dates after 326 AD 1538 | 1. Original method, with date converted to Gregorian 1539 | calendar, valid in years 1583 to 4099 1540 | 1. Revised method, in Gregorian calendar, valid in 1541 | years 1583 to 4099 as well 1542 | 1543 | These methods are represented by the constants: 1544 | {{{ 1545 | EASTER_JULIAN = 1 1546 | EASTER_ORTHODOX = 2 1547 | EASTER_WESTERN = 3 1548 | }}} 1549 | 1550 | The default method is method 3. 1551 | 1552 | === tz === 1553 | This module offers timezone implementations subclassing 1554 | the abstract {{{datetime.tzinfo}}} type. There are 1555 | classes to handle [http://www.twinsun.com/tz/tz-link.htm tzfile] 1556 | format files (usually are in /etc/localtime, 1557 | /usr/share/zoneinfo, etc), TZ environment string (in all 1558 | known formats), given ranges (with help from relative 1559 | deltas), local machine timezone, fixed offset timezone, 1560 | and UTC timezone. 1561 | 1562 | ==== tzutc type ==== 1563 | This type implements a basic UTC timezone. The constructor of this 1564 | type accepts no parameters. 1565 | 1566 | ==== tzutc examples ==== 1567 | {{{ 1568 | >>> from datetime import * 1569 | >>> from dateutil.tz import * 1570 | 1571 | >>> datetime.now() 1572 | datetime.datetime(2003, 9, 27, 9, 40, 1, 521290) 1573 | 1574 | >>> datetime.now(tzutc()) 1575 | datetime.datetime(2003, 9, 27, 12, 40, 12, 156379, tzinfo=tzutc()) 1576 | 1577 | >>> datetime.now(tzutc()).tzname() 1578 | 'UTC' 1579 | }}} 1580 | 1581 | ==== tzoffset type ==== 1582 | This type implements a fixed offset timezone, with no 1583 | support to daylight saving times. Here is the prototype of the 1584 | type constructor: 1585 | {{{ 1586 | tzoffset(name, offset) 1587 | }}} 1588 | 1589 | The {{{name}}} parameter may be optionally set to {{{None}}}, and 1590 | {{{offset}}} must be given in seconds. 1591 | 1592 | ==== tzoffset examples ==== 1593 | {{{ 1594 | >>> from datetime import * 1595 | >>> from dateutil.tz import * 1596 | 1597 | >>> datetime.now(tzoffset("BRST", -10800)) 1598 | datetime.datetime(2003, 9, 27, 9, 52, 43, 624904, 1599 | tzinfo=tzinfo=tzoffset('BRST', -10800)) 1600 | 1601 | >>> datetime.now(tzoffset("BRST", -10800)).tzname() 1602 | 'BRST' 1603 | 1604 | >>> datetime.now(tzoffset("BRST", -10800)).astimezone(tzutc()) 1605 | datetime.datetime(2003, 9, 27, 12, 53, 11, 446419, 1606 | tzinfo=tzutc()) 1607 | }}} 1608 | 1609 | ==== tzlocal type ==== 1610 | This type implements timezone settings as known by the 1611 | operating system. The constructor of this type accepts no 1612 | parameters. 1613 | 1614 | ==== tzlocal examples ==== 1615 | {{{ 1616 | >>> from datetime import * 1617 | >>> from dateutil.tz import * 1618 | 1619 | >>> datetime.now(tzlocal()) 1620 | datetime.datetime(2003, 9, 27, 10, 1, 43, 673605, 1621 | tzinfo=tzlocal()) 1622 | 1623 | >>> datetime.now(tzlocal()).tzname() 1624 | 'BRST' 1625 | 1626 | >>> datetime.now(tzlocal()).astimezone(tzoffset(None, 0)) 1627 | datetime.datetime(2003, 9, 27, 13, 3, 0, 11493, 1628 | tzinfo=tzoffset(None, 0)) 1629 | }}} 1630 | 1631 | ==== tzstr type ==== 1632 | This type implements timezone settings extracted from a 1633 | string in known TZ environment variable formats. Here is the prototype 1634 | of the constructor: 1635 | {{{ 1636 | tzstr(str) 1637 | }}} 1638 | 1639 | ==== tzstr examples ==== 1640 | Here are examples of the recognized formats: 1641 | 1642 | * {{{EST5EDT}}} 1643 | * {{{EST5EDT,4,0,6,7200,10,0,26,7200,3600}}} 1644 | * {{{EST5EDT,4,1,0,7200,10,-1,0,7200,3600}}} 1645 | * {{{EST5EDT4,M4.1.0/02:00:00,M10-5-0/02:00}}} 1646 | * {{{EST5EDT4,95/02:00:00,298/02:00}}} 1647 | * {{{EST5EDT4,J96/02:00:00,J299/02:00}}} 1648 | 1649 | Notice that if daylight information is not present, but a 1650 | daylight abbreviation was provided, {{{tzstr}}} will follow the 1651 | convention of using the first sunday of April to start daylight 1652 | saving, and the last sunday of October to end it. If start or 1653 | end time is not present, 2AM will be used, and if the daylight 1654 | offset is not present, the standard offset plus one hour will 1655 | be used. This convention is the same as used in the GNU libc. 1656 | 1657 | This also means that some of the above examples are exactly 1658 | equivalent, and all of these examples are equivalent 1659 | in the year of 2003. 1660 | 1661 | Here is the example mentioned in the 1662 | [http://www.python.org/doc/current/lib/module-time.html time module documentation]. 1663 | {{{ 1664 | >>> os.environ['TZ'] = 'EST+05EDT,M4.1.0,M10.5.0' 1665 | >>> time.tzset() 1666 | >>> time.strftime('%X %x %Z') 1667 | '02:07:36 05/08/03 EDT' 1668 | >>> os.environ['TZ'] = 'AEST-10AEDT-11,M10.5.0,M3.5.0' 1669 | >>> time.tzset() 1670 | >>> time.strftime('%X %x %Z') 1671 | '16:08:12 05/08/03 AEST' 1672 | }}} 1673 | 1674 | And here is an example showing the same information using {{{tzstr}}}, 1675 | without touching system settings. 1676 | {{{ 1677 | >>> tz1 = tzstr('EST+05EDT,M4.1.0,M10.5.0') 1678 | >>> tz2 = tzstr('AEST-10AEDT-11,M10.5.0,M3.5.0') 1679 | >>> dt = datetime(2003, 5, 8, 2, 7, 36, tzinfo=tz1) 1680 | >>> dt.strftime('%X %x %Z') 1681 | '02:07:36 05/08/03 EDT' 1682 | >>> dt.astimezone(tz2).strftime('%X %x %Z') 1683 | '16:07:36 05/08/03 AEST' 1684 | }}} 1685 | 1686 | Are these really equivalent? 1687 | {{{ 1688 | >>> tzstr('EST5EDT') == tzstr('EST5EDT,4,1,0,7200,10,-1,0,7200,3600') 1689 | True 1690 | }}} 1691 | 1692 | Check the daylight limit. 1693 | {{{ 1694 | >>> datetime(2003, 4, 6, 1, 59, tzinfo=tz).tzname() 1695 | 'EST' 1696 | >>> datetime(2003, 4, 6, 2, 00, tzinfo=tz).tzname() 1697 | 'EDT' 1698 | >>> datetime(2003, 10, 26, 0, 59, tzinfo=tz).tzname() 1699 | 'EDT' 1700 | >>> datetime(2003, 10, 26, 1, 00, tzinfo=tz).tzname() 1701 | 'EST' 1702 | }}} 1703 | 1704 | ==== tzrange type ==== 1705 | This type offers the same functionality as the {{{tzstr}}} type, but 1706 | instead of timezone strings, information is passed using 1707 | {{{relativedelta}}}s which are applied to a datetime set to the first 1708 | day of the year. Here is the prototype of this type's constructor: 1709 | {{{ 1710 | tzrange(stdabbr, stdoffset=None, dstabbr=None, dstoffset=None, 1711 | start=None, end=None): 1712 | }}} 1713 | 1714 | Offsets must be given in seconds. Information not provided will be 1715 | set to the defaults, as explained in the {{{tzstr}}} section above. 1716 | 1717 | ==== tzrange examples ==== 1718 | {{{ 1719 | >>> tzstr('EST5EDT') == tzrange("EST", -18000, "EDT") 1720 | True 1721 | 1722 | >>> from dateutil.relativedelta import * 1723 | >>> range1 = tzrange("EST", -18000, "EDT") 1724 | >>> range2 = tzrange("EST", -18000, "EDT", -14400, 1725 | ... relativedelta(hours=+2, month=4, day=1, 1726 | weekday=SU(+1)), 1727 | ... relativedelta(hours=+1, month=10, day=31, 1728 | weekday=SU(-1))) 1729 | >>> tzstr('EST5EDT') == range1 == range2 1730 | True 1731 | }}} 1732 | 1733 | Notice a minor detail in the last example: while the DST should end 1734 | at 2AM, the delta will catch 1AM. That's because the daylight saving 1735 | time should end at 2AM standard time (the difference between STD and 1736 | DST is 1h in the given example) instead of the DST time. That's how 1737 | the {{{tzinfo}}} subtypes should deal with the extra hour that happens 1738 | when going back to the standard time. Check 1739 | [http://www.python.org/doc/current/lib/datetime-tzinfo.html tzinfo documentation] 1740 | for more information. 1741 | 1742 | ==== tzfile type ==== 1743 | This type allows one to use tzfile(5) format timezone files to extract 1744 | current and historical zone information. Here is the type constructor 1745 | prototype: 1746 | {{{ 1747 | tzfile(fileobj) 1748 | }}} 1749 | 1750 | Where {{{fileobj}}} is either a filename or a file-like object with 1751 | a {{{read()}}} method. 1752 | 1753 | ==== tzfile examples ==== 1754 | {{{ 1755 | >>> tz = tzfile("/etc/localtime") 1756 | >>> datetime.now(tz) 1757 | datetime.datetime(2003, 9, 27, 12, 3, 48, 392138, 1758 | tzinfo=tzfile('/etc/localtime')) 1759 | 1760 | >>> datetime.now(tz).astimezone(tzutc()) 1761 | datetime.datetime(2003, 9, 27, 15, 3, 53, 70863, 1762 | tzinfo=tzutc()) 1763 | 1764 | >>> datetime.now(tz).tzname() 1765 | 'BRST' 1766 | >>> datetime(2003, 1, 1, tzinfo=tz).tzname() 1767 | 'BRDT' 1768 | }}} 1769 | 1770 | Check the daylight limit. 1771 | {{{ 1772 | >>> tz = tzfile('/usr/share/zoneinfo/EST5EDT') 1773 | >>> datetime(2003, 4, 6, 1, 59, tzinfo=tz).tzname() 1774 | 'EST' 1775 | >>> datetime(2003, 4, 6, 2, 00, tzinfo=tz).tzname() 1776 | 'EDT' 1777 | >>> datetime(2003, 10, 26, 0, 59, tzinfo=tz).tzname() 1778 | 'EDT' 1779 | >>> datetime(2003, 10, 26, 1, 00, tzinfo=tz).tzname() 1780 | 'EST' 1781 | }}} 1782 | 1783 | ==== tzical type ==== 1784 | This type is able to parse 1785 | [ftp://ftp.rfc-editor.org/in-notes/rfc2445.txt iCalendar] 1786 | style {{{VTIMEZONE}}} sessions into a Python timezone object. 1787 | The constuctor prototype is: 1788 | {{{ 1789 | tzical(fileobj) 1790 | }}} 1791 | 1792 | Where {{{fileobj}}} is either a filename or a file-like object with 1793 | a {{{read()}}} method. 1794 | 1795 | ==== tzical methods ==== 1796 | 1797 | tzical.get(tzid=None):: 1798 | Since a single iCalendar file may contain more than one timezone, 1799 | you must ask for the timezone you want with this method. If there's 1800 | more than one timezone in the parsed file, you'll need to pass the 1801 | {{{tzid}}} parameter. Otherwise, leaving it empty will yield the only 1802 | available timezone. 1803 | 1804 | ==== tzical examples ==== 1805 | Here is a sample file extracted from the RFC. This file defines 1806 | the {{{EST5EDT}}} timezone, and will be used in the following example. 1807 | {{{ 1808 | BEGIN:VTIMEZONE 1809 | TZID:US-Eastern 1810 | LAST-MODIFIED:19870101T000000Z 1811 | TZURL:http://zones.stds_r_us.net/tz/US-Eastern 1812 | BEGIN:STANDARD 1813 | DTSTART:19671029T020000 1814 | RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10 1815 | TZOFFSETFROM:-0400 1816 | TZOFFSETTO:-0500 1817 | TZNAME:EST 1818 | END:STANDARD 1819 | BEGIN:DAYLIGHT 1820 | DTSTART:19870405T020000 1821 | RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4 1822 | TZOFFSETFROM:-0500 1823 | TZOFFSETTO:-0400 1824 | TZNAME:EDT 1825 | END:DAYLIGHT 1826 | END:VTIMEZONE 1827 | }}} 1828 | 1829 | And here is an example exploring a {{{tzical}}} type: 1830 | {{{ 1831 | >>> from dateutil.tz import *; from datetime import * 1832 | 1833 | >>> tz = tzical('EST5EDT.ics') 1834 | >>> tz.keys() 1835 | ['US-Eastern'] 1836 | 1837 | >>> est = tz.get('US-Eastern') 1838 | >>> est 1839 | 1840 | 1841 | >>> datetime.now(est) 1842 | datetime.datetime(2003, 10, 6, 19, 44, 18, 667987, 1843 | tzinfo=) 1844 | 1845 | >>> est == tz.get() 1846 | True 1847 | }}} 1848 | 1849 | Let's check the daylight ranges, as usual: 1850 | {{{ 1851 | >>> datetime(2003, 4, 6, 1, 59, tzinfo=est).tzname() 1852 | 'EST' 1853 | >>> datetime(2003, 4, 6, 2, 00, tzinfo=est).tzname() 1854 | 'EDT' 1855 | 1856 | >>> datetime(2003, 10, 26, 0, 59, tzinfo=est).tzname() 1857 | 'EDT' 1858 | >>> datetime(2003, 10, 26, 1, 00, tzinfo=est).tzname() 1859 | 'EST' 1860 | }}} 1861 | 1862 | ==== tzwin type ==== 1863 | This type offers access to internal registry-based Windows timezones. 1864 | The constuctor prototype is: 1865 | {{{ 1866 | tzwin(name) 1867 | }}} 1868 | 1869 | Where {{{name}}} is the timezone name. There's a static {{{tzwin.list()}}} 1870 | method to check the available names, 1871 | 1872 | ==== tzwin methods ==== 1873 | 1874 | tzwin.display():: 1875 | This method returns the timezone extended name. 1876 | 1877 | tzwin.list():: 1878 | This static method lists all available timezone names. 1879 | 1880 | ==== tzwin examples ==== 1881 | {{{ 1882 | >>> tz = tzwin("E. South America Standard Time") 1883 | }}} 1884 | 1885 | ==== tzwinlocal type ==== 1886 | This type offers access to internal registry-based Windows timezones. 1887 | The constructor accepts no parameters, so the prototype is: 1888 | {{{ 1889 | tzwinlocal() 1890 | }}} 1891 | 1892 | ==== tzwinlocal methods ==== 1893 | 1894 | tzwinlocal.display():: 1895 | This method returns the timezone extended name, and returns 1896 | {{{None}}} if one is not available. 1897 | 1898 | ==== tzwinlocal examples ==== 1899 | {{{ 1900 | >>> tz = tzwinlocal() 1901 | }}} 1902 | 1903 | ==== gettz() function ==== 1904 | This function is a helper that will try its best to get the right 1905 | timezone for your environment, or for the given string. The prototype 1906 | is as follows: 1907 | {{{ 1908 | gettz(name=None) 1909 | }}} 1910 | 1911 | If given, the parameter may be a filename, a path relative to the base 1912 | of the timezone information path (the base could be 1913 | {{{/usr/share/zoneinfo}}}, for example), a string timezone 1914 | specification, or a timezone abbreviation. If {{{name}}} is not given, 1915 | and the {{{TZ}}} environment variable is set, it's used instead. If the 1916 | parameter is not given, and {{{TZ}}} is not set, the default tzfile 1917 | paths will be tried. Then, if no timezone information is found, 1918 | an internal compiled database of timezones is used. When running 1919 | on Windows, the internal registry-based Windows timezones are also 1920 | considered. 1921 | 1922 | Example: 1923 | {{{ 1924 | >>> from dateutil.tz import * 1925 | >>> gettz() 1926 | tzfile('/etc/localtime') 1927 | 1928 | >>> gettz("America/Sao Paulo") 1929 | tzfile('/usr/share/zoneinfo/America/Sao_Paulo') 1930 | 1931 | >>> gettz("EST5EDT") 1932 | tzfile('/usr/share/zoneinfo/EST5EDT') 1933 | 1934 | >>> gettz("EST5") 1935 | tzstr('EST5') 1936 | 1937 | >>> gettz('BRST') 1938 | tzlocal() 1939 | 1940 | >>> os.environ["TZ"] = "America/Sao Paulo" 1941 | >>> gettz() 1942 | tzfile('/usr/share/zoneinfo/America/Sao_Paulo') 1943 | 1944 | >>> os.environ["TZ"] = "BRST" 1945 | >>> gettz() 1946 | tzlocal() 1947 | 1948 | >>> gettz("Unavailable") 1949 | >>> 1950 | }}} 1951 | 1952 | === zoneinfo === 1953 | This module provides direct access to the internal compiled 1954 | database of timezones. The timezone data and the compiling tools 1955 | are obtained from the following project: 1956 | 1957 | http://www.twinsun.com/tz/tz-link.htm 1958 | 1959 | ==== gettz() function ==== 1960 | This function will try to retrieve the given timezone information 1961 | from the internal compiled database, and will cache its results. 1962 | 1963 | Example: 1964 | {{{ 1965 | >>> from dateutil import zoneinfo 1966 | >>> zoneinfo.gettz("Brazil/East") 1967 | tzfile('Brazil/East') 1968 | }}} 1969 | 1970 | ## vim:ft=moin 1971 | -------------------------------------------------------------------------------- /dateutil/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2003-2010 Gustavo Niemeyer 3 | 4 | This module offers extensions to the standard python 2.3+ 5 | datetime module. 6 | """ 7 | __author__ = "Gustavo Niemeyer " 8 | __license__ = "PSF License" 9 | __version__ = "1.5.0.1" 10 | -------------------------------------------------------------------------------- /dateutil/easter.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2003-2007 Gustavo Niemeyer 3 | 4 | This module offers extensions to the standard python 2.3+ 5 | datetime module. 6 | """ 7 | __author__ = "Gustavo Niemeyer " 8 | __license__ = "PSF License" 9 | 10 | import datetime 11 | 12 | __all__ = ["easter", "EASTER_JULIAN", "EASTER_ORTHODOX", "EASTER_WESTERN"] 13 | 14 | EASTER_JULIAN = 1 15 | EASTER_ORTHODOX = 2 16 | EASTER_WESTERN = 3 17 | 18 | def easter(year, method=EASTER_WESTERN): 19 | """ 20 | This method was ported from the work done by GM Arts, 21 | on top of the algorithm by Claus Tondering, which was 22 | based in part on the algorithm of Ouding (1940), as 23 | quoted in "Explanatory Supplement to the Astronomical 24 | Almanac", P. Kenneth Seidelmann, editor. 25 | 26 | This algorithm implements three different easter 27 | calculation methods: 28 | 29 | 1 - Original calculation in Julian calendar, valid in 30 | dates after 326 AD 31 | 2 - Original method, with date converted to Gregorian 32 | calendar, valid in years 1583 to 4099 33 | 3 - Revised method, in Gregorian calendar, valid in 34 | years 1583 to 4099 as well 35 | 36 | These methods are represented by the constants: 37 | 38 | EASTER_JULIAN = 1 39 | EASTER_ORTHODOX = 2 40 | EASTER_WESTERN = 3 41 | 42 | The default method is method 3. 43 | 44 | More about the algorithm may be found at: 45 | 46 | http://users.chariot.net.au/~gmarts/eastalg.htm 47 | 48 | and 49 | 50 | http://www.tondering.dk/claus/calendar.html 51 | 52 | """ 53 | 54 | if not (1 <= method <= 3): 55 | raise ValueError, "invalid method" 56 | 57 | # g - Golden year - 1 58 | # c - Century 59 | # h - (23 - Epact) mod 30 60 | # i - Number of days from March 21 to Paschal Full Moon 61 | # j - Weekday for PFM (0=Sunday, etc) 62 | # p - Number of days from March 21 to Sunday on or before PFM 63 | # (-6 to 28 methods 1 & 3, to 56 for method 2) 64 | # e - Extra days to add for method 2 (converting Julian 65 | # date to Gregorian date) 66 | 67 | y = year 68 | g = y % 19 69 | e = 0 70 | if method < 3: 71 | # Old method 72 | i = (19*g+15)%30 73 | j = (y+y//4+i)%7 74 | if method == 2: 75 | # Extra dates to convert Julian to Gregorian date 76 | e = 10 77 | if y > 1600: 78 | e = e+y//100-16-(y//100-16)//4 79 | else: 80 | # New method 81 | c = y//100 82 | h = (c-c//4-(8*c+13)//25+19*g+15)%30 83 | i = h-(h//28)*(1-(h//28)*(29//(h+1))*((21-g)//11)) 84 | j = (y+y//4+i+2-c+c//4)%7 85 | 86 | # p can be from -6 to 56 corresponding to dates 22 March to 23 May 87 | # (later dates apply to method 2, although 23 May never actually occurs) 88 | p = i-j+e 89 | d = 1+(p+27+(p+6)//40)%31 90 | m = 3+(p+26)//30 91 | return datetime.date(int(y),int(m),int(d)) 92 | 93 | -------------------------------------------------------------------------------- /dateutil/parser.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paxan/python-dateutil/16e385ab1bcae57d7db38a1ab525b954d3614f56/dateutil/parser.py -------------------------------------------------------------------------------- /dateutil/relativedelta.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2003-2010 Gustavo Niemeyer 3 | 4 | This module offers extensions to the standard python 2.3+ 5 | datetime module. 6 | """ 7 | __author__ = "Gustavo Niemeyer " 8 | __license__ = "PSF License" 9 | 10 | import datetime 11 | import calendar 12 | 13 | __all__ = ["relativedelta", "MO", "TU", "WE", "TH", "FR", "SA", "SU"] 14 | 15 | class weekday(object): 16 | __slots__ = ["weekday", "n"] 17 | 18 | def __init__(self, weekday, n=None): 19 | self.weekday = weekday 20 | self.n = n 21 | 22 | def __call__(self, n): 23 | if n == self.n: 24 | return self 25 | else: 26 | return self.__class__(self.weekday, n) 27 | 28 | def __eq__(self, other): 29 | try: 30 | if self.weekday != other.weekday or self.n != other.n: 31 | return False 32 | except AttributeError: 33 | return False 34 | return True 35 | 36 | def __repr__(self): 37 | s = ("MO", "TU", "WE", "TH", "FR", "SA", "SU")[self.weekday] 38 | if not self.n: 39 | return s 40 | else: 41 | return "%s(%+d)" % (s, self.n) 42 | 43 | MO, TU, WE, TH, FR, SA, SU = weekdays = tuple([weekday(x) for x in range(7)]) 44 | 45 | class relativedelta: 46 | """ 47 | The relativedelta type is based on the specification of the excelent 48 | work done by M.-A. Lemburg in his mx.DateTime extension. However, 49 | notice that this type does *NOT* implement the same algorithm as 50 | his work. Do *NOT* expect it to behave like mx.DateTime's counterpart. 51 | 52 | There's two different ways to build a relativedelta instance. The 53 | first one is passing it two date/datetime classes: 54 | 55 | relativedelta(datetime1, datetime2) 56 | 57 | And the other way is to use the following keyword arguments: 58 | 59 | year, month, day, hour, minute, second, microsecond: 60 | Absolute information. 61 | 62 | years, months, weeks, days, hours, minutes, seconds, microseconds: 63 | Relative information, may be negative. 64 | 65 | weekday: 66 | One of the weekday instances (MO, TU, etc). These instances may 67 | receive a parameter N, specifying the Nth weekday, which could 68 | be positive or negative (like MO(+1) or MO(-2). Not specifying 69 | it is the same as specifying +1. You can also use an integer, 70 | where 0=MO. 71 | 72 | leapdays: 73 | Will add given days to the date found, if year is a leap 74 | year, and the date found is post 28 of february. 75 | 76 | yearday, nlyearday: 77 | Set the yearday or the non-leap year day (jump leap days). 78 | These are converted to day/month/leapdays information. 79 | 80 | Here is the behavior of operations with relativedelta: 81 | 82 | 1) Calculate the absolute year, using the 'year' argument, or the 83 | original datetime year, if the argument is not present. 84 | 85 | 2) Add the relative 'years' argument to the absolute year. 86 | 87 | 3) Do steps 1 and 2 for month/months. 88 | 89 | 4) Calculate the absolute day, using the 'day' argument, or the 90 | original datetime day, if the argument is not present. Then, 91 | subtract from the day until it fits in the year and month 92 | found after their operations. 93 | 94 | 5) Add the relative 'days' argument to the absolute day. Notice 95 | that the 'weeks' argument is multiplied by 7 and added to 96 | 'days'. 97 | 98 | 6) Do steps 1 and 2 for hour/hours, minute/minutes, second/seconds, 99 | microsecond/microseconds. 100 | 101 | 7) If the 'weekday' argument is present, calculate the weekday, 102 | with the given (wday, nth) tuple. wday is the index of the 103 | weekday (0-6, 0=Mon), and nth is the number of weeks to add 104 | forward or backward, depending on its signal. Notice that if 105 | the calculated date is already Monday, for example, using 106 | (0, 1) or (0, -1) won't change the day. 107 | """ 108 | 109 | def __init__(self, dt1=None, dt2=None, 110 | years=0, months=0, days=0, leapdays=0, weeks=0, 111 | hours=0, minutes=0, seconds=0, microseconds=0, 112 | year=None, month=None, day=None, weekday=None, 113 | yearday=None, nlyearday=None, 114 | hour=None, minute=None, second=None, microsecond=None): 115 | if dt1 and dt2: 116 | if not isinstance(dt1, datetime.date) or \ 117 | not isinstance(dt2, datetime.date): 118 | raise TypeError, "relativedelta only diffs datetime/date" 119 | if type(dt1) is not type(dt2): 120 | if not isinstance(dt1, datetime.datetime): 121 | dt1 = datetime.datetime.fromordinal(dt1.toordinal()) 122 | elif not isinstance(dt2, datetime.datetime): 123 | dt2 = datetime.datetime.fromordinal(dt2.toordinal()) 124 | self.years = 0 125 | self.months = 0 126 | self.days = 0 127 | self.leapdays = 0 128 | self.hours = 0 129 | self.minutes = 0 130 | self.seconds = 0 131 | self.microseconds = 0 132 | self.year = None 133 | self.month = None 134 | self.day = None 135 | self.weekday = None 136 | self.hour = None 137 | self.minute = None 138 | self.second = None 139 | self.microsecond = None 140 | self._has_time = 0 141 | 142 | months = (dt1.year*12+dt1.month)-(dt2.year*12+dt2.month) 143 | self._set_months(months) 144 | dtm = self.__radd__(dt2) 145 | if dt1 < dt2: 146 | while dt1 > dtm: 147 | months += 1 148 | self._set_months(months) 149 | dtm = self.__radd__(dt2) 150 | else: 151 | while dt1 < dtm: 152 | months -= 1 153 | self._set_months(months) 154 | dtm = self.__radd__(dt2) 155 | delta = dt1 - dtm 156 | self.seconds = delta.seconds+delta.days*86400 157 | self.microseconds = delta.microseconds 158 | else: 159 | self.years = years 160 | self.months = months 161 | self.days = days+weeks*7 162 | self.leapdays = leapdays 163 | self.hours = hours 164 | self.minutes = minutes 165 | self.seconds = seconds 166 | self.microseconds = microseconds 167 | self.year = year 168 | self.month = month 169 | self.day = day 170 | self.hour = hour 171 | self.minute = minute 172 | self.second = second 173 | self.microsecond = microsecond 174 | 175 | if type(weekday) is int: 176 | self.weekday = weekdays[weekday] 177 | else: 178 | self.weekday = weekday 179 | 180 | yday = 0 181 | if nlyearday: 182 | yday = nlyearday 183 | elif yearday: 184 | yday = yearday 185 | if yearday > 59: 186 | self.leapdays = -1 187 | if yday: 188 | ydayidx = [31,59,90,120,151,181,212,243,273,304,334,366] 189 | for idx, ydays in enumerate(ydayidx): 190 | if yday <= ydays: 191 | self.month = idx+1 192 | if idx == 0: 193 | self.day = yday 194 | else: 195 | self.day = yday-ydayidx[idx-1] 196 | break 197 | else: 198 | raise ValueError, "invalid year day (%d)" % yday 199 | 200 | self._fix() 201 | 202 | def _fix(self): 203 | if abs(self.microseconds) > 999999: 204 | s = self.microseconds//abs(self.microseconds) 205 | div, mod = divmod(self.microseconds*s, 1000000) 206 | self.microseconds = mod*s 207 | self.seconds += div*s 208 | if abs(self.seconds) > 59: 209 | s = self.seconds//abs(self.seconds) 210 | div, mod = divmod(self.seconds*s, 60) 211 | self.seconds = mod*s 212 | self.minutes += div*s 213 | if abs(self.minutes) > 59: 214 | s = self.minutes//abs(self.minutes) 215 | div, mod = divmod(self.minutes*s, 60) 216 | self.minutes = mod*s 217 | self.hours += div*s 218 | if abs(self.hours) > 23: 219 | s = self.hours//abs(self.hours) 220 | div, mod = divmod(self.hours*s, 24) 221 | self.hours = mod*s 222 | self.days += div*s 223 | if abs(self.months) > 11: 224 | s = self.months//abs(self.months) 225 | div, mod = divmod(self.months*s, 12) 226 | self.months = mod*s 227 | self.years += div*s 228 | if (self.hours or self.minutes or self.seconds or self.microseconds or 229 | self.hour is not None or self.minute is not None or 230 | self.second is not None or self.microsecond is not None): 231 | self._has_time = 1 232 | else: 233 | self._has_time = 0 234 | 235 | def _set_months(self, months): 236 | self.months = months 237 | if abs(self.months) > 11: 238 | s = self.months//abs(self.months) 239 | div, mod = divmod(self.months*s, 12) 240 | self.months = mod*s 241 | self.years = div*s 242 | else: 243 | self.years = 0 244 | 245 | def __radd__(self, other): 246 | if not isinstance(other, datetime.date): 247 | raise TypeError, "unsupported type for add operation" 248 | elif self._has_time and not isinstance(other, datetime.datetime): 249 | other = datetime.datetime.fromordinal(other.toordinal()) 250 | year = (self.year or other.year)+self.years 251 | month = self.month or other.month 252 | if self.months: 253 | assert 1 <= abs(self.months) <= 12 254 | month += self.months 255 | if month > 12: 256 | year += 1 257 | month -= 12 258 | elif month < 1: 259 | year -= 1 260 | month += 12 261 | day = min(calendar.monthrange(year, month)[1], 262 | self.day or other.day) 263 | repl = {"year": year, "month": month, "day": day} 264 | for attr in ["hour", "minute", "second", "microsecond"]: 265 | value = getattr(self, attr) 266 | if value is not None: 267 | repl[attr] = value 268 | days = self.days 269 | if self.leapdays and month > 2 and calendar.isleap(year): 270 | days += self.leapdays 271 | ret = (other.replace(**repl) 272 | + datetime.timedelta(days=days, 273 | hours=self.hours, 274 | minutes=self.minutes, 275 | seconds=self.seconds, 276 | microseconds=self.microseconds)) 277 | if self.weekday: 278 | weekday, nth = self.weekday.weekday, self.weekday.n or 1 279 | jumpdays = (abs(nth)-1)*7 280 | if nth > 0: 281 | jumpdays += (7-ret.weekday()+weekday)%7 282 | else: 283 | jumpdays += (ret.weekday()-weekday)%7 284 | jumpdays *= -1 285 | ret += datetime.timedelta(days=jumpdays) 286 | return ret 287 | 288 | def __rsub__(self, other): 289 | return self.__neg__().__radd__(other) 290 | 291 | def __add__(self, other): 292 | if not isinstance(other, relativedelta): 293 | raise TypeError, "unsupported type for add operation" 294 | return relativedelta(years=other.years+self.years, 295 | months=other.months+self.months, 296 | days=other.days+self.days, 297 | hours=other.hours+self.hours, 298 | minutes=other.minutes+self.minutes, 299 | seconds=other.seconds+self.seconds, 300 | microseconds=other.microseconds+self.microseconds, 301 | leapdays=other.leapdays or self.leapdays, 302 | year=other.year or self.year, 303 | month=other.month or self.month, 304 | day=other.day or self.day, 305 | weekday=other.weekday or self.weekday, 306 | hour=other.hour or self.hour, 307 | minute=other.minute or self.minute, 308 | second=other.second or self.second, 309 | microsecond=other.second or self.microsecond) 310 | 311 | def __sub__(self, other): 312 | if not isinstance(other, relativedelta): 313 | raise TypeError, "unsupported type for sub operation" 314 | return relativedelta(years=other.years-self.years, 315 | months=other.months-self.months, 316 | days=other.days-self.days, 317 | hours=other.hours-self.hours, 318 | minutes=other.minutes-self.minutes, 319 | seconds=other.seconds-self.seconds, 320 | microseconds=other.microseconds-self.microseconds, 321 | leapdays=other.leapdays or self.leapdays, 322 | year=other.year or self.year, 323 | month=other.month or self.month, 324 | day=other.day or self.day, 325 | weekday=other.weekday or self.weekday, 326 | hour=other.hour or self.hour, 327 | minute=other.minute or self.minute, 328 | second=other.second or self.second, 329 | microsecond=other.second or self.microsecond) 330 | 331 | def __neg__(self): 332 | return relativedelta(years=-self.years, 333 | months=-self.months, 334 | days=-self.days, 335 | hours=-self.hours, 336 | minutes=-self.minutes, 337 | seconds=-self.seconds, 338 | microseconds=-self.microseconds, 339 | leapdays=self.leapdays, 340 | year=self.year, 341 | month=self.month, 342 | day=self.day, 343 | weekday=self.weekday, 344 | hour=self.hour, 345 | minute=self.minute, 346 | second=self.second, 347 | microsecond=self.microsecond) 348 | 349 | def __nonzero__(self): 350 | return not (not self.years and 351 | not self.months and 352 | not self.days and 353 | not self.hours and 354 | not self.minutes and 355 | not self.seconds and 356 | not self.microseconds and 357 | not self.leapdays and 358 | self.year is None and 359 | self.month is None and 360 | self.day is None and 361 | self.weekday is None and 362 | self.hour is None and 363 | self.minute is None and 364 | self.second is None and 365 | self.microsecond is None) 366 | 367 | def __mul__(self, other): 368 | f = float(other) 369 | return relativedelta(years = int(round(self.years*f)), 370 | months = int(round(self.months*f)), 371 | days = int(round(self.days*f)), 372 | hours = int(round(self.hours*f)), 373 | minutes = int(round(self.minutes*f)), 374 | seconds = int(round(self.seconds*f)), 375 | microseconds = self.microseconds*f, 376 | leapdays = self.leapdays, 377 | year = self.year, 378 | month = self.month, 379 | day = self.day, 380 | weekday = self.weekday, 381 | hour = self.hour, 382 | minute = self.minute, 383 | second = self.second, 384 | microsecond = self.microsecond) 385 | 386 | def __eq__(self, other): 387 | if not isinstance(other, relativedelta): 388 | return False 389 | if self.weekday or other.weekday: 390 | if not self.weekday or not other.weekday: 391 | return False 392 | if self.weekday.weekday != other.weekday.weekday: 393 | return False 394 | n1, n2 = self.weekday.n, other.weekday.n 395 | if n1 != n2 and not ((not n1 or n1 == 1) and (not n2 or n2 == 1)): 396 | return False 397 | return (self.years == other.years and 398 | self.months == other.months and 399 | self.days == other.days and 400 | self.hours == other.hours and 401 | self.minutes == other.minutes and 402 | self.seconds == other.seconds and 403 | self.leapdays == other.leapdays and 404 | self.year == other.year and 405 | self.month == other.month and 406 | self.day == other.day and 407 | self.hour == other.hour and 408 | self.minute == other.minute and 409 | self.second == other.second and 410 | self.microsecond == other.microsecond) 411 | 412 | def __ne__(self, other): 413 | return not self.__eq__(other) 414 | 415 | def __div__(self, other): 416 | return self.__mul__(1/float(other)) 417 | 418 | def __repr__(self): 419 | l = [] 420 | for attr in ["years", "months", "days", "leapdays", 421 | "hours", "minutes", "seconds", "microseconds"]: 422 | value = getattr(self, attr) 423 | if value: 424 | l.append("%s=%+d" % (attr, value)) 425 | for attr in ["year", "month", "day", "weekday", 426 | "hour", "minute", "second", "microsecond"]: 427 | value = getattr(self, attr) 428 | if value is not None: 429 | l.append("%s=%s" % (attr, `value`)) 430 | return "%s(%s)" % (self.__class__.__name__, ", ".join(l)) 431 | 432 | # vim:ts=4:sw=4:et 433 | -------------------------------------------------------------------------------- /dateutil/rrule.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2003-2010 Gustavo Niemeyer 3 | 4 | This module offers extensions to the standard python 2.3+ 5 | datetime module. 6 | """ 7 | __author__ = "Gustavo Niemeyer " 8 | __license__ = "PSF License" 9 | 10 | import itertools 11 | import datetime 12 | import calendar 13 | import thread 14 | import heapq 15 | import sys 16 | 17 | __all__ = ["rrule", "rruleset", "rrulestr", 18 | "YEARLY", "MONTHLY", "WEEKLY", "DAILY", 19 | "HOURLY", "MINUTELY", "SECONDLY", 20 | "MO", "TU", "WE", "TH", "FR", "SA", "SU"] 21 | 22 | # Every mask is 7 days longer to handle cross-year weekly periods. 23 | M366MASK = tuple([1]*31+[2]*29+[3]*31+[4]*30+[5]*31+[6]*30+ 24 | [7]*31+[8]*31+[9]*30+[10]*31+[11]*30+[12]*31+[1]*7) 25 | M365MASK = list(M366MASK) 26 | M29, M30, M31 = range(1,30), range(1,31), range(1,32) 27 | MDAY366MASK = tuple(M31+M29+M31+M30+M31+M30+M31+M31+M30+M31+M30+M31+M31[:7]) 28 | MDAY365MASK = list(MDAY366MASK) 29 | M29, M30, M31 = range(-29,0), range(-30,0), range(-31,0) 30 | NMDAY366MASK = tuple(M31+M29+M31+M30+M31+M30+M31+M31+M30+M31+M30+M31+M31[:7]) 31 | NMDAY365MASK = list(NMDAY366MASK) 32 | M366RANGE = (0,31,60,91,121,152,182,213,244,274,305,335,366) 33 | M365RANGE = (0,31,59,90,120,151,181,212,243,273,304,334,365) 34 | WDAYMASK = [0,1,2,3,4,5,6]*55 35 | del M29, M30, M31, M365MASK[59], MDAY365MASK[59], NMDAY365MASK[31] 36 | MDAY365MASK = tuple(MDAY365MASK) 37 | M365MASK = tuple(M365MASK) 38 | 39 | (YEARLY, 40 | MONTHLY, 41 | WEEKLY, 42 | DAILY, 43 | HOURLY, 44 | MINUTELY, 45 | SECONDLY) = range(7) 46 | 47 | # Imported on demand. 48 | easter = None 49 | parser = None 50 | 51 | class weekday(object): 52 | __slots__ = ["weekday", "n"] 53 | 54 | def __init__(self, weekday, n=None): 55 | if n == 0: 56 | raise ValueError, "Can't create weekday with n == 0" 57 | self.weekday = weekday 58 | self.n = n 59 | 60 | def __call__(self, n): 61 | if n == self.n: 62 | return self 63 | else: 64 | return self.__class__(self.weekday, n) 65 | 66 | def __eq__(self, other): 67 | try: 68 | if self.weekday != other.weekday or self.n != other.n: 69 | return False 70 | except AttributeError: 71 | return False 72 | return True 73 | 74 | def __repr__(self): 75 | s = ("MO", "TU", "WE", "TH", "FR", "SA", "SU")[self.weekday] 76 | if not self.n: 77 | return s 78 | else: 79 | return "%s(%+d)" % (s, self.n) 80 | 81 | MO, TU, WE, TH, FR, SA, SU = weekdays = tuple([weekday(x) for x in range(7)]) 82 | 83 | class rrulebase: 84 | def __init__(self, cache=False): 85 | if cache: 86 | self._cache = [] 87 | self._cache_lock = thread.allocate_lock() 88 | self._cache_gen = self._iter() 89 | self._cache_complete = False 90 | else: 91 | self._cache = None 92 | self._cache_complete = False 93 | self._len = None 94 | 95 | def __iter__(self): 96 | if self._cache_complete: 97 | return iter(self._cache) 98 | elif self._cache is None: 99 | return self._iter() 100 | else: 101 | return self._iter_cached() 102 | 103 | def _iter_cached(self): 104 | i = 0 105 | gen = self._cache_gen 106 | cache = self._cache 107 | acquire = self._cache_lock.acquire 108 | release = self._cache_lock.release 109 | while gen: 110 | if i == len(cache): 111 | acquire() 112 | if self._cache_complete: 113 | break 114 | try: 115 | for j in range(10): 116 | cache.append(gen.next()) 117 | except StopIteration: 118 | self._cache_gen = gen = None 119 | self._cache_complete = True 120 | break 121 | release() 122 | yield cache[i] 123 | i += 1 124 | while i < self._len: 125 | yield cache[i] 126 | i += 1 127 | 128 | def __getitem__(self, item): 129 | if self._cache_complete: 130 | return self._cache[item] 131 | elif isinstance(item, slice): 132 | if item.step and item.step < 0: 133 | return list(iter(self))[item] 134 | else: 135 | return list(itertools.islice(self, 136 | item.start or 0, 137 | item.stop or sys.maxint, 138 | item.step or 1)) 139 | elif item >= 0: 140 | gen = iter(self) 141 | try: 142 | for i in range(item+1): 143 | res = gen.next() 144 | except StopIteration: 145 | raise IndexError 146 | return res 147 | else: 148 | return list(iter(self))[item] 149 | 150 | def __contains__(self, item): 151 | if self._cache_complete: 152 | return item in self._cache 153 | else: 154 | for i in self: 155 | if i == item: 156 | return True 157 | elif i > item: 158 | return False 159 | return False 160 | 161 | # __len__() introduces a large performance penality. 162 | def count(self): 163 | if self._len is None: 164 | for x in self: pass 165 | return self._len 166 | 167 | def before(self, dt, inc=False): 168 | if self._cache_complete: 169 | gen = self._cache 170 | else: 171 | gen = self 172 | last = None 173 | if inc: 174 | for i in gen: 175 | if i > dt: 176 | break 177 | last = i 178 | else: 179 | for i in gen: 180 | if i >= dt: 181 | break 182 | last = i 183 | return last 184 | 185 | def after(self, dt, inc=False): 186 | if self._cache_complete: 187 | gen = self._cache 188 | else: 189 | gen = self 190 | if inc: 191 | for i in gen: 192 | if i >= dt: 193 | return i 194 | else: 195 | for i in gen: 196 | if i > dt: 197 | return i 198 | return None 199 | 200 | def between(self, after, before, inc=False): 201 | if self._cache_complete: 202 | gen = self._cache 203 | else: 204 | gen = self 205 | started = False 206 | l = [] 207 | if inc: 208 | for i in gen: 209 | if i > before: 210 | break 211 | elif not started: 212 | if i >= after: 213 | started = True 214 | l.append(i) 215 | else: 216 | l.append(i) 217 | else: 218 | for i in gen: 219 | if i >= before: 220 | break 221 | elif not started: 222 | if i > after: 223 | started = True 224 | l.append(i) 225 | else: 226 | l.append(i) 227 | return l 228 | 229 | class rrule(rrulebase): 230 | def __init__(self, freq, dtstart=None, 231 | interval=1, wkst=None, count=None, until=None, bysetpos=None, 232 | bymonth=None, bymonthday=None, byyearday=None, byeaster=None, 233 | byweekno=None, byweekday=None, 234 | byhour=None, byminute=None, bysecond=None, 235 | cache=False): 236 | rrulebase.__init__(self, cache) 237 | global easter 238 | if not dtstart: 239 | dtstart = datetime.datetime.now().replace(microsecond=0) 240 | elif not isinstance(dtstart, datetime.datetime): 241 | dtstart = datetime.datetime.fromordinal(dtstart.toordinal()) 242 | else: 243 | dtstart = dtstart.replace(microsecond=0) 244 | self._dtstart = dtstart 245 | self._tzinfo = dtstart.tzinfo 246 | self._freq = freq 247 | self._interval = interval 248 | self._count = count 249 | if until and not isinstance(until, datetime.datetime): 250 | until = datetime.datetime.fromordinal(until.toordinal()) 251 | self._until = until 252 | if wkst is None: 253 | self._wkst = calendar.firstweekday() 254 | elif type(wkst) is int: 255 | self._wkst = wkst 256 | else: 257 | self._wkst = wkst.weekday 258 | if bysetpos is None: 259 | self._bysetpos = None 260 | elif type(bysetpos) is int: 261 | if bysetpos == 0 or not (-366 <= bysetpos <= 366): 262 | raise ValueError("bysetpos must be between 1 and 366, " 263 | "or between -366 and -1") 264 | self._bysetpos = (bysetpos,) 265 | else: 266 | self._bysetpos = tuple(bysetpos) 267 | for pos in self._bysetpos: 268 | if pos == 0 or not (-366 <= pos <= 366): 269 | raise ValueError("bysetpos must be between 1 and 366, " 270 | "or between -366 and -1") 271 | if not (byweekno or byyearday or bymonthday or 272 | byweekday is not None or byeaster is not None): 273 | if freq == YEARLY: 274 | if not bymonth: 275 | bymonth = dtstart.month 276 | bymonthday = dtstart.day 277 | elif freq == MONTHLY: 278 | bymonthday = dtstart.day 279 | elif freq == WEEKLY: 280 | byweekday = dtstart.weekday() 281 | # bymonth 282 | if not bymonth: 283 | self._bymonth = None 284 | elif type(bymonth) is int: 285 | self._bymonth = (bymonth,) 286 | else: 287 | self._bymonth = tuple(bymonth) 288 | # byyearday 289 | if not byyearday: 290 | self._byyearday = None 291 | elif type(byyearday) is int: 292 | self._byyearday = (byyearday,) 293 | else: 294 | self._byyearday = tuple(byyearday) 295 | # byeaster 296 | if byeaster is not None: 297 | if not easter: 298 | from dateutil import easter 299 | if type(byeaster) is int: 300 | self._byeaster = (byeaster,) 301 | else: 302 | self._byeaster = tuple(byeaster) 303 | else: 304 | self._byeaster = None 305 | # bymonthay 306 | if not bymonthday: 307 | self._bymonthday = () 308 | self._bynmonthday = () 309 | elif type(bymonthday) is int: 310 | if bymonthday < 0: 311 | self._bynmonthday = (bymonthday,) 312 | self._bymonthday = () 313 | else: 314 | self._bymonthday = (bymonthday,) 315 | self._bynmonthday = () 316 | else: 317 | self._bymonthday = tuple([x for x in bymonthday if x > 0]) 318 | self._bynmonthday = tuple([x for x in bymonthday if x < 0]) 319 | # byweekno 320 | if byweekno is None: 321 | self._byweekno = None 322 | elif type(byweekno) is int: 323 | self._byweekno = (byweekno,) 324 | else: 325 | self._byweekno = tuple(byweekno) 326 | # byweekday / bynweekday 327 | if byweekday is None: 328 | self._byweekday = None 329 | self._bynweekday = None 330 | elif type(byweekday) is int: 331 | self._byweekday = (byweekday,) 332 | self._bynweekday = None 333 | elif hasattr(byweekday, "n"): 334 | if not byweekday.n or freq > MONTHLY: 335 | self._byweekday = (byweekday.weekday,) 336 | self._bynweekday = None 337 | else: 338 | self._bynweekday = ((byweekday.weekday, byweekday.n),) 339 | self._byweekday = None 340 | else: 341 | self._byweekday = [] 342 | self._bynweekday = [] 343 | for wday in byweekday: 344 | if type(wday) is int: 345 | self._byweekday.append(wday) 346 | elif not wday.n or freq > MONTHLY: 347 | self._byweekday.append(wday.weekday) 348 | else: 349 | self._bynweekday.append((wday.weekday, wday.n)) 350 | self._byweekday = tuple(self._byweekday) 351 | self._bynweekday = tuple(self._bynweekday) 352 | if not self._byweekday: 353 | self._byweekday = None 354 | elif not self._bynweekday: 355 | self._bynweekday = None 356 | # byhour 357 | if byhour is None: 358 | if freq < HOURLY: 359 | self._byhour = (dtstart.hour,) 360 | else: 361 | self._byhour = None 362 | elif type(byhour) is int: 363 | self._byhour = (byhour,) 364 | else: 365 | self._byhour = tuple(byhour) 366 | # byminute 367 | if byminute is None: 368 | if freq < MINUTELY: 369 | self._byminute = (dtstart.minute,) 370 | else: 371 | self._byminute = None 372 | elif type(byminute) is int: 373 | self._byminute = (byminute,) 374 | else: 375 | self._byminute = tuple(byminute) 376 | # bysecond 377 | if bysecond is None: 378 | if freq < SECONDLY: 379 | self._bysecond = (dtstart.second,) 380 | else: 381 | self._bysecond = None 382 | elif type(bysecond) is int: 383 | self._bysecond = (bysecond,) 384 | else: 385 | self._bysecond = tuple(bysecond) 386 | 387 | if self._freq >= HOURLY: 388 | self._timeset = None 389 | else: 390 | self._timeset = [] 391 | for hour in self._byhour: 392 | for minute in self._byminute: 393 | for second in self._bysecond: 394 | self._timeset.append( 395 | datetime.time(hour, minute, second, 396 | tzinfo=self._tzinfo)) 397 | self._timeset.sort() 398 | self._timeset = tuple(self._timeset) 399 | 400 | def _iter(self): 401 | year, month, day, hour, minute, second, weekday, yearday, _ = \ 402 | self._dtstart.timetuple() 403 | 404 | # Some local variables to speed things up a bit 405 | freq = self._freq 406 | interval = self._interval 407 | wkst = self._wkst 408 | until = self._until 409 | bymonth = self._bymonth 410 | byweekno = self._byweekno 411 | byyearday = self._byyearday 412 | byweekday = self._byweekday 413 | byeaster = self._byeaster 414 | bymonthday = self._bymonthday 415 | bynmonthday = self._bynmonthday 416 | bysetpos = self._bysetpos 417 | byhour = self._byhour 418 | byminute = self._byminute 419 | bysecond = self._bysecond 420 | 421 | ii = _iterinfo(self) 422 | ii.rebuild(year, month) 423 | 424 | getdayset = {YEARLY:ii.ydayset, 425 | MONTHLY:ii.mdayset, 426 | WEEKLY:ii.wdayset, 427 | DAILY:ii.ddayset, 428 | HOURLY:ii.ddayset, 429 | MINUTELY:ii.ddayset, 430 | SECONDLY:ii.ddayset}[freq] 431 | 432 | if freq < HOURLY: 433 | timeset = self._timeset 434 | else: 435 | gettimeset = {HOURLY:ii.htimeset, 436 | MINUTELY:ii.mtimeset, 437 | SECONDLY:ii.stimeset}[freq] 438 | if ((freq >= HOURLY and 439 | self._byhour and hour not in self._byhour) or 440 | (freq >= MINUTELY and 441 | self._byminute and minute not in self._byminute) or 442 | (freq >= SECONDLY and 443 | self._bysecond and second not in self._bysecond)): 444 | timeset = () 445 | else: 446 | timeset = gettimeset(hour, minute, second) 447 | 448 | total = 0 449 | count = self._count 450 | while True: 451 | # Get dayset with the right frequency 452 | dayset, start, end = getdayset(year, month, day) 453 | 454 | # Do the "hard" work ;-) 455 | filtered = False 456 | for i in dayset[start:end]: 457 | if ((bymonth and ii.mmask[i] not in bymonth) or 458 | (byweekno and not ii.wnomask[i]) or 459 | (byweekday and ii.wdaymask[i] not in byweekday) or 460 | (ii.nwdaymask and not ii.nwdaymask[i]) or 461 | (byeaster and not ii.eastermask[i]) or 462 | ((bymonthday or bynmonthday) and 463 | ii.mdaymask[i] not in bymonthday and 464 | ii.nmdaymask[i] not in bynmonthday) or 465 | (byyearday and 466 | ((i < ii.yearlen and i+1 not in byyearday 467 | and -ii.yearlen+i not in byyearday) or 468 | (i >= ii.yearlen and i+1-ii.yearlen not in byyearday 469 | and -ii.nextyearlen+i-ii.yearlen 470 | not in byyearday)))): 471 | dayset[i] = None 472 | filtered = True 473 | 474 | # Output results 475 | if bysetpos and timeset: 476 | poslist = [] 477 | for pos in bysetpos: 478 | if pos < 0: 479 | daypos, timepos = divmod(pos, len(timeset)) 480 | else: 481 | daypos, timepos = divmod(pos-1, len(timeset)) 482 | try: 483 | i = [x for x in dayset[start:end] 484 | if x is not None][daypos] 485 | time = timeset[timepos] 486 | except IndexError: 487 | pass 488 | else: 489 | date = datetime.date.fromordinal(ii.yearordinal+i) 490 | res = datetime.datetime.combine(date, time) 491 | if res not in poslist: 492 | poslist.append(res) 493 | poslist.sort() 494 | for res in poslist: 495 | if until and res > until: 496 | self._len = total 497 | return 498 | elif res >= self._dtstart: 499 | total += 1 500 | yield res 501 | if count: 502 | count -= 1 503 | if not count: 504 | self._len = total 505 | return 506 | else: 507 | for i in dayset[start:end]: 508 | if i is not None: 509 | date = datetime.date.fromordinal(ii.yearordinal+i) 510 | for time in timeset: 511 | res = datetime.datetime.combine(date, time) 512 | if until and res > until: 513 | self._len = total 514 | return 515 | elif res >= self._dtstart: 516 | total += 1 517 | yield res 518 | if count: 519 | count -= 1 520 | if not count: 521 | self._len = total 522 | return 523 | 524 | # Handle frequency and interval 525 | fixday = False 526 | if freq == YEARLY: 527 | year += interval 528 | if year > datetime.MAXYEAR: 529 | self._len = total 530 | return 531 | ii.rebuild(year, month) 532 | elif freq == MONTHLY: 533 | month += interval 534 | if month > 12: 535 | div, mod = divmod(month, 12) 536 | month = mod 537 | year += div 538 | if month == 0: 539 | month = 12 540 | year -= 1 541 | if year > datetime.MAXYEAR: 542 | self._len = total 543 | return 544 | ii.rebuild(year, month) 545 | elif freq == WEEKLY: 546 | if wkst > weekday: 547 | day += -(weekday+1+(6-wkst))+self._interval*7 548 | else: 549 | day += -(weekday-wkst)+self._interval*7 550 | weekday = wkst 551 | fixday = True 552 | elif freq == DAILY: 553 | day += interval 554 | fixday = True 555 | elif freq == HOURLY: 556 | if filtered: 557 | # Jump to one iteration before next day 558 | hour += ((23-hour)//interval)*interval 559 | while True: 560 | hour += interval 561 | div, mod = divmod(hour, 24) 562 | if div: 563 | hour = mod 564 | day += div 565 | fixday = True 566 | if not byhour or hour in byhour: 567 | break 568 | timeset = gettimeset(hour, minute, second) 569 | elif freq == MINUTELY: 570 | if filtered: 571 | # Jump to one iteration before next day 572 | minute += ((1439-(hour*60+minute))//interval)*interval 573 | while True: 574 | minute += interval 575 | div, mod = divmod(minute, 60) 576 | if div: 577 | minute = mod 578 | hour += div 579 | div, mod = divmod(hour, 24) 580 | if div: 581 | hour = mod 582 | day += div 583 | fixday = True 584 | filtered = False 585 | if ((not byhour or hour in byhour) and 586 | (not byminute or minute in byminute)): 587 | break 588 | timeset = gettimeset(hour, minute, second) 589 | elif freq == SECONDLY: 590 | if filtered: 591 | # Jump to one iteration before next day 592 | second += (((86399-(hour*3600+minute*60+second)) 593 | //interval)*interval) 594 | while True: 595 | second += self._interval 596 | div, mod = divmod(second, 60) 597 | if div: 598 | second = mod 599 | minute += div 600 | div, mod = divmod(minute, 60) 601 | if div: 602 | minute = mod 603 | hour += div 604 | div, mod = divmod(hour, 24) 605 | if div: 606 | hour = mod 607 | day += div 608 | fixday = True 609 | if ((not byhour or hour in byhour) and 610 | (not byminute or minute in byminute) and 611 | (not bysecond or second in bysecond)): 612 | break 613 | timeset = gettimeset(hour, minute, second) 614 | 615 | if fixday and day > 28: 616 | daysinmonth = calendar.monthrange(year, month)[1] 617 | if day > daysinmonth: 618 | while day > daysinmonth: 619 | day -= daysinmonth 620 | month += 1 621 | if month == 13: 622 | month = 1 623 | year += 1 624 | if year > datetime.MAXYEAR: 625 | self._len = total 626 | return 627 | daysinmonth = calendar.monthrange(year, month)[1] 628 | ii.rebuild(year, month) 629 | 630 | class _iterinfo(object): 631 | __slots__ = ["rrule", "lastyear", "lastmonth", 632 | "yearlen", "nextyearlen", "yearordinal", "yearweekday", 633 | "mmask", "mrange", "mdaymask", "nmdaymask", 634 | "wdaymask", "wnomask", "nwdaymask", "eastermask"] 635 | 636 | def __init__(self, rrule): 637 | for attr in self.__slots__: 638 | setattr(self, attr, None) 639 | self.rrule = rrule 640 | 641 | def rebuild(self, year, month): 642 | # Every mask is 7 days longer to handle cross-year weekly periods. 643 | rr = self.rrule 644 | if year != self.lastyear: 645 | self.yearlen = 365+calendar.isleap(year) 646 | self.nextyearlen = 365+calendar.isleap(year+1) 647 | firstyday = datetime.date(year, 1, 1) 648 | self.yearordinal = firstyday.toordinal() 649 | self.yearweekday = firstyday.weekday() 650 | 651 | wday = datetime.date(year, 1, 1).weekday() 652 | if self.yearlen == 365: 653 | self.mmask = M365MASK 654 | self.mdaymask = MDAY365MASK 655 | self.nmdaymask = NMDAY365MASK 656 | self.wdaymask = WDAYMASK[wday:] 657 | self.mrange = M365RANGE 658 | else: 659 | self.mmask = M366MASK 660 | self.mdaymask = MDAY366MASK 661 | self.nmdaymask = NMDAY366MASK 662 | self.wdaymask = WDAYMASK[wday:] 663 | self.mrange = M366RANGE 664 | 665 | if not rr._byweekno: 666 | self.wnomask = None 667 | else: 668 | self.wnomask = [0]*(self.yearlen+7) 669 | #no1wkst = firstwkst = self.wdaymask.index(rr._wkst) 670 | no1wkst = firstwkst = (7-self.yearweekday+rr._wkst)%7 671 | if no1wkst >= 4: 672 | no1wkst = 0 673 | # Number of days in the year, plus the days we got 674 | # from last year. 675 | wyearlen = self.yearlen+(self.yearweekday-rr._wkst)%7 676 | else: 677 | # Number of days in the year, minus the days we 678 | # left in last year. 679 | wyearlen = self.yearlen-no1wkst 680 | div, mod = divmod(wyearlen, 7) 681 | numweeks = div+mod//4 682 | for n in rr._byweekno: 683 | if n < 0: 684 | n += numweeks+1 685 | if not (0 < n <= numweeks): 686 | continue 687 | if n > 1: 688 | i = no1wkst+(n-1)*7 689 | if no1wkst != firstwkst: 690 | i -= 7-firstwkst 691 | else: 692 | i = no1wkst 693 | for j in range(7): 694 | self.wnomask[i] = 1 695 | i += 1 696 | if self.wdaymask[i] == rr._wkst: 697 | break 698 | if 1 in rr._byweekno: 699 | # Check week number 1 of next year as well 700 | # TODO: Check -numweeks for next year. 701 | i = no1wkst+numweeks*7 702 | if no1wkst != firstwkst: 703 | i -= 7-firstwkst 704 | if i < self.yearlen: 705 | # If week starts in next year, we 706 | # don't care about it. 707 | for j in range(7): 708 | self.wnomask[i] = 1 709 | i += 1 710 | if self.wdaymask[i] == rr._wkst: 711 | break 712 | if no1wkst: 713 | # Check last week number of last year as 714 | # well. If no1wkst is 0, either the year 715 | # started on week start, or week number 1 716 | # got days from last year, so there are no 717 | # days from last year's last week number in 718 | # this year. 719 | if -1 not in rr._byweekno: 720 | lyearweekday = datetime.date(year-1,1,1).weekday() 721 | lno1wkst = (7-lyearweekday+rr._wkst)%7 722 | lyearlen = 365+calendar.isleap(year-1) 723 | if lno1wkst >= 4: 724 | lno1wkst = 0 725 | lnumweeks = 52+(lyearlen+ 726 | (lyearweekday-rr._wkst)%7)%7//4 727 | else: 728 | lnumweeks = 52+(self.yearlen-no1wkst)%7//4 729 | else: 730 | lnumweeks = -1 731 | if lnumweeks in rr._byweekno: 732 | for i in range(no1wkst): 733 | self.wnomask[i] = 1 734 | 735 | if (rr._bynweekday and 736 | (month != self.lastmonth or year != self.lastyear)): 737 | ranges = [] 738 | if rr._freq == YEARLY: 739 | if rr._bymonth: 740 | for month in rr._bymonth: 741 | ranges.append(self.mrange[month-1:month+1]) 742 | else: 743 | ranges = [(0, self.yearlen)] 744 | elif rr._freq == MONTHLY: 745 | ranges = [self.mrange[month-1:month+1]] 746 | if ranges: 747 | # Weekly frequency won't get here, so we may not 748 | # care about cross-year weekly periods. 749 | self.nwdaymask = [0]*self.yearlen 750 | for first, last in ranges: 751 | last -= 1 752 | for wday, n in rr._bynweekday: 753 | if n < 0: 754 | i = last+(n+1)*7 755 | i -= (self.wdaymask[i]-wday)%7 756 | else: 757 | i = first+(n-1)*7 758 | i += (7-self.wdaymask[i]+wday)%7 759 | if first <= i <= last: 760 | self.nwdaymask[i] = 1 761 | 762 | if rr._byeaster: 763 | self.eastermask = [0]*(self.yearlen+7) 764 | eyday = easter.easter(year).toordinal()-self.yearordinal 765 | for offset in rr._byeaster: 766 | self.eastermask[eyday+offset] = 1 767 | 768 | self.lastyear = year 769 | self.lastmonth = month 770 | 771 | def ydayset(self, year, month, day): 772 | return range(self.yearlen), 0, self.yearlen 773 | 774 | def mdayset(self, year, month, day): 775 | set = [None]*self.yearlen 776 | start, end = self.mrange[month-1:month+1] 777 | for i in range(start, end): 778 | set[i] = i 779 | return set, start, end 780 | 781 | def wdayset(self, year, month, day): 782 | # We need to handle cross-year weeks here. 783 | set = [None]*(self.yearlen+7) 784 | i = datetime.date(year, month, day).toordinal()-self.yearordinal 785 | start = i 786 | for j in range(7): 787 | set[i] = i 788 | i += 1 789 | #if (not (0 <= i < self.yearlen) or 790 | # self.wdaymask[i] == self.rrule._wkst): 791 | # This will cross the year boundary, if necessary. 792 | if self.wdaymask[i] == self.rrule._wkst: 793 | break 794 | return set, start, i 795 | 796 | def ddayset(self, year, month, day): 797 | set = [None]*self.yearlen 798 | i = datetime.date(year, month, day).toordinal()-self.yearordinal 799 | set[i] = i 800 | return set, i, i+1 801 | 802 | def htimeset(self, hour, minute, second): 803 | set = [] 804 | rr = self.rrule 805 | for minute in rr._byminute: 806 | for second in rr._bysecond: 807 | set.append(datetime.time(hour, minute, second, 808 | tzinfo=rr._tzinfo)) 809 | set.sort() 810 | return set 811 | 812 | def mtimeset(self, hour, minute, second): 813 | set = [] 814 | rr = self.rrule 815 | for second in rr._bysecond: 816 | set.append(datetime.time(hour, minute, second, tzinfo=rr._tzinfo)) 817 | set.sort() 818 | return set 819 | 820 | def stimeset(self, hour, minute, second): 821 | return (datetime.time(hour, minute, second, 822 | tzinfo=self.rrule._tzinfo),) 823 | 824 | 825 | class rruleset(rrulebase): 826 | 827 | class _genitem: 828 | def __init__(self, genlist, gen): 829 | try: 830 | self.dt = gen() 831 | genlist.append(self) 832 | except StopIteration: 833 | pass 834 | self.genlist = genlist 835 | self.gen = gen 836 | 837 | def next(self): 838 | try: 839 | self.dt = self.gen() 840 | except StopIteration: 841 | if self.genlist[0] is self: 842 | heapq.heappop(self.genlist) 843 | else: 844 | self.genlist.remove(self) 845 | heapq.heapify(self.genlist) 846 | 847 | def __cmp__(self, other): 848 | return cmp(self.dt, other.dt) 849 | 850 | def __init__(self, cache=False): 851 | rrulebase.__init__(self, cache) 852 | self._rrule = [] 853 | self._rdate = [] 854 | self._exrule = [] 855 | self._exdate = [] 856 | 857 | def rrule(self, rrule): 858 | self._rrule.append(rrule) 859 | 860 | def rdate(self, rdate): 861 | self._rdate.append(rdate) 862 | 863 | def exrule(self, exrule): 864 | self._exrule.append(exrule) 865 | 866 | def exdate(self, exdate): 867 | self._exdate.append(exdate) 868 | 869 | def _iter(self): 870 | rlist = [] 871 | self._rdate.sort() 872 | self._genitem(rlist, iter(self._rdate).next) 873 | for gen in [iter(x).next for x in self._rrule]: 874 | self._genitem(rlist, gen) 875 | heapq.heapify(rlist) 876 | exlist = [] 877 | self._exdate.sort() 878 | self._genitem(exlist, iter(self._exdate).next) 879 | for gen in [iter(x).next for x in self._exrule]: 880 | self._genitem(exlist, gen) 881 | heapq.heapify(exlist) 882 | lastdt = None 883 | total = 0 884 | while rlist: 885 | ritem = rlist[0] 886 | if not lastdt or lastdt != ritem.dt: 887 | while exlist and exlist[0] < ritem: 888 | exitem = exlist[0] 889 | exitem.next() 890 | if exlist and exlist[0] is exitem: 891 | heapq.heapreplace(exlist, exitem) 892 | if not exlist or ritem != exlist[0]: 893 | total += 1 894 | yield ritem.dt 895 | lastdt = ritem.dt 896 | ritem.next() 897 | if rlist and rlist[0] is ritem: 898 | heapq.heapreplace(rlist, ritem) 899 | self._len = total 900 | 901 | class _rrulestr: 902 | 903 | _freq_map = {"YEARLY": YEARLY, 904 | "MONTHLY": MONTHLY, 905 | "WEEKLY": WEEKLY, 906 | "DAILY": DAILY, 907 | "HOURLY": HOURLY, 908 | "MINUTELY": MINUTELY, 909 | "SECONDLY": SECONDLY} 910 | 911 | _weekday_map = {"MO":0,"TU":1,"WE":2,"TH":3,"FR":4,"SA":5,"SU":6} 912 | 913 | def _handle_int(self, rrkwargs, name, value, **kwargs): 914 | rrkwargs[name.lower()] = int(value) 915 | 916 | def _handle_int_list(self, rrkwargs, name, value, **kwargs): 917 | rrkwargs[name.lower()] = [int(x) for x in value.split(',')] 918 | 919 | _handle_INTERVAL = _handle_int 920 | _handle_COUNT = _handle_int 921 | _handle_BYSETPOS = _handle_int_list 922 | _handle_BYMONTH = _handle_int_list 923 | _handle_BYMONTHDAY = _handle_int_list 924 | _handle_BYYEARDAY = _handle_int_list 925 | _handle_BYEASTER = _handle_int_list 926 | _handle_BYWEEKNO = _handle_int_list 927 | _handle_BYHOUR = _handle_int_list 928 | _handle_BYMINUTE = _handle_int_list 929 | _handle_BYSECOND = _handle_int_list 930 | 931 | def _handle_FREQ(self, rrkwargs, name, value, **kwargs): 932 | rrkwargs["freq"] = self._freq_map[value] 933 | 934 | def _handle_UNTIL(self, rrkwargs, name, value, **kwargs): 935 | global parser 936 | if not parser: 937 | from dateutil import parser 938 | try: 939 | rrkwargs["until"] = parser.parse(value, 940 | ignoretz=kwargs.get("ignoretz"), 941 | tzinfos=kwargs.get("tzinfos")) 942 | except ValueError: 943 | raise ValueError, "invalid until date" 944 | 945 | def _handle_WKST(self, rrkwargs, name, value, **kwargs): 946 | rrkwargs["wkst"] = self._weekday_map[value] 947 | 948 | def _handle_BYWEEKDAY(self, rrkwargs, name, value, **kwarsg): 949 | l = [] 950 | for wday in value.split(','): 951 | for i in range(len(wday)): 952 | if wday[i] not in '+-0123456789': 953 | break 954 | n = wday[:i] or None 955 | w = wday[i:] 956 | if n: n = int(n) 957 | l.append(weekdays[self._weekday_map[w]](n)) 958 | rrkwargs["byweekday"] = l 959 | 960 | _handle_BYDAY = _handle_BYWEEKDAY 961 | 962 | def _parse_rfc_rrule(self, line, 963 | dtstart=None, 964 | cache=False, 965 | ignoretz=False, 966 | tzinfos=None): 967 | if line.find(':') != -1: 968 | name, value = line.split(':') 969 | if name != "RRULE": 970 | raise ValueError, "unknown parameter name" 971 | else: 972 | value = line 973 | rrkwargs = {} 974 | for pair in value.split(';'): 975 | name, value = pair.split('=') 976 | name = name.upper() 977 | value = value.upper() 978 | try: 979 | getattr(self, "_handle_"+name)(rrkwargs, name, value, 980 | ignoretz=ignoretz, 981 | tzinfos=tzinfos) 982 | except AttributeError: 983 | raise ValueError, "unknown parameter '%s'" % name 984 | except (KeyError, ValueError): 985 | raise ValueError, "invalid '%s': %s" % (name, value) 986 | return rrule(dtstart=dtstart, cache=cache, **rrkwargs) 987 | 988 | def _parse_rfc(self, s, 989 | dtstart=None, 990 | cache=False, 991 | unfold=False, 992 | forceset=False, 993 | compatible=False, 994 | ignoretz=False, 995 | tzinfos=None): 996 | global parser 997 | if compatible: 998 | forceset = True 999 | unfold = True 1000 | s = s.upper() 1001 | if not s.strip(): 1002 | raise ValueError, "empty string" 1003 | if unfold: 1004 | lines = s.splitlines() 1005 | i = 0 1006 | while i < len(lines): 1007 | line = lines[i].rstrip() 1008 | if not line: 1009 | del lines[i] 1010 | elif i > 0 and line[0] in (" ", "\t"): 1011 | lines[i-1] += line[1:] 1012 | del lines[i] 1013 | else: 1014 | i += 1 1015 | else: 1016 | lines = s.split() 1017 | if (not forceset and len(lines) == 1 and 1018 | (s.find(':') == -1 or s.startswith('RRULE:'))): 1019 | return self._parse_rfc_rrule(lines[0], cache=cache, 1020 | dtstart=dtstart, ignoretz=ignoretz, 1021 | tzinfos=tzinfos) 1022 | else: 1023 | rrulevals = [] 1024 | rdatevals = [] 1025 | exrulevals = [] 1026 | exdatevals = [] 1027 | for line in lines: 1028 | if not line: 1029 | continue 1030 | if line.find(':') == -1: 1031 | name = "RRULE" 1032 | value = line 1033 | else: 1034 | name, value = line.split(':', 1) 1035 | parms = name.split(';') 1036 | if not parms: 1037 | raise ValueError, "empty property name" 1038 | name = parms[0] 1039 | parms = parms[1:] 1040 | if name == "RRULE": 1041 | for parm in parms: 1042 | raise ValueError, "unsupported RRULE parm: "+parm 1043 | rrulevals.append(value) 1044 | elif name == "RDATE": 1045 | for parm in parms: 1046 | if parm != "VALUE=DATE-TIME": 1047 | raise ValueError, "unsupported RDATE parm: "+parm 1048 | rdatevals.append(value) 1049 | elif name == "EXRULE": 1050 | for parm in parms: 1051 | raise ValueError, "unsupported EXRULE parm: "+parm 1052 | exrulevals.append(value) 1053 | elif name == "EXDATE": 1054 | for parm in parms: 1055 | if parm != "VALUE=DATE-TIME": 1056 | raise ValueError, "unsupported RDATE parm: "+parm 1057 | exdatevals.append(value) 1058 | elif name == "DTSTART": 1059 | for parm in parms: 1060 | raise ValueError, "unsupported DTSTART parm: "+parm 1061 | if not parser: 1062 | from dateutil import parser 1063 | dtstart = parser.parse(value, ignoretz=ignoretz, 1064 | tzinfos=tzinfos) 1065 | elif name.upper().startswith('X-'): 1066 | # Ignore experimental properties. 1067 | pass 1068 | else: 1069 | raise ValueError, "unsupported property: "+name 1070 | if (forceset or len(rrulevals) > 1 or 1071 | rdatevals or exrulevals or exdatevals): 1072 | if not parser and (rdatevals or exdatevals): 1073 | from dateutil import parser 1074 | set = rruleset(cache=cache) 1075 | for value in rrulevals: 1076 | set.rrule(self._parse_rfc_rrule(value, dtstart=dtstart, 1077 | ignoretz=ignoretz, 1078 | tzinfos=tzinfos)) 1079 | for value in rdatevals: 1080 | for datestr in value.split(','): 1081 | set.rdate(parser.parse(datestr, 1082 | ignoretz=ignoretz, 1083 | tzinfos=tzinfos)) 1084 | for value in exrulevals: 1085 | set.exrule(self._parse_rfc_rrule(value, dtstart=dtstart, 1086 | ignoretz=ignoretz, 1087 | tzinfos=tzinfos)) 1088 | for value in exdatevals: 1089 | for datestr in value.split(','): 1090 | set.exdate(parser.parse(datestr, 1091 | ignoretz=ignoretz, 1092 | tzinfos=tzinfos)) 1093 | if compatible and dtstart: 1094 | set.rdate(dtstart) 1095 | return set 1096 | else: 1097 | return self._parse_rfc_rrule(rrulevals[0], 1098 | dtstart=dtstart, 1099 | cache=cache, 1100 | ignoretz=ignoretz, 1101 | tzinfos=tzinfos) 1102 | 1103 | def __call__(self, s, **kwargs): 1104 | return self._parse_rfc(s, **kwargs) 1105 | 1106 | rrulestr = _rrulestr() 1107 | 1108 | # vim:ts=4:sw=4:et 1109 | -------------------------------------------------------------------------------- /dateutil/tz.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2003-2007 Gustavo Niemeyer 3 | 4 | This module offers extensions to the standard python 2.3+ 5 | datetime module. 6 | """ 7 | __author__ = "Gustavo Niemeyer " 8 | __license__ = "PSF License" 9 | 10 | import datetime 11 | import struct 12 | import time 13 | import sys 14 | import os 15 | 16 | relativedelta = None 17 | parser = None 18 | rrule = None 19 | 20 | __all__ = ["tzutc", "tzoffset", "tzlocal", "tzfile", "tzrange", 21 | "tzstr", "tzical", "tzwin", "tzwinlocal", "gettz"] 22 | 23 | try: 24 | from dateutil.tzwin import tzwin, tzwinlocal 25 | except (ImportError, OSError): 26 | tzwin, tzwinlocal = None, None 27 | 28 | ZERO = datetime.timedelta(0) 29 | EPOCHORDINAL = datetime.datetime.utcfromtimestamp(0).toordinal() 30 | 31 | class tzutc(datetime.tzinfo): 32 | 33 | def utcoffset(self, dt): 34 | return ZERO 35 | 36 | def dst(self, dt): 37 | return ZERO 38 | 39 | def tzname(self, dt): 40 | return "UTC" 41 | 42 | def __eq__(self, other): 43 | return (isinstance(other, tzutc) or 44 | (isinstance(other, tzoffset) and other._offset == ZERO)) 45 | 46 | def __ne__(self, other): 47 | return not self.__eq__(other) 48 | 49 | def __repr__(self): 50 | return "%s()" % self.__class__.__name__ 51 | 52 | __reduce__ = object.__reduce__ 53 | 54 | class tzoffset(datetime.tzinfo): 55 | 56 | def __init__(self, name, offset): 57 | self._name = name 58 | self._offset = datetime.timedelta(seconds=offset) 59 | 60 | def utcoffset(self, dt): 61 | return self._offset 62 | 63 | def dst(self, dt): 64 | return ZERO 65 | 66 | def tzname(self, dt): 67 | return self._name 68 | 69 | def __eq__(self, other): 70 | return (isinstance(other, tzoffset) and 71 | self._offset == other._offset) 72 | 73 | def __ne__(self, other): 74 | return not self.__eq__(other) 75 | 76 | def __repr__(self): 77 | return "%s(%s, %s)" % (self.__class__.__name__, 78 | `self._name`, 79 | self._offset.days*86400+self._offset.seconds) 80 | 81 | __reduce__ = object.__reduce__ 82 | 83 | class tzlocal(datetime.tzinfo): 84 | 85 | _std_offset = datetime.timedelta(seconds=-time.timezone) 86 | if time.daylight: 87 | _dst_offset = datetime.timedelta(seconds=-time.altzone) 88 | else: 89 | _dst_offset = _std_offset 90 | 91 | def utcoffset(self, dt): 92 | if self._isdst(dt): 93 | return self._dst_offset 94 | else: 95 | return self._std_offset 96 | 97 | def dst(self, dt): 98 | if self._isdst(dt): 99 | return self._dst_offset-self._std_offset 100 | else: 101 | return ZERO 102 | 103 | def tzname(self, dt): 104 | return time.tzname[self._isdst(dt)] 105 | 106 | def _isdst(self, dt): 107 | # We can't use mktime here. It is unstable when deciding if 108 | # the hour near to a change is DST or not. 109 | # 110 | # timestamp = time.mktime((dt.year, dt.month, dt.day, dt.hour, 111 | # dt.minute, dt.second, dt.weekday(), 0, -1)) 112 | # return time.localtime(timestamp).tm_isdst 113 | # 114 | # The code above yields the following result: 115 | # 116 | #>>> import tz, datetime 117 | #>>> t = tz.tzlocal() 118 | #>>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname() 119 | #'BRDT' 120 | #>>> datetime.datetime(2003,2,16,0,tzinfo=t).tzname() 121 | #'BRST' 122 | #>>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname() 123 | #'BRST' 124 | #>>> datetime.datetime(2003,2,15,22,tzinfo=t).tzname() 125 | #'BRDT' 126 | #>>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname() 127 | #'BRDT' 128 | # 129 | # Here is a more stable implementation: 130 | # 131 | timestamp = ((dt.toordinal() - EPOCHORDINAL) * 86400 132 | + dt.hour * 3600 133 | + dt.minute * 60 134 | + dt.second) 135 | return time.localtime(timestamp+time.timezone).tm_isdst 136 | 137 | def __eq__(self, other): 138 | if not isinstance(other, tzlocal): 139 | return False 140 | return (self._std_offset == other._std_offset and 141 | self._dst_offset == other._dst_offset) 142 | return True 143 | 144 | def __ne__(self, other): 145 | return not self.__eq__(other) 146 | 147 | def __repr__(self): 148 | return "%s()" % self.__class__.__name__ 149 | 150 | __reduce__ = object.__reduce__ 151 | 152 | class _ttinfo(object): 153 | __slots__ = ["offset", "delta", "isdst", "abbr", "isstd", "isgmt"] 154 | 155 | def __init__(self): 156 | for attr in self.__slots__: 157 | setattr(self, attr, None) 158 | 159 | def __repr__(self): 160 | l = [] 161 | for attr in self.__slots__: 162 | value = getattr(self, attr) 163 | if value is not None: 164 | l.append("%s=%s" % (attr, `value`)) 165 | return "%s(%s)" % (self.__class__.__name__, ", ".join(l)) 166 | 167 | def __eq__(self, other): 168 | if not isinstance(other, _ttinfo): 169 | return False 170 | return (self.offset == other.offset and 171 | self.delta == other.delta and 172 | self.isdst == other.isdst and 173 | self.abbr == other.abbr and 174 | self.isstd == other.isstd and 175 | self.isgmt == other.isgmt) 176 | 177 | def __ne__(self, other): 178 | return not self.__eq__(other) 179 | 180 | def __getstate__(self): 181 | state = {} 182 | for name in self.__slots__: 183 | state[name] = getattr(self, name, None) 184 | return state 185 | 186 | def __setstate__(self, state): 187 | for name in self.__slots__: 188 | if name in state: 189 | setattr(self, name, state[name]) 190 | 191 | class tzfile(datetime.tzinfo): 192 | 193 | # http://www.twinsun.com/tz/tz-link.htm 194 | # ftp://elsie.nci.nih.gov/pub/tz*.tar.gz 195 | 196 | def __init__(self, fileobj): 197 | if isinstance(fileobj, basestring): 198 | self._filename = fileobj 199 | fileobj = open(fileobj) 200 | elif hasattr(fileobj, "name"): 201 | self._filename = fileobj.name 202 | else: 203 | self._filename = `fileobj` 204 | 205 | # From tzfile(5): 206 | # 207 | # The time zone information files used by tzset(3) 208 | # begin with the magic characters "TZif" to identify 209 | # them as time zone information files, followed by 210 | # sixteen bytes reserved for future use, followed by 211 | # six four-byte values of type long, written in a 212 | # ``standard'' byte order (the high-order byte 213 | # of the value is written first). 214 | 215 | if fileobj.read(4) != "TZif": 216 | raise ValueError, "magic not found" 217 | 218 | fileobj.read(16) 219 | 220 | ( 221 | # The number of UTC/local indicators stored in the file. 222 | ttisgmtcnt, 223 | 224 | # The number of standard/wall indicators stored in the file. 225 | ttisstdcnt, 226 | 227 | # The number of leap seconds for which data is 228 | # stored in the file. 229 | leapcnt, 230 | 231 | # The number of "transition times" for which data 232 | # is stored in the file. 233 | timecnt, 234 | 235 | # The number of "local time types" for which data 236 | # is stored in the file (must not be zero). 237 | typecnt, 238 | 239 | # The number of characters of "time zone 240 | # abbreviation strings" stored in the file. 241 | charcnt, 242 | 243 | ) = struct.unpack(">6l", fileobj.read(24)) 244 | 245 | # The above header is followed by tzh_timecnt four-byte 246 | # values of type long, sorted in ascending order. 247 | # These values are written in ``standard'' byte order. 248 | # Each is used as a transition time (as returned by 249 | # time(2)) at which the rules for computing local time 250 | # change. 251 | 252 | if timecnt: 253 | self._trans_list = struct.unpack(">%dl" % timecnt, 254 | fileobj.read(timecnt*4)) 255 | else: 256 | self._trans_list = [] 257 | 258 | # Next come tzh_timecnt one-byte values of type unsigned 259 | # char; each one tells which of the different types of 260 | # ``local time'' types described in the file is associated 261 | # with the same-indexed transition time. These values 262 | # serve as indices into an array of ttinfo structures that 263 | # appears next in the file. 264 | 265 | if timecnt: 266 | self._trans_idx = struct.unpack(">%dB" % timecnt, 267 | fileobj.read(timecnt)) 268 | else: 269 | self._trans_idx = [] 270 | 271 | # Each ttinfo structure is written as a four-byte value 272 | # for tt_gmtoff of type long, in a standard byte 273 | # order, followed by a one-byte value for tt_isdst 274 | # and a one-byte value for tt_abbrind. In each 275 | # structure, tt_gmtoff gives the number of 276 | # seconds to be added to UTC, tt_isdst tells whether 277 | # tm_isdst should be set by localtime(3), and 278 | # tt_abbrind serves as an index into the array of 279 | # time zone abbreviation characters that follow the 280 | # ttinfo structure(s) in the file. 281 | 282 | ttinfo = [] 283 | 284 | for i in range(typecnt): 285 | ttinfo.append(struct.unpack(">lbb", fileobj.read(6))) 286 | 287 | abbr = fileobj.read(charcnt) 288 | 289 | # Then there are tzh_leapcnt pairs of four-byte 290 | # values, written in standard byte order; the 291 | # first value of each pair gives the time (as 292 | # returned by time(2)) at which a leap second 293 | # occurs; the second gives the total number of 294 | # leap seconds to be applied after the given time. 295 | # The pairs of values are sorted in ascending order 296 | # by time. 297 | 298 | # Not used, for now 299 | if leapcnt: 300 | leap = struct.unpack(">%dl" % (leapcnt*2), 301 | fileobj.read(leapcnt*8)) 302 | 303 | # Then there are tzh_ttisstdcnt standard/wall 304 | # indicators, each stored as a one-byte value; 305 | # they tell whether the transition times associated 306 | # with local time types were specified as standard 307 | # time or wall clock time, and are used when 308 | # a time zone file is used in handling POSIX-style 309 | # time zone environment variables. 310 | 311 | if ttisstdcnt: 312 | isstd = struct.unpack(">%db" % ttisstdcnt, 313 | fileobj.read(ttisstdcnt)) 314 | 315 | # Finally, there are tzh_ttisgmtcnt UTC/local 316 | # indicators, each stored as a one-byte value; 317 | # they tell whether the transition times associated 318 | # with local time types were specified as UTC or 319 | # local time, and are used when a time zone file 320 | # is used in handling POSIX-style time zone envi- 321 | # ronment variables. 322 | 323 | if ttisgmtcnt: 324 | isgmt = struct.unpack(">%db" % ttisgmtcnt, 325 | fileobj.read(ttisgmtcnt)) 326 | 327 | # ** Everything has been read ** 328 | 329 | # Build ttinfo list 330 | self._ttinfo_list = [] 331 | for i in range(typecnt): 332 | gmtoff, isdst, abbrind = ttinfo[i] 333 | # Round to full-minutes if that's not the case. Python's 334 | # datetime doesn't accept sub-minute timezones. Check 335 | # http://python.org/sf/1447945 for some information. 336 | gmtoff = (gmtoff+30)//60*60 337 | tti = _ttinfo() 338 | tti.offset = gmtoff 339 | tti.delta = datetime.timedelta(seconds=gmtoff) 340 | tti.isdst = isdst 341 | tti.abbr = abbr[abbrind:abbr.find('\x00', abbrind)] 342 | tti.isstd = (ttisstdcnt > i and isstd[i] != 0) 343 | tti.isgmt = (ttisgmtcnt > i and isgmt[i] != 0) 344 | self._ttinfo_list.append(tti) 345 | 346 | # Replace ttinfo indexes for ttinfo objects. 347 | trans_idx = [] 348 | for idx in self._trans_idx: 349 | trans_idx.append(self._ttinfo_list[idx]) 350 | self._trans_idx = tuple(trans_idx) 351 | 352 | # Set standard, dst, and before ttinfos. before will be 353 | # used when a given time is before any transitions, 354 | # and will be set to the first non-dst ttinfo, or to 355 | # the first dst, if all of them are dst. 356 | self._ttinfo_std = None 357 | self._ttinfo_dst = None 358 | self._ttinfo_before = None 359 | if self._ttinfo_list: 360 | if not self._trans_list: 361 | self._ttinfo_std = self._ttinfo_first = self._ttinfo_list[0] 362 | else: 363 | for i in range(timecnt-1,-1,-1): 364 | tti = self._trans_idx[i] 365 | if not self._ttinfo_std and not tti.isdst: 366 | self._ttinfo_std = tti 367 | elif not self._ttinfo_dst and tti.isdst: 368 | self._ttinfo_dst = tti 369 | if self._ttinfo_std and self._ttinfo_dst: 370 | break 371 | else: 372 | if self._ttinfo_dst and not self._ttinfo_std: 373 | self._ttinfo_std = self._ttinfo_dst 374 | 375 | for tti in self._ttinfo_list: 376 | if not tti.isdst: 377 | self._ttinfo_before = tti 378 | break 379 | else: 380 | self._ttinfo_before = self._ttinfo_list[0] 381 | 382 | # Now fix transition times to become relative to wall time. 383 | # 384 | # I'm not sure about this. In my tests, the tz source file 385 | # is setup to wall time, and in the binary file isstd and 386 | # isgmt are off, so it should be in wall time. OTOH, it's 387 | # always in gmt time. Let me know if you have comments 388 | # about this. 389 | laststdoffset = 0 390 | self._trans_list = list(self._trans_list) 391 | for i in range(len(self._trans_list)): 392 | tti = self._trans_idx[i] 393 | if not tti.isdst: 394 | # This is std time. 395 | self._trans_list[i] += tti.offset 396 | laststdoffset = tti.offset 397 | else: 398 | # This is dst time. Convert to std. 399 | self._trans_list[i] += laststdoffset 400 | self._trans_list = tuple(self._trans_list) 401 | 402 | def _find_ttinfo(self, dt, laststd=0): 403 | timestamp = ((dt.toordinal() - EPOCHORDINAL) * 86400 404 | + dt.hour * 3600 405 | + dt.minute * 60 406 | + dt.second) 407 | idx = 0 408 | for trans in self._trans_list: 409 | if timestamp < trans: 410 | break 411 | idx += 1 412 | else: 413 | return self._ttinfo_std 414 | if idx == 0: 415 | return self._ttinfo_before 416 | if laststd: 417 | while idx > 0: 418 | tti = self._trans_idx[idx-1] 419 | if not tti.isdst: 420 | return tti 421 | idx -= 1 422 | else: 423 | return self._ttinfo_std 424 | else: 425 | return self._trans_idx[idx-1] 426 | 427 | def utcoffset(self, dt): 428 | if not self._ttinfo_std: 429 | return ZERO 430 | return self._find_ttinfo(dt).delta 431 | 432 | def dst(self, dt): 433 | if not self._ttinfo_dst: 434 | return ZERO 435 | tti = self._find_ttinfo(dt) 436 | if not tti.isdst: 437 | return ZERO 438 | 439 | # The documentation says that utcoffset()-dst() must 440 | # be constant for every dt. 441 | return tti.delta-self._find_ttinfo(dt, laststd=1).delta 442 | 443 | # An alternative for that would be: 444 | # 445 | # return self._ttinfo_dst.offset-self._ttinfo_std.offset 446 | # 447 | # However, this class stores historical changes in the 448 | # dst offset, so I belive that this wouldn't be the right 449 | # way to implement this. 450 | 451 | def tzname(self, dt): 452 | if not self._ttinfo_std: 453 | return None 454 | return self._find_ttinfo(dt).abbr 455 | 456 | def __eq__(self, other): 457 | if not isinstance(other, tzfile): 458 | return False 459 | return (self._trans_list == other._trans_list and 460 | self._trans_idx == other._trans_idx and 461 | self._ttinfo_list == other._ttinfo_list) 462 | 463 | def __ne__(self, other): 464 | return not self.__eq__(other) 465 | 466 | 467 | def __repr__(self): 468 | return "%s(%s)" % (self.__class__.__name__, `self._filename`) 469 | 470 | def __reduce__(self): 471 | if not os.path.isfile(self._filename): 472 | raise ValueError, "Unpickable %s class" % self.__class__.__name__ 473 | return (self.__class__, (self._filename,)) 474 | 475 | class tzrange(datetime.tzinfo): 476 | 477 | def __init__(self, stdabbr, stdoffset=None, 478 | dstabbr=None, dstoffset=None, 479 | start=None, end=None): 480 | global relativedelta 481 | if not relativedelta: 482 | from dateutil import relativedelta 483 | self._std_abbr = stdabbr 484 | self._dst_abbr = dstabbr 485 | if stdoffset is not None: 486 | self._std_offset = datetime.timedelta(seconds=stdoffset) 487 | else: 488 | self._std_offset = ZERO 489 | if dstoffset is not None: 490 | self._dst_offset = datetime.timedelta(seconds=dstoffset) 491 | elif dstabbr and stdoffset is not None: 492 | self._dst_offset = self._std_offset+datetime.timedelta(hours=+1) 493 | else: 494 | self._dst_offset = ZERO 495 | if dstabbr and start is None: 496 | self._start_delta = relativedelta.relativedelta( 497 | hours=+2, month=4, day=1, weekday=relativedelta.SU(+1)) 498 | else: 499 | self._start_delta = start 500 | if dstabbr and end is None: 501 | self._end_delta = relativedelta.relativedelta( 502 | hours=+1, month=10, day=31, weekday=relativedelta.SU(-1)) 503 | else: 504 | self._end_delta = end 505 | 506 | def utcoffset(self, dt): 507 | if self._isdst(dt): 508 | return self._dst_offset 509 | else: 510 | return self._std_offset 511 | 512 | def dst(self, dt): 513 | if self._isdst(dt): 514 | return self._dst_offset-self._std_offset 515 | else: 516 | return ZERO 517 | 518 | def tzname(self, dt): 519 | if self._isdst(dt): 520 | return self._dst_abbr 521 | else: 522 | return self._std_abbr 523 | 524 | def _isdst(self, dt): 525 | if not self._start_delta: 526 | return False 527 | year = datetime.datetime(dt.year,1,1) 528 | start = year+self._start_delta 529 | end = year+self._end_delta 530 | dt = dt.replace(tzinfo=None) 531 | if start < end: 532 | return dt >= start and dt < end 533 | else: 534 | return dt >= start or dt < end 535 | 536 | def __eq__(self, other): 537 | if not isinstance(other, tzrange): 538 | return False 539 | return (self._std_abbr == other._std_abbr and 540 | self._dst_abbr == other._dst_abbr and 541 | self._std_offset == other._std_offset and 542 | self._dst_offset == other._dst_offset and 543 | self._start_delta == other._start_delta and 544 | self._end_delta == other._end_delta) 545 | 546 | def __ne__(self, other): 547 | return not self.__eq__(other) 548 | 549 | def __repr__(self): 550 | return "%s(...)" % self.__class__.__name__ 551 | 552 | __reduce__ = object.__reduce__ 553 | 554 | class tzstr(tzrange): 555 | 556 | def __init__(self, s): 557 | global parser 558 | if not parser: 559 | from dateutil import parser 560 | self._s = s 561 | 562 | res = parser._parsetz(s) 563 | if res is None: 564 | raise ValueError, "unknown string format" 565 | 566 | # Here we break the compatibility with the TZ variable handling. 567 | # GMT-3 actually *means* the timezone -3. 568 | if res.stdabbr in ("GMT", "UTC"): 569 | res.stdoffset *= -1 570 | 571 | # We must initialize it first, since _delta() needs 572 | # _std_offset and _dst_offset set. Use False in start/end 573 | # to avoid building it two times. 574 | tzrange.__init__(self, res.stdabbr, res.stdoffset, 575 | res.dstabbr, res.dstoffset, 576 | start=False, end=False) 577 | 578 | if not res.dstabbr: 579 | self._start_delta = None 580 | self._end_delta = None 581 | else: 582 | self._start_delta = self._delta(res.start) 583 | if self._start_delta: 584 | self._end_delta = self._delta(res.end, isend=1) 585 | 586 | def _delta(self, x, isend=0): 587 | kwargs = {} 588 | if x.month is not None: 589 | kwargs["month"] = x.month 590 | if x.weekday is not None: 591 | kwargs["weekday"] = relativedelta.weekday(x.weekday, x.week) 592 | if x.week > 0: 593 | kwargs["day"] = 1 594 | else: 595 | kwargs["day"] = 31 596 | elif x.day: 597 | kwargs["day"] = x.day 598 | elif x.yday is not None: 599 | kwargs["yearday"] = x.yday 600 | elif x.jyday is not None: 601 | kwargs["nlyearday"] = x.jyday 602 | if not kwargs: 603 | # Default is to start on first sunday of april, and end 604 | # on last sunday of october. 605 | if not isend: 606 | kwargs["month"] = 4 607 | kwargs["day"] = 1 608 | kwargs["weekday"] = relativedelta.SU(+1) 609 | else: 610 | kwargs["month"] = 10 611 | kwargs["day"] = 31 612 | kwargs["weekday"] = relativedelta.SU(-1) 613 | if x.time is not None: 614 | kwargs["seconds"] = x.time 615 | else: 616 | # Default is 2AM. 617 | kwargs["seconds"] = 7200 618 | if isend: 619 | # Convert to standard time, to follow the documented way 620 | # of working with the extra hour. See the documentation 621 | # of the tzinfo class. 622 | delta = self._dst_offset-self._std_offset 623 | kwargs["seconds"] -= delta.seconds+delta.days*86400 624 | return relativedelta.relativedelta(**kwargs) 625 | 626 | def __repr__(self): 627 | return "%s(%s)" % (self.__class__.__name__, `self._s`) 628 | 629 | class _tzicalvtzcomp: 630 | def __init__(self, tzoffsetfrom, tzoffsetto, isdst, 631 | tzname=None, rrule=None): 632 | self.tzoffsetfrom = datetime.timedelta(seconds=tzoffsetfrom) 633 | self.tzoffsetto = datetime.timedelta(seconds=tzoffsetto) 634 | self.tzoffsetdiff = self.tzoffsetto-self.tzoffsetfrom 635 | self.isdst = isdst 636 | self.tzname = tzname 637 | self.rrule = rrule 638 | 639 | class _tzicalvtz(datetime.tzinfo): 640 | def __init__(self, tzid, comps=[]): 641 | self._tzid = tzid 642 | self._comps = comps 643 | self._cachedate = [] 644 | self._cachecomp = [] 645 | 646 | def _find_comp(self, dt): 647 | if len(self._comps) == 1: 648 | return self._comps[0] 649 | dt = dt.replace(tzinfo=None) 650 | try: 651 | return self._cachecomp[self._cachedate.index(dt)] 652 | except ValueError: 653 | pass 654 | lastcomp = None 655 | lastcompdt = None 656 | for comp in self._comps: 657 | if not comp.isdst: 658 | # Handle the extra hour in DST -> STD 659 | compdt = comp.rrule.before(dt-comp.tzoffsetdiff, inc=True) 660 | else: 661 | compdt = comp.rrule.before(dt, inc=True) 662 | if compdt and (not lastcompdt or lastcompdt < compdt): 663 | lastcompdt = compdt 664 | lastcomp = comp 665 | if not lastcomp: 666 | # RFC says nothing about what to do when a given 667 | # time is before the first onset date. We'll look for the 668 | # first standard component, or the first component, if 669 | # none is found. 670 | for comp in self._comps: 671 | if not comp.isdst: 672 | lastcomp = comp 673 | break 674 | else: 675 | lastcomp = comp[0] 676 | self._cachedate.insert(0, dt) 677 | self._cachecomp.insert(0, lastcomp) 678 | if len(self._cachedate) > 10: 679 | self._cachedate.pop() 680 | self._cachecomp.pop() 681 | return lastcomp 682 | 683 | def utcoffset(self, dt): 684 | return self._find_comp(dt).tzoffsetto 685 | 686 | def dst(self, dt): 687 | comp = self._find_comp(dt) 688 | if comp.isdst: 689 | return comp.tzoffsetdiff 690 | else: 691 | return ZERO 692 | 693 | def tzname(self, dt): 694 | return self._find_comp(dt).tzname 695 | 696 | def __repr__(self): 697 | return "" % `self._tzid` 698 | 699 | __reduce__ = object.__reduce__ 700 | 701 | class tzical: 702 | def __init__(self, fileobj): 703 | global rrule 704 | if not rrule: 705 | from dateutil import rrule 706 | 707 | if isinstance(fileobj, basestring): 708 | self._s = fileobj 709 | fileobj = open(fileobj) 710 | elif hasattr(fileobj, "name"): 711 | self._s = fileobj.name 712 | else: 713 | self._s = `fileobj` 714 | 715 | self._vtz = {} 716 | 717 | self._parse_rfc(fileobj.read()) 718 | 719 | def keys(self): 720 | return self._vtz.keys() 721 | 722 | def get(self, tzid=None): 723 | if tzid is None: 724 | keys = self._vtz.keys() 725 | if len(keys) == 0: 726 | raise ValueError, "no timezones defined" 727 | elif len(keys) > 1: 728 | raise ValueError, "more than one timezone available" 729 | tzid = keys[0] 730 | return self._vtz.get(tzid) 731 | 732 | def _parse_offset(self, s): 733 | s = s.strip() 734 | if not s: 735 | raise ValueError, "empty offset" 736 | if s[0] in ('+', '-'): 737 | signal = (-1,+1)[s[0]=='+'] 738 | s = s[1:] 739 | else: 740 | signal = +1 741 | if len(s) == 4: 742 | return (int(s[:2])*3600+int(s[2:])*60)*signal 743 | elif len(s) == 6: 744 | return (int(s[:2])*3600+int(s[2:4])*60+int(s[4:]))*signal 745 | else: 746 | raise ValueError, "invalid offset: "+s 747 | 748 | def _parse_rfc(self, s): 749 | lines = s.splitlines() 750 | if not lines: 751 | raise ValueError, "empty string" 752 | 753 | # Unfold 754 | i = 0 755 | while i < len(lines): 756 | line = lines[i].rstrip() 757 | if not line: 758 | del lines[i] 759 | elif i > 0 and line[0] in (" ", "\t"): 760 | lines[i-1] += line[1:] 761 | del lines[i] 762 | else: 763 | i += 1 764 | 765 | tzid = None 766 | comps = [] 767 | invtz = False 768 | comptype = None 769 | for line in lines: 770 | if not line: 771 | continue 772 | name, value = line.split(':', 1) 773 | parms = name.split(';') 774 | if not parms: 775 | raise ValueError, "empty property name" 776 | name = parms[0].upper() 777 | parms = parms[1:] 778 | if invtz: 779 | if name == "BEGIN": 780 | if value in ("STANDARD", "DAYLIGHT"): 781 | # Process component 782 | pass 783 | else: 784 | raise ValueError, "unknown component: "+value 785 | comptype = value 786 | founddtstart = False 787 | tzoffsetfrom = None 788 | tzoffsetto = None 789 | rrulelines = [] 790 | tzname = None 791 | elif name == "END": 792 | if value == "VTIMEZONE": 793 | if comptype: 794 | raise ValueError, \ 795 | "component not closed: "+comptype 796 | if not tzid: 797 | raise ValueError, \ 798 | "mandatory TZID not found" 799 | if not comps: 800 | raise ValueError, \ 801 | "at least one component is needed" 802 | # Process vtimezone 803 | self._vtz[tzid] = _tzicalvtz(tzid, comps) 804 | invtz = False 805 | elif value == comptype: 806 | if not founddtstart: 807 | raise ValueError, \ 808 | "mandatory DTSTART not found" 809 | if tzoffsetfrom is None: 810 | raise ValueError, \ 811 | "mandatory TZOFFSETFROM not found" 812 | if tzoffsetto is None: 813 | raise ValueError, \ 814 | "mandatory TZOFFSETFROM not found" 815 | # Process component 816 | rr = None 817 | if rrulelines: 818 | rr = rrule.rrulestr("\n".join(rrulelines), 819 | compatible=True, 820 | ignoretz=True, 821 | cache=True) 822 | comp = _tzicalvtzcomp(tzoffsetfrom, tzoffsetto, 823 | (comptype == "DAYLIGHT"), 824 | tzname, rr) 825 | comps.append(comp) 826 | comptype = None 827 | else: 828 | raise ValueError, \ 829 | "invalid component end: "+value 830 | elif comptype: 831 | if name == "DTSTART": 832 | rrulelines.append(line) 833 | founddtstart = True 834 | elif name in ("RRULE", "RDATE", "EXRULE", "EXDATE"): 835 | rrulelines.append(line) 836 | elif name == "TZOFFSETFROM": 837 | if parms: 838 | raise ValueError, \ 839 | "unsupported %s parm: %s "%(name, parms[0]) 840 | tzoffsetfrom = self._parse_offset(value) 841 | elif name == "TZOFFSETTO": 842 | if parms: 843 | raise ValueError, \ 844 | "unsupported TZOFFSETTO parm: "+parms[0] 845 | tzoffsetto = self._parse_offset(value) 846 | elif name == "TZNAME": 847 | if parms: 848 | raise ValueError, \ 849 | "unsupported TZNAME parm: "+parms[0] 850 | tzname = value 851 | elif name == "COMMENT": 852 | pass 853 | elif name.upper().startswith('X-'): 854 | # Ignore experimental properties. 855 | pass 856 | else: 857 | raise ValueError, "unsupported property: "+name 858 | else: 859 | if name == "TZID": 860 | for p in parms: 861 | if not p.upper().startswith('X-'): 862 | raise ValueError, \ 863 | "unsupported TZID parm: "+p 864 | tzid = value 865 | elif name in ("TZURL", "LAST-MODIFIED", "COMMENT"): 866 | pass 867 | elif name.upper().startswith('X-'): 868 | # Ignore experimental properties. 869 | pass 870 | else: 871 | raise ValueError, "unsupported property: "+name 872 | elif name == "BEGIN" and value == "VTIMEZONE": 873 | tzid = None 874 | comps = [] 875 | invtz = True 876 | 877 | def __repr__(self): 878 | return "%s(%s)" % (self.__class__.__name__, `self._s`) 879 | 880 | if sys.platform != "win32": 881 | TZFILES = ["/etc/localtime", "localtime"] 882 | TZPATHS = ["/usr/share/zoneinfo", "/usr/lib/zoneinfo", "/etc/zoneinfo"] 883 | else: 884 | TZFILES = [] 885 | TZPATHS = [] 886 | 887 | def gettz(name=None): 888 | tz = None 889 | if not name: 890 | try: 891 | name = os.environ["TZ"] 892 | except KeyError: 893 | pass 894 | if name is None or name == ":": 895 | for filepath in TZFILES: 896 | if not os.path.isabs(filepath): 897 | filename = filepath 898 | for path in TZPATHS: 899 | filepath = os.path.join(path, filename) 900 | if os.path.isfile(filepath): 901 | break 902 | else: 903 | continue 904 | if os.path.isfile(filepath): 905 | try: 906 | tz = tzfile(filepath) 907 | break 908 | except (IOError, OSError, ValueError): 909 | pass 910 | else: 911 | tz = tzlocal() 912 | else: 913 | if name.startswith(":"): 914 | name = name[:-1] 915 | if os.path.isabs(name): 916 | if os.path.isfile(name): 917 | tz = tzfile(name) 918 | else: 919 | tz = None 920 | else: 921 | for path in TZPATHS: 922 | filepath = os.path.join(path, name) 923 | if not os.path.isfile(filepath): 924 | filepath = filepath.replace(' ','_') 925 | if not os.path.isfile(filepath): 926 | continue 927 | try: 928 | tz = tzfile(filepath) 929 | break 930 | except (IOError, OSError, ValueError): 931 | pass 932 | else: 933 | tz = None 934 | if tzwin: 935 | try: 936 | tz = tzwin(name) 937 | except OSError: 938 | pass 939 | if not tz: 940 | from dateutil.zoneinfo import gettz 941 | tz = gettz(name) 942 | if not tz: 943 | for c in name: 944 | # name must have at least one offset to be a tzstr 945 | if c in "0123456789": 946 | try: 947 | tz = tzstr(name) 948 | except ValueError: 949 | pass 950 | break 951 | else: 952 | if name in ("GMT", "UTC"): 953 | tz = tzutc() 954 | elif name in time.tzname: 955 | tz = tzlocal() 956 | return tz 957 | 958 | # vim:ts=4:sw=4:et 959 | -------------------------------------------------------------------------------- /dateutil/tzwin.py: -------------------------------------------------------------------------------- 1 | # This code was originally contributed by Jeffrey Harris. 2 | import datetime 3 | import struct 4 | import _winreg 5 | 6 | __author__ = "Jeffrey Harris & Gustavo Niemeyer " 7 | 8 | __all__ = ["tzwin", "tzwinlocal"] 9 | 10 | ONEWEEK = datetime.timedelta(7) 11 | 12 | TZKEYNAMENT = r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones" 13 | TZKEYNAME9X = r"SOFTWARE\Microsoft\Windows\CurrentVersion\Time Zones" 14 | TZLOCALKEYNAME = r"SYSTEM\CurrentControlSet\Control\TimeZoneInformation" 15 | 16 | def _settzkeyname(): 17 | global TZKEYNAME 18 | handle = _winreg.ConnectRegistry(None, _winreg.HKEY_LOCAL_MACHINE) 19 | try: 20 | _winreg.OpenKey(handle, TZKEYNAMENT).Close() 21 | TZKEYNAME = TZKEYNAMENT 22 | except WindowsError: 23 | TZKEYNAME = TZKEYNAME9X 24 | handle.Close() 25 | 26 | _settzkeyname() 27 | 28 | class tzwinbase(datetime.tzinfo): 29 | """tzinfo class based on win32's timezones available in the registry.""" 30 | 31 | def utcoffset(self, dt): 32 | if self._isdst(dt): 33 | return datetime.timedelta(minutes=self._dstoffset) 34 | else: 35 | return datetime.timedelta(minutes=self._stdoffset) 36 | 37 | def dst(self, dt): 38 | if self._isdst(dt): 39 | minutes = self._dstoffset - self._stdoffset 40 | return datetime.timedelta(minutes=minutes) 41 | else: 42 | return datetime.timedelta(0) 43 | 44 | def tzname(self, dt): 45 | if self._isdst(dt): 46 | return self._dstname 47 | else: 48 | return self._stdname 49 | 50 | def list(): 51 | """Return a list of all time zones known to the system.""" 52 | handle = _winreg.ConnectRegistry(None, _winreg.HKEY_LOCAL_MACHINE) 53 | tzkey = _winreg.OpenKey(handle, TZKEYNAME) 54 | result = [_winreg.EnumKey(tzkey, i) 55 | for i in range(_winreg.QueryInfoKey(tzkey)[0])] 56 | tzkey.Close() 57 | handle.Close() 58 | return result 59 | list = staticmethod(list) 60 | 61 | def display(self): 62 | return self._display 63 | 64 | def _isdst(self, dt): 65 | dston = picknthweekday(dt.year, self._dstmonth, self._dstdayofweek, 66 | self._dsthour, self._dstminute, 67 | self._dstweeknumber) 68 | dstoff = picknthweekday(dt.year, self._stdmonth, self._stddayofweek, 69 | self._stdhour, self._stdminute, 70 | self._stdweeknumber) 71 | if dston < dstoff: 72 | return dston <= dt.replace(tzinfo=None) < dstoff 73 | else: 74 | return not dstoff <= dt.replace(tzinfo=None) < dston 75 | 76 | 77 | class tzwin(tzwinbase): 78 | 79 | def __init__(self, name): 80 | self._name = name 81 | 82 | handle = _winreg.ConnectRegistry(None, _winreg.HKEY_LOCAL_MACHINE) 83 | tzkey = _winreg.OpenKey(handle, "%s\%s" % (TZKEYNAME, name)) 84 | keydict = valuestodict(tzkey) 85 | tzkey.Close() 86 | handle.Close() 87 | 88 | self._stdname = keydict["Std"].encode("iso-8859-1") 89 | self._dstname = keydict["Dlt"].encode("iso-8859-1") 90 | 91 | self._display = keydict["Display"] 92 | 93 | # See http://ww_winreg.jsiinc.com/SUBA/tip0300/rh0398.htm 94 | tup = struct.unpack("=3l16h", keydict["TZI"]) 95 | self._stdoffset = -tup[0]-tup[1] # Bias + StandardBias * -1 96 | self._dstoffset = self._stdoffset-tup[2] # + DaylightBias * -1 97 | 98 | (self._stdmonth, 99 | self._stddayofweek, # Sunday = 0 100 | self._stdweeknumber, # Last = 5 101 | self._stdhour, 102 | self._stdminute) = tup[4:9] 103 | 104 | (self._dstmonth, 105 | self._dstdayofweek, # Sunday = 0 106 | self._dstweeknumber, # Last = 5 107 | self._dsthour, 108 | self._dstminute) = tup[12:17] 109 | 110 | def __repr__(self): 111 | return "tzwin(%s)" % repr(self._name) 112 | 113 | def __reduce__(self): 114 | return (self.__class__, (self._name,)) 115 | 116 | 117 | class tzwinlocal(tzwinbase): 118 | 119 | def __init__(self): 120 | 121 | handle = _winreg.ConnectRegistry(None, _winreg.HKEY_LOCAL_MACHINE) 122 | 123 | tzlocalkey = _winreg.OpenKey(handle, TZLOCALKEYNAME) 124 | keydict = valuestodict(tzlocalkey) 125 | tzlocalkey.Close() 126 | 127 | self._stdname = keydict["StandardName"].encode("iso-8859-1") 128 | self._dstname = keydict["DaylightName"].encode("iso-8859-1") 129 | 130 | try: 131 | tzkey = _winreg.OpenKey(handle, "%s\%s"%(TZKEYNAME, self._stdname)) 132 | _keydict = valuestodict(tzkey) 133 | self._display = _keydict["Display"] 134 | tzkey.Close() 135 | except OSError: 136 | self._display = None 137 | 138 | handle.Close() 139 | 140 | self._stdoffset = -keydict["Bias"]-keydict["StandardBias"] 141 | self._dstoffset = self._stdoffset-keydict["DaylightBias"] 142 | 143 | 144 | # See http://ww_winreg.jsiinc.com/SUBA/tip0300/rh0398.htm 145 | tup = struct.unpack("=8h", keydict["StandardStart"]) 146 | 147 | (self._stdmonth, 148 | self._stddayofweek, # Sunday = 0 149 | self._stdweeknumber, # Last = 5 150 | self._stdhour, 151 | self._stdminute) = tup[1:6] 152 | 153 | tup = struct.unpack("=8h", keydict["DaylightStart"]) 154 | 155 | (self._dstmonth, 156 | self._dstdayofweek, # Sunday = 0 157 | self._dstweeknumber, # Last = 5 158 | self._dsthour, 159 | self._dstminute) = tup[1:6] 160 | 161 | def __reduce__(self): 162 | return (self.__class__, ()) 163 | 164 | def picknthweekday(year, month, dayofweek, hour, minute, whichweek): 165 | """dayofweek == 0 means Sunday, whichweek 5 means last instance""" 166 | first = datetime.datetime(year, month, 1, hour, minute) 167 | weekdayone = first.replace(day=((dayofweek-first.isoweekday())%7+1)) 168 | for n in xrange(whichweek): 169 | dt = weekdayone+(whichweek-n)*ONEWEEK 170 | if dt.month == month: 171 | return dt 172 | 173 | def valuestodict(key): 174 | """Convert a registry key's values to a dictionary.""" 175 | dict = {} 176 | size = _winreg.QueryInfoKey(key)[1] 177 | for i in range(size): 178 | data = _winreg.EnumValue(key, i) 179 | dict[data[0]] = data[1] 180 | return dict 181 | -------------------------------------------------------------------------------- /dateutil/zoneinfo/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2003-2005 Gustavo Niemeyer 3 | 4 | This module offers extensions to the standard python 2.3+ 5 | datetime module. 6 | """ 7 | from dateutil.tz import tzfile 8 | from tarfile import TarFile 9 | import os 10 | 11 | __author__ = "Gustavo Niemeyer " 12 | __license__ = "PSF License" 13 | 14 | __all__ = ["setcachesize", "gettz", "rebuild"] 15 | 16 | CACHE = {} 17 | 18 | class tzfile(tzfile): 19 | def __reduce__(self): 20 | return (gettz, (self._filename,)) 21 | 22 | def getzoneinfofile(): 23 | filenames = os.listdir(os.path.join(os.path.dirname(__file__))) 24 | filenames.sort() 25 | filenames.reverse() 26 | for entry in filenames: 27 | if entry.startswith("zoneinfo") and ".tar." in entry: 28 | return os.path.join(os.path.dirname(__file__), entry) 29 | return None 30 | 31 | def buildcache(): 32 | global CACHE 33 | zoneinfofile = getzoneinfofile() 34 | if zoneinfofile: 35 | tf = TarFile.open(zoneinfofile) 36 | try: 37 | for tarinfo in tf.getmembers(): 38 | if tarinfo.islnk() or tarinfo.isfile(): 39 | zonefile = tf.extractfile(tarinfo) 40 | CACHE[tarinfo.name] = tzfile(zonefile) 41 | finally: 42 | tf.close() 43 | 44 | buildcache() 45 | 46 | del getzoneinfofile 47 | del buildcache 48 | 49 | def setcachesize(_): 50 | # Since the cache now eagerly initialized at 51 | # import time, there's no point in controlling 52 | # its size. 53 | pass 54 | 55 | def gettz(name): 56 | return CACHE.get(name) 57 | 58 | def rebuild(filename, tag=None, format="gz"): 59 | import tempfile, shutil 60 | tmpdir = tempfile.mkdtemp() 61 | zonedir = os.path.join(tmpdir, "zoneinfo") 62 | moduledir = os.path.dirname(__file__) 63 | if tag: tag = "-"+tag 64 | targetname = "zoneinfo%s.tar.%s" % (tag, format) 65 | try: 66 | tf = TarFile.open(filename) 67 | for name in tf.getnames(): 68 | if not (name.endswith(".sh") or 69 | name.endswith(".tab") or 70 | name == "leapseconds"): 71 | tf.extract(name, tmpdir) 72 | filepath = os.path.join(tmpdir, name) 73 | os.system("zic -d %s %s" % (zonedir, filepath)) 74 | tf.close() 75 | target = os.path.join(moduledir, targetname) 76 | for entry in os.listdir(moduledir): 77 | if entry.startswith("zoneinfo") and ".tar." in entry: 78 | os.unlink(os.path.join(moduledir, entry)) 79 | tf = TarFile.open(target, "w:%s" % format) 80 | for entry in os.listdir(zonedir): 81 | entrypath = os.path.join(zonedir, entry) 82 | tf.add(entrypath, entry) 83 | tf.close() 84 | finally: 85 | shutil.rmtree(tmpdir) 86 | -------------------------------------------------------------------------------- /dateutil/zoneinfo/zoneinfo-2012c.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paxan/python-dateutil/16e385ab1bcae57d7db38a1ab525b954d3614f56/dateutil/zoneinfo/zoneinfo-2012c.tar.gz -------------------------------------------------------------------------------- /example.py: -------------------------------------------------------------------------------- 1 | from dateutil.relativedelta import * 2 | from dateutil.easter import * 3 | from dateutil.rrule import * 4 | from dateutil.parser import * 5 | from datetime import * 6 | import commands 7 | import os 8 | now = parse(commands.getoutput("date")) 9 | today = now.date() 10 | year = rrule(YEARLY,bymonth=8,bymonthday=13,byweekday=FR)[0].year 11 | rdelta = relativedelta(easter(year), today) 12 | print "Today is:", today 13 | print "Year with next Aug 13th on a Friday is:", year 14 | print "How far is the Easter of that year:", rdelta 15 | print "And the Easter of that year is:", today+rdelta 16 | -------------------------------------------------------------------------------- /python_dateutil.egg-info/PKG-INFO: -------------------------------------------------------------------------------- 1 | Metadata-Version: 1.0 2 | Name: python-dateutil 3 | Version: 1.5.0.1 4 | Summary: Extensions to the standard python 2.3+ datetime module 5 | Home-page: http://labix.org/python-dateutil 6 | Author: Gustavo Niemeyer 7 | Author-email: gustavo@niemeyer.net 8 | License: PSF License 9 | Description: The dateutil module provides powerful extensions to the standard 10 | datetime module, available in Python 2.3+. 11 | 12 | Platform: UNKNOWN 13 | -------------------------------------------------------------------------------- /python_dateutil.egg-info/SOURCES.txt: -------------------------------------------------------------------------------- 1 | LICENSE 2 | MANIFEST.in 3 | Makefile 4 | NEWS 5 | README 6 | example.py 7 | setup.cfg 8 | setup.py 9 | test.py 10 | updatezinfo.py 11 | dateutil/__init__.py 12 | dateutil/easter.py 13 | dateutil/parser.py 14 | dateutil/relativedelta.py 15 | dateutil/rrule.py 16 | dateutil/tz.py 17 | dateutil/tzwin.py 18 | dateutil/zoneinfo/__init__.py 19 | dateutil/zoneinfo/zoneinfo-2010g.tar.gz 20 | python_dateutil.egg-info/PKG-INFO 21 | python_dateutil.egg-info/SOURCES.txt 22 | python_dateutil.egg-info/dependency_links.txt 23 | python_dateutil.egg-info/not-zip-safe 24 | python_dateutil.egg-info/top_level.txt 25 | sandbox/rrulewrapper.py 26 | sandbox/scheduler.py -------------------------------------------------------------------------------- /python_dateutil.egg-info/dependency_links.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /python_dateutil.egg-info/not-zip-safe: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /python_dateutil.egg-info/top_level.txt: -------------------------------------------------------------------------------- 1 | dateutil 2 | -------------------------------------------------------------------------------- /sandbox/rrulewrapper.py: -------------------------------------------------------------------------------- 1 | from rrule import * 2 | 3 | class rrulewrapper: 4 | def __init__(self, freq, **kwargs): 5 | self._construct = kwargs.copy() 6 | self._construct["freq"] = freq 7 | self._rrule = rrule(**self._construct) 8 | 9 | def __getattr__(self, name): 10 | if name in self.__dict__: 11 | return self.__dict__[name] 12 | return getattr(self._rrule, name) 13 | 14 | def set(self, **kwargs): 15 | self._construct.update(kwargs) 16 | self._rrule = rrule(**self._construct) 17 | -------------------------------------------------------------------------------- /sandbox/scheduler.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2003-2005 Gustavo Niemeyer 3 | 4 | This module offers extensions to the standard python 2.3+ 5 | datetime module. 6 | """ 7 | __author__ = "Gustavo Niemeyer " 8 | __license__ = "PSF License" 9 | 10 | import datetime 11 | import thread 12 | import signal 13 | import time 14 | 15 | class sched: 16 | 17 | def __init__(self, rrule, 18 | tolerance=None, last=None, 19 | execute=None, args=None, kwargs=None): 20 | self._rrule = rrule 21 | if tolerance: 22 | self._tolerance = datetime.timedelta(seconds=tolerance) 23 | else: 24 | self._tolerance = None 25 | self._last = last 26 | self._execute = execute 27 | self._args = args or () 28 | self._kwargs = kwargs or {} 29 | 30 | def last(self): 31 | return self._last 32 | 33 | def next(self, now=None): 34 | if not now: 35 | now = datetime.datetime.now() 36 | return self._rrule.after(now) 37 | 38 | def check(self, now=None, readonly=False): 39 | if not now: 40 | now = datetime.datetime.now() 41 | item = self._rrule.before(now, inc=True) 42 | if (item is None or item == self._last or 43 | (self._tolerance and item+self._tolerance < now)): 44 | return None 45 | if not readonly: 46 | self._last = item 47 | if self._execute: 48 | self._execute(*self._args, **self._kwargs) 49 | return item 50 | 51 | 52 | class schedset: 53 | def __init__(self): 54 | self._scheds = [] 55 | 56 | def add(self, sched): 57 | self._scheds.append(sched) 58 | 59 | def next(self, now=None): 60 | if not now: 61 | now = datetime.datetime.now() 62 | res = None 63 | for sched in self._scheds: 64 | next = sched.next(now) 65 | if next and (not res or next < res): 66 | res = next 67 | return res 68 | 69 | def check(self, now=None, readonly=False): 70 | if not now: 71 | now = datetime.datetime.now() 72 | res = False 73 | for sched in self._scheds: 74 | if sched.check(now, readonly): 75 | res = True 76 | return res 77 | 78 | 79 | class schedthread: 80 | 81 | def __init__(self, sched, lock=None): 82 | self._sched = sched 83 | self._lock = lock 84 | self._running = False 85 | 86 | def running(self): 87 | return self._running 88 | 89 | def run(self): 90 | self._running = True 91 | thread.start_new_thread(self._loop, ()) 92 | 93 | def stop(self): 94 | self._running = False 95 | 96 | def _loop(self): 97 | while self._running: 98 | if self._lock: 99 | self._lock.acquire() 100 | now = datetime.datetime.now() 101 | self._sched.check(now) 102 | if self._lock: 103 | self._lock.release() 104 | seconds = _seconds_left(self._sched.next(now)) 105 | if seconds is None: 106 | self._running = False 107 | break 108 | if self._running: 109 | time.sleep(seconds) 110 | 111 | 112 | class schedalarm: 113 | 114 | def __init__(self, sched, lock=None): 115 | self._sched = sched 116 | self._lock = lock 117 | self._running = False 118 | 119 | def running(self): 120 | return self._running 121 | 122 | def run(self): 123 | self._running = True 124 | signal.signal(signal.SIGALRM, self._handler) 125 | self._handler(None, None) 126 | 127 | def stop(self): 128 | self._running = False 129 | 130 | def _handler(self, sig, frame): 131 | while self._running: 132 | if self._lock: 133 | self._lock.acquire() 134 | now = datetime.datetime.now() 135 | self._sched.check(now) 136 | if self._lock: 137 | self._lock.release() 138 | if self._running: 139 | seconds = _seconds_left(self._sched.next(now)) 140 | if seconds: 141 | signal.alarm(seconds) 142 | break 143 | elif seconds is None: 144 | self._running = False 145 | break 146 | 147 | 148 | def _seconds_left(next): 149 | if not next: 150 | return None 151 | now = datetime.datetime.now() 152 | delta = next-now 153 | seconds = delta.days*86400+delta.seconds 154 | if seconds < 0: 155 | seconds = 0 156 | return seconds 157 | 158 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_rpm] 2 | doc_files = README LICENSE 3 | 4 | [egg_info] 5 | tag_build = 6 | tag_date = 0 7 | tag_svn_revision = 0 8 | 9 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | from os.path import isfile, join 3 | import glob 4 | import os 5 | import re 6 | 7 | from setuptools import setup 8 | 9 | 10 | if isfile("MANIFEST"): 11 | os.unlink("MANIFEST") 12 | 13 | 14 | TOPDIR = os.path.dirname(__file__) or "." 15 | VERSION = re.search('__version__ = "([^"]+)"', 16 | open(TOPDIR + "/dateutil/__init__.py").read()).group(1) 17 | 18 | 19 | setup(name="python-dateutil", 20 | version = VERSION, 21 | description = "Extensions to the standard python 2.3+ datetime module", 22 | author = "Gustavo Niemeyer", 23 | author_email = "gustavo@niemeyer.net", 24 | url = "http://labix.org/python-dateutil", 25 | license = "PSF License", 26 | long_description = 27 | """\ 28 | The dateutil module provides powerful extensions to the standard 29 | datetime module, available in Python 2.3+. 30 | """, 31 | packages = ["dateutil", "dateutil.zoneinfo"], 32 | package_data={"": ["*.tar.gz"]}, 33 | include_package_data=True, 34 | zip_safe=False, 35 | ) 36 | -------------------------------------------------------------------------------- /updatezinfo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | from dateutil.zoneinfo import rebuild 3 | import shutil 4 | import sys 5 | import os 6 | import re 7 | 8 | SERVER = "ftp.iana.org" 9 | DIR = "/tz" 10 | NAME = re.compile("tzdata(.*).tar.gz") 11 | 12 | def main(): 13 | if len(sys.argv) == 2: 14 | tzdata = sys.argv[1] 15 | else: 16 | from ftplib import FTP 17 | print "Connecting to %s..." % SERVER 18 | ftp = FTP(SERVER) 19 | print "Logging in..." 20 | ftp.login() 21 | print "Changing to %s..." % DIR 22 | ftp.cwd(DIR) 23 | print "Listing files..." 24 | for name in ftp.nlst(): 25 | if NAME.match(name): 26 | break 27 | else: 28 | sys.exit("error: file matching %s not found" % NAME.pattern) 29 | if os.path.isfile(name): 30 | print "Found local %s..." % name 31 | else: 32 | print "Retrieving %s..." % name 33 | file = open(name, "w") 34 | ftp.retrbinary("RETR "+name, file.write) 35 | file.close() 36 | ftp.close() 37 | tzdata = name 38 | if not tzdata or not NAME.match(tzdata): 39 | sys.exit("Usage: updatezinfo.py tzdataXXXXX.tar.gz") 40 | print "Updating timezone information..." 41 | rebuild(tzdata, NAME.match(tzdata).group(1)) 42 | print "Done." 43 | 44 | if __name__ == "__main__": 45 | main() 46 | --------------------------------------------------------------------------------