parsedatetime-2.4/0000755000076500000240000000000013106001723014233 5ustar bearstaff00000000000000parsedatetime-2.4/AUTHORS.txt0000644000076500000240000000251612611302347016133 0ustar bearstaff00000000000000Mike Taylor https://bear.im Darshana Chhajed Michael Lim Bernd Zeimetz Geoffrey Floyd https://github.com/geoffreyfloyd Alexis Sasha Acker https://github.com/sashaacker Yu-Jie Lin https://github.com/livibetter rl-0x0 https://github.com/rl-0x0 Bernardo Sulzbach https://github.com/mafagafogigante Philip Tzou https://github.com/philiptzou Ian Paterson https://github.com/idpaterson Omer Katz https://github.com/thedrow Rob Curtis https://github.com/Rob1080 Michael Maltese https://github.com/michaelmaltese Dan Steeves https://github.com/dansteeves68 Alexander Sapronov https://github.com/WarmongeR1 Bernardo Sulzbach https://github.com/mafagafogigante Zed https://github.com/zed https://github.com/fake-name https://github.com/rl-0x0 https://github.com/dirkjankrijnders https://github.com/inean https://github.com/oxan https://github.com/jstegle https://github.com/devainandor https://github.com/borgstrom https://github.com/arcimboldo https://github.com/ccho-sevenrooms https://github.com/boppreh https://github.com/lborgav https://github.com/yishaibeeri https://github.com/rmecham see https://github.com/bear/parsedatetime/graphs/contributors for the full list parsedatetime-2.4/CHANGES.txt0000644000076500000240000004423713105774361016074 0ustar bearstaff0000000000000014 May 2017 - bear v2.4 release v2.5 bump Issue #219 - remove 'setup_requires' from setup.py 10 Mar 2017 - bear v2.3 release v2.4 bump Issue #215 - tests fail in March 02 Mar 2016 - bear v2.1 released v2.2 bump Issue #156 parsedatetime 2.0 doesn't work on py26 PR 157 unwrap dictionary comprehensions to allow for python 2.6 to work - Issue #156 29 Feb 2016 - bear v2.0 released Issue #155 Relative times containing years fail when computed from a leap day Issue #145 cal.parse('2015-11-18') returns November 19th 2015 Issue #143 What is the second value returned by `parse`? Issue #141 Bad test case in TestComplexDateTimes Issue #123 update supporting files for v2.0 release Issue #124 Put locales into config-files (yaml) Issue #125 Remove extra files Issue #137 Year is parsed wrongly if the date is of format MMM DD, YYxx xx:SS bug Issue #136 Why I see 2016 instead of 2015? Issue #133 Bug: "2015-01-01" is parsed as the current date. Issue #126 "Unresolved attribute reference 'parse' for class 'object'... " in Pycharm IDE. bug PR #153 Fix/day of week offsets PR #146 Test failure: eom is correct, but expectation is wrong PR #142 Fixed all PyICU test failure PR #138 bug(date3): rely on comparison of hour and year strings but not strict char position PR #135 update manifest, clean up setup.py and move historical text files PR #130 Refactoring of pdt_locales PR #134 Uses `codecov` to generate coverage report PR #128 Master PR #127 Issue #126 - removed inheritance from object and removed return value… 20 Sep 2015 - bear bump version to v2.0 because of the fix for Issue #120 Issue #120 the pdt_locales/en_AU.py file uses en_A for the localID instead of en_AU Issue #114 Dates in the format 'YYYY-MM-DD HH:MM' give the incorrect month and day Issue #112 Document getting a time from parsedatetime into a standard Python structure Issue #110 AttributeError when running in the context of an HTTP request Issue #109 YearParseStyle is ignored for dates in MM/DD style Issue #107 yyyy/mm/dd date format Issue #105 "this week" is not parsed Issue #103 get UTC times from parseDT - trouble with at 9:30 clock times being interpreted directly in UTC Issue #100 Fractional deltas result in incoherent results. PR #118 ADD: improve russian locale PR #117 ADD: Russian Locale PR #116 Fix spelling of "separator". PR #115 Update README.rst PR #113 Add datetime example to readme. PR #111 Allowed real number appear in text like "5.5 days ago" 25 Jun 2015 - bear Issue #73 add Makefile bump version to v1.6 released v1.5 Issue #99 Which year is implied when given just a month and day? Next and last? question Issue #96 Word boundary issues for specials (on, at, in) in nlp Issue #94 inconsistent application of sourceTime in Calendar.parseDT Issue #87 nlp() doesn't recognize some "next ..." expressions Issue #84 Afternoon? bug Issue #82 'last week' and 'next week' are broken Issue #81 parse returns default time of 0900 with dates like 'next friday' despite passed struct_time bug Issue #78 Link for Travis in README is wrong Issue #72 Enable travis Issue #71 Calendar() class can not be initialized 1.4 (it's fine) Issue #66 Unexpected struct_time flag with Calendar.parse on HTML string Issue #65 NLP false positives Issue #63 Supporting multiple shortweekday abbreviations Issue #61 Short weekday abbreviations bug Issue #56 Parse words to numbers (thirteen => 13) Issue #54 testMonths fails commit 107c7e4655 fix for issue 95 - parsing 'next june 15' commit 2c0c8ec778 Fixed faulty test, "730am" parses as "73:0 am" which is a bug for a later day. commit 6f244e891d Fix "ones" parsing as "1s." Require a word boundary between spelled numbers and units. commit 035818edef Fix "1 day ago" parsing like "1d 1y ago" where "a" within the word "day" is interpreted as 1. commit 45002e6eec Fixes "next week" and similar modifier + unit pairs in nlp() commit 47d2e1d527 Fixed "last week" 11 Jul 2014 - bear bump version to v1.5 released v1.4 Updated setup.py for wheel compatibility renamed README.txt to README.rst renamed MANIFEST to MANIFEST.in cleaned up a lot of the doc and notes Commit 3fc165e701 mafagafo Now it works for Python 3.4.1 Commit d5883801e7 borgstrom Restore Python 2.6 compatibility 8 Jul 2014 - bear bumped version to 1.4 Issue #45 make a new release to really fix backwards compatibility Issue #43 Please tag version 1.3 Commit 29c5c8961d devainandor fixed Python 3 compatibility in pdtLocale_icu Commit d7304f18f7 inean Fix support for 'now' when no modifiers are present Commit 26bfc91c28 sashaacker Added parseDT method. Commit 848deb47e2 rmecham Added support for dotted meridians. Commit c821e08ce2 ccho-sevenrooms corrected misspelling of 'thirteen' 24 Jan 2014 - bear bumped version to 1.3 many changes - amazing how hard it is to keep this file up to date when using GitHub. See https://github.com/bear/parsedatetime/commits/master for details. Biggest change is the addition of the nlp() function by Geoffrey Floyd: nlp() function that utilizes parse() after making judgements about what datetime information belongs together. It makes logical groupings based on proximity and returns a parsed datetime for each matched grouping of datetime text, along with location info within the given inputString. 27 Jun 2013 - bear bumped version to 1.2 04 Mar 2013 - bear bumped version to 1.1.2 deploy import fix from Antonio Messina also noticed that the urls were pointing to my older site, corrected 03 Mar 2013 - bear bumped version to 1.1.1 Ugh - debug log caused an error during formatting Issue 10 https://github.com/bear/parsedatetime/issues/10 14 Nov 2012 - bear Added test for "last friday" Updated MANIFEST to reflect renamed README file Bumped version to 1.1 15 Mar 2011 - bear Updated 1.0.0 code to work with 2.6+ (need to try 2.5) and also updated docs and other supporting code 07 Sep 2009 - bear Created branches/python25 from current trunk to save the current code Converted trunk to Python 3 and also refactored how the module is structured so that it no longer requires import parsedatetime.parsedatetime Bumped version to 1.0.0 to reflect the major refactoring 07 Jan 2009 - bear 0.8.7 release Apply patch submitted by Michael Lim to fix the problem parsedatetime was having handling dates when the month day preceeded the month Issue 26 http://code.google.com/p/parsedatetime/issues/detail?id=26 Fixed TestErrors when in a local where the bad date actually returns a date ;) Checked in the TestGermanLocale unit test file missed from previous commit 20 Apr 2008 - bear Upating Copyright year info Fixing defects from Google Project page The comparison routine for the "failing" test was not accurate. The test was being flagged as failing incorrectly Issue 18 http://code.google.com/p/parsedatetime/issues/detail?id=18 Added patch from Bernd Zeimetz for the German localized constants! http://svn.debian.org/viewsvn/*checkout*/python-modules/packages/parsedatetime/trunk/debian/patches/locale-de.dpatch He identifies some issues with how unicode is handled and also some other glitches - will have to work on them Issue 20 http://code.google.com/p/parsedatetime/issues/detail?id=20 Tweaked run_tests.py to default to all tests if not given on the command line Removed 'this' from the list of "specials" - it was causing some grief and from the looks of the unit tests, not all that necessary Worked on bug 19 - Bernd identified that for the German locale the dayofweek check was being triggered for the dayoffset word "morgen" (the "mo" matched the day "morgen") To solve this I added a small check to make sure if the whole word being checked was not in the dayOffsets list, and if so not trigger. Issue 19 http://code.google.com/p/parsedatetime/issues/detail?id=19 28 Nov 2007 - bear 0.8.5.1 release - removed debug code 0.8.5 release bumping version to 0.8.6 in trunk Fixing two bugs found by Chandler QA Time range of "today 3:30-5pm" was actually causing a traceback. Added a new regex to cover this range type and a new test. OSAF 11299 https://bugzilla.osafoundation.org/show_bug.cgi?id=11299 A really embarrassing for a date/time library - was actually *not* considering leap years when returning days in a month! Added tests for Feb 29th of various known leap years and also added a check for the daysInMonth() routine which was created to replace the naively simple DaysInMonthList. OSAF 11203 https://bugzilla.osafoundation.org/show_bug.cgi?id=11203 12 Jun 2007 - bear 0.8.4 release bumping version to 0.8.5 in trunk 22 Feb 2007 - bear Fixed a bug reported via the code.google project page by Alexis where parsedatetime was not parsing day suffixes properly. For example, the text "Aug 25th, 2008" would return the year as 2007 - the parser was not 'seeing' 2008 as a part of the expression. The fix was to enhance one of the "long date" regexes to handle that situation but yet not break the current tests - always fun for sure! Issue 16 http://code.google.com/p/parsedatetime/issues/detail?id=16 21 Feb 2007 - bear Fixed a bug Brian K. (one of the Chandler devs) found when parsing with the locale set to fr_FR. The phrase "same 3 folders" was causing a key error inside the parser and it turns out that it's because short weekday names in French have a trailing '.' so "sam." was being used in the regular expression and the '.' was being treated as a regex symbol and not as a period. It turned out to be a simple fix - just needed to add some code to run re.escape over the lists before adding them to the re_values dictionary. Also added a TestFrenchLocale set of unit tests but made them only run if PyICU is found until I can build an internal locale for fr_FR. Issue #17 http://code.google.com/p/parsedatetime/issues/detail?id=17 14 Feb 2007 - bear 0.8.3 release Minor doc string changes and other typo fixes Updated Copyright years Added a fallbackLocales=[] parameter to parsedatetime_consts init routine to control what locales are scanned if the default or given locale is not found in PyICU. Issue #9 http://code.google.com/p/parsedatetime/issues/detail?id=9 While working on the regex compile-on-demand issue below, I realized that parsedatetime was storing the compiled regex's locally and that this would cause prevent parsedatetime from switching locales easily. I've always wanted to make it so parsedatetime can be set to parse within a locale just by changing a single reference - this is one step closer to that. Made the regex compiles on-demand to help with performance Requested by the Chandler folks Issue #15 http://code.google.com/p/parsedatetime/issues/detail?id=15 To test the change I ran 100 times the following code: for i in range(0, 100): c = pdc.Constants() p = pdt.Calendar(c) p = None c = None and that was measured by hotshot: 24356 function calls (22630 primitive calls) in 0.188 CPU seconds after the change: 5000 function calls in 0.140 CPU seconds but that doesn't test the true time as it doesn't reference any regex's so any time saved is deferred. To test this I then ran before and after tests where I parsed the major unit test bits: before the change: 80290 function calls (75929 primitive calls) in 1.055 CPU seconds after the change: 55803 function calls (52445 primitive calls) in 0.997 CPU seconds This tells me while doing the lazy compile does save time, it's not a lot over the normal usage. I'll leave it in as it is saving time for the simple use-cases. 27 Dec 2006 - bear Added some support files to try and increase our cheesecake index :) Created an examples directory and added back the docs/* content so the source distribution will contain the generated docs Changed how setup.py works to allow for a doc command 26 Dec 2006 - bear 0.8.1 release Setting trunk to 0.8.2 Fixed the 'eom' part of testEndOfPhrases. It was not adjusting the year when checking for month rollover to the new year. Changed API docs to reflect that it's a struct_time type (or a time tuple) that we accept and return instead of a datetime value. I believe this lead to Issue #14 being reported. Also added some error handling to change a datetime value into a struct_time value if passed to parse(). 3 Nov 2006 - darshana Fixed issue#13 (Nov 4 5pm parses as just 5pm). Also fixed "1-8pm" and other ranges which were not working if there were no spaces before and after the '-'. 1 Nov 2006 - darshana Strings like "Thursday?" were not parsed. Changes made to the regex to allow special characters to be parsed after weekday names and month names. 24 Oct 2006 - bear 0.8.0 release Setting trunk to 0.8.1 Merged in changes from Darshana's change_parse_to_return_enum branch This is a big change in that instead of a simple True/False that is returned to show if the date is valid or not, Parse() now returns a "masked" value that represents what is valid: date = 1 time = 2 so a value of zero means nothing was parseable/valid and a value of 3 means both were parsed/valid. 20 Oct 2006 - darshana Implemented the CalculateDOWDelta() method in parsedatetime.py Added a new flag CurrentDOWParseStyle in parsedatetime_consts.py for the current DOW. 19 Oct 2006 - bear Changed birthday epoch to be a constant defined in parsedatetime_const Lots of little cosmetic code changes Removed the individual files in the docs/ folder Added dist, build and parsedatetime-egg.info to svn:ignore 17 Oct 2006 - darshana Added birthday epoch constraint Fixed date parsing. 3-digit year not allowed now. Fixed the unit tests too to either have yy or yyyy. 9 Oct 2006 - bear 0.7.4 release Setting trunk to 0.7.5 5 Oct 2006 - darshana Fixed "ago" bug -- Issue #7 http://code.google.com/p/parsedatetime/issues/detail?id=7 Fixed bug where default year for dates that are in the future get next year, not current year -- Issue #8 http://code.google.com/p/parsedatetime/issues/detail?id=8 Fixed strings like "1 week ago", "lunch tomorrow" 25 Sep 2006 - bear 0.7.3 release Setting trunk to 0.7.4 13 Sep 2006 - bear Added Darshana as an author and updated the copyright text Added "eom" and "eoy" tests 11 Sep 2006 - bear Fixed a subtle dictionary reference bug in buildSources() that was causing any source related modifier to not honor the day, month or year. It only started being seen as I was working on adding "eod" support as a 'true' modifier instead. Found another subtle bug in evalModifier() if the modifier was followed by the day of the week - the offset math was not consistent with the other day-of-week offset calculations. Worked on converting "eod" support from the special case modifier to work as a true modifier. The following is now supported: eod tomorrow tomorrow eod monday eod eod monday meeting eod eod meeting 10 Sep 2006 - bear Added a sub-range test in response to Issue #6 http://code.google.com/p/parsedatetime/issues/detail?id=6 Not that it works, just wanted to start the process. 6 Sep 2006 - bear Alan Green filed Issue #5 http://code.google.com/p/parsedatetime/issues/detail?id=5 In it he asked for support for Australian date formats "dd-mm-yyyy" This is the first attempt at supporting the parsing of dates where the order of the day, month and year can vary. I adjusted the parseDate() code to be data driven and added a dp_order list to the Constants() class that is either initialized to the proper order by the pdtLocale classes or the order is determined by parsing the ICU short date format to figure out what the date separator is and then to find out what order it's in. I also added a TestAustralianLocale.py as a starting point for tests. Attaching a diff of this code to the Issue so he can test it. 1 Sep 2006 - bear 0.7.2 release 31 Aug 2006 - bear Fixed two bugs found by Darshana today. The first is one of those forehead-slapping bugs that you see as being so obvious *after* the fact :) The problem is with Inc() for months - if you increment from a month with the day set to a value that is past the end of the month for the new month you get an error. For example Aug 31 to Sept - Sept doesn't have 31 days so it's an invalid date. The second is with the code that identifies modifiers when you have multiple "chunks" of text. Darshana describes the bug this way: "if you have "flight from SFO at 4pm" i.e. if you have a non-date/time string before a modifier, then the invalidflag is set" I provided the Inc() fix and Darshana the modifier fix. I also added a new unit test for the Inc() bug and also a new test file for the modifier bug: TestPhrases.py 29 Aug 2006 - bear Updated ez_setup.py to latest version v0.6c1 and removed hard-coded version in setup.py for setuptools 25 Aug 2006 - bear Moved the error tests into a single TestErrors.py Added two tests for days - it figures out what the previous day and next day is from the weekday the test is run on 24 Aug 2006 - bear Issue #2 http://code.google.com/p/parsedatetime/issues/detail?id=2 Turns out that ICU works with weekdays in Sun..Sat order and that Python uses Mon..Sun order. Fixed PyICU locale code to build the internal weekday list to be Python Style. Bumping version to 0.7.1 as this is causing Chandler bug 6567 http://bugzilla.osafoundation.org/show_bug.cgi?id=6567 parsedatetime-2.4/examples/0000755000076500000240000000000013106001723016051 5ustar bearstaff00000000000000parsedatetime-2.4/examples/basic.py0000644000076500000240000000324212611302347017513 0ustar bearstaff00000000000000""" Basic examples of how to use parsedatetime """ __license__ = """ Copyright (c) 2004-2006 Mike Taylor Copyright (c) 2006 Darshana Chhajed All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import parsedatetime as pdt # create an instance of Constants class so we can override some of the defaults c = pdt.Constants() c.BirthdayEpoch = 80 # BirthdayEpoch controls how parsedatetime # handles two digit years. If the parsed # value is less than this value then the year # is set to 2000 + the value, otherwise # it's set to 1900 + the value # create an instance of the Calendar class and pass in our Constants # object instead of letting it create a default p = pdt.Calendar(c) # parse "tomorrow" and return the result result = p.parse("tomorrow") # parseDate() is a helper function that bypasses all of the # natural language stuff and just tries to parse basic dates # but using the locale information result = p.parseDate("4/4/80") # parseDateText() is a helper function that tries to parse # long-form dates using the locale information result = p.parseDateText("March 5th, 1980") parsedatetime-2.4/examples/README.txt0000644000076500000240000000031612611302347017555 0ustar bearstaff00000000000000Examples of parsedatetime usage basic.py outlines all the "simple" usage with_pyicu.py using parsedatetime with PyICU with_locale.py using parsedatetime with built-in locale classes parsedatetime-2.4/examples/with_locale.py0000644000076500000240000000462412611302347020731 0ustar bearstaff00000000000000# -*- coding: utf-8 -*- """ Examples of how to use parsedatetime with locale information provided. Locale information can come from either PyICU (if available) or from the more basic internal locale classes that are included in the `Constants` class. """ __license__ = """ Copyright (c) 2004-2006 Mike Taylor Copyright (c) 2006 Darshana Chhajed All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import parsedatetime as pdt # create an instance of Constants class so we can specify the locale c = pdt.Constants("en") p = pdt.Calendar(c) # print out the values from Constants to show how the locale information # is being used/stored internally values = (c.uses24, # 24hr clock? c.usesMeridian, # AM/PM used? c.usePyICU, # was PyICU found/enabled? c.meridian, # list of the am and pm values c.am, # list of the lowercase and stripped AM string c.pm, # list of the lowercase and stripped PM string c.dateFormats, # dict of available date format strings c.timeFormats, # dict of available time format strings c.timeSep, # list of time separator, e.g. the ':' in '12:45' c.dateSep, # list of date separator, e.g. the '/' in '11/23/2006' c.Months, # list of full month names c.shortMonths, # list of the short month names c.Weekdays, # list of the full week day names c.localeID # the locale identifier ) print('\n'.join((str(value) for value in values))) result = p.parse("March 24th") # create an instance of Constants class and force it not to use PyICU # and to use the internal Spanish locale class c = pdt.Constants(localeID="es", usePyICU=False) p = pdt.Calendar(c) result = p.parse("Marzo 24") parsedatetime-2.4/LICENSE.txt0000644000076500000240000002272512611302347016074 0ustar bearstaff00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: 1. You must give any other recipients of the Work or Derivative Works a copy of this License; and 2. You must cause any modified files to carry prominent notices stating that You changed the files; and 3. You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and 4. If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS parsedatetime-2.4/Makefile0000644000076500000240000000332513106001044015672 0ustar bearstaff00000000000000.PHONY: docs test UNAME_S := $(shell uname -s) ifeq ($(UNAME_S),Darwin) BREWPATH = $(shell brew --prefix) PYICU_LD = -L${BREWPATH}/opt/icu4c/lib PYICU_CPP = -I${BREWPATH}/opt/icu4c/include ICU_VER = 58.2 else PYICU_LD = PYICU_CPP = ICU_VER = endif help: @echo " env install all production dependencies" @echo " dev install all dev and production dependencies (virtualenv is assumed)" @echo " clean remove unwanted stuff" @echo " lint check style with flake8" @echo " test run tests" @echo " build generate source and wheel dist files" @echo " upload generate source and wheel dist files and upload them" env: pip install -U pip dev: env pip install -Uqr requirements.testing.txt | tee @echo "on OS X use homebrew to install icu4c" LDFLAGS=${PYICU_LD} CPPFLAGS=${PYICU_CPP} ICU_VERSION=${ICU_VER} \ pip install -U pyicu pyenv install -s 2.6.9 pyenv install -s 2.7.11 pyenv install -s 3.6.1 pyenv install -s pypy-5.3 pyenv local 2.6.9 2.7.11 3.6.1 pypy-5.3 info: @python --version @pyenv --version @pip --version clean: rm -fr build rm -fr dist find . -name '*.pyc' -exec rm -f {} \; find . -name '*.pyo' -exec rm -f {} \; find . -name '*~' -exec rm -f {} \; docs: epydoc --html --config epydoc.conf lint: flake8 parsedatetime > violations.flake8.txt test: lint python setup.py test tox: clean tox coverage: clean @coverage run --source=parsedatetime setup.py test @coverage html @coverage report ci: tox coverage CODECOV_TOKEN=`cat .codecov-token` codecov build: clean python setup.py check python setup.py sdist python setup.py bdist_wheel upload: clean python setup.py sdist upload python setup.py bdist_wheel upload parsedatetime-2.4/MANIFEST.in0000644000076500000240000000023312611315170015773 0ustar bearstaff00000000000000include *.rst include *.txt include Makefile recursive-include examples *.py recursive-include examples *.txt recursive-include tests *.py prune .DS_Store parsedatetime-2.4/parsedatetime/0000755000076500000240000000000013106001723017062 5ustar bearstaff00000000000000parsedatetime-2.4/parsedatetime/__init__.py0000644000076500000240000031517513060633202021211 0ustar bearstaff00000000000000# -*- coding: utf-8 -*- # # vim: sw=2 ts=2 sts=2 # # Copyright 2004-2016 Mike Taylor # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """parsedatetime Parse human-readable date/time text. Requires Python 2.6 or later """ from __future__ import with_statement, absolute_import, unicode_literals import re import time import logging import warnings import datetime import calendar import contextlib import email.utils from .pdt_locales import (locales as _locales, get_icu, load_locale) from .context import pdtContext, pdtContextStack from .warns import pdt20DeprecationWarning __author__ = 'Mike Taylor' __email__ = 'bear@bear.im' __copyright__ = 'Copyright (c) 2017 Mike Taylor' __license__ = 'Apache License 2.0' __version__ = '2.4' __url__ = 'https://github.com/bear/parsedatetime' __download_url__ = 'https://pypi.python.org/pypi/parsedatetime' __description__ = 'Parse human-readable date/time text.' # as a library, do *not* setup logging # see docs.python.org/2/howto/logging.html#configuring-logging-for-a-library # Set default logging handler to avoid "No handler found" warnings. try: # Python 2.7+ from logging import NullHandler except ImportError: class NullHandler(logging.Handler): def emit(self, record): pass log = logging.getLogger(__name__) log.addHandler(NullHandler()) debug = False pdtLocales = dict([(x, load_locale(x)) for x in _locales]) # Copied from feedparser.py # Universal Feedparser # Copyright (c) 2002-2006, Mark Pilgrim, All rights reserved. # Originally a def inside of _parse_date_w3dtf() def _extract_date(m): year = int(m.group('year')) if year < 100: year = 100 * int(time.gmtime()[0] / 100) + int(year) if year < 1000: return 0, 0, 0 julian = m.group('julian') if julian: julian = int(julian) month = julian / 30 + 1 day = julian % 30 + 1 jday = None while jday != julian: t = time.mktime((year, month, day, 0, 0, 0, 0, 0, 0)) jday = time.gmtime(t)[-2] diff = abs(jday - julian) if jday > julian: if diff < day: day = day - diff else: month = month - 1 day = 31 elif jday < julian: if day + diff < 28: day = day + diff else: month = month + 1 return year, month, day month = m.group('month') day = 1 if month is None: month = 1 else: month = int(month) day = m.group('day') if day: day = int(day) else: day = 1 return year, month, day # Copied from feedparser.py # Universal Feedparser # Copyright (c) 2002-2006, Mark Pilgrim, All rights reserved. # Originally a def inside of _parse_date_w3dtf() def _extract_time(m): if not m: return 0, 0, 0 hours = m.group('hours') if not hours: return 0, 0, 0 hours = int(hours) minutes = int(m.group('minutes')) seconds = m.group('seconds') if seconds: seconds = seconds.replace(',', '.').split('.', 1)[0] seconds = int(seconds) else: seconds = 0 return hours, minutes, seconds def _pop_time_accuracy(m, ctx): if not m: return if m.group('hours'): ctx.updateAccuracy(ctx.ACU_HOUR) if m.group('minutes'): ctx.updateAccuracy(ctx.ACU_MIN) if m.group('seconds'): ctx.updateAccuracy(ctx.ACU_SEC) # Copied from feedparser.py # Universal Feedparser # Copyright (c) 2002-2006, Mark Pilgrim, All rights reserved. # Modified to return a tuple instead of mktime # # Original comment: # W3DTF-style date parsing adapted from PyXML xml.utils.iso8601, written by # Drake and licensed under the Python license. Removed all range checking # for month, day, hour, minute, and second, since mktime will normalize # these later def __closure_parse_date_w3dtf(): # the __extract_date and __extract_time methods were # copied-out so they could be used by my code --bear def __extract_tzd(m): '''Return the Time Zone Designator as an offset in seconds from UTC.''' if not m: return 0 tzd = m.group('tzd') if not tzd: return 0 if tzd == 'Z': return 0 hours = int(m.group('tzdhours')) minutes = m.group('tzdminutes') if minutes: minutes = int(minutes) else: minutes = 0 offset = (hours * 60 + minutes) * 60 if tzd[0] == '+': return -offset return offset def _parse_date_w3dtf(dateString): m = __datetime_rx.match(dateString) if m is None or m.group() != dateString: return return _extract_date(m) + _extract_time(m) + (0, 0, 0) __date_re = (r'(?P\d\d\d\d)' r'(?:(?P-|)' r'(?:(?P\d\d\d)' r'|(?P\d\d)(?:(?P=dsep)(?P\d\d))?))?') __tzd_re = r'(?P[-+](?P\d\d)(?::?(?P\d\d))|Z)' # __tzd_rx = re.compile(__tzd_re) __time_re = (r'(?P\d\d)(?P:|)(?P\d\d)' r'(?:(?P=tsep)(?P\d\d(?:[.,]\d+)?))?' + __tzd_re) __datetime_re = '%s(?:T%s)?' % (__date_re, __time_re) __datetime_rx = re.compile(__datetime_re) return _parse_date_w3dtf _parse_date_w3dtf = __closure_parse_date_w3dtf() del __closure_parse_date_w3dtf _monthnames = set([ 'jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec', 'january', 'february', 'march', 'april', 'may', 'june', 'july', 'august', 'september', 'october', 'november', 'december']) _daynames = set(['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun']) # Copied from feedparser.py # Universal Feedparser # Copyright (c) 2002-2006, Mark Pilgrim, All rights reserved. # Modified to return a tuple instead of mktime def _parse_date_rfc822(dateString): '''Parse an RFC822, RFC1123, RFC2822, or asctime-style date''' data = dateString.split() if data[0][-1] in (',', '.') or data[0].lower() in _daynames: del data[0] if len(data) == 4: s = data[3] s = s.split('+', 1) if len(s) == 2: data[3:] = s else: data.append('') dateString = " ".join(data) if len(data) < 5: dateString += ' 00:00:00 GMT' return email.utils.parsedate_tz(dateString) # rfc822.py defines several time zones, but we define some extra ones. # 'ET' is equivalent to 'EST', etc. # _additional_timezones = {'AT': -400, 'ET': -500, # 'CT': -600, 'MT': -700, # 'PT': -800} # email.utils._timezones.update(_additional_timezones) VERSION_FLAG_STYLE = 1 VERSION_CONTEXT_STYLE = 2 class Calendar(object): """ A collection of routines to input, parse and manipulate date and times. The text can either be 'normal' date values or it can be human readable. """ def __init__(self, constants=None, version=VERSION_FLAG_STYLE): """ Default constructor for the L{Calendar} class. @type constants: object @param constants: Instance of the class L{Constants} @type version: integer @param version: Default style version of current Calendar instance. Valid value can be 1 (L{VERSION_FLAG_STYLE}) or 2 (L{VERSION_CONTEXT_STYLE}). See L{parse()}. @rtype: object @return: L{Calendar} instance """ # if a constants reference is not included, use default if constants is None: self.ptc = Constants() else: self.ptc = constants self.version = version if version == VERSION_FLAG_STYLE: warnings.warn( 'Flag style will be deprecated in parsedatetime 2.0. ' 'Instead use the context style by instantiating `Calendar()` ' 'with argument `version=parsedatetime.VERSION_CONTEXT_STYLE`.', pdt20DeprecationWarning) self._ctxStack = pdtContextStack() @contextlib.contextmanager def context(self): ctx = pdtContext() self._ctxStack.push(ctx) yield ctx ctx = self._ctxStack.pop() if not self._ctxStack.isEmpty(): self.currentContext.update(ctx) @property def currentContext(self): return self._ctxStack.last() def _convertUnitAsWords(self, unitText): """ Converts text units into their number value. @type unitText: string @param unitText: number text to convert @rtype: integer @return: numerical value of unitText """ word_list, a, b = re.split(r"[,\s-]+", unitText), 0, 0 for word in word_list: x = self.ptc.small.get(word) if x is not None: a += x elif word == "hundred": a *= 100 else: x = self.ptc.magnitude.get(word) if x is not None: b += a * x a = 0 elif word in self.ptc.ignore: pass else: raise Exception("Unknown number: " + word) return a + b def _buildTime(self, source, quantity, modifier, units): """ Take C{quantity}, C{modifier} and C{unit} strings and convert them into values. After converting, calcuate the time and return the adjusted sourceTime. @type source: time @param source: time to use as the base (or source) @type quantity: string @param quantity: quantity string @type modifier: string @param modifier: how quantity and units modify the source time @type units: string @param units: unit of the quantity (i.e. hours, days, months, etc) @rtype: struct_time @return: C{struct_time} of the calculated time """ ctx = self.currentContext debug and log.debug('_buildTime: [%s][%s][%s]', quantity, modifier, units) if source is None: source = time.localtime() if quantity is None: quantity = '' else: quantity = quantity.strip() qty = self._quantityToReal(quantity) if modifier in self.ptc.Modifiers: qty = qty * self.ptc.Modifiers[modifier] if units is None or units == '': units = 'dy' # plurals are handled by regex's (could be a bug tho) (yr, mth, dy, hr, mn, sec, _, _, _) = source start = datetime.datetime(yr, mth, dy, hr, mn, sec) target = start # realunit = next((key for key, values in self.ptc.units.items() # if any(imap(units.__contains__, values))), None) realunit = units for key, values in self.ptc.units.items(): if units in values: realunit = key break debug and log.debug('units %s --> realunit %s (qty=%s)', units, realunit, qty) try: if realunit in ('years', 'months'): target = self.inc(start, **{realunit[:-1]: qty}) elif realunit in ('days', 'hours', 'minutes', 'seconds', 'weeks'): delta = datetime.timedelta(**{realunit: qty}) target = start + delta except OverflowError: # OverflowError is raise when target.year larger than 9999 pass else: ctx.updateAccuracy(realunit) return target.timetuple() def parseDate(self, dateString, sourceTime=None): """ Parse short-form date strings:: '05/28/2006' or '04.21' @type dateString: string @param dateString: text to convert to a C{datetime} @type sourceTime: struct_time @param sourceTime: C{struct_time} value to use as the base @rtype: struct_time @return: calculated C{struct_time} value of dateString """ if sourceTime is None: yr, mth, dy, hr, mn, sec, wd, yd, isdst = time.localtime() else: yr, mth, dy, hr, mn, sec, wd, yd, isdst = sourceTime # values pulled from regex's will be stored here and later # assigned to mth, dy, yr based on information from the locale # -1 is used as the marker value because we want zero values # to be passed thru so they can be flagged as errors later v1 = -1 v2 = -1 v3 = -1 accuracy = [] s = dateString m = self.ptc.CRE_DATE2.search(s) if m is not None: index = m.start() v1 = int(s[:index]) s = s[index + 1:] m = self.ptc.CRE_DATE2.search(s) if m is not None: index = m.start() v2 = int(s[:index]) v3 = int(s[index + 1:]) else: v2 = int(s.strip()) v = [v1, v2, v3] d = {'m': mth, 'd': dy, 'y': yr} # yyyy/mm/dd format dp_order = self.ptc.dp_order if v1 <= 31 else ['y', 'm', 'd'] for i in range(0, 3): n = v[i] c = dp_order[i] if n >= 0: d[c] = n accuracy.append({'m': pdtContext.ACU_MONTH, 'd': pdtContext.ACU_DAY, 'y': pdtContext.ACU_YEAR}[c]) # if the year is not specified and the date has already # passed, increment the year if v3 == -1 and ((mth > d['m']) or (mth == d['m'] and dy > d['d'])): yr = d['y'] + self.ptc.YearParseStyle else: yr = d['y'] mth = d['m'] dy = d['d'] # birthday epoch constraint if yr < self.ptc.BirthdayEpoch: yr += 2000 elif yr < 100: yr += 1900 daysInCurrentMonth = self.ptc.daysInMonth(mth, yr) debug and log.debug('parseDate: %s %s %s %s', yr, mth, dy, daysInCurrentMonth) with self.context() as ctx: if mth > 0 and mth <= 12 and dy > 0 and \ dy <= daysInCurrentMonth: sourceTime = (yr, mth, dy, hr, mn, sec, wd, yd, isdst) ctx.updateAccuracy(*accuracy) else: # return current time if date string is invalid sourceTime = time.localtime() return sourceTime def parseDateText(self, dateString, sourceTime=None): """ Parse long-form date strings:: 'May 31st, 2006' 'Jan 1st' 'July 2006' @type dateString: string @param dateString: text to convert to a datetime @type sourceTime: struct_time @param sourceTime: C{struct_time} value to use as the base @rtype: struct_time @return: calculated C{struct_time} value of dateString """ if sourceTime is None: yr, mth, dy, hr, mn, sec, wd, yd, isdst = time.localtime() else: yr, mth, dy, hr, mn, sec, wd, yd, isdst = sourceTime currentMth = mth currentDy = dy accuracy = [] debug and log.debug('parseDateText currentMth %s currentDy %s', mth, dy) s = dateString.lower() m = self.ptc.CRE_DATE3.search(s) mth = m.group('mthname') mth = self.ptc.MonthOffsets[mth] accuracy.append('month') if m.group('day') is not None: dy = int(m.group('day')) accuracy.append('day') else: dy = 1 if m.group('year') is not None: yr = int(m.group('year')) accuracy.append('year') # birthday epoch constraint if yr < self.ptc.BirthdayEpoch: yr += 2000 elif yr < 100: yr += 1900 elif (mth < currentMth) or (mth == currentMth and dy < currentDy): # if that day and month have already passed in this year, # then increment the year by 1 yr += self.ptc.YearParseStyle with self.context() as ctx: if dy > 0 and dy <= self.ptc.daysInMonth(mth, yr): sourceTime = (yr, mth, dy, hr, mn, sec, wd, yd, isdst) ctx.updateAccuracy(*accuracy) else: # Return current time if date string is invalid sourceTime = time.localtime() debug and log.debug('parseDateText returned ' 'mth %d dy %d yr %d sourceTime %s', mth, dy, yr, sourceTime) return sourceTime def evalRanges(self, datetimeString, sourceTime=None): """ Evaluate the C{datetimeString} text and determine if it represents a date or time range. @type datetimeString: string @param datetimeString: datetime text to evaluate @type sourceTime: struct_time @param sourceTime: C{struct_time} value to use as the base @rtype: tuple @return: tuple of: start datetime, end datetime and the invalid flag """ rangeFlag = retFlag = 0 startStr = endStr = '' s = datetimeString.strip().lower() if self.ptc.rangeSep in s: s = s.replace(self.ptc.rangeSep, ' %s ' % self.ptc.rangeSep) s = s.replace(' ', ' ') for cre, rflag in [(self.ptc.CRE_TIMERNG1, 1), (self.ptc.CRE_TIMERNG2, 2), (self.ptc.CRE_TIMERNG4, 7), (self.ptc.CRE_TIMERNG3, 3), (self.ptc.CRE_DATERNG1, 4), (self.ptc.CRE_DATERNG2, 5), (self.ptc.CRE_DATERNG3, 6)]: m = cre.search(s) if m is not None: rangeFlag = rflag break debug and log.debug('evalRanges: rangeFlag = %s [%s]', rangeFlag, s) if m is not None: if (m.group() != s): # capture remaining string parseStr = m.group() chunk1 = s[:m.start()] chunk2 = s[m.end():] s = '%s %s' % (chunk1, chunk2) sourceTime, ctx = self.parse(s, sourceTime, VERSION_CONTEXT_STYLE) if not ctx.hasDateOrTime: sourceTime = None else: parseStr = s if rangeFlag in (1, 2): m = re.search(self.ptc.rangeSep, parseStr) startStr = parseStr[:m.start()] endStr = parseStr[m.start() + 1:] retFlag = 2 elif rangeFlag in (3, 7): m = re.search(self.ptc.rangeSep, parseStr) # capturing the meridian from the end time if self.ptc.usesMeridian: ampm = re.search(self.ptc.am[0], parseStr) # appending the meridian to the start time if ampm is not None: startStr = parseStr[:m.start()] + self.ptc.meridian[0] else: startStr = parseStr[:m.start()] + self.ptc.meridian[1] else: startStr = parseStr[:m.start()] endStr = parseStr[m.start() + 1:] retFlag = 2 elif rangeFlag == 4: m = re.search(self.ptc.rangeSep, parseStr) startStr = parseStr[:m.start()] endStr = parseStr[m.start() + 1:] retFlag = 1 elif rangeFlag == 5: m = re.search(self.ptc.rangeSep, parseStr) endStr = parseStr[m.start() + 1:] # capturing the year from the end date date = self.ptc.CRE_DATE3.search(endStr) endYear = date.group('year') # appending the year to the start date if the start date # does not have year information and the end date does. # eg : "Aug 21 - Sep 4, 2007" if endYear is not None: startStr = (parseStr[:m.start()]).strip() date = self.ptc.CRE_DATE3.search(startStr) startYear = date.group('year') if startYear is None: startStr = startStr + ', ' + endYear else: startStr = parseStr[:m.start()] retFlag = 1 elif rangeFlag == 6: m = re.search(self.ptc.rangeSep, parseStr) startStr = parseStr[:m.start()] # capturing the month from the start date mth = self.ptc.CRE_DATE3.search(startStr) mth = mth.group('mthname') # appending the month name to the end date endStr = mth + parseStr[(m.start() + 1):] retFlag = 1 else: # if range is not found startDT = endDT = time.localtime() if retFlag: startDT, sctx = self.parse(startStr, sourceTime, VERSION_CONTEXT_STYLE) endDT, ectx = self.parse(endStr, sourceTime, VERSION_CONTEXT_STYLE) if not sctx.hasDateOrTime or not ectx.hasDateOrTime: retFlag = 0 return startDT, endDT, retFlag def _CalculateDOWDelta(self, wd, wkdy, offset, style, currentDayStyle): """ Based on the C{style} and C{currentDayStyle} determine what day-of-week value is to be returned. @type wd: integer @param wd: day-of-week value for the current day @type wkdy: integer @param wkdy: day-of-week value for the parsed day @type offset: integer @param offset: offset direction for any modifiers (-1, 0, 1) @type style: integer @param style: normally the value set in C{Constants.DOWParseStyle} @type currentDayStyle: integer @param currentDayStyle: normally the value set in C{Constants.CurrentDOWParseStyle} @rtype: integer @return: calculated day-of-week """ diffBase = wkdy - wd origOffset = offset if offset == 2: # no modifier is present. # i.e. string to be parsed is just DOW if wkdy * style > wd * style or \ currentDayStyle and wkdy == wd: # wkdy located in current week offset = 0 elif style in (-1, 1): # wkdy located in last (-1) or next (1) week offset = style else: # invalid style, or should raise error? offset = 0 # offset = -1 means last week # offset = 0 means current week # offset = 1 means next week diff = diffBase + 7 * offset if style == 1 and diff < -7: diff += 7 elif style == -1 and diff > 7: diff -= 7 debug and log.debug("wd %s, wkdy %s, offset %d, " "style %d, currentDayStyle %d", wd, wkdy, origOffset, style, currentDayStyle) return diff def _quantityToReal(self, quantity): """ Convert a quantity, either spelled-out or numeric, to a float @type quantity: string @param quantity: quantity to parse to float @rtype: int @return: the quantity as an float, defaulting to 0.0 """ if not quantity: return 1.0 try: return float(quantity.replace(',', '.')) except ValueError: pass try: return float(self.ptc.numbers[quantity]) except KeyError: pass return 0.0 def _evalModifier(self, modifier, chunk1, chunk2, sourceTime): """ Evaluate the C{modifier} string and following text (passed in as C{chunk1} and C{chunk2}) and if they match any known modifiers calculate the delta and apply it to C{sourceTime}. @type modifier: string @param modifier: modifier text to apply to sourceTime @type chunk1: string @param chunk1: text chunk that preceded modifier (if any) @type chunk2: string @param chunk2: text chunk that followed modifier (if any) @type sourceTime: struct_time @param sourceTime: C{struct_time} value to use as the base @rtype: tuple @return: tuple of: remaining text and the modified sourceTime """ ctx = self.currentContext offset = self.ptc.Modifiers[modifier] if sourceTime is not None: (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = sourceTime else: (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = time.localtime() if self.ptc.StartTimeFromSourceTime: startHour = hr startMinute = mn startSecond = sec else: startHour = 9 startMinute = 0 startSecond = 0 # capture the units after the modifier and the remaining # string after the unit m = self.ptc.CRE_REMAINING.search(chunk2) if m is not None: index = m.start() + 1 unit = chunk2[:m.start()] chunk2 = chunk2[index:] else: unit = chunk2 chunk2 = '' debug and log.debug("modifier [%s] chunk1 [%s] " "chunk2 [%s] unit [%s]", modifier, chunk1, chunk2, unit) if unit in self.ptc.units['months']: currentDaysInMonth = self.ptc.daysInMonth(mth, yr) if offset == 0: dy = currentDaysInMonth sourceTime = (yr, mth, dy, startHour, startMinute, startSecond, wd, yd, isdst) elif offset == 2: # if day is the last day of the month, calculate the last day # of the next month if dy == currentDaysInMonth: dy = self.ptc.daysInMonth(mth + 1, yr) start = datetime.datetime(yr, mth, dy, startHour, startMinute, startSecond) target = self.inc(start, month=1) sourceTime = target.timetuple() else: start = datetime.datetime(yr, mth, 1, startHour, startMinute, startSecond) target = self.inc(start, month=offset) sourceTime = target.timetuple() ctx.updateAccuracy(ctx.ACU_MONTH) elif unit in self.ptc.units['weeks']: if offset == 0: start = datetime.datetime(yr, mth, dy, 17, 0, 0) target = start + datetime.timedelta(days=(4 - wd)) sourceTime = target.timetuple() elif offset == 2: start = datetime.datetime(yr, mth, dy, startHour, startMinute, startSecond) target = start + datetime.timedelta(days=7) sourceTime = target.timetuple() else: start = datetime.datetime(yr, mth, dy, startHour, startMinute, startSecond) target = start + offset * datetime.timedelta(weeks=1) sourceTime = target.timetuple() ctx.updateAccuracy(ctx.ACU_WEEK) elif unit in self.ptc.units['days']: if offset == 0: sourceTime = (yr, mth, dy, 17, 0, 0, wd, yd, isdst) ctx.updateAccuracy(ctx.ACU_HALFDAY) elif offset == 2: start = datetime.datetime(yr, mth, dy, hr, mn, sec) target = start + datetime.timedelta(days=1) sourceTime = target.timetuple() else: start = datetime.datetime(yr, mth, dy, startHour, startMinute, startSecond) target = start + datetime.timedelta(days=offset) sourceTime = target.timetuple() ctx.updateAccuracy(ctx.ACU_DAY) elif unit in self.ptc.units['hours']: if offset == 0: sourceTime = (yr, mth, dy, hr, 0, 0, wd, yd, isdst) else: start = datetime.datetime(yr, mth, dy, hr, 0, 0) target = start + datetime.timedelta(hours=offset) sourceTime = target.timetuple() ctx.updateAccuracy(ctx.ACU_HOUR) elif unit in self.ptc.units['years']: if offset == 0: sourceTime = (yr, 12, 31, hr, mn, sec, wd, yd, isdst) elif offset == 2: sourceTime = (yr + 1, mth, dy, hr, mn, sec, wd, yd, isdst) else: sourceTime = (yr + offset, 1, 1, startHour, startMinute, startSecond, wd, yd, isdst) ctx.updateAccuracy(ctx.ACU_YEAR) elif modifier == 'eom': dy = self.ptc.daysInMonth(mth, yr) sourceTime = (yr, mth, dy, startHour, startMinute, startSecond, wd, yd, isdst) ctx.updateAccuracy(ctx.ACU_DAY) elif modifier == 'eoy': mth = 12 dy = self.ptc.daysInMonth(mth, yr) sourceTime = (yr, mth, dy, startHour, startMinute, startSecond, wd, yd, isdst) ctx.updateAccuracy(ctx.ACU_MONTH) elif self.ptc.CRE_WEEKDAY.match(unit): m = self.ptc.CRE_WEEKDAY.match(unit) debug and log.debug('CRE_WEEKDAY matched') wkdy = m.group() if modifier == 'eod': ctx.updateAccuracy(ctx.ACU_HOUR) # Calculate the upcoming weekday sourceTime, subctx = self.parse(wkdy, sourceTime, VERSION_CONTEXT_STYLE) sTime = self.ptc.getSource(modifier, sourceTime) if sTime is not None: sourceTime = sTime ctx.updateAccuracy(ctx.ACU_HALFDAY) else: # unless one of these modifiers is being applied to the # day-of-week, we want to start with target as the day # in the current week. dowOffset = offset relativeModifier = modifier not in ['this', 'next', 'last', 'prior', 'previous'] if relativeModifier: dowOffset = 0 wkdy = self.ptc.WeekdayOffsets[wkdy] diff = self._CalculateDOWDelta( wd, wkdy, dowOffset, self.ptc.DOWParseStyle, self.ptc.CurrentDOWParseStyle) start = datetime.datetime(yr, mth, dy, startHour, startMinute, startSecond) target = start + datetime.timedelta(days=diff) if chunk1 != '' and relativeModifier: # consider "one day before thursday": we need to parse chunk1 ("one day") # and apply according to the offset ("before"), rather than allowing the # remaining parse step to apply "one day" without the offset direction. t, subctx = self.parse(chunk1, sourceTime, VERSION_CONTEXT_STYLE) if subctx.hasDateOrTime: delta = time.mktime(t) - time.mktime(sourceTime) target = start + datetime.timedelta(days=diff) + datetime.timedelta(seconds=delta * offset) chunk1 = '' sourceTime = target.timetuple() ctx.updateAccuracy(ctx.ACU_DAY) elif chunk1 == '' and chunk2 == '' and self.ptc.CRE_TIME.match(unit): m = self.ptc.CRE_TIME.match(unit) debug and log.debug('CRE_TIME matched') (yr, mth, dy, hr, mn, sec, wd, yd, isdst), subctx = \ self.parse(unit, None, VERSION_CONTEXT_STYLE) start = datetime.datetime(yr, mth, dy, hr, mn, sec) target = start + datetime.timedelta(days=offset) sourceTime = target.timetuple() else: # check if the remaining text is parsable and if so, # use it as the base time for the modifier source time debug and log.debug('check for modifications ' 'to source time [%s] [%s]', chunk1, unit) unit = unit.strip() if unit: s = '%s %s' % (unit, chunk2) t, subctx = self.parse(s, sourceTime, VERSION_CONTEXT_STYLE) if subctx.hasDate: # working with dates u = unit.lower() if u in self.ptc.Months or \ u in self.ptc.shortMonths: yr, mth, dy, hr, mn, sec, wd, yd, isdst = t start = datetime.datetime( yr, mth, dy, hr, mn, sec) t = self.inc(start, year=offset).timetuple() elif u in self.ptc.Weekdays: t = t + datetime.timedelta(weeks=offset) if subctx.hasDateOrTime: sourceTime = t chunk2 = '' chunk1 = chunk1.strip() # if the word after next is a number, the string is more than # likely to be "next 4 hrs" which we will have to combine the # units with the rest of the string if chunk1: try: m = list(self.ptc.CRE_NUMBER.finditer(chunk1))[-1] except IndexError: pass else: qty = None debug and log.debug('CRE_NUMBER matched') qty = self._quantityToReal(m.group()) * offset chunk1 = '%s%s%s' % (chunk1[:m.start()], qty, chunk1[m.end():]) t, subctx = self.parse(chunk1, sourceTime, VERSION_CONTEXT_STYLE) chunk1 = '' if subctx.hasDateOrTime: sourceTime = t debug and log.debug('looking for modifier %s', modifier) sTime = self.ptc.getSource(modifier, sourceTime) if sTime is not None: debug and log.debug('modifier found in sources') sourceTime = sTime ctx.updateAccuracy(ctx.ACU_HALFDAY) debug and log.debug('returning chunk = "%s %s" and sourceTime = %s', chunk1, chunk2, sourceTime) return '%s %s' % (chunk1, chunk2), sourceTime def _evalDT(self, datetimeString, sourceTime): """ Calculate the datetime from known format like RFC822 or W3CDTF Examples handled:: RFC822, W3CDTF formatted dates HH:MM[:SS][ am/pm] MM/DD/YYYY DD MMMM YYYY @type datetimeString: string @param datetimeString: text to try and parse as more "traditional" date/time text @type sourceTime: struct_time @param sourceTime: C{struct_time} value to use as the base @rtype: datetime @return: calculated C{struct_time} value or current C{struct_time} if not parsed """ ctx = self.currentContext s = datetimeString.strip() # Given string date is a RFC822 date if sourceTime is None: sourceTime = _parse_date_rfc822(s) debug and log.debug( 'attempt to parse as rfc822 - %s', str(sourceTime)) if sourceTime is not None: (yr, mth, dy, hr, mn, sec, wd, yd, isdst, _) = sourceTime ctx.updateAccuracy(ctx.ACU_YEAR, ctx.ACU_MONTH, ctx.ACU_DAY) if hr != 0 and mn != 0 and sec != 0: ctx.updateAccuracy(ctx.ACU_HOUR, ctx.ACU_MIN, ctx.ACU_SEC) sourceTime = (yr, mth, dy, hr, mn, sec, wd, yd, isdst) # Given string date is a W3CDTF date if sourceTime is None: sourceTime = _parse_date_w3dtf(s) if sourceTime is not None: ctx.updateAccuracy(ctx.ACU_YEAR, ctx.ACU_MONTH, ctx.ACU_DAY, ctx.ACU_HOUR, ctx.ACU_MIN, ctx.ACU_SEC) if sourceTime is None: sourceTime = time.localtime() return sourceTime def _evalUnits(self, datetimeString, sourceTime): """ Evaluate text passed by L{_partialParseUnits()} """ s = datetimeString.strip() sourceTime = self._evalDT(datetimeString, sourceTime) # Given string is a time string with units like "5 hrs 30 min" modifier = '' # TODO m = self.ptc.CRE_UNITS.search(s) if m is not None: units = m.group('units') quantity = s[:m.start('units')] sourceTime = self._buildTime(sourceTime, quantity, modifier, units) return sourceTime def _evalQUnits(self, datetimeString, sourceTime): """ Evaluate text passed by L{_partialParseQUnits()} """ s = datetimeString.strip() sourceTime = self._evalDT(datetimeString, sourceTime) # Given string is a time string with single char units like "5 h 30 m" modifier = '' # TODO m = self.ptc.CRE_QUNITS.search(s) if m is not None: units = m.group('qunits') quantity = s[:m.start('qunits')] sourceTime = self._buildTime(sourceTime, quantity, modifier, units) return sourceTime def _evalDateStr(self, datetimeString, sourceTime): """ Evaluate text passed by L{_partialParseDateStr()} """ s = datetimeString.strip() sourceTime = self._evalDT(datetimeString, sourceTime) # Given string is in the format "May 23rd, 2005" debug and log.debug('checking for MMM DD YYYY') return self.parseDateText(s, sourceTime) def _evalDateStd(self, datetimeString, sourceTime): """ Evaluate text passed by L{_partialParseDateStd()} """ s = datetimeString.strip() sourceTime = self._evalDT(datetimeString, sourceTime) # Given string is in the format 07/21/2006 return self.parseDate(s, sourceTime) def _evalDayStr(self, datetimeString, sourceTime): """ Evaluate text passed by L{_partialParseDaystr()} """ s = datetimeString.strip() sourceTime = self._evalDT(datetimeString, sourceTime) # Given string is a natural language date string like today, tomorrow.. (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = sourceTime try: offset = self.ptc.dayOffsets[s] except KeyError: offset = 0 if self.ptc.StartTimeFromSourceTime: startHour = hr startMinute = mn startSecond = sec else: startHour = 9 startMinute = 0 startSecond = 0 self.currentContext.updateAccuracy(pdtContext.ACU_DAY) start = datetime.datetime(yr, mth, dy, startHour, startMinute, startSecond) target = start + datetime.timedelta(days=offset) return target.timetuple() def _evalWeekday(self, datetimeString, sourceTime): """ Evaluate text passed by L{_partialParseWeekday()} """ s = datetimeString.strip() sourceTime = self._evalDT(datetimeString, sourceTime) # Given string is a weekday yr, mth, dy, hr, mn, sec, wd, yd, isdst = sourceTime start = datetime.datetime(yr, mth, dy, hr, mn, sec) wkdy = self.ptc.WeekdayOffsets[s] if wkdy > wd: qty = self._CalculateDOWDelta(wd, wkdy, 2, self.ptc.DOWParseStyle, self.ptc.CurrentDOWParseStyle) else: qty = self._CalculateDOWDelta(wd, wkdy, 2, self.ptc.DOWParseStyle, self.ptc.CurrentDOWParseStyle) self.currentContext.updateAccuracy(pdtContext.ACU_DAY) target = start + datetime.timedelta(days=qty) return target.timetuple() def _evalTimeStr(self, datetimeString, sourceTime): """ Evaluate text passed by L{_partialParseTimeStr()} """ s = datetimeString.strip() sourceTime = self._evalDT(datetimeString, sourceTime) if s in self.ptc.re_values['now']: self.currentContext.updateAccuracy(pdtContext.ACU_NOW) else: # Given string is a natural language time string like # lunch, midnight, etc sTime = self.ptc.getSource(s, sourceTime) if sTime: sourceTime = sTime self.currentContext.updateAccuracy(pdtContext.ACU_HALFDAY) return sourceTime def _evalMeridian(self, datetimeString, sourceTime): """ Evaluate text passed by L{_partialParseMeridian()} """ s = datetimeString.strip() sourceTime = self._evalDT(datetimeString, sourceTime) # Given string is in the format HH:MM(:SS)(am/pm) yr, mth, dy, hr, mn, sec, wd, yd, isdst = sourceTime m = self.ptc.CRE_TIMEHMS2.search(s) if m is not None: dt = s[:m.start('meridian')].strip() if len(dt) <= 2: hr = int(dt) mn = 0 sec = 0 else: hr, mn, sec = _extract_time(m) if hr == 24: hr = 0 meridian = m.group('meridian').lower() # if 'am' found and hour is 12 - force hour to 0 (midnight) if (meridian in self.ptc.am) and hr == 12: hr = 0 # if 'pm' found and hour < 12, add 12 to shift to evening if (meridian in self.ptc.pm) and hr < 12: hr += 12 # time validation if hr < 24 and mn < 60 and sec < 60: sourceTime = (yr, mth, dy, hr, mn, sec, wd, yd, isdst) _pop_time_accuracy(m, self.currentContext) return sourceTime def _evalTimeStd(self, datetimeString, sourceTime): """ Evaluate text passed by L{_partialParseTimeStd()} """ s = datetimeString.strip() sourceTime = self._evalDT(datetimeString, sourceTime) # Given string is in the format HH:MM(:SS) yr, mth, dy, hr, mn, sec, wd, yd, isdst = sourceTime m = self.ptc.CRE_TIMEHMS.search(s) if m is not None: hr, mn, sec = _extract_time(m) if hr == 24: hr = 0 # time validation if hr < 24 and mn < 60 and sec < 60: sourceTime = (yr, mth, dy, hr, mn, sec, wd, yd, isdst) _pop_time_accuracy(m, self.currentContext) return sourceTime def _UnitsTrapped(self, s, m, key): # check if a day suffix got trapped by a unit match # for example Dec 31st would match for 31s (aka 31 seconds) # Dec 31st # ^ ^ # | +-- m.start('units') # | and also m2.start('suffix') # +---- m.start('qty') # and also m2.start('day') m2 = self.ptc.CRE_DAY2.search(s) if m2 is not None: t = '%s%s' % (m2.group('day'), m.group(key)) if m.start(key) == m2.start('suffix') and \ m.start('qty') == m2.start('day') and \ m.group('qty') == t: return True else: return False else: return False def _partialParseModifier(self, s, sourceTime): """ test if giving C{s} matched CRE_MODIFIER, used by L{parse()} @type s: string @param s: date/time text to evaluate @type sourceTime: struct_time @param sourceTime: C{struct_time} value to use as the base @rtype: tuple @return: tuple of remained date/time text, datetime object and an boolean value to describ if matched or not """ parseStr = None chunk1 = chunk2 = '' # Modifier like next/prev/from/after/prior.. m = self.ptc.CRE_MODIFIER.search(s) if m is not None: if m.group() != s: # capture remaining string parseStr = m.group() chunk1 = s[:m.start()].strip() chunk2 = s[m.end():].strip() else: parseStr = s if parseStr: debug and log.debug('found (modifier) [%s][%s][%s]', parseStr, chunk1, chunk2) s, sourceTime = self._evalModifier(parseStr, chunk1, chunk2, sourceTime) return s, sourceTime, bool(parseStr) def _partialParseUnits(self, s, sourceTime): """ test if giving C{s} matched CRE_UNITS, used by L{parse()} @type s: string @param s: date/time text to evaluate @type sourceTime: struct_time @param sourceTime: C{struct_time} value to use as the base @rtype: tuple @return: tuple of remained date/time text, datetime object and an boolean value to describ if matched or not """ parseStr = None chunk1 = chunk2 = '' # Quantity + Units m = self.ptc.CRE_UNITS.search(s) if m is not None: debug and log.debug('CRE_UNITS matched') if self._UnitsTrapped(s, m, 'units'): debug and log.debug('day suffix trapped by unit match') else: if (m.group('qty') != s): # capture remaining string parseStr = m.group('qty') chunk1 = s[:m.start('qty')].strip() chunk2 = s[m.end('qty'):].strip() if chunk1[-1:] == '-': parseStr = '-%s' % parseStr chunk1 = chunk1[:-1] s = '%s %s' % (chunk1, chunk2) else: parseStr = s s = '' if parseStr: debug and log.debug('found (units) [%s][%s][%s]', parseStr, chunk1, chunk2) sourceTime = self._evalUnits(parseStr, sourceTime) return s, sourceTime, bool(parseStr) def _partialParseQUnits(self, s, sourceTime): """ test if giving C{s} matched CRE_QUNITS, used by L{parse()} @type s: string @param s: date/time text to evaluate @type sourceTime: struct_time @param sourceTime: C{struct_time} value to use as the base @rtype: tuple @return: tuple of remained date/time text, datetime object and an boolean value to describ if matched or not """ parseStr = None chunk1 = chunk2 = '' # Quantity + Units m = self.ptc.CRE_QUNITS.search(s) if m is not None: debug and log.debug('CRE_QUNITS matched') if self._UnitsTrapped(s, m, 'qunits'): debug and log.debug( 'day suffix trapped by qunit match') else: if (m.group('qty') != s): # capture remaining string parseStr = m.group('qty') chunk1 = s[:m.start('qty')].strip() chunk2 = s[m.end('qty'):].strip() if chunk1[-1:] == '-': parseStr = '-%s' % parseStr chunk1 = chunk1[:-1] s = '%s %s' % (chunk1, chunk2) else: parseStr = s s = '' if parseStr: debug and log.debug('found (qunits) [%s][%s][%s]', parseStr, chunk1, chunk2) sourceTime = self._evalQUnits(parseStr, sourceTime) return s, sourceTime, bool(parseStr) def _partialParseDateStr(self, s, sourceTime): """ test if giving C{s} matched CRE_DATE3, used by L{parse()} @type s: string @param s: date/time text to evaluate @type sourceTime: struct_time @param sourceTime: C{struct_time} value to use as the base @rtype: tuple @return: tuple of remained date/time text, datetime object and an boolean value to describ if matched or not """ parseStr = None chunk1 = chunk2 = '' m = self.ptc.CRE_DATE3.search(s) # NO LONGER NEEDED, THE REGEXP HANDLED MTHNAME NOW # for match in self.ptc.CRE_DATE3.finditer(s): # to prevent "HH:MM(:SS) time strings" expressions from # triggering this regex, we checks if the month field # exists in the searched expression, if it doesn't exist, # the date field is not valid # if match.group('mthname'): # m = self.ptc.CRE_DATE3.search(s, match.start()) # valid_date = True # break # String date format if m is not None: if (m.group('date') != s): # capture remaining string mStart = m.start('date') mEnd = m.end('date') # we need to check that anything following the parsed # date is a time expression because it is often picked # up as a valid year if the hour is 2 digits fTime = False mm = self.ptc.CRE_TIMEHMS2.search(s) # "February 24th 1PM" doesn't get caught # "February 24th 12PM" does mYear = m.group('year') if mm is not None and mYear is not None: fTime = True else: # "February 24th 12:00" mm = self.ptc.CRE_TIMEHMS.search(s) if mm is not None and mYear is None: fTime = True if fTime: hoursStart = mm.start('hours') if hoursStart < m.end('year'): mEnd = hoursStart parseStr = s[mStart:mEnd] chunk1 = s[:mStart] chunk2 = s[mEnd:] s = '%s %s' % (chunk1, chunk2) else: parseStr = s s = '' if parseStr: debug and log.debug( 'found (date3) [%s][%s][%s]', parseStr, chunk1, chunk2) sourceTime = self._evalDateStr(parseStr, sourceTime) return s, sourceTime, bool(parseStr) def _partialParseDateStd(self, s, sourceTime): """ test if giving C{s} matched CRE_DATE, used by L{parse()} @type s: string @param s: date/time text to evaluate @type sourceTime: struct_time @param sourceTime: C{struct_time} value to use as the base @rtype: tuple @return: tuple of remained date/time text, datetime object and an boolean value to describ if matched or not """ parseStr = None chunk1 = chunk2 = '' # Standard date format m = self.ptc.CRE_DATE.search(s) if m is not None: if (m.group('date') != s): # capture remaining string parseStr = m.group('date') chunk1 = s[:m.start('date')] chunk2 = s[m.end('date'):] s = '%s %s' % (chunk1, chunk2) else: parseStr = s s = '' if parseStr: debug and log.debug( 'found (date) [%s][%s][%s]', parseStr, chunk1, chunk2) sourceTime = self._evalDateStd(parseStr, sourceTime) return s, sourceTime, bool(parseStr) def _partialParseDayStr(self, s, sourceTime): """ test if giving C{s} matched CRE_DAY, used by L{parse()} @type s: string @param s: date/time text to evaluate @type sourceTime: struct_time @param sourceTime: C{struct_time} value to use as the base @rtype: tuple @return: tuple of remained date/time text, datetime object and an boolean value to describ if matched or not """ parseStr = None chunk1 = chunk2 = '' # Natural language day strings m = self.ptc.CRE_DAY.search(s) if m is not None: if (m.group() != s): # capture remaining string parseStr = m.group() chunk1 = s[:m.start()] chunk2 = s[m.end():] s = '%s %s' % (chunk1, chunk2) else: parseStr = s s = '' if parseStr: debug and log.debug( 'found (day) [%s][%s][%s]', parseStr, chunk1, chunk2) sourceTime = self._evalDayStr(parseStr, sourceTime) return s, sourceTime, bool(parseStr) def _partialParseWeekday(self, s, sourceTime): """ test if giving C{s} matched CRE_WEEKDAY, used by L{parse()} @type s: string @param s: date/time text to evaluate @type sourceTime: struct_time @param sourceTime: C{struct_time} value to use as the base @rtype: tuple @return: tuple of remained date/time text, datetime object and an boolean value to describ if matched or not """ parseStr = None chunk1 = chunk2 = '' ctx = self.currentContext log.debug('eval %s with context - %s, %s', s, ctx.hasDate, ctx.hasTime) # Weekday m = self.ptc.CRE_WEEKDAY.search(s) if m is not None: gv = m.group() if s not in self.ptc.dayOffsets: if (gv != s): # capture remaining string parseStr = gv chunk1 = s[:m.start()] chunk2 = s[m.end():] s = '%s %s' % (chunk1, chunk2) else: parseStr = s s = '' if parseStr and not ctx.hasDate: debug and log.debug( 'found (weekday) [%s][%s][%s]', parseStr, chunk1, chunk2) sourceTime = self._evalWeekday(parseStr, sourceTime) return s, sourceTime, bool(parseStr) def _partialParseTimeStr(self, s, sourceTime): """ test if giving C{s} matched CRE_TIME, used by L{parse()} @type s: string @param s: date/time text to evaluate @type sourceTime: struct_time @param sourceTime: C{struct_time} value to use as the base @rtype: tuple @return: tuple of remained date/time text, datetime object and an boolean value to describ if matched or not """ parseStr = None chunk1 = chunk2 = '' # Natural language time strings m = self.ptc.CRE_TIME.search(s) if m is not None or s in self.ptc.re_values['now']: if (m and m.group() != s): # capture remaining string parseStr = m.group() chunk1 = s[:m.start()] chunk2 = s[m.end():] s = '%s %s' % (chunk1, chunk2) else: parseStr = s s = '' if parseStr: debug and log.debug( 'found (time) [%s][%s][%s]', parseStr, chunk1, chunk2) sourceTime = self._evalTimeStr(parseStr, sourceTime) return s, sourceTime, bool(parseStr) def _partialParseMeridian(self, s, sourceTime): """ test if giving C{s} matched CRE_TIMEHMS2, used by L{parse()} @type s: string @param s: date/time text to evaluate @type sourceTime: struct_time @param sourceTime: C{struct_time} value to use as the base @rtype: tuple @return: tuple of remained date/time text, datetime object and an boolean value to describ if matched or not """ parseStr = None chunk1 = chunk2 = '' # HH:MM(:SS) am/pm time strings m = self.ptc.CRE_TIMEHMS2.search(s) if m is not None: if m.group('minutes') is not None: if m.group('seconds') is not None: parseStr = '%s:%s:%s' % (m.group('hours'), m.group('minutes'), m.group('seconds')) else: parseStr = '%s:%s' % (m.group('hours'), m.group('minutes')) else: parseStr = m.group('hours') parseStr += ' ' + m.group('meridian') chunk1 = s[:m.start()] chunk2 = s[m.end():] s = '%s %s' % (chunk1, chunk2) if parseStr: debug and log.debug('found (meridian) [%s][%s][%s]', parseStr, chunk1, chunk2) sourceTime = self._evalMeridian(parseStr, sourceTime) return s, sourceTime, bool(parseStr) def _partialParseTimeStd(self, s, sourceTime): """ test if giving C{s} matched CRE_TIMEHMS, used by L{parse()} @type s: string @param s: date/time text to evaluate @type sourceTime: struct_time @param sourceTime: C{struct_time} value to use as the base @rtype: tuple @return: tuple of remained date/time text, datetime object and an boolean value to describ if matched or not """ parseStr = None chunk1 = chunk2 = '' # HH:MM(:SS) time strings m = self.ptc.CRE_TIMEHMS.search(s) if m is not None: if m.group('seconds') is not None: parseStr = '%s:%s:%s' % (m.group('hours'), m.group('minutes'), m.group('seconds')) chunk1 = s[:m.start('hours')] chunk2 = s[m.end('seconds'):] else: parseStr = '%s:%s' % (m.group('hours'), m.group('minutes')) chunk1 = s[:m.start('hours')] chunk2 = s[m.end('minutes'):] s = '%s %s' % (chunk1, chunk2) if parseStr: debug and log.debug( 'found (hms) [%s][%s][%s]', parseStr, chunk1, chunk2) sourceTime = self._evalTimeStd(parseStr, sourceTime) return s, sourceTime, bool(parseStr) def parseDT(self, datetimeString, sourceTime=None, tzinfo=None, version=None): """ C{datetimeString} is as C{.parse}, C{sourceTime} has the same semantic meaning as C{.parse}, but now also accepts datetime objects. C{tzinfo} accepts a tzinfo object. It is advisable to use pytz. @type datetimeString: string @param datetimeString: date/time text to evaluate @type sourceTime: struct_time, datetime, date, time @param sourceTime: time value to use as the base @type tzinfo: tzinfo @param tzinfo: Timezone to apply to generated datetime objs. @type version: integer @param version: style version, default will use L{Calendar} parameter version value @rtype: tuple @return: tuple of: modified C{sourceTime} and the result flag/context see .parse for return code details. """ # if sourceTime has a timetuple method, use thet, else, just pass the # entire thing to parse and prey the user knows what the hell they are # doing. sourceTime = getattr(sourceTime, 'timetuple', (lambda: sourceTime))() # You REALLY SHOULD be using pytz. Using localize if available, # hacking if not. Note, None is a valid tzinfo object in the case of # the ugly hack. localize = getattr( tzinfo, 'localize', (lambda dt: dt.replace(tzinfo=tzinfo)), # ugly hack is ugly :( ) # Punt time_struct, ret_code = self.parse( datetimeString, sourceTime=sourceTime, version=version) # Comments from GHI indicate that it is desired to have the same return # signature on this method as that one it punts to, with the exception # of using datetime objects instead of time_structs. dt = localize(datetime.datetime(*time_struct[:6])) return dt, ret_code def parse(self, datetimeString, sourceTime=None, version=None): """ Splits the given C{datetimeString} into tokens, finds the regex patterns that match and then calculates a C{struct_time} value from the chunks. If C{sourceTime} is given then the C{struct_time} value will be calculated from that value, otherwise from the current date/time. If the C{datetimeString} is parsed and date/time value found, then:: If C{version} equals to L{VERSION_FLAG_STYLE}, the second item of the returned tuple will be a flag to let you know what kind of C{struct_time} value is being returned:: 0 = not parsed at all 1 = parsed as a C{date} 2 = parsed as a C{time} 3 = parsed as a C{datetime} If C{version} equals to L{VERSION_CONTEXT_STYLE}, the second value will be an instance of L{pdtContext} @type datetimeString: string @param datetimeString: date/time text to evaluate @type sourceTime: struct_time @param sourceTime: C{struct_time} value to use as the base @type version: integer @param version: style version, default will use L{Calendar} parameter version value @rtype: tuple @return: tuple of: modified C{sourceTime} and the result flag/context """ debug and log.debug('parse()') datetimeString = re.sub(r'(\w)\.(\s)', r'\1\2', datetimeString) datetimeString = re.sub(r'(\w)[\'"](\s|$)', r'\1 \2', datetimeString) datetimeString = re.sub(r'(\s|^)[\'"](\w)', r'\1 \2', datetimeString) if sourceTime: if isinstance(sourceTime, datetime.datetime): debug and log.debug('coercing datetime to timetuple') sourceTime = sourceTime.timetuple() else: if not isinstance(sourceTime, time.struct_time) and \ not isinstance(sourceTime, tuple): raise ValueError('sourceTime is not a struct_time') else: sourceTime = time.localtime() with self.context() as ctx: s = datetimeString.lower().strip() debug and log.debug('remainedString (before parsing): [%s]', s) while s: for parseMeth in (self._partialParseModifier, self._partialParseUnits, self._partialParseQUnits, self._partialParseDateStr, self._partialParseDateStd, self._partialParseDayStr, self._partialParseWeekday, self._partialParseTimeStr, self._partialParseMeridian, self._partialParseTimeStd): retS, retTime, matched = parseMeth(s, sourceTime) if matched: s, sourceTime = retS.strip(), retTime break else: # nothing matched s = '' debug and log.debug('hasDate: [%s], hasTime: [%s]', ctx.hasDate, ctx.hasTime) debug and log.debug('remainedString: [%s]', s) # String is not parsed at all if sourceTime is None: debug and log.debug('not parsed [%s]', str(sourceTime)) sourceTime = time.localtime() if not isinstance(sourceTime, time.struct_time): sourceTime = time.struct_time(sourceTime) version = self.version if version is None else version if version == VERSION_CONTEXT_STYLE: return sourceTime, ctx else: return sourceTime, ctx.dateTimeFlag def inc(self, source, month=None, year=None): """ Takes the given C{source} date, or current date if none is passed, and increments it according to the values passed in by month and/or year. This routine is needed because Python's C{timedelta()} function does not allow for month or year increments. @type source: struct_time @param source: C{struct_time} value to increment @type month: float or integer @param month: optional number of months to increment @type year: float or integer @param year: optional number of years to increment @rtype: datetime @return: C{source} incremented by the number of months and/or years """ yr = source.year mth = source.month dy = source.day try: month = float(month) except (TypeError, ValueError): month = 0 try: year = float(year) except (TypeError, ValueError): year = 0 finally: month += year * 12 year = 0 subMi = 0.0 maxDay = 0 if month: mi = int(month) subMi = month - mi y = int(mi / 12.0) m = mi - y * 12 mth = mth + m if mth < 1: # cross start-of-year? y -= 1 # yes - decrement year mth += 12 # and fix month elif mth > 12: # cross end-of-year? y += 1 # yes - increment year mth -= 12 # and fix month yr += y # if the day ends up past the last day of # the new month, set it to the last day maxDay = self.ptc.daysInMonth(mth, yr) if dy > maxDay: dy = maxDay if yr > datetime.MAXYEAR or yr < datetime.MINYEAR: raise OverflowError('year is out of range') d = source.replace(year=yr, month=mth, day=dy) if subMi: d += datetime.timedelta(days=subMi * maxDay) return source + (d - source) def nlp(self, inputString, sourceTime=None, version=None): """Utilizes parse() after making judgements about what datetime information belongs together. It makes logical groupings based on proximity and returns a parsed datetime for each matched grouping of datetime text, along with location info within the given inputString. @type inputString: string @param inputString: natural language text to evaluate @type sourceTime: struct_time @param sourceTime: C{struct_time} value to use as the base @type version: integer @param version: style version, default will use L{Calendar} parameter version value @rtype: tuple or None @return: tuple of tuples in the format (parsed_datetime as datetime.datetime, flags as int, start_pos as int, end_pos as int, matched_text as string) or None if there were no matches """ orig_inputstring = inputString # replace periods at the end of sentences w/ spaces # opposed to removing them altogether in order to # retain relative positions (identified by alpha, period, space). # this is required for some of the regex patterns to match inputString = re.sub(r'(\w)(\.)(\s)', r'\1 \3', inputString).lower() inputString = re.sub(r'(\w)(\'|")(\s|$)', r'\1 \3', inputString) inputString = re.sub(r'(\s|^)(\'|")(\w)', r'\1 \3', inputString) startpos = 0 # the start position in the inputString during the loop # list of lists in format: # [startpos, endpos, matchedstring, flags, type] matches = [] while startpos < len(inputString): # empty match leftmost_match = [0, 0, None, 0, None] # Modifier like next\prev.. m = self.ptc.CRE_MODIFIER.search(inputString[startpos:]) if m is not None: if leftmost_match[1] == 0 or \ leftmost_match[0] > m.start() + startpos: leftmost_match[0] = m.start() + startpos leftmost_match[1] = m.end() + startpos leftmost_match[2] = m.group() leftmost_match[3] = 0 leftmost_match[4] = 'modifier' # Quantity + Units m = self.ptc.CRE_UNITS.search(inputString[startpos:]) if m is not None: debug and log.debug('CRE_UNITS matched') if self._UnitsTrapped(inputString[startpos:], m, 'units'): debug and log.debug('day suffix trapped by unit match') else: if leftmost_match[1] == 0 or \ leftmost_match[0] > m.start('qty') + startpos: leftmost_match[0] = m.start('qty') + startpos leftmost_match[1] = m.end('qty') + startpos leftmost_match[2] = m.group('qty') leftmost_match[3] = 3 leftmost_match[4] = 'units' if m.start('qty') > 0 and \ inputString[m.start('qty') - 1] == '-': leftmost_match[0] = leftmost_match[0] - 1 leftmost_match[2] = '-' + leftmost_match[2] # Quantity + Units m = self.ptc.CRE_QUNITS.search(inputString[startpos:]) if m is not None: debug and log.debug('CRE_QUNITS matched') if self._UnitsTrapped(inputString[startpos:], m, 'qunits'): debug and log.debug('day suffix trapped by qunit match') else: if leftmost_match[1] == 0 or \ leftmost_match[0] > m.start('qty') + startpos: leftmost_match[0] = m.start('qty') + startpos leftmost_match[1] = m.end('qty') + startpos leftmost_match[2] = m.group('qty') leftmost_match[3] = 3 leftmost_match[4] = 'qunits' if m.start('qty') > 0 and \ inputString[m.start('qty') - 1] == '-': leftmost_match[0] = leftmost_match[0] - 1 leftmost_match[2] = '-' + leftmost_match[2] m = self.ptc.CRE_DATE3.search(inputString[startpos:]) # NO LONGER NEEDED, THE REGEXP HANDLED MTHNAME NOW # for match in self.ptc.CRE_DATE3.finditer(inputString[startpos:]): # to prevent "HH:MM(:SS) time strings" expressions from # triggering this regex, we checks if the month field exists # in the searched expression, if it doesn't exist, the date # field is not valid # if match.group('mthname'): # m = self.ptc.CRE_DATE3.search(inputString[startpos:], # match.start()) # break # String date format if m is not None: if leftmost_match[1] == 0 or \ leftmost_match[0] > m.start('date') + startpos: leftmost_match[0] = m.start('date') + startpos leftmost_match[1] = m.end('date') + startpos leftmost_match[2] = m.group('date') leftmost_match[3] = 1 leftmost_match[4] = 'dateStr' # Standard date format m = self.ptc.CRE_DATE.search(inputString[startpos:]) if m is not None: if leftmost_match[1] == 0 or \ leftmost_match[0] > m.start('date') + startpos: leftmost_match[0] = m.start('date') + startpos leftmost_match[1] = m.end('date') + startpos leftmost_match[2] = m.group('date') leftmost_match[3] = 1 leftmost_match[4] = 'dateStd' # Natural language day strings m = self.ptc.CRE_DAY.search(inputString[startpos:]) if m is not None: if leftmost_match[1] == 0 or \ leftmost_match[0] > m.start() + startpos: leftmost_match[0] = m.start() + startpos leftmost_match[1] = m.end() + startpos leftmost_match[2] = m.group() leftmost_match[3] = 1 leftmost_match[4] = 'dayStr' # Weekday m = self.ptc.CRE_WEEKDAY.search(inputString[startpos:]) if m is not None: if inputString[startpos:] not in self.ptc.dayOffsets: if leftmost_match[1] == 0 or \ leftmost_match[0] > m.start() + startpos: leftmost_match[0] = m.start() + startpos leftmost_match[1] = m.end() + startpos leftmost_match[2] = m.group() leftmost_match[3] = 1 leftmost_match[4] = 'weekdy' # Natural language time strings m = self.ptc.CRE_TIME.search(inputString[startpos:]) if m is not None: if leftmost_match[1] == 0 or \ leftmost_match[0] > m.start() + startpos: leftmost_match[0] = m.start() + startpos leftmost_match[1] = m.end() + startpos leftmost_match[2] = m.group() leftmost_match[3] = 2 leftmost_match[4] = 'timeStr' # HH:MM(:SS) am/pm time strings m = self.ptc.CRE_TIMEHMS2.search(inputString[startpos:]) if m is not None: if leftmost_match[1] == 0 or \ leftmost_match[0] > m.start('hours') + startpos: leftmost_match[0] = m.start('hours') + startpos leftmost_match[1] = m.end('meridian') + startpos leftmost_match[2] = inputString[leftmost_match[0]: leftmost_match[1]] leftmost_match[3] = 2 leftmost_match[4] = 'meridian' # HH:MM(:SS) time strings m = self.ptc.CRE_TIMEHMS.search(inputString[startpos:]) if m is not None: if leftmost_match[1] == 0 or \ leftmost_match[0] > m.start('hours') + startpos: leftmost_match[0] = m.start('hours') + startpos if m.group('seconds') is not None: leftmost_match[1] = m.end('seconds') + startpos else: leftmost_match[1] = m.end('minutes') + startpos leftmost_match[2] = inputString[leftmost_match[0]: leftmost_match[1]] leftmost_match[3] = 2 leftmost_match[4] = 'timeStd' # Units only; must be preceded by a modifier if len(matches) > 0 and matches[-1][3] == 0: m = self.ptc.CRE_UNITS_ONLY.search(inputString[startpos:]) # Ensure that any match is immediately proceded by the # modifier. "Next is the word 'month'" should not parse as a # date while "next month" should if m is not None and \ inputString[startpos:startpos + m.start()].strip() == '': debug and log.debug('CRE_UNITS_ONLY matched [%s]', m.group()) if leftmost_match[1] == 0 or \ leftmost_match[0] > m.start() + startpos: leftmost_match[0] = m.start() + startpos leftmost_match[1] = m.end() + startpos leftmost_match[2] = m.group() leftmost_match[3] = 3 leftmost_match[4] = 'unitsOnly' # set the start position to the end pos of the leftmost match startpos = leftmost_match[1] # nothing was detected # so break out of the loop if startpos == 0: startpos = len(inputString) else: if leftmost_match[3] > 0: m = self.ptc.CRE_NLP_PREFIX.search( inputString[:leftmost_match[0]] + ' ' + str(leftmost_match[3])) if m is not None: leftmost_match[0] = m.start('nlp_prefix') leftmost_match[2] = inputString[leftmost_match[0]: leftmost_match[1]] matches.append(leftmost_match) # find matches in proximity with one another and # return all the parsed values proximity_matches = [] if len(matches) > 1: combined = '' from_match_index = 0 date = matches[0][3] == 1 time = matches[0][3] == 2 units = matches[0][3] == 3 for i in range(1, len(matches)): # test proximity (are there characters between matches?) endofprevious = matches[i - 1][1] begofcurrent = matches[i][0] if orig_inputstring[endofprevious: begofcurrent].lower().strip() != '': # this one isn't in proximity, but maybe # we have enough to make a datetime # TODO: make sure the combination of # formats (modifier, dateStd, etc) makes logical sense # before parsing together if date or time or units: combined = orig_inputstring[matches[from_match_index] [0]:matches[i - 1][1]] parsed_datetime, flags = self.parse(combined, sourceTime, version) proximity_matches.append(( datetime.datetime(*parsed_datetime[:6]), flags, matches[from_match_index][0], matches[i - 1][1], combined)) # not in proximity, reset starting from current from_match_index = i date = matches[i][3] == 1 time = matches[i][3] == 2 units = matches[i][3] == 3 continue else: if matches[i][3] == 1: date = True if matches[i][3] == 2: time = True if matches[i][3] == 3: units = True # check last # we have enough to make a datetime if date or time or units: combined = orig_inputstring[matches[from_match_index][0]: matches[len(matches) - 1][1]] parsed_datetime, flags = self.parse(combined, sourceTime, version) proximity_matches.append(( datetime.datetime(*parsed_datetime[:6]), flags, matches[from_match_index][0], matches[len(matches) - 1][1], combined)) elif len(matches) == 0: return None else: if matches[0][3] == 0: # not enough info to parse return None else: combined = orig_inputstring[matches[0][0]:matches[0][1]] parsed_datetime, flags = self.parse(matches[0][2], sourceTime, version) proximity_matches.append(( datetime.datetime(*parsed_datetime[:6]), flags, matches[0][0], matches[0][1], combined)) return tuple(proximity_matches) def _initSymbols(ptc): """ Initialize symbols and single character constants. """ # build am and pm lists to contain # original case, lowercase, first-char and dotted # versions of the meridian text ptc.am = ['', ''] ptc.pm = ['', ''] for idx, xm in enumerate(ptc.locale.meridian[:2]): # 0: am # 1: pm target = ['am', 'pm'][idx] setattr(ptc, target, [xm]) target = getattr(ptc, target) if xm: lxm = xm.lower() target.extend((xm[0], '{0}.{1}.'.format(*xm), lxm, lxm[0], '{0}.{1}.'.format(*lxm))) class Constants(object): """ Default set of constants for parsedatetime. If PyICU is present, then the class will first try to get PyICU to return a locale specified by C{localeID}. If either C{localeID} is None or if the locale does not exist within PyICU, then each of the locales defined in C{fallbackLocales} is tried in order. If PyICU is not present or none of the specified locales can be used, then the class will initialize itself to the en_US locale. if PyICU is not present or not requested, only the locales defined by C{pdtLocales} will be searched. """ def __init__(self, localeID=None, usePyICU=True, fallbackLocales=['en_US']): self.localeID = localeID self.fallbackLocales = fallbackLocales[:] if 'en_US' not in self.fallbackLocales: self.fallbackLocales.append('en_US') # define non-locale specific constants self.locale = None self.usePyICU = usePyICU # starting cache of leap years # daysInMonth will add to this if during # runtime it gets a request for a year not found self._leapYears = list(range(1904, 2097, 4)) self.Second = 1 self.Minute = 60 # 60 * self.Second self.Hour = 3600 # 60 * self.Minute self.Day = 86400 # 24 * self.Hour self.Week = 604800 # 7 * self.Day self.Month = 2592000 # 30 * self.Day self.Year = 31536000 # 365 * self.Day self._DaysInMonthList = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) self.rangeSep = '-' self.BirthdayEpoch = 50 # When True the starting time for all relative calculations will come # from the given SourceTime, otherwise it will be 9am self.StartTimeFromSourceTime = False # YearParseStyle controls how we parse "Jun 12", i.e. dates that do # not have a year present. The default is to compare the date given # to the current date, and if prior, then assume the next year. # Setting this to 0 will prevent that. self.YearParseStyle = 1 # DOWParseStyle controls how we parse "Tuesday" # If the current day was Thursday and the text to parse is "Tuesday" # then the following table shows how each style would be returned # -1, 0, +1 # # Current day marked as *** # # Sun Mon Tue Wed Thu Fri Sat # week -1 # current -1,0 *** # week +1 +1 # # If the current day was Monday and the text to parse is "Tuesday" # then the following table shows how each style would be returned # -1, 0, +1 # # Sun Mon Tue Wed Thu Fri Sat # week -1 -1 # current *** 0,+1 # week +1 self.DOWParseStyle = 1 # CurrentDOWParseStyle controls how we parse "Friday" # If the current day was Friday and the text to parse is "Friday" # then the following table shows how each style would be returned # True/False. This also depends on DOWParseStyle. # # Current day marked as *** # # DOWParseStyle = 0 # Sun Mon Tue Wed Thu Fri Sat # week -1 # current T,F # week +1 # # DOWParseStyle = -1 # Sun Mon Tue Wed Thu Fri Sat # week -1 F # current T # week +1 # # DOWParseStyle = +1 # # Sun Mon Tue Wed Thu Fri Sat # week -1 # current T # week +1 F self.CurrentDOWParseStyle = False if self.usePyICU: self.locale = get_icu(self.localeID) if self.locale.icu is None: self.usePyICU = False self.locale = None if self.locale is None: if self.localeID not in pdtLocales: for localeId in range(0, len(self.fallbackLocales)): self.localeID = self.fallbackLocales[localeId] if self.localeID in pdtLocales: break self.locale = pdtLocales[self.localeID] if self.locale is not None: def _getLocaleDataAdjusted(localeData): """ If localeData is defined as ["mon|mnd", 'tu|tues'...] then this function splits those definitions on | """ adjusted = [] for d in localeData: if '|' in d: adjusted += d.split("|") else: adjusted.append(d) return adjusted def re_join(g): return '|'.join(re.escape(i) for i in g) mths = _getLocaleDataAdjusted(self.locale.Months) smths = _getLocaleDataAdjusted(self.locale.shortMonths) swds = _getLocaleDataAdjusted(self.locale.shortWeekdays) wds = _getLocaleDataAdjusted(self.locale.Weekdays) # escape any regex special characters that may be found self.locale.re_values['months'] = re_join(mths) self.locale.re_values['shortmonths'] = re_join(smths) self.locale.re_values['days'] = re_join(wds) self.locale.re_values['shortdays'] = re_join(swds) self.locale.re_values['dayoffsets'] = \ re_join(self.locale.dayOffsets) self.locale.re_values['numbers'] = \ re_join(self.locale.numbers) self.locale.re_values['decimal_mark'] = \ re.escape(self.locale.decimal_mark) units = [unit for units in self.locale.units.values() for unit in units] # flatten units.sort(key=len, reverse=True) # longest first self.locale.re_values['units'] = re_join(units) self.locale.re_values['modifiers'] = re_join(self.locale.Modifiers) self.locale.re_values['sources'] = re_join(self.locale.re_sources) # For distinguishing numeric dates from times, look for timeSep # and meridian, if specified in the locale self.locale.re_values['timecomponents'] = \ re_join(self.locale.timeSep + self.locale.meridian) # build weekday offsets - yes, it assumes the Weekday and # shortWeekday lists are in the same order and Mon..Sun # (Python style) def _buildOffsets(offsetDict, localeData, indexStart): o = indexStart for key in localeData: if '|' in key: for k in key.split('|'): offsetDict[k] = o else: offsetDict[key] = o o += 1 _buildOffsets(self.locale.WeekdayOffsets, self.locale.Weekdays, 0) _buildOffsets(self.locale.WeekdayOffsets, self.locale.shortWeekdays, 0) # build month offsets - yes, it assumes the Months and shortMonths # lists are in the same order and Jan..Dec _buildOffsets(self.locale.MonthOffsets, self.locale.Months, 1) _buildOffsets(self.locale.MonthOffsets, self.locale.shortMonths, 1) _initSymbols(self) # TODO: add code to parse the date formats and build the regexes up # from sub-parts, find all hard-coded uses of date/time separators # not being used in code, but kept in case others are manually # utilizing this regex for their own purposes self.RE_DATE4 = r'''(?P ( ( (?P\d\d?) (?P{daysuffix})? (,)? (\s)* ) (?P \b({months}|{shortmonths})\b )\s* (?P\d\d (\d\d)? )? ) )'''.format(**self.locale.re_values) # still not completely sure of the behavior of the regex and # whether it would be best to consume all possible irrelevant # characters before the option groups (but within the {1,3} repetition # group or inside of each option group, as it currently does # however, right now, all tests are passing that were, # including fixing the bug of matching a 4-digit year as ddyy # when the day is absent from the string self.RE_DATE3 = r'''(?P (?: (?:^|\s+) (?P {months}|{shortmonths} )\b | (?:^|\s+) (?P[1-9]|[012]\d|3[01]) (?P{daysuffix}|)\b (?!\s*(?:{timecomponents})) | ,?\s+ (?P\d\d(?:\d\d|))\b (?!\s*(?:{timecomponents})) ){{1,3}} (?(mthname)|$-^) )'''.format(**self.locale.re_values) # not being used in code, but kept in case others are manually # utilizing this regex for their own purposes self.RE_MONTH = r'''(\s+|^) (?P ( (?P \b({months}|{shortmonths})\b ) (\s* (?P(\d{{4}})) )? ) ) (?=\s+|$|[^\w])'''.format(**self.locale.re_values) self.RE_WEEKDAY = r'''\b (?: {days}|{shortdays} ) \b'''.format(**self.locale.re_values) self.RE_NUMBER = (r'(\b(?:{numbers})\b|\d+(?:{decimal_mark}\d+|))' .format(**self.locale.re_values)) self.RE_SPECIAL = (r'(?P^[{specials}]+)\s+' .format(**self.locale.re_values)) self.RE_UNITS_ONLY = (r'''\b({units})\b''' .format(**self.locale.re_values)) self.RE_UNITS = r'''\b(?P -? (?:\d+(?:{decimal_mark}\d+|)|(?:{numbers})\b)\s* (?P{units}) )\b'''.format(**self.locale.re_values) self.RE_QUNITS = r'''\b(?P -? (?:\d+(?:{decimal_mark}\d+|)|(?:{numbers})\s+)\s* (?P{qunits}) )\b'''.format(**self.locale.re_values) self.RE_MODIFIER = r'''\b(?: {modifiers} )\b'''.format(**self.locale.re_values) self.RE_TIMEHMS = r'''([\s(\["'-]|^) (?P\d\d?) (?P{timeseparator}|) (?P\d\d) (?:(?P=tsep) (?P\d\d (?:[\.,]\d+)? ) )?\b'''.format(**self.locale.re_values) self.RE_TIMEHMS2 = r'''([\s(\["'-]|^) (?P\d\d?) (?: (?P{timeseparator}|) (?P\d\d?) (?:(?P=tsep) (?P\d\d? (?:[\.,]\d+)? ) )? )?'''.format(**self.locale.re_values) # 1, 2, and 3 here refer to the type of match date, time, or units self.RE_NLP_PREFIX = r'''\b(?P (on) (\s)+1 | (at|in) (\s)+2 | (in) (\s)+3 )''' if 'meridian' in self.locale.re_values: self.RE_TIMEHMS2 += (r'\s*(?P{meridian})\b' .format(**self.locale.re_values)) else: self.RE_TIMEHMS2 += r'\b' # Always support common . and - separators dateSeps = ''.join(re.escape(s) for s in self.locale.dateSep + ['-', '.']) self.RE_DATE = r'''([\s(\["'-]|^) (?P \d\d?[{0}]\d\d?(?:[{0}]\d\d(?:\d\d)?)? | \d{{4}}[{0}]\d\d?[{0}]\d\d? ) \b'''.format(dateSeps) self.RE_DATE2 = r'[{0}]'.format(dateSeps) assert 'dayoffsets' in self.locale.re_values self.RE_DAY = r'''\b (?: {dayoffsets} ) \b'''.format(**self.locale.re_values) self.RE_DAY2 = r'''(?P\d\d?) (?P{daysuffix})? '''.format(**self.locale.re_values) self.RE_TIME = r'''\b (?: {sources} ) \b'''.format(**self.locale.re_values) self.RE_REMAINING = r'\s+' # Regex for date/time ranges self.RE_RTIMEHMS = r'''(\s*|^) (\d\d?){timeseparator} (\d\d) ({timeseparator}(\d\d))? (\s*|$)'''.format(**self.locale.re_values) self.RE_RTIMEHMS2 = (r'''(\s*|^) (\d\d?) ({timeseparator}(\d\d?))? ({timeseparator}(\d\d?))?''' .format(**self.locale.re_values)) if 'meridian' in self.locale.re_values: self.RE_RTIMEHMS2 += (r'\s*({meridian})' .format(**self.locale.re_values)) self.RE_RDATE = r'(\d+([%s]\d+)+)' % dateSeps self.RE_RDATE3 = r'''( ( ( \b({months})\b )\s* ( (\d\d?) (\s?|{daysuffix}|$)+ )? (,\s*\d{{4}})? ) )'''.format(**self.locale.re_values) # "06/07/06 - 08/09/06" self.DATERNG1 = (r'{0}\s*{rangeseparator}\s*{0}' .format(self.RE_RDATE, **self.locale.re_values)) # "march 31 - june 1st, 2006" self.DATERNG2 = (r'{0}\s*{rangeseparator}\s*{0}' .format(self.RE_RDATE3, **self.locale.re_values)) # "march 1rd -13th" self.DATERNG3 = (r'{0}\s*{rangeseparator}\s*(\d\d?)\s*(rd|st|nd|th)?' .format(self.RE_RDATE3, **self.locale.re_values)) # "4:00:55 pm - 5:90:44 am", '4p-5p' self.TIMERNG1 = (r'{0}\s*{rangeseparator}\s*{0}' .format(self.RE_RTIMEHMS2, **self.locale.re_values)) self.TIMERNG2 = (r'{0}\s*{rangeseparator}\s*{0}' .format(self.RE_RTIMEHMS, **self.locale.re_values)) # "4-5pm " self.TIMERNG3 = (r'\d\d?\s*{rangeseparator}\s*{0}' .format(self.RE_RTIMEHMS2, **self.locale.re_values)) # "4:30-5pm " self.TIMERNG4 = (r'{0}\s*{rangeseparator}\s*{1}' .format(self.RE_RTIMEHMS, self.RE_RTIMEHMS2, **self.locale.re_values)) self.re_option = re.IGNORECASE + re.VERBOSE self.cre_source = {'CRE_SPECIAL': self.RE_SPECIAL, 'CRE_NUMBER': self.RE_NUMBER, 'CRE_UNITS': self.RE_UNITS, 'CRE_UNITS_ONLY': self.RE_UNITS_ONLY, 'CRE_QUNITS': self.RE_QUNITS, 'CRE_MODIFIER': self.RE_MODIFIER, 'CRE_TIMEHMS': self.RE_TIMEHMS, 'CRE_TIMEHMS2': self.RE_TIMEHMS2, 'CRE_DATE': self.RE_DATE, 'CRE_DATE2': self.RE_DATE2, 'CRE_DATE3': self.RE_DATE3, 'CRE_DATE4': self.RE_DATE4, 'CRE_MONTH': self.RE_MONTH, 'CRE_WEEKDAY': self.RE_WEEKDAY, 'CRE_DAY': self.RE_DAY, 'CRE_DAY2': self.RE_DAY2, 'CRE_TIME': self.RE_TIME, 'CRE_REMAINING': self.RE_REMAINING, 'CRE_RTIMEHMS': self.RE_RTIMEHMS, 'CRE_RTIMEHMS2': self.RE_RTIMEHMS2, 'CRE_RDATE': self.RE_RDATE, 'CRE_RDATE3': self.RE_RDATE3, 'CRE_TIMERNG1': self.TIMERNG1, 'CRE_TIMERNG2': self.TIMERNG2, 'CRE_TIMERNG3': self.TIMERNG3, 'CRE_TIMERNG4': self.TIMERNG4, 'CRE_DATERNG1': self.DATERNG1, 'CRE_DATERNG2': self.DATERNG2, 'CRE_DATERNG3': self.DATERNG3, 'CRE_NLP_PREFIX': self.RE_NLP_PREFIX} self.cre_keys = set(self.cre_source.keys()) def __getattr__(self, name): if name in self.cre_keys: value = re.compile(self.cre_source[name], self.re_option) setattr(self, name, value) return value elif name in self.locale.locale_keys: return getattr(self.locale, name) else: raise AttributeError(name) def daysInMonth(self, month, year): """ Take the given month (1-12) and a given year (4 digit) return the number of days in the month adjusting for leap year as needed """ result = None debug and log.debug('daysInMonth(%s, %s)', month, year) if month > 0 and month <= 12: result = self._DaysInMonthList[month - 1] if month == 2: if year in self._leapYears: result += 1 else: if calendar.isleap(year): self._leapYears.append(year) result += 1 return result def getSource(self, sourceKey, sourceTime=None): """ GetReturn a date/time tuple based on the giving source key and the corresponding key found in self.re_sources. The current time is used as the default and any specified item found in self.re_sources is inserted into the value and the generated dictionary is returned. """ if sourceKey not in self.re_sources: return None if sourceTime is None: (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = time.localtime() else: (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = sourceTime defaults = {'yr': yr, 'mth': mth, 'dy': dy, 'hr': hr, 'mn': mn, 'sec': sec} source = self.re_sources[sourceKey] values = {} for key, default in defaults.items(): values[key] = source.get(key, default) return (values['yr'], values['mth'], values['dy'], values['hr'], values['mn'], values['sec'], wd, yd, isdst) parsedatetime-2.4/parsedatetime/context.py0000644000076500000240000001105412741420635021134 0ustar bearstaff00000000000000# -*- coding: utf-8 -*- """ parsedatetime/context.py Context related classes """ from threading import local class pdtContextStack(object): """ A thread-safe stack to store context(s) Internally used by L{Calendar} object """ def __init__(self): self.__local = local() @property def __stack(self): if not hasattr(self.__local, 'stack'): self.__local.stack = [] return self.__local.stack def push(self, ctx): self.__stack.append(ctx) def pop(self): try: return self.__stack.pop() except IndexError: return None def last(self): try: return self.__stack[-1] except IndexError: raise RuntimeError('context stack is empty') def isEmpty(self): return not self.__stack class pdtContext(object): """ Context contains accuracy flag detected by L{Calendar.parse()} Accuracy flag uses bitwise-OR operation and is combined by: ACU_YEAR - "next year", "2014" ACU_MONTH - "March", "July 2014" ACU_WEEK - "last week", "next 3 weeks" ACU_DAY - "tomorrow", "July 4th 2014" ACU_HALFDAY - "morning", "tonight" ACU_HOUR - "18:00", "next hour" ACU_MIN - "18:32", "next 10 minutes" ACU_SEC - "18:32:55" ACU_NOW - "now" """ __slots__ = ('accuracy',) ACU_YEAR = 2 ** 0 ACU_MONTH = 2 ** 1 ACU_WEEK = 2 ** 2 ACU_DAY = 2 ** 3 ACU_HALFDAY = 2 ** 4 ACU_HOUR = 2 ** 5 ACU_MIN = 2 ** 6 ACU_SEC = 2 ** 7 ACU_NOW = 2 ** 8 ACU_DATE = ACU_YEAR | ACU_MONTH | ACU_WEEK | ACU_DAY ACU_TIME = ACU_HALFDAY | ACU_HOUR | ACU_MIN | ACU_SEC | ACU_NOW _ACCURACY_MAPPING = [ (ACU_YEAR, 'year'), (ACU_MONTH, 'month'), (ACU_WEEK, 'week'), (ACU_DAY, 'day'), (ACU_HALFDAY, 'halfday'), (ACU_HOUR, 'hour'), (ACU_MIN, 'min'), (ACU_SEC, 'sec'), (ACU_NOW, 'now')] _ACCURACY_REVERSE_MAPPING = { 'year': ACU_YEAR, 'years': ACU_YEAR, 'month': ACU_MONTH, 'months': ACU_MONTH, 'week': ACU_WEEK, 'weeks': ACU_WEEK, 'day': ACU_DAY, 'days': ACU_DAY, 'halfday': ACU_HALFDAY, 'morning': ACU_HALFDAY, 'afternoon': ACU_HALFDAY, 'evening': ACU_HALFDAY, 'night': ACU_HALFDAY, 'tonight': ACU_HALFDAY, 'midnight': ACU_HALFDAY, 'hour': ACU_HOUR, 'hours': ACU_HOUR, 'min': ACU_MIN, 'minute': ACU_MIN, 'mins': ACU_MIN, 'minutes': ACU_MIN, 'sec': ACU_SEC, 'second': ACU_SEC, 'secs': ACU_SEC, 'seconds': ACU_SEC, 'now': ACU_NOW} def __init__(self, accuracy=0): """ Default constructor of L{pdtContext} class. @type accuracy: integer @param accuracy: Accuracy flag @rtype: object @return: L{pdtContext} instance """ self.accuracy = accuracy def updateAccuracy(self, *accuracy): """ Updates current accuracy flag """ for acc in accuracy: if not isinstance(acc, int): acc = self._ACCURACY_REVERSE_MAPPING[acc] self.accuracy |= acc def update(self, context): """ Uses another L{pdtContext} instance to update current one """ self.updateAccuracy(context.accuracy) @property def hasDate(self): """ Returns True if current context is accurate to date """ return bool(self.accuracy & self.ACU_DATE) @property def hasTime(self): """ Returns True if current context is accurate to time """ return bool(self.accuracy & self.ACU_TIME) @property def dateTimeFlag(self): """ Returns the old date/time flag code """ return int(self.hasDate and 1) | int(self.hasTime and 2) @property def hasDateOrTime(self): """ Returns True if current context is accurate to date/time """ return bool(self.accuracy) def __repr__(self): accuracy_repr = [] for acc, name in self._ACCURACY_MAPPING: if acc & self.accuracy: accuracy_repr.append('pdtContext.ACU_%s' % name.upper()) if accuracy_repr: accuracy_repr = 'accuracy=' + ' | '.join(accuracy_repr) else: accuracy_repr = '' return 'pdtContext(%s)' % accuracy_repr def __eq__(self, ctx): return self.accuracy == ctx.accuracy parsedatetime-2.4/parsedatetime/parsedatetime.py0000644000076500000240000000007512741420635022300 0ustar bearstaff00000000000000# Backward compatibility fix. from . import * # noqa parsedatetime-2.4/parsedatetime/pdt_locales/0000755000076500000240000000000013106001723021353 5ustar bearstaff00000000000000parsedatetime-2.4/parsedatetime/pdt_locales/__init__.py0000644000076500000240000000131713012207661023473 0ustar bearstaff00000000000000# -*- encoding: utf-8 -*- """ pdt_locales All of the included locale classes shipped with pdt. """ from __future__ import absolute_import from .icu import get_icu locales = ['de_DE', 'en_AU', 'en_US', 'es', 'nl_NL', 'pt_BR', 'ru_RU', 'fr_FR'] __locale_caches = {} __all__ = ['get_icu', 'load_locale'] def load_locale(locale, icu=False): """ Return data of locale :param locale: :return: """ if locale not in locales: raise NotImplementedError("The locale '%s' is not supported" % locale) if locale not in __locale_caches: mod = __import__(__name__, fromlist=[locale], level=0) __locale_caches[locale] = getattr(mod, locale) return __locale_caches[locale] parsedatetime-2.4/parsedatetime/pdt_locales/base.py0000644000076500000240000001076313012207661022653 0ustar bearstaff00000000000000from __future__ import unicode_literals locale_keys = set([ 'MonthOffsets', 'Months', 'WeekdayOffsets', 'Weekdays', 'dateFormats', 'dateSep', 'dayOffsets', 'dp_order', 'localeID', 'meridian', 'Modifiers', 're_sources', 're_values', 'shortMonths', 'shortWeekdays', 'timeFormats', 'timeSep', 'units', 'uses24', 'usesMeridian', 'numbers', 'decimal_mark', 'small', 'magnitude', 'ignore']) localeID = None dateSep = ['/', '.'] timeSep = [':'] meridian = ['AM', 'PM'] usesMeridian = True uses24 = True WeekdayOffsets = {} MonthOffsets = {} # always lowercase any lookup values - helper code expects that Weekdays = [ 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday', ] shortWeekdays = [ 'mon', 'tues|tue', 'wed', 'thu', 'fri', 'sat', 'sun', ] Months = [ 'january', 'february', 'march', 'april', 'may', 'june', 'july', 'august', 'september', 'october', 'november', 'december', ] shortMonths = [ 'jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec', ] # use the same formats as ICU by default dateFormats = { 'full': 'EEEE, MMMM d, yyyy', 'long': 'MMMM d, yyyy', 'medium': 'MMM d, yyyy', 'short': 'M/d/yy' } timeFormats = { 'full': 'h:mm:ss a z', 'long': 'h:mm:ss a z', 'medium': 'h:mm:ss a', 'short': 'h:mm a', } dp_order = ['m', 'd', 'y'] # Used to parse expressions like "in 5 hours" numbers = { 'zero': 0, 'one': 1, 'a': 1, 'an': 1, 'two': 2, 'three': 3, 'four': 4, 'five': 5, 'six': 6, 'seven': 7, 'eight': 8, 'nine': 9, 'ten': 10, 'eleven': 11, 'thirteen': 13, 'fourteen': 14, 'fifteen': 15, 'sixteen': 16, 'seventeen': 17, 'eighteen': 18, 'nineteen': 19, 'twenty': 20, } decimal_mark = '.' # this will be added to re_values later units = { 'seconds': ['second', 'seconds', 'sec', 's'], 'minutes': ['minute', 'minutes', 'min', 'm'], 'hours': ['hour', 'hours', 'hr', 'h'], 'days': ['day', 'days', 'dy', 'd'], 'weeks': ['week', 'weeks', 'wk', 'w'], 'months': ['month', 'months', 'mth'], 'years': ['year', 'years', 'yr', 'y'], } # text constants to be used by later regular expressions re_values = { 'specials': 'in|on|of|at', 'timeseparator': ':', 'rangeseparator': '-', 'daysuffix': 'rd|st|nd|th', 'meridian': r'am|pm|a\.m\.|p\.m\.|a|p', 'qunits': 'h|m|s|d|w|y', 'now': ['now', 'right now'], } # Used to adjust the returned date before/after the source Modifiers = { 'from': 1, 'before': -1, 'after': 1, 'ago': -1, 'prior': -1, 'prev': -1, 'last': -1, 'next': 1, 'previous': -1, 'end of': 0, 'this': 0, 'eod': 1, 'eom': 1, 'eoy': 1, } dayOffsets = { 'tomorrow': 1, 'today': 0, 'yesterday': -1, } # special day and/or times, i.e. lunch, noon, evening # each element in the dictionary is a dictionary that is used # to fill in any value to be replace - the current date/time will # already have been populated by the method buildSources re_sources = { 'noon': {'hr': 12, 'mn': 0, 'sec': 0}, 'afternoon': {'hr': 13, 'mn': 0, 'sec': 0}, 'lunch': {'hr': 12, 'mn': 0, 'sec': 0}, 'morning': {'hr': 6, 'mn': 0, 'sec': 0}, 'breakfast': {'hr': 8, 'mn': 0, 'sec': 0}, 'dinner': {'hr': 19, 'mn': 0, 'sec': 0}, 'evening': {'hr': 18, 'mn': 0, 'sec': 0}, 'midnight': {'hr': 0, 'mn': 0, 'sec': 0}, 'night': {'hr': 21, 'mn': 0, 'sec': 0}, 'tonight': {'hr': 21, 'mn': 0, 'sec': 0}, 'eod': {'hr': 17, 'mn': 0, 'sec': 0}, } small = { 'zero': 0, 'one': 1, 'a': 1, 'an': 1, 'two': 2, 'three': 3, 'four': 4, 'five': 5, 'six': 6, 'seven': 7, 'eight': 8, 'nine': 9, 'ten': 10, 'eleven': 11, 'twelve': 12, 'thirteen': 13, 'fourteen': 14, 'fifteen': 15, 'sixteen': 16, 'seventeen': 17, 'eighteen': 18, 'nineteen': 19, 'twenty': 20, 'thirty': 30, 'forty': 40, 'fifty': 50, 'sixty': 60, 'seventy': 70, 'eighty': 80, 'ninety': 90 } magnitude = { 'thousand': 1000, 'million': 1000000, 'billion': 1000000000, 'trillion': 1000000000000, 'quadrillion': 1000000000000000, 'quintillion': 1000000000000000000, 'sextillion': 1000000000000000000000, 'septillion': 1000000000000000000000000, 'octillion': 1000000000000000000000000000, 'nonillion': 1000000000000000000000000000000, 'decillion': 1000000000000000000000000000000000, } ignore = ('and', ',') parsedatetime-2.4/parsedatetime/pdt_locales/de_DE.py0000644000076500000240000000607212611312320022670 0ustar bearstaff00000000000000# -*- coding: utf-8 -*- from __future__ import unicode_literals from .base import * # noqa # don't use an unicode string localeID = 'de_DE' dateSep = ['.'] timeSep = [':'] meridian = [] usesMeridian = False uses24 = True decimal_mark = ',' Weekdays = [ 'montag', 'dienstag', 'mittwoch', 'donnerstag', 'freitag', 'samstag', 'sonntag', ] shortWeekdays = ['mo', 'di', 'mi', 'do', 'fr', 'sa', 'so'] Months = [ 'januar', 'februar', 'märz', 'april', 'mai', 'juni', 'juli', 'august', 'september', 'oktober', 'november', 'dezember', ] shortMonths = [ 'jan', 'feb', 'mrz', 'apr', 'mai', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'dez', ] dateFormats = { 'full': 'EEEE, d. MMMM yyyy', 'long': 'd. MMMM yyyy', 'medium': 'dd.MM.yyyy', 'short': 'dd.MM.yy', } timeFormats = { 'full': 'HH:mm:ss v', 'long': 'HH:mm:ss z', 'medium': 'HH:mm:ss', 'short': 'HH:mm', } dp_order = ['d', 'm', 'y'] # the short version would be a capital M, # as I understand it we can't distinguish # between m for minutes and M for months. units = { 'seconds': ['sekunden', 'sek', 's'], 'minutes': ['minuten', 'min', 'm'], 'hours': ['stunden', 'std', 'h'], 'days': ['tag', 'tage', 't'], 'weeks': ['wochen', 'w'], 'months': ['monat', 'monate'], 'years': ['jahr', 'jahre', 'j'], } re_values = re_values.copy() re_values.update({ 'specials': 'am|dem|der|im|in|den|zum', 'timeseparator': ':', 'rangeseparator': '-', 'daysuffix': '', 'qunits': 'h|m|s|t|w|m|j', 'now': ['jetzt'], }) # Used to adjust the returned date before/after the source # still looking for insight on how to translate all of them to german. Modifiers = { 'from': 1, 'before': -1, 'after': 1, 'vergangener': -1, 'vorheriger': -1, 'prev': -1, 'letzter': -1, 'nächster': 1, 'dieser': 0, 'previous': -1, 'in a': 2, 'end of': 0, 'eod': 0, 'eo': 0, } # morgen/abermorgen does not work, see # http://code.google.com/p/parsedatetime/issues/detail?id=19 dayOffsets = { 'morgen': 1, 'heute': 0, 'gestern': -1, 'vorgestern': -2, 'übermorgen': 2, } # special day and/or times, i.e. lunch, noon, evening # each element in the dictionary is a dictionary that is used # to fill in any value to be replace - the current date/time will # already have been populated by the method buildSources re_sources = { 'mittag': {'hr': 12, 'mn': 0, 'sec': 0}, 'mittags': {'hr': 12, 'mn': 0, 'sec': 0}, 'mittagessen': {'hr': 12, 'mn': 0, 'sec': 0}, 'morgen': {'hr': 6, 'mn': 0, 'sec': 0}, 'morgens': {'hr': 6, 'mn': 0, 'sec': 0}, 'frühstück': {'hr': 8, 'mn': 0, 'sec': 0}, 'abendessen': {'hr': 19, 'mn': 0, 'sec': 0}, 'abend': {'hr': 18, 'mn': 0, 'sec': 0}, 'abends': {'hr': 18, 'mn': 0, 'sec': 0}, 'mitternacht': {'hr': 0, 'mn': 0, 'sec': 0}, 'nacht': {'hr': 21, 'mn': 0, 'sec': 0}, 'nachts': {'hr': 21, 'mn': 0, 'sec': 0}, 'heute abend': {'hr': 21, 'mn': 0, 'sec': 0}, 'heute nacht': {'hr': 21, 'mn': 0, 'sec': 0}, 'feierabend': {'hr': 17, 'mn': 0, 'sec': 0}, } parsedatetime-2.4/parsedatetime/pdt_locales/en_AU.py0000644000076500000240000000057413012207661022727 0ustar bearstaff00000000000000# -*- coding: utf-8 -*- from __future__ import unicode_literals from .base import * # noqa # don't use an unicode string localeID = 'en_AU' dateSep = ['-', '/'] uses24 = False dateFormats = { 'full': 'EEEE, d MMMM yyyy', 'long': 'd MMMM yyyy', 'medium': 'dd/MM/yyyy', 'short': 'd/MM/yy', } timeFormats['long'] = timeFormats['full'] dp_order = ['d', 'm', 'y'] parsedatetime-2.4/parsedatetime/pdt_locales/en_US.py0000644000076500000240000000023512611312320022734 0ustar bearstaff00000000000000# -*- coding: utf-8 -*- from __future__ import unicode_literals from .base import * # noqa # don't use an unicode string localeID = 'en_US' uses24 = False parsedatetime-2.4/parsedatetime/pdt_locales/es.py0000644000076500000240000000167712611312320022345 0ustar bearstaff00000000000000# -*- coding: utf-8 -*- from __future__ import unicode_literals from .base import * # noqa # don't use an unicode string localeID = 'es' dateSep = ['/'] usesMeridian = False uses24 = True decimal_mark = ',' Weekdays = [ 'lunes', 'martes', 'miércoles', 'jueves', 'viernes', 'sábado', 'domingo', ] shortWeekdays = [ 'lun', 'mar', 'mié', 'jue', 'vie', 'sáb', 'dom', ] Months = [ 'enero', 'febrero', 'marzo', 'abril', 'mayo', 'junio', 'julio', 'agosto', 'septiembre', 'octubre', 'noviembre', 'diciembre', ] shortMonths = [ 'ene', 'feb', 'mar', 'abr', 'may', 'jun', 'jul', 'ago', 'sep', 'oct', 'nov', 'dic', ] dateFormats = { 'full': "EEEE d' de 'MMMM' de 'yyyy", 'long': "d' de 'MMMM' de 'yyyy", 'medium': "dd-MMM-yy", 'short': "d/MM/yy", } timeFormats = { 'full': "HH'H'mm' 'ss z", 'long': "HH:mm:ss z", 'medium': "HH:mm:ss", 'short': "HH:mm", } dp_order = ['d', 'm', 'y'] parsedatetime-2.4/parsedatetime/pdt_locales/fr_FR.py0000644000076500000240000001367613012207661022745 0ustar bearstaff00000000000000# -*- coding: utf-8 -*- from __future__ import unicode_literals from .base import * # noqa # don't use an unicode string localeID = 'fr_FR' dateSep = ['\/'] timeSep = [':', 'h'] meridian = ['du matin', 'du soir'] usesMeridian = True uses24 = True WeekdayOffsets = {} MonthOffsets = {} # always lowercase any lookup values - helper code expects that Weekdays = [ 'lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi', 'samedi', 'dimanche', ] shortWeekdays = [ 'lun', 'mar', 'mer', 'jeu', 'ven', 'sam', 'dim', ] Months = [ 'janvier', 'février|fevrier', 'mars', 'avril', 'mai', 'juin', 'juillet', 'août|aout', 'septembre', 'octobre', 'novembre', 'décembre|decembre', ] # We do not list 'mar' as a short name for 'mars' as it conflicts with # the 'mar' of 'mardi' shortMonths = [ 'jan', 'fév|fev', 'mars', 'avr', 'mai', 'jui', 'juil', 'aoû|aou', 'sep', 'oct', 'nov', 'déc|dec', ] # use the same formats as ICU by default dateFormats = { 'full': 'EEEE d MMMM yyyy', 'long': 'd MMMM yyyy', 'medium': 'd MMM yyyy', 'short': 'd/M/yy' } timeFormats = { 'full': 'h:mm:ss a z', 'long': 'h:mm:ss a z', 'medium': 'h:mm:ss a', 'short': 'h:mm a', } dp_order = ['d', 'm', 'y'] # Used to parse expressions like "in 5 hours" numbers = { 'zéro': 0, 'zero': 0, 'un': 1, 'une': 1, 'deux': 2, 'trois': 3, 'quatre': 4, 'cinq': 5, 'six': 6, 'sept': 7, 'huit': 8, 'neuf': 9, 'dix': 10, 'onze': 11, 'douze': 12, 'treize': 13, 'quatorze': 14, 'quinze': 15, 'seize': 16, 'dix-sept': 17, 'dix sept': 17, 'dix-huit': 18, 'dix huit': 18, 'dix-neuf': 19, 'dix neuf': 19, 'vingt': 20, 'vingt-et-un': 21, 'vingt et un': 21, 'vingt-deux': 22, 'vingt deux': 22, 'vingt-trois': 23, 'vingt trois': 23, 'vingt-quatre': 24, 'vingt quatre': 24, } decimal_mark = ',' # this will be added to re_values later units = { 'seconds': ['seconde', 'secondes', 'sec', 's'], 'minutes': ['minute', 'minutes', 'min', 'mn'], 'hours': ['heure', 'heures', 'h'], 'days': ['jour', 'jours', 'journée', 'journee', 'journées', 'journees', 'j'], 'weeks': ['semaine', 'semaines', 'sem'], 'months': ['mois', 'm'], 'years': ['année', 'annee', 'an', 'années', 'annees', 'ans'], } # text constants to be used by later regular expressions re_values = { 'specials': 'à|a|le|la|du|de', 'timeseparator': '(?:\:|h|\s*heures?\s*)', 'rangeseparator': '-', 'daysuffix': 'ième|ieme|ème|eme|ère|ere|nde', 'meridian': None, 'qunits': 'h|m|s|j|sem|a', 'now': ['maintenant', 'tout de suite', 'immédiatement', 'immediatement', 'à l\'instant', 'a l\'instant'], } # Used to adjust the returned date before/after the source Modifiers = { 'avant': -1, 'il y a': -1, 'plus tot': -1, 'plus tôt': -1, 'y a': -1, 'antérieur': -1, 'anterieur': -1, 'dernier': -1, 'dernière': -1, 'derniere': -1, 'précédent': -1, 'précedent': -1, 'precédent': -1, 'precedent': -1, 'fin de': 0, 'fin du': 0, 'fin de la': 0, 'fin des': 0, 'fin d\'': 0, 'ce': 0, 'cette': 0, 'depuis': 1, 'dans': 1, 'à partir': 1, 'a partir': 1, 'après': 1, 'apres': 1, 'lendemain': 1, 'prochain': 1, 'prochaine': 1, 'suivant': 1, 'suivante': 1, 'plus tard': 1 } dayOffsets = { 'après-demain': 2, 'apres-demain': 2, 'après demain': 2, 'apres demain': 2, 'demain': 1, 'aujourd\'hui': 0, 'hier': -1, 'avant-hier': -2, 'avant hier': -2 } # special day and/or times, i.e. lunch, noon, evening # each element in the dictionary is a dictionary that is used # to fill in any value to be replace - the current date/time will # already have been populated by the method buildSources re_sources = { 'après-midi': {'hr': 13, 'mn': 0, 'sec': 0}, 'apres-midi': {'hr': 13, 'mn': 0, 'sec': 0}, 'après midi': {'hr': 13, 'mn': 0, 'sec': 0}, 'apres midi': {'hr': 13, 'mn': 0, 'sec': 0}, 'midi': {'hr': 12, 'mn': 0, 'sec': 0}, 'déjeuner': {'hr': 12, 'mn': 0, 'sec': 0}, 'dejeuner': {'hr': 12, 'mn': 0, 'sec': 0}, 'matin': {'hr': 6, 'mn': 0, 'sec': 0}, 'petit-déjeuner': {'hr': 8, 'mn': 0, 'sec': 0}, 'petit-dejeuner': {'hr': 8, 'mn': 0, 'sec': 0}, 'petit déjeuner': {'hr': 8, 'mn': 0, 'sec': 0}, 'petit dejeuner': {'hr': 8, 'mn': 0, 'sec': 0}, 'diner': {'hr': 19, 'mn': 0, 'sec': 0}, 'dîner': {'hr': 19, 'mn': 0, 'sec': 0}, 'soir': {'hr': 18, 'mn': 0, 'sec': 0}, 'soirée': {'hr': 18, 'mn': 0, 'sec': 0}, 'soiree': {'hr': 18, 'mn': 0, 'sec': 0}, 'minuit': {'hr': 0, 'mn': 0, 'sec': 0}, 'nuit': {'hr': 21, 'mn': 0, 'sec': 0}, } small = { 'zéro': 0, 'zero': 0, 'un': 1, 'une': 1, 'deux': 2, 'trois': 3, 'quatre': 4, 'cinq': 5, 'six': 6, 'sept': 7, 'huit': 8, 'neuf': 9, 'dix': 10, 'onze': 11, 'douze': 12, 'treize': 13, 'quatorze': 14, 'quinze': 15, 'seize': 16, 'dix-sept': 17, 'dix sept': 17, 'dix-huit': 18, 'dix huit': 18, 'dix-neuf': 19, 'dix neuf': 19, 'vingt': 20, 'vingt-et-un': 21, 'vingt et un': 21, 'trente': 30, 'quarante': 40, 'cinquante': 50, 'soixante': 60, 'soixante-dix': 70, 'soixante dix': 70, 'quatre-vingt': 80, 'quatre vingt': 80, 'quatre-vingt-dix': 90, 'quatre vingt dix': 90 } magnitude = { 'mille': 1000, 'millier': 1000, 'million': 1000000, 'milliard': 1000000000, 'trillion': 1000000000000, 'quadrillion': 1000000000000000, 'quintillion': 1000000000000000000, 'sextillion': 1000000000000000000000, 'septillion': 1000000000000000000000000, 'octillion': 1000000000000000000000000000, 'nonillion': 1000000000000000000000000000000, 'décillion': 1000000000000000000000000000000000, 'decillion': 1000000000000000000000000000000000, } ignore = ('et', ',') parsedatetime-2.4/parsedatetime/pdt_locales/icu.py0000644000076500000240000001037313105773676022537 0ustar bearstaff00000000000000# -*- encoding: utf-8 -*- """ pdt_locales All of the included locale classes shipped with pdt. """ import datetime try: range = xrange except NameError: pass try: import PyICU as pyicu except ImportError: pyicu = None def icu_object(mapping): return type('_icu', (object,), mapping) def merge_weekdays(base_wd, icu_wd): result = [] for left, right in zip(base_wd, icu_wd): if left == right: result.append(left) continue left = set(left.split('|')) right = set(right.split('|')) result.append('|'.join(left | right)) return result def get_icu(locale): from . import base result = dict([(key, getattr(base, key)) for key in dir(base) if not key.startswith('_')]) result['icu'] = None if pyicu is None: return icu_object(result) if locale is None: locale = 'en_US' result['icu'] = icu = pyicu.Locale(locale) if icu is None: return icu_object(result) # grab spelled out format of all numbers from 0 to 100 rbnf = pyicu.RuleBasedNumberFormat(pyicu.URBNFRuleSetTag.SPELLOUT, icu) result['numbers'].update([(rbnf.format(i), i) for i in range(0, 100)]) symbols = result['symbols'] = pyicu.DateFormatSymbols(icu) # grab ICU list of weekdays, skipping first entry which # is always blank wd = [w.lower() for w in symbols.getWeekdays()[1:]] swd = [sw.lower() for sw in symbols.getShortWeekdays()[1:]] # store them in our list with Monday first (ICU puts Sunday first) result['Weekdays'] = merge_weekdays(result['Weekdays'], wd[1:] + wd[0:1]) result['shortWeekdays'] = merge_weekdays(result['shortWeekdays'], swd[1:] + swd[0:1]) result['Months'] = [m.lower() for m in symbols.getMonths()] result['shortMonths'] = [sm.lower() for sm in symbols.getShortMonths()] keys = ['full', 'long', 'medium', 'short'] createDateInstance = pyicu.DateFormat.createDateInstance createTimeInstance = pyicu.DateFormat.createTimeInstance icu_df = result['icu_df'] = { 'full': createDateInstance(pyicu.DateFormat.kFull, icu), 'long': createDateInstance(pyicu.DateFormat.kLong, icu), 'medium': createDateInstance(pyicu.DateFormat.kMedium, icu), 'short': createDateInstance(pyicu.DateFormat.kShort, icu), } icu_tf = result['icu_tf'] = { 'full': createTimeInstance(pyicu.DateFormat.kFull, icu), 'long': createTimeInstance(pyicu.DateFormat.kLong, icu), 'medium': createTimeInstance(pyicu.DateFormat.kMedium, icu), 'short': createTimeInstance(pyicu.DateFormat.kShort, icu), } result['dateFormats'] = {} result['timeFormats'] = {} for x in keys: result['dateFormats'][x] = icu_df[x].toPattern() result['timeFormats'][x] = icu_tf[x].toPattern() am = pm = ts = '' # ICU doesn't seem to provide directly the date or time separator # so we have to figure it out o = result['icu_tf']['short'] s = result['timeFormats']['short'] result['usesMeridian'] = 'a' in s result['uses24'] = 'H' in s # '11:45 AM' or '11:45' s = o.format(datetime.datetime(2003, 10, 30, 11, 45)) # ': AM' or ':' s = s.replace('11', '').replace('45', '') if len(s) > 0: ts = s[0] if result['usesMeridian']: # '23:45 AM' or '23:45' am = s[1:].strip() s = o.format(datetime.datetime(2003, 10, 30, 23, 45)) if result['uses24']: s = s.replace('23', '') else: s = s.replace('11', '') # 'PM' or '' pm = s.replace('45', '').replace(ts, '').strip() result['timeSep'] = [ts] result['meridian'] = [am, pm] if am and pm else [] o = result['icu_df']['short'] s = o.format(datetime.datetime(2003, 10, 30, 11, 45)) s = s.replace('10', '').replace('30', '').replace( '03', '').replace('2003', '') if len(s) > 0: ds = s[0] else: ds = '/' result['dateSep'] = [ds] s = result['dateFormats']['short'] l = s.lower().split(ds) dp_order = [] for s in l: if len(s) > 0: dp_order.append(s[:1]) result['dp_order'] = dp_order return icu_object(result) parsedatetime-2.4/parsedatetime/pdt_locales/nl_NL.py0000644000076500000240000000571012611312320022730 0ustar bearstaff00000000000000# -*- coding: utf-8 -*- from __future__ import unicode_literals from .base import * # noqa # don't use an unicode string localeID = 'nl_NL' dateSep = ['-', '/'] timeSep = [':'] meridian = [] usesMeridian = False uses24 = True decimal_mark = ',' Weekdays = [ 'maandag', 'dinsdag', 'woensdag', 'donderdag', 'vrijdag', 'zaterdag', 'zondag', ] shortWeekdays = [ 'ma', 'di', 'wo', 'do', 'vr', 'za', 'zo', ] Months = [ 'januari', 'februari', 'maart', 'april', 'mei', 'juni', 'juli', 'augustus', 'september', 'oktober', 'november', 'december', ] shortMonths = [ 'jan', 'feb', 'mar', 'apr', 'mei', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'dec', ] dateFormats = { 'full': 'EEEE, dd MMMM yyyy', 'long': 'dd MMMM yyyy', 'medium': 'dd-MM-yyyy', 'short': 'dd-MM-yy', } timeFormats = { 'full': 'HH:mm:ss v', 'long': 'HH:mm:ss z', 'medium': 'HH:mm:ss', 'short': 'HH:mm', } dp_order = ['d', 'm', 'y'] # the short version would be a capital M, # as I understand it we can't distinguish # between m for minutes and M for months. units = { 'seconds': ['secunden', 'sec', 's'], 'minutes': ['minuten', 'min', 'm'], 'hours': ['uren', 'uur', 'h'], 'days': ['dagen', 'dag', 'd'], 'weeks': ['weken', 'w'], 'months': ['maanden', 'maand'], 'years': ['jaar', 'jaren', 'j'], } re_values = re_values.copy() re_values.update({ 'specials': 'om', 'timeseparator': ':', 'rangeseparator': '-', 'daysuffix': ' |de', 'qunits': 'h|m|s|d|w|m|j', 'now': ['nu'], }) # Used to adjust the returned date before/after the source # still looking for insight on how to translate all of them to german. Modifiers = { 'vanaf': 1, 'voor': -1, 'na': 1, 'vorige': -1, 'eervorige': -1, 'prev': -1, 'laastste': -1, 'volgende': 1, 'deze': 0, 'vorige': -1, 'over': 2, 'eind van': 0, } # morgen/abermorgen does not work, see # http://code.google.com/p/parsedatetime/issues/detail?id=19 dayOffsets = { 'morgen': 1, 'vandaag': 0, 'gisteren': -1, 'eergisteren': -2, 'overmorgen': 2, } # special day and/or times, i.e. lunch, noon, evening # each element in the dictionary is a dictionary that is used # to fill in any value to be replace - the current date/time will # already have been populated by the method buildSources re_sources = { 'middag': {'hr': 12, 'mn': 0, 'sec': 0}, 'vanmiddag': {'hr': 12, 'mn': 0, 'sec': 0}, 'lunch': {'hr': 12, 'mn': 0, 'sec': 0}, 'morgen': {'hr': 6, 'mn': 0, 'sec': 0}, "'s morgens": {'hr': 6, 'mn': 0, 'sec': 0}, 'ontbijt': {'hr': 8, 'mn': 0, 'sec': 0}, 'avondeten': {'hr': 19, 'mn': 0, 'sec': 0}, 'avond': {'hr': 18, 'mn': 0, 'sec': 0}, 'avonds': {'hr': 18, 'mn': 0, 'sec': 0}, 'middernacht': {'hr': 0, 'mn': 0, 'sec': 0}, 'nacht': {'hr': 21, 'mn': 0, 'sec': 0}, 'nachts': {'hr': 21, 'mn': 0, 'sec': 0}, 'vanavond': {'hr': 21, 'mn': 0, 'sec': 0}, 'vannacht': {'hr': 21, 'mn': 0, 'sec': 0}, } parsedatetime-2.4/parsedatetime/pdt_locales/pt_BR.py0000644000076500000240000000215012611312320022727 0ustar bearstaff00000000000000# -*- coding: utf-8 -*- from __future__ import unicode_literals from .base import * # noqa # don't use an unicode string localeID = 'pt_BR' dateSep = ['/'] usesMeridian = False uses24 = True decimal_mark = ',' Weekdays = [ 'segunda-feira', 'terça-feira', 'quarta-feira', 'quinta-feira', 'sexta-feira', 'sábado', 'domingo', ] shortWeekdays = [ 'seg', 'ter', 'qua', 'qui', 'sex', 'sáb', 'dom', ] Months = [ 'janeiro', 'fevereiro', 'março', 'abril', 'maio', 'junho', 'julho', 'agosto', 'setembro', 'outubro', 'novembro', 'dezembro' ] shortMonths = [ 'jan', 'fev', 'mar', 'abr', 'mai', 'jun', 'jul', 'ago', 'set', 'out', 'nov', 'dez' ] dateFormats = { 'full': "EEEE, d' de 'MMMM' de 'yyyy", 'long': "d' de 'MMMM' de 'yyyy", 'medium': "dd-MM-yy", 'short': "dd/MM/yyyy", } timeFormats = { 'full': "HH'H'mm' 'ss z", 'long': "HH:mm:ss z", 'medium': "HH:mm:ss", 'short': "HH:mm", } dp_order = ['d', 'm', 'y'] units = { 'seconds': ['segundo', 'seg', 's'], 'minutes': ['minuto', 'min', 'm'], 'days': ['dia', 'dias', 'd'], 'months': ['mês', 'meses'], } parsedatetime-2.4/parsedatetime/pdt_locales/ru_RU.py0000644000076500000240000001074112611312320022762 0ustar bearstaff00000000000000# -*- coding: utf-8 -*- from __future__ import unicode_literals from .base import * # noqa # don't use an unicode string localeID = 'ru_RU' dateSep = ['-', '.'] timeSep = [':'] meridian = [] usesMeridian = False uses24 = True Weekdays = [ 'понедельник', 'вторник', 'среда', 'четверг', 'пятница', 'суббота', 'воскресенье', ] shortWeekdays = [ 'пн', 'вт', 'ср', 'чт', 'пт', 'сб', 'вс', ] # library does not know how to conjugate words # библиотека не умеет спрягать слова Months = [ 'января', 'февраля', 'марта', 'апреля', 'мая', 'июня', 'июля', 'августа', 'сентября', 'октября', 'ноября', 'декабря', ] shortMonths = [ 'явн', 'фев', 'мрт', 'апр', 'май', 'июн', 'июл', 'авг', 'сен', 'окт', 'нбр', 'дек', ] dateFormats = { 'full': 'EEEE, dd MMMM yyyy', 'long': 'dd MMMM yyyy', 'medium': 'dd-MM-yyyy', 'short': 'dd-MM-yy', } timeFormats = { 'full': 'HH:mm:ss v', 'long': 'HH:mm:ss z', 'medium': 'HH:mm:ss', 'short': 'HH:mm', } dp_order = ['d', 'm', 'y'] decimal_mark = '.' units = { 'seconds': ['секунда', 'секунды', 'секунд', 'сек', 'с'], 'minutes': ['минута', 'минуты', 'минут', 'мин', 'м'], 'hours': ['час', 'часов', 'часа', 'ч'], 'days': ['день', 'дней', 'д'], 'weeks': ['неделя', 'недели', 'н'], 'months': ['месяц', 'месяца', 'мес'], 'years': ['год', 'года', 'годы', 'г'], } re_values = re_values.copy() re_values.update({ 'specials': 'om', 'timeseparator': ':', 'rangeseparator': '-', 'daysuffix': 'ого|ой|ий|тье', 'qunits': 'д|мес|г|ч|н|м|с', 'now': ['сейчас'], }) Modifiers = { 'после': 1, 'назад': -1, 'предыдущий': -1, 'последний': -1, 'далее': 1, 'ранее': -1, } dayOffsets = { 'завтра': 1, 'сегодня': 0, 'вчера': -1, 'позавчера': -2, 'послезавтра': 2, } re_sources = { 'полдень': {'hr': 12, 'mn': 0, 'sec': 0}, 'день': {'hr': 13, 'mn': 0, 'sec': 0}, 'обед': {'hr': 12, 'mn': 0, 'sec': 0}, 'утро': {'hr': 6, 'mn': 0, 'sec': 0}, 'завтрак': {'hr': 8, 'mn': 0, 'sec': 0}, 'ужин': {'hr': 19, 'mn': 0, 'sec': 0}, 'вечер': {'hr': 18, 'mn': 0, 'sec': 0}, 'полночь': {'hr': 0, 'mn': 0, 'sec': 0}, 'ночь': {'hr': 21, 'mn': 0, 'sec': 0}, } small = { 'ноль': 0, 'один': 1, 'два': 2, 'три': 3, 'четыре': 4, 'пять': 5, 'шесть': 6, 'семь': 7, 'восемь': 8, 'девять': 9, 'десять': 10, 'одиннадцать': 11, 'двенадцать': 12, 'тринадцать': 13, 'четырнадцать': 14, 'пятнадцать': 15, 'шестнадцать': 16, 'семнадцать': 17, 'восемнадцать': 18, 'девятнадцать': 19, 'двадцать': 20, 'тридцать': 30, 'сорок': 40, 'пятьдесят': 50, 'шестьдесят': 60, 'семьдесят': 70, 'восемьдесят': 80, 'девяносто': 90, } numbers = { 'ноль': 0, 'один': 1, 'два': 2, 'три': 3, 'четыре': 4, 'пять': 5, 'шесть': 6, 'семь': 7, 'восемь': 8, 'девять': 9, 'десять': 10, 'одиннадцать': 11, 'двенадцать': 12, 'тринадцать': 13, 'четырнадцать': 14, 'пятнадцать': 15, 'шестнадцать': 16, 'семнадцать': 17, 'восемнадцать': 18, 'девятнадцать': 19, 'двадцать': 20, } magnitude = { 'тысяча': 1000, 'миллион': 1000000, 'миллиард': 1000000000, 'триллион': 1000000000000, 'квадриллион': 1000000000000000, 'квинтиллион': 1000000000000000000, 'секстиллион': 1000000000000000000000, 'септиллион': 1000000000000000000000000, 'октиллион': 1000000000000000000000000000, 'нониллион': 1000000000000000000000000000000, 'дециллион': 1000000000000000000000000000000000, } parsedatetime-2.4/parsedatetime/warns.py0000644000076500000240000000074512611302347020602 0ustar bearstaff00000000000000# -*- coding: utf-8 -*- """ parsedatetime/warns.py All subclasses inherited from `Warning` class """ from __future__ import absolute_import import warnings class pdtDeprecationWarning(DeprecationWarning): pass class pdtPendingDeprecationWarning(PendingDeprecationWarning): pass class pdt20DeprecationWarning(pdtPendingDeprecationWarning): pass warnings.simplefilter('default', pdtDeprecationWarning) warnings.simplefilter('ignore', pdtPendingDeprecationWarning) parsedatetime-2.4/parsedatetime.egg-info/0000755000076500000240000000000013106001723020554 5ustar bearstaff00000000000000parsedatetime-2.4/parsedatetime.egg-info/dependency_links.txt0000644000076500000240000000000113106001723024622 0ustar bearstaff00000000000000 parsedatetime-2.4/parsedatetime.egg-info/pbr.json0000644000076500000240000000005712741421260022242 0ustar bearstaff00000000000000{"is_release": false, "git_version": "e3cf2a1"}parsedatetime-2.4/parsedatetime.egg-info/PKG-INFO0000644000076500000240000001334713106001723021661 0ustar bearstaff00000000000000Metadata-Version: 1.1 Name: parsedatetime Version: 2.4 Summary: Parse human-readable date/time text. Home-page: https://github.com/bear/parsedatetime Author: Mike Taylor Author-email: bear@bear.im License: Apache License 2.0 Download-URL: https://pypi.python.org/pypi/parsedatetime Description: parsedatetime Parse human-readable date/time strings. Python 2.6 or greater is required for parsedatetime version 1.0 or greater. While we still test with Python 2.6 we cannot guarantee that future changes will not break under 2.6 .. image:: https://img.shields.io/pypi/v/parsedatetime.svg :target: https://pypi.python.org/pypi/parsedatetime/ :alt: Downloads .. image:: https://travis-ci.org/bear/parsedatetime.svg?branch=master :target: https://travis-ci.org/bear/parsedatetime :alt: Travis CI .. image:: http://codecov.io/github/bear/parsedatetime/coverage.svg?branch=master :target: http://codecov.io/github/bear/parsedatetime :alt: Codecov .. image:: https://requires.io/github/bear/parsedatetime/requirements.svg?branch=master :target: https://requires.io/github/bear/parsedatetime/requirements/?branch=master :alt: Requirements Status .. image:: https://dependencyci.com/github/bear/parsedatetime/badge :target: https://dependencyci.com/github/bear/parsedatetime :alt: Dependency Status ========== Installing ========== You can install parsedatetime using:: pip install parsedatetime ============= Running Tests ============= From the source directory:: make test To run tests on several python versions, type ``make tox``:: $ make tox [... tox creates a virtualenv for every python version and runs tests inside of each] py27: commands succeeded py35: commands succeeded This assumes that you have the versions you want to test under installed as part of your PyEnv environment:: pyenv install -s 2.6.9 pyenv install -s 2.7.11 pyenv install -s 3.5.2 pyenv install -s pypy-5.3 pyenv global 2.7.11 3.5.2 2.6.9 pypy-5.3 The tests depend on PyICU being installed. PyICU depends on icu4c which on OS X requires homebrew:: brew install icu4c *and* then will require that you pass in the ``LDFLAGS`` and ``CPPFLAGS`` that homebrew will display after the install:: LDFLAGS: -L/usr/local/opt/icu4c/lib CPPFLAGS: -I/usr/local/opt/icu4c/include The Makefile contains the OS X default values for them so you may need to tweak them. =================== Using parsedatetime =================== An example of how to use parsedatetime:: import parsedatetime cal = parsedatetime.Calendar() cal.parse("tomorrow") To get it to a Python ``datetime`` object:: from datetime import datetime time_struct, parse_status = cal.parse("tomorrow") datetime(*time_struct[:6]) Parse datetime with timezone support (using pytz package):: import parsedatetime import pytz from pytz import timezone cal = parsedatetime.Calendar() datetime_obj, _ = cal.parseDT(datetimeString="tomorrow", tzinfo=timezone("US/Pacific")) More detailed examples can be found in the examples directory. ============= Documentation ============= The generated documentation is included by default in the docs directory and can also be viewed online at https://bear.im/code/parsedatetime/docs/index.html The docs can be generated by running:: make docs ===== Notes ===== The ``Calendar`` class has a member property named ``ptc`` which is created during the class init method to be an instance of ``parsedatetime_consts.CalendarConstants()``. ======= History ======= The code in parsedatetime has been implemented over the years in many different languages (C, Clipper, Delphi) as part of different custom/proprietary systems I've worked on. Sadly the previous code is not "open" in any sense of that word. When I went to work for Open Source Applications Foundation and realized that the Chandler project could benefit from my experience with parsing of date/time text I decided to start from scratch and implement the code using Python and make it truly open. After working on the initial concept and creating something that could be shown to the Chandler folks, the code has now evolved to its current state with the help of the Chandler folks, most especially Darshana. Platform: Any Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: OS Independent Classifier: Topic :: Text Processing Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 parsedatetime-2.4/parsedatetime.egg-info/requires.txt0000644000076500000240000000000713106001723023151 0ustar bearstaff00000000000000future parsedatetime-2.4/parsedatetime.egg-info/SOURCES.txt0000644000076500000240000000275113106001723022445 0ustar bearstaff00000000000000AUTHORS.txt CHANGES.txt LICENSE.txt MANIFEST.in Makefile README.rst requirements.testing.txt requirements.txt setup.cfg setup.py violations.flake8.txt examples/README.txt examples/basic.py examples/with_locale.py parsedatetime/__init__.py parsedatetime/context.py parsedatetime/parsedatetime.py parsedatetime/warns.py parsedatetime.egg-info/PKG-INFO parsedatetime.egg-info/SOURCES.txt parsedatetime.egg-info/dependency_links.txt parsedatetime.egg-info/pbr.json parsedatetime.egg-info/requires.txt parsedatetime.egg-info/top_level.txt parsedatetime/pdt_locales/__init__.py parsedatetime/pdt_locales/base.py parsedatetime/pdt_locales/de_DE.py parsedatetime/pdt_locales/en_AU.py parsedatetime/pdt_locales/en_US.py parsedatetime/pdt_locales/es.py parsedatetime/pdt_locales/fr_FR.py parsedatetime/pdt_locales/icu.py parsedatetime/pdt_locales/nl_NL.py parsedatetime/pdt_locales/pt_BR.py parsedatetime/pdt_locales/ru_RU.py tests/TestAlternativeAbbreviations.py tests/TestAustralianLocale.py tests/TestComplexDateTimes.py tests/TestContext.py tests/TestConvertUnitAsWords.py tests/TestDelta.py tests/TestErrors.py tests/TestFrenchLocale.py tests/TestGermanLocale.py tests/TestInc.py tests/TestLocaleBase.py tests/TestMultiple.py tests/TestNlp.py tests/TestPhrases.py tests/TestRanges.py tests/TestRussianLocale.py tests/TestSimpleDateTimes.py tests/TestSimpleOffsets.py tests/TestSimpleOffsetsHours.py tests/TestSimpleOffsetsNoon.py tests/TestStartTimeFromSourceTime.py tests/TestUnits.py tests/__init__.py tests/utils.pyparsedatetime-2.4/parsedatetime.egg-info/top_level.txt0000644000076500000240000000001613106001723023303 0ustar bearstaff00000000000000parsedatetime parsedatetime-2.4/PKG-INFO0000644000076500000240000001334713106001723015340 0ustar bearstaff00000000000000Metadata-Version: 1.1 Name: parsedatetime Version: 2.4 Summary: Parse human-readable date/time text. Home-page: https://github.com/bear/parsedatetime Author: Mike Taylor Author-email: bear@bear.im License: Apache License 2.0 Download-URL: https://pypi.python.org/pypi/parsedatetime Description: parsedatetime Parse human-readable date/time strings. Python 2.6 or greater is required for parsedatetime version 1.0 or greater. While we still test with Python 2.6 we cannot guarantee that future changes will not break under 2.6 .. image:: https://img.shields.io/pypi/v/parsedatetime.svg :target: https://pypi.python.org/pypi/parsedatetime/ :alt: Downloads .. image:: https://travis-ci.org/bear/parsedatetime.svg?branch=master :target: https://travis-ci.org/bear/parsedatetime :alt: Travis CI .. image:: http://codecov.io/github/bear/parsedatetime/coverage.svg?branch=master :target: http://codecov.io/github/bear/parsedatetime :alt: Codecov .. image:: https://requires.io/github/bear/parsedatetime/requirements.svg?branch=master :target: https://requires.io/github/bear/parsedatetime/requirements/?branch=master :alt: Requirements Status .. image:: https://dependencyci.com/github/bear/parsedatetime/badge :target: https://dependencyci.com/github/bear/parsedatetime :alt: Dependency Status ========== Installing ========== You can install parsedatetime using:: pip install parsedatetime ============= Running Tests ============= From the source directory:: make test To run tests on several python versions, type ``make tox``:: $ make tox [... tox creates a virtualenv for every python version and runs tests inside of each] py27: commands succeeded py35: commands succeeded This assumes that you have the versions you want to test under installed as part of your PyEnv environment:: pyenv install -s 2.6.9 pyenv install -s 2.7.11 pyenv install -s 3.5.2 pyenv install -s pypy-5.3 pyenv global 2.7.11 3.5.2 2.6.9 pypy-5.3 The tests depend on PyICU being installed. PyICU depends on icu4c which on OS X requires homebrew:: brew install icu4c *and* then will require that you pass in the ``LDFLAGS`` and ``CPPFLAGS`` that homebrew will display after the install:: LDFLAGS: -L/usr/local/opt/icu4c/lib CPPFLAGS: -I/usr/local/opt/icu4c/include The Makefile contains the OS X default values for them so you may need to tweak them. =================== Using parsedatetime =================== An example of how to use parsedatetime:: import parsedatetime cal = parsedatetime.Calendar() cal.parse("tomorrow") To get it to a Python ``datetime`` object:: from datetime import datetime time_struct, parse_status = cal.parse("tomorrow") datetime(*time_struct[:6]) Parse datetime with timezone support (using pytz package):: import parsedatetime import pytz from pytz import timezone cal = parsedatetime.Calendar() datetime_obj, _ = cal.parseDT(datetimeString="tomorrow", tzinfo=timezone("US/Pacific")) More detailed examples can be found in the examples directory. ============= Documentation ============= The generated documentation is included by default in the docs directory and can also be viewed online at https://bear.im/code/parsedatetime/docs/index.html The docs can be generated by running:: make docs ===== Notes ===== The ``Calendar`` class has a member property named ``ptc`` which is created during the class init method to be an instance of ``parsedatetime_consts.CalendarConstants()``. ======= History ======= The code in parsedatetime has been implemented over the years in many different languages (C, Clipper, Delphi) as part of different custom/proprietary systems I've worked on. Sadly the previous code is not "open" in any sense of that word. When I went to work for Open Source Applications Foundation and realized that the Chandler project could benefit from my experience with parsing of date/time text I decided to start from scratch and implement the code using Python and make it truly open. After working on the initial concept and creating something that could be shown to the Chandler folks, the code has now evolved to its current state with the help of the Chandler folks, most especially Darshana. Platform: Any Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: OS Independent Classifier: Topic :: Text Processing Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 parsedatetime-2.4/README.rst0000644000076500000240000000765313060630704015743 0ustar bearstaff00000000000000parsedatetime Parse human-readable date/time strings. Python 2.6 or greater is required for parsedatetime version 1.0 or greater. While we still test with Python 2.6 we cannot guarantee that future changes will not break under 2.6 .. image:: https://img.shields.io/pypi/v/parsedatetime.svg :target: https://pypi.python.org/pypi/parsedatetime/ :alt: Downloads .. image:: https://travis-ci.org/bear/parsedatetime.svg?branch=master :target: https://travis-ci.org/bear/parsedatetime :alt: Travis CI .. image:: http://codecov.io/github/bear/parsedatetime/coverage.svg?branch=master :target: http://codecov.io/github/bear/parsedatetime :alt: Codecov .. image:: https://requires.io/github/bear/parsedatetime/requirements.svg?branch=master :target: https://requires.io/github/bear/parsedatetime/requirements/?branch=master :alt: Requirements Status .. image:: https://dependencyci.com/github/bear/parsedatetime/badge :target: https://dependencyci.com/github/bear/parsedatetime :alt: Dependency Status ========== Installing ========== You can install parsedatetime using:: pip install parsedatetime ============= Running Tests ============= From the source directory:: make test To run tests on several python versions, type ``make tox``:: $ make tox [... tox creates a virtualenv for every python version and runs tests inside of each] py27: commands succeeded py35: commands succeeded This assumes that you have the versions you want to test under installed as part of your PyEnv environment:: pyenv install -s 2.6.9 pyenv install -s 2.7.11 pyenv install -s 3.5.2 pyenv install -s pypy-5.3 pyenv global 2.7.11 3.5.2 2.6.9 pypy-5.3 The tests depend on PyICU being installed. PyICU depends on icu4c which on OS X requires homebrew:: brew install icu4c *and* then will require that you pass in the ``LDFLAGS`` and ``CPPFLAGS`` that homebrew will display after the install:: LDFLAGS: -L/usr/local/opt/icu4c/lib CPPFLAGS: -I/usr/local/opt/icu4c/include The Makefile contains the OS X default values for them so you may need to tweak them. =================== Using parsedatetime =================== An example of how to use parsedatetime:: import parsedatetime cal = parsedatetime.Calendar() cal.parse("tomorrow") To get it to a Python ``datetime`` object:: from datetime import datetime time_struct, parse_status = cal.parse("tomorrow") datetime(*time_struct[:6]) Parse datetime with timezone support (using pytz package):: import parsedatetime import pytz from pytz import timezone cal = parsedatetime.Calendar() datetime_obj, _ = cal.parseDT(datetimeString="tomorrow", tzinfo=timezone("US/Pacific")) More detailed examples can be found in the examples directory. ============= Documentation ============= The generated documentation is included by default in the docs directory and can also be viewed online at https://bear.im/code/parsedatetime/docs/index.html The docs can be generated by running:: make docs ===== Notes ===== The ``Calendar`` class has a member property named ``ptc`` which is created during the class init method to be an instance of ``parsedatetime_consts.CalendarConstants()``. ======= History ======= The code in parsedatetime has been implemented over the years in many different languages (C, Clipper, Delphi) as part of different custom/proprietary systems I've worked on. Sadly the previous code is not "open" in any sense of that word. When I went to work for Open Source Applications Foundation and realized that the Chandler project could benefit from my experience with parsing of date/time text I decided to start from scratch and implement the code using Python and make it truly open. After working on the initial concept and creating something that could be shown to the Chandler folks, the code has now evolved to its current state with the help of the Chandler folks, most especially Darshana. parsedatetime-2.4/requirements.testing.txt0000644000076500000240000000015712747562750021224 0ustar bearstaff00000000000000pytest pytest-cov pytest-runner mccabe flake8 coverage coveralls codecov check-manifest unittest2 tox tox-pyenvparsedatetime-2.4/requirements.txt0000644000076500000240000000000012611317252017514 0ustar bearstaff00000000000000parsedatetime-2.4/setup.cfg0000644000076500000240000000045613106001723016061 0ustar bearstaff00000000000000[check-manifest] ignore = .travis.yml circle.yml violations.flake8.txt [flake8] ignore = E111,E124,E126,E201,E202,E221,E241,E302,E501,F405 [pep8] ignore = E111,E124,E126,E128,E201,E202,E221,E226,E241,E301,E302,E303,E402,E501,W291,F405 max-line-length = 160 [egg_info] tag_build = tag_date = 0 parsedatetime-2.4/setup.py0000644000076500000240000000337213105773676016000 0ustar bearstaff00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- import os import re import codecs from setuptools import setup, find_packages cwd = os.path.abspath(os.path.dirname(__file__)) def read(filename): with codecs.open(os.path.join(cwd, filename), 'rb', 'utf-8') as h: return h.read() metadata = read(os.path.join(cwd, 'parsedatetime', '__init__.py')) def extract_metaitem(meta): # swiped from https://hynek.me 's attr package meta_match = re.search(r"""^__{meta}__\s+=\s+['\"]([^'\"]*)['\"]""".format(meta=meta), metadata, re.MULTILINE) if meta_match: return meta_match.group(1) raise RuntimeError('Unable to find __{meta}__ string.'.format(meta=meta)) setup( name='parsedatetime', version=extract_metaitem('version'), author=extract_metaitem('author'), author_email=extract_metaitem('email'), url=extract_metaitem('url'), download_url=extract_metaitem('download_url'), description=extract_metaitem('description'), license=extract_metaitem('license'), packages=find_packages(exclude=['tests', 'docs']), platforms=['Any'], long_description=read('README.rst'), install_requires=['future'], tests_require=['pytest'], test_suite='tests', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'License :: OSI Approved :: Apache Software License', 'Operating System :: OS Independent', 'Topic :: Text Processing', 'Topic :: Software Development :: Libraries :: Python Modules', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', ] ) parsedatetime-2.4/tests/0000755000076500000240000000000013106001723015375 5ustar bearstaff00000000000000parsedatetime-2.4/tests/__init__.py0000644000076500000240000000124712747166534017540 0ustar bearstaff00000000000000""" Unit tests for parsedatetime The tests can be run as a C{suite} by running:: nosetests Requires Python 3.0 or later """ import logging __author__ = 'Mike Taylor (bear@code-bear.com)' __copyright__ = 'Copyright (c) 2004 Mike Taylor' __license__ = 'Apache v2.0' __version__ = '1.0.0' __contributors__ = ['Darshana Chhajed', 'Michael Lim (lim.ck.michael@gmail.com)', 'Bernd Zeimetz (bzed@debian.org)', ] log = logging.getLogger('parsedatetime') echoHandler = logging.StreamHandler() echoFormatter = logging.Formatter('%(levelname)-8s %(message)s') log.addHandler(echoHandler) # log.setLevel(logging.DEBUG) parsedatetime-2.4/tests/TestAlternativeAbbreviations.py0000644000076500000240000001334612747560752023633 0ustar bearstaff00000000000000# -*- coding: utf-8 -*- from __future__ import unicode_literals import sys import time import datetime import parsedatetime as pdt from parsedatetime.pdt_locales import get_icu from . import utils if sys.version_info < (2, 7): import unittest2 as unittest else: import unittest pdtLocale_en = get_icu('en_US') pdtLocale_en.Weekdays = [ 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'] pdtLocale_en.shortWeekdays = [ 'mon|mond', 'tue|tues', 'wed|wedn', 'thu|thur|thurs', 'fri|frid', 'sat|sa', 'sun|su'] pdtLocale_en.Months = [ 'january', 'february', 'march', 'april', 'may', 'june', 'july', 'august', 'september', 'october', 'november', 'december'] pdtLocale_en.shortMonths = [ 'jan|janu', 'feb|febr', 'mar|marc', 'apr|apri', 'may', 'jun|june', 'jul', 'aug|augu', 'sep|sept', 'oct|octo', 'nov|novem', 'dec|decem'] class test(unittest.TestCase): @utils.assertEqualWithComparator def assertExpectedResult(self, result, check, **kwargs): return utils.compareResultByTimeTuplesAndFlags(result, check, **kwargs) def setUp(self): pdt.pdtLocales['en_us'] = pdtLocale_en # override for the test self.ptc = pdt.Constants('en_us', usePyICU=False) self.cal = pdt.Calendar(self.ptc) (self.yr, self.mth, self.dy, self.hr, self.mn, self.sec, self.wd, self.yd, self.isdst) = time.localtime() def testDaysOfWeek(self): start = datetime.datetime( 2014, 10, 25, self.hr, self.mn, self.sec).timetuple() target = datetime.datetime( 2014, 10, 26, self.hr, self.mn, self.sec).timetuple() self.assertExpectedResult(self.cal.parse('sunday', start), (target, 1)) self.assertExpectedResult(self.cal.parse('sun', start), (target, 1)) self.assertExpectedResult(self.cal.parse('su', start), (target, 1)) target = datetime.datetime( 2014, 10, 27, self.hr, self.mn, self.sec).timetuple() self.assertExpectedResult(self.cal.parse('Monday', start), (target, 1)) self.assertExpectedResult(self.cal.parse('mon', start), (target, 1)) self.assertExpectedResult(self.cal.parse('mond', start), (target, 1)) target = datetime.datetime( 2014, 10, 28, self.hr, self.mn, self.sec).timetuple() self.assertExpectedResult( self.cal.parse('tuesday', start), (target, 1)) self.assertExpectedResult(self.cal.parse('tues', start), (target, 1)) self.assertExpectedResult(self.cal.parse('tue', start), (target, 1)) target = datetime.datetime( 2014, 10, 29, self.hr, self.mn, self.sec).timetuple() self.assertExpectedResult( self.cal.parse('wednesday', start), (target, 1)) self.assertExpectedResult(self.cal.parse('wedn', start), (target, 1)) self.assertExpectedResult(self.cal.parse('wed', start), (target, 1)) target = datetime.datetime( 2014, 10, 30, self.hr, self.mn, self.sec).timetuple() self.assertExpectedResult( self.cal.parse('thursday', start), (target, 1)) self.assertExpectedResult(self.cal.parse('thu', start), (target, 1)) self.assertExpectedResult(self.cal.parse('thur', start), (target, 1)) self.assertExpectedResult(self.cal.parse('thurs', start), (target, 1)) target = datetime.datetime( 2014, 10, 31, self.hr, self.mn, self.sec).timetuple() self.assertExpectedResult(self.cal.parse('friday', start), (target, 1)) self.assertExpectedResult(self.cal.parse('fri', start), (target, 1)) self.assertExpectedResult(self.cal.parse('frid', start), (target, 1)) target = datetime.datetime( 2014, 11, 1, self.hr, self.mn, self.sec).timetuple() self.assertExpectedResult( self.cal.parse('saturday', start), (target, 1)) self.assertExpectedResult(self.cal.parse('sat', start), (target, 1)) self.assertExpectedResult(self.cal.parse('sa', start), (target, 1)) def testMonths(self): start = datetime.datetime( 2014, 1, 1, self.hr, self.mn, self.sec).timetuple() for dates, expected_date in [ ('jan|janu|january', datetime.datetime( 2014, 1, 1, self.hr, self.mn, self.sec).timetuple()), ('feb|febr|february', datetime.datetime( 2014, 2, 1, self.hr, self.mn, self.sec).timetuple()), ('mar|marc|march', datetime.datetime( 2014, 3, 1, self.hr, self.mn, self.sec).timetuple()), ('apr|apri|april', datetime.datetime( 2014, 4, 1, self.hr, self.mn, self.sec).timetuple()), ('may|may', datetime.datetime( 2014, 5, 1, self.hr, self.mn, self.sec).timetuple()), ('jun|june', datetime.datetime( 2014, 6, 1, self.hr, self.mn, self.sec).timetuple()), ('jul|july', datetime.datetime( 2014, 7, 1, self.hr, self.mn, self.sec).timetuple()), ('aug|augu|august', datetime.datetime( 2014, 8, 1, self.hr, self.mn, self.sec).timetuple()), ('sep|sept|september', datetime.datetime( 2014, 9, 1, self.hr, self.mn, self.sec).timetuple()), ('oct|octo|october', datetime.datetime( 2014, 10, 1, self.hr, self.mn, self.sec).timetuple()), ('nov|novem|november', datetime.datetime( 2014, 11, 1, self.hr, self.mn, self.sec).timetuple()), ('dec|decem|december', datetime.datetime( 2014, 12, 1, self.hr, self.mn, self.sec).timetuple()) ]: for dateText in dates.split("|"): # print dateText self.assertExpectedResult( self.cal.parse(dateText, start), (expected_date, 1)) parsedatetime-2.4/tests/TestAustralianLocale.py0000644000076500000240000001073412747560752022065 0ustar bearstaff00000000000000# -*- coding: utf-8 -*- """ Test parsing of simple date and times using the Australian locale """ from __future__ import unicode_literals import sys import time import datetime import parsedatetime as pdt from . import utils if sys.version_info < (2, 7): import unittest2 as unittest else: import unittest class test(unittest.TestCase): @utils.assertEqualWithComparator def assertExpectedResult(self, result, check, **kwargs): return utils.compareResultByTimeTuplesAndFlags(result, check, **kwargs) def setUp(self): self.ptc = pdt.Constants('en_AU', usePyICU=False) self.cal = pdt.Calendar(self.ptc) (self.yr, self.mth, self.dy, self.hr, self.mn, self.sec, self.wd, self.yd, self.isdst) = time.localtime() if self.ptc.localeID != 'en_AU': raise unittest.SkipTest( 'Locale not set to en_AU - check if PyICU is installed') def testTimes(self): start = datetime.datetime( self.yr, self.mth, self.dy, self.hr, self.mn, self.sec).timetuple() target = datetime.datetime( self.yr, self.mth, self.dy, 23, 0, 0).timetuple() self.assertExpectedResult( self.cal.parse('11:00:00 PM', start), (target, 2)) self.assertExpectedResult( self.cal.parse('11:00 PM', start), (target, 2)) self.assertExpectedResult(self.cal.parse('11 PM', start), (target, 2)) self.assertExpectedResult(self.cal.parse('11PM', start), (target, 2)) self.assertExpectedResult(self.cal.parse('2300', start), (target, 2)) self.assertExpectedResult(self.cal.parse('23:00', start), (target, 2)) self.assertExpectedResult(self.cal.parse('11p', start), (target, 2)) self.assertExpectedResult(self.cal.parse('11pm', start), (target, 2)) target = datetime.datetime( self.yr, self.mth, self.dy, 11, 0, 0).timetuple() self.assertExpectedResult( self.cal.parse('11:00:00 AM', start), (target, 2)) self.assertExpectedResult( self.cal.parse('11:00 AM', start), (target, 2)) self.assertExpectedResult(self.cal.parse('11 AM', start), (target, 2)) self.assertExpectedResult(self.cal.parse('11AM', start), (target, 2)) self.assertExpectedResult(self.cal.parse('1100', start), (target, 2)) self.assertExpectedResult(self.cal.parse('11:00', start), (target, 2)) self.assertExpectedResult(self.cal.parse('11a', start), (target, 2)) self.assertExpectedResult(self.cal.parse('11am', start), (target, 2)) target = datetime.datetime( self.yr, self.mth, self.dy, 7, 30, 0).timetuple() self.assertExpectedResult(self.cal.parse('730', start), (target, 2)) self.assertExpectedResult(self.cal.parse('0730', start), (target, 2)) target = datetime.datetime( self.yr, self.mth, self.dy, 17, 30, 0).timetuple() self.assertExpectedResult(self.cal.parse('1730', start), (target, 2)) self.assertExpectedResult(self.cal.parse('173000', start), (target, 2)) def testDates(self): start = datetime.datetime( self.yr, self.mth, self.dy, self.hr, self.mn, self.sec).timetuple() target = datetime.datetime( 2006, 8, 25, self.hr, self.mn, self.sec).timetuple() self.assertExpectedResult( self.cal.parse('25-08-2006', start), (target, 1)) self.assertExpectedResult( self.cal.parse('25/08/2006', start), (target, 1)) self.assertExpectedResult( self.cal.parse('25.08.2006', start), (target, 1)) self.assertExpectedResult( self.cal.parse('25-8-06', start), (target, 1)) self.assertExpectedResult( self.cal.parse('25/8/06', start), (target, 1)) if self.mth > 8 or (self.mth == 8 and self.dy > 25): target = datetime.datetime( self.yr + 1, 8, 25, self.hr, self.mn, self.sec).timetuple() else: target = datetime.datetime( self.yr, 8, 25, self.hr, self.mn, self.sec).timetuple() self.assertExpectedResult(self.cal.parse('25-8', start), (target, 1)) self.assertExpectedResult(self.cal.parse('25/8', start), (target, 1)) self.assertExpectedResult(self.cal.parse('25.8', start), (target, 1)) self.assertExpectedResult(self.cal.parse('25-08', start), (target, 1)) self.assertExpectedResult(self.cal.parse('25/08', start), (target, 1)) if __name__ == "__main__": unittest.main() parsedatetime-2.4/tests/TestComplexDateTimes.py0000644000076500000240000001525213105773676022051 0ustar bearstaff00000000000000# -*- coding: utf-8 -*- """ Test parsing of complex date and times """ from __future__ import unicode_literals import sys import time from datetime import datetime import parsedatetime as pdt from . import utils if sys.version_info < (2, 7): import unittest2 as unittest else: import unittest class test(unittest.TestCase): @utils.assertEqualWithComparator def assertExpectedResult(self, result, check, **kwargs): return utils.compareResultByTimeTuplesAndFlags(result, check, **kwargs) def setUp(self): self.cal = pdt.Calendar() (self.yr, self.mth, self.dy, self.hr, self.mn, self.sec, self.wd, self.yd, self.isdst) = time.localtime() def testDate3ConfusedHourAndYear(self): start = datetime( self.yr, self.mth, self.dy, self.hr, self.mn, self.sec).timetuple() self.assertExpectedResult( self.cal.parse('Aug 05, 2014 4:15 AM'), (datetime(2014, 8, 5, 4, 15, 0).timetuple(), 3)) self.assertExpectedResult( self.cal.parse('Aug 05, 2003 3:15 AM'), (datetime(2003, 8, 5, 3, 15, 0).timetuple(), 3)) self.assertExpectedResult( self.cal.parse('Aug 05, 2003 03:15 AM'), (datetime(2003, 8, 5, 3, 15, 0).timetuple(), 3)) self.assertExpectedResult( self.cal.parse('June 30th 12PM', start), (datetime(self.yr if datetime.now() < datetime(self.yr, 6, 30, 12) else self.yr + 1, 6, 30, 12, 0, 0).timetuple(), 3)) self.assertExpectedResult( self.cal.parse('June 30th 12:00', start), (datetime(self.yr if datetime.now() < datetime(self.yr, 6, 30, 12) else self.yr + 1, 6, 30, 12, 0, 0).timetuple(), 3)) self.assertExpectedResult( self.cal.parse('December 30th 23PM', start), (datetime(self.yr if datetime.now() < datetime(self.yr, 12, 30, 23) else self.yr + 1, 12, 30, 23, 0, 0).timetuple(), 3)) self.assertExpectedResult( self.cal.parse('December 30th 23:02', start), (datetime(self.yr if datetime.now() < datetime(self.yr, 12, 30, 23, 2) else self.yr + 1, 12, 30, 23, 2, 0).timetuple(), 3)) def testDates(self): start = datetime( self.yr, self.mth, self.dy, self.hr, self.mn, self.sec).timetuple() target = datetime(2006, 8, 25, 17, 0, 0).timetuple() self.assertExpectedResult( self.cal.parse('08/25/2006 5pm', start), (target, 3)) self.assertExpectedResult( self.cal.parse('5pm on 08.25.2006', start), (target, 3)) self.assertExpectedResult( self.cal.parse('5pm August 25, 2006', start), (target, 3)) self.assertExpectedResult( self.cal.parse('5pm August 25th, 2006', start), (target, 3)) self.assertExpectedResult( self.cal.parse('5pm 25 August, 2006', start), (target, 3)) self.assertExpectedResult( self.cal.parse('5pm 25th August, 2006', start), (target, 3)) self.assertExpectedResult( self.cal.parse('Aug 25, 2006 5pm', start), (target, 3)) self.assertExpectedResult( self.cal.parse('Aug 25th, 2006 5pm', start), (target, 3)) self.assertExpectedResult( self.cal.parse('25 Aug, 2006 5pm', start), (target, 3)) self.assertExpectedResult( self.cal.parse('25th Aug 2006, 5pm', start), (target, 3)) if self.mth > 8 or (self.mth == 8 and self.dy > 5): target = datetime(self.yr + 1, 8, 5, 17, 0, 0).timetuple() else: target = datetime(self.yr, 8, 5, 17, 0, 0).timetuple() self.assertExpectedResult( self.cal.parse('8/5 at 5pm', start), (target, 3)) self.assertExpectedResult( self.cal.parse('5pm 8.5', start), (target, 3)) self.assertExpectedResult( self.cal.parse('08/05 5pm', start), (target, 3)) self.assertExpectedResult( self.cal.parse('August 5 5pm', start), (target, 3)) self.assertExpectedResult( self.cal.parse('5pm Aug 05', start), (target, 3)) self.assertExpectedResult( self.cal.parse('Aug 05 5pm', start), (target, 3)) self.assertExpectedResult( self.cal.parse('Aug 05th 5pm', start), (target, 3)) self.assertExpectedResult( self.cal.parse('5 August 5pm', start), (target, 3)) self.assertExpectedResult( self.cal.parse('5th August 5pm', start), (target, 3)) self.assertExpectedResult( self.cal.parse('5pm 05 Aug', start), (target, 3)) self.assertExpectedResult( self.cal.parse('05 Aug 5pm', start), (target, 3)) self.assertExpectedResult( self.cal.parse('05th Aug 5pm', start), (target, 3)) self.assertExpectedResult( self.cal.parse('August 5th 5pm', start), (target, 3)) if self.mth > 8 or (self.mth == 8 and self.dy > 5): target = datetime(self.yr + 1, 8, 5, 12, 0, 0).timetuple() else: target = datetime(self.yr, 8, 5, 12, 0, 0).timetuple() self.assertExpectedResult( self.cal.parse('August 5th 12:00', start), (target, 3)) self.assertExpectedResult( self.cal.parse('August 5th 12pm', start), (target, 3)) self.assertExpectedResult( self.cal.parse('August 5th 12:00pm', start), (target, 3)) self.assertExpectedResult( self.cal.parse('August 5th 12 pm', start), (target, 3)) self.assertExpectedResult( self.cal.parse('August 5th 12:00 pm', start), (target, 3)) if self.mth > 8 or (self.mth == 8 and self.dy > 22): target = datetime( self.yr + 1, 8, 22, 3, 26, 0).timetuple() else: target = datetime(self.yr, 8, 22, 3, 26, 0).timetuple() self.assertExpectedResult( self.cal.parse('August 22nd 3:26', start), (target, 3)) self.assertExpectedResult( self.cal.parse('August 22nd 3:26am', start), (target, 3)) self.assertExpectedResult( self.cal.parse('August 22nd 3:26 am', start), (target, 3)) def testDatesWithDay(self): start = datetime( self.yr, self.mth, self.dy, self.hr, self.mn, self.sec).timetuple() target = datetime(2016, 8, 23, 17, 0, 0).timetuple() self.assertExpectedResult( self.cal.parse('tuesday august 23nd 2016 at 5pm', start), (target, 3)) if __name__ == "__main__": unittest.main() parsedatetime-2.4/tests/TestContext.py0000644000076500000240000000362312747560752020265 0ustar bearstaff00000000000000# -*- coding: utf-8 -*- """ Test pdtContext """ import sys import time import parsedatetime as pdt from parsedatetime.context import pdtContext if sys.version_info < (2, 7): import unittest2 as unittest else: import unittest class test(unittest.TestCase): def setUp(self): self.cal = pdt.Calendar(version=pdt.VERSION_CONTEXT_STYLE) (self.yr, self.mth, self.dy, self.hr, self.mn, self.sec, self.wd, self.yd, self.isdst) = time.localtime() def testContext(self): self.assertEqual(self.cal.parse('5 min from now')[1], pdtContext(pdtContext.ACU_MIN | pdtContext.ACU_NOW)) self.assertEqual(self.cal.parse('5 min from now', version=pdt.VERSION_FLAG_STYLE)[1], 2) self.assertEqual(self.cal.parse('7/11/2015')[1], pdtContext(pdtContext.ACU_YEAR | pdtContext.ACU_MONTH | pdtContext.ACU_DAY)) self.assertEqual(self.cal.parse('7/11/2015', version=pdt.VERSION_FLAG_STYLE)[1], 1) self.assertEqual(self.cal.parse('14/32/2015')[1], pdtContext(0)) self.assertEqual(self.cal.parse('25:23')[1], pdtContext()) def testSources(self): self.assertEqual(self.cal.parse('afternoon 5pm')[1], pdtContext(pdtContext.ACU_HALFDAY | pdtContext.ACU_HOUR)) self.assertEqual(self.cal.parse('morning')[1], pdtContext(pdtContext.ACU_HALFDAY)) self.assertEqual(self.cal.parse('night', version=1)[1], 2) def testThreadRun(self): from threading import Thread t = Thread(target=lambda: self.cal.evalRanges('4p-6p')) # should not throw out AttributeError t.start() if __name__ == "__main__": unittest.main() parsedatetime-2.4/tests/TestConvertUnitAsWords.py0000644000076500000240000000225313105773676022422 0ustar bearstaff00000000000000""" Tests the _convertUnitAsWords method. """ import sys import parsedatetime as pdt if sys.version_info < (2, 7): import unittest2 as unittest else: import unittest class test(unittest.TestCase): def setUp(self): self.cal = pdt.Calendar() self.tests = (('one', 1), ('zero', 0), ('eleven', 11), ('forty two', 42), ('a hundred', 100), ('four hundred and fifteen', 415), ('twelve thousand twenty', 12020), ('nine hundred and ninety nine', 999), ('three quintillion four billion', 3000000004000000000), ('forty three thousand, nine hundred and ninety nine', 43999), ('one hundred thirty three billion four hundred thousand three hundred fourteen', 133000400314), ('an octillion', 1000000000000000000000000000) ) def testConversions(self): for pair in self.tests: self.assertEqual(self.cal._convertUnitAsWords(pair[0]), pair[1]) if __name__ == "__main__": unittest.main() parsedatetime-2.4/tests/TestDelta.py0000644000076500000240000000554413060631413017654 0ustar bearstaff00000000000000# -*- coding: utf-8 -*- """ Test time delta """ import sys import time import datetime import parsedatetime as pdt if sys.version_info < (2, 7): import unittest2 as unittest else: import unittest # added to support Python 2.6 which does not have total_seconds() method for timedelta def total_seconds(timedelta): return (timedelta.microseconds + 0.0 + (timedelta.seconds + timedelta.days * 24 * 3600) * 10 ** 6) / 10 ** 6 class test(unittest.TestCase): def setUp(self): self.cal = pdt.Calendar(version=pdt.VERSION_CONTEXT_STYLE) self.source = (2017, 1, 1, 7, 1, 2, 6, 1, 1) def assertDelta(self, ts, years=None, months=None, **deltakw): ts = ts[0] source = self.source delta = datetime.timedelta(**deltakw) calc_delta = (datetime.datetime(*ts[:6]) - datetime.datetime(*source[:6])) delta -= datetime.timedelta(microseconds=delta.microseconds) if not years and not months: self.assertEqual(delta, calc_delta) return if years: delta += datetime.timedelta(days=365 * years) if months: delta += datetime.timedelta(days=30 * months) diff = abs((total_seconds(calc_delta) - total_seconds(delta)) / total_seconds(delta)) self.assertTrue(diff < 0.05, '%s is not less than 0.05' % diff) def testInteger(self): self.assertDelta( self.cal.parse('5 minutes ago', self.source), minutes=-5) self.assertDelta( self.cal.parse('34 hours ago', self.source), hours=-34) self.assertDelta( self.cal.parse('2 days ago', self.source), days=-2) def testFloat(self): self.assertDelta( self.cal.parse('58.4 minutes ago', self.source), minutes=-58.4) self.assertDelta( self.cal.parse('1855336.424 minutes ago', self.source), minutes=-1855336.424) self.assertDelta( self.cal.parse('8.3 hours ago', self.source), hours=-8.3) self.assertDelta( self.cal.parse('22.355 hours ago', self.source), hours=-22.355) self.assertDelta( self.cal.parse('7.2 days ago', self.source), days=-7.2) self.assertDelta( self.cal.parse('7.3 days ago', self.source), days=-7.3) self.assertDelta( self.cal.parse('17.7 days ago', self.source), days=-17.7) self.assertDelta( self.cal.parse('1.4 months ago', self.source), months=-1.4) self.assertDelta( self.cal.parse('4.8 months ago', self.source), months=-4.8) self.assertDelta( self.cal.parse('5.1 months ago', self.source), months=-5.1) self.assertDelta( self.cal.parse('5.11553 years ago', self.source), years=-5.11553) if __name__ == "__main__": unittest.main() parsedatetime-2.4/tests/TestErrors.py0000644000076500000240000000661612747560752020122 0ustar bearstaff00000000000000# -*- coding: utf-8 -*- """ Test parsing of units """ from __future__ import unicode_literals import sys import time import datetime import parsedatetime as pdt from . import utils if sys.version_info < (2, 7): import unittest2 as unittest else: import unittest class test(unittest.TestCase): @utils.assertEqualWithComparator def assertExpectedResult(self, result, check, **kwargs): return utils.compareResultByTimeTuplesAndFlags(result, check, **kwargs) @utils.assertEqualWithComparator def assertExpectedErrorFlag(self, result, check, **kwargs): return utils.compareResultByFlags(result, check, **kwargs) def setUp(self): self.cal = pdt.Calendar() (self.yr, self.mth, self.dy, self.hr, self.mn, self.sec, self.wd, self.yd, self.isdst) = time.localtime() def testErrors(self): s = datetime.datetime.now() start = s.timetuple() # These tests all return current date/time as they are out of range self.assertExpectedResult(self.cal.parse('01/0', start), (start, 0)) self.assertExpectedResult(self.cal.parse('08/35', start), (start, 0)) self.assertExpectedResult(self.cal.parse('18/35', start), (start, 0)) self.assertExpectedResult(self.cal.parse('1799', start), (start, 0)) self.assertExpectedResult(self.cal.parse('781', start), (start, 0)) self.assertExpectedResult(self.cal.parse('2702', start), (start, 0)) self.assertExpectedResult(self.cal.parse('78', start), (start, 0)) self.assertExpectedResult(self.cal.parse('11', start), (start, 0)) self.assertExpectedResult(self.cal.parse('1', start), (start, 0)) self.assertExpectedResult(self.cal.parse('174565', start), (start, 0)) self.assertExpectedResult(self.cal.parse('177505', start), (start, 0)) # ensure short month names do not cause false positives within a word - # jun (june) self.assertExpectedResult( self.cal.parse('injunction', start), (start, 0)) # ensure short month names do not cause false positives at the start of # a word - jul (juuly) self.assertExpectedResult(self.cal.parse('julius', start), (start, 0)) # ensure short month names do not cause false positives at the end of a # word - mar (march) self.assertExpectedResult(self.cal.parse('lamar', start), (start, 0)) # ensure short weekday names do not cause false positives within a word # - mon (monday) self.assertExpectedResult( self.cal.parse('demonize', start), (start, 0)) # ensure short weekday names do not cause false positives at the start # of a word - mon (monday) self.assertExpectedResult(self.cal.parse('money', start), (start, 0)) # ensure short weekday names do not cause false positives at the end of # a word - th (thursday) self.assertExpectedResult(self.cal.parse('month', start), (start, 0)) self.assertExpectedErrorFlag( self.cal.parse('30/030/01/071/07', start), (start, 0)) # overflow due to Python's datetime self.assertExpectedResult(self.cal.parse('12345 y', start), (start, 0)) self.assertExpectedResult( self.cal.parse('654321 w', start), (start, 0)) self.assertExpectedResult( self.cal.parse('3700000 d', start), (start, 0)) if __name__ == "__main__": unittest.main() parsedatetime-2.4/tests/TestFrenchLocale.py0000644000076500000240000001013113105773676021156 0ustar bearstaff00000000000000# -*- coding: utf-8 -*- """ Test parsing of simple date and times using the French locale Note: requires PyICU """ from __future__ import unicode_literals import sys import time import datetime import parsedatetime as pdt from parsedatetime.pdt_locales import get_icu from . import utils if sys.version_info < (2, 7): import unittest2 as unittest else: import unittest class test(unittest.TestCase): @utils.assertEqualWithComparator def assertExpectedResult(self, result, check, **kwargs): return utils.compareResultByTimeTuplesAndFlags(result, check, **kwargs) def setUp(self): locale = 'fr_FR' self.ptc = pdt.Constants(locale, usePyICU=False) self.cal = pdt.Calendar(self.ptc) (self.yr, self.mth, self.dy, self.hr, self.mn, self.sec, self.wd, self.yd, self.isdst) = time.localtime() if self.ptc.localeID != locale: raise unittest.SkipTest( 'Locale not set to fr_FR - check if PyICU is installed') def testTimes(self): start = datetime.datetime( self.yr, self.mth, self.dy, self.hr, self.mn, self.sec).timetuple() target = datetime.datetime( self.yr, self.mth, self.dy, 23, 0, 0).timetuple() self.assertExpectedResult( self.cal.parse('2300', start), (target, 2)) self.assertExpectedResult( self.cal.parse('23:00', start), (target, 2)) target = datetime.datetime( self.yr, self.mth, self.dy, 11, 0, 0).timetuple() self.assertExpectedResult( self.cal.parse('1100', start), (target, 2)) self.assertExpectedResult( self.cal.parse('11:00', start), (target, 2)) target = datetime.datetime( self.yr, self.mth, self.dy, 7, 30, 0).timetuple() self.assertExpectedResult( self.cal.parse('730', start), (target, 2)) self.assertExpectedResult( self.cal.parse('0730', start), (target, 2)) target = datetime.datetime( self.yr, self.mth, self.dy, 17, 30, 0).timetuple() self.assertExpectedResult( self.cal.parse('1730', start), (target, 2)) self.assertExpectedResult( self.cal.parse('173000', start), (target, 2)) def testDates(self): start = datetime.datetime( self.yr, self.mth, self.dy, self.hr, self.mn, self.sec).timetuple() target = datetime.datetime( 2006, 8, 25, self.hr, self.mn, self.sec).timetuple() self.assertExpectedResult( self.cal.parse('25/08/2006', start), (target, 1)) self.assertExpectedResult( self.cal.parse('25/8/06', start), (target, 1)) self.assertExpectedResult( self.cal.parse('août 25, 2006', start), (target, 1)) self.assertExpectedResult( self.cal.parse('août 25 2006', start), (target, 1)) if self.mth > 8 or (self.mth == 8 and self.dy > 25): target = datetime.datetime( self.yr + 1, 8, 25, self.hr, self.mn, self.sec).timetuple() else: target = datetime.datetime( self.yr, 8, 25, self.hr, self.mn, self.sec).timetuple() self.assertExpectedResult( self.cal.parse('25/8', start), (target, 1)) self.assertExpectedResult( self.cal.parse('25/08', start), (target, 1)) def testWeekDays(self): start = datetime.datetime( self.yr, self.mth, self.dy, self.hr, self.mn, self.sec).timetuple() o1 = self.ptc.CurrentDOWParseStyle o2 = self.ptc.DOWParseStyle # set it up so the current dow returns current day self.ptc.CurrentDOWParseStyle = True self.ptc.DOWParseStyle = 1 for i in range(0, 7): dow = self.ptc.shortWeekdays[i] print(dow) result = self.cal.parse(dow, start) yr, mth, dy, hr, mn, sec, wd, yd, isdst = result[0] self.assertEqual(wd, i) self.ptc.CurrentDOWParseStyle = o1 self.ptc.DOWParseStyle = o2 if __name__ == "__main__": unittest.main() parsedatetime-2.4/tests/TestGermanLocale.py0000644000076500000240000000634712747560752021200 0ustar bearstaff00000000000000# -*- coding: utf-8 -*- """ Test parsing of simple date and times using the German locale """ from __future__ import unicode_literals import sys import time import datetime import parsedatetime as pdt from . import utils if sys.version_info < (2, 7): import unittest2 as unittest else: import unittest class test(unittest.TestCase): @utils.assertEqualWithComparator def assertExpectedResult(self, result, check, **kwargs): return utils.compareResultByTimeTuplesAndFlags(result, check, **kwargs) def setUp(self): self.ptc = pdt.Constants('de_DE', usePyICU=False) self.cal = pdt.Calendar(self.ptc) (self.yr, self.mth, self.dy, self.hr, self.mn, self.sec, self.wd, self.yd, self.isdst) = time.localtime() if self.ptc.localeID != 'de_DE': raise unittest.SkipTest( 'Locale not set to de_DE - check if PyICU is installed') def testTimes(self): start = datetime.datetime( self.yr, self.mth, self.dy, self.hr, self.mn, self.sec).timetuple() target = datetime.datetime( self.yr, self.mth, self.dy, 23, 0, 0).timetuple() self.assertExpectedResult( self.cal.parse('23:00:00', start), (target, 2)) self.assertExpectedResult( self.cal.parse('23:00', start), (target, 2)) self.assertExpectedResult( self.cal.parse('2300', start), (target, 2)) target = datetime.datetime( self.yr, self.mth, self.dy, 11, 0, 0).timetuple() self.assertExpectedResult( self.cal.parse('11:00:00', start), (target, 2)) self.assertExpectedResult( self.cal.parse('11:00', start), (target, 2)) self.assertExpectedResult( self.cal.parse('1100', start), (target, 2)) target = datetime.datetime( self.yr, self.mth, self.dy, 7, 30, 0).timetuple() self.assertExpectedResult(self.cal.parse('730', start), (target, 2)) self.assertExpectedResult(self.cal.parse('0730', start), (target, 2)) target = datetime.datetime( self.yr, self.mth, self.dy, 17, 30, 0).timetuple() self.assertExpectedResult(self.cal.parse('1730', start), (target, 2)) self.assertExpectedResult(self.cal.parse('173000', start), (target, 2)) def testDates(self): start = datetime.datetime( self.yr, self.mth, self.dy, self.hr, self.mn, self.sec).timetuple() target = datetime.datetime( 2006, 8, 25, self.hr, self.mn, self.sec).timetuple() self.assertExpectedResult( self.cal.parse('25.08.2006', start), (target, 1)) self.assertExpectedResult( self.cal.parse('25.8.06', start), (target, 1)) if self.mth > 8 or (self.mth == 8 and self.dy > 25): target = datetime.datetime( self.yr + 1, 8, 25, self.hr, self.mn, self.sec).timetuple() else: target = datetime.datetime( self.yr, 8, 25, self.hr, self.mn, self.sec).timetuple() self.assertExpectedResult( self.cal.parse('25.8', start), (target, 1)) self.assertExpectedResult( self.cal.parse('25.08', start), (target, 1)) if __name__ == "__main__": unittest.main() parsedatetime-2.4/tests/TestInc.py0000644000076500000240000001007312747560752017347 0ustar bearstaff00000000000000# -*- coding: utf-8 -*- """ Test Calendar.Inc() routine """ from __future__ import unicode_literals import sys import time import datetime import parsedatetime as pdt from . import utils if sys.version_info < (2, 7): import unittest2 as unittest else: import unittest class test(unittest.TestCase): @utils.assertEqualWithComparator def assertExpectedResult(self, result, check, **kwargs): return utils.compareResultByTimeTuplesAndFlags((result, 1), (check, 1), **kwargs) def setUp(self): self.cal = pdt.Calendar() (self.yr, self.mth, self.dy, self.hr, self.mn, self.sec, self.wd, self.yd, self.isdst) = time.localtime() def testIncMonths(self): s = datetime.datetime(2006, 1, 1, 12, 0, 0) t = datetime.datetime(2006, 2, 1, 12, 0, 0) self.assertExpectedResult( self.cal.inc(s, month=1).timetuple(), t.timetuple()) s = datetime.datetime(2006, 12, 1, 12, 0, 0) t = datetime.datetime(2007, 1, 1, 12, 0, 0) self.assertExpectedResult( self.cal.inc(s, month=1).timetuple(), t.timetuple()) # leap year, Feb 1 s = datetime.datetime(2008, 2, 1, 12, 0, 0) t = datetime.datetime(2008, 3, 1, 12, 0, 0) self.assertExpectedResult( self.cal.inc(s, month=1).timetuple(), t.timetuple()) # leap year, Feb 29 s = datetime.datetime(2008, 2, 29, 12, 0, 0) t = datetime.datetime(2008, 3, 29, 12, 0, 0) self.assertExpectedResult( self.cal.inc(s, month=1).timetuple(), t.timetuple()) s = datetime.datetime(2006, 1, 1, 12, 0, 0) t = datetime.datetime(2005, 12, 1, 12, 0, 0) self.assertExpectedResult( self.cal.inc(s, month=-1).timetuple(), t.timetuple()) # End of month Jan 31 to Feb - Febuary only has 28 days s = datetime.datetime(2006, 1, 31, 12, 0, 0) t = datetime.datetime(2006, 2, 28, 12, 0, 0) self.assertExpectedResult( self.cal.inc(s, month=1).timetuple(), t.timetuple()) # walk thru months and make sure month increment doesn't set the day # to be past the last day of the new month # think Jan transition to Feb - 31 days to 28 days for m in range(1, 11): d = self.cal.ptc.daysInMonth(m, 2006) s = datetime.datetime(2006, m, d, 12, 0, 0) if d > self.cal.ptc.daysInMonth(m + 1, 2006): d = self.cal.ptc.daysInMonth(m + 1, 2006) t = datetime.datetime(2006, m + 1, d, 12, 0, 0) self.assertExpectedResult( self.cal.inc(s, month=1).timetuple(), t.timetuple()) def testIncYears(self): s = datetime.datetime(2006, 1, 1, 12, 0, 0) t = datetime.datetime(2007, 1, 1, 12, 0, 0) self.assertExpectedResult( self.cal.inc(s, year=1).timetuple(), t.timetuple()) s = datetime.datetime(2006, 1, 1, 12, 0, 0) t = datetime.datetime(2008, 1, 1, 12, 0, 0) self.assertExpectedResult( self.cal.inc(s, year=2).timetuple(), t.timetuple()) s = datetime.datetime(2006, 12, 31, 12, 0, 0) t = datetime.datetime(2007, 12, 31, 12, 0, 0) self.assertExpectedResult( self.cal.inc(s, year=1).timetuple(), t.timetuple()) s = datetime.datetime(2006, 12, 31, 12, 0, 0) t = datetime.datetime(2005, 12, 31, 12, 0, 0) self.assertExpectedResult( self.cal.inc(s, year=-1).timetuple(), t.timetuple()) s = datetime.datetime(2008, 3, 1, 12, 0, 0) t = datetime.datetime(2009, 3, 1, 12, 0, 0) self.assertExpectedResult( self.cal.inc(s, year=1).timetuple(), t.timetuple()) s = datetime.datetime(2008, 3, 1, 12, 0, 0) t = datetime.datetime(2007, 3, 1, 12, 0, 0) self.assertExpectedResult( self.cal.inc(s, year=-1).timetuple(), t.timetuple()) if __name__ == "__main__": unittest.main() parsedatetime-2.4/tests/TestLocaleBase.py0000644000076500000240000001225113012207661020607 0ustar bearstaff00000000000000# -*- coding: utf-8 -*- """ Test parsing of simple date and times using the French locale Note: requires PyICU """ from __future__ import unicode_literals import sys import time import datetime import pytest import parsedatetime as pdt from . import utils if sys.version_info < (2, 7): import unittest2 as unittest else: import unittest class test(unittest.TestCase): @utils.assertEqualWithComparator def assertExpectedResult(self, result, check, **kwargs): return utils.compareResultByTimeTuplesAndFlags(result, check, **kwargs) def setUp(self): self.ptc = pdt.Constants('fr_FR') self.cal = pdt.Calendar(self.ptc) (self.yr, self.mth, self.dy, self.hr, self.mn, self.sec, self.wd, self.yd, self.isdst) = time.localtime() def testTimes(self): if self.ptc.localeID == 'fr_FR': start = datetime.datetime( self.yr, self.mth, self.dy, self.hr, self.mn, self.sec).timetuple() target = datetime.datetime( self.yr, self.mth, self.dy, 23, 0, 0).timetuple() self.assertExpectedResult( self.cal.parse('2300', start), (target, 2)) self.assertExpectedResult( self.cal.parse('23:00', start), (target, 2)) target = datetime.datetime( self.yr, self.mth, self.dy, 11, 0, 0).timetuple() self.assertExpectedResult( self.cal.parse('1100', start), (target, 2)) self.assertExpectedResult( self.cal.parse('11:00', start), (target, 2)) target = datetime.datetime( self.yr, self.mth, self.dy, 7, 30, 0).timetuple() self.assertExpectedResult( self.cal.parse('730', start), (target, 2)) self.assertExpectedResult( self.cal.parse('0730', start), (target, 2)) target = datetime.datetime( self.yr, self.mth, self.dy, 17, 30, 0).timetuple() self.assertExpectedResult( self.cal.parse('1730', start), (target, 2)) self.assertExpectedResult( self.cal.parse('173000', start), (target, 2)) def testDates(self): if self.ptc.localeID == 'fr_FR': start = datetime.datetime( self.yr, self.mth, self.dy, self.hr, self.mn, self.sec).timetuple() target = datetime.datetime( 2006, 8, 25, self.hr, self.mn, self.sec).timetuple() self.assertExpectedResult( self.cal.parse('25/08/2006', start), (target, 1)) self.assertExpectedResult( self.cal.parse('25/8/06', start), (target, 1)) self.assertExpectedResult( self.cal.parse('août 25, 2006', start), (target, 1)) self.assertExpectedResult( self.cal.parse('août 25 2006', start), (target, 1)) if self.mth > 8 or (self.mth == 8 and self.dy > 25): target = datetime.datetime( self.yr + 1, 8, 25, self.hr, self.mn, self.sec).timetuple() else: target = datetime.datetime( self.yr, 8, 25, self.hr, self.mn, self.sec).timetuple() self.assertExpectedResult( self.cal.parse('25/8', start), (target, 1)) self.assertExpectedResult( self.cal.parse('25/08', start), (target, 1)) def testWeekDays(self): if self.ptc.localeID == 'fr_FR': start = datetime.datetime( self.yr, self.mth, self.dy, self.hr, self.mn, self.sec).timetuple() o1 = self.ptc.CurrentDOWParseStyle o2 = self.ptc.DOWParseStyle # set it up so the current dow returns current day self.ptc.CurrentDOWParseStyle = True self.ptc.DOWParseStyle = 1 for i in range(0, 7): dow = self.ptc.shortWeekdays[i] result = self.cal.parse(dow, start) yr, mth, dy, hr, mn, sec, wd, yd, isdst = result[0] self.assertEqual(wd, i) self.ptc.CurrentDOWParseStyle = o1 self.ptc.DOWParseStyle = o2 class TestDayOffsets(test): # test how Aujourd'hui/Demain/Hier are parsed def setUp(self): super(TestDayOffsets, self).setUp() self.ptc = pdt.Constants('fr_FR', usePyICU=False) self.cal = pdt.Calendar(self.ptc) def test_dayoffsets(self): start = datetime.datetime(self.yr, self.mth, self.dy, 9) for date_string, expected_day_offset in [ ("Aujourd'hui", 0), ("aujourd'hui", 0), ("Demain", 1), ("demain", 1), ("Hier", -1), ("hier", -1), ("au jour de hui", None)]: got_dt, rc = self.cal.parseDT(date_string, start) if expected_day_offset is not None: self.assertEqual(rc, 1) target = (start + datetime.timedelta(days=expected_day_offset)) self.assertEqual(got_dt, target) else: self.assertEqual(rc, 0) if __name__ == "__main__": unittest.main() parsedatetime-2.4/tests/TestMultiple.py0000644000076500000240000000616112747560752020434 0ustar bearstaff00000000000000# -*- coding: utf-8 -*- """ Test parsing of strings with multiple chunks """ from __future__ import unicode_literals import sys import time import datetime import parsedatetime as pdt from . import utils if sys.version_info < (2, 7): import unittest2 as unittest else: import unittest class test(unittest.TestCase): @utils.assertEqualWithComparator def assertExpectedResult(self, result, check, **kwargs): return utils.compareResultByTimeTuplesAndFlags(result, check, **kwargs) def setUp(self): self.cal = pdt.Calendar() (self.yr, self.mth, self.dy, self.hr, self.mn, self.sec, self.wd, self.yd, self.isdst) = time.localtime() def testSimpleMultipleItems(self): s = datetime.datetime.now() t = self.cal.inc(s, year=3) + datetime.timedelta(days=5, weeks=2) start = s.timetuple() target = t.timetuple() self.assertExpectedResult( self.cal.parse('3 years 2 weeks 5 days', start), (target, 1)) self.assertExpectedResult( self.cal.parse('3years 2weeks 5days', start), (target, 1)) def testMultipleItemsSingleCharUnits(self): s = datetime.datetime.now() t = self.cal.inc(s, year=3) + datetime.timedelta(days=5, weeks=2) start = s.timetuple() target = t.timetuple() self.assertExpectedResult( self.cal.parse('3 y 2 w 5 d', start), (target, 1)) self.assertExpectedResult( self.cal.parse('3y 2w 5d', start), (target, 1)) t = self.cal.inc(s, year=3) + datetime.timedelta(hours=5, minutes=50) target = t.timetuple() self.assertExpectedResult( self.cal.parse('3y 5h 50m', start), (target, 3)) def testMultipleItemsWithPunctuation(self): s = datetime.datetime.now() t = self.cal.inc(s, year=3) + datetime.timedelta(days=5, weeks=2) start = s.timetuple() target = t.timetuple() self.assertExpectedResult( self.cal.parse('3 years, 2 weeks, 5 days', start), (target, 1)) self.assertExpectedResult( self.cal.parse('3 years, 2 weeks and 5 days', start), (target, 1)) self.assertExpectedResult( self.cal.parse('3y, 2w, 5d ', start), (target, 1)) def testUnixATStyle(self): s = datetime.datetime.now() t = s + datetime.timedelta(days=3) t = t.replace(hour=16, minute=0, second=0) start = s.timetuple() target = t.timetuple() self.assertExpectedResult( self.cal.parse('4pm + 3 days', start), (target, 3)) self.assertExpectedResult( self.cal.parse('4pm +3 days', start), (target, 3)) def testUnixATStyleNegative(self): s = datetime.datetime.now() t = s + datetime.timedelta(days=-3) t = t.replace(hour=16, minute=0, second=0) start = s.timetuple() target = t.timetuple() self.assertExpectedResult( self.cal.parse('4pm - 3 days', start), (target, 3)) self.assertExpectedResult( self.cal.parse('4pm -3 days', start), (target, 3)) if __name__ == "__main__": unittest.main() parsedatetime-2.4/tests/TestNlp.py0000644000076500000240000001164712753164745017376 0ustar bearstaff00000000000000# -*- coding: utf-8 -*- """ Test parsing of strings that are phrases """ from __future__ import unicode_literals import sys import time import datetime import parsedatetime as pdt from . import utils if sys.version_info < (2, 7): import unittest2 as unittest else: import unittest class test(unittest.TestCase): # a special compare function for nlp returned data @utils.assertEqualWithComparator def assertExpectedResult(self, result, check, dateOnly=False): target = result value = check if target is None and value is None: return True if (target is None and value is not None) or \ (target is not None and value is None): return False if len(target) != len(value): return False for i in range(0, len(target)): target_date = target[i][0] value_date = value[i][0] if target_date.year != value_date.year or \ target_date.month != value_date.month or \ target_date.day != value_date.day or \ target_date.hour != value_date.hour or \ target_date.minute != value_date.minute: return False if target[i][1] != value[i][1]: return False if target[i][2] != value[i][2]: return False if target[i][3] != value[i][3]: return False if target[i][4] != value[i][4]: return False return True def setUp(self): self.cal = pdt.Calendar() (self.yr, self.mth, self.dy, self.hr, self.mn, self.sec, self.wd, self.yd, self.isdst) = time.localtime() def testLongPhrase(self): # note: these tests do not need to be as dynamic as the others because # this is still based on the parse() function, so all tests of # the actual processing of the datetime value returned are # applicable to this. Here we are concerned with ensuring the # correct portions of text and their positions are extracted and # processed. start = datetime.datetime(2013, 8, 1, 21, 25, 0).timetuple() target = ((datetime.datetime(2013, 8, 5, 20, 0), 3, 17, 37, 'At 8PM on August 5th'), (datetime.datetime(2013, 8, 9, 21, 0), 3, 72, 90, 'next Friday at 9PM'), (datetime.datetime(2013, 8, 1, 21, 30, 0), 2, 120, 132, 'in 5 minutes'), (datetime.datetime(2013, 8, 8, 9, 0), 1, 173, 182, 'next week')) # positive testing self.assertExpectedResult(self.cal.nlp( "I'm so excited!! At 8PM on August 5th i'm going to fly to " "Florida. Then next Friday at 9PM i'm going to Dog n Bone! " "And in 5 minutes I'm going to eat some food! Talk to you " "next week.", start), target) target = datetime.datetime( self.yr, self.mth, self.dy, 17, 0, 0).timetuple() def testQuotes(self): # quotes should not interfere with datetime language recognition start = datetime.datetime(2013, 8, 1, 21, 25, 0).timetuple() target = self.cal.nlp( "I'm so excited!! At '8PM on August 5th' i'm going to fly to " "Florida. Then 'next Friday at 9PM' i'm going to Dog n Bone! " "And in '5 minutes' I'm going to eat some food! Talk to you " '"next week"', start) self.assertEqual(target[0][4], "At '8PM on August 5th") self.assertEqual(target[1][4], "next Friday at 9PM") self.assertEqual(target[2][4], "in '5 minutes") self.assertEqual(target[3][4], "next week") def testPrefixes(self): # nlp has special handling for on/in/at prefixes start = datetime.datetime(2013, 8, 1, 21, 25, 0).timetuple() target = self.cal.nlp("Buy a balloon on Monday", start) self.assertEqual(target[0][4], "on Monday") target = self.cal.nlp("Buy a balloon at noon", start) self.assertEqual(target[0][4], "at noon") target = self.cal.nlp("Buy a balloon in a month", start) self.assertEqual(target[0][4], "in a month") # Should no longer pull "on" off the end of balloon target = self.cal.nlp("Buy a balloon Monday", start) self.assertEqual(target[0][4], "Monday") def testFalsePositives(self): # negative testing - no matches should return None start = datetime.datetime(2013, 8, 1, 21, 25, 0).timetuple() self.assertExpectedResult(self.cal.nlp( "Next, I'm so excited!! So many things that are going to " "happen every week!!", start), None) self.assertExpectedResult(self.cal.nlp("$300", start), None) self.assertExpectedResult(self.cal.nlp("300ml", start), None) self.assertExpectedResult(self.cal.nlp("nice ass", start), None) parsedatetime-2.4/tests/TestPhrases.py0000644000076500000240000001135312747560752020245 0ustar bearstaff00000000000000# -*- coding: utf-8 -*- """ Test parsing of strings that are phrases """ from __future__ import unicode_literals import sys import time import datetime import parsedatetime as pdt from . import utils if sys.version_info < (2, 7): import unittest2 as unittest else: import unittest class test(unittest.TestCase): @utils.assertEqualWithComparator def assertExpectedResult(self, result, check, **kwargs): return utils.compareResultByTimeTuplesAndFlags(result, check, **kwargs) def setUp(self): self.cal = pdt.Calendar() (self.yr, self.mth, self.dy, self.hr, self.mn, self.sec, self.wd, self.yd, self.isdst) = time.localtime() def testPhrases(self): # # NOTE - this test will fail under certain conditions # It is building an absolute date for comparison and then # testing the parsing of relative phrases and as such will fail # if run near the midnight transition. # Thanks to Chris Petrilli for asking about it and prompting me # to create this note! # start = datetime.datetime( self.yr, self.mth, self.dy, self.hr, self.mn, self.sec).timetuple() target = datetime.datetime( self.yr, self.mth, self.dy, 16, 0, 0).timetuple() self.assertExpectedResult( self.cal.parse('flight from SFO at 4pm', start), (target, 2)) target = datetime.datetime( self.yr, self.mth, self.dy, 17, 0, 0).timetuple() self.assertExpectedResult( self.cal.parse('eod', start), (target, 2)) self.assertExpectedResult( self.cal.parse('meeting eod', start), (target, 2)) self.assertExpectedResult( self.cal.parse('eod meeting', start), (target, 2)) target = datetime.datetime( self.yr, self.mth, self.dy, 17, 0, 0) + datetime.timedelta(days=1) target = target.timetuple() self.assertExpectedResult( self.cal.parse('tomorrow eod', start), (target, 3)) self.assertExpectedResult( self.cal.parse('eod tomorrow', start), (target, 3)) def testPhraseWithDays_DOWStyle_1_False(self): s = datetime.datetime.now() # find out what day we are currently on # and determine what the next day of week is t = s + datetime.timedelta(days=1) start = s.timetuple() (yr, mth, dy, _, _, _, wd, yd, isdst) = t.timetuple() target = (yr, mth, dy, 17, 0, 0, wd, yd, isdst) d = self.wd + 1 if d > 6: d = 0 day = self.cal.ptc.Weekdays[d] self.assertExpectedResult( self.cal.parse('eod %s' % day, start), (target, 3)) # find out what day we are currently on # and determine what the previous day of week is t = s + datetime.timedelta(days=6) (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = t.timetuple() target = (yr, mth, dy, 17, 0, 0, wd, yd, isdst) d = self.wd - 1 if d < 0: d = 6 day = self.cal.ptc.Weekdays[d] self.assertExpectedResult( self.cal.parse('eod %s' % day, start), (target, 3)) def testEndOfPhrases(self): s = datetime.datetime.now() # find out what month we are currently on # set the day to 1 and then go back a day # to get the end of the current month (yr, mth, _, hr, mn, sec, _, _, _) = s.timetuple() mth += 1 if mth > 12: mth = 1 yr += 1 t = datetime.datetime( yr, mth, 1, 9, 0, 0) + datetime.timedelta(days=-1) start = s.timetuple() target = t.timetuple() self.assertExpectedResult( self.cal.parse('eom', start), (target, 1)) self.assertExpectedResult( self.cal.parse('meeting eom', start), (target, 1)) s = datetime.datetime.now() (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = s.timetuple() t = datetime.datetime(yr, 12, 31, 9, 0, 0) start = s.timetuple() target = t.timetuple() self.assertExpectedResult( self.cal.parse('eoy', start), (target, 1)) self.assertExpectedResult( self.cal.parse('meeting eoy', start), (target, 1)) def testLastPhrases(self): for day in (11, 12, 13, 14, 15, 16, 17): start = datetime.datetime(2012, 11, day, 9, 0, 0) (yr, mth, dy, _, _, _, wd, yd, isdst) = start.timetuple() n = 4 - wd if n >= 0: n -= 7 target = start + datetime.timedelta(days=n) self.assertExpectedResult( self.cal.parse('last friday', start.timetuple()), (target.timetuple(), 1), dateOnly=True) parsedatetime-2.4/tests/TestRanges.py0000644000076500000240000000774012747560752020064 0ustar bearstaff00000000000000# -*- coding: utf-8 -*- """ Test parsing of simple date and times """ from __future__ import unicode_literals import sys import time import datetime import parsedatetime as pdt from . import utils if sys.version_info < (2, 7): import unittest2 as unittest else: import unittest class test(unittest.TestCase): @utils.assertEqualWithComparator def assertExpectedResult(self, result, check, **kwargs): return utils.compareResultByTimeTupleRangesAndFlags( result, check, **kwargs) def setUp(self): self.cal = pdt.Calendar() (self.yr, self.mth, self.dy, self.hr, self.mn, self.sec, self.wd, self.yd, self.isdst) = time.localtime() def testTimes(self): start = datetime.datetime( self.yr, self.mth, self.dy, self.hr, self.mn, self.sec).timetuple() targetStart = datetime.datetime( self.yr, self.mth, self.dy, 14, 0, 0).timetuple() targetEnd = datetime.datetime( self.yr, self.mth, self.dy, 17, 30, 0).timetuple() self.assertExpectedResult(self.cal.evalRanges( "2 pm - 5:30 pm", start), (targetStart, targetEnd, 2)) self.assertExpectedResult(self.cal.evalRanges( "2pm - 5:30pm", start), (targetStart, targetEnd, 2)) self.assertExpectedResult(self.cal.evalRanges( "2:00:00 pm - 5:30:00 pm", start), (targetStart, targetEnd, 2)) self.assertExpectedResult(self.cal.evalRanges( "2 - 5:30pm", start), (targetStart, targetEnd, 2)) self.assertExpectedResult(self.cal.evalRanges( "14:00 - 17:30", start), (targetStart, targetEnd, 2)) targetStart = datetime.datetime( self.yr, self.mth, self.dy, 10, 0, 0).timetuple() targetEnd = datetime.datetime( self.yr, self.mth, self.dy, 13, 30, 0).timetuple() self.assertExpectedResult(self.cal.evalRanges( "10AM - 1:30PM", start), (targetStart, targetEnd, 2)) self.assertExpectedResult(self.cal.evalRanges( "10:00:00 am - 1:30:00 pm", start), (targetStart, targetEnd, 2)) self.assertExpectedResult(self.cal.evalRanges( "10:00 - 13:30", start), (targetStart, targetEnd, 2)) targetStart = datetime.datetime( self.yr, self.mth, self.dy, 15, 30, 0).timetuple() targetEnd = datetime.datetime( self.yr, self.mth, self.dy, 17, 0, 0).timetuple() self.assertExpectedResult( self.cal.evalRanges("today 3:30-5PM", start), (targetStart, targetEnd, 2)) def testDates(self): start = datetime.datetime( self.yr, self.mth, self.dy, self.hr, self.mn, self.sec).timetuple() targetStart = datetime.datetime( 2006, 8, 29, self.hr, self.mn, self.sec).timetuple() targetEnd = datetime.datetime( 2006, 9, 2, self.hr, self.mn, self.sec).timetuple() self.assertExpectedResult( self.cal.evalRanges("August 29, 2006 - September 2, 2006", start), (targetStart, targetEnd, 1)) self.assertExpectedResult( self.cal.evalRanges("August 29 - September 2, 2006", start), (targetStart, targetEnd, 1)) targetStart = datetime.datetime( 2006, 8, 29, self.hr, self.mn, self.sec).timetuple() targetEnd = datetime.datetime( 2006, 9, 2, self.hr, self.mn, self.sec).timetuple() self.assertExpectedResult( self.cal.evalRanges("08/29/06 - 09/02/06", start), (targetStart, targetEnd, 1)) def _testSubRanges(self): start = datetime.datetime( self.yr, self.mth, self.dy, self.hr, self.mn, self.sec).timetuple() targetStart = datetime.datetime(2006, 8, 1, 9, 0, 0).timetuple() targetEnd = datetime.datetime(2006, 8, 15, 9, 0, 0).timetuple() self.assertExpectedResult( self.cal.evalRanges("August 1-15, 2006", start), (targetStart, targetEnd, 1)) if __name__ == "__main__": unittest.main() parsedatetime-2.4/tests/TestRussianLocale.py0000644000076500000240000001201113105773676021374 0ustar bearstaff00000000000000# -*- coding: utf-8 -*- """ Test parsing of simple date and times using the Russian locale """ from __future__ import unicode_literals import sys import time import datetime import parsedatetime as pdt from . import utils if sys.version_info < (2, 7): import unittest2 as unittest else: import unittest class test(unittest.TestCase): @utils.assertEqualWithComparator def assertExpectedResult(self, result, check, **kwargs): return utils.compareResultByTimeTuplesAndFlags(result, check, **kwargs) def setUp(self): locale = 'ru_RU' self.ptc = pdt.Constants(locale, usePyICU=False) self.cal = pdt.Calendar(self.ptc) (self.yr, self.mth, self.dy, self.hr, self.mn, self.sec, self.wd, self.yd, self.isdst) = time.localtime() if self.ptc.localeID != locale: raise unittest.SkipTest( 'Locale not set to %s - check if PyICU is installed' % locale) def testTimes(self): start = datetime.datetime( self.yr, self.mth, self.dy, self.hr, self.mn, self.sec).timetuple() target = datetime.datetime( self.yr, self.mth, self.dy, 23, 0, 0).timetuple() self.assertExpectedResult( self.cal.parse('23:00:00', start), (target, 2)) self.assertExpectedResult(self.cal.parse('23:00', start), (target, 2)) self.assertExpectedResult(self.cal.parse('2300', start), (target, 2)) target = datetime.datetime( self.yr, self.mth, self.dy, 11, 0, 0).timetuple() self.assertExpectedResult( self.cal.parse('11:00:00', start), (target, 2)) self.assertExpectedResult(self.cal.parse('11:00', start), (target, 2)) self.assertExpectedResult(self.cal.parse('1100', start), (target, 2)) target = datetime.datetime( self.yr, self.mth, self.dy, 7, 30, 0).timetuple() self.assertExpectedResult(self.cal.parse('730', start), (target, 2)) self.assertExpectedResult(self.cal.parse('0730', start), (target, 2)) target = datetime.datetime( self.yr, self.mth, self.dy, 17, 30, 0).timetuple() self.assertExpectedResult(self.cal.parse('1730', start), (target, 2)) self.assertExpectedResult(self.cal.parse('173000', start), (target, 2)) def testDates(self): start = datetime.datetime( self.yr, self.mth, self.dy, self.hr, self.mn, self.sec).timetuple() target = datetime.datetime( 2006, 8, 25, self.hr, self.mn, self.sec).timetuple() self.assertExpectedResult( self.cal.parse('25.08.2006', start), (target, 1)) self.assertExpectedResult( self.cal.parse('25.8.06', start), (target, 1)) if self.mth > 8 or (self.mth == 8 and self.dy > 25): target = datetime.datetime( self.yr + 1, 8, 25, self.hr, self.mn, self.sec).timetuple() else: target = datetime.datetime( self.yr, 8, 25, self.hr, self.mn, self.sec).timetuple() self.assertExpectedResult(self.cal.parse('25.8', start), (target, 1)) self.assertExpectedResult(self.cal.parse('25.08', start), (target, 1)) def testDatesLang(self): if sys.version_info >= (3, 0): target = datetime.datetime(2006, 8, 25, 23, 5).timetuple() self.assertExpectedResult( self.cal.parse('25 августа 2006 23:05'), (target, 3)) target = datetime.datetime( 2006, 8, 25, self.hr, self.mn, self.sec).timetuple() self.assertExpectedResult( self.cal.parse('25 августа 2006'), (target, 1)) self.assertEqual(self.cal.parse('23:05')[0][3], 23) self.assertEqual(self.cal.parse('23:05')[0][4], 5) def testConjugate(self): if sys.version_info >= (3, 0): target = datetime.datetime(2006, 9, 25, 23, 5).timetuple() self.assertExpectedResult( self.cal.parse('25 сентября 2006 23:05'), (target, 3)) # self.assertExpectedResult( # self.cal.parse('25 сентябрь 2006 23:05'), (target, 3)) # does not work with travis # datetime.now() return non correct data # def testdayOffsets(self): # def get_datetime(tuple_time): # return datetime.datetime(*tuple_time[:6]).date() # # now = datetime.datetime.today().date() # # self.assertEqual( # get_datetime(self.cal.parse("вчера")[0]), # now - datetime.timedelta(days=1) # ) # self.assertEqual( # get_datetime(self.cal.parse("завтра")[0]), # now + datetime.timedelta(days=1) # ) # # self.assertEqual( # get_datetime(self.cal.parse("позавчера")[0]), # now - datetime.timedelta(days=2) # ) # # self.assertEqual( # get_datetime(self.cal.parse("послезавтра")[0]), # now + datetime.timedelta(days=2) # ) if __name__ == "__main__": unittest.main() parsedatetime-2.4/tests/TestSimpleDateTimes.py0000644000076500000240000005614613105773676021702 0ustar bearstaff00000000000000# -*- coding: utf-8 -*- """ Test parsing of simple date and times """ from __future__ import unicode_literals import sys import time import datetime import string import parsedatetime as pdt from . import utils if sys.version_info < (2, 7): import unittest2 as unittest else: import unittest class test(unittest.TestCase): @utils.assertEqualWithComparator def assertExpectedResult(self, result, check, **kwargs): return utils.compareResultByTimeTuplesAndFlags(result, check, **kwargs) def setUp(self): self.cal = pdt.Calendar() (self.yr, self.mth, self.dy, self.hr, self.mn, self.sec, self.wd, self.yd, self.isdst) = time.localtime() def testDays(self): s = datetime.datetime.now() t = s + datetime.timedelta(days=1) start = s.timetuple() target = t.timetuple() d = self.wd + 1 if d > 6: d = 0 day = self.cal.ptc.Weekdays[d] self.assertExpectedResult(self.cal.parse(day, start), (target, 1)) t = s + datetime.timedelta(days=6) target = t.timetuple() d = self.wd - 1 if d < 0: d = 6 day = self.cal.ptc.Weekdays[d] self.assertExpectedResult(self.cal.parse(day, start), (target, 1)) def testTimes(self): start = datetime.datetime( self.yr, self.mth, self.dy, self.hr, self.mn, self.sec).timetuple() target = datetime.datetime( self.yr, self.mth, self.dy, 23, 0, 0).timetuple() self.assertExpectedResult( self.cal.parse('11:00:00 PM', start), (target, 2)) self.assertExpectedResult( self.cal.parse('11:00 PM', start), (target, 2)) self.assertExpectedResult( self.cal.parse('11 PM', start), (target, 2)) self.assertExpectedResult( self.cal.parse('11PM', start), (target, 2)) self.assertExpectedResult( self.cal.parse('2300', start), (target, 2)) self.assertExpectedResult( self.cal.parse('23:00', start), (target, 2)) self.assertExpectedResult( self.cal.parse('11p', start), (target, 2)) self.assertExpectedResult( self.cal.parse('11pm', start), (target, 2)) self.assertExpectedResult( self.cal.parse('11:00:00 P.M.', start), (target, 2)) self.assertExpectedResult( self.cal.parse('11:00 P.M.', start), (target, 2)) self.assertExpectedResult( self.cal.parse('11 P.M.', start), (target, 2)) self.assertExpectedResult( self.cal.parse('11P.M.', start), (target, 2)) self.assertExpectedResult( self.cal.parse('11p.m.', start), (target, 2)) self.assertExpectedResult( self.cal.parse('11 p.m.', start), (target, 2)) self.assertExpectedResult( self.cal.parse('"11 p.m."', start), (target, 2)) target = datetime.datetime( self.yr, self.mth, self.dy, 11, 0, 0).timetuple() self.assertExpectedResult( self.cal.parse('11:00:00 AM', start), (target, 2)) self.assertExpectedResult( self.cal.parse('11:00 AM', start), (target, 2)) self.assertExpectedResult( self.cal.parse('11 AM', start), (target, 2)) self.assertExpectedResult( self.cal.parse('11AM', start), (target, 2)) self.assertExpectedResult( self.cal.parse('1100', start), (target, 2)) self.assertExpectedResult( self.cal.parse('11:00', start), (target, 2)) self.assertExpectedResult( self.cal.parse('11a', start), (target, 2)) self.assertExpectedResult( self.cal.parse('11am', start), (target, 2)) self.assertExpectedResult( self.cal.parse('11:00:00 A.M.', start), (target, 2)) self.assertExpectedResult( self.cal.parse('11:00 A.M.', start), (target, 2)) self.assertExpectedResult( self.cal.parse('11 A.M.', start), (target, 2)) self.assertExpectedResult( self.cal.parse('11A.M.', start), (target, 2)) self.assertExpectedResult( self.cal.parse('11a.m.', start), (target, 2)) self.assertExpectedResult( self.cal.parse('11 a.m.', start), (target, 2)) self.assertExpectedResult( self.cal.parse('(11 a.m.)', start), (target, 2)) target = datetime.datetime( self.yr, self.mth, self.dy, 7, 30, 0).timetuple() self.assertExpectedResult(self.cal.parse('730', start), (target, 2)) self.assertExpectedResult(self.cal.parse('0730', start), (target, 2)) self.assertExpectedResult(self.cal.parse('0730am', start), (target, 2)) target = datetime.datetime( self.yr, self.mth, self.dy, 17, 30, 0).timetuple() self.assertExpectedResult(self.cal.parse('1730', start), (target, 2)) self.assertExpectedResult(self.cal.parse('173000', start), (target, 2)) # Should not parse as a time due to prefix self.assertExpectedResult(self.cal.parse('$300', start), (start, 0)) self.assertExpectedResult(self.cal.parse('300ml', start), (start, 0)) # Should not parse as a time due to false meridian self.assertExpectedResult(self.cal.parse('3 axmx', start), (start, 0)) self.assertExpectedResult(self.cal.parse('3 pxmx', start), (start, 0)) def testDates(self): start = datetime.datetime( self.yr, self.mth, self.dy, self.hr, self.mn, self.sec).timetuple() target = datetime.datetime( 2006, 8, 25, self.hr, self.mn, self.sec).timetuple() self.assertExpectedResult( self.cal.parse('08/25/2006', start), (target, 1)) self.assertExpectedResult( self.cal.parse('08.25.2006', start), (target, 1)) self.assertExpectedResult( self.cal.parse('2006/08/25', start), (target, 1)) self.assertExpectedResult( self.cal.parse('2006/8/25', start), (target, 1)) self.assertExpectedResult( self.cal.parse('2006-08-25', start), (target, 1)) self.assertExpectedResult( self.cal.parse('8/25/06', start), (target, 1)) self.assertExpectedResult( self.cal.parse('August 25, 2006', start), (target, 1)) self.assertExpectedResult( self.cal.parse('Aug 25, 2006', start), (target, 1)) self.assertExpectedResult( self.cal.parse('Aug. 25, 2006', start), (target, 1)) self.assertExpectedResult( self.cal.parse('August 25 2006', start), (target, 1)) self.assertExpectedResult( self.cal.parse('Aug 25 2006', start), (target, 1)) self.assertExpectedResult( self.cal.parse('Aug. 25 2006', start), (target, 1)) self.assertExpectedResult( self.cal.parse('25 August 2006', start), (target, 1)) self.assertExpectedResult( self.cal.parse('25 Aug 2006', start), (target, 1)) if self.mth > 8 or (self.mth == 8 and self.dy > 25): target = datetime.datetime( self.yr + 1, 8, 25, self.hr, self.mn, self.sec).timetuple() else: target = datetime.datetime( self.yr, 8, 25, self.hr, self.mn, self.sec).timetuple() self.assertExpectedResult( self.cal.parse('8/25', start), (target, 1)) self.assertExpectedResult( self.cal.parse('8.25', start), (target, 1)) self.assertExpectedResult( self.cal.parse('08/25', start), (target, 1)) self.assertExpectedResult( self.cal.parse('August 25', start), (target, 1)) self.assertExpectedResult( self.cal.parse('Aug 25', start), (target, 1)) self.assertExpectedResult( self.cal.parse('Aug. 25', start), (target, 1)) self.assertExpectedResult( self.cal.parse('"8.25"', start), (target, 1)) self.assertExpectedResult( self.cal.parse('(8.25)', start), (target, 1)) # Should not parse as dates self.assertExpectedResult(self.cal.parse('$1.23', start), (start, 0)) self.assertExpectedResult(self.cal.parse('$12.34', start), (start, 0)) # added test to ensure 4-digit year is recognized in the absence of day target = datetime.datetime( 2013, 8, 1, self.hr, self.mn, self.sec).timetuple() self.assertExpectedResult( self.cal.parse('Aug. 2013', start), (target, 1)) self.assertExpectedResult( self.cal.parse('Aug 2013', start), (target, 1)) def testLeapDays(self): start = datetime.datetime( self.yr, self.mth, self.dy, self.hr, self.mn, self.sec).timetuple() target = datetime.datetime( 2000, 2, 29, self.hr, self.mn, self.sec).timetuple() self.assertExpectedResult( self.cal.parse('02/29/2000', start), (target, 1)) target = datetime.datetime( 2004, 2, 29, self.hr, self.mn, self.sec).timetuple() self.assertExpectedResult( self.cal.parse('02/29/2004', start), (target, 1)) target = datetime.datetime( 2008, 2, 29, self.hr, self.mn, self.sec).timetuple() self.assertExpectedResult( self.cal.parse('02/29/2008', start), (target, 1)) target = datetime.datetime( 2012, 2, 29, self.hr, self.mn, self.sec).timetuple() self.assertExpectedResult( self.cal.parse('02/29/2012', start), (target, 1)) dNormal = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) dLeap = (31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) for i in range(1, 12): self.assertTrue(self.cal.ptc.daysInMonth(i, 1999), dNormal[i - 1]) self.assertTrue(self.cal.ptc.daysInMonth(i, 2000), dLeap[i - 1]) self.assertTrue(self.cal.ptc.daysInMonth(i, 2001), dNormal[i - 1]) self.assertTrue(self.cal.ptc.daysInMonth(i, 2002), dNormal[i - 1]) self.assertTrue(self.cal.ptc.daysInMonth(i, 2003), dNormal[i - 1]) self.assertTrue(self.cal.ptc.daysInMonth(i, 2004), dLeap[i - 1]) self.assertTrue(self.cal.ptc.daysInMonth(i, 2005), dNormal[i - 1]) def testDaySuffixes(self): start = datetime.datetime( self.yr, self.mth, self.dy, self.hr, self.mn, self.sec).timetuple() target = datetime.datetime( 2008, 8, 22, self.hr, self.mn, self.sec).timetuple() self.assertExpectedResult( self.cal.parse('August 22nd, 2008', start), (target, 1)) self.assertExpectedResult( self.cal.parse('Aug 22nd, 2008', start), (target, 1)) self.assertExpectedResult( self.cal.parse('Aug. 22nd, 2008', start), (target, 1)) self.assertExpectedResult( self.cal.parse('August 22nd 2008', start), (target, 1)) self.assertExpectedResult( self.cal.parse('Aug 22nd 2008', start), (target, 1)) self.assertExpectedResult( self.cal.parse('Aug. 22nd 2008', start), (target, 1)) self.assertExpectedResult( self.cal.parse('22nd August 2008', start), (target, 1)) self.assertExpectedResult( self.cal.parse('22nd Aug 2008', start), (target, 1)) target = datetime.datetime( 1949, 12, 31, self.hr, self.mn, self.sec).timetuple() self.assertExpectedResult( self.cal.parse('December 31st, 1949', start), (target, 1)) self.assertExpectedResult( self.cal.parse('Dec 31st, 1949', start), (target, 1)) self.assertExpectedResult( self.cal.parse('December 31st 1949', start), (target, 1)) self.assertExpectedResult( self.cal.parse('Dec 31st 1949', start), (target, 1)) self.assertExpectedResult( self.cal.parse('31st December 1949', start), (target, 1)) self.assertExpectedResult( self.cal.parse('31st Dec 1949', start), (target, 1)) target = datetime.datetime( 2008, 8, 23, self.hr, self.mn, self.sec).timetuple() self.assertExpectedResult( self.cal.parse('August 23rd, 2008', start), (target, 1)) self.assertExpectedResult( self.cal.parse('Aug 23rd, 2008', start), (target, 1)) self.assertExpectedResult( self.cal.parse('Aug. 23rd, 2008', start), (target, 1)) self.assertExpectedResult( self.cal.parse('August 23rd 2008', start), (target, 1)) self.assertExpectedResult( self.cal.parse('Aug 23rd 2008', start), (target, 1)) self.assertExpectedResult( self.cal.parse('Aug. 23rd 2008', start), (target, 1)) target = datetime.datetime( 2008, 8, 25, self.hr, self.mn, self.sec).timetuple() self.assertExpectedResult( self.cal.parse('August 25th, 2008', start), (target, 1)) self.assertExpectedResult( self.cal.parse('Aug 25th, 2008', start), (target, 1)) self.assertExpectedResult( self.cal.parse('Aug. 25th, 2008', start), (target, 1)) self.assertExpectedResult( self.cal.parse('August 25th 2008', start), (target, 1)) self.assertExpectedResult( self.cal.parse('Aug 25th 2008', start), (target, 1)) self.assertExpectedResult( self.cal.parse('Aug. 25th 2008', start), (target, 1)) def testSpecialTimes(self): start = datetime.datetime( self.yr, self.mth, self.dy, self.hr, self.mn, self.sec).timetuple() target = datetime.datetime( self.yr, self.mth, self.dy, 6, 0, 0).timetuple() self.assertExpectedResult( self.cal.parse('morning', start), (target, 2)) target = datetime.datetime( self.yr, self.mth, self.dy, 8, 0, 0).timetuple() self.assertExpectedResult( self.cal.parse('breakfast', start), (target, 2)) target = datetime.datetime( self.yr, self.mth, self.dy, 12, 0, 0).timetuple() self.assertExpectedResult(self.cal.parse('lunch', start), (target, 2)) target = datetime.datetime( self.yr, self.mth, self.dy, 13, 0, 0).timetuple() self.assertExpectedResult( self.cal.parse('afternoon', start), (target, 2)) target = datetime.datetime( self.yr, self.mth, self.dy, 18, 0, 0).timetuple() self.assertExpectedResult( self.cal.parse('evening', start), (target, 2)) target = datetime.datetime( self.yr, self.mth, self.dy, 19, 0, 0).timetuple() self.assertExpectedResult(self.cal.parse('dinner', start), (target, 2)) target = datetime.datetime( self.yr, self.mth, self.dy, 21, 0, 0).timetuple() self.assertExpectedResult( self.cal.parse('night', start), (target, 2)) self.assertExpectedResult( self.cal.parse('tonight', start), (target, 2)) def testMidnight(self): start = datetime.datetime( self.yr, self.mth, self.dy, self.hr, self.mn, self.sec).timetuple() target = datetime.datetime( self.yr, self.mth, self.dy, 0, 0, 0).timetuple() self.assertExpectedResult( self.cal.parse('midnight', start), (target, 2)) self.assertExpectedResult( self.cal.parse('12:00:00 AM', start), (target, 2)) self.assertExpectedResult( self.cal.parse('12:00 AM', start), (target, 2)) self.assertExpectedResult( self.cal.parse('12 AM', start), (target, 2)) self.assertExpectedResult( self.cal.parse('12AM', start), (target, 2)) self.assertExpectedResult( self.cal.parse('12am', start), (target, 2)) self.assertExpectedResult( self.cal.parse('12a', start), (target, 2)) self.assertExpectedResult( self.cal.parse('0000', start), (target, 2)) self.assertExpectedResult( self.cal.parse('00:00', start), (target, 2)) self.assertExpectedResult( self.cal.parse('12:00:00 A.M.', start), (target, 2)) self.assertExpectedResult( self.cal.parse('12:00 A.M.', start), (target, 2)) self.assertExpectedResult( self.cal.parse('12 A.M.', start), (target, 2)) self.assertExpectedResult( self.cal.parse('12A.M.', start), (target, 2)) self.assertExpectedResult( self.cal.parse('12a.m.', start), (target, 2)) def testNoon(self): start = datetime.datetime( self.yr, self.mth, self.dy, self.hr, self.mn, self.sec).timetuple() target = datetime.datetime( self.yr, self.mth, self.dy, 12, 0, 0).timetuple() self.assertExpectedResult( self.cal.parse('noon', start), (target, 2)) self.assertExpectedResult( self.cal.parse('12:00:00 PM', start), (target, 2)) self.assertExpectedResult( self.cal.parse('12:00 PM', start), (target, 2)) self.assertExpectedResult( self.cal.parse('12 PM', start), (target, 2)) self.assertExpectedResult( self.cal.parse('12PM', start), (target, 2)) self.assertExpectedResult( self.cal.parse('12pm', start), (target, 2)) self.assertExpectedResult( self.cal.parse('12p', start), (target, 2)) self.assertExpectedResult( self.cal.parse('1200', start), (target, 2)) self.assertExpectedResult( self.cal.parse('12:00', start), (target, 2)) self.assertExpectedResult( self.cal.parse('12:00:00 P.M.', start), (target, 2)) self.assertExpectedResult( self.cal.parse('12:00 P.M.', start), (target, 2)) self.assertExpectedResult( self.cal.parse('12 P.M.', start), (target, 2)) self.assertExpectedResult( self.cal.parse('12P.M.', start), (target, 2)) self.assertExpectedResult( self.cal.parse('12p.m.', start), (target, 2)) def testDaysOfWeek(self): start = datetime.datetime( 2014, 10, 25, self.hr, self.mn, self.sec).timetuple() target = datetime.datetime( 2014, 10, 26, self.hr, self.mn, self.sec).timetuple() self.assertExpectedResult( self.cal.parse('sunday', start), (target, 1)) self.assertExpectedResult( self.cal.parse('sun', start), (target, 1)) target = datetime.datetime( 2014, 10, 27, self.hr, self.mn, self.sec).timetuple() self.assertExpectedResult( self.cal.parse('Monday', start), (target, 1)) self.assertExpectedResult( self.cal.parse('mon', start), (target, 1)) target = datetime.datetime( 2014, 10, 28, self.hr, self.mn, self.sec).timetuple() self.assertExpectedResult( self.cal.parse('tuesday', start), (target, 1)) self.assertExpectedResult( self.cal.parse('tues', start), (target, 1)) target = datetime.datetime( 2014, 10, 29, self.hr, self.mn, self.sec).timetuple() self.assertExpectedResult( self.cal.parse('wednesday', start), (target, 1)) self.assertExpectedResult( self.cal.parse('wed', start), (target, 1)) target = datetime.datetime( 2014, 10, 30, self.hr, self.mn, self.sec).timetuple() self.assertExpectedResult( self.cal.parse('thursday', start), (target, 1)) self.assertExpectedResult( self.cal.parse('thu', start), (target, 1)) target = datetime.datetime( 2014, 10, 31, self.hr, self.mn, self.sec).timetuple() self.assertExpectedResult( self.cal.parse('friday', start), (target, 1)) self.assertExpectedResult( self.cal.parse('fri', start), (target, 1)) target = datetime.datetime( 2014, 11, 1, self.hr, self.mn, self.sec).timetuple() self.assertExpectedResult( self.cal.parse('saturday', start), (target, 1)) self.assertExpectedResult( self.cal.parse('sat', start), (target, 1)) def testWordBoundaries(self): # Ensure that keywords appearing at the start of a word are not parsed # as if they were standalone keywords. For example, "10 dogs" should # not be interpreted the same as "10 d" start = datetime.datetime( self.yr, self.mth, self.dy, self.hr, self.mn, self.sec).timetuple() target = datetime.datetime.now().timetuple() keywords = [] loc = self.cal.ptc.locale def flattenWeekdays(wds): return sum([wd.split('|') for wd in wds], []) # Test all known keywords for the locale keywords.extend(loc.meridian) keywords.extend(flattenWeekdays(loc.Weekdays)) keywords.extend(flattenWeekdays(loc.shortWeekdays)) keywords.extend(loc.Months) keywords.extend(loc.shortMonths) keywords.extend(loc.numbers.keys()) keywords.extend(loc.Modifiers.keys()) keywords.extend(loc.dayOffsets.keys()) keywords.extend(loc.re_sources.keys()) keywords.extend(loc.small.keys()) keywords.extend(loc.magnitude.keys()) for units in loc.units.values(): keywords.extend(units) # Finally, test all lowercase letters to be particularly thorough - it # would be very difficult to track down bugs due to single letters. keywords.extend(list(string.ascii_lowercase)) for keyword in keywords: phrase = '1 %sfoo' % keyword self.assertExpectedResult( self.cal.parse(phrase, start), (target, 0), 'Result does not match target value: %s' % repr(phrase)) def testYearParseStyle(self): config = pdt.Constants() config.YearParseStyle = 0 calendar = pdt.Calendar(config) start = datetime.datetime(self.yr, self.mth, self.dy, self.hr, self.mn, self.sec).timetuple() target = datetime.datetime(self.yr, 7, 28, self.hr, self.mn, self.sec).timetuple() self.assertExpectedResult(calendar.parse('7/28', start), (target, 1)) # def testMonths(self): # start = datetime.datetime( # self.yr, self.mth, self.dy, # self.hr, self.mn, self.sec).timetuple() # target = datetime.datetime( # self.yr, self.mth, self.dy, 12, 0, 0).timetuple() # self.assertExpectedResult(self.cal.parse('jun', start), (target, 2)) # self.assertExpectedResult( # self.cal.parse('12:00:00 PM', start), (target, 2)) # self.assertExpectedResult( # self.cal.parse('12:00 PM', start), (target, 2)) # self.assertExpectedResult(self.cal.parse('12 PM', start), # (target, 2)) # self.assertExpectedResult(self.cal.parse('12PM', start), (target, 2)) # self.assertExpectedResult(self.cal.parse('12pm', start), (target, 2)) # self.assertExpectedResult(self.cal.parse('12p', start), (target, 2)) # self.assertExpectedResult(self.cal.parse('1200', start), (target, 2)) # self.assertExpectedResult(self.cal.parse('12:00', start), # (target, 2)) if __name__ == "__main__": unittest.main() parsedatetime-2.4/tests/TestSimpleOffsets.py0000644000076500000240000002407013105773676021423 0ustar bearstaff00000000000000# -*- coding: utf-8 -*- """ Test parsing of 'simple' offsets """ from __future__ import unicode_literals import sys import time import datetime import calendar import parsedatetime as pdt from . import utils if sys.version_info < (2, 7): import unittest2 as unittest else: import unittest def _truncateResult(result, trunc_seconds=True, trunc_hours=False): try: dt, flag = result except ValueError: # wtf?! return result if trunc_seconds: dt = dt[:5] + (0,) * 4 if trunc_hours: dt = dt[:3] + (0,) * 6 return dt, flag _tr = _truncateResult class test(unittest.TestCase): @utils.assertEqualWithComparator def assertExpectedResult(self, result, check, **kwargs): return utils.compareResultByTimeTuplesAndFlags(result, check, **kwargs) def setUp(self): self.cal = pdt.Calendar() (self.yr, self.mth, self.dy, self.hr, self.mn, self.sec, self.wd, self.yd, self.isdst) = time.localtime() def testNow(self): s = datetime.datetime.now() start = s.timetuple() target = s.timetuple() self.assertExpectedResult(self.cal.parse('now', start), (target, 2)) def testRightNow(self): s = datetime.datetime.now() start = s.timetuple() target = s.timetuple() self.assertExpectedResult(self.cal.parse('right now', start), (target, 2)) def testOffsetFromDayOfWeek(self): self.cal.ptc.StartTimeFromSourceTime = True s = datetime.datetime(2016, 2, 16) # a Tuesday t = datetime.datetime(2016, 2, 18) # Thursday of the same week tPlusOffset = t + datetime.timedelta(hours=1) start = s.timetuple() target = t.timetuple() targetPlusOffset = tPlusOffset.timetuple() self.assertExpectedResult( self.cal.parse('Thursday', start), (target, 1)) self.assertExpectedResult( self.cal.parse('one hour from Thursday', start), (targetPlusOffset, 3)) def testOffsetBeforeDayOfWeek(self): self.cal.ptc.StartTimeFromSourceTime = True s = datetime.datetime(2016, 2, 16) # a Tuesday t = datetime.datetime(2016, 2, 18) # Thursday of the same week tPlusOffset = t + datetime.timedelta(hours=-1) start = s.timetuple() target = t.timetuple() targetPlusOffset = tPlusOffset.timetuple() self.assertExpectedResult( self.cal.parse('Thursday', start), (target, 1)) self.assertExpectedResult( self.cal.parse('one hour before Thursday', start), (targetPlusOffset, 3)) def testMinutesFromNow(self): s = datetime.datetime.now() t = s + datetime.timedelta(minutes=5) start = s.timetuple() target = t.timetuple() self.assertExpectedResult( self.cal.parse('5 minutes from now', start), (target, 2)) self.assertExpectedResult( self.cal.parse('5 min from now', start), (target, 2)) self.assertExpectedResult( self.cal.parse('5m from now', start), (target, 2)) self.assertExpectedResult( self.cal.parse('in 5 minutes', start), (target, 2)) self.assertExpectedResult( self.cal.parse('in 5 min', start), (target, 2)) self.assertExpectedResult( self.cal.parse('5 minutes', start), (target, 2)) self.assertExpectedResult(self.cal.parse('5 min', start), (target, 2)) self.assertExpectedResult(self.cal.parse('5m', start), (target, 2)) self.assertExpectedResult( self.cal.parse('five minutes from now', start), (target, 2)) self.assertExpectedResult( self.cal.parse('five min from now', start), (target, 2)) self.assertExpectedResult( self.cal.parse('in five minutes', start), (target, 2)) self.assertExpectedResult( self.cal.parse('in five min', start), (target, 2)) self.assertExpectedResult( self.cal.parse('five minutes', start), (target, 2)) self.assertExpectedResult( self.cal.parse('five min', start), (target, 2)) def testMinutesBeforeNow(self): s = datetime.datetime.now() t = s + datetime.timedelta(minutes=-5) start = s.timetuple() target = t.timetuple() self.assertExpectedResult( self.cal.parse('5 minutes before now', start), (target, 2)) self.assertExpectedResult( self.cal.parse('5 min before now', start), (target, 2)) self.assertExpectedResult( self.cal.parse('5m before now', start), (target, 2)) self.assertExpectedResult( self.cal.parse('5 minutes ago', start), (target, 2)) self.assertExpectedResult( self.cal.parse('five minutes before now', start), (target, 2)) self.assertExpectedResult( self.cal.parse('five min before now', start), (target, 2)) def testWeekFromNow(self): s = datetime.datetime.now() t = s + datetime.timedelta(weeks=1) start = s.timetuple() target = t.timetuple() self.assertExpectedResult( self.cal.parse('in 1 week', start), (target, 1)) self.assertExpectedResult( self.cal.parse('1 week from now', start), (target, 3)) self.assertExpectedResult( self.cal.parse('in one week', start), (target, 1)) self.assertExpectedResult( self.cal.parse('one week from now', start), (target, 3)) self.assertExpectedResult( self.cal.parse('in a week', start), (target, 1)) self.assertExpectedResult( self.cal.parse('a week from now', start), (target, 3)) self.assertExpectedResult( self.cal.parse('in 7 days', start), (target, 1)) self.assertExpectedResult( self.cal.parse('7 days from now', start), (target, 3)) self.assertExpectedResult( self.cal.parse('in seven days', start), (target, 1)) self.assertExpectedResult( self.cal.parse('seven days from now', start), (target, 3)) self.assertEqual(_tr(self.cal.parse('next week', start), trunc_hours=True), _tr((target, 1), trunc_hours=True)) def testNextWeekDay(self): start = datetime.datetime.now() target = start + datetime.timedelta(days=4 + 7 - start.weekday()) start = start.timetuple() target = target.timetuple() self.assertExpectedResult(self.cal.parse('next friday', start), (target, 1), dateOnly=True) self.assertExpectedResult(self.cal.parse('next friday?', start), (target, 1), dateOnly=True) self.cal.ptc.StartTimeFromSourceTime = True self.assertExpectedResult(self.cal.parse('next friday', start), (target, 1)) def testNextWeekDayWithTime(self): start = datetime.datetime.now() target = start + datetime.timedelta(days=4 + 7 - start.weekday()) target = target.replace(hour=13, minute=0, second=0) target = target.timetuple() self.assertExpectedResult(self.cal.parse('next friday at 1pm', start), (target, 3)) self.assertExpectedResult(self.cal.parse('1pm next friday', start), (target, 3)) target = start + datetime.timedelta(days=4 - start.weekday()) target = target.replace(hour=13, minute=0, second=0) target = target.timetuple() self.assertExpectedResult(self.cal.parse('1pm this friday', start), (target, 3)) def testWeekBeforeNow(self): s = datetime.datetime.now() t = s + datetime.timedelta(weeks=-1) start = s.timetuple() target = t.timetuple() self.assertEqual(_tr(self.cal.parse('1 week before now', start)), _tr((target, 3))) self.assertEqual(_tr(self.cal.parse('one week before now', start)), _tr((target, 3))) self.assertEqual(_tr(self.cal.parse('a week before now', start)), _tr((target, 3))) self.assertEqual(_tr(self.cal.parse('7 days before now', start)), _tr((target, 3))) self.assertEqual(_tr(self.cal.parse('seven days before now', start)), _tr((target, 3))) self.assertEqual(_tr(self.cal.parse('1 week ago', start)), _tr((target, 1))) self.assertEqual(_tr(self.cal.parse('a week ago', start)), _tr((target, 1))) self.assertEqual(_tr(self.cal.parse('last week', start), trunc_hours=True), _tr((target, 1), trunc_hours=True)) def testNextMonth(self): s = (datetime.datetime(self.yr, self.mth, self.dy, self.hr, self.mn, self.sec) + datetime.timedelta(days=1)) t = self.cal.inc(s, year=1) start = s.timetuple() target = t.timetuple() phrase = 'next %s %s' % (calendar.month_name[t.month], t.day) self.assertEqual(_tr(self.cal.parse(phrase, start)), _tr((target, 1))) def testSpecials(self): s = datetime.datetime.now() t = datetime.datetime( self.yr, self.mth, self.dy, 9, 0, 0) + datetime.timedelta(days=1) start = s.timetuple() target = t.timetuple() self.assertExpectedResult( self.cal.parse('tomorrow', start), (target, 1)) self.assertExpectedResult( self.cal.parse('next day', start), (target, 1)) t = datetime.datetime( self.yr, self.mth, self.dy, 9, 0, 0) + datetime.timedelta(days=-1) target = t.timetuple() self.assertExpectedResult( self.cal.parse('yesterday', start), (target, 1)) t = datetime.datetime(self.yr, self.mth, self.dy, 9, 0, 0) target = t.timetuple() self.assertExpectedResult(self.cal.parse('today', start), (target, 1)) if __name__ == "__main__": unittest.main() parsedatetime-2.4/tests/TestSimpleOffsetsHours.py0000644000076500000240000001021312747560752022436 0ustar bearstaff00000000000000# -*- coding: utf-8 -*- """ Test parsing of 'simple' offsets """ from __future__ import unicode_literals import sys import time import datetime import parsedatetime as pdt from . import utils if sys.version_info < (2, 7): import unittest2 as unittest else: import unittest class test(unittest.TestCase): @utils.assertEqualWithComparator def assertExpectedResult(self, result, check, **kwargs): return utils.compareResultByTimeTuplesAndFlags(result, check, **kwargs) def setUp(self): self.cal = pdt.Calendar() (self.yr, self.mth, self.dy, self.hr, self.mn, self.sec, self.wd, self.yd, self.isdst) = time.localtime() def testHoursFromNow(self): s = datetime.datetime.now() t = s + datetime.timedelta(hours=5) start = s.timetuple() target = t.timetuple() self.assertExpectedResult( self.cal.parse('5 hours from now', start), (target, 2)) self.assertExpectedResult( self.cal.parse('5 hour from now', start), (target, 2)) self.assertExpectedResult( self.cal.parse('5 hr from now', start), (target, 2)) self.assertExpectedResult( self.cal.parse('in 5 hours', start), (target, 2)) self.assertExpectedResult( self.cal.parse('in 5 hour', start), (target, 2)) self.assertExpectedResult( self.cal.parse('5 hours', start), (target, 2)) self.assertExpectedResult(self.cal.parse('5 hr', start), (target, 2)) self.assertExpectedResult(self.cal.parse('5h', start), (target, 2)) self.assertExpectedResult( self.cal.parse('five hours from now', start), (target, 2)) self.assertExpectedResult( self.cal.parse('five hour from now', start), (target, 2)) self.assertExpectedResult( self.cal.parse('five hr from now', start), (target, 2)) self.assertExpectedResult( self.cal.parse('in five hours', start), (target, 2)) self.assertExpectedResult( self.cal.parse('in five hour', start), (target, 2)) self.assertExpectedResult( self.cal.parse('five hours', start), (target, 2)) self.assertExpectedResult( self.cal.parse('five hr', start), (target, 2)) # Test "an" t = s + datetime.timedelta(hours=1) target = t.timetuple() self.assertExpectedResult( self.cal.parse('an hour from now', start), (target, 2)) self.assertExpectedResult( self.cal.parse('in an hour', start), (target, 2)) self.assertExpectedResult( self.cal.parse('an hour', start), (target, 2)) self.assertExpectedResult(self.cal.parse('an hr', start), (target, 2)) self.assertExpectedResult(self.cal.parse('an h', start), (target, 2)) # No match, should require a word boundary self.assertExpectedResult(self.cal.parse('anhour', start), (start, 0)) self.assertExpectedResult( self.cal.parse('an hamburger', start), (start, 0)) def testHoursBeforeNow(self): s = datetime.datetime.now() t = s + datetime.timedelta(hours=-5) start = s.timetuple() target = t.timetuple() self.assertExpectedResult( self.cal.parse('5 hours before now', start), (target, 2)) self.assertExpectedResult( self.cal.parse('5 hr before now', start), (target, 2)) self.assertExpectedResult( self.cal.parse('5h before now', start), (target, 2)) self.assertExpectedResult( self.cal.parse('five hours before now', start), (target, 2)) self.assertExpectedResult( self.cal.parse('five hr before now', start), (target, 2)) # Test "an" t = s + datetime.timedelta(hours=-1) target = t.timetuple() self.assertExpectedResult( self.cal.parse('an hour before now', start), (target, 2)) self.assertExpectedResult( self.cal.parse('an hr before now', start), (target, 2)) self.assertExpectedResult( self.cal.parse('an h before now', start), (target, 2)) if __name__ == "__main__": unittest.main() parsedatetime-2.4/tests/TestSimpleOffsetsNoon.py0000644000076500000240000000607713105773676022264 0ustar bearstaff00000000000000# -*- coding: utf-8 -*- """ Test parsing of 'simple' offsets """ from __future__ import unicode_literals import sys import time import datetime import parsedatetime as pdt from . import utils if sys.version_info < (2, 7): import unittest2 as unittest else: import unittest class test(unittest.TestCase): @utils.assertEqualWithComparator def assertExpectedResult(self, result, check, **kwargs): return utils.compareResultByTimeTuplesAndFlags(result, check, **kwargs) def setUp(self): self.cal = pdt.Calendar() (self.yr, self.mth, self.dy, self.hr, self.mn, self.sec, self.wd, self.yd, self.isdst) = time.localtime() def testOffsetAfterNoon(self): s = datetime.datetime(self.yr, self.mth, self.dy, 10, 0, 0) t = datetime.datetime( self.yr, self.mth, self.dy, 12, 0, 0) + datetime.timedelta(hours=5) start = s.timetuple() target = t.timetuple() self.assertExpectedResult( self.cal.parse('5 hours after 12pm', start), (target, 2)) self.assertExpectedResult( self.cal.parse('five hours after 12pm', start), (target, 2)) self.assertExpectedResult( self.cal.parse('5 hours after 12 pm', start), (target, 2)) self.assertExpectedResult( self.cal.parse('5 hours after 12:00pm', start), (target, 2)) self.assertExpectedResult( self.cal.parse('5 hours after 12:00 pm', start), (target, 2)) self.assertExpectedResult( self.cal.parse('5 hours after noon', start), (target, 2)) self.assertExpectedResult( self.cal.parse('5 hours from noon', start), (target, 2)) def testOffsetBeforeNoon(self): s = datetime.datetime.now() t = (datetime.datetime(self.yr, self.mth, self.dy, 12, 0, 0) + datetime.timedelta(hours=-5)) start = s.timetuple() target = t.timetuple() self.assertExpectedResult( self.cal.parse('5 hours before noon', start), (target, 2)) self.assertExpectedResult( self.cal.parse('5 hours before 12pm', start), (target, 2)) self.assertExpectedResult( self.cal.parse('five hours before 12pm', start), (target, 2)) self.assertExpectedResult( self.cal.parse('5 hours before 12 pm', start), (target, 2)) self.assertExpectedResult( self.cal.parse('5 hours before 12:00pm', start), (target, 2)) self.assertExpectedResult( self.cal.parse('5 hours before 12:00 pm', start), (target, 2)) def testOffsetBeforeModifiedNoon(self): # A contrived test of two modifiers applied to noon - offset by # -5 from the following day (-5 + 24) s = datetime.datetime.now() t = (datetime.datetime(self.yr, self.mth, self.dy, 12, 0, 0) + datetime.timedelta(hours=-5 + 24)) start = s.timetuple() target = t.timetuple() self.assertExpectedResult( self.cal.parse('5 hours before next noon', start), (target, 2)) if __name__ == "__main__": unittest.main() parsedatetime-2.4/tests/TestStartTimeFromSourceTime.py0000644000076500000240000000370112747560752023376 0ustar bearstaff00000000000000# -*- coding: utf-8 -*- """ Test parsing of strings that are phrases with the ptc.StartTimeFromSourceTime flag set to True """ from __future__ import unicode_literals import sys import time import datetime import parsedatetime as pdt from . import utils if sys.version_info < (2, 7): import unittest2 as unittest else: import unittest class test(unittest.TestCase): @utils.assertEqualWithComparator def assertExpectedResult(self, result, check, **kwargs): return utils.compareResultByTimeTuplesAndFlags(result, check, **kwargs) def setUp(self): self.cal = pdt.Calendar() self.cal.ptc.StartTimeFromSourceTime = True (self.yr, self.mth, self.dy, self.hr, self.mn, self.sec, self.wd, self.yd, self.isdst) = time.localtime() def testEndOfPhrases(self): s = datetime.datetime.now() # find out what month we are currently on # set the day to 1 and then go back a day # to get the end of the current month (yr, mth, dy, hr, mn, sec, _, _, _) = s.timetuple() s = datetime.datetime(yr, mth, dy, 13, 14, 15) mth += 1 if mth > 12: mth = 1 yr += 1 t = datetime.datetime( yr, mth, 1, 13, 14, 15) + datetime.timedelta(days=-1) start = s.timetuple() target = t.timetuple() self.assertExpectedResult(self.cal.parse('eom', start), (target, 1)) self.assertExpectedResult( self.cal.parse('meeting eom', start), (target, 1)) s = datetime.datetime.now() (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = s.timetuple() s = datetime.datetime(yr, mth, 1, 13, 14, 15) t = datetime.datetime(yr, 12, 31, 13, 14, 15) start = s.timetuple() target = t.timetuple() self.assertExpectedResult(self.cal.parse('eoy', start), (target, 1)) self.assertExpectedResult( self.cal.parse('meeting eoy', start), (target, 1)) parsedatetime-2.4/tests/TestUnits.py0000644000076500000240000001474112747560752017746 0ustar bearstaff00000000000000# -*- coding: utf-8 -*- """ Test parsing of units """ from __future__ import unicode_literals import sys import time import datetime import parsedatetime as pdt from . import utils if sys.version_info < (2, 7): import unittest2 as unittest else: import unittest class test(unittest.TestCase): @utils.assertEqualWithComparator def assertExpectedResult(self, result, check, **kwargs): return utils.compareResultByTimeTuplesAndFlags(result, check, **kwargs) def setUp(self): self.cal = pdt.Calendar() (self.yr, self.mth, self.dy, self.hr, self.mn, self.sec, self.wd, self.yd, self.isdst) = time.localtime() def testMinutes(self): s = datetime.datetime.now() t = s + datetime.timedelta(minutes=1) h = s - datetime.timedelta(minutes=1) start = s.timetuple() target = t.timetuple() history = h.timetuple() self.assertExpectedResult( self.cal.parse('1 minutes', start), (target, 2)) self.assertExpectedResult( self.cal.parse('1 minute', start), (target, 2)) self.assertExpectedResult( self.cal.parse('1 min', start), (target, 2)) self.assertExpectedResult( self.cal.parse('1min', start), (target, 2)) self.assertExpectedResult( self.cal.parse('1 m', start), (target, 2)) self.assertExpectedResult( self.cal.parse('1m', start), (target, 2)) self.assertExpectedResult( self.cal.parse('1 minutes ago', start), (history, 2)) self.assertExpectedResult( self.cal.parse('1 minute ago', start), (history, 2)) def testHours(self): s = datetime.datetime.now() t = s + datetime.timedelta(hours=1) h = s - datetime.timedelta(hours=1) start = s.timetuple() target = t.timetuple() history = h.timetuple() self.assertExpectedResult( self.cal.parse('1 hour', start), (target, 2)) self.assertExpectedResult( self.cal.parse('1 hours', start), (target, 2)) self.assertExpectedResult( self.cal.parse('1 hr', start), (target, 2)) self.assertExpectedResult( self.cal.parse('1 hour ago', start), (history, 2)) self.assertExpectedResult( self.cal.parse('1 hours ago', start), (history, 2)) def testDays(self): s = datetime.datetime.now() t = s + datetime.timedelta(days=1) start = s.timetuple() target = t.timetuple() self.assertExpectedResult(self.cal.parse('1 day', start), (target, 1)) self.assertExpectedResult(self.cal.parse('1 days', start), (target, 1)) self.assertExpectedResult(self.cal.parse('1days', start), (target, 1)) self.assertExpectedResult(self.cal.parse('1 dy', start), (target, 1)) self.assertExpectedResult(self.cal.parse('1 d', start), (target, 1)) def testNegativeDays(self): s = datetime.datetime.now() t = s + datetime.timedelta(days=-1) start = s.timetuple() target = t.timetuple() self.assertExpectedResult( self.cal.parse('-1 day', start), (target, 1)) self.assertExpectedResult( self.cal.parse('-1 days', start), (target, 1)) self.assertExpectedResult( self.cal.parse('-1days', start), (target, 1)) self.assertExpectedResult( self.cal.parse('-1 dy', start), (target, 1)) self.assertExpectedResult( self.cal.parse('-1 d', start), (target, 1)) self.assertExpectedResult( self.cal.parse('- 1 day', start), (target, 1)) self.assertExpectedResult( self.cal.parse('- 1 days', start), (target, 1)) self.assertExpectedResult( self.cal.parse('- 1days', start), (target, 1)) self.assertExpectedResult( self.cal.parse('- 1 dy', start), (target, 1)) self.assertExpectedResult( self.cal.parse('- 1 d', start), (target, 1)) self.assertExpectedResult( self.cal.parse('1 day ago', start), (target, 1)) self.assertExpectedResult( self.cal.parse('1 days ago', start), (target, 1)) def testWeeks(self): s = datetime.datetime.now() t = s + datetime.timedelta(weeks=1) h = s - datetime.timedelta(weeks=1) start = s.timetuple() target = t.timetuple() history = h.timetuple() self.assertExpectedResult( self.cal.parse('1 week', start), (target, 1)) self.assertExpectedResult( self.cal.parse('1week', start), (target, 1)) self.assertExpectedResult( self.cal.parse('1 weeks', start), (target, 1)) self.assertExpectedResult( self.cal.parse('1 wk', start), (target, 1)) self.assertExpectedResult( self.cal.parse('1 w', start), (target, 1)) self.assertExpectedResult( self.cal.parse('1w', start), (target, 1)) self.assertExpectedResult( self.cal.parse('1 week ago', start), (history, 1)) self.assertExpectedResult( self.cal.parse('1 weeks ago', start), (history, 1)) def testMonths(self): s = datetime.datetime.now() t = self.cal.inc(s, month=1) h = self.cal.inc(s, month=-1) start = s.timetuple() target = t.timetuple() history = h.timetuple() self.assertExpectedResult( self.cal.parse('1 month', start), (target, 1)) self.assertExpectedResult( self.cal.parse('1 months', start), (target, 1)) self.assertExpectedResult( self.cal.parse('1month', start), (target, 1)) self.assertExpectedResult( self.cal.parse('1 month ago', start), (history, 1)) self.assertExpectedResult( self.cal.parse('1 months ago', start), (history, 1)) def testYears(self): s = datetime.datetime.now() t = self.cal.inc(s, year=1) start = s.timetuple() target = t.timetuple() self.assertExpectedResult( self.cal.parse('1 year', start), (target, 1)) self.assertExpectedResult( self.cal.parse('1 years', start), (target, 1)) self.assertExpectedResult( self.cal.parse('1 yr', start), (target, 1)) self.assertExpectedResult( self.cal.parse('1 y', start), (target, 1)) self.assertExpectedResult( self.cal.parse('1y', start), (target, 1)) if __name__ == "__main__": unittest.main() parsedatetime-2.4/tests/utils.py0000644000076500000240000000465712617261456017144 0ustar bearstaff00000000000000# -*- coding: utf-8 -*- """ Internal helper functions for unit tests of parsedatetime """ from __future__ import unicode_literals def assertEqualWithComparator(comparator): """ Fail a little less cryptically that unittest.assertTrue when comparing a result against a target value. Shows the result and the target in the failure message. """ def decoratedComparator(self, result, check, errMsg=None, **kwargs): errMsg = errMsg or 'Result does not match target value' equal = comparator(self, result, check, **kwargs) failureMessage = ('%s\n\n\t' 'Result:\n\t%s\n\n\tExpected:\n\t%s') if not equal: self.fail(failureMessage % (errMsg, result, check)) return decoratedComparator def compareResultByTimeTuplesAndFlags(result, check, dateOnly=False): """ Ensures that flags are an exact match and time tuples a close match when given data in the format ((timetuple), flag) """ return (_compareTimeTuples(result[0], check[0], dateOnly) and _compareFlags(result[1], check[1])) def compareResultByFlags(result, check, dateOnly=False): """ Ensures that flags are an exact match when given data in the format ((timetuple), flag) """ return _compareFlags(result[1], check[1]) def compareResultByTimeTupleRangesAndFlags(result, check, dateOnly=False): """ Ensures that flags are an exact match and time tuples a close match when given data in the format ((timetuple), (timetuple), flag) """ return (_compareTimeTuples(result[0], check[0], dateOnly) and _compareTimeTuples(result[1], check[1], dateOnly) and _compareFlags(result[2], check[2])) def _compareTimeTuples(target, value, dateOnly=False): """ Ignores minutes and seconds as running the test could cross a minute boundary. Technically the year, month, day, hour, minute, and second could all change if the test is run on New Year's Eve, but we won't worry about less than per-hour granularity. """ t_yr, t_mth, t_dy, t_hr, t_min, _, _, _, _ = target v_yr, v_mth, v_dy, v_hr, v_min, _, _, _, _ = value if dateOnly: return ((t_yr == v_yr) and (t_mth == v_mth) and (t_dy == v_dy)) else: return ((t_yr == v_yr) and (t_mth == v_mth) and (t_dy == v_dy) and (t_hr == v_hr) and (t_min == v_min)) def _compareFlags(result, check): return (result == check) parsedatetime-2.4/violations.flake8.txt0000644000076500000240000000000013105777606020345 0ustar bearstaff00000000000000