├── .autocode └── config.yml ├── .circleci └── config.yml ├── .coveragerc ├── .editorconfig ├── .gitignore ├── AUTHORS.txt ├── CHANGES.txt ├── LICENSE.txt ├── MANIFEST.in ├── Makefile ├── Pipfile ├── Pipfile.lock ├── README.md ├── epydoc.conf ├── examples ├── README.txt ├── basic.py └── with_locale.py ├── notes ├── THANKS.txt ├── implementation_notes.txt └── locale_date_grouping_notes.txt ├── parsedatetime ├── __init__.py ├── context.py ├── parsedatetime.py ├── pdt_locales │ ├── __init__.py │ ├── base.py │ ├── de_DE.py │ ├── en_AU.py │ ├── en_US.py │ ├── es.py │ ├── fr_FR.py │ ├── icu.py │ ├── nl_NL.py │ ├── pt_BR.py │ └── ru_RU.py └── warns.py ├── pytest.ini ├── renovate.json ├── setup.cfg ├── setup.py └── tests ├── TestAlternativeAbbreviations.py ├── TestAustralianLocale.py ├── TestComplexDateTimes.py ├── TestContext.py ├── TestConvertUnitAsWords.py ├── TestDelta.py ├── TestErrors.py ├── TestFrenchLocale.py ├── TestGermanLocale.py ├── TestInc.py ├── TestLocaleBase.py ├── TestMultiple.py ├── TestNlp.py ├── TestPhrases.py ├── TestRanges.py ├── TestRussianLocale.py ├── TestSimpleDateTimes.py ├── TestSimpleOffsets.py ├── TestSimpleOffsetsHours.py ├── TestSimpleOffsetsNoon.py ├── TestStartTimeFromSourceTime.py ├── TestUnits.py ├── __init__.py └── utils.py /.autocode/config.yml: -------------------------------------------------------------------------------- 1 | name: bear/parsedatetime 2 | author: 3 | name: Omer Katz 4 | email: omer.drow@gmail.com 5 | url: 'http://omerkatz.com' 6 | copyright: 2015 Omer Katz 7 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | 2 | version: 2.1 3 | 4 | executors: 5 | python: 6 | docker: 7 | - image: cimg/python:3.12 8 | 9 | commands: 10 | setup_working_dir: 11 | description: get docker initialized and checkout code 12 | steps: 13 | - setup_remote_docker 14 | - checkout 15 | - run: pip3 install pipenv 16 | - run: make dev 17 | lint: 18 | description: Run flake8 19 | steps: 20 | - run: make lint 21 | test: 22 | description: Run pytest and generate report 23 | steps: 24 | - run: make test 25 | coverage: 26 | description: Generate coverage report 27 | steps: 28 | - run: make coverage 29 | build: 30 | description: build package distribution 31 | steps: 32 | - run: make build 33 | jobs: 34 | lint-and-test: 35 | executor: python 36 | steps: 37 | - setup_working_dir 38 | - lint 39 | - test 40 | - coverage 41 | build-package: 42 | executor: python 43 | steps: 44 | - setup_working_dir 45 | - lint 46 | - test 47 | - build 48 | 49 | workflows: 50 | test: 51 | jobs: 52 | - lint-and-test: 53 | filters: 54 | branches: 55 | ignore: master 56 | build: 57 | jobs: 58 | - build-package: 59 | filters: 60 | branches: 61 | only: master 62 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | source = parsedatetime 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | # 4 space indentation 5 | [*.py] 6 | indent_style = space 7 | indent_size = 4 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[co] 2 | foo.py 3 | 4 | # IDE 5 | .idea/ 6 | .vscode 7 | 8 | # Packages 9 | *.egg 10 | *.egg-info 11 | dist 12 | build 13 | eggs 14 | parts 15 | bin 16 | var 17 | sdist 18 | develop-eggs 19 | .installed.cfg 20 | 21 | # Installer logs 22 | pip-log.txt 23 | 24 | # Unit test / coverage reports 25 | .coverage 26 | .codecov-token 27 | .tox 28 | nosetests.xml 29 | htmlcov 30 | coverage.xml 31 | 32 | #Translations 33 | *.mo 34 | 35 | #Mr Developer 36 | .mr.developer.cfg 37 | 38 | # vim 39 | *.swp 40 | *.swo 41 | 42 | .cache 43 | .eggs 44 | .mypy_cache 45 | 46 | violations.txt 47 | .python-version 48 | -------------------------------------------------------------------------------- /AUTHORS.txt: -------------------------------------------------------------------------------- 1 | Mike Taylor https://bear.im 2 | Darshana Chhajed 3 | Michael Lim 4 | Bernd Zeimetz 5 | Geoffrey Floyd https://github.com/geoffreyfloyd 6 | Alexis Sasha Acker https://github.com/sashaacker 7 | Yu-Jie Lin https://github.com/livibetter 8 | rl-0x0 https://github.com/rl-0x0 9 | Bernardo Sulzbach https://github.com/mafagafogigante 10 | Philip Tzou https://github.com/philiptzou 11 | Ian Paterson https://github.com/idpaterson 12 | Omer Katz https://github.com/thedrow 13 | Rob Curtis https://github.com/Rob1080 14 | Michael Maltese https://github.com/michaelmaltese 15 | Dan Steeves https://github.com/dansteeves68 16 | Alexander Sapronov https://github.com/WarmongeR1 17 | Bernardo Sulzbach https://github.com/mafagafogigante 18 | Zed https://github.com/zed 19 | 20 | https://github.com/fake-name 21 | https://github.com/rl-0x0 22 | https://github.com/dirkjankrijnders 23 | https://github.com/inean 24 | https://github.com/oxan 25 | https://github.com/jstegle 26 | https://github.com/devainandor 27 | https://github.com/borgstrom 28 | https://github.com/arcimboldo 29 | https://github.com/ccho-sevenrooms 30 | https://github.com/boppreh 31 | https://github.com/lborgav 32 | https://github.com/yishaibeeri 33 | https://github.com/rmecham 34 | 35 | see https://github.com/bear/parsedatetime/graphs/contributors for the full list 36 | -------------------------------------------------------------------------------- /CHANGES.txt: -------------------------------------------------------------------------------- 1 | 9 Oct 2021 - bear 2 | PR #261 Bump urllib3 from 1.25.9 to 1.26.5 3 | PR #255 support 'next 10 days' query 4 | PR #252 Include pytest.ini in source distributions, fixes tests 5 | PR #250 Update nl_NL.py 6 | PR #248 pyicu 'module' object has no attribute 'Locale' 7 | 8 | Update Pipfile to reference Python 3.9 9 | Updated Copyright statement 10 | 11 | 31 May 2020 - bear 12 | v2.6 release 13 | bump version to v2.7 14 | 15 | PR #244 Polished README.rst 16 | PR #242 fix pyicu import to suppress warnings 17 | PR #239 Fixed missing comma in seconds strings 18 | 19 | Updated Pipfile and Makefile to: 20 | - update and move packages to the "dev" section 21 | - use Python 3.7 for pipenv 22 | - install tox-pipenv plugin to try and fix Tox (currently doesn't) 23 | - simplify tox.ini to try and fix Tox (didn't) 24 | - move ci makefile target to the circle config 25 | 26 | Currently Tox is broken (see https://github.com/tox-dev/tox-pipenv/issues/61) 27 | 28 | 18 Nov 2019 - bear 29 | v2.5 release 30 | 31 | PR #222 Fix to sanitize abbreviated months from icu 32 | PR #223 typo in RU locale in abbreviation for January 33 | PR #224 Fix lint errors for flake8 v3.5.0 34 | PR #225 Add a constant for start hour 35 | PR #233 Add 'secs' and 'mins' into base units 36 | PR #226 Remove unused dependency on future 37 | 38 | 14 May 2017 - bear 39 | v2.4 release 40 | v2.5 bump 41 | 42 | Issue #219 - remove 'setup_requires' from setup.py 43 | 44 | 10 Mar 2017 - bear 45 | v2.3 release 46 | v2.4 bump 47 | 48 | Issue #215 - tests fail in March 49 | 50 | 02 Mar 2016 - bear 51 | v2.1 released 52 | v2.2 bump 53 | 54 | Issue #156 parsedatetime 2.0 doesn't work on py26 55 | 56 | PR 157 unwrap dictionary comprehensions to allow for python 2.6 to work - Issue #156 57 | 58 | 29 Feb 2016 - bear 59 | v2.0 released 60 | 61 | Issue #155 Relative times containing years fail when computed from a leap day 62 | Issue #145 cal.parse('2015-11-18') returns November 19th 2015 63 | Issue #143 What is the second value returned by `parse`? 64 | Issue #141 Bad test case in TestComplexDateTimes 65 | Issue #123 update supporting files for v2.0 release 66 | Issue #124 Put locales into config-files (yaml) 67 | Issue #125 Remove extra files 68 | Issue #137 Year is parsed wrongly if the date is of format MMM DD, YYxx xx:SS bug 69 | Issue #136 Why I see 2016 instead of 2015? 70 | Issue #133 Bug: "2015-01-01" is parsed as the current date. 71 | Issue #126 "Unresolved attribute reference 'parse' for class 'object'... " in Pycharm IDE. bug 72 | 73 | PR #153 Fix/day of week offsets 74 | PR #146 Test failure: eom is correct, but expectation is wrong 75 | PR #142 Fixed all PyICU test failure 76 | PR #138 bug(date3): rely on comparison of hour and year strings but not strict char position 77 | PR #135 update manifest, clean up setup.py and move historical text files 78 | PR #130 Refactoring of pdt_locales 79 | PR #134 Uses `codecov` to generate coverage report 80 | PR #128 Master 81 | PR #127 Issue #126 - removed inheritance from object and removed return value… 82 | 83 | 20 Sep 2015 - bear 84 | bump version to v2.0 because of the fix for Issue #120 85 | 86 | Issue #120 the pdt_locales/en_AU.py file uses en_A for the localID instead of en_AU 87 | Issue #114 Dates in the format 'YYYY-MM-DD HH:MM' give the incorrect month and day 88 | Issue #112 Document getting a time from parsedatetime into a standard Python structure 89 | Issue #110 AttributeError when running in the context of an HTTP request 90 | Issue #109 YearParseStyle is ignored for dates in MM/DD style 91 | Issue #107 yyyy/mm/dd date format 92 | Issue #105 "this week" is not parsed 93 | Issue #103 get UTC times from parseDT - trouble with at 9:30 clock times being interpreted directly in UTC 94 | Issue #100 Fractional deltas result in incoherent results. 95 | 96 | PR #118 ADD: improve russian locale 97 | PR #117 ADD: Russian Locale 98 | PR #116 Fix spelling of "separator". 99 | PR #115 Update README.rst 100 | PR #113 Add datetime example to readme. 101 | PR #111 Allowed real number appear in text like "5.5 days ago" 102 | 103 | 25 Jun 2015 - bear 104 | Issue #73 add Makefile 105 | 106 | bump version to v1.6 107 | released v1.5 108 | 109 | Issue #99 Which year is implied when given just a month and day? Next and last? question 110 | Issue #96 Word boundary issues for specials (on, at, in) in nlp 111 | Issue #94 inconsistent application of sourceTime in Calendar.parseDT 112 | Issue #87 nlp() doesn't recognize some "next ..." expressions 113 | Issue #84 Afternoon? bug 114 | Issue #82 'last week' and 'next week' are broken 115 | Issue #81 parse returns default time of 0900 with dates like 'next friday' despite passed struct_time bug 116 | Issue #78 Link for Travis in README is wrong 117 | Issue #72 Enable travis 118 | Issue #71 Calendar() class can not be initialized 1.4 (it's fine) 119 | Issue #66 Unexpected struct_time flag with Calendar.parse on HTML string 120 | Issue #65 NLP false positives 121 | Issue #63 Supporting multiple shortweekday abbreviations 122 | Issue #61 Short weekday abbreviations bug 123 | Issue #56 Parse words to numbers (thirteen => 13) 124 | Issue #54 testMonths fails 125 | 126 | commit 107c7e4655 fix for issue 95 - parsing 'next june 15' 127 | commit 2c0c8ec778 Fixed faulty test, "730am" parses as "73:0 am" which is a bug for a later day. 128 | commit 6f244e891d Fix "ones" parsing as "1s." Require a word boundary between spelled numbers and units. 129 | commit 035818edef Fix "1 day ago" parsing like "1d 1y ago" where "a" within the word "day" is interpreted as 1. 130 | commit 45002e6eec Fixes "next week" and similar modifier + unit pairs in nlp() 131 | commit 47d2e1d527 Fixed "last week" 132 | 133 | 11 Jul 2014 - bear 134 | bump version to v1.5 135 | released v1.4 136 | 137 | Updated setup.py for wheel compatibility 138 | renamed README.txt to README.rst 139 | renamed MANIFEST to MANIFEST.in 140 | cleaned up a lot of the doc and notes 141 | 142 | Commit 3fc165e701 mafagafo Now it works for Python 3.4.1 143 | Commit d5883801e7 borgstrom Restore Python 2.6 compatibility 144 | 145 | 8 Jul 2014 - bear 146 | bumped version to 1.4 147 | 148 | Issue #45 make a new release to really fix backwards compatibility 149 | Issue #43 Please tag version 1.3 150 | 151 | Commit 29c5c8961d devainandor fixed Python 3 compatibility in pdtLocale_icu 152 | Commit d7304f18f7 inean Fix support for 'now' when no modifiers are present 153 | Commit 26bfc91c28 sashaacker Added parseDT method. 154 | Commit 848deb47e2 rmecham Added support for dotted meridians. 155 | Commit c821e08ce2 ccho-sevenrooms corrected misspelling of 'thirteen' 156 | 157 | 24 Jan 2014 - bear 158 | bumped version to 1.3 159 | 160 | many changes - amazing how hard it is to keep this file up to date 161 | when using GitHub. 162 | 163 | See https://github.com/bear/parsedatetime/commits/master for details. 164 | 165 | Biggest change is the addition of the nlp() function by Geoffrey Floyd: 166 | nlp() function that utilizes parse() after making judgements about 167 | what datetime information belongs together. It makes logical groupings 168 | based on proximity and returns a parsed datetime for each matched 169 | grouping of datetime text, along with location info within the given inputString. 170 | 171 | 27 Jun 2013 - bear 172 | bumped version to 1.2 173 | 174 | 04 Mar 2013 - bear 175 | bumped version to 1.1.2 176 | 177 | deploy import fix from Antonio Messina 178 | also noticed that the urls were pointing to my older site, corrected 179 | 180 | 03 Mar 2013 - bear 181 | bumped version to 1.1.1 182 | 183 | Ugh - debug log caused an error during formatting 184 | Issue 10 https://github.com/bear/parsedatetime/issues/10 185 | 186 | 14 Nov 2012 - bear 187 | 188 | Added test for "last friday" 189 | Updated MANIFEST to reflect renamed README file 190 | Bumped version to 1.1 191 | 192 | 15 Mar 2011 - bear 193 | 194 | Updated 1.0.0 code to work with 2.6+ (need to try 2.5) and also updated 195 | docs and other supporting code 196 | 197 | 07 Sep 2009 - bear 198 | 199 | Created branches/python25 from current trunk to save the current code 200 | 201 | Converted trunk to Python 3 and also refactored how the module is structured 202 | so that it no longer requires import parsedatetime.parsedatetime 203 | 204 | Bumped version to 1.0.0 to reflect the major refactoring 205 | 206 | 07 Jan 2009 - bear 207 | 208 | 0.8.7 release 209 | Apply patch submitted by Michael Lim to fix the problem parsedatetime 210 | was having handling dates when the month day preceeded the month 211 | Issue 26 http://code.google.com/p/parsedatetime/issues/detail?id=26 212 | 213 | Fixed TestErrors when in a local where the bad date actually returns 214 | a date ;) 215 | 216 | Checked in the TestGermanLocale unit test file missed from previous commit 217 | 218 | 20 Apr 2008 - bear 219 | 220 | Upating Copyright year info 221 | Fixing defects from Google Project page 222 | 223 | The comparison routine for the "failing" test was not accurate. 224 | The test was being flagged as failing incorrectly 225 | Issue 18 http://code.google.com/p/parsedatetime/issues/detail?id=18 226 | 227 | Added patch from Bernd Zeimetz for the German localized constants! 228 | http://svn.debian.org/viewsvn/*checkout*/python-modules/packages/parsedatetime/trunk/debian/patches/locale-de.dpatch 229 | He identifies some issues with how unicode is handled and also some other 230 | glitches - will have to work on them 231 | Issue 20 http://code.google.com/p/parsedatetime/issues/detail?id=20 232 | 233 | Tweaked run_tests.py to default to all tests if not given on the command line 234 | Removed 'this' from the list of "specials" - it was causing some grief and from the 235 | looks of the unit tests, not all that necessary 236 | 237 | Worked on bug 19 - Bernd identified that for the German locale the dayofweek check 238 | was being triggered for the dayoffset word "morgen" (the "mo" matched the day "morgen") 239 | To solve this I added a small check to make sure if the whole word being checked was 240 | not in the dayOffsets list, and if so not trigger. 241 | Issue 19 http://code.google.com/p/parsedatetime/issues/detail?id=19 242 | 243 | 244 | 28 Nov 2007 - bear 245 | 246 | 0.8.5.1 release - removed debug code 247 | 248 | 0.8.5 release 249 | bumping version to 0.8.6 in trunk 250 | 251 | Fixing two bugs found by Chandler QA 252 | 253 | Time range of "today 3:30-5pm" was actually causing a traceback. 254 | Added a new regex to cover this range type and a new test. 255 | 256 | OSAF 11299 https://bugzilla.osafoundation.org/show_bug.cgi?id=11299 257 | 258 | A really embarrassing for a date/time library - was actually *not* 259 | considering leap years when returning days in a month! 260 | Added tests for Feb 29th of various known leap years and also added 261 | a check for the daysInMonth() routine which was created to replace 262 | the naively simple DaysInMonthList. 263 | 264 | OSAF 11203 https://bugzilla.osafoundation.org/show_bug.cgi?id=11203 265 | 266 | 12 Jun 2007 - bear 267 | 268 | 0.8.4 release 269 | bumping version to 0.8.5 in trunk 270 | 271 | 272 | 22 Feb 2007 - bear 273 | 274 | Fixed a bug reported via the code.google project page by Alexis where 275 | parsedatetime was not parsing day suffixes properly. For example, the 276 | text "Aug 25th, 2008" would return the year as 2007 - the parser was 277 | not 'seeing' 2008 as a part of the expression. 278 | 279 | The fix was to enhance one of the "long date" regexes to handle that 280 | situation but yet not break the current tests - always fun for sure! 281 | 282 | Issue 16 http://code.google.com/p/parsedatetime/issues/detail?id=16 283 | 284 | 285 | 21 Feb 2007 - bear 286 | 287 | Fixed a bug Brian K. (one of the Chandler devs) found when parsing with 288 | the locale set to fr_FR. The phrase "same 3 folders" was causing a key 289 | error inside the parser and it turns out that it's because short weekday 290 | names in French have a trailing '.' so "sam." was being used in the 291 | regular expression and the '.' was being treated as a regex symbol and 292 | not as a period. 293 | 294 | It turned out to be a simple fix - just needed to add some code to run 295 | re.escape over the lists before adding them to the re_values dictionary. 296 | 297 | Also added a TestFrenchLocale set of unit tests but made them only run 298 | if PyICU is found until I can build an internal locale for fr_FR. 299 | Issue #17 http://code.google.com/p/parsedatetime/issues/detail?id=17 300 | 301 | 302 | 14 Feb 2007 - bear 303 | 304 | 0.8.3 release 305 | 306 | Minor doc string changes and other typo fixes 307 | 308 | Updated Copyright years 309 | 310 | Added a fallbackLocales=[] parameter to parsedatetime_consts init routine 311 | to control what locales are scanned if the default or given locale is not 312 | found in PyICU. 313 | Issue #9 http://code.google.com/p/parsedatetime/issues/detail?id=9 314 | 315 | While working on the regex compile-on-demand issue below, I realized that 316 | parsedatetime was storing the compiled regex's locally and that this would 317 | cause prevent parsedatetime from switching locales easily. I've always 318 | wanted to make it so parsedatetime can be set to parse within a locale just 319 | by changing a single reference - this is one step closer to that. 320 | 321 | Made the regex compiles on-demand to help with performance 322 | Requested by the Chandler folks 323 | Issue #15 http://code.google.com/p/parsedatetime/issues/detail?id=15 324 | 325 | To test the change I ran 100 times the following code: 326 | for i in range(0, 100): 327 | c = pdc.Constants() 328 | p = pdt.Calendar(c) 329 | p = None 330 | c = None 331 | 332 | and that was measured by hotshot: 333 | 334 | 24356 function calls (22630 primitive calls) in 0.188 CPU seconds 335 | 336 | after the change: 337 | 338 | 5000 function calls in 0.140 CPU seconds 339 | 340 | but that doesn't test the true time as it doesn't reference any regex's 341 | so any time saved is deferred. To test this I then ran before and after 342 | tests where I parsed the major unit test bits: 343 | 344 | before the change: 345 | 346 | 80290 function calls (75929 primitive calls) in 1.055 CPU seconds 347 | 348 | after the change: 349 | 350 | 55803 function calls (52445 primitive calls) in 0.997 CPU seconds 351 | 352 | This tells me while doing the lazy compile does save time, it's not a lot 353 | over the normal usage. I'll leave it in as it is saving time for the 354 | simple use-cases. 355 | 356 | 357 | 27 Dec 2006 - bear 358 | 359 | Added some support files to try and increase our cheesecake index :) 360 | 361 | Created an examples directory and added back the docs/* content so the 362 | source distribution will contain the generated docs 363 | 364 | Changed how setup.py works to allow for a doc command 365 | 366 | 26 Dec 2006 - bear 367 | 368 | 0.8.1 release 369 | Setting trunk to 0.8.2 370 | 371 | Fixed the 'eom' part of testEndOfPhrases. It was not adjusting the year 372 | when checking for month rollover to the new year. 373 | 374 | Changed API docs to reflect that it's a struct_time type (or a time tuple) that 375 | we accept and return instead of a datetime value. I believe this lead to Issue #14 376 | being reported. Also added some error handling to change a datetime value into a 377 | struct_time value if passed to parse(). 378 | 379 | 3 Nov 2006 - darshana 380 | 381 | Fixed issue#13 (Nov 4 5pm parses as just 5pm). 382 | Also fixed "1-8pm" and other ranges which were not working if there were no spaces before and after the '-'. 383 | 384 | 1 Nov 2006 - darshana 385 | 386 | Strings like "Thursday?" were not parsed. Changes made to the regex to 387 | allow special characters to be parsed after weekday names and month names. 388 | 389 | 24 Oct 2006 - bear 390 | 391 | 0.8.0 release 392 | Setting trunk to 0.8.1 393 | 394 | Merged in changes from Darshana's change_parse_to_return_enum branch 395 | 396 | This is a big change in that instead of a simple True/False that is 397 | returned to show if the date is valid or not, Parse() now returns 398 | a "masked" value that represents what is valid: 399 | 400 | date = 1 401 | time = 2 402 | 403 | so a value of zero means nothing was parseable/valid and a value of 404 | 3 means both were parsed/valid. 405 | 406 | 20 Oct 2006 - darshana 407 | 408 | Implemented the CalculateDOWDelta() method in parsedatetime.py 409 | Added a new flag CurrentDOWParseStyle in parsedatetime_consts.py for the current DOW. 410 | 411 | 19 Oct 2006 - bear 412 | 413 | Changed birthday epoch to be a constant defined in parsedatetime_const 414 | Lots of little cosmetic code changes 415 | Removed the individual files in the docs/ folder 416 | Added dist, build and parsedatetime-egg.info to svn:ignore 417 | 418 | 17 Oct 2006 - darshana 419 | 420 | Added birthday epoch constraint 421 | Fixed date parsing. 3-digit year not allowed now. 422 | Fixed the unit tests too to either have yy or yyyy. 423 | 424 | 9 Oct 2006 - bear 425 | 426 | 0.7.4 release 427 | Setting trunk to 0.7.5 428 | 429 | 5 Oct 2006 - darshana 430 | 431 | Fixed "ago" bug -- Issue #7 http://code.google.com/p/parsedatetime/issues/detail?id=7 432 | 433 | Fixed bug where default year for dates that are in the future get next year, not 434 | current year -- Issue #8 http://code.google.com/p/parsedatetime/issues/detail?id=8 435 | 436 | Fixed strings like "1 week ago", "lunch tomorrow" 437 | 438 | 25 Sep 2006 - bear 439 | 440 | 0.7.3 release 441 | Setting trunk to 0.7.4 442 | 443 | 13 Sep 2006 - bear 444 | 445 | Added Darshana as an author and updated the copyright text 446 | Added "eom" and "eoy" tests 447 | 448 | 11 Sep 2006 - bear 449 | 450 | Fixed a subtle dictionary reference bug in buildSources() that was causing 451 | any source related modifier to not honor the day, month or year. It only 452 | started being seen as I was working on adding "eod" support as a 'true' 453 | modifier instead. 454 | 455 | Found another subtle bug in evalModifier() if the modifier was followed 456 | by the day of the week - the offset math was not consistent with the 457 | other day-of-week offset calculations. 458 | 459 | Worked on converting "eod" support from the special case modifier to work 460 | as a true modifier. 461 | 462 | The following is now supported: 463 | eod tomorrow 464 | tomorrow eod 465 | monday eod 466 | eod monday 467 | meeting eod 468 | eod meeting 469 | 470 | 10 Sep 2006 - bear 471 | 472 | Added a sub-range test in response to Issue #6 http://code.google.com/p/parsedatetime/issues/detail?id=6 473 | 474 | Not that it works, just wanted to start the process. 475 | 476 | 6 Sep 2006 - bear 477 | 478 | Alan Green filed Issue #5 http://code.google.com/p/parsedatetime/issues/detail?id=5 479 | 480 | In it he asked for support for Australian date formats "dd-mm-yyyy" 481 | 482 | This is the first attempt at supporting the parsing of dates where the order of the 483 | day, month and year can vary. I adjusted the parseDate() code to be data driven 484 | and added a dp_order list to the Constants() class that is either initialized to the 485 | proper order by the pdtLocale classes or the order is determined by parsing the ICU 486 | short date format to figure out what the date separator is and then to find out what 487 | order it's in. 488 | 489 | I also added a TestAustralianLocale.py as a starting point for tests. 490 | 491 | Attaching a diff of this code to the Issue so he can test it. 492 | 493 | 1 Sep 2006 - bear 494 | 495 | 0.7.2 release 496 | 497 | 31 Aug 2006 - bear 498 | 499 | Fixed two bugs found by Darshana today. 500 | 501 | The first is one of those forehead-slapping bugs that you see as 502 | being so obvious *after* the fact :) The problem is with Inc() 503 | for months - if you increment from a month with the day set to 504 | a value that is past the end of the month for the new month you 505 | get an error. For example Aug 31 to Sept - Sept doesn't have 31 506 | days so it's an invalid date. 507 | 508 | The second is with the code that identifies modifiers when you have 509 | multiple "chunks" of text. Darshana describes the bug this way: 510 | 511 | "if you have "flight from SFO at 4pm" i.e. if you have a 512 | non-date/time string before a modifier, then the invalidflag 513 | is set" 514 | 515 | I provided the Inc() fix and Darshana the modifier fix. 516 | 517 | I also added a new unit test for the Inc() bug and also a new test 518 | file for the modifier bug: TestPhrases.py 519 | 520 | 29 Aug 2006 - bear 521 | 522 | Updated ez_setup.py to latest version v0.6c1 and removed 523 | hard-coded version in setup.py for setuptools 524 | 525 | 25 Aug 2006 - bear 526 | 527 | Moved the error tests into a single TestErrors.py 528 | Added two tests for days - it figures out what the 529 | previous day and next day is from the weekday the 530 | test is run on 531 | 532 | 24 Aug 2006 - bear 533 | 534 | Issue #2 http://code.google.com/p/parsedatetime/issues/detail?id=2 535 | 536 | Turns out that ICU works with weekdays in Sun..Sat order 537 | and that Python uses Mon..Sun order. Fixed PyICU locale code to 538 | build the internal weekday list to be Python Style. 539 | 540 | Bumping version to 0.7.1 as this is causing Chandler bug 6567 541 | http://bugzilla.osafoundation.org/show_bug.cgi?id=6567 542 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, and 10 | distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by the 13 | copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all other 16 | entities that control, are controlled by, or are under common control with 17 | that entity. For the purposes of this definition, "control" means (i) the 18 | power, direct or indirect, to cause the direction or management of such 19 | entity, whether by contract or otherwise, or (ii) ownership of fifty percent 20 | (50%) or more of the outstanding shares, or (iii) beneficial ownership of 21 | such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity exercising 24 | permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation source, 28 | and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but not limited 32 | to compiled object code, generated documentation, and conversions to 33 | other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or Object 36 | form, made available under the License, as indicated by a copyright 37 | notice that is included in or attached to the work (an example is 38 | provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object form, 41 | that is based on (or derived from) the Work and for which the editorial 42 | revisions, annotations, elaborations, or other modifications represent, 43 | as a whole, an original work of authorship. For the purposes of this 44 | License, Derivative Works shall not include works that remain separable 45 | from, or merely link (or bind by name) to the interfaces of, the Work and 46 | Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including the original 49 | version of the Work and any modifications or additions to that Work or 50 | Derivative Works thereof, that is intentionally submitted to Licensor for 51 | inclusion in the Work by the copyright owner or by an individual or Legal 52 | Entity authorized to submit on behalf of the copyright owner. For the 53 | purposes of this definition, "submitted" means any form of electronic, 54 | verbal, or written communication sent to the Licensor or its 55 | representatives, including but not limited to communication on electronic 56 | mailing lists, source code control systems, and issue tracking systems 57 | that are managed by, or on behalf of, the Licensor for the purpose of 58 | discussing and improving the Work, but excluding communication that is 59 | conspicuously marked or otherwise designated in writing by the copyright 60 | owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity on 63 | behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of this 67 | License, each Contributor hereby grants to You a perpetual, worldwide, 68 | non-exclusive, no-charge, royalty-free, irrevocable copyright license to 69 | reproduce, prepare Derivative Works of, publicly display, publicly 70 | perform, sublicense, and distribute the Work and such Derivative Works in 71 | Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of this 74 | License, each Contributor hereby grants to You a perpetual, worldwide, 75 | non-exclusive, no-charge, royalty-free, irrevocable (except as stated in 76 | this section) patent license to make, have made, use, offer to sell, 77 | sell, import, and otherwise transfer the Work, where such license applies 78 | only to those patent claims licensable by such Contributor that are 79 | necessarily infringed by their Contribution(s) alone or by combination of 80 | their Contribution(s) with the Work to which such Contribution(s) was 81 | submitted. If You institute patent litigation against any entity 82 | (including a cross-claim or counterclaim in a lawsuit) alleging that the 83 | Work or a Contribution incorporated within the Work constitutes direct or 84 | contributory patent infringement, then any patent licenses granted to You 85 | under this License for that Work shall terminate as of the date such 86 | litigation is filed. 87 | 88 | 4. Redistribution. You may reproduce and distribute copies of the Work or 89 | Derivative Works thereof in any medium, with or without modifications, 90 | and in Source or Object form, provided that You meet the following 91 | conditions: 92 | 93 | 1. You must give any other recipients of the Work or Derivative Works a 94 | copy of this License; and 95 | 96 | 2. You must cause any modified files to carry prominent notices stating 97 | that You changed the files; and 98 | 99 | 3. You must retain, in the Source form of any Derivative Works that You 100 | distribute, all copyright, patent, trademark, and attribution notices 101 | from the Source form of the Work, excluding those notices that do not 102 | pertain to any part of the Derivative Works; and 103 | 104 | 4. If the Work includes a "NOTICE" text file as part of its distribution, 105 | then any Derivative Works that You distribute must include a readable 106 | copy of the attribution notices contained within such NOTICE file, 107 | excluding those notices that do not pertain to any part of the 108 | Derivative Works, in at least one of the following places: within a 109 | NOTICE text file distributed as part of the Derivative Works; within 110 | the Source form or documentation, if provided along with the 111 | Derivative Works; or, within a display generated by the Derivative 112 | Works, if and wherever such third-party notices normally appear. The 113 | contents of the NOTICE file are for informational purposes only and do 114 | not modify the License. You may add Your own attribution notices 115 | within Derivative Works that You distribute, alongside or as an 116 | addendum to the NOTICE text from the Work, provided that such 117 | additional attribution notices cannot be construed as modifying the 118 | License. 119 | 120 | You may add Your own copyright statement to Your modifications and may 121 | provide additional or different license terms and conditions for use, 122 | reproduction, or distribution of Your modifications, or for any such 123 | Derivative Works as a whole, provided Your use, reproduction, and 124 | distribution of the Work otherwise complies with the conditions stated in 125 | this License. 126 | 127 | 5. Submission of Contributions. Unless You explicitly state otherwise, any 128 | Contribution intentionally submitted for inclusion in the Work by You to 129 | the Licensor shall be under the terms and conditions of this License, 130 | without any additional terms or conditions. Notwithstanding the above, 131 | nothing herein shall supersede or modify the terms of any separate 132 | license agreement you may have executed with Licensor regarding such 133 | Contributions. 134 | 135 | 6. Trademarks. This License does not grant permission to use the trade 136 | names, trademarks, service marks, or product names of the Licensor, 137 | except as required for reasonable and customary use in describing the 138 | origin of the Work and reproducing the content of the NOTICE file. 139 | 140 | 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in 141 | writing, Licensor provides the Work (and each Contributor provides its 142 | Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 143 | ANY KIND, either express or implied, including, without limitation, any 144 | warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or 145 | FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for 146 | determining the appropriateness of using or redistributing the Work and 147 | assume any risks associated with Your exercise of permissions under this 148 | License. 149 | 150 | 8. Limitation of Liability. In no event and under no legal theory, whether 151 | in tort (including negligence), contract, or otherwise, unless required 152 | by applicable law (such as deliberate and grossly negligent acts) or 153 | agreed to in writing, shall any Contributor be liable to You for damages, 154 | including any direct, indirect, special, incidental, or consequential 155 | damages of any character arising as a result of this License or out of 156 | the use or inability to use the Work (including but not limited to 157 | damages for loss of goodwill, work stoppage, computer failure or 158 | malfunction, or any and all other commercial damages or losses), even if 159 | such Contributor has been advised of the possibility of such damages. 160 | 161 | 9. Accepting Warranty or Additional Liability. While redistributing the Work 162 | or Derivative Works thereof, You may choose to offer, and charge a fee 163 | for, acceptance of support, warranty, indemnity, or other liability 164 | obligations and/or rights consistent with this License. However, in 165 | accepting such obligations, You may act only on Your own behalf and on 166 | Your sole responsibility, not on behalf of any other Contributor, and 167 | only if You agree to indemnify, defend, and hold each Contributor 168 | harmless for any liability incurred by, or claims asserted against, such 169 | Contributor by reason of your accepting any such warranty or additional 170 | liability. 171 | 172 | END OF TERMS AND CONDITIONS 173 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *.md 2 | include *.txt 3 | include Makefile 4 | include pytest.ini 5 | recursive-include examples *.py 6 | recursive-include examples *.txt 7 | recursive-include tests *.py 8 | prune .DS_Store 9 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: dev info clean docs lint test 2 | 3 | help: 4 | @echo " dev install all dev and production dependencies (virtualenv is assumed)" 5 | @echo " clean remove unwanted stuff" 6 | @echo " lint check style with flake8" 7 | @echo " test run tests" 8 | @echo " build generate source and wheel dist files" 9 | @echo " upload generate source and wheel dist files and upload them" 10 | 11 | info: 12 | @pipenv --version 13 | @pipenv run python --version 14 | 15 | env: info 16 | pipenv install --dev --python 3.9 17 | pipenv install black --pre --dev 18 | 19 | dev: info 20 | pipenv install --dev 21 | 22 | clean: 23 | rm -fr build 24 | rm -fr dist 25 | find . -name '*.pyc' -exec rm -f {} \; 26 | find . -name '*.pyo' -exec rm -f {} \; 27 | find . -name '*~' -exec rm -f {} \; 28 | 29 | docs: 30 | pipenv run epydoc --html --config epydoc.conf 31 | 32 | lint: clean 33 | pipenv run flake8 parsedatetime > violations.txt 34 | pipenv run mypy parsedatetime >> violations.txt 35 | 36 | test: clean 37 | pipenv run pytest 38 | 39 | coverage: clean 40 | @pipenv run coverage run --source=parsedatetime setup.py test 41 | @pipenv run coverage html 42 | @pipenv run coverage report 43 | 44 | check: clean lint 45 | pipenv run python setup.py check 46 | 47 | build: check 48 | pipenv run python setup.py sdist bdist_wheel 49 | 50 | # requires PyPI Twine - brew install pypi-twine 51 | upload: build 52 | twine upload dist/* 53 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [dev-packages] 7 | pytest = "*" 8 | pytest-cov = "*" 9 | pytest-runner = "*" 10 | pytest-flake8 = "*" 11 | pyicu-binary = "*" 12 | flake8 = "*" 13 | mypy = "*" 14 | coverage = "*" 15 | coveralls = "*" 16 | codecov = "*" 17 | check-manifest = "*" 18 | unittest2 = "*" 19 | black = "*" 20 | 21 | [requires] 22 | python_version = "3.9" 23 | 24 | [pipenv] 25 | allow_prereleases = true 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # parsedatetime 2 | Parse human-readable date/time strings. 3 | 4 | [![PyPI Version][pypi-image]][pypi-url] 5 | [![Build Status][build-image]][build-url] 6 | [![Code Coverage][coverage-image]][coverage-url] 7 | 8 | Parsedatetime now targets Python 3 and is currently tested with Python 3.9 9 | 10 | Use https://github.com/bear/parsedatetime/releases/tag/v2.6 if you need Python 2.7 compatibility. 11 | 12 | ## Installing 13 | You can install parsedatetime using 14 | ``` 15 | pip install parsedatetime 16 | ``` 17 | 18 | ## Development environment 19 | Development is done using a `pipenv` virtual environment 20 | ``` 21 | make env 22 | ``` 23 | 24 | *Note*: `black` is still listed as a beta library, and as such, must be installed with the `--pre` flag 25 | 26 | ## Running Tests 27 | From the source directory 28 | ``` 29 | make test 30 | ``` 31 | 32 | To run tests on several Python versions that are installed in the `pipenv` virtual environment 33 | ``` 34 | $ make tox 35 | [... tox creates a virtualenv for every python version and runs tests inside of each] 36 | py39: commands succeeded 37 | ``` 38 | 39 | The tests depend on PyICU being installed using the `pyicu-binary` package which removes the source build step. PyICU depends on icu4c which on macOS requires homebrew 40 | ``` 41 | brew install icu4c 42 | ``` 43 | 44 | # Using parsedatetime 45 | Detailed examples can be found in the `examples` directory. 46 | 47 | as a time `tuple` 48 | ```python 49 | import parsedatetime 50 | 51 | cal = parsedatetime.Calendar() 52 | cal.parse("tomorrow") 53 | ``` 54 | 55 | as a Python `datetime` object 56 | ```python 57 | from datetime import datetime 58 | 59 | time_struct, parse_status = cal.parse("tomorrow") 60 | datetime(*time_struct[:6]) 61 | ``` 62 | 63 | with timezone support using `pytz` 64 | ```python 65 | import parsedatetime 66 | from pytz import timezone 67 | 68 | cal = parsedatetime.Calendar() 69 | datetime_obj, _ = cal.parseDT(datetimeString="tomorrow", tzinfo=timezone("US/Pacific")) 70 | ``` 71 | 72 | ## Documentation 73 | 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 74 | 75 | The documentation is generated with 76 | ``` 77 | make docs 78 | ``` 79 | 80 | ## Notes 81 | 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()`. 82 | 83 | ## History 84 | 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. 85 | 86 | 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. 87 | 88 | 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. 89 | 90 | 91 | [pypi-image]: https://img.shields.io/pypi/v/parsedatetime 92 | [pypi-url]: https://pypi.org/project/parsedatetime/ 93 | [build-image]: https://circleci.com/gh/bear/parsedatetime.svg?style=svg 94 | [build-url]: https://circleci.com/gh/bear/parsedatetime 95 | [coverage-image]: https://codecov.io/gh/bear/parsedatetime/branch/master/graph/badge.svg 96 | [coverage-url]: https://codecov.io/gh/bear/parsedatetime 97 | -------------------------------------------------------------------------------- /epydoc.conf: -------------------------------------------------------------------------------- 1 | [epydoc] 2 | name: parsedatetime 3 | url: https://bear.im/code/parsedatetime 4 | 5 | modules: parsedatetime/ 6 | 7 | target: docs/ 8 | -------------------------------------------------------------------------------- /examples/README.txt: -------------------------------------------------------------------------------- 1 | Examples of parsedatetime usage 2 | 3 | basic.py outlines all the "simple" usage 4 | with_pyicu.py using parsedatetime with PyICU 5 | with_locale.py using parsedatetime with built-in locale classes 6 | 7 | -------------------------------------------------------------------------------- /examples/basic.py: -------------------------------------------------------------------------------- 1 | """ 2 | Basic examples of how to use parsedatetime 3 | """ 4 | 5 | __license__ = """ 6 | Copyright (c) 2004-2006 Mike Taylor 7 | Copyright (c) 2006 Darshana Chhajed 8 | All rights reserved. 9 | 10 | Licensed under the Apache License, Version 2.0 (the "License"); 11 | you may not use this file except in compliance with the License. 12 | You may obtain a copy of the License at 13 | 14 | http://www.apache.org/licenses/LICENSE-2.0 15 | 16 | Unless required by applicable law or agreed to in writing, software 17 | distributed under the License is distributed on an "AS IS" BASIS, 18 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | See the License for the specific language governing permissions and 20 | limitations under the License. 21 | """ 22 | 23 | import parsedatetime as pdt 24 | 25 | # create an instance of Constants class so we can override some of the defaults 26 | 27 | c = pdt.Constants() 28 | 29 | c.BirthdayEpoch = 80 # BirthdayEpoch controls how parsedatetime 30 | # handles two digit years. If the parsed 31 | # value is less than this value then the year 32 | # is set to 2000 + the value, otherwise 33 | # it's set to 1900 + the value 34 | 35 | # create an instance of the Calendar class and pass in our Constants 36 | # object instead of letting it create a default 37 | 38 | p = pdt.Calendar(c) 39 | 40 | # parse "tomorrow" and return the result 41 | 42 | result = p.parse("tomorrow") 43 | 44 | # parseDate() is a helper function that bypasses all of the 45 | # natural language stuff and just tries to parse basic dates 46 | # but using the locale information 47 | 48 | result = p.parseDate("4/4/80") 49 | 50 | # parseDateText() is a helper function that tries to parse 51 | # long-form dates using the locale information 52 | 53 | result = p.parseDateText("March 5th, 1980") 54 | 55 | -------------------------------------------------------------------------------- /examples/with_locale.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Examples of how to use parsedatetime with locale information provided. 4 | 5 | Locale information can come from either PyICU (if available) or from 6 | the more basic internal locale classes that are included in the 7 | `Constants` class. 8 | """ 9 | 10 | __license__ = """ 11 | Copyright (c) 2004-2006 Mike Taylor 12 | Copyright (c) 2006 Darshana Chhajed 13 | All rights reserved. 14 | 15 | Licensed under the Apache License, Version 2.0 (the "License"); 16 | you may not use this file except in compliance with the License. 17 | You may obtain a copy of the License at 18 | 19 | http://www.apache.org/licenses/LICENSE-2.0 20 | 21 | Unless required by applicable law or agreed to in writing, software 22 | distributed under the License is distributed on an "AS IS" BASIS, 23 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 24 | See the License for the specific language governing permissions and 25 | limitations under the License. 26 | """ 27 | 28 | import parsedatetime as pdt 29 | 30 | # create an instance of Constants class so we can specify the locale 31 | 32 | c = pdt.Constants("en") 33 | p = pdt.Calendar(c) 34 | 35 | # print out the values from Constants to show how the locale information 36 | # is being used/stored internally 37 | 38 | values = (c.uses24, # 24hr clock? 39 | c.usesMeridian, # AM/PM used? 40 | c.usePyICU, # was PyICU found/enabled? 41 | c.meridian, # list of the am and pm values 42 | c.am, # list of the lowercase and stripped AM string 43 | c.pm, # list of the lowercase and stripped PM string 44 | c.dateFormats, # dict of available date format strings 45 | c.timeFormats, # dict of available time format strings 46 | c.timeSep, # list of time separator, e.g. the ':' in '12:45' 47 | c.dateSep, # list of date separator, e.g. the '/' in '11/23/2006' 48 | c.Months, # list of full month names 49 | c.shortMonths, # list of the short month names 50 | c.Weekdays, # list of the full week day names 51 | c.localeID # the locale identifier 52 | ) 53 | 54 | 55 | print('\n'.join((str(value) for value in values))) 56 | 57 | 58 | result = p.parse("March 24th") 59 | 60 | 61 | # create an instance of Constants class and force it not to use PyICU 62 | # and to use the internal Spanish locale class 63 | 64 | c = pdt.Constants(localeID="es", usePyICU=False) 65 | p = pdt.Calendar(c) 66 | 67 | result = p.parse("Marzo 24") 68 | -------------------------------------------------------------------------------- /notes/THANKS.txt: -------------------------------------------------------------------------------- 1 | While the initial work on parsedatetime is based on my past work 2 | in dealing with validation of date/time text, the current code 3 | could not have happened without the help, aid and pokings of the 4 | following folks: 5 | 6 | Darshana Chhajed 7 | 8 | As the first "real" user of the new Python code, Darshana pointed 9 | out many of the initial flaws and bugs and then started fixing them! 10 | Her aide was so helpful that I was happy to add her as co-author. 11 | 12 | Ted Leung 13 | 14 | Early "sounding board" advice and continued support to make the 15 | code as open and useful as it is. 16 | 17 | Heikki Toivenan 18 | 19 | While Heikki hasn't aided directly he has been extremely helpful 20 | in making sure my Python code is clean and simple - any current 21 | ugliness is purely my own fault 22 | 23 | OSAF 24 | 25 | The folks at OSAF (Open Source Applications Foundation) helped a lot 26 | in shaping how the code works and in answering my "how do I do *this*" 27 | questions - especially Jeffrey, Philippe, Grant, Andi, Alex and Katie. 28 | -------------------------------------------------------------------------------- /notes/implementation_notes.txt: -------------------------------------------------------------------------------- 1 | 7EST 8EDT (\d)(edt|est|cdt)\s+ 2 | 4 digits (\d{4}) 3 | 2 or 4 digits (\d{4}|\d{2}) 4 | 2 digits (\d{2}) 5 | 1 or 2 digits (\d{1,2}) 6 | hour part of absolute timezone (?:[0-1][0-9]|2[0-3]) 7 | minute part of absolute timezone (?:[0-5][0-9]) 8 | absolute timezone +07 +07:00 -07 (GMT) 9 | (?:\s*([+-](?:|:|))(?:\s*\('(?:\[^)]+\))?) 10 | 11 | now, today 12 | yesterday, tomorrow 13 | +# -# 14 | noon, midnight, morning, evening, lunch 15 | at, of, on, future, later, past, next, prev, previous, prior 16 | 17 | 18 | 19 | units: hour, minute, second, day, week, month, year 20 | modifier: from, before, after, prior, prev, previous 21 | 22 | (?P\d+)\s+(?P[hour|minute|second|day|week|month|year]+) 23 | (?P[from|before|after|ago|prior]+)\s+ 24 | 25 | 5 minutes from now 26 | in 5 minutes 27 | 5 minutes ago 28 | 1 hour from noon 29 | last week 30 | second week from tomorrow 31 | 3 hrs from next monday 32 | third monday in may 33 | 34 | date parsing idea 35 | 36 | as text is typed start breaking it into chunks based on the characters: 37 | 38 | given this input: 2 Feb 2004 39 | 40 | first is '2' - it's a number so flag as literal 41 | next is space - that's a separator so call previous chunk processing 42 | - that means the 2 is "tagged" as literal/value/2 43 | next is F - label flag or keyword and it's relation to the previous 44 | chunk of literal/value implies a unit qualifier 45 | so search for any that start with F - possible offering 46 | a completion prompt for 'Feb' or 'from' 47 | next is e - matches potential list for Feb so offer that as completion 48 | next is b - matches potential list for Feb so .... 49 | next is space - finish tagging 'Feb' chunk, mark it as literal/value of month 50 | also note that we have a pattern of literal/value/2 and 51 | literal/value/month/Feb - so tag those two as a 'date' chunk 52 | possible offer completion of full date assuming current year 53 | next is 2 - it's a number so flag as literal - prior chunk is tagged 54 | as date so check for year and offer completion 55 | and so on 56 | 57 | with input of 2 am 58 | 59 | 2 - literal value 60 | space - mark previous as literal/value/2 61 | a - mark as label/keyword - matches pattern for am/pm so offer that 62 | m - mark as label/keyword - matches for am/pm so offer completion 63 | and since prior chunk is literal/value/2 flag this chunk as 'time' chunk 64 | 65 | 66 | Possible blog postings: 67 | 68 | 69 | note: posted the first part in my blog so I deleted the text from here 70 | 71 | === Thoughts on Chunking the human text into parseable bits 72 | 73 | After looking at the previously mentioned date and time parsing routines, it seemed to me that they all were very good at parsing specific problem domains. Now that seems obvious to most people as it did to me, but then I asked myself this question: 74 | 75 | '''What would happen if you took some of the basic tenants of grammar parsing and used that as a pre-filter to the date/time parsing problem?''' 76 | 77 | The answer came to me as an exciting _aha_ moment. It seemed to show that you could segment the text into chunks that could be handled by specialized parsing code. The chunks then could be parsed based on their relationships into a date/time result. 78 | 79 | Now I'm sure someone out in the intarnet will email me a reference to a paper or article that has that same thought and shows how to do it in haskell in under 20 lines, but since I don't have an academic background and all of my parsing training is self-taught, I'm excited to have figured it out for myself :) 80 | 81 | Now to see if it the theory holds up well when implemented. 82 | 83 | First I worked out on paper the chunks required for the following three test items: 84 | 85 | 1. in 5 minutes 86 | 2. one week from monday 87 | 3. next thursday 88 | 89 | Now I know that three items hardly a statistical sample makes, but the three rather simple test items will break all of the open-source parsers I've found to date. If you know of any that will do the job, please send me the links - I would love to compare notes. 90 | 91 | The details of the steps to identify the different chunks is as follows: 92 | 93 | 1. Identify any "specials". Specials are words that have little-to-no semantic information but hinder the chunk parsing. For example, the words I currently identify as special: "at", "of", "on", and "in". 94 | 95 | 2. Spot the reference chunk. A reference chunk is the item that serves as the "point of reference" from which all offsets are based. Most text items have an implied reference point of the current date/time but text items that have something like "from monday" or "from next tuesday" have explicit reference points. 96 | 97 | 3. Determine the offset direction. All offsets are from the reference point in either a past or future direction. This information is determined by spotting offset keywords, such as "at", "of", "on", "future", "later", "past", "next", "prev", "previous" and "prior". 98 | 99 | Once the above steps have given you the chunks, the next step is to go thru each chunk and parse it according to it's own unique requirements. 100 | 101 | === Thoughts on parsing "twenty five" into the value 25 102 | 103 | Now most people, including myself, just started snickering and thinking to themselves: "oh heck, that's *easy*". Yep - that one is. Now try parsing this: 104 | 105 | Twenty five thousand four hundred and eleven 106 | 107 | Not laughing any more are ya! (and if you are, please send me code! ;) 108 | 109 | First pass would be to remove any empty words ("and", "or") and then to start walking thru the text in reverse and storing the items into a stack. The trigger for when a phrase is pushed onto the stack is when you hit a units word. For the sample the unit-word triggers are, in right to left order: "hundred" and "thousand" 110 | 111 | After empty word removal: 112 | 113 | Twenty five thousand four hundred eleven 114 | 115 | After the first pass of looking for unit-words: 116 | 117 | [eleven] 118 | [four hundred] 119 | [Twenty five thousand] 120 | 121 | Now that nicely breaks up the phrases and also includes units for converting the values. 122 | 123 | Another test: 124 | 125 | Two hundred 126 | 127 | I'll skip the empty word step and show the unit-word result: 128 | 129 | [Two hundred] 130 | 131 | That simple example also worked and as far as I can tell, as long as you "require" that phrases have to have the proper unit-words, it will always work. Here is an example of text that doesn't follow the "always have unit-words" rule: 132 | 133 | twenty five forty three 134 | 135 | Humans reading the above can understand and know that the value is 2543. But I think that is a context-land-mine area for a computer parser. The method I outlined above would not handle this input at all simply because there are no unit words to give the parser hints at the value to give each word's numeric value. It would simply convert them to numbers and then add them all up and spit out the value of 68 (25 + 5 + 40 + 3). 136 | 137 | -------------------------------------------------------------------------------- /notes/locale_date_grouping_notes.txt: -------------------------------------------------------------------------------- 1 | http://wiki.osafoundation.org/script/getIrcTranscript.cgi?channel=chandler&date=20070518&startTime=1249 2 | 3 | DarshanaC so how are we supposed to change pdt 4 | bear hehehe - I don't quite know just yet :) 5 | DarshanaC hmmm 6 | bear but I think I will have to add some helper functions 7 | bear that can take the locale info and scan the text being passed in 8 | bear to identify any date related locale clues 9 | DarshanaC hmmm 10 | DarshanaC can we pass the locale as an input to parse() alongwith the text 11 | bear one problem is that I think big chunks of text are being passed to pdt to let it find the dates 12 | DarshanaC that is locale info is present 13 | bear yea - pdt will need to change so that it caches any pdc objects that are created 14 | bear I think a new entry point will be useful 15 | bear give it a new block of text and have it scan for date/time possibilities 16 | bear and then return a list of them with the parsed results 17 | * DarshanaC nods 18 | bear that could be where the locale work can be handled - that way the core code doesn't have to change much 19 | DarshanaC true 20 | bear and it could just be a simple list of words that can be date/time along with position, then make a pass to gather them into groups based on distance to each other 21 | bear set the distance threshold to something like 4 or 5 words and it may give a nice natural distribution 22 | bear would need to test that to find a good threshold value 23 | DarshanaC this is going to be complicated 24 | bear yep - for sure 25 | DarshanaC :( 26 | bear it borders on NLP 27 | bear I think we could make do with a brute force item first 28 | bear get a list of patterns that fit 80% 29 | bear thinks like ## or year 30 | bear and then just add 2-3 words in front and behind 31 | bear do a quick check for overlaps and then make that the list 32 | DarshanaC we have to make sure that we dont mistake date/time ranges for 2 sperate date/times 33 | bear then the locale code can loop over the text for the system locale and english - if a hit is found go with it 34 | bear yea 35 | bear but that can be part of the overlap check 36 | DarshanaC ok 37 | bear if the range trigger is found inbetween two groups then just group them 38 | DarshanaC hmm 39 | bear let me get some test code for this in the repository 40 | bear that way you don't have to spend a lot of time on it (unless you want to :) 41 | bear and then you can test it by trying to fit it into the bug fix 42 | DarshanaC I dont know how i am going to get time to work on this.. I am defending my MS on 05/24 and starting my new job on 05/29 43 | DarshanaC have to work on this on weekends :) 44 | bear no worries - I can do the up front part - that way you can integrate it 45 | DarshanaC ok 46 | bear yea - this doesn't have to be done next week 47 | bear just before preview ships :) 48 | DarshanaC when is that? 49 | bear and that won't be for a month 50 | DarshanaC ok 51 | DarshanaC will talk to you.. need to have lunch now 52 | DarshanaC later 53 | bear same here - later! 54 | DarshanaC bye 55 | * bear waves 56 | * bear copies convesation into a bug note 57 | -------------------------------------------------------------------------------- /parsedatetime/context.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | parsedatetime/context.py 4 | 5 | Context related classes 6 | 7 | """ 8 | 9 | from threading import local 10 | 11 | 12 | class pdtContextStack(object): 13 | """ 14 | A thread-safe stack to store context(s) 15 | 16 | Internally used by L{Calendar} object 17 | """ 18 | 19 | def __init__(self): 20 | self.__local = local() 21 | 22 | @property 23 | def __stack(self): 24 | if not hasattr(self.__local, 'stack'): 25 | self.__local.stack = [] 26 | return self.__local.stack 27 | 28 | def push(self, ctx): 29 | self.__stack.append(ctx) 30 | 31 | def pop(self): 32 | try: 33 | return self.__stack.pop() 34 | except IndexError: 35 | return None 36 | 37 | def last(self): 38 | try: 39 | return self.__stack[-1] 40 | except IndexError: 41 | raise RuntimeError('context stack is empty') 42 | 43 | def isEmpty(self): 44 | return not self.__stack 45 | 46 | 47 | class pdtContext(object): 48 | """ 49 | Context contains accuracy flag detected by L{Calendar.parse()} 50 | 51 | Accuracy flag uses bitwise-OR operation and is combined by: 52 | 53 | ACU_YEAR - "next year", "2014" 54 | ACU_MONTH - "March", "July 2014" 55 | ACU_WEEK - "last week", "next 3 weeks" 56 | ACU_DAY - "tomorrow", "July 4th 2014" 57 | ACU_HALFDAY - "morning", "tonight" 58 | ACU_HOUR - "18:00", "next hour" 59 | ACU_MIN - "18:32", "next 10 minutes" 60 | ACU_SEC - "18:32:55" 61 | ACU_NOW - "now" 62 | 63 | """ 64 | 65 | __slots__ = ('accuracy',) 66 | 67 | ACU_YEAR = 2 ** 0 68 | ACU_MONTH = 2 ** 1 69 | ACU_WEEK = 2 ** 2 70 | ACU_DAY = 2 ** 3 71 | ACU_HALFDAY = 2 ** 4 72 | ACU_HOUR = 2 ** 5 73 | ACU_MIN = 2 ** 6 74 | ACU_SEC = 2 ** 7 75 | ACU_NOW = 2 ** 8 76 | 77 | ACU_DATE = ACU_YEAR | ACU_MONTH | ACU_WEEK | ACU_DAY 78 | ACU_TIME = ACU_HALFDAY | ACU_HOUR | ACU_MIN | ACU_SEC | ACU_NOW 79 | 80 | _ACCURACY_MAPPING = [ 81 | (ACU_YEAR, 'year'), 82 | (ACU_MONTH, 'month'), 83 | (ACU_WEEK, 'week'), 84 | (ACU_DAY, 'day'), 85 | (ACU_HALFDAY, 'halfday'), 86 | (ACU_HOUR, 'hour'), 87 | (ACU_MIN, 'min'), 88 | (ACU_SEC, 'sec'), 89 | (ACU_NOW, 'now')] 90 | 91 | _ACCURACY_REVERSE_MAPPING = { 92 | 'year': ACU_YEAR, 93 | 'years': ACU_YEAR, 94 | 'month': ACU_MONTH, 95 | 'months': ACU_MONTH, 96 | 'week': ACU_WEEK, 97 | 'weeks': ACU_WEEK, 98 | 'day': ACU_DAY, 99 | 'days': ACU_DAY, 100 | 'halfday': ACU_HALFDAY, 101 | 'morning': ACU_HALFDAY, 102 | 'afternoon': ACU_HALFDAY, 103 | 'evening': ACU_HALFDAY, 104 | 'night': ACU_HALFDAY, 105 | 'tonight': ACU_HALFDAY, 106 | 'midnight': ACU_HALFDAY, 107 | 'hour': ACU_HOUR, 108 | 'hours': ACU_HOUR, 109 | 'min': ACU_MIN, 110 | 'minute': ACU_MIN, 111 | 'mins': ACU_MIN, 112 | 'minutes': ACU_MIN, 113 | 'sec': ACU_SEC, 114 | 'second': ACU_SEC, 115 | 'secs': ACU_SEC, 116 | 'seconds': ACU_SEC, 117 | 'now': ACU_NOW} 118 | 119 | def __init__(self, accuracy=0): 120 | """ 121 | Default constructor of L{pdtContext} class. 122 | 123 | @type accuracy: integer 124 | @param accuracy: Accuracy flag 125 | 126 | @rtype: object 127 | @return: L{pdtContext} instance 128 | """ 129 | self.accuracy = accuracy 130 | 131 | def updateAccuracy(self, *accuracy): 132 | """ 133 | Updates current accuracy flag 134 | """ 135 | for acc in accuracy: 136 | if not isinstance(acc, int): 137 | acc = self._ACCURACY_REVERSE_MAPPING[acc] 138 | self.accuracy |= acc 139 | 140 | def update(self, context): 141 | """ 142 | Uses another L{pdtContext} instance to update current one 143 | """ 144 | self.updateAccuracy(context.accuracy) 145 | 146 | @property 147 | def hasDate(self): 148 | """ 149 | Returns True if current context is accurate to date 150 | """ 151 | return bool(self.accuracy & self.ACU_DATE) 152 | 153 | @property 154 | def hasTime(self): 155 | """ 156 | Returns True if current context is accurate to time 157 | """ 158 | return bool(self.accuracy & self.ACU_TIME) 159 | 160 | @property 161 | def dateTimeFlag(self): 162 | """ 163 | Returns the old date/time flag code 164 | """ 165 | return int(self.hasDate and 1) | int(self.hasTime and 2) 166 | 167 | @property 168 | def hasDateOrTime(self): 169 | """ 170 | Returns True if current context is accurate to date/time 171 | """ 172 | return bool(self.accuracy) 173 | 174 | def __repr__(self): 175 | accuracy_repr = [] 176 | for acc, name in self._ACCURACY_MAPPING: 177 | if acc & self.accuracy: 178 | accuracy_repr.append('pdtContext.ACU_%s' % name.upper()) 179 | if accuracy_repr: 180 | accuracy_repr = 'accuracy=' + ' | '.join(accuracy_repr) 181 | else: 182 | accuracy_repr = '' 183 | 184 | return 'pdtContext(%s)' % accuracy_repr 185 | 186 | def __eq__(self, ctx): 187 | return self.accuracy == ctx.accuracy 188 | -------------------------------------------------------------------------------- /parsedatetime/parsedatetime.py: -------------------------------------------------------------------------------- 1 | # Backward compatibility fix. 2 | from . import * # noqa 3 | -------------------------------------------------------------------------------- /parsedatetime/pdt_locales/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | """ 4 | pdt_locales 5 | 6 | All of the included locale classes shipped with pdt. 7 | """ 8 | 9 | from __future__ import absolute_import 10 | from .icu import get_icu 11 | 12 | locales = ['de_DE', 'en_AU', 'en_US', 'es', 'nl_NL', 'pt_BR', 'ru_RU', 'fr_FR'] 13 | 14 | __locale_caches = {} 15 | 16 | __all__ = ['get_icu', 'load_locale'] 17 | 18 | 19 | def load_locale(locale, icu=False): 20 | """ 21 | Return data of locale 22 | :param locale: 23 | :return: 24 | """ 25 | if locale not in locales: 26 | raise NotImplementedError("The locale '%s' is not supported" % locale) 27 | if locale not in __locale_caches: 28 | mod = __import__(__name__, fromlist=[locale], level=0) 29 | __locale_caches[locale] = getattr(mod, locale) 30 | return __locale_caches[locale] 31 | -------------------------------------------------------------------------------- /parsedatetime/pdt_locales/base.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | from typing import Dict 3 | 4 | locale_keys = set([ 5 | 'MonthOffsets', 'Months', 'WeekdayOffsets', 'Weekdays', 6 | 'dateFormats', 'dateSep', 'dayOffsets', 'dp_order', 7 | 'localeID', 'meridian', 'Modifiers', 're_sources', 're_values', 8 | 'shortMonths', 'shortWeekdays', 'timeFormats', 'timeSep', 'units', 9 | 'uses24', 'usesMeridian', 'numbers', 'decimal_mark', 'small', 10 | 'magnitude', 'ignore']) 11 | 12 | localeID = 'en_US' 13 | 14 | dateSep = ['/', '.'] 15 | timeSep = [':'] 16 | meridian = ['AM', 'PM'] 17 | usesMeridian = True 18 | uses24 = True 19 | WeekdayOffsets: Dict = {} 20 | MonthOffsets: Dict = {} 21 | 22 | # always lowercase any lookup values - helper code expects that 23 | Weekdays = [ 24 | 'monday', 'tuesday', 'wednesday', 'thursday', 25 | 'friday', 'saturday', 'sunday', 26 | ] 27 | 28 | shortWeekdays = [ 29 | 'mon', 'tues|tue', 'wed', 'thu', 'fri', 'sat', 'sun', 30 | ] 31 | 32 | Months = [ 33 | 'january', 'february', 'march', 'april', 'may', 'june', 'july', 34 | 'august', 'september', 'october', 'november', 'december', 35 | ] 36 | 37 | shortMonths = [ 38 | 'jan', 'feb', 'mar', 'apr', 'may', 'jun', 39 | 'jul', 'aug', 'sep', 'oct', 'nov', 'dec', 40 | ] 41 | 42 | # use the same formats as ICU by default 43 | dateFormats = { 44 | 'full': 'EEEE, MMMM d, yyyy', 45 | 'long': 'MMMM d, yyyy', 46 | 'medium': 'MMM d, yyyy', 47 | 'short': 'M/d/yy' 48 | } 49 | 50 | timeFormats = { 51 | 'full': 'h:mm:ss a z', 52 | 'long': 'h:mm:ss a z', 53 | 'medium': 'h:mm:ss a', 54 | 'short': 'h:mm a', 55 | } 56 | 57 | dp_order = ['m', 'd', 'y'] 58 | 59 | # Used to parse expressions like "in 5 hours" 60 | numbers = { 61 | 'zero': 0, 62 | 'one': 1, 63 | 'a': 1, 64 | 'an': 1, 65 | 'two': 2, 66 | 'three': 3, 67 | 'four': 4, 68 | 'five': 5, 69 | 'six': 6, 70 | 'seven': 7, 71 | 'eight': 8, 72 | 'nine': 9, 73 | 'ten': 10, 74 | 'eleven': 11, 75 | 'thirteen': 13, 76 | 'fourteen': 14, 77 | 'fifteen': 15, 78 | 'sixteen': 16, 79 | 'seventeen': 17, 80 | 'eighteen': 18, 81 | 'nineteen': 19, 82 | 'twenty': 20, 83 | } 84 | 85 | decimal_mark = '.' 86 | 87 | 88 | # this will be added to re_values later 89 | units = { 90 | 'seconds': ['second', 'seconds', 'sec', 'secs', 's'], 91 | 'minutes': ['minute', 'minutes', 'min', 'mins', 'm'], 92 | 'hours': ['hour', 'hours', 'hr', 'h'], 93 | 'days': ['day', 'days', 'dy', 'd'], 94 | 'weeks': ['week', 'weeks', 'wk', 'w'], 95 | 'months': ['month', 'months', 'mth'], 96 | 'years': ['year', 'years', 'yr', 'y'], 97 | } 98 | 99 | 100 | # text constants to be used by later regular expressions 101 | re_values = { 102 | 'specials': 'in|on|of|at', 103 | 'timeseparator': ':', 104 | 'rangeseparator': '-', 105 | 'daysuffix': 'rd|st|nd|th', 106 | 'meridian': r'am|pm|a\.m\.|p\.m\.|a|p', 107 | 'qunits': 'h|m|s|d|w|y', 108 | 'now': ['now', 'right now'], 109 | } 110 | 111 | # Used to adjust the returned date before/after the source 112 | Modifiers = { 113 | 'from': 1, 114 | 'before': -1, 115 | 'after': 1, 116 | 'ago': -1, 117 | 'prior': -1, 118 | 'prev': -1, 119 | 'last': -1, 120 | 'next': 1, 121 | 'previous': -1, 122 | 'end of': 0, 123 | 'this': 0, 124 | 'eod': 1, 125 | 'eom': 1, 126 | 'eoy': 1, 127 | } 128 | 129 | dayOffsets = { 130 | 'tomorrow': 1, 131 | 'today': 0, 132 | 'yesterday': -1, 133 | } 134 | 135 | # special day and/or times, i.e. lunch, noon, evening 136 | # each element in the dictionary is a dictionary that is used 137 | # to fill in any value to be replace - the current date/time will 138 | # already have been populated by the method buildSources 139 | re_sources = { 140 | 'noon': {'hr': 12, 'mn': 0, 'sec': 0}, 141 | 'afternoon': {'hr': 13, 'mn': 0, 'sec': 0}, 142 | 'lunch': {'hr': 12, 'mn': 0, 'sec': 0}, 143 | 'morning': {'hr': 6, 'mn': 0, 'sec': 0}, 144 | 'breakfast': {'hr': 8, 'mn': 0, 'sec': 0}, 145 | 'dinner': {'hr': 19, 'mn': 0, 'sec': 0}, 146 | 'evening': {'hr': 18, 'mn': 0, 'sec': 0}, 147 | 'midnight': {'hr': 0, 'mn': 0, 'sec': 0}, 148 | 'night': {'hr': 21, 'mn': 0, 'sec': 0}, 149 | 'tonight': {'hr': 21, 'mn': 0, 'sec': 0}, 150 | 'eod': {'hr': 17, 'mn': 0, 'sec': 0}, 151 | } 152 | 153 | small = { 154 | 'zero': 0, 155 | 'one': 1, 156 | 'a': 1, 157 | 'an': 1, 158 | 'two': 2, 159 | 'three': 3, 160 | 'four': 4, 161 | 'five': 5, 162 | 'six': 6, 163 | 'seven': 7, 164 | 'eight': 8, 165 | 'nine': 9, 166 | 'ten': 10, 167 | 'eleven': 11, 168 | 'twelve': 12, 169 | 'thirteen': 13, 170 | 'fourteen': 14, 171 | 'fifteen': 15, 172 | 'sixteen': 16, 173 | 'seventeen': 17, 174 | 'eighteen': 18, 175 | 'nineteen': 19, 176 | 'twenty': 20, 177 | 'thirty': 30, 178 | 'forty': 40, 179 | 'fifty': 50, 180 | 'sixty': 60, 181 | 'seventy': 70, 182 | 'eighty': 80, 183 | 'ninety': 90 184 | } 185 | 186 | magnitude = { 187 | 'thousand': 1000, 188 | 'million': 1000000, 189 | 'billion': 1000000000, 190 | 'trillion': 1000000000000, 191 | 'quadrillion': 1000000000000000, 192 | 'quintillion': 1000000000000000000, 193 | 'sextillion': 1000000000000000000000, 194 | 'septillion': 1000000000000000000000000, 195 | 'octillion': 1000000000000000000000000000, 196 | 'nonillion': 1000000000000000000000000000000, 197 | 'decillion': 1000000000000000000000000000000000, 198 | } 199 | 200 | ignore = ('and', ',') 201 | -------------------------------------------------------------------------------- /parsedatetime/pdt_locales/de_DE.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | from .base import * # noqa 4 | 5 | # don't use an unicode string 6 | localeID = 'de_DE' 7 | dateSep = ['.'] 8 | timeSep = [':'] 9 | meridian = [] 10 | usesMeridian = False 11 | uses24 = True 12 | decimal_mark = ',' 13 | 14 | Weekdays = [ 15 | 'montag', 'dienstag', 'mittwoch', 16 | 'donnerstag', 'freitag', 'samstag', 'sonntag', 17 | ] 18 | shortWeekdays = ['mo', 'di', 'mi', 'do', 'fr', 'sa', 'so'] 19 | Months = [ 20 | 'januar', 'februar', 'märz', 21 | 'april', 'mai', 'juni', 22 | 'juli', 'august', 'september', 23 | 'oktober', 'november', 'dezember', 24 | ] 25 | shortMonths = [ 26 | 'jan', 'feb', 'mrz', 'apr', 'mai', 'jun', 27 | 'jul', 'aug', 'sep', 'okt', 'nov', 'dez', 28 | ] 29 | 30 | dateFormats = { 31 | 'full': 'EEEE, d. MMMM yyyy', 32 | 'long': 'd. MMMM yyyy', 33 | 'medium': 'dd.MM.yyyy', 34 | 'short': 'dd.MM.yy', 35 | } 36 | 37 | timeFormats = { 38 | 'full': 'HH:mm:ss v', 39 | 'long': 'HH:mm:ss z', 40 | 'medium': 'HH:mm:ss', 41 | 'short': 'HH:mm', 42 | } 43 | 44 | dp_order = ['d', 'm', 'y'] 45 | 46 | # the short version would be a capital M, 47 | # as I understand it we can't distinguish 48 | # between m for minutes and M for months. 49 | units = { 50 | 'seconds': ['sekunden', 'sek', 's'], 51 | 'minutes': ['minuten', 'min', 'm'], 52 | 'hours': ['stunden', 'std', 'h'], 53 | 'days': ['tag', 'tage', 't'], 54 | 'weeks': ['wochen', 'w'], 55 | 'months': ['monat', 'monate'], 56 | 'years': ['jahr', 'jahre', 'j'], 57 | } 58 | 59 | re_values = re_values.copy() 60 | re_values.update({ 61 | 'specials': 'am|dem|der|im|in|den|zum', 62 | 'timeseparator': ':', 63 | 'rangeseparator': '-', 64 | 'daysuffix': '', 65 | 'qunits': 'h|m|s|t|w|m|j', 66 | 'now': ['jetzt'], 67 | }) 68 | 69 | # Used to adjust the returned date before/after the source 70 | # still looking for insight on how to translate all of them to german. 71 | Modifiers = { 72 | 'from': 1, 73 | 'before': -1, 74 | 'after': 1, 75 | 'vergangener': -1, 76 | 'vorheriger': -1, 77 | 'prev': -1, 78 | 'letzter': -1, 79 | 'nächster': 1, 80 | 'dieser': 0, 81 | 'previous': -1, 82 | 'in a': 2, 83 | 'end of': 0, 84 | 'eod': 0, 85 | 'eo': 0, 86 | } 87 | 88 | # morgen/abermorgen does not work, see 89 | # http://code.google.com/p/parsedatetime/issues/detail?id=19 90 | dayOffsets = { 91 | 'morgen': 1, 92 | 'heute': 0, 93 | 'gestern': -1, 94 | 'vorgestern': -2, 95 | 'übermorgen': 2, 96 | } 97 | 98 | # special day and/or times, i.e. lunch, noon, evening 99 | # each element in the dictionary is a dictionary that is used 100 | # to fill in any value to be replace - the current date/time will 101 | # already have been populated by the method buildSources 102 | re_sources = { 103 | 'mittag': {'hr': 12, 'mn': 0, 'sec': 0}, 104 | 'mittags': {'hr': 12, 'mn': 0, 'sec': 0}, 105 | 'mittagessen': {'hr': 12, 'mn': 0, 'sec': 0}, 106 | 'morgen': {'hr': 6, 'mn': 0, 'sec': 0}, 107 | 'morgens': {'hr': 6, 'mn': 0, 'sec': 0}, 108 | 'frühstück': {'hr': 8, 'mn': 0, 'sec': 0}, 109 | 'abendessen': {'hr': 19, 'mn': 0, 'sec': 0}, 110 | 'abend': {'hr': 18, 'mn': 0, 'sec': 0}, 111 | 'abends': {'hr': 18, 'mn': 0, 'sec': 0}, 112 | 'mitternacht': {'hr': 0, 'mn': 0, 'sec': 0}, 113 | 'nacht': {'hr': 21, 'mn': 0, 'sec': 0}, 114 | 'nachts': {'hr': 21, 'mn': 0, 'sec': 0}, 115 | 'heute abend': {'hr': 21, 'mn': 0, 'sec': 0}, 116 | 'heute nacht': {'hr': 21, 'mn': 0, 'sec': 0}, 117 | 'feierabend': {'hr': 17, 'mn': 0, 'sec': 0}, 118 | } 119 | -------------------------------------------------------------------------------- /parsedatetime/pdt_locales/en_AU.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | from .base import * # noqa 4 | 5 | # don't use an unicode string 6 | localeID = 'en_AU' 7 | dateSep = ['-', '/'] 8 | uses24 = False 9 | 10 | dateFormats = { 11 | 'full': 'EEEE, d MMMM yyyy', 12 | 'long': 'd MMMM yyyy', 13 | 'medium': 'dd/MM/yyyy', 14 | 'short': 'd/MM/yy', 15 | } 16 | 17 | timeFormats['long'] = timeFormats['full'] 18 | 19 | dp_order = ['d', 'm', 'y'] 20 | -------------------------------------------------------------------------------- /parsedatetime/pdt_locales/en_US.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | from .base import * # noqa 4 | 5 | # don't use an unicode string 6 | uses24 = False 7 | -------------------------------------------------------------------------------- /parsedatetime/pdt_locales/es.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | from .base import * # noqa 4 | 5 | # don't use an unicode string 6 | localeID = 'es' 7 | dateSep = ['/'] 8 | usesMeridian = False 9 | uses24 = True 10 | decimal_mark = ',' 11 | 12 | Weekdays = [ 13 | 'lunes', 'martes', 'miércoles', 14 | 'jueves', 'viernes', 'sábado', 'domingo', 15 | ] 16 | shortWeekdays = [ 17 | 'lun', 'mar', 'mié', 18 | 'jue', 'vie', 'sáb', 'dom', 19 | ] 20 | Months = [ 21 | 'enero', 'febrero', 'marzo', 22 | 'abril', 'mayo', 'junio', 23 | 'julio', 'agosto', 'septiembre', 24 | 'octubre', 'noviembre', 'diciembre', 25 | ] 26 | shortMonths = [ 27 | 'ene', 'feb', 'mar', 28 | 'abr', 'may', 'jun', 29 | 'jul', 'ago', 'sep', 30 | 'oct', 'nov', 'dic', 31 | ] 32 | dateFormats = { 33 | 'full': "EEEE d' de 'MMMM' de 'yyyy", 34 | 'long': "d' de 'MMMM' de 'yyyy", 35 | 'medium': "dd-MMM-yy", 36 | 'short': "d/MM/yy", 37 | } 38 | 39 | timeFormats = { 40 | 'full': "HH'H'mm' 'ss z", 41 | 'long': "HH:mm:ss z", 42 | 'medium': "HH:mm:ss", 43 | 'short': "HH:mm", 44 | } 45 | 46 | dp_order = ['d', 'm', 'y'] 47 | -------------------------------------------------------------------------------- /parsedatetime/pdt_locales/fr_FR.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | from .base import * # noqa 4 | 5 | # don't use an unicode string 6 | localeID = 'fr_FR' 7 | dateSep = [r'\/'] 8 | timeSep = [':', 'h'] 9 | meridian = ['du matin', 'du soir'] 10 | usesMeridian = False 11 | uses24 = True 12 | WeekdayOffsets = {} 13 | MonthOffsets = {} 14 | 15 | # always lowercase any lookup values - helper code expects that 16 | Weekdays = [ 17 | 'lundi', 'mardi', 'mercredi', 'jeudi', 18 | 'vendredi', 'samedi', 'dimanche', 19 | ] 20 | 21 | shortWeekdays = [ 22 | 'lun', 'mar', 'mer', 'jeu', 'ven', 'sam', 'dim', 23 | ] 24 | 25 | Months = [ 26 | 'janvier', 'février|fevrier', 'mars', 'avril', 'mai', 'juin', 'juillet', 27 | 'août|aout', 'septembre', 'octobre', 'novembre', 'décembre|decembre', 28 | ] 29 | 30 | # We do not list 'mar' as a short name for 'mars' as it conflicts with 31 | # the 'mar' of 'mardi' 32 | shortMonths = [ 33 | 'jan', 'fév|fev', 'mars', 'avr', 'mai', 'jui', 34 | 'juil', 'aoû|aou', 'sep', 'oct', 'nov', 'déc|dec', 35 | ] 36 | 37 | # use the same formats as ICU by default 38 | dateFormats = { 39 | 'full': 'EEEE d MMMM yyyy', 40 | 'long': 'd MMMM yyyy', 41 | 'medium': 'd MMM yyyy', 42 | 'short': 'd/M/yy' 43 | } 44 | 45 | timeFormats = { 46 | 'full': 'h:mm:ss a z', 47 | 'long': 'h:mm:ss a z', 48 | 'medium': 'h:mm:ss a', 49 | 'short': 'h:mm a', 50 | } 51 | 52 | dp_order = ['d', 'm', 'y'] 53 | 54 | # Used to parse expressions like "in 5 hours" 55 | numbers = { 56 | 'zéro': 0, 57 | 'zero': 0, 58 | 'un': 1, 59 | 'une': 1, 60 | 'deux': 2, 61 | 'trois': 3, 62 | 'quatre': 4, 63 | 'cinq': 5, 64 | 'six': 6, 65 | 'sept': 7, 66 | 'huit': 8, 67 | 'neuf': 9, 68 | 'dix': 10, 69 | 'onze': 11, 70 | 'douze': 12, 71 | 'treize': 13, 72 | 'quatorze': 14, 73 | 'quinze': 15, 74 | 'seize': 16, 75 | 'dix-sept': 17, 76 | 'dix sept': 17, 77 | 'dix-huit': 18, 78 | 'dix huit': 18, 79 | 'dix-neuf': 19, 80 | 'dix neuf': 19, 81 | 'vingt': 20, 82 | 'vingt-et-un': 21, 83 | 'vingt et un': 21, 84 | 'vingt-deux': 22, 85 | 'vingt deux': 22, 86 | 'vingt-trois': 23, 87 | 'vingt trois': 23, 88 | 'vingt-quatre': 24, 89 | 'vingt quatre': 24, 90 | } 91 | 92 | decimal_mark = ',' 93 | 94 | # this will be added to re_values later 95 | units = { 96 | 'seconds': ['seconde', 'secondes', 'sec', 's'], 97 | 'minutes': ['minute', 'minutes', 'min', 'mn'], 98 | 'hours': ['heure', 'heures', 'h'], 99 | 'days': ['jour', 'jours', 'journée', 'journee', 'journées', 'journees', 'j'], 100 | 'weeks': ['semaine', 'semaines', 'sem'], 101 | 'months': ['mois', 'm'], 102 | 'years': ['année', 'annee', 'an', 'années', 'annees', 'ans'], 103 | } 104 | 105 | # text constants to be used by later regular expressions 106 | re_values = { 107 | 'specials': r'à|a|le|la|du|de', 108 | 'timeseparator': r'(?:\:|h|\s*heures?\s*)', 109 | 'rangeseparator': r'-', 110 | 'daysuffix': r'ième|ieme|ème|eme|ère|ere|nde', 111 | 'meridian': r'', 112 | 'qunits': r'h|m|s|j|sem|a', 113 | 'now': [r'maintenant', r'tout de suite', r'immédiatement', r'immediatement', r'à l\'instant', r'a l\'instant'], 114 | } 115 | 116 | # Used to adjust the returned date before/after the source 117 | Modifiers = { 118 | 'avant': -1, 119 | 'il y a': -1, 120 | 'plus tot': -1, 121 | 'plus tôt': -1, 122 | 'y a': -1, 123 | 'antérieur': -1, 124 | 'anterieur': -1, 125 | 'dernier': -1, 126 | 'dernière': -1, 127 | 'derniere': -1, 128 | 'précédent': -1, 129 | 'précedent': -1, 130 | 'precédent': -1, 131 | 'precedent': -1, 132 | 'fin de': 0, 133 | 'fin du': 0, 134 | 'fin de la': 0, 135 | 'fin des': 0, 136 | 'fin d\'': 0, 137 | 'ce': 0, 138 | 'cette': 0, 139 | 'depuis': 1, 140 | 'dans': 1, 141 | 'à partir': 1, 142 | 'a partir': 1, 143 | 'après': 1, 144 | 'apres': 1, 145 | 'lendemain': 1, 146 | 'prochain': 1, 147 | 'prochaine': 1, 148 | 'suivant': 1, 149 | 'suivante': 1, 150 | 'plus tard': 1 151 | } 152 | 153 | dayOffsets = { 154 | 'après-demain': 2, 155 | 'apres-demain': 2, 156 | 'après demain': 2, 157 | 'apres demain': 2, 158 | 'demain': 1, 159 | 'aujourd\'hui': 0, 160 | 'hier': -1, 161 | 'avant-hier': -2, 162 | 'avant hier': -2 163 | } 164 | 165 | # special day and/or times, i.e. lunch, noon, evening 166 | # each element in the dictionary is a dictionary that is used 167 | # to fill in any value to be replace - the current date/time will 168 | # already have been populated by the method buildSources 169 | re_sources = { 170 | 'après-midi': {'hr': 13, 'mn': 0, 'sec': 0}, 171 | 'apres-midi': {'hr': 13, 'mn': 0, 'sec': 0}, 172 | 'après midi': {'hr': 13, 'mn': 0, 'sec': 0}, 173 | 'apres midi': {'hr': 13, 'mn': 0, 'sec': 0}, 174 | 'midi': {'hr': 12, 'mn': 0, 'sec': 0}, 175 | 'déjeuner': {'hr': 12, 'mn': 0, 'sec': 0}, 176 | 'dejeuner': {'hr': 12, 'mn': 0, 'sec': 0}, 177 | 'matin': {'hr': 6, 'mn': 0, 'sec': 0}, 178 | 'petit-déjeuner': {'hr': 8, 'mn': 0, 'sec': 0}, 179 | 'petit-dejeuner': {'hr': 8, 'mn': 0, 'sec': 0}, 180 | 'petit déjeuner': {'hr': 8, 'mn': 0, 'sec': 0}, 181 | 'petit dejeuner': {'hr': 8, 'mn': 0, 'sec': 0}, 182 | 'diner': {'hr': 19, 'mn': 0, 'sec': 0}, 183 | 'dîner': {'hr': 19, 'mn': 0, 'sec': 0}, 184 | 'soir': {'hr': 18, 'mn': 0, 'sec': 0}, 185 | 'soirée': {'hr': 18, 'mn': 0, 'sec': 0}, 186 | 'soiree': {'hr': 18, 'mn': 0, 'sec': 0}, 187 | 'minuit': {'hr': 0, 'mn': 0, 'sec': 0}, 188 | 'nuit': {'hr': 21, 'mn': 0, 'sec': 0}, 189 | } 190 | 191 | small = { 192 | 'zéro': 0, 193 | 'zero': 0, 194 | 'un': 1, 195 | 'une': 1, 196 | 'deux': 2, 197 | 'trois': 3, 198 | 'quatre': 4, 199 | 'cinq': 5, 200 | 'six': 6, 201 | 'sept': 7, 202 | 'huit': 8, 203 | 'neuf': 9, 204 | 'dix': 10, 205 | 'onze': 11, 206 | 'douze': 12, 207 | 'treize': 13, 208 | 'quatorze': 14, 209 | 'quinze': 15, 210 | 'seize': 16, 211 | 'dix-sept': 17, 212 | 'dix sept': 17, 213 | 'dix-huit': 18, 214 | 'dix huit': 18, 215 | 'dix-neuf': 19, 216 | 'dix neuf': 19, 217 | 'vingt': 20, 218 | 'vingt-et-un': 21, 219 | 'vingt et un': 21, 220 | 'trente': 30, 221 | 'quarante': 40, 222 | 'cinquante': 50, 223 | 'soixante': 60, 224 | 'soixante-dix': 70, 225 | 'soixante dix': 70, 226 | 'quatre-vingt': 80, 227 | 'quatre vingt': 80, 228 | 'quatre-vingt-dix': 90, 229 | 'quatre vingt dix': 90 230 | } 231 | 232 | magnitude = { 233 | 'mille': 1000, 234 | 'millier': 1000, 235 | 'million': 1000000, 236 | 'milliard': 1000000000, 237 | 'trillion': 1000000000000, 238 | 'quadrillion': 1000000000000000, 239 | 'quintillion': 1000000000000000000, 240 | 'sextillion': 1000000000000000000000, 241 | 'septillion': 1000000000000000000000000, 242 | 'octillion': 1000000000000000000000000000, 243 | 'nonillion': 1000000000000000000000000000000, 244 | 'décillion': 1000000000000000000000000000000000, 245 | 'decillion': 1000000000000000000000000000000000, 246 | } 247 | 248 | ignore = ('et', ',') 249 | -------------------------------------------------------------------------------- /parsedatetime/pdt_locales/icu.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | from __future__ import absolute_import 3 | 4 | """ 5 | pdt_locales 6 | 7 | All of the included locale classes shipped with pdt. 8 | """ 9 | import datetime 10 | 11 | try: 12 | import pyicu # type: ignore 13 | except ImportError: 14 | pyicu = None 15 | 16 | 17 | def icu_object(mapping): 18 | return type('_icu', (object,), mapping) 19 | 20 | 21 | def merge_weekdays(base_wd, icu_wd): 22 | result = [] 23 | for left, right in zip(base_wd, icu_wd): 24 | if left == right: 25 | result.append(left) 26 | continue 27 | left = set(left.split('|')) 28 | right = set(right.split('|')) 29 | result.append('|'.join(left | right)) 30 | return result 31 | 32 | 33 | def get_icu(locale): 34 | 35 | def _sanitize_key(k): 36 | import re 37 | return re.sub("\\.(\\||$)", "\\1", k) 38 | 39 | from . import base 40 | result = dict([(key, getattr(base, key)) 41 | for key in dir(base) if not key.startswith('_')]) 42 | result['icu'] = None 43 | 44 | if pyicu is None: 45 | return icu_object(result) 46 | 47 | if locale is None: 48 | locale = 'en_US' 49 | result['icu'] = icu = pyicu.Locale(locale) 50 | 51 | if icu is None: 52 | return icu_object(result) 53 | 54 | # grab spelled out format of all numbers from 0 to 100 55 | rbnf = pyicu.RuleBasedNumberFormat(pyicu.URBNFRuleSetTag.SPELLOUT, icu) 56 | result['numbers'].update([(rbnf.format(i), i) for i in range(0, 100)]) 57 | 58 | symbols = result['symbols'] = pyicu.DateFormatSymbols(icu) 59 | 60 | # grab ICU list of weekdays, skipping first entry which 61 | # is always blank 62 | wd = [_sanitize_key(w.lower()) for w in symbols.getWeekdays()[1:]] 63 | swd = [_sanitize_key(sw.lower()) for sw in symbols.getShortWeekdays()[1:]] 64 | 65 | # store them in our list with Monday first (ICU puts Sunday first) 66 | result['Weekdays'] = merge_weekdays(result['Weekdays'], 67 | wd[1:] + wd[0:1]) 68 | result['shortWeekdays'] = merge_weekdays(result['shortWeekdays'], 69 | swd[1:] + swd[0:1]) 70 | result['Months'] = [_sanitize_key(m.lower()) for m in symbols.getMonths()] 71 | result['shortMonths'] = [_sanitize_key(sm.lower()) for sm in symbols.getShortMonths()] 72 | keys = ['full', 'long', 'medium', 'short'] 73 | 74 | createDateInstance = pyicu.DateFormat.createDateInstance 75 | createTimeInstance = pyicu.DateFormat.createTimeInstance 76 | icu_df = result['icu_df'] = { 77 | 'full': createDateInstance(pyicu.DateFormat.kFull, icu), 78 | 'long': createDateInstance(pyicu.DateFormat.kLong, icu), 79 | 'medium': createDateInstance(pyicu.DateFormat.kMedium, icu), 80 | 'short': createDateInstance(pyicu.DateFormat.kShort, icu), 81 | } 82 | icu_tf = result['icu_tf'] = { 83 | 'full': createTimeInstance(pyicu.DateFormat.kFull, icu), 84 | 'long': createTimeInstance(pyicu.DateFormat.kLong, icu), 85 | 'medium': createTimeInstance(pyicu.DateFormat.kMedium, icu), 86 | 'short': createTimeInstance(pyicu.DateFormat.kShort, icu), 87 | } 88 | 89 | result['dateFormats'] = {} 90 | result['timeFormats'] = {} 91 | for x in keys: 92 | result['dateFormats'][x] = icu_df[x].toPattern() 93 | result['timeFormats'][x] = icu_tf[x].toPattern() 94 | 95 | am = pm = ts = '' 96 | 97 | # ICU doesn't seem to provide directly the date or time separator 98 | # so we have to figure it out 99 | o = result['icu_tf']['short'] 100 | s = result['timeFormats']['short'] 101 | 102 | result['usesMeridian'] = 'a' in s 103 | result['uses24'] = 'H' in s 104 | 105 | # '11:45 AM' or '11:45' 106 | s = o.format(datetime.datetime(2003, 10, 30, 11, 45)) 107 | 108 | # ': AM' or ':' 109 | s = s.replace('11', '').replace('45', '') 110 | 111 | if len(s) > 0: 112 | ts = s[0] 113 | 114 | if result['usesMeridian']: 115 | # '23:45 AM' or '23:45' 116 | am = s[1:].strip() 117 | s = o.format(datetime.datetime(2003, 10, 30, 23, 45)) 118 | 119 | if result['uses24']: 120 | s = s.replace('23', '') 121 | else: 122 | s = s.replace('11', '') 123 | 124 | # 'PM' or '' 125 | pm = s.replace('45', '').replace(ts, '').strip() 126 | 127 | result['timeSep'] = [ts] 128 | result['meridian'] = [am, pm] if am and pm else [] 129 | 130 | o = result['icu_df']['short'] 131 | s = o.format(datetime.datetime(2003, 10, 30, 11, 45)) 132 | s = s.replace('10', '').replace('30', '').replace( 133 | '03', '').replace('2003', '') 134 | 135 | if len(s) > 0: 136 | ds = s[0] 137 | else: 138 | ds = '/' 139 | 140 | result['dateSep'] = [ds] 141 | s = result['dateFormats']['short'] 142 | ll = s.lower().split(ds) 143 | dp_order = [] 144 | 145 | for s in ll: 146 | if len(s) > 0: 147 | dp_order.append(s[:1]) 148 | 149 | result['dp_order'] = dp_order 150 | return icu_object(result) 151 | -------------------------------------------------------------------------------- /parsedatetime/pdt_locales/nl_NL.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | from .base import * # noqa 4 | 5 | # don't use an unicode string 6 | localeID = 'nl_NL' 7 | dateSep = ['-', '/'] 8 | timeSep = [':'] 9 | meridian = [] 10 | usesMeridian = False 11 | uses24 = True 12 | decimal_mark = ',' 13 | 14 | Weekdays = [ 15 | 'maandag', 'dinsdag', 'woensdag', 'donderdag', 16 | 'vrijdag', 'zaterdag', 'zondag', 17 | ] 18 | shortWeekdays = [ 19 | 'ma', 'di', 'wo', 'do', 'vr', 'za', 'zo', 20 | ] 21 | Months = [ 22 | 'januari', 'februari', 'maart', 'april', 'mei', 'juni', 'juli', 23 | 'augustus', 'september', 'oktober', 'november', 'december', 24 | ] 25 | shortMonths = [ 26 | 'jan', 'feb', 'mar', 'apr', 'mei', 'jun', 27 | 'jul', 'aug', 'sep', 'okt', 'nov', 'dec', 28 | ] 29 | dateFormats = { 30 | 'full': 'EEEE, dd MMMM yyyy', 31 | 'long': 'dd MMMM yyyy', 32 | 'medium': 'dd-MM-yyyy', 33 | 'short': 'dd-MM-yy', 34 | } 35 | 36 | timeFormats = { 37 | 'full': 'HH:mm:ss v', 38 | 'long': 'HH:mm:ss z', 39 | 'medium': 'HH:mm:ss', 40 | 'short': 'HH:mm', 41 | } 42 | 43 | dp_order = ['d', 'm', 'y'] 44 | 45 | # the short version would be a capital M, 46 | # as I understand it we can't distinguish 47 | # between m for minutes and M for months. 48 | units = { 49 | 'seconds': ['seconden', 'sec', 's'], 50 | 'minutes': ['minuten', 'min', 'm'], 51 | 'hours': ['uren', 'uur', 'h', 'u'], 52 | 'days': ['dagen', 'dag', 'd'], 53 | 'weeks': ['weken', 'w'], 54 | 'months': ['maanden', 'maand'], 55 | 'years': ['jaar', 'jaren', 'j'], 56 | } 57 | 58 | re_values = re_values.copy() 59 | re_values.update({ 60 | 'specials': 'om', 61 | 'timeseparator': ':', 62 | 'rangeseparator': '-', 63 | 'daysuffix': ' |de', 64 | 'qunits': 'h|m|s|d|w|m|j', 65 | 'now': ['nu'], 66 | }) 67 | 68 | # Used to adjust the returned date before/after the source 69 | # still looking for insight on how to translate all of them to dutch. 70 | Modifiers = { 71 | 'vanaf': 1, 72 | 'voor': -1, 73 | 'na': 1, 74 | 'eergisteren': -1, 75 | 'prev': -1, 76 | 'laatste': -1, 77 | 'volgende': 1, 78 | 'deze': 0, 79 | 'heden': 0, 80 | 'vorige': -1, 81 | 'vorig': -1, 82 | 'over': 2, 83 | 'eind van': 0, 84 | 'einde van': 0, 85 | } 86 | 87 | # morgen/overmorgen does not work, see 88 | # http://code.google.com/p/parsedatetime/issues/detail?id=19 89 | dayOffsets = { 90 | 'morgen': 1, 91 | 'vandaag': 0, 92 | 'gisteren': -1, 93 | 'eergisteren': -2, 94 | 'overmorgen': 2, 95 | } 96 | 97 | # special day and/or times, i.e. lunch, noon, evening 98 | # each element in the dictionary is a dictionary that is used 99 | # to fill in any value to be replace - the current date/time will 100 | # already have been populated by the method buildSources 101 | re_sources = { 102 | 'middag': {'hr': 12, 'mn': 0, 'sec': 0}, 103 | 'vanmiddag': {'hr': 12, 'mn': 0, 'sec': 0}, 104 | 'lunch': {'hr': 12, 'mn': 0, 'sec': 0}, 105 | 'morgen': {'hr': 6, 'mn': 0, 'sec': 0}, 106 | "'s morgens": {'hr': 6, 'mn': 0, 'sec': 0}, 107 | 'vanmorgen': {'hr': 6, 'mn': 0, 'sec': 0}, 108 | 'ontbijt': {'hr': 8, 'mn': 0, 'sec': 0}, 109 | 'avondeten': {'hr': 19, 'mn': 0, 'sec': 0}, 110 | 'avond': {'hr': 18, 'mn': 0, 'sec': 0}, 111 | 'avonds': {'hr': 18, 'mn': 0, 'sec': 0}, 112 | 'middernacht': {'hr': 0, 'mn': 0, 'sec': 0}, 113 | 'nacht': {'hr': 21, 'mn': 0, 'sec': 0}, 114 | 'nachts': {'hr': 21, 'mn': 0, 'sec': 0}, 115 | 'vanavond': {'hr': 21, 'mn': 0, 'sec': 0}, 116 | 'vannacht': {'hr': 21, 'mn': 0, 'sec': 0}, 117 | } 118 | -------------------------------------------------------------------------------- /parsedatetime/pdt_locales/pt_BR.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | from .base import * # noqa 4 | 5 | # don't use an unicode string 6 | localeID = 'pt_BR' 7 | dateSep = ['/'] 8 | usesMeridian = False 9 | uses24 = True 10 | decimal_mark = ',' 11 | 12 | Weekdays = [ 13 | 'segunda-feira', 'terça-feira', 'quarta-feira', 14 | 'quinta-feira', 'sexta-feira', 'sábado', 'domingo', 15 | ] 16 | shortWeekdays = [ 17 | 'seg', 'ter', 'qua', 'qui', 'sex', 'sáb', 'dom', 18 | ] 19 | Months = [ 20 | 'janeiro', 'fevereiro', 'março', 'abril', 'maio', 'junho', 'julho', 21 | 'agosto', 'setembro', 'outubro', 'novembro', 'dezembro' 22 | ] 23 | shortMonths = [ 24 | 'jan', 'fev', 'mar', 'abr', 'mai', 'jun', 25 | 'jul', 'ago', 'set', 'out', 'nov', 'dez' 26 | ] 27 | dateFormats = { 28 | 'full': "EEEE, d' de 'MMMM' de 'yyyy", 29 | 'long': "d' de 'MMMM' de 'yyyy", 30 | 'medium': "dd-MM-yy", 31 | 'short': "dd/MM/yyyy", 32 | } 33 | 34 | timeFormats = { 35 | 'full': "HH'H'mm' 'ss z", 36 | 'long': "HH:mm:ss z", 37 | 'medium': "HH:mm:ss", 38 | 'short': "HH:mm", 39 | } 40 | 41 | dp_order = ['d', 'm', 'y'] 42 | 43 | units = { 44 | 'seconds': ['segundo', 'seg', 's'], 45 | 'minutes': ['minuto', 'min', 'm'], 46 | 'days': ['dia', 'dias', 'd'], 47 | 'months': ['mês', 'meses'], 48 | } 49 | -------------------------------------------------------------------------------- /parsedatetime/pdt_locales/ru_RU.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | from .base import * # noqa 4 | 5 | # don't use an unicode string 6 | localeID = 'ru_RU' 7 | dateSep = ['-', '.'] 8 | timeSep = [':'] 9 | meridian = [] 10 | usesMeridian = False 11 | uses24 = True 12 | 13 | Weekdays = [ 14 | 'понедельник', 'вторник', 'среда', 'четверг', 15 | 'пятница', 'суббота', 'воскресенье', 16 | ] 17 | shortWeekdays = [ 18 | 'пн', 'вт', 'ср', 'чт', 'пт', 'сб', 'вс', 19 | ] 20 | # library does not know how to conjugate words 21 | # библиотека не умеет спрягать слова 22 | Months = [ 23 | 'января', 'февраля', 'марта', 'апреля', 'мая', 'июня', 'июля', 24 | 'августа', 'сентября', 'октября', 'ноября', 'декабря', 25 | ] 26 | shortMonths = [ 27 | 'янв', 'фев', 'мрт', 'апр', 'май', 'июн', 28 | 'июл', 'авг', 'сен', 'окт', 'нбр', 'дек', 29 | ] 30 | dateFormats = { 31 | 'full': 'EEEE, dd MMMM yyyy', 32 | 'long': 'dd MMMM yyyy', 33 | 'medium': 'dd-MM-yyyy', 34 | 'short': 'dd-MM-yy', 35 | } 36 | 37 | timeFormats = { 38 | 'full': 'HH:mm:ss v', 39 | 'long': 'HH:mm:ss z', 40 | 'medium': 'HH:mm:ss', 41 | 'short': 'HH:mm', 42 | } 43 | 44 | dp_order = ['d', 'm', 'y'] 45 | 46 | decimal_mark = '.' 47 | 48 | units = { 49 | 'seconds': ['секунда', 'секунды', 'секунд', 'сек', 'с'], 50 | 'minutes': ['минута', 'минуты', 'минут', 'мин', 'м'], 51 | 'hours': ['час', 'часов', 'часа', 'ч'], 52 | 'days': ['день', 'дней', 'д'], 53 | 'weeks': ['неделя', 'недели', 'н'], 54 | 'months': ['месяц', 'месяца', 'мес'], 55 | 'years': ['год', 'года', 'годы', 'г'], 56 | } 57 | 58 | re_values = re_values.copy() 59 | re_values.update({ 60 | 'specials': 'om', 61 | 'timeseparator': ':', 62 | 'rangeseparator': '-', 63 | 'daysuffix': 'ого|ой|ий|тье', 64 | 'qunits': 'д|мес|г|ч|н|м|с', 65 | 'now': ['сейчас'], 66 | }) 67 | 68 | Modifiers = { 69 | 'после': 1, 70 | 'назад': -1, 71 | 'предыдущий': -1, 72 | 'последний': -1, 73 | 'далее': 1, 74 | 'ранее': -1, 75 | } 76 | 77 | dayOffsets = { 78 | 'завтра': 1, 79 | 'сегодня': 0, 80 | 'вчера': -1, 81 | 'позавчера': -2, 82 | 'послезавтра': 2, 83 | } 84 | 85 | re_sources = { 86 | 'полдень': {'hr': 12, 'mn': 0, 'sec': 0}, 87 | 'день': {'hr': 13, 'mn': 0, 'sec': 0}, 88 | 'обед': {'hr': 12, 'mn': 0, 'sec': 0}, 89 | 'утро': {'hr': 6, 'mn': 0, 'sec': 0}, 90 | 'завтрак': {'hr': 8, 'mn': 0, 'sec': 0}, 91 | 'ужин': {'hr': 19, 'mn': 0, 'sec': 0}, 92 | 'вечер': {'hr': 18, 'mn': 0, 'sec': 0}, 93 | 'полночь': {'hr': 0, 'mn': 0, 'sec': 0}, 94 | 'ночь': {'hr': 21, 'mn': 0, 'sec': 0}, 95 | } 96 | 97 | small = { 98 | 'ноль': 0, 99 | 'один': 1, 100 | 'два': 2, 101 | 'три': 3, 102 | 'четыре': 4, 103 | 'пять': 5, 104 | 'шесть': 6, 105 | 'семь': 7, 106 | 'восемь': 8, 107 | 'девять': 9, 108 | 'десять': 10, 109 | 'одиннадцать': 11, 110 | 'двенадцать': 12, 111 | 'тринадцать': 13, 112 | 'четырнадцать': 14, 113 | 'пятнадцать': 15, 114 | 'шестнадцать': 16, 115 | 'семнадцать': 17, 116 | 'восемнадцать': 18, 117 | 'девятнадцать': 19, 118 | 'двадцать': 20, 119 | 'тридцать': 30, 120 | 'сорок': 40, 121 | 'пятьдесят': 50, 122 | 'шестьдесят': 60, 123 | 'семьдесят': 70, 124 | 'восемьдесят': 80, 125 | 'девяносто': 90, 126 | } 127 | 128 | numbers = { 129 | 'ноль': 0, 130 | 'один': 1, 131 | 'два': 2, 132 | 'три': 3, 133 | 'четыре': 4, 134 | 'пять': 5, 135 | 'шесть': 6, 136 | 'семь': 7, 137 | 'восемь': 8, 138 | 'девять': 9, 139 | 'десять': 10, 140 | 'одиннадцать': 11, 141 | 'двенадцать': 12, 142 | 'тринадцать': 13, 143 | 'четырнадцать': 14, 144 | 'пятнадцать': 15, 145 | 'шестнадцать': 16, 146 | 'семнадцать': 17, 147 | 'восемнадцать': 18, 148 | 'девятнадцать': 19, 149 | 'двадцать': 20, 150 | } 151 | 152 | magnitude = { 153 | 'тысяча': 1000, 154 | 'миллион': 1000000, 155 | 'миллиард': 1000000000, 156 | 'триллион': 1000000000000, 157 | 'квадриллион': 1000000000000000, 158 | 'квинтиллион': 1000000000000000000, 159 | 'секстиллион': 1000000000000000000000, 160 | 'септиллион': 1000000000000000000000000, 161 | 'октиллион': 1000000000000000000000000000, 162 | 'нониллион': 1000000000000000000000000000000, 163 | 'дециллион': 1000000000000000000000000000000000, 164 | } 165 | -------------------------------------------------------------------------------- /parsedatetime/warns.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | parsedatetime/warns.py 4 | 5 | All subclasses inherited from `Warning` class 6 | 7 | """ 8 | from __future__ import absolute_import 9 | 10 | import warnings 11 | 12 | 13 | class pdtDeprecationWarning(DeprecationWarning): 14 | pass 15 | 16 | 17 | class pdtPendingDeprecationWarning(PendingDeprecationWarning): 18 | pass 19 | 20 | 21 | class pdt20DeprecationWarning(pdtPendingDeprecationWarning): 22 | pass 23 | 24 | 25 | warnings.simplefilter('default', pdtDeprecationWarning) 26 | warnings.simplefilter('ignore', pdtPendingDeprecationWarning) 27 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | norecursedirs = .tox 3 | python_files=Test*.py 4 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:recommended" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [check-manifest] 2 | ignore = 3 | violations.txt 4 | 5 | [flake8] 6 | ignore = E111,E124,E126,E201,E202,E221,E241,E302,E501,F405 7 | 8 | [pep8] 9 | ignore = E111,E124,E126,E128,E201,E202,E221,E226,E241,E301,E302,E303,E402,E501,W291,F405 10 | max-line-length = 160 11 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import os 5 | import re 6 | import codecs 7 | 8 | from setuptools import setup, find_packages 9 | 10 | cwd = os.path.abspath(os.path.dirname(__file__)) 11 | 12 | def read(filename): 13 | with codecs.open(os.path.join(cwd, filename), 'rb', 'utf-8') as h: 14 | return h.read() 15 | 16 | metadata = read(os.path.join(cwd, 'parsedatetime', '__init__.py')) 17 | 18 | def extract_metaitem(meta): 19 | # swiped from https://hynek.me 's attr package 20 | meta_match = re.search(r"""^__{meta}__\s+=\s+['\"]([^'\"]*)['\"]""".format(meta=meta), 21 | metadata, re.MULTILINE) 22 | if meta_match: 23 | return meta_match.group(1) 24 | raise RuntimeError('Unable to find __{meta}__ string.'.format(meta=meta)) 25 | 26 | setup( 27 | name='parsedatetime', 28 | version=extract_metaitem('version'), 29 | author=extract_metaitem('author'), 30 | author_email=extract_metaitem('email'), 31 | url=extract_metaitem('url'), 32 | download_url=extract_metaitem('download_url'), 33 | description=extract_metaitem('description'), 34 | license=extract_metaitem('license'), 35 | packages=find_packages(exclude=['tests', 'docs']), 36 | platforms=['Any'], 37 | long_description=read('README.md'), 38 | tests_require=['pytest'], 39 | test_suite='tests', 40 | classifiers=[ 41 | 'Development Status :: 5 - Production/Stable', 42 | 'Intended Audience :: Developers', 43 | 'License :: OSI Approved :: Apache Software License', 44 | 'Operating System :: OS Independent', 45 | 'Topic :: Text Processing', 46 | 'Topic :: Software Development :: Libraries :: Python Modules', 47 | 'Programming Language :: Python :: 3', 48 | 'Programming Language :: Python :: 3.9', 49 | ] 50 | ) 51 | -------------------------------------------------------------------------------- /tests/TestAlternativeAbbreviations.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import sys 3 | import time 4 | import datetime 5 | import unittest 6 | import parsedatetime as pdt 7 | from parsedatetime.pdt_locales import get_icu 8 | from parsedatetime.context import pdtContext 9 | from . import utils 10 | 11 | 12 | pdtLocale_en = get_icu('en_US') 13 | pdtLocale_en.Weekdays = [ 14 | 'monday', 'tuesday', 'wednesday', 15 | 'thursday', 'friday', 'saturday', 'sunday'] 16 | 17 | pdtLocale_en.shortWeekdays = [ 18 | 'mon|mond', 'tue|tues', 'wed|wedn', 19 | 'thu|thur|thurs', 'fri|frid', 'sat|sa', 'sun|su'] 20 | 21 | pdtLocale_en.Months = [ 22 | 'january', 'february', 'march', 'april', 'may', 'june', 23 | 'july', 'august', 'september', 'october', 'november', 'december'] 24 | 25 | pdtLocale_en.shortMonths = [ 26 | 'jan|janu', 'feb|febr', 'mar|marc', 'apr|apri', 'may', 'jun|june', 27 | 'jul', 'aug|augu', 'sep|sept', 'oct|octo', 'nov|novem', 'dec|decem'] 28 | 29 | 30 | class test(unittest.TestCase): 31 | 32 | @utils.assertEqualWithComparator 33 | def assertExpectedResult(self, result, check, **kwargs): 34 | return utils.compareResultByTimeTuplesAndFlags(result, check, **kwargs) 35 | 36 | def setUp(self): 37 | pdt.pdtLocales['en_us'] = pdtLocale_en # override for the test 38 | self.ptc = pdt.Constants('en_us', usePyICU=False) 39 | self.cal = pdt.Calendar(self.ptc) 40 | (self.yr, self.mth, self.dy, self.hr, 41 | self.mn, self.sec, self.wd, self.yd, self.isdst) = time.localtime() 42 | 43 | def testDaysOfWeek(self): 44 | start = datetime.datetime( 45 | 2014, 10, 25, self.hr, self.mn, self.sec).timetuple() 46 | 47 | target = datetime.datetime( 48 | 2014, 10, 26, self.hr, self.mn, self.sec).timetuple() 49 | self.assertExpectedResult(self.cal.parse('sunday', start), (target, pdtContext(pdtContext.ACU_DAY))) 50 | self.assertExpectedResult(self.cal.parse('sun', start), (target, pdtContext(pdtContext.ACU_DAY))) 51 | self.assertExpectedResult(self.cal.parse('su', start), (target, pdtContext(pdtContext.ACU_DAY))) 52 | 53 | target = datetime.datetime( 54 | 2014, 10, 27, self.hr, self.mn, self.sec).timetuple() 55 | self.assertExpectedResult(self.cal.parse('Monday', start), (target, pdtContext(pdtContext.ACU_DAY))) 56 | self.assertExpectedResult(self.cal.parse('mon', start), (target, pdtContext(pdtContext.ACU_DAY))) 57 | self.assertExpectedResult(self.cal.parse('mond', start), (target, pdtContext(pdtContext.ACU_DAY))) 58 | 59 | target = datetime.datetime( 60 | 2014, 10, 28, self.hr, self.mn, self.sec).timetuple() 61 | self.assertExpectedResult( 62 | self.cal.parse('tuesday', start), (target, pdtContext(pdtContext.ACU_DAY))) 63 | self.assertExpectedResult(self.cal.parse('tues', start), (target, pdtContext(pdtContext.ACU_DAY))) 64 | self.assertExpectedResult(self.cal.parse('tue', start), (target, pdtContext(pdtContext.ACU_DAY))) 65 | 66 | target = datetime.datetime( 67 | 2014, 10, 29, self.hr, self.mn, self.sec).timetuple() 68 | self.assertExpectedResult( 69 | self.cal.parse('wednesday', start), (target, pdtContext(pdtContext.ACU_DAY))) 70 | self.assertExpectedResult(self.cal.parse('wedn', start), (target, pdtContext(pdtContext.ACU_DAY))) 71 | self.assertExpectedResult(self.cal.parse('wed', start), (target, pdtContext(pdtContext.ACU_DAY))) 72 | 73 | target = datetime.datetime( 74 | 2014, 10, 30, self.hr, self.mn, self.sec).timetuple() 75 | self.assertExpectedResult( 76 | self.cal.parse('thursday', start), (target, pdtContext(pdtContext.ACU_DAY))) 77 | self.assertExpectedResult(self.cal.parse('thu', start), (target, pdtContext(pdtContext.ACU_DAY))) 78 | self.assertExpectedResult(self.cal.parse('thur', start), (target, pdtContext(pdtContext.ACU_DAY))) 79 | self.assertExpectedResult(self.cal.parse('thurs', start), (target, pdtContext(pdtContext.ACU_DAY))) 80 | 81 | target = datetime.datetime( 82 | 2014, 10, 31, self.hr, self.mn, self.sec).timetuple() 83 | self.assertExpectedResult(self.cal.parse('friday', start), (target, pdtContext(pdtContext.ACU_DAY))) 84 | self.assertExpectedResult(self.cal.parse('fri', start), (target, pdtContext(pdtContext.ACU_DAY))) 85 | self.assertExpectedResult(self.cal.parse('frid', start), (target, pdtContext(pdtContext.ACU_DAY))) 86 | 87 | target = datetime.datetime( 88 | 2014, 11, 1, self.hr, self.mn, self.sec).timetuple() 89 | self.assertExpectedResult( 90 | self.cal.parse('saturday', start), (target, pdtContext(pdtContext.ACU_DAY))) 91 | self.assertExpectedResult(self.cal.parse('sat', start), (target, pdtContext(pdtContext.ACU_DAY))) 92 | self.assertExpectedResult(self.cal.parse('sa', start), (target, pdtContext(pdtContext.ACU_DAY))) 93 | 94 | def testMonths(self): 95 | start = datetime.datetime( 96 | 2014, 1, 1, self.hr, self.mn, self.sec).timetuple() 97 | for dates, expected_date in [ 98 | ('jan|janu|january', datetime.datetime( 99 | 2014, 1, 1, self.hr, self.mn, self.sec).timetuple()), 100 | ('feb|febr|february', datetime.datetime( 101 | 2014, 2, 1, self.hr, self.mn, self.sec).timetuple()), 102 | ('mar|marc|march', datetime.datetime( 103 | 2014, 3, 1, self.hr, self.mn, self.sec).timetuple()), 104 | ('apr|apri|april', datetime.datetime( 105 | 2014, 4, 1, self.hr, self.mn, self.sec).timetuple()), 106 | ('may|may', datetime.datetime( 107 | 2014, 5, 1, self.hr, self.mn, self.sec).timetuple()), 108 | ('jun|june', datetime.datetime( 109 | 2014, 6, 1, self.hr, self.mn, self.sec).timetuple()), 110 | ('jul|july', datetime.datetime( 111 | 2014, 7, 1, self.hr, self.mn, self.sec).timetuple()), 112 | ('aug|augu|august', datetime.datetime( 113 | 2014, 8, 1, self.hr, self.mn, self.sec).timetuple()), 114 | ('sep|sept|september', datetime.datetime( 115 | 2014, 9, 1, self.hr, self.mn, self.sec).timetuple()), 116 | ('oct|octo|october', datetime.datetime( 117 | 2014, 10, 1, self.hr, self.mn, self.sec).timetuple()), 118 | ('nov|novem|november', datetime.datetime( 119 | 2014, 11, 1, self.hr, self.mn, self.sec).timetuple()), 120 | ('dec|decem|december', datetime.datetime( 121 | 2014, 12, 1, self.hr, self.mn, self.sec).timetuple()) 122 | ]: 123 | for dateText in dates.split("|"): 124 | # print dateText 125 | self.assertExpectedResult( 126 | self.cal.parse(dateText, start), (expected_date, pdtContext(pdtContext.ACU_MONTH))) 127 | -------------------------------------------------------------------------------- /tests/TestAustralianLocale.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Test parsing of simple date and times using the Australian locale 4 | """ 5 | import sys 6 | import time 7 | import datetime 8 | import unittest 9 | import parsedatetime as pdt 10 | from parsedatetime.context import pdtContext 11 | from . import utils 12 | 13 | 14 | class test(unittest.TestCase): 15 | 16 | @utils.assertEqualWithComparator 17 | def assertExpectedResult(self, result, check, **kwargs): 18 | return utils.compareResultByTimeTuplesAndFlags(result, check, **kwargs) 19 | 20 | def setUp(self): 21 | self.ptc = pdt.Constants('en_AU', usePyICU=False) 22 | self.cal = pdt.Calendar(self.ptc) 23 | 24 | (self.yr, self.mth, self.dy, self.hr, 25 | self.mn, self.sec, self.wd, self.yd, self.isdst) = time.localtime() 26 | 27 | if self.ptc.localeID != 'en_AU': 28 | raise unittest.SkipTest( 29 | 'Locale not set to en_AU - check if PyICU is installed') 30 | 31 | def testTimes(self): 32 | start = datetime.datetime( 33 | self.yr, self.mth, self.dy, self.hr, self.mn, self.sec).timetuple() 34 | target = datetime.datetime( 35 | self.yr, self.mth, self.dy, 23, 0, 0).timetuple() 36 | 37 | self.assertExpectedResult( 38 | self.cal.parse('11:00:00 PM', start), (target, pdtContext(pdtContext.ACU_HOUR | pdtContext.ACU_MIN | pdtContext.ACU_SEC))) 39 | self.assertExpectedResult( 40 | self.cal.parse('11:00 PM', start), (target, pdtContext(pdtContext.ACU_HOUR | pdtContext.ACU_MIN))) 41 | self.assertExpectedResult(self.cal.parse('11 PM', start), (target, pdtContext(pdtContext.ACU_HOUR))) 42 | self.assertExpectedResult(self.cal.parse('11PM', start), (target, pdtContext(pdtContext.ACU_HOUR))) 43 | self.assertExpectedResult(self.cal.parse('2300', start), (target, pdtContext(pdtContext.ACU_HOUR | pdtContext.ACU_MIN))) 44 | self.assertExpectedResult(self.cal.parse('23:00', start), (target, pdtContext(pdtContext.ACU_HOUR | pdtContext.ACU_MIN))) 45 | self.assertExpectedResult(self.cal.parse('11p', start), (target, pdtContext(pdtContext.ACU_HOUR))) 46 | self.assertExpectedResult(self.cal.parse('11pm', start), (target, pdtContext(pdtContext.ACU_HOUR))) 47 | 48 | target = datetime.datetime( 49 | self.yr, self.mth, self.dy, 11, 0, 0).timetuple() 50 | 51 | self.assertExpectedResult( 52 | self.cal.parse('11:00:00 AM', start), (target, pdtContext(pdtContext.ACU_HOUR | pdtContext.ACU_MIN | pdtContext.ACU_SEC))) 53 | self.assertExpectedResult( 54 | self.cal.parse('11:00 AM', start), (target, pdtContext(pdtContext.ACU_HOUR | pdtContext.ACU_MIN))) 55 | self.assertExpectedResult(self.cal.parse('11 AM', start), (target, pdtContext(pdtContext.ACU_HOUR))) 56 | self.assertExpectedResult(self.cal.parse('11AM', start), (target, pdtContext(pdtContext.ACU_HOUR))) 57 | self.assertExpectedResult(self.cal.parse('1100', start), (target, pdtContext(pdtContext.ACU_HOUR | pdtContext.ACU_MIN))) 58 | self.assertExpectedResult(self.cal.parse('11:00', start), (target, pdtContext(pdtContext.ACU_HOUR | pdtContext.ACU_MIN))) 59 | self.assertExpectedResult(self.cal.parse('11a', start), (target, pdtContext(pdtContext.ACU_HOUR))) 60 | self.assertExpectedResult(self.cal.parse('11am', start), (target, pdtContext(pdtContext.ACU_HOUR))) 61 | 62 | target = datetime.datetime( 63 | self.yr, self.mth, self.dy, 7, 30, 0).timetuple() 64 | 65 | self.assertExpectedResult(self.cal.parse('730', start), (target, pdtContext(pdtContext.ACU_HOUR | pdtContext.ACU_MIN))) 66 | self.assertExpectedResult(self.cal.parse('0730', start), (target, pdtContext(pdtContext.ACU_HOUR | pdtContext.ACU_MIN))) 67 | 68 | target = datetime.datetime( 69 | self.yr, self.mth, self.dy, 17, 30, 0).timetuple() 70 | 71 | self.assertExpectedResult(self.cal.parse('1730', start), (target, pdtContext(pdtContext.ACU_HOUR | pdtContext.ACU_MIN))) 72 | self.assertExpectedResult(self.cal.parse('173000', start), (target, pdtContext(pdtContext.ACU_HOUR | pdtContext.ACU_MIN | pdtContext.ACU_SEC))) 73 | 74 | def testDates(self): 75 | start = datetime.datetime( 76 | self.yr, self.mth, self.dy, self.hr, self.mn, self.sec).timetuple() 77 | target = datetime.datetime( 78 | 2006, 8, 25, self.hr, self.mn, self.sec).timetuple() 79 | 80 | self.assertExpectedResult( 81 | self.cal.parse('25-08-2006', start), (target, pdtContext(pdtContext.ACU_YEAR | pdtContext.ACU_MONTH | pdtContext.ACU_DAY))) 82 | self.assertExpectedResult( 83 | self.cal.parse('25/08/2006', start), (target, pdtContext(pdtContext.ACU_YEAR | pdtContext.ACU_MONTH | pdtContext.ACU_DAY))) 84 | self.assertExpectedResult( 85 | self.cal.parse('25.08.2006', start), (target, pdtContext(pdtContext.ACU_YEAR | pdtContext.ACU_MONTH | pdtContext.ACU_DAY))) 86 | self.assertExpectedResult( 87 | self.cal.parse('25-8-06', start), (target, pdtContext(pdtContext.ACU_YEAR | pdtContext.ACU_MONTH | pdtContext.ACU_DAY))) 88 | self.assertExpectedResult( 89 | self.cal.parse('25/8/06', start), (target, pdtContext(pdtContext.ACU_YEAR | pdtContext.ACU_MONTH | pdtContext.ACU_DAY))) 90 | 91 | if self.mth > 8 or (self.mth == 8 and self.dy > 25): 92 | target = datetime.datetime( 93 | self.yr + 1, 8, 25, self.hr, self.mn, self.sec).timetuple() 94 | else: 95 | target = datetime.datetime( 96 | self.yr, 8, 25, self.hr, self.mn, self.sec).timetuple() 97 | 98 | self.assertExpectedResult(self.cal.parse('25/8', start), (target, pdtContext(pdtContext.ACU_MONTH | pdtContext.ACU_DAY))) 99 | self.assertExpectedResult(self.cal.parse('25.8', start), (target, pdtContext(pdtContext.ACU_MONTH | pdtContext.ACU_DAY))) 100 | self.assertExpectedResult(self.cal.parse('25-08', start), (target, pdtContext(pdtContext.ACU_MONTH | pdtContext.ACU_DAY))) 101 | self.assertExpectedResult(self.cal.parse('25/08', start), (target, pdtContext(pdtContext.ACU_MONTH | pdtContext.ACU_DAY))) 102 | 103 | 104 | if __name__ == "__main__": 105 | unittest.main() 106 | -------------------------------------------------------------------------------- /tests/TestComplexDateTimes.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Test parsing of complex date and times 4 | """ 5 | import sys 6 | import time 7 | from datetime import datetime 8 | import unittest 9 | import parsedatetime as pdt 10 | from parsedatetime.context import pdtContext 11 | from . import utils 12 | 13 | 14 | class test(unittest.TestCase): 15 | 16 | @utils.assertEqualWithComparator 17 | def assertExpectedResult(self, result, check, **kwargs): 18 | return utils.compareResultByTimeTuplesAndFlags(result, check, **kwargs) 19 | 20 | def setUp(self): 21 | self.cal = pdt.Calendar() 22 | (self.yr, self.mth, self.dy, self.hr, 23 | self.mn, self.sec, self.wd, self.yd, self.isdst) = time.localtime() 24 | 25 | def testDate3ConfusedHourAndYear(self): 26 | ctx = pdtContext(pdtContext.ACU_YEAR | pdtContext.ACU_MONTH | pdtContext.ACU_DAY | pdtContext.ACU_HOUR | pdtContext.ACU_MIN) 27 | start = datetime( 28 | self.yr, self.mth, self.dy, self.hr, self.mn, self.sec).timetuple() 29 | self.assertExpectedResult( 30 | self.cal.parse('Aug 05, 2014 4:15 AM'), 31 | (datetime(2014, 8, 5, 4, 15, 0).timetuple(), 32 | pdtContext(pdtContext.ACU_YEAR | pdtContext.ACU_MONTH | pdtContext.ACU_DAY | pdtContext.ACU_HOUR | pdtContext.ACU_MIN))) 33 | self.assertExpectedResult( 34 | self.cal.parse('Aug 05, 2003 3:15 AM'), 35 | (datetime(2003, 8, 5, 3, 15, 0).timetuple(), 36 | pdtContext(pdtContext.ACU_YEAR | pdtContext.ACU_MONTH | pdtContext.ACU_DAY | pdtContext.ACU_HOUR | pdtContext.ACU_MIN))) 37 | self.assertExpectedResult( 38 | self.cal.parse('Aug 05, 2003 03:15 AM'), 39 | (datetime(2003, 8, 5, 3, 15, 0).timetuple(), 40 | pdtContext(pdtContext.ACU_YEAR | pdtContext.ACU_MONTH | pdtContext.ACU_DAY | pdtContext.ACU_HOUR | pdtContext.ACU_MIN))) 41 | self.assertExpectedResult( 42 | self.cal.parse('June 30th 12PM', start), 43 | (datetime(self.yr 44 | if datetime.now() < datetime(self.yr, 6, 30, 12) 45 | else self.yr + 1, 46 | 6, 30, 12, 0, 0).timetuple(), 47 | pdtContext(pdtContext.ACU_MONTH | pdtContext.ACU_DAY | pdtContext.ACU_HOUR))) 48 | self.assertExpectedResult( 49 | self.cal.parse('June 30th 12:00', start), 50 | (datetime(self.yr 51 | if datetime.now() < datetime(self.yr, 6, 30, 12) 52 | else self.yr + 1, 53 | 6, 30, 12, 0, 0).timetuple(), 54 | pdtContext(pdtContext.ACU_MONTH | pdtContext.ACU_DAY | pdtContext.ACU_HOUR | pdtContext.ACU_MIN))) 55 | self.assertExpectedResult( 56 | self.cal.parse('December 30th 23PM', start), 57 | (datetime(self.yr 58 | if datetime.now() < datetime(self.yr, 12, 30, 23) 59 | else self.yr + 1, 60 | 12, 30, 23, 0, 0).timetuple(), 61 | pdtContext(pdtContext.ACU_MONTH | pdtContext.ACU_DAY | pdtContext.ACU_HOUR))) 62 | self.assertExpectedResult( 63 | self.cal.parse('December 30th 23:02', start), 64 | (datetime(self.yr 65 | if datetime.now() < datetime(self.yr, 12, 30, 23, 2) 66 | else self.yr + 1, 67 | 12, 30, 23, 2, 0).timetuple(), 68 | pdtContext(pdtContext.ACU_MONTH | pdtContext.ACU_DAY | pdtContext.ACU_HOUR | pdtContext.ACU_MIN))) 69 | 70 | def testDates(self): 71 | start = datetime( 72 | self.yr, self.mth, self.dy, self.hr, self.mn, self.sec).timetuple() 73 | target = datetime(2006, 8, 25, 17, 0, 0).timetuple() 74 | 75 | self.assertExpectedResult( 76 | self.cal.parse('08/25/2006 5pm', start), 77 | (target, pdtContext(pdtContext.ACU_YEAR | pdtContext.ACU_MONTH | pdtContext.ACU_DAY | pdtContext.ACU_HOUR))) 78 | self.assertExpectedResult( 79 | self.cal.parse('5pm on 08.25.2006', start), 80 | (target, pdtContext(pdtContext.ACU_YEAR | pdtContext.ACU_MONTH | pdtContext.ACU_DAY | pdtContext.ACU_HOUR))) 81 | self.assertExpectedResult( 82 | self.cal.parse('5pm August 25, 2006', start), 83 | (target, pdtContext(pdtContext.ACU_YEAR | pdtContext.ACU_MONTH | pdtContext.ACU_DAY | pdtContext.ACU_HOUR))) 84 | self.assertExpectedResult( 85 | self.cal.parse('5pm August 25th, 2006', start), 86 | (target, pdtContext(pdtContext.ACU_YEAR | pdtContext.ACU_MONTH | pdtContext.ACU_DAY | pdtContext.ACU_HOUR))) 87 | self.assertExpectedResult( 88 | self.cal.parse('5pm 25 August, 2006', start), 89 | (target, pdtContext(pdtContext.ACU_YEAR | pdtContext.ACU_MONTH | pdtContext.ACU_DAY | pdtContext.ACU_HOUR))) 90 | self.assertExpectedResult( 91 | self.cal.parse('5pm 25th August, 2006', start), 92 | (target, pdtContext(pdtContext.ACU_YEAR | pdtContext.ACU_MONTH | pdtContext.ACU_DAY | pdtContext.ACU_HOUR))) 93 | self.assertExpectedResult( 94 | self.cal.parse('Aug 25, 2006 5pm', start), 95 | (target, pdtContext(pdtContext.ACU_YEAR | pdtContext.ACU_MONTH | pdtContext.ACU_DAY | pdtContext.ACU_HOUR))) 96 | self.assertExpectedResult( 97 | self.cal.parse('Aug 25th, 2006 5pm', start), 98 | (target, pdtContext(pdtContext.ACU_YEAR | pdtContext.ACU_MONTH | pdtContext.ACU_DAY | pdtContext.ACU_HOUR))) 99 | self.assertExpectedResult( 100 | self.cal.parse('25 Aug, 2006 5pm', start), 101 | (target, pdtContext(pdtContext.ACU_YEAR | pdtContext.ACU_MONTH | pdtContext.ACU_DAY | pdtContext.ACU_HOUR))) 102 | self.assertExpectedResult( 103 | self.cal.parse('25th Aug 2006, 5pm', start), 104 | (target, pdtContext(pdtContext.ACU_YEAR | pdtContext.ACU_MONTH | pdtContext.ACU_DAY | pdtContext.ACU_HOUR))) 105 | 106 | if self.mth > 8 or (self.mth == 8 and self.dy > 5): 107 | target = datetime(self.yr + 1, 8, 5, 17, 0, 0).timetuple() 108 | else: 109 | target = datetime(self.yr, 8, 5, 17, 0, 0).timetuple() 110 | 111 | self.assertExpectedResult( 112 | self.cal.parse('8/5 at 5pm', start), 113 | (target, pdtContext(pdtContext.ACU_MONTH | pdtContext.ACU_DAY | pdtContext.ACU_HOUR))) 114 | self.assertExpectedResult( 115 | self.cal.parse('5pm 8.5', start), 116 | (target, pdtContext(pdtContext.ACU_MONTH | pdtContext.ACU_DAY | pdtContext.ACU_HOUR))) 117 | self.assertExpectedResult( 118 | self.cal.parse('08/05 5pm', start), 119 | (target, pdtContext(pdtContext.ACU_MONTH | pdtContext.ACU_DAY | pdtContext.ACU_HOUR))) 120 | self.assertExpectedResult( 121 | self.cal.parse('August 5 5pm', start), 122 | (target, pdtContext(pdtContext.ACU_MONTH | pdtContext.ACU_DAY | pdtContext.ACU_HOUR))) 123 | self.assertExpectedResult( 124 | self.cal.parse('5pm Aug 05', start), 125 | (target, pdtContext(pdtContext.ACU_MONTH | pdtContext.ACU_DAY | pdtContext.ACU_HOUR))) 126 | self.assertExpectedResult( 127 | self.cal.parse('Aug 05 5pm', start), 128 | (target, pdtContext(pdtContext.ACU_MONTH | pdtContext.ACU_DAY | pdtContext.ACU_HOUR))) 129 | self.assertExpectedResult( 130 | self.cal.parse('Aug 05th 5pm', start), 131 | (target, pdtContext(pdtContext.ACU_MONTH | pdtContext.ACU_DAY | pdtContext.ACU_HOUR))) 132 | self.assertExpectedResult( 133 | self.cal.parse('5 August 5pm', start), 134 | (target, pdtContext(pdtContext.ACU_MONTH | pdtContext.ACU_DAY | pdtContext.ACU_HOUR))) 135 | self.assertExpectedResult( 136 | self.cal.parse('5th August 5pm', start), 137 | (target, pdtContext(pdtContext.ACU_MONTH | pdtContext.ACU_DAY | pdtContext.ACU_HOUR))) 138 | self.assertExpectedResult( 139 | self.cal.parse('5pm 05 Aug', start), 140 | (target, pdtContext(pdtContext.ACU_MONTH | pdtContext.ACU_DAY | pdtContext.ACU_HOUR))) 141 | self.assertExpectedResult( 142 | self.cal.parse('05 Aug 5pm', start), 143 | (target, pdtContext(pdtContext.ACU_MONTH | pdtContext.ACU_DAY | pdtContext.ACU_HOUR))) 144 | self.assertExpectedResult( 145 | self.cal.parse('05th Aug 5pm', start), 146 | (target, pdtContext(pdtContext.ACU_MONTH | pdtContext.ACU_DAY | pdtContext.ACU_HOUR))) 147 | self.assertExpectedResult( 148 | self.cal.parse('August 5th 5pm', start), 149 | (target, pdtContext(pdtContext.ACU_MONTH | pdtContext.ACU_DAY | pdtContext.ACU_HOUR))) 150 | 151 | if self.mth > 8 or (self.mth == 8 and self.dy > 5): 152 | target = datetime(self.yr + 1, 8, 5, 12, 0, 0).timetuple() 153 | else: 154 | target = datetime(self.yr, 8, 5, 12, 0, 0).timetuple() 155 | 156 | self.assertExpectedResult( 157 | self.cal.parse('August 5th 12:00', start), 158 | (target, pdtContext(pdtContext.ACU_MONTH | pdtContext.ACU_DAY | pdtContext.ACU_HOUR | pdtContext.ACU_MIN))) 159 | self.assertExpectedResult( 160 | self.cal.parse('August 5th 12pm', start), 161 | (target, pdtContext(pdtContext.ACU_MONTH | pdtContext.ACU_DAY | pdtContext.ACU_HOUR))) 162 | self.assertExpectedResult( 163 | self.cal.parse('August 5th 12:00pm', start), 164 | (target, pdtContext(pdtContext.ACU_MONTH | pdtContext.ACU_DAY | pdtContext.ACU_HOUR | pdtContext.ACU_MIN))) 165 | self.assertExpectedResult( 166 | self.cal.parse('August 5th 12 pm', start), 167 | (target, pdtContext(pdtContext.ACU_MONTH | pdtContext.ACU_DAY | pdtContext.ACU_HOUR))) 168 | self.assertExpectedResult( 169 | self.cal.parse('August 5th 12:00 pm', start), 170 | (target, pdtContext(pdtContext.ACU_MONTH | pdtContext.ACU_DAY | pdtContext.ACU_HOUR | pdtContext.ACU_MIN))) 171 | 172 | if self.mth > 8 or (self.mth == 8 and self.dy > 22): 173 | target = datetime( 174 | self.yr + 1, 8, 22, 3, 26, 0).timetuple() 175 | else: 176 | target = datetime(self.yr, 8, 22, 3, 26, 0).timetuple() 177 | 178 | self.assertExpectedResult( 179 | self.cal.parse('August 22nd 3:26', start), 180 | (target, pdtContext(pdtContext.ACU_MONTH | pdtContext.ACU_DAY | pdtContext.ACU_HOUR | pdtContext.ACU_MIN))) 181 | self.assertExpectedResult( 182 | self.cal.parse('August 22nd 3:26am', start), 183 | (target, pdtContext(pdtContext.ACU_MONTH | pdtContext.ACU_DAY | pdtContext.ACU_HOUR | pdtContext.ACU_MIN))) 184 | self.assertExpectedResult( 185 | self.cal.parse('August 22nd 3:26 am', start), 186 | (target, pdtContext(pdtContext.ACU_MONTH | pdtContext.ACU_DAY | pdtContext.ACU_HOUR | pdtContext.ACU_MIN))) 187 | 188 | def testDatesWithDay(self): 189 | start = datetime( 190 | self.yr, self.mth, self.dy, self.hr, self.mn, self.sec).timetuple() 191 | target = datetime(2016, 8, 23, 17, 0, 0).timetuple() 192 | self.assertExpectedResult( 193 | self.cal.parse('tuesday august 23nd 2016 at 5pm', start), 194 | (target, pdtContext(pdtContext.ACU_YEAR | pdtContext.ACU_MONTH | pdtContext.ACU_DAY | pdtContext.ACU_HOUR))) 195 | 196 | 197 | if __name__ == "__main__": 198 | unittest.main() 199 | -------------------------------------------------------------------------------- /tests/TestContext.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Test pdtContext 4 | """ 5 | import sys 6 | import time 7 | import unittest 8 | import parsedatetime as pdt 9 | from parsedatetime.context import pdtContext 10 | 11 | 12 | class test(unittest.TestCase): 13 | 14 | def setUp(self): 15 | self.cal = pdt.Calendar(version=pdt.VERSION_CONTEXT_STYLE) 16 | (self.yr, self.mth, self.dy, self.hr, self.mn, 17 | self.sec, self.wd, self.yd, self.isdst) = time.localtime() 18 | 19 | def testContext(self): 20 | self.assertEqual(self.cal.parse('5 min from now')[1], 21 | pdtContext(pdtContext.ACU_MIN | pdtContext.ACU_NOW)) 22 | self.assertEqual(self.cal.parse('5 min from now', 23 | version=pdt.VERSION_FLAG_STYLE)[1], 2) 24 | self.assertEqual(self.cal.parse('7/11/2015')[1], 25 | pdtContext(pdtContext.ACU_YEAR | 26 | pdtContext.ACU_MONTH | pdtContext.ACU_DAY)) 27 | self.assertEqual(self.cal.parse('7/11/2015', 28 | version=pdt.VERSION_FLAG_STYLE)[1], 1) 29 | self.assertEqual(self.cal.parse('14/32/2015')[1], 30 | pdtContext(0)) 31 | self.assertEqual(self.cal.parse('25:23')[1], 32 | pdtContext()) 33 | 34 | def testSources(self): 35 | self.assertEqual(self.cal.parse('afternoon 5pm')[1], 36 | pdtContext(pdtContext.ACU_HALFDAY | 37 | pdtContext.ACU_HOUR)) 38 | 39 | self.assertEqual(self.cal.parse('morning')[1], 40 | pdtContext(pdtContext.ACU_HALFDAY)) 41 | 42 | self.assertEqual(self.cal.parse('night', version=1)[1], 2) 43 | 44 | def testThreadRun(self): 45 | from threading import Thread 46 | t = Thread(target=lambda: self.cal.evalRanges('4p-6p')) 47 | # should not throw out AttributeError 48 | t.start() 49 | 50 | 51 | if __name__ == "__main__": 52 | unittest.main() 53 | -------------------------------------------------------------------------------- /tests/TestConvertUnitAsWords.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tests the _convertUnitAsWords method. 3 | """ 4 | import sys 5 | import unittest 6 | import parsedatetime as pdt 7 | from parsedatetime.context import pdtContext 8 | 9 | 10 | class test(unittest.TestCase): 11 | def setUp(self): 12 | self.cal = pdt.Calendar() 13 | self.tests = (('one', 1), 14 | ('zero', 0), 15 | ('eleven', 11), 16 | ('forty two', 42), 17 | ('a hundred', 100), 18 | ('four hundred and fifteen', 415), 19 | ('twelve thousand twenty', 12020), 20 | ('nine hundred and ninety nine', 999), 21 | ('three quintillion four billion', 3000000004000000000), 22 | ('forty three thousand, nine hundred and ninety nine', 43999), 23 | ('one hundred thirty three billion four hundred thousand three hundred fourteen', 133000400314), 24 | ('an octillion', 1000000000000000000000000000) 25 | ) 26 | 27 | def testConversions(self): 28 | for pair in self.tests: 29 | self.assertEqual(self.cal._convertUnitAsWords(pair[0]), pair[1]) 30 | 31 | 32 | if __name__ == "__main__": 33 | unittest.main() 34 | -------------------------------------------------------------------------------- /tests/TestDelta.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Test time delta 4 | """ 5 | import sys 6 | import time 7 | import datetime 8 | import unittest 9 | import parsedatetime as pdt 10 | from parsedatetime.context import pdtContext 11 | 12 | 13 | class test(unittest.TestCase): 14 | 15 | def setUp(self): 16 | self.cal = pdt.Calendar(version=pdt.VERSION_CONTEXT_STYLE) 17 | self.source = (2017, 1, 1, 7, 1, 2, 6, 1, 1) 18 | 19 | def assertDelta(self, ts, years=None, months=None, **deltakw): 20 | ts = ts[0] 21 | source = self.source 22 | delta = datetime.timedelta(**deltakw) 23 | calc_delta = (datetime.datetime(*ts[:6]) - 24 | datetime.datetime(*source[:6])) 25 | delta -= datetime.timedelta(microseconds=delta.microseconds) 26 | if not years and not months: 27 | self.assertEqual(delta, calc_delta) 28 | return 29 | if years: 30 | delta += datetime.timedelta(days=365 * years) 31 | if months: 32 | delta += datetime.timedelta(days=30 * months) 33 | diff = abs((calc_delta.total_seconds() - 34 | delta.total_seconds()) / 35 | delta.total_seconds()) 36 | self.assertTrue(diff < 0.05, '%s is not less than 0.05' % diff) 37 | 38 | def testInteger(self): 39 | self.assertDelta( 40 | self.cal.parse('5 minutes ago', self.source), minutes=-5) 41 | self.assertDelta( 42 | self.cal.parse('34 hours ago', self.source), hours=-34) 43 | self.assertDelta( 44 | self.cal.parse('2 days ago', self.source), days=-2) 45 | 46 | def testFloat(self): 47 | self.assertDelta( 48 | self.cal.parse('58.4 minutes ago', self.source), minutes=-58.4) 49 | self.assertDelta( 50 | self.cal.parse('1855336.424 minutes ago', self.source), 51 | minutes=-1855336.424) 52 | self.assertDelta( 53 | self.cal.parse('8.3 hours ago', self.source), hours=-8.3) 54 | self.assertDelta( 55 | self.cal.parse('22.355 hours ago', self.source), hours=-22.355) 56 | self.assertDelta( 57 | self.cal.parse('7.2 days ago', self.source), days=-7.2) 58 | self.assertDelta( 59 | self.cal.parse('7.3 days ago', self.source), days=-7.3) 60 | self.assertDelta( 61 | self.cal.parse('17.7 days ago', self.source), days=-17.7) 62 | self.assertDelta( 63 | self.cal.parse('1.4 months ago', self.source), months=-1.4) 64 | self.assertDelta( 65 | self.cal.parse('4.8 months ago', self.source), months=-4.8) 66 | self.assertDelta( 67 | self.cal.parse('5.1 months ago', self.source), months=-5.1) 68 | self.assertDelta( 69 | self.cal.parse('5.11553 years ago', self.source), years=-5.11553) 70 | 71 | 72 | if __name__ == "__main__": 73 | unittest.main() 74 | -------------------------------------------------------------------------------- /tests/TestErrors.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Test parsing of units 4 | """ 5 | import sys 6 | import time 7 | import datetime 8 | import unittest 9 | import parsedatetime as pdt 10 | from parsedatetime.context import pdtContext 11 | from . import utils 12 | 13 | 14 | class test(unittest.TestCase): 15 | 16 | @utils.assertEqualWithComparator 17 | def assertExpectedResult(self, result, check, **kwargs): 18 | return utils.compareResultByTimeTuplesAndFlags(result, check, **kwargs) 19 | 20 | @utils.assertEqualWithComparator 21 | def assertExpectedErrorFlag(self, result, check, **kwargs): 22 | return utils.compareResultByFlags(result, check, **kwargs) 23 | 24 | def setUp(self): 25 | self.cal = pdt.Calendar() 26 | (self.yr, self.mth, self.dy, self.hr, 27 | self.mn, self.sec, self.wd, self.yd, self.isdst) = time.localtime() 28 | 29 | def testErrors(self): 30 | s = datetime.datetime.now() 31 | start = s.timetuple() 32 | 33 | # These tests all return current date/time as they are out of range 34 | self.assertExpectedResult(self.cal.parse('01/0', start), (start, pdtContext())) 35 | self.assertExpectedResult(self.cal.parse('08/35', start), (start, pdtContext())) 36 | self.assertExpectedResult(self.cal.parse('18/35', start), (start, pdtContext())) 37 | self.assertExpectedResult(self.cal.parse('1799', start), (start, pdtContext())) 38 | self.assertExpectedResult(self.cal.parse('781', start), (start, pdtContext())) 39 | self.assertExpectedResult(self.cal.parse('2702', start), (start, pdtContext())) 40 | self.assertExpectedResult(self.cal.parse('78', start), (start, pdtContext())) 41 | self.assertExpectedResult(self.cal.parse('11', start), (start, pdtContext())) 42 | self.assertExpectedResult(self.cal.parse('1', start), (start, pdtContext())) 43 | self.assertExpectedResult(self.cal.parse('174565', start), (start, pdtContext())) 44 | self.assertExpectedResult(self.cal.parse('177505', start), (start, pdtContext())) 45 | # ensure short month names do not cause false positives within a word - 46 | # jun (june) 47 | self.assertExpectedResult( 48 | self.cal.parse('injunction', start), (start, pdtContext())) 49 | # ensure short month names do not cause false positives at the start of 50 | # a word - jul (juuly) 51 | self.assertExpectedResult(self.cal.parse('julius', start), (start, pdtContext())) 52 | # ensure short month names do not cause false positives at the end of a 53 | # word - mar (march) 54 | self.assertExpectedResult(self.cal.parse('lamar', start), (start, pdtContext())) 55 | # ensure short weekday names do not cause false positives within a word 56 | # - mon (monday) 57 | self.assertExpectedResult( 58 | self.cal.parse('demonize', start), (start, pdtContext())) 59 | # ensure short weekday names do not cause false positives at the start 60 | # of a word - mon (monday) 61 | self.assertExpectedResult(self.cal.parse('money', start), (start, pdtContext())) 62 | # ensure short weekday names do not cause false positives at the end of 63 | # a word - th (thursday) 64 | self.assertExpectedResult(self.cal.parse('month', start), (start, pdtContext())) 65 | self.assertExpectedErrorFlag( 66 | self.cal.parse('30/030/01/071/07', start), (start, pdtContext())) 67 | # overflow due to Python's datetime 68 | self.assertExpectedResult(self.cal.parse('12345 y', start), (start, pdtContext())) 69 | self.assertExpectedResult( 70 | self.cal.parse('654321 w', start), (start, pdtContext())) 71 | self.assertExpectedResult( 72 | self.cal.parse('3700000 d', start), (start, pdtContext())) 73 | 74 | 75 | if __name__ == "__main__": 76 | unittest.main() 77 | -------------------------------------------------------------------------------- /tests/TestFrenchLocale.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Test parsing of simple date and times using the French locale 4 | 5 | Note: requires PyICU 6 | """ 7 | import sys 8 | import time 9 | import datetime 10 | import unittest 11 | import parsedatetime as pdt 12 | from parsedatetime.context import pdtContext 13 | from parsedatetime.pdt_locales import get_icu 14 | from . import utils 15 | 16 | 17 | class test(unittest.TestCase): 18 | 19 | @utils.assertEqualWithComparator 20 | def assertExpectedResult(self, result, check, **kwargs): 21 | return utils.compareResultByTimeTuplesAndFlags(result, check, **kwargs) 22 | 23 | def setUp(self): 24 | locale = 'fr_FR' 25 | self.ptc = pdt.Constants(locale, usePyICU=False) 26 | self.cal = pdt.Calendar(self.ptc) 27 | 28 | (self.yr, self.mth, self.dy, self.hr, 29 | self.mn, self.sec, self.wd, self.yd, self.isdst) = time.localtime() 30 | 31 | if self.ptc.localeID != locale: 32 | raise unittest.SkipTest( 33 | 'Locale not set to fr_FR - check if PyICU is installed') 34 | 35 | def testTimes(self): 36 | start = datetime.datetime( 37 | self.yr, self.mth, self.dy, 38 | self.hr, self.mn, self.sec).timetuple() 39 | target = datetime.datetime( 40 | self.yr, self.mth, self.dy, 23, 0, 0).timetuple() 41 | 42 | self.assertExpectedResult( 43 | self.cal.parse('2300', start), (target, pdtContext(pdtContext.ACU_HOUR | pdtContext.ACU_MIN))) 44 | self.assertExpectedResult( 45 | self.cal.parse('23:00', start), (target, pdtContext(pdtContext.ACU_HOUR | pdtContext.ACU_MIN))) 46 | 47 | target = datetime.datetime( 48 | self.yr, self.mth, self.dy, 11, 0, 0).timetuple() 49 | 50 | self.assertExpectedResult( 51 | self.cal.parse('1100', start), (target, pdtContext(pdtContext.ACU_HOUR | pdtContext.ACU_MIN))) 52 | self.assertExpectedResult( 53 | self.cal.parse('11:00', start), (target, pdtContext(pdtContext.ACU_HOUR | pdtContext.ACU_MIN))) 54 | 55 | target = datetime.datetime( 56 | self.yr, self.mth, self.dy, 7, 30, 0).timetuple() 57 | 58 | self.assertExpectedResult( 59 | self.cal.parse('730', start), (target, pdtContext(pdtContext.ACU_HOUR | pdtContext.ACU_MIN))) 60 | self.assertExpectedResult( 61 | self.cal.parse('0730', start), (target, pdtContext(pdtContext.ACU_HOUR | pdtContext.ACU_MIN))) 62 | 63 | target = datetime.datetime( 64 | self.yr, self.mth, self.dy, 17, 30, 0).timetuple() 65 | 66 | self.assertExpectedResult( 67 | self.cal.parse('1730', start), (target, pdtContext(pdtContext.ACU_HOUR | pdtContext.ACU_MIN))) 68 | self.assertExpectedResult( 69 | self.cal.parse('173000', start), (target, pdtContext(pdtContext.ACU_HOUR | pdtContext.ACU_MIN | pdtContext.ACU_SEC))) 70 | 71 | def testDates(self): 72 | start = datetime.datetime( 73 | self.yr, self.mth, self.dy, 74 | self.hr, self.mn, self.sec).timetuple() 75 | target = datetime.datetime( 76 | 2006, 8, 25, self.hr, self.mn, self.sec).timetuple() 77 | 78 | self.assertExpectedResult( 79 | self.cal.parse('25/08/2006', start), (target, pdtContext(pdtContext.ACU_YEAR |pdtContext.ACU_MONTH | pdtContext.ACU_DAY))) 80 | self.assertExpectedResult( 81 | self.cal.parse('25/8/06', start), (target, pdtContext(pdtContext.ACU_YEAR |pdtContext.ACU_MONTH | pdtContext.ACU_DAY))) 82 | self.assertExpectedResult( 83 | self.cal.parse('août 25, 2006', start), (target, pdtContext(pdtContext.ACU_YEAR |pdtContext.ACU_MONTH | pdtContext.ACU_DAY))) 84 | self.assertExpectedResult( 85 | self.cal.parse('août 25 2006', start), (target, pdtContext(pdtContext.ACU_YEAR |pdtContext.ACU_MONTH | pdtContext.ACU_DAY))) 86 | 87 | if self.mth > 8 or (self.mth == 8 and self.dy > 25): 88 | target = datetime.datetime( 89 | self.yr + 1, 8, 25, self.hr, self.mn, self.sec).timetuple() 90 | else: 91 | target = datetime.datetime( 92 | self.yr, 8, 25, self.hr, self.mn, self.sec).timetuple() 93 | 94 | self.assertExpectedResult( 95 | self.cal.parse('25/8', start), (target, pdtContext(pdtContext.ACU_MONTH | pdtContext.ACU_DAY))) 96 | self.assertExpectedResult( 97 | self.cal.parse('25/08', start), (target, pdtContext(pdtContext.ACU_MONTH | pdtContext.ACU_DAY))) 98 | 99 | def testWeekDays(self): 100 | start = datetime.datetime( 101 | self.yr, self.mth, self.dy, 102 | self.hr, self.mn, self.sec).timetuple() 103 | 104 | o1 = self.ptc.CurrentDOWParseStyle 105 | o2 = self.ptc.DOWParseStyle 106 | 107 | # set it up so the current dow returns current day 108 | self.ptc.CurrentDOWParseStyle = True 109 | self.ptc.DOWParseStyle = 1 110 | 111 | for i in range(0, 7): 112 | dow = self.ptc.shortWeekdays[i] 113 | print(dow) 114 | 115 | result = self.cal.parse(dow, start) 116 | 117 | yr, mth, dy, hr, mn, sec, wd, yd, isdst = result[0] 118 | 119 | self.assertEqual(wd, i) 120 | 121 | self.ptc.CurrentDOWParseStyle = o1 122 | self.ptc.DOWParseStyle = o2 123 | 124 | 125 | if __name__ == "__main__": 126 | unittest.main() 127 | -------------------------------------------------------------------------------- /tests/TestGermanLocale.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Test parsing of simple date and times using the German locale 4 | """ 5 | import sys 6 | import time 7 | import datetime 8 | import unittest 9 | import parsedatetime as pdt 10 | from parsedatetime.context import pdtContext 11 | from . import utils 12 | 13 | 14 | class test(unittest.TestCase): 15 | 16 | @utils.assertEqualWithComparator 17 | def assertExpectedResult(self, result, check, **kwargs): 18 | return utils.compareResultByTimeTuplesAndFlags(result, check, **kwargs) 19 | 20 | def setUp(self): 21 | self.ptc = pdt.Constants('de_DE', usePyICU=False) 22 | self.cal = pdt.Calendar(self.ptc) 23 | 24 | (self.yr, self.mth, self.dy, self.hr, 25 | self.mn, self.sec, self.wd, self.yd, self.isdst) = time.localtime() 26 | 27 | if self.ptc.localeID != 'de_DE': 28 | raise unittest.SkipTest( 29 | 'Locale not set to de_DE - check if PyICU is installed') 30 | 31 | def testTimes(self): 32 | start = datetime.datetime( 33 | self.yr, self.mth, self.dy, self.hr, self.mn, self.sec).timetuple() 34 | target = datetime.datetime( 35 | self.yr, self.mth, self.dy, 23, 0, 0).timetuple() 36 | 37 | self.assertExpectedResult( 38 | self.cal.parse('23:00:00', start), (target, pdtContext(pdtContext.ACU_HOUR | pdtContext.ACU_MIN | pdtContext.ACU_SEC))) 39 | self.assertExpectedResult( 40 | self.cal.parse('23:00', start), (target, pdtContext(pdtContext.ACU_HOUR | pdtContext.ACU_MIN))) 41 | self.assertExpectedResult( 42 | self.cal.parse('2300', start), (target, pdtContext(pdtContext.ACU_HOUR | pdtContext.ACU_MIN))) 43 | 44 | target = datetime.datetime( 45 | self.yr, self.mth, self.dy, 11, 0, 0).timetuple() 46 | 47 | self.assertExpectedResult( 48 | self.cal.parse('11:00:00', start), (target, pdtContext(pdtContext.ACU_HOUR | pdtContext.ACU_MIN | pdtContext.ACU_SEC))) 49 | self.assertExpectedResult( 50 | self.cal.parse('11:00', start), (target, pdtContext(pdtContext.ACU_HOUR | pdtContext.ACU_MIN))) 51 | self.assertExpectedResult( 52 | self.cal.parse('1100', start), (target, pdtContext(pdtContext.ACU_HOUR | pdtContext.ACU_MIN))) 53 | 54 | target = datetime.datetime( 55 | self.yr, self.mth, self.dy, 7, 30, 0).timetuple() 56 | 57 | self.assertExpectedResult(self.cal.parse('730', start), (target, pdtContext(pdtContext.ACU_HOUR | pdtContext.ACU_MIN))) 58 | self.assertExpectedResult(self.cal.parse('0730', start), (target, pdtContext(pdtContext.ACU_HOUR | pdtContext.ACU_MIN))) 59 | 60 | target = datetime.datetime( 61 | self.yr, self.mth, self.dy, 17, 30, 0).timetuple() 62 | 63 | self.assertExpectedResult(self.cal.parse('1730', start), (target, pdtContext(pdtContext.ACU_HOUR | pdtContext.ACU_MIN))) 64 | self.assertExpectedResult(self.cal.parse('173000', start), (target, pdtContext(pdtContext.ACU_HOUR | pdtContext.ACU_MIN | pdtContext.ACU_SEC))) 65 | 66 | def testDates(self): 67 | start = datetime.datetime( 68 | self.yr, self.mth, self.dy, self.hr, self.mn, self.sec).timetuple() 69 | target = datetime.datetime( 70 | 2006, 8, 25, self.hr, self.mn, self.sec).timetuple() 71 | 72 | self.assertExpectedResult( 73 | self.cal.parse('25.08.2006', start), (target, pdtContext(pdtContext.ACU_YEAR | pdtContext.ACU_MONTH | pdtContext.ACU_DAY))) 74 | self.assertExpectedResult( 75 | self.cal.parse('25.8.06', start), (target, pdtContext(pdtContext.ACU_YEAR | pdtContext.ACU_MONTH | pdtContext.ACU_DAY))) 76 | 77 | if self.mth > 8 or (self.mth == 8 and self.dy > 25): 78 | target = datetime.datetime( 79 | self.yr + 1, 8, 25, self.hr, self.mn, self.sec).timetuple() 80 | else: 81 | target = datetime.datetime( 82 | self.yr, 8, 25, self.hr, self.mn, self.sec).timetuple() 83 | 84 | self.assertExpectedResult( 85 | self.cal.parse('25.8', start), (target, pdtContext(pdtContext.ACU_MONTH | pdtContext.ACU_DAY))) 86 | self.assertExpectedResult( 87 | self.cal.parse('25.08', start), (target, pdtContext(pdtContext.ACU_MONTH | pdtContext.ACU_DAY))) 88 | 89 | 90 | if __name__ == "__main__": 91 | unittest.main() 92 | -------------------------------------------------------------------------------- /tests/TestInc.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Test Calendar.Inc() routine 4 | """ 5 | import sys 6 | import time 7 | import datetime 8 | import unittest 9 | import parsedatetime as pdt 10 | from parsedatetime.context import pdtContext 11 | from . import utils 12 | 13 | 14 | class test(unittest.TestCase): 15 | 16 | @utils.assertEqualWithComparator 17 | def assertExpectedResult(self, result, check, **kwargs): 18 | return utils.compareResultByTimeTuplesAndFlags((result, 1), 19 | (check, 1), **kwargs) 20 | 21 | def setUp(self): 22 | self.cal = pdt.Calendar() 23 | (self.yr, self.mth, self.dy, self.hr, 24 | self.mn, self.sec, self.wd, self.yd, self.isdst) = time.localtime() 25 | 26 | def testIncMonths(self): 27 | s = datetime.datetime(2006, 1, 1, 12, 0, 0) 28 | t = datetime.datetime(2006, 2, 1, 12, 0, 0) 29 | self.assertExpectedResult( 30 | self.cal.inc(s, month=1).timetuple(), t.timetuple()) 31 | 32 | s = datetime.datetime(2006, 12, 1, 12, 0, 0) 33 | t = datetime.datetime(2007, 1, 1, 12, 0, 0) 34 | self.assertExpectedResult( 35 | self.cal.inc(s, month=1).timetuple(), t.timetuple()) 36 | 37 | # leap year, Feb 1 38 | s = datetime.datetime(2008, 2, 1, 12, 0, 0) 39 | t = datetime.datetime(2008, 3, 1, 12, 0, 0) 40 | self.assertExpectedResult( 41 | self.cal.inc(s, month=1).timetuple(), t.timetuple()) 42 | 43 | # leap year, Feb 29 44 | s = datetime.datetime(2008, 2, 29, 12, 0, 0) 45 | t = datetime.datetime(2008, 3, 29, 12, 0, 0) 46 | self.assertExpectedResult( 47 | self.cal.inc(s, month=1).timetuple(), t.timetuple()) 48 | 49 | s = datetime.datetime(2006, 1, 1, 12, 0, 0) 50 | t = datetime.datetime(2005, 12, 1, 12, 0, 0) 51 | self.assertExpectedResult( 52 | self.cal.inc(s, month=-1).timetuple(), t.timetuple()) 53 | 54 | # End of month Jan 31 to Feb - February only has 28 days 55 | s = datetime.datetime(2006, 1, 31, 12, 0, 0) 56 | t = datetime.datetime(2006, 2, 28, 12, 0, 0) 57 | self.assertExpectedResult( 58 | self.cal.inc(s, month=1).timetuple(), t.timetuple()) 59 | 60 | # walk thru months and make sure month increment doesn't set the day 61 | # to be past the last day of the new month 62 | # think Jan transition to Feb - 31 days to 28 days 63 | for m in range(1, 11): 64 | d = self.cal.ptc.daysInMonth(m, 2006) 65 | s = datetime.datetime(2006, m, d, 12, 0, 0) 66 | 67 | if d > self.cal.ptc.daysInMonth(m + 1, 2006): 68 | d = self.cal.ptc.daysInMonth(m + 1, 2006) 69 | 70 | t = datetime.datetime(2006, m + 1, d, 12, 0, 0) 71 | 72 | self.assertExpectedResult( 73 | self.cal.inc(s, month=1).timetuple(), t.timetuple()) 74 | 75 | def testIncYears(self): 76 | s = datetime.datetime(2006, 1, 1, 12, 0, 0) 77 | t = datetime.datetime(2007, 1, 1, 12, 0, 0) 78 | self.assertExpectedResult( 79 | self.cal.inc(s, year=1).timetuple(), t.timetuple()) 80 | 81 | s = datetime.datetime(2006, 1, 1, 12, 0, 0) 82 | t = datetime.datetime(2008, 1, 1, 12, 0, 0) 83 | self.assertExpectedResult( 84 | self.cal.inc(s, year=2).timetuple(), t.timetuple()) 85 | 86 | s = datetime.datetime(2006, 12, 31, 12, 0, 0) 87 | t = datetime.datetime(2007, 12, 31, 12, 0, 0) 88 | self.assertExpectedResult( 89 | self.cal.inc(s, year=1).timetuple(), t.timetuple()) 90 | 91 | s = datetime.datetime(2006, 12, 31, 12, 0, 0) 92 | t = datetime.datetime(2005, 12, 31, 12, 0, 0) 93 | self.assertExpectedResult( 94 | self.cal.inc(s, year=-1).timetuple(), t.timetuple()) 95 | 96 | s = datetime.datetime(2008, 3, 1, 12, 0, 0) 97 | t = datetime.datetime(2009, 3, 1, 12, 0, 0) 98 | self.assertExpectedResult( 99 | self.cal.inc(s, year=1).timetuple(), t.timetuple()) 100 | 101 | s = datetime.datetime(2008, 3, 1, 12, 0, 0) 102 | t = datetime.datetime(2007, 3, 1, 12, 0, 0) 103 | self.assertExpectedResult( 104 | self.cal.inc(s, year=-1).timetuple(), t.timetuple()) 105 | 106 | 107 | if __name__ == "__main__": 108 | unittest.main() 109 | -------------------------------------------------------------------------------- /tests/TestLocaleBase.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Test parsing of simple date and times using the French locale 4 | 5 | Note: requires PyICU 6 | """ 7 | import sys 8 | import time 9 | import datetime 10 | import unittest 11 | import pytest 12 | import parsedatetime as pdt 13 | from parsedatetime.context import pdtContext 14 | from . import utils 15 | 16 | 17 | class test(unittest.TestCase): 18 | 19 | @utils.assertEqualWithComparator 20 | def assertExpectedResult(self, result, check, **kwargs): 21 | return utils.compareResultByTimeTuplesAndFlags(result, check, **kwargs) 22 | 23 | def setUp(self): 24 | self.ptc = pdt.Constants('fr_FR') 25 | self.cal = pdt.Calendar(self.ptc) 26 | 27 | (self.yr, self.mth, self.dy, self.hr, 28 | self.mn, self.sec, self.wd, self.yd, self.isdst) = time.localtime() 29 | 30 | def testTimes(self): 31 | if self.ptc.localeID == 'fr_FR': 32 | start = datetime.datetime( 33 | self.yr, self.mth, self.dy, 34 | self.hr, self.mn, self.sec).timetuple() 35 | target = datetime.datetime( 36 | self.yr, self.mth, self.dy, 23, 0, 0).timetuple() 37 | 38 | self.assertExpectedResult( 39 | self.cal.parse('2300', start), (target, pdtContext(pdtContext.ACU_HOUR | pdtContext.ACU_MIN))) 40 | self.assertExpectedResult( 41 | self.cal.parse('23:00', start), (target, pdtContext(pdtContext.ACU_HOUR | pdtContext.ACU_MIN))) 42 | 43 | target = datetime.datetime( 44 | self.yr, self.mth, self.dy, 11, 0, 0).timetuple() 45 | 46 | self.assertExpectedResult( 47 | self.cal.parse('1100', start), (target, pdtContext(pdtContext.ACU_HOUR | pdtContext.ACU_MIN))) 48 | self.assertExpectedResult( 49 | self.cal.parse('11:00', start), (target, pdtContext(pdtContext.ACU_HOUR | pdtContext.ACU_MIN))) 50 | 51 | target = datetime.datetime( 52 | self.yr, self.mth, self.dy, 7, 30, 0).timetuple() 53 | 54 | self.assertExpectedResult( 55 | self.cal.parse('730', start), (target, pdtContext(pdtContext.ACU_HOUR | pdtContext.ACU_MIN))) 56 | self.assertExpectedResult( 57 | self.cal.parse('0730', start), (target, pdtContext(pdtContext.ACU_HOUR | pdtContext.ACU_MIN))) 58 | 59 | target = datetime.datetime( 60 | self.yr, self.mth, self.dy, 17, 30, 0).timetuple() 61 | 62 | self.assertExpectedResult( 63 | self.cal.parse('1730', start), (target, pdtContext(pdtContext.ACU_HOUR | pdtContext.ACU_MIN))) 64 | self.assertExpectedResult( 65 | self.cal.parse('173000', start), (target, pdtContext(pdtContext.ACU_HOUR | pdtContext.ACU_MIN | pdtContext.ACU_SEC))) 66 | 67 | def testDates(self): 68 | if self.ptc.localeID == 'fr_FR': 69 | start = datetime.datetime( 70 | self.yr, self.mth, self.dy, 71 | self.hr, self.mn, self.sec).timetuple() 72 | target = datetime.datetime( 73 | 2006, 8, 25, self.hr, self.mn, self.sec).timetuple() 74 | 75 | self.assertExpectedResult( 76 | self.cal.parse('25/08/2006', start), (target, pdtContext(pdtContext.ACU_YEAR | pdtContext.ACU_MONTH | pdtContext.ACU_DAY))) 77 | self.assertExpectedResult( 78 | self.cal.parse('25/8/06', start), (target, pdtContext(pdtContext.ACU_YEAR | pdtContext.ACU_MONTH | pdtContext.ACU_DAY))) 79 | self.assertExpectedResult( 80 | self.cal.parse('août 25, 2006', start), (target, pdtContext(pdtContext.ACU_YEAR | pdtContext.ACU_MONTH | pdtContext.ACU_DAY))) 81 | self.assertExpectedResult( 82 | self.cal.parse('août 25 2006', start), (target, pdtContext(pdtContext.ACU_YEAR | pdtContext.ACU_MONTH | pdtContext.ACU_DAY))) 83 | 84 | if self.mth > 8 or (self.mth == 8 and self.dy > 25): 85 | target = datetime.datetime( 86 | self.yr + 1, 8, 25, self.hr, self.mn, self.sec).timetuple() 87 | else: 88 | target = datetime.datetime( 89 | self.yr, 8, 25, self.hr, self.mn, self.sec).timetuple() 90 | 91 | self.assertExpectedResult( 92 | self.cal.parse('25/8', start), (target, pdtContext(pdtContext.ACU_MONTH | pdtContext.ACU_DAY))) 93 | self.assertExpectedResult( 94 | self.cal.parse('25/08', start), (target, pdtContext(pdtContext.ACU_MONTH | pdtContext.ACU_DAY))) 95 | 96 | def testWeekDays(self): 97 | if self.ptc.localeID == 'fr_FR': 98 | start = datetime.datetime( 99 | self.yr, self.mth, self.dy, 100 | self.hr, self.mn, self.sec).timetuple() 101 | 102 | o1 = self.ptc.CurrentDOWParseStyle 103 | o2 = self.ptc.DOWParseStyle 104 | 105 | # set it up so the current dow returns current day 106 | self.ptc.CurrentDOWParseStyle = True 107 | self.ptc.DOWParseStyle = 1 108 | 109 | for i in range(0, 7): 110 | dow = self.ptc.shortWeekdays[i] 111 | 112 | result = self.cal.parse(dow, start) 113 | 114 | yr, mth, dy, hr, mn, sec, wd, yd, isdst = result[0] 115 | 116 | self.assertEqual(wd, i) 117 | 118 | self.ptc.CurrentDOWParseStyle = o1 119 | self.ptc.DOWParseStyle = o2 120 | 121 | 122 | class TestDayOffsets(test): 123 | # test how Aujourd'hui/Demain/Hier are parsed 124 | 125 | def setUp(self): 126 | super(TestDayOffsets, self).setUp() 127 | self.ptc = pdt.Constants('fr_FR', usePyICU=False) 128 | self.cal = pdt.Calendar(self.ptc) 129 | 130 | def test_dayoffsets(self): 131 | start = datetime.datetime(self.yr, self.mth, self.dy, 9) 132 | for date_string, expected_day_offset in [ 133 | ("Aujourd'hui", 0), 134 | ("aujourd'hui", 0), 135 | ("Demain", 1), 136 | ("demain", 1), 137 | ("Hier", -1), 138 | ("hier", -1), 139 | ("au jour de hui", None)]: 140 | got_dt, rc = self.cal.parseDT(date_string, start) 141 | if expected_day_offset is not None: 142 | self.assertEqual(rc, pdtContext(pdtContext.ACU_DAY)) 143 | target = (start + datetime.timedelta(days=expected_day_offset)) 144 | self.assertEqual(got_dt, target) 145 | else: 146 | self.assertEqual(rc, pdtContext()) 147 | 148 | 149 | if __name__ == "__main__": 150 | unittest.main() 151 | -------------------------------------------------------------------------------- /tests/TestMultiple.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Test parsing of strings with multiple chunks 4 | """ 5 | import sys 6 | import time 7 | import datetime 8 | import unittest 9 | import parsedatetime as pdt 10 | from parsedatetime.context import pdtContext 11 | from . import utils 12 | 13 | 14 | class test(unittest.TestCase): 15 | 16 | @utils.assertEqualWithComparator 17 | def assertExpectedResult(self, result, check, **kwargs): 18 | return utils.compareResultByTimeTuplesAndFlags(result, check, **kwargs) 19 | 20 | def setUp(self): 21 | self.cal = pdt.Calendar() 22 | (self.yr, self.mth, self.dy, self.hr, 23 | self.mn, self.sec, self.wd, self.yd, self.isdst) = time.localtime() 24 | 25 | def testSimpleMultipleItems(self): 26 | s = datetime.datetime.now() 27 | t = self.cal.inc(s, year=3) + datetime.timedelta(days=5, weeks=2) 28 | 29 | start = s.timetuple() 30 | target = t.timetuple() 31 | 32 | self.assertExpectedResult( 33 | self.cal.parse('3 years 2 weeks 5 days', start), (target, pdtContext(pdtContext.ACU_YEAR | pdtContext.ACU_WEEK | pdtContext.ACU_DAY))) 34 | self.assertExpectedResult( 35 | self.cal.parse('3years 2weeks 5days', start), (target, pdtContext(pdtContext.ACU_YEAR | pdtContext.ACU_WEEK | pdtContext.ACU_DAY))) 36 | 37 | def testMultipleItemsSingleCharUnits(self): 38 | s = datetime.datetime.now() 39 | t = self.cal.inc(s, year=3) + datetime.timedelta(days=5, weeks=2) 40 | 41 | start = s.timetuple() 42 | target = t.timetuple() 43 | 44 | self.assertExpectedResult( 45 | self.cal.parse('3 y 2 w 5 d', start), (target, pdtContext(pdtContext.ACU_YEAR | pdtContext.ACU_WEEK | pdtContext.ACU_DAY))) 46 | self.assertExpectedResult( 47 | self.cal.parse('3y 2w 5d', start), (target, pdtContext(pdtContext.ACU_YEAR | pdtContext.ACU_WEEK | pdtContext.ACU_DAY))) 48 | 49 | t = self.cal.inc(s, year=3) + datetime.timedelta(hours=5, minutes=50) 50 | target = t.timetuple() 51 | 52 | self.assertExpectedResult( 53 | self.cal.parse('3y 5h 50m', start), (target, pdtContext(pdtContext.ACU_YEAR | pdtContext.ACU_HOUR | pdtContext.ACU_MIN))) 54 | 55 | def testMultipleItemsWithPunctuation(self): 56 | s = datetime.datetime.now() 57 | t = self.cal.inc(s, year=3) + datetime.timedelta(days=5, weeks=2) 58 | 59 | start = s.timetuple() 60 | target = t.timetuple() 61 | 62 | self.assertExpectedResult( 63 | self.cal.parse('3 years, 2 weeks, 5 days', start), (target, pdtContext(pdtContext.ACU_YEAR | pdtContext.ACU_WEEK | pdtContext.ACU_DAY))) 64 | self.assertExpectedResult( 65 | self.cal.parse('3 years, 2 weeks and 5 days', start), (target, pdtContext(pdtContext.ACU_YEAR | pdtContext.ACU_WEEK | pdtContext.ACU_DAY))) 66 | self.assertExpectedResult( 67 | self.cal.parse('3y, 2w, 5d ', start), (target, pdtContext(pdtContext.ACU_YEAR | pdtContext.ACU_WEEK | pdtContext.ACU_DAY))) 68 | 69 | def testUnixATStyle(self): 70 | s = datetime.datetime.now() 71 | t = s + datetime.timedelta(days=3) 72 | 73 | t = t.replace(hour=16, minute=0, second=0) 74 | 75 | start = s.timetuple() 76 | target = t.timetuple() 77 | 78 | self.assertExpectedResult( 79 | self.cal.parse('4pm + 3 days', start), (target, pdtContext(pdtContext.ACU_DAY | pdtContext.ACU_HOUR))) 80 | self.assertExpectedResult( 81 | self.cal.parse('4pm +3 days', start), (target, pdtContext(pdtContext.ACU_DAY | pdtContext.ACU_HOUR))) 82 | 83 | def testUnixATStyleNegative(self): 84 | s = datetime.datetime.now() 85 | t = s + datetime.timedelta(days=-3) 86 | 87 | t = t.replace(hour=16, minute=0, second=0) 88 | 89 | start = s.timetuple() 90 | target = t.timetuple() 91 | 92 | self.assertExpectedResult( 93 | self.cal.parse('4pm - 3 days', start), (target, pdtContext(pdtContext.ACU_DAY | pdtContext.ACU_HOUR))) 94 | self.assertExpectedResult( 95 | self.cal.parse('4pm -3 days', start), (target, pdtContext(pdtContext.ACU_DAY | pdtContext.ACU_HOUR))) 96 | 97 | 98 | if __name__ == "__main__": 99 | unittest.main() 100 | -------------------------------------------------------------------------------- /tests/TestNlp.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Test parsing of strings that are phrases 4 | """ 5 | import sys 6 | import time 7 | import datetime 8 | import unittest 9 | import parsedatetime as pdt 10 | from parsedatetime.context import pdtContext 11 | from . import utils 12 | 13 | 14 | class test(unittest.TestCase): 15 | 16 | # a special compare function for nlp returned data 17 | 18 | @utils.assertEqualWithComparator 19 | def assertExpectedResult(self, result, check, dateOnly=False): 20 | target = result 21 | value = check 22 | 23 | if target is None and value is None: 24 | return True 25 | 26 | if (target is None and value is not None) or \ 27 | (target is not None and value is None): 28 | return False 29 | 30 | if len(target) != len(value): 31 | return False 32 | 33 | for i in range(0, len(target)): 34 | target_date = target[i][0] 35 | value_date = value[i][0] 36 | 37 | if target_date.year != value_date.year or \ 38 | target_date.month != value_date.month or \ 39 | target_date.day != value_date.day or \ 40 | target_date.hour != value_date.hour or \ 41 | target_date.minute != value_date.minute: 42 | return False 43 | if target[i][1] != value[i][1]: 44 | return False 45 | if target[i][2] != value[i][2]: 46 | return False 47 | if target[i][3] != value[i][3]: 48 | return False 49 | if target[i][4] != value[i][4]: 50 | return False 51 | 52 | return True 53 | 54 | def setUp(self): 55 | self.cal = pdt.Calendar() 56 | (self.yr, self.mth, self.dy, self.hr, 57 | self.mn, self.sec, self.wd, self.yd, self.isdst) = time.localtime() 58 | 59 | def testLongPhrase(self): 60 | # note: these tests do not need to be as dynamic as the others because 61 | # this is still based on the parse() function, so all tests of 62 | # the actual processing of the datetime value returned are 63 | # applicable to this. Here we are concerned with ensuring the 64 | # correct portions of text and their positions are extracted and 65 | # processed. 66 | start = datetime.datetime(2013, 8, 1, 21, 25, 0).timetuple() 67 | target = ((datetime.datetime(2013, 8, 5, 20, 0), 68 | pdtContext(pdtContext.ACU_MONTH | pdtContext.ACU_DAY | pdtContext.ACU_HOUR), 69 | 17, 37, 'At 8PM on August 5th'), 70 | (datetime.datetime(2013, 8, 9, 21, 0), 71 | pdtContext(pdtContext.ACU_DAY | pdtContext.ACU_HOUR), 72 | 72, 90, 'next Friday at 9PM'), 73 | (datetime.datetime(2013, 8, 1, 21, 30, 0), 74 | pdtContext(pdtContext.ACU_MIN), 75 | 120, 132, 'in 5 minutes'), 76 | (datetime.datetime(2013, 8, 8, 9, 0), 77 | pdtContext(pdtContext.ACU_WEEK), 78 | 173, 182, 'next week')) 79 | 80 | # positive testing 81 | self.assertExpectedResult(self.cal.nlp( 82 | "I'm so excited!! At 8PM on August 5th i'm going to fly to " 83 | "Florida. Then next Friday at 9PM i'm going to Dog n Bone! " 84 | "And in 5 minutes I'm going to eat some food! Talk to you " 85 | "next week.", start), target) 86 | 87 | target = datetime.datetime( 88 | self.yr, self.mth, self.dy, 17, 0, 0).timetuple() 89 | 90 | def testQuotes(self): 91 | # quotes should not interfere with datetime language recognition 92 | start = datetime.datetime(2013, 8, 1, 21, 25, 0).timetuple() 93 | target = self.cal.nlp( 94 | "I'm so excited!! At '8PM on August 5th' i'm going to fly to " 95 | "Florida. Then 'next Friday at 9PM' i'm going to Dog n Bone! " 96 | "And in '5 minutes' I'm going to eat some food! Talk to you " 97 | '"next week"', start) 98 | 99 | self.assertEqual(target[0][4], "At '8PM on August 5th") 100 | self.assertEqual(target[1][4], "next Friday at 9PM") 101 | self.assertEqual(target[2][4], "in '5 minutes") 102 | self.assertEqual(target[3][4], "next week") 103 | 104 | def testPrefixes(self): 105 | # nlp has special handling for on/in/at prefixes 106 | start = datetime.datetime(2013, 8, 1, 21, 25, 0).timetuple() 107 | 108 | target = self.cal.nlp("Buy a balloon on Monday", start) 109 | self.assertEqual(target[0][4], "on Monday") 110 | 111 | target = self.cal.nlp("Buy a balloon at noon", start) 112 | self.assertEqual(target[0][4], "at noon") 113 | 114 | target = self.cal.nlp("Buy a balloon in a month", start) 115 | self.assertEqual(target[0][4], "in a month") 116 | 117 | # Should no longer pull "on" off the end of balloon 118 | target = self.cal.nlp("Buy a balloon Monday", start) 119 | self.assertEqual(target[0][4], "Monday") 120 | 121 | def testFalsePositives(self): 122 | # negative testing - no matches should return None 123 | start = datetime.datetime(2013, 8, 1, 21, 25, 0).timetuple() 124 | self.assertExpectedResult(self.cal.nlp( 125 | "Next, I'm so excited!! So many things that are going to " 126 | "happen every week!!", start), None) 127 | self.assertExpectedResult(self.cal.nlp("$300", start), None) 128 | self.assertExpectedResult(self.cal.nlp("300ml", start), None) 129 | self.assertExpectedResult(self.cal.nlp("nice ass", start), None) 130 | -------------------------------------------------------------------------------- /tests/TestPhrases.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Test parsing of strings that are phrases 4 | """ 5 | import sys 6 | import time 7 | import datetime 8 | import unittest 9 | import parsedatetime as pdt 10 | from parsedatetime.context import pdtContext 11 | from . import utils 12 | 13 | 14 | class test(unittest.TestCase): 15 | 16 | @utils.assertEqualWithComparator 17 | def assertExpectedResult(self, result, check, **kwargs): 18 | return utils.compareResultByTimeTuplesAndFlags(result, check, **kwargs) 19 | 20 | def setUp(self): 21 | self.cal = pdt.Calendar() 22 | (self.yr, self.mth, self.dy, self.hr, 23 | self.mn, self.sec, self.wd, self.yd, self.isdst) = time.localtime() 24 | 25 | def testPhrases(self): 26 | # 27 | # NOTE - this test will fail under certain conditions 28 | # It is building an absolute date for comparison and then 29 | # testing the parsing of relative phrases and as such will fail 30 | # if run near the midnight transition. 31 | # Thanks to Chris Petrilli for asking about it and prompting me 32 | # to create this note! 33 | # 34 | start = datetime.datetime( 35 | self.yr, self.mth, self.dy, self.hr, self.mn, self.sec).timetuple() 36 | target = datetime.datetime( 37 | self.yr, self.mth, self.dy, 16, 0, 0).timetuple() 38 | 39 | self.assertExpectedResult( 40 | self.cal.parse('flight from SFO at 4pm', start), (target, pdtContext(pdtContext.ACU_HOUR))) 41 | 42 | target = datetime.datetime( 43 | self.yr, self.mth, self.dy, 17, 0, 0).timetuple() 44 | 45 | self.assertExpectedResult( 46 | self.cal.parse('eod', start), (target, pdtContext(pdtContext.ACU_HALFDAY))) 47 | self.assertExpectedResult( 48 | self.cal.parse('meeting eod', start), (target, pdtContext(pdtContext.ACU_HALFDAY))) 49 | self.assertExpectedResult( 50 | self.cal.parse('eod meeting', start), (target, pdtContext(pdtContext.ACU_HALFDAY))) 51 | 52 | target = datetime.datetime( 53 | self.yr, self.mth, self.dy, 17, 0, 0) + datetime.timedelta(days=1) 54 | target = target.timetuple() 55 | 56 | self.assertExpectedResult( 57 | self.cal.parse('tomorrow eod', start), (target, pdtContext(pdtContext.ACU_DAY | pdtContext.ACU_HALFDAY))) 58 | self.assertExpectedResult( 59 | self.cal.parse('eod tomorrow', start), (target, pdtContext(pdtContext.ACU_DAY | pdtContext.ACU_HALFDAY))) 60 | 61 | def testPhraseWithDays_DOWStyle_1_False(self): 62 | s = datetime.datetime.now() 63 | 64 | # find out what day we are currently on 65 | # and determine what the next day of week is 66 | t = s + datetime.timedelta(days=1) 67 | start = s.timetuple() 68 | 69 | (yr, mth, dy, _, _, _, wd, yd, isdst) = t.timetuple() 70 | 71 | target = (yr, mth, dy, 17, 0, 0, wd, yd, isdst) 72 | 73 | d = self.wd + 1 74 | if d > 6: 75 | d = 0 76 | 77 | day = self.cal.ptc.Weekdays[d] 78 | 79 | self.assertExpectedResult( 80 | self.cal.parse('eod %s' % day, start), (target, pdtContext(pdtContext.ACU_DAY | pdtContext.ACU_HALFDAY | pdtContext.ACU_HOUR))) 81 | 82 | # find out what day we are currently on 83 | # and determine what the previous day of week is 84 | t = s + datetime.timedelta(days=6) 85 | 86 | (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = t.timetuple() 87 | 88 | target = (yr, mth, dy, 17, 0, 0, wd, yd, isdst) 89 | 90 | d = self.wd - 1 91 | if d < 0: 92 | d = 6 93 | 94 | day = self.cal.ptc.Weekdays[d] 95 | 96 | self.assertExpectedResult( 97 | self.cal.parse('eod %s' % day, start), (target, pdtContext(pdtContext.ACU_DAY | pdtContext.ACU_HALFDAY | pdtContext.ACU_HOUR))) 98 | 99 | def testEndOfPhrases(self): 100 | s = datetime.datetime.now() 101 | 102 | # find out what month we are currently on 103 | # set the day to 1 and then go back a day 104 | # to get the end of the current month 105 | (yr, mth, _, hr, mn, sec, _, _, _) = s.timetuple() 106 | 107 | mth += 1 108 | if mth > 12: 109 | mth = 1 110 | yr += 1 111 | 112 | t = datetime.datetime( 113 | yr, mth, 1, 9, 0, 0) + datetime.timedelta(days=-1) 114 | 115 | start = s.timetuple() 116 | target = t.timetuple() 117 | 118 | self.assertExpectedResult( 119 | self.cal.parse('eom', start), (target, pdtContext(pdtContext.ACU_DAY))) 120 | self.assertExpectedResult( 121 | self.cal.parse('meeting eom', start), (target, pdtContext(pdtContext.ACU_DAY))) 122 | 123 | s = datetime.datetime.now() 124 | 125 | (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = s.timetuple() 126 | 127 | t = datetime.datetime(yr, 12, 31, 9, 0, 0) 128 | 129 | start = s.timetuple() 130 | target = t.timetuple() 131 | 132 | self.assertExpectedResult( 133 | self.cal.parse('eoy', start), (target, pdtContext(pdtContext.ACU_MONTH))) 134 | self.assertExpectedResult( 135 | self.cal.parse('meeting eoy', start), (target, pdtContext(pdtContext.ACU_MONTH))) 136 | 137 | def testLastPhrases(self): 138 | for day in (11, 12, 13, 14, 15, 16, 17): 139 | start = datetime.datetime(2012, 11, day, 9, 0, 0) 140 | 141 | (yr, mth, dy, _, _, _, wd, yd, isdst) = start.timetuple() 142 | 143 | n = 4 - wd 144 | if n >= 0: 145 | n -= 7 146 | 147 | target = start + datetime.timedelta(days=n) 148 | 149 | self.assertExpectedResult( 150 | self.cal.parse('last friday', start.timetuple()), 151 | (target.timetuple(), pdtContext(pdtContext.ACU_DAY)), dateOnly=True) 152 | -------------------------------------------------------------------------------- /tests/TestRanges.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Test parsing of simple date and times 4 | """ 5 | import sys 6 | import time 7 | import datetime 8 | import unittest 9 | import parsedatetime as pdt 10 | from parsedatetime.context import pdtContext 11 | from . import utils 12 | 13 | 14 | class test(unittest.TestCase): 15 | 16 | @utils.assertEqualWithComparator 17 | def assertExpectedResult(self, result, check, **kwargs): 18 | return utils.compareResultByTimeTupleRangesAndFlags( 19 | result, check, **kwargs) 20 | 21 | def setUp(self): 22 | self.cal = pdt.Calendar() 23 | (self.yr, self.mth, self.dy, self.hr, 24 | self.mn, self.sec, self.wd, self.yd, self.isdst) = time.localtime() 25 | 26 | def testTimes(self): 27 | start = datetime.datetime( 28 | self.yr, self.mth, self.dy, self.hr, self.mn, self.sec).timetuple() 29 | 30 | targetStart = datetime.datetime( 31 | self.yr, self.mth, self.dy, 14, 0, 0).timetuple() 32 | targetEnd = datetime.datetime( 33 | self.yr, self.mth, self.dy, 17, 30, 0).timetuple() 34 | 35 | self.assertExpectedResult(self.cal.evalRanges( 36 | "2 pm - 5:30 pm", start), (targetStart, targetEnd, 2)) 37 | self.assertExpectedResult(self.cal.evalRanges( 38 | "2pm - 5:30pm", start), (targetStart, targetEnd, 2)) 39 | self.assertExpectedResult(self.cal.evalRanges( 40 | "2:00:00 pm - 5:30:00 pm", start), (targetStart, targetEnd, 2)) 41 | self.assertExpectedResult(self.cal.evalRanges( 42 | "2 - 5:30pm", start), (targetStart, targetEnd, 2)) 43 | self.assertExpectedResult(self.cal.evalRanges( 44 | "14:00 - 17:30", start), (targetStart, targetEnd, 2)) 45 | 46 | targetStart = datetime.datetime( 47 | self.yr, self.mth, self.dy, 10, 0, 0).timetuple() 48 | targetEnd = datetime.datetime( 49 | self.yr, self.mth, self.dy, 13, 30, 0).timetuple() 50 | 51 | self.assertExpectedResult(self.cal.evalRanges( 52 | "10AM - 1:30PM", start), (targetStart, targetEnd, 2)) 53 | self.assertExpectedResult(self.cal.evalRanges( 54 | "10:00:00 am - 1:30:00 pm", start), (targetStart, targetEnd, 2)) 55 | self.assertExpectedResult(self.cal.evalRanges( 56 | "10:00 - 13:30", start), (targetStart, targetEnd, 2)) 57 | 58 | targetStart = datetime.datetime( 59 | self.yr, self.mth, self.dy, 15, 30, 0).timetuple() 60 | targetEnd = datetime.datetime( 61 | self.yr, self.mth, self.dy, 17, 0, 0).timetuple() 62 | 63 | self.assertExpectedResult( 64 | self.cal.evalRanges("today 3:30-5PM", start), 65 | (targetStart, targetEnd, 2)) 66 | 67 | def testDates(self): 68 | start = datetime.datetime( 69 | self.yr, self.mth, self.dy, self.hr, self.mn, self.sec).timetuple() 70 | 71 | targetStart = datetime.datetime( 72 | 2006, 8, 29, self.hr, self.mn, self.sec).timetuple() 73 | targetEnd = datetime.datetime( 74 | 2006, 9, 2, self.hr, self.mn, self.sec).timetuple() 75 | 76 | self.assertExpectedResult( 77 | self.cal.evalRanges("August 29, 2006 - September 2, 2006", start), 78 | (targetStart, targetEnd, 1)) 79 | self.assertExpectedResult( 80 | self.cal.evalRanges("August 29 - September 2, 2006", start), 81 | (targetStart, targetEnd, 1)) 82 | 83 | targetStart = datetime.datetime( 84 | 2006, 8, 29, self.hr, self.mn, self.sec).timetuple() 85 | targetEnd = datetime.datetime( 86 | 2006, 9, 2, self.hr, self.mn, self.sec).timetuple() 87 | 88 | self.assertExpectedResult( 89 | self.cal.evalRanges("08/29/06 - 09/02/06", start), 90 | (targetStart, targetEnd, 1)) 91 | 92 | def _testSubRanges(self): 93 | start = datetime.datetime( 94 | self.yr, self.mth, self.dy, self.hr, self.mn, self.sec).timetuple() 95 | 96 | targetStart = datetime.datetime(2006, 8, 1, 9, 0, 0).timetuple() 97 | targetEnd = datetime.datetime(2006, 8, 15, 9, 0, 0).timetuple() 98 | 99 | self.assertExpectedResult( 100 | self.cal.evalRanges("August 1-15, 2006", start), 101 | (targetStart, targetEnd, 1)) 102 | 103 | 104 | if __name__ == "__main__": 105 | unittest.main() 106 | -------------------------------------------------------------------------------- /tests/TestRussianLocale.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Test parsing of simple date and times using the Russian locale 4 | """ 5 | import sys 6 | import time 7 | import datetime 8 | import unittest 9 | import parsedatetime as pdt 10 | from parsedatetime.context import pdtContext 11 | from . import utils 12 | 13 | 14 | class test(unittest.TestCase): 15 | 16 | @utils.assertEqualWithComparator 17 | def assertExpectedResult(self, result, check, **kwargs): 18 | return utils.compareResultByTimeTuplesAndFlags(result, check, **kwargs) 19 | 20 | def setUp(self): 21 | locale = 'ru_RU' 22 | self.ptc = pdt.Constants(locale, usePyICU=False) 23 | self.cal = pdt.Calendar(self.ptc) 24 | 25 | (self.yr, self.mth, self.dy, self.hr, 26 | self.mn, self.sec, self.wd, self.yd, self.isdst) = time.localtime() 27 | 28 | if self.ptc.localeID != locale: 29 | raise unittest.SkipTest( 30 | 'Locale not set to %s - check if PyICU is installed' % locale) 31 | 32 | def testTimes(self): 33 | start = datetime.datetime( 34 | self.yr, self.mth, self.dy, self.hr, self.mn, self.sec).timetuple() 35 | target = datetime.datetime( 36 | self.yr, self.mth, self.dy, 23, 0, 0).timetuple() 37 | 38 | self.assertExpectedResult( 39 | self.cal.parse('23:00:00', start), (target, pdtContext(pdtContext.ACU_HOUR | pdtContext.ACU_MIN | pdtContext.ACU_SEC))) 40 | self.assertExpectedResult(self.cal.parse('23:00', start), (target, pdtContext(pdtContext.ACU_HOUR | pdtContext.ACU_MIN))) 41 | self.assertExpectedResult(self.cal.parse('2300', start), (target, pdtContext(pdtContext.ACU_HOUR | pdtContext.ACU_MIN))) 42 | 43 | target = datetime.datetime( 44 | self.yr, self.mth, self.dy, 11, 0, 0).timetuple() 45 | 46 | self.assertExpectedResult( 47 | self.cal.parse('11:00:00', start), (target, pdtContext(pdtContext.ACU_HOUR | pdtContext.ACU_MIN | pdtContext.ACU_SEC))) 48 | self.assertExpectedResult(self.cal.parse('11:00', start), (target, pdtContext(pdtContext.ACU_HOUR | pdtContext.ACU_MIN))) 49 | self.assertExpectedResult(self.cal.parse('1100', start), (target, pdtContext(pdtContext.ACU_HOUR | pdtContext.ACU_MIN))) 50 | 51 | target = datetime.datetime( 52 | self.yr, self.mth, self.dy, 7, 30, 0).timetuple() 53 | 54 | self.assertExpectedResult(self.cal.parse('730', start), (target, pdtContext(pdtContext.ACU_HOUR | pdtContext.ACU_MIN))) 55 | self.assertExpectedResult(self.cal.parse('0730', start), (target, pdtContext(pdtContext.ACU_HOUR | pdtContext.ACU_MIN))) 56 | 57 | target = datetime.datetime( 58 | self.yr, self.mth, self.dy, 17, 30, 0).timetuple() 59 | 60 | self.assertExpectedResult(self.cal.parse('1730', start), (target, pdtContext(pdtContext.ACU_HOUR | pdtContext.ACU_MIN))) 61 | self.assertExpectedResult(self.cal.parse('173000', start), (target, pdtContext(pdtContext.ACU_HOUR | pdtContext.ACU_MIN | pdtContext.ACU_SEC))) 62 | 63 | def testDates(self): 64 | start = datetime.datetime( 65 | self.yr, self.mth, self.dy, self.hr, self.mn, self.sec).timetuple() 66 | target = datetime.datetime( 67 | 2006, 8, 25, self.hr, self.mn, self.sec).timetuple() 68 | 69 | self.assertExpectedResult( 70 | self.cal.parse('25.08.2006', start), (target, pdtContext(pdtContext.ACU_YEAR | pdtContext.ACU_MONTH | pdtContext.ACU_DAY))) 71 | self.assertExpectedResult( 72 | self.cal.parse('25.8.06', start), (target, pdtContext(pdtContext.ACU_YEAR | pdtContext.ACU_MONTH | pdtContext.ACU_DAY))) 73 | 74 | if self.mth > 8 or (self.mth == 8 and self.dy > 25): 75 | target = datetime.datetime( 76 | self.yr + 1, 8, 25, self.hr, self.mn, self.sec).timetuple() 77 | else: 78 | target = datetime.datetime( 79 | self.yr, 8, 25, self.hr, self.mn, self.sec).timetuple() 80 | 81 | self.assertExpectedResult(self.cal.parse('25.8', start), (target, pdtContext(pdtContext.ACU_MONTH | pdtContext.ACU_DAY))) 82 | self.assertExpectedResult(self.cal.parse('25.08', start), (target, pdtContext(pdtContext.ACU_MONTH | pdtContext.ACU_DAY))) 83 | 84 | def testDatesLang(self): 85 | if sys.version_info >= (3, 0): 86 | target = datetime.datetime(2006, 8, 25, 23, 5).timetuple() 87 | self.assertExpectedResult( 88 | self.cal.parse('25 августа 2006 23:05'), (target, pdtContext(pdtContext.ACU_YEAR | pdtContext.ACU_MONTH | pdtContext.ACU_DAY | pdtContext.ACU_HOUR | pdtContext.ACU_MIN))) 89 | target = datetime.datetime( 90 | 2006, 8, 25, self.hr, self.mn, self.sec).timetuple() 91 | self.assertExpectedResult( 92 | self.cal.parse('25 августа 2006'), (target, pdtContext(pdtContext.ACU_YEAR | pdtContext.ACU_MONTH | pdtContext.ACU_DAY))) 93 | 94 | def testConjugate(self): 95 | if sys.version_info >= (3, 0): 96 | target = datetime.datetime(2006, 9, 25, 23, 5).timetuple() 97 | self.assertExpectedResult( 98 | self.cal.parse('25 сентября 2006 23:05'), (target, pdtContext(pdtContext.ACU_YEAR | pdtContext.ACU_MONTH | pdtContext.ACU_DAY | pdtContext.ACU_HOUR | pdtContext.ACU_MIN))) 99 | # self.assertExpectedResult( 100 | # self.cal.parse('25 сентябрь 2006 23:05'), (target, pdtContext(pdtContext.ACU_YEAR | pdtContext.ACU_MONTH | pdtContext.ACU_DAY | pdtContext.ACU_HOUR | pdtContext.ACU_MIN))) 101 | 102 | def testdayOffsets(self): 103 | def get_datetime(tuple_time): 104 | return datetime.datetime(*tuple_time[:6]).date() 105 | 106 | now = datetime.datetime.today().date() 107 | 108 | self.assertEqual( 109 | get_datetime(self.cal.parse("вчера")[0]), 110 | now - datetime.timedelta(days=1) 111 | ) 112 | self.assertEqual( 113 | get_datetime(self.cal.parse("завтра")[0]), 114 | now + datetime.timedelta(days=1) 115 | ) 116 | 117 | self.assertEqual( 118 | get_datetime(self.cal.parse("позавчера")[0]), 119 | now - datetime.timedelta(days=2) 120 | ) 121 | 122 | self.assertEqual( 123 | get_datetime(self.cal.parse("послезавтра")[0]), 124 | now + datetime.timedelta(days=2) 125 | ) 126 | 127 | 128 | if __name__ == "__main__": 129 | unittest.main() 130 | -------------------------------------------------------------------------------- /tests/TestSimpleOffsets.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Test parsing of 'simple' offsets 4 | """ 5 | import sys 6 | import time 7 | import datetime 8 | import calendar 9 | import unittest 10 | import parsedatetime as pdt 11 | from parsedatetime.context import pdtContext 12 | from . import utils 13 | 14 | 15 | def _truncateResult(result, trunc_seconds=True, trunc_hours=False): 16 | try: 17 | dt, flag = result 18 | except ValueError: 19 | # wtf?! 20 | return result 21 | if trunc_seconds: 22 | dt = dt[:5] + (0,) * 4 23 | if trunc_hours: 24 | dt = dt[:3] + (0,) * 6 25 | return dt, flag 26 | 27 | 28 | _tr = _truncateResult 29 | 30 | 31 | class test(unittest.TestCase): 32 | 33 | @utils.assertEqualWithComparator 34 | def assertExpectedResult(self, result, check, **kwargs): 35 | return utils.compareResultByTimeTuplesAndFlags(result, check, **kwargs) 36 | 37 | def setUp(self): 38 | self.cal = pdt.Calendar() 39 | (self.yr, self.mth, self.dy, self.hr, 40 | self.mn, self.sec, self.wd, self.yd, self.isdst) = time.localtime() 41 | 42 | def testNow(self): 43 | s = datetime.datetime.now() 44 | 45 | start = s.timetuple() 46 | target = s.timetuple() 47 | 48 | self.assertExpectedResult( 49 | self.cal.parse('now', start), 50 | (target, pdtContext(pdtContext.ACU_NOW))) 51 | 52 | def testRightNow(self): 53 | s = datetime.datetime.now() 54 | 55 | start = s.timetuple() 56 | target = s.timetuple() 57 | 58 | self.assertExpectedResult( 59 | self.cal.parse('right now', start), 60 | (target, pdtContext(pdtContext.ACU_NOW))) 61 | 62 | def testOffsetFromDayOfWeek(self): 63 | self.cal.ptc.StartTimeFromSourceTime = True 64 | 65 | s = datetime.datetime(2016, 2, 16) # a Tuesday 66 | t = datetime.datetime(2016, 2, 18) # Thursday of the same week 67 | tPlusOffset = t + datetime.timedelta(hours=1) 68 | 69 | start = s.timetuple() 70 | target = t.timetuple() 71 | targetPlusOffset = tPlusOffset.timetuple() 72 | 73 | self.assertExpectedResult( 74 | self.cal.parse('Thursday', start), (target, pdtContext(pdtContext.ACU_DAY))) 75 | 76 | self.assertExpectedResult( 77 | self.cal.parse('one hour from Thursday', start), 78 | (targetPlusOffset, pdtContext(pdtContext.ACU_DAY | pdtContext.ACU_HOUR))) 79 | 80 | def testOffsetBeforeDayOfWeek(self): 81 | self.cal.ptc.StartTimeFromSourceTime = True 82 | 83 | s = datetime.datetime(2016, 2, 16) # a Tuesday 84 | t = datetime.datetime(2016, 2, 18) # Thursday of the same week 85 | tPlusOffset = t + datetime.timedelta(hours=-1) 86 | 87 | start = s.timetuple() 88 | target = t.timetuple() 89 | targetPlusOffset = tPlusOffset.timetuple() 90 | 91 | self.assertExpectedResult( 92 | self.cal.parse('Thursday', start), 93 | (target, pdtContext(pdtContext.ACU_DAY))) 94 | 95 | self.assertExpectedResult( 96 | self.cal.parse('one hour before Thursday', start), 97 | (targetPlusOffset, pdtContext(pdtContext.ACU_DAY | pdtContext.ACU_HOUR))) 98 | 99 | def testMinutesFromNow(self): 100 | s = datetime.datetime.now() 101 | t = s + datetime.timedelta(minutes=5) 102 | 103 | start = s.timetuple() 104 | target = t.timetuple() 105 | 106 | self.assertExpectedResult( 107 | self.cal.parse('5 minutes from now', start), 108 | (target, pdtContext(pdtContext.ACU_MIN | pdtContext.ACU_NOW))) 109 | self.assertExpectedResult( 110 | self.cal.parse('5 min from now', start), 111 | (target, pdtContext(pdtContext.ACU_MIN | pdtContext.ACU_NOW))) 112 | self.assertExpectedResult( 113 | self.cal.parse('5m from now', start), 114 | (target, pdtContext(pdtContext.ACU_MIN | pdtContext.ACU_NOW))) 115 | self.assertExpectedResult( 116 | self.cal.parse('in 5 minutes', start), 117 | (target, pdtContext(pdtContext.ACU_MIN))) 118 | self.assertExpectedResult( 119 | self.cal.parse('in 5 min', start), 120 | (target, pdtContext(pdtContext.ACU_MIN))) 121 | self.assertExpectedResult( 122 | self.cal.parse('5 minutes', start), 123 | (target, pdtContext(pdtContext.ACU_MIN))) 124 | self.assertExpectedResult( 125 | self.cal.parse('5 min', start), 126 | (target, pdtContext(pdtContext.ACU_MIN))) 127 | self.assertExpectedResult( 128 | self.cal.parse('5m', start), 129 | (target, pdtContext(pdtContext.ACU_MIN))) 130 | 131 | self.assertExpectedResult( 132 | self.cal.parse('five minutes from now', start), 133 | (target, pdtContext(pdtContext.ACU_MIN | pdtContext.ACU_NOW))) 134 | self.assertExpectedResult( 135 | self.cal.parse('five min from now', start), 136 | (target, pdtContext(pdtContext.ACU_MIN | pdtContext.ACU_NOW))) 137 | self.assertExpectedResult( 138 | self.cal.parse('in five minutes', start), 139 | (target, pdtContext(pdtContext.ACU_MIN))) 140 | self.assertExpectedResult( 141 | self.cal.parse('in five min', start), 142 | (target, pdtContext(pdtContext.ACU_MIN))) 143 | self.assertExpectedResult( 144 | self.cal.parse('five minutes', start), 145 | (target, pdtContext(pdtContext.ACU_MIN))) 146 | self.assertExpectedResult( 147 | self.cal.parse('five min', start), 148 | (target, pdtContext(pdtContext.ACU_MIN))) 149 | 150 | def testMinutesBeforeNow(self): 151 | s = datetime.datetime.now() 152 | t = s + datetime.timedelta(minutes=-5) 153 | 154 | start = s.timetuple() 155 | target = t.timetuple() 156 | 157 | self.assertExpectedResult( 158 | self.cal.parse('5 minutes before now', start), 159 | (target, pdtContext(pdtContext.ACU_MIN | pdtContext.ACU_NOW))) 160 | self.assertExpectedResult( 161 | self.cal.parse('5 min before now', start), 162 | (target, pdtContext(pdtContext.ACU_MIN | pdtContext.ACU_NOW))) 163 | self.assertExpectedResult( 164 | self.cal.parse('5m before now', start), 165 | (target, pdtContext(pdtContext.ACU_MIN | pdtContext.ACU_NOW))) 166 | self.assertExpectedResult( 167 | self.cal.parse('5 minutes ago', start), 168 | (target, pdtContext(pdtContext.ACU_MIN))) 169 | self.assertExpectedResult( 170 | self.cal.parse('five minutes before now', start), 171 | (target, pdtContext(pdtContext.ACU_MIN | pdtContext.ACU_NOW))) 172 | self.assertExpectedResult( 173 | self.cal.parse('five min before now', start), 174 | (target, pdtContext(pdtContext.ACU_MIN | pdtContext.ACU_NOW))) 175 | 176 | def testWeekFromNow(self): 177 | s = datetime.datetime.now() 178 | t = s + datetime.timedelta(weeks=1) 179 | 180 | start = s.timetuple() 181 | target = t.timetuple() 182 | 183 | self.assertExpectedResult( 184 | self.cal.parse('in 1 week', start), 185 | (target, pdtContext(pdtContext.ACU_WEEK))) 186 | self.assertExpectedResult( 187 | self.cal.parse('1 week from now', start), 188 | (target, pdtContext(pdtContext.ACU_WEEK | pdtContext.ACU_NOW))) 189 | self.assertExpectedResult( 190 | self.cal.parse('in one week', start), 191 | (target, pdtContext(pdtContext.ACU_WEEK))) 192 | self.assertExpectedResult( 193 | self.cal.parse('one week from now', start), 194 | (target, pdtContext(pdtContext.ACU_WEEK | pdtContext.ACU_NOW))) 195 | self.assertExpectedResult( 196 | self.cal.parse('in a week', start), 197 | (target, pdtContext(pdtContext.ACU_WEEK))) 198 | self.assertExpectedResult( 199 | self.cal.parse('a week from now', start), 200 | (target, pdtContext(pdtContext.ACU_WEEK | pdtContext.ACU_NOW))) 201 | self.assertExpectedResult( 202 | self.cal.parse('in 7 days', start), 203 | (target, pdtContext(pdtContext.ACU_DAY))) 204 | self.assertExpectedResult( 205 | self.cal.parse('7 days from now', start), 206 | (target, pdtContext(pdtContext.ACU_DAY | pdtContext.ACU_NOW))) 207 | self.assertExpectedResult( 208 | self.cal.parse('in seven days', start), 209 | (target, pdtContext(pdtContext.ACU_DAY))) 210 | self.assertExpectedResult( 211 | self.cal.parse('seven days from now', start), 212 | (target, pdtContext(pdtContext.ACU_DAY | pdtContext.ACU_NOW))) 213 | self.assertEqual(_tr(self.cal.parse('next week', start), 214 | trunc_hours=True), 215 | _tr((target, pdtContext(pdtContext.ACU_WEEK)), 216 | trunc_hours=True)) 217 | 218 | def testNextWeekDay(self): 219 | start = datetime.datetime.now() 220 | target = start + datetime.timedelta(days=4 + 7 - start.weekday()) 221 | start = start.timetuple() 222 | target = target.timetuple() 223 | 224 | self.assertExpectedResult(self.cal.parse('next friday', start), 225 | (target, pdtContext(pdtContext.ACU_DAY)), 226 | dateOnly=True) 227 | self.assertExpectedResult(self.cal.parse('next friday?', start), 228 | (target, pdtContext(pdtContext.ACU_DAY)), 229 | dateOnly=True) 230 | self.cal.ptc.StartTimeFromSourceTime = True 231 | self.assertExpectedResult(self.cal.parse('next friday', start), 232 | (target, pdtContext(pdtContext.ACU_DAY))) 233 | 234 | def testNextWeekDayWithTime(self): 235 | start = datetime.datetime.now() 236 | target = start + datetime.timedelta(days=4 + 7 - start.weekday()) 237 | target = target.replace(hour=13, minute=0, second=0) 238 | target = target.timetuple() 239 | 240 | self.assertExpectedResult(self.cal.parse('next friday at 1pm', start), 241 | (target, pdtContext(pdtContext.ACU_DAY | pdtContext.ACU_HOUR))) 242 | self.assertExpectedResult(self.cal.parse('1pm next friday', start), 243 | (target, pdtContext(pdtContext.ACU_DAY | pdtContext.ACU_HOUR))) 244 | 245 | target = start + datetime.timedelta(days=4 - start.weekday()) 246 | target = target.replace(hour=13, minute=0, second=0) 247 | target = target.timetuple() 248 | self.assertExpectedResult(self.cal.parse('1pm this friday', start), 249 | (target, pdtContext(pdtContext.ACU_DAY | pdtContext.ACU_HOUR))) 250 | 251 | def testWeekBeforeNow(self): 252 | s = datetime.datetime.now() 253 | t = s + datetime.timedelta(weeks=-1) 254 | 255 | start = s.timetuple() 256 | target = t.timetuple() 257 | 258 | self.assertEqual(_tr(self.cal.parse('1 week before now', start)), 259 | _tr((target, pdtContext(pdtContext.ACU_WEEK | pdtContext.ACU_NOW)))) 260 | self.assertEqual(_tr(self.cal.parse('one week before now', start)), 261 | _tr((target, pdtContext(pdtContext.ACU_WEEK | pdtContext.ACU_NOW)))) 262 | self.assertEqual(_tr(self.cal.parse('a week before now', start)), 263 | _tr((target, pdtContext(pdtContext.ACU_WEEK | pdtContext.ACU_NOW)))) 264 | self.assertEqual(_tr(self.cal.parse('7 days before now', start)), 265 | _tr((target, pdtContext(pdtContext.ACU_DAY | pdtContext.ACU_NOW)))) 266 | self.assertEqual(_tr(self.cal.parse('seven days before now', start)), 267 | _tr((target, pdtContext(pdtContext.ACU_DAY | pdtContext.ACU_NOW)))) 268 | self.assertEqual(_tr(self.cal.parse('1 week ago', start)), 269 | _tr((target, pdtContext(pdtContext.ACU_WEEK)))) 270 | self.assertEqual(_tr(self.cal.parse('a week ago', start)), 271 | _tr((target, pdtContext(pdtContext.ACU_WEEK)))) 272 | self.assertEqual(_tr(self.cal.parse('last week', start), 273 | trunc_hours=True), 274 | _tr((target, pdtContext(pdtContext.ACU_WEEK)), 275 | trunc_hours=True)) 276 | 277 | def testNextMonth(self): 278 | s = (datetime.datetime(self.yr, self.mth, self.dy, 279 | self.hr, self.mn, self.sec) + 280 | datetime.timedelta(days=1)) 281 | t = self.cal.inc(s, year=1) 282 | 283 | start = s.timetuple() 284 | target = t.timetuple() 285 | 286 | phrase = 'next %s %s' % (calendar.month_name[t.month], t.day) 287 | 288 | self.assertEqual(_tr(self.cal.parse(phrase, start)), 289 | _tr((target, pdtContext(pdtContext.ACU_MONTH | pdtContext.ACU_DAY)))) 290 | 291 | def testSpecials(self): 292 | s = datetime.datetime.now() 293 | t = datetime.datetime( 294 | self.yr, self.mth, self.dy, 9, 0, 0) + datetime.timedelta(days=1) 295 | 296 | start = s.timetuple() 297 | target = t.timetuple() 298 | 299 | self.assertExpectedResult( 300 | self.cal.parse('tomorrow', start), 301 | (target, pdtContext(pdtContext.ACU_DAY))) 302 | self.assertExpectedResult( 303 | self.cal.parse('next day', start), 304 | (target, pdtContext(pdtContext.ACU_DAY))) 305 | 306 | t = datetime.datetime( 307 | self.yr, self.mth, self.dy, 9, 0, 0) + datetime.timedelta(days=-1) 308 | target = t.timetuple() 309 | 310 | self.assertExpectedResult( 311 | self.cal.parse('yesterday', start), 312 | (target, pdtContext(pdtContext.ACU_DAY))) 313 | 314 | t = datetime.datetime(self.yr, self.mth, self.dy, 9, 0, 0) 315 | target = t.timetuple() 316 | 317 | self.assertExpectedResult( 318 | self.cal.parse('today', start), 319 | (target, pdtContext(pdtContext.ACU_DAY))) 320 | 321 | 322 | if __name__ == "__main__": 323 | unittest.main() 324 | -------------------------------------------------------------------------------- /tests/TestSimpleOffsetsHours.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Test parsing of 'simple' offsets 4 | """ 5 | import sys 6 | import time 7 | import datetime 8 | import unittest 9 | import parsedatetime as pdt 10 | from parsedatetime.context import pdtContext 11 | from . import utils 12 | 13 | 14 | class test(unittest.TestCase): 15 | 16 | @utils.assertEqualWithComparator 17 | def assertExpectedResult(self, result, check, **kwargs): 18 | return utils.compareResultByTimeTuplesAndFlags(result, check, **kwargs) 19 | 20 | def setUp(self): 21 | self.cal = pdt.Calendar() 22 | (self.yr, self.mth, self.dy, self.hr, 23 | self.mn, self.sec, self.wd, self.yd, self.isdst) = time.localtime() 24 | 25 | def testHoursFromNow(self): 26 | s = datetime.datetime.now() 27 | t = s + datetime.timedelta(hours=5) 28 | 29 | start = s.timetuple() 30 | target = t.timetuple() 31 | 32 | self.assertExpectedResult( 33 | self.cal.parse('5 hours from now', start), 34 | (target, pdtContext(pdtContext.ACU_HOUR | pdtContext.ACU_NOW))) 35 | self.assertExpectedResult( 36 | self.cal.parse('5 hour from now', start), 37 | (target, pdtContext(pdtContext.ACU_HOUR | pdtContext.ACU_NOW))) 38 | self.assertExpectedResult( 39 | self.cal.parse('5 hr from now', start), 40 | (target, pdtContext(pdtContext.ACU_HOUR | pdtContext.ACU_NOW))) 41 | self.assertExpectedResult( 42 | self.cal.parse('in 5 hours', start), 43 | (target, pdtContext(pdtContext.ACU_HOUR))) 44 | self.assertExpectedResult( 45 | self.cal.parse('in 5 hour', start), 46 | (target, pdtContext(pdtContext.ACU_HOUR))) 47 | self.assertExpectedResult( 48 | self.cal.parse('5 hours', start), 49 | (target, pdtContext(pdtContext.ACU_HOUR))) 50 | self.assertExpectedResult(self.cal.parse('5 hr', start), 51 | (target, pdtContext(pdtContext.ACU_HOUR))) 52 | self.assertExpectedResult(self.cal.parse('5h', start), 53 | (target, pdtContext(pdtContext.ACU_HOUR))) 54 | 55 | self.assertExpectedResult( 56 | self.cal.parse('five hours from now', start), 57 | (target, pdtContext(pdtContext.ACU_HOUR | pdtContext.ACU_NOW))) 58 | self.assertExpectedResult( 59 | self.cal.parse('five hour from now', start), 60 | (target, pdtContext(pdtContext.ACU_HOUR | pdtContext.ACU_NOW))) 61 | self.assertExpectedResult( 62 | self.cal.parse('five hr from now', start), 63 | (target, pdtContext(pdtContext.ACU_HOUR | pdtContext.ACU_NOW))) 64 | self.assertExpectedResult( 65 | self.cal.parse('in five hours', start), 66 | (target, pdtContext(pdtContext.ACU_HOUR))) 67 | self.assertExpectedResult( 68 | self.cal.parse('in five hour', start), 69 | (target, pdtContext(pdtContext.ACU_HOUR))) 70 | self.assertExpectedResult( 71 | self.cal.parse('five hours', start), 72 | (target, pdtContext(pdtContext.ACU_HOUR))) 73 | self.assertExpectedResult( 74 | self.cal.parse('five hr', start), 75 | (target, pdtContext(pdtContext.ACU_HOUR))) 76 | 77 | # Test "an" 78 | t = s + datetime.timedelta(hours=1) 79 | target = t.timetuple() 80 | 81 | self.assertExpectedResult( 82 | self.cal.parse('an hour from now', start), 83 | (target, pdtContext(pdtContext.ACU_HOUR | pdtContext.ACU_NOW))) 84 | self.assertExpectedResult( 85 | self.cal.parse('in an hour', start), 86 | (target, pdtContext(pdtContext.ACU_HOUR))) 87 | self.assertExpectedResult( 88 | self.cal.parse('an hour', start), 89 | (target, pdtContext(pdtContext.ACU_HOUR))) 90 | self.assertExpectedResult( 91 | self.cal.parse('an hr', start), 92 | (target, pdtContext(pdtContext.ACU_HOUR))) 93 | self.assertExpectedResult( 94 | self.cal.parse('an h', start), 95 | (target, pdtContext(pdtContext.ACU_HOUR))) 96 | 97 | # No match, should require a word boundary 98 | self.assertExpectedResult( 99 | self.cal.parse('anhour', start), 100 | (start, pdtContext())) 101 | self.assertExpectedResult( 102 | self.cal.parse('an hamburger', start), 103 | (start, pdtContext())) 104 | 105 | def testHoursBeforeNow(self): 106 | s = datetime.datetime.now() 107 | t = s + datetime.timedelta(hours=-5) 108 | 109 | start = s.timetuple() 110 | target = t.timetuple() 111 | 112 | self.assertExpectedResult( 113 | self.cal.parse('5 hours before now', start), 114 | (target, pdtContext(pdtContext.ACU_HOUR | pdtContext.ACU_NOW))) 115 | self.assertExpectedResult( 116 | self.cal.parse('5 hr before now', start), 117 | (target, pdtContext(pdtContext.ACU_HOUR | pdtContext.ACU_NOW))) 118 | self.assertExpectedResult( 119 | self.cal.parse('5h before now', start), 120 | (target, pdtContext(pdtContext.ACU_HOUR | pdtContext.ACU_NOW))) 121 | 122 | self.assertExpectedResult( 123 | self.cal.parse('five hours before now', start), 124 | (target, pdtContext(pdtContext.ACU_HOUR | pdtContext.ACU_NOW))) 125 | self.assertExpectedResult( 126 | self.cal.parse('five hr before now', start), 127 | (target, pdtContext(pdtContext.ACU_HOUR | pdtContext.ACU_NOW))) 128 | 129 | # Test "an" 130 | t = s + datetime.timedelta(hours=-1) 131 | target = t.timetuple() 132 | 133 | self.assertExpectedResult( 134 | self.cal.parse('an hour before now', start), 135 | (target, pdtContext(pdtContext.ACU_HOUR | pdtContext.ACU_NOW))) 136 | self.assertExpectedResult( 137 | self.cal.parse('an hr before now', start), 138 | (target, pdtContext(pdtContext.ACU_HOUR | pdtContext.ACU_NOW))) 139 | self.assertExpectedResult( 140 | self.cal.parse('an h before now', start), 141 | (target, pdtContext(pdtContext.ACU_HOUR | pdtContext.ACU_NOW))) 142 | 143 | 144 | if __name__ == "__main__": 145 | unittest.main() 146 | -------------------------------------------------------------------------------- /tests/TestSimpleOffsetsNoon.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Test parsing of 'simple' offsets 4 | """ 5 | import sys 6 | import time 7 | import datetime 8 | import unittest 9 | import parsedatetime as pdt 10 | from parsedatetime.context import pdtContext 11 | from . import utils 12 | 13 | 14 | class test(unittest.TestCase): 15 | 16 | @utils.assertEqualWithComparator 17 | def assertExpectedResult(self, result, check, **kwargs): 18 | return utils.compareResultByTimeTuplesAndFlags(result, check, **kwargs) 19 | 20 | def setUp(self): 21 | self.cal = pdt.Calendar() 22 | (self.yr, self.mth, self.dy, self.hr, 23 | self.mn, self.sec, self.wd, self.yd, self.isdst) = time.localtime() 24 | 25 | def testOffsetAfterNoon(self): 26 | s = datetime.datetime(self.yr, self.mth, self.dy, 10, 0, 0) 27 | t = datetime.datetime( 28 | self.yr, self.mth, self.dy, 12, 0, 0) + datetime.timedelta(hours=5) 29 | 30 | start = s.timetuple() 31 | target = t.timetuple() 32 | 33 | self.assertExpectedResult( 34 | self.cal.parse('5 hours after 12pm', start), 35 | (target, pdtContext(pdtContext.ACU_HOUR))) 36 | self.assertExpectedResult( 37 | self.cal.parse('five hours after 12pm', start), 38 | (target, pdtContext(pdtContext.ACU_HOUR))) 39 | self.assertExpectedResult( 40 | self.cal.parse('5 hours after 12 pm', start), 41 | (target, pdtContext(pdtContext.ACU_HOUR))) 42 | self.assertExpectedResult( 43 | self.cal.parse('5 hours after 12:00pm', start), 44 | (target, pdtContext(pdtContext.ACU_HOUR | pdtContext.ACU_MIN))) 45 | self.assertExpectedResult( 46 | self.cal.parse('5 hours after 12:00 pm', start), 47 | (target, pdtContext(pdtContext.ACU_HOUR | pdtContext.ACU_MIN))) 48 | self.assertExpectedResult( 49 | self.cal.parse('5 hours after noon', start), 50 | (target, pdtContext(pdtContext.ACU_HALFDAY | pdtContext.ACU_HOUR))) 51 | self.assertExpectedResult( 52 | self.cal.parse('5 hours from noon', start), 53 | (target, pdtContext(pdtContext.ACU_HALFDAY | pdtContext.ACU_HOUR))) 54 | 55 | def testOffsetBeforeNoon(self): 56 | s = datetime.datetime.now() 57 | t = (datetime.datetime(self.yr, self.mth, self.dy, 12, 0, 0) + 58 | datetime.timedelta(hours=-5)) 59 | 60 | start = s.timetuple() 61 | target = t.timetuple() 62 | 63 | self.assertExpectedResult( 64 | self.cal.parse('5 hours before noon', start), 65 | (target, pdtContext(pdtContext.ACU_HALFDAY | pdtContext.ACU_HOUR))) 66 | self.assertExpectedResult( 67 | self.cal.parse('5 hours before 12pm', start), 68 | (target, pdtContext(pdtContext.ACU_HOUR))) 69 | self.assertExpectedResult( 70 | self.cal.parse('five hours before 12pm', start), 71 | (target, pdtContext(pdtContext.ACU_HOUR))) 72 | self.assertExpectedResult( 73 | self.cal.parse('5 hours before 12 pm', start), 74 | (target, pdtContext(pdtContext.ACU_HOUR))) 75 | self.assertExpectedResult( 76 | self.cal.parse('5 hours before 12:00pm', start), 77 | (target, pdtContext(pdtContext.ACU_HOUR | pdtContext.ACU_MIN))) 78 | self.assertExpectedResult( 79 | self.cal.parse('5 hours before 12:00 pm', start), 80 | (target, pdtContext(pdtContext.ACU_HOUR | pdtContext.ACU_MIN))) 81 | 82 | def testOffsetBeforeModifiedNoon(self): 83 | # A contrived test of two modifiers applied to noon - offset by 84 | # -5 from the following day (-5 + 24) 85 | s = datetime.datetime.now() 86 | t = (datetime.datetime(self.yr, self.mth, self.dy, 12, 0, 0) + 87 | datetime.timedelta(hours=-5 + 24)) 88 | 89 | start = s.timetuple() 90 | target = t.timetuple() 91 | 92 | self.assertExpectedResult( 93 | self.cal.parse('5 hours before next noon', start), 94 | (target, pdtContext(pdtContext.ACU_HALFDAY | pdtContext.ACU_HOUR))) 95 | 96 | 97 | if __name__ == "__main__": 98 | unittest.main() 99 | -------------------------------------------------------------------------------- /tests/TestStartTimeFromSourceTime.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Test parsing of strings that are phrases with the 4 | ptc.StartTimeFromSourceTime flag set to True 5 | """ 6 | import sys 7 | import time 8 | import datetime 9 | import unittest 10 | import parsedatetime as pdt 11 | from parsedatetime.context import pdtContext 12 | from . import utils 13 | 14 | 15 | class test(unittest.TestCase): 16 | 17 | @utils.assertEqualWithComparator 18 | def assertExpectedResult(self, result, check, **kwargs): 19 | return utils.compareResultByTimeTuplesAndFlags(result, check, **kwargs) 20 | 21 | def setUp(self): 22 | self.cal = pdt.Calendar() 23 | self.cal.ptc.StartTimeFromSourceTime = True 24 | (self.yr, self.mth, self.dy, self.hr, 25 | self.mn, self.sec, self.wd, self.yd, self.isdst) = time.localtime() 26 | 27 | def testEndOfPhrases(self): 28 | s = datetime.datetime.now() 29 | 30 | # find out what month we are currently on 31 | # set the day to 1 and then go back a day 32 | # to get the end of the current month 33 | (yr, mth, dy, hr, mn, sec, _, _, _) = s.timetuple() 34 | 35 | s = datetime.datetime(yr, mth, dy, 13, 14, 15) 36 | 37 | mth += 1 38 | if mth > 12: 39 | mth = 1 40 | yr += 1 41 | t = datetime.datetime( 42 | yr, mth, 1, 13, 14, 15) + datetime.timedelta(days=-1) 43 | 44 | start = s.timetuple() 45 | target = t.timetuple() 46 | 47 | self.assertExpectedResult( 48 | self.cal.parse('eom', start), 49 | (target, pdtContext(pdtContext.ACU_DAY))) 50 | self.assertExpectedResult( 51 | self.cal.parse('meeting eom', start), 52 | (target, pdtContext(pdtContext.ACU_DAY))) 53 | 54 | s = datetime.datetime.now() 55 | 56 | (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = s.timetuple() 57 | 58 | s = datetime.datetime(yr, mth, 1, 13, 14, 15) 59 | t = datetime.datetime(yr, 12, 31, 13, 14, 15) 60 | 61 | start = s.timetuple() 62 | target = t.timetuple() 63 | 64 | self.assertExpectedResult( 65 | self.cal.parse('eoy', start), 66 | (target, pdtContext(pdtContext.ACU_MONTH))) 67 | self.assertExpectedResult( 68 | self.cal.parse('meeting eoy', start), 69 | (target, pdtContext(pdtContext.ACU_MONTH))) 70 | -------------------------------------------------------------------------------- /tests/TestUnits.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Test parsing of units 4 | """ 5 | import sys 6 | import time 7 | import datetime 8 | import unittest 9 | import parsedatetime as pdt 10 | from parsedatetime.context import pdtContext 11 | from . import utils 12 | 13 | 14 | class test(unittest.TestCase): 15 | 16 | @utils.assertEqualWithComparator 17 | def assertExpectedResult(self, result, check, **kwargs): 18 | return utils.compareResultByTimeTuplesAndFlags(result, check, **kwargs) 19 | 20 | def setUp(self): 21 | self.cal = pdt.Calendar() 22 | (self.yr, self.mth, self.dy, self.hr, 23 | self.mn, self.sec, self.wd, self.yd, self.isdst) = time.localtime() 24 | 25 | def testMinutes(self): 26 | s = datetime.datetime.now() 27 | t = s + datetime.timedelta(minutes=1) 28 | h = s - datetime.timedelta(minutes=1) 29 | 30 | start = s.timetuple() 31 | target = t.timetuple() 32 | history = h.timetuple() 33 | 34 | self.assertExpectedResult( 35 | self.cal.parse('1 minutes', start), 36 | (target, pdtContext(pdtContext.ACU_MIN))) 37 | self.assertExpectedResult( 38 | self.cal.parse('1 minute', start), 39 | (target, pdtContext(pdtContext.ACU_MIN))) 40 | self.assertExpectedResult( 41 | self.cal.parse('1 min', start), 42 | (target, pdtContext(pdtContext.ACU_MIN))) 43 | self.assertExpectedResult( 44 | self.cal.parse('1min', start), 45 | (target, pdtContext(pdtContext.ACU_MIN))) 46 | self.assertExpectedResult( 47 | self.cal.parse('1 m', start), 48 | (target, pdtContext(pdtContext.ACU_MIN))) 49 | self.assertExpectedResult( 50 | self.cal.parse('1m', start), 51 | (target, pdtContext(pdtContext.ACU_MIN))) 52 | 53 | self.assertExpectedResult( 54 | self.cal.parse('1 minutes ago', start), 55 | (history, pdtContext(pdtContext.ACU_MIN))) 56 | self.assertExpectedResult( 57 | self.cal.parse('1 minute ago', start), 58 | (history, pdtContext(pdtContext.ACU_MIN))) 59 | 60 | def testHours(self): 61 | s = datetime.datetime.now() 62 | t = s + datetime.timedelta(hours=1) 63 | h = s - datetime.timedelta(hours=1) 64 | 65 | start = s.timetuple() 66 | target = t.timetuple() 67 | history = h.timetuple() 68 | 69 | self.assertExpectedResult( 70 | self.cal.parse('1 hour', start), 71 | (target, pdtContext(pdtContext.ACU_HOUR))) 72 | self.assertExpectedResult( 73 | self.cal.parse('1 hours', start), 74 | (target, pdtContext(pdtContext.ACU_HOUR))) 75 | self.assertExpectedResult( 76 | self.cal.parse('1 hr', start), 77 | (target, pdtContext(pdtContext.ACU_HOUR))) 78 | 79 | self.assertExpectedResult( 80 | self.cal.parse('1 hour ago', start), 81 | (history, pdtContext(pdtContext.ACU_HOUR))) 82 | self.assertExpectedResult( 83 | self.cal.parse('1 hours ago', start), 84 | (history, pdtContext(pdtContext.ACU_HOUR))) 85 | 86 | def testDays(self): 87 | s = datetime.datetime.now() 88 | t = s + datetime.timedelta(days=1) 89 | 90 | start = s.timetuple() 91 | target = t.timetuple() 92 | 93 | self.assertExpectedResult( 94 | self.cal.parse('1 day', start), 95 | (target, pdtContext(pdtContext.ACU_DAY))) 96 | self.assertExpectedResult( 97 | self.cal.parse('1 days', start), 98 | (target, pdtContext(pdtContext.ACU_DAY))) 99 | self.assertExpectedResult( 100 | self.cal.parse('1days', start), 101 | (target, pdtContext(pdtContext.ACU_DAY))) 102 | self.assertExpectedResult( 103 | self.cal.parse('1 dy', start), 104 | (target, pdtContext(pdtContext.ACU_DAY))) 105 | self.assertExpectedResult( 106 | self.cal.parse('1 d', start), 107 | (target, pdtContext(pdtContext.ACU_DAY))) 108 | 109 | def testNegativeDays(self): 110 | s = datetime.datetime.now() 111 | t = s + datetime.timedelta(days=-1) 112 | 113 | start = s.timetuple() 114 | target = t.timetuple() 115 | 116 | self.assertExpectedResult( 117 | self.cal.parse('-1 day', start), 118 | (target, pdtContext(pdtContext.ACU_DAY))) 119 | self.assertExpectedResult( 120 | self.cal.parse('-1 days', start), 121 | (target, pdtContext(pdtContext.ACU_DAY))) 122 | self.assertExpectedResult( 123 | self.cal.parse('-1days', start), 124 | (target, pdtContext(pdtContext.ACU_DAY))) 125 | self.assertExpectedResult( 126 | self.cal.parse('-1 dy', start), 127 | (target, pdtContext(pdtContext.ACU_DAY))) 128 | self.assertExpectedResult( 129 | self.cal.parse('-1 d', start), 130 | (target, pdtContext(pdtContext.ACU_DAY))) 131 | 132 | self.assertExpectedResult( 133 | self.cal.parse('- 1 day', start), 134 | (target, pdtContext(pdtContext.ACU_DAY))) 135 | self.assertExpectedResult( 136 | self.cal.parse('- 1 days', start), 137 | (target, pdtContext(pdtContext.ACU_DAY))) 138 | self.assertExpectedResult( 139 | self.cal.parse('- 1days', start), 140 | (target, pdtContext(pdtContext.ACU_DAY))) 141 | self.assertExpectedResult( 142 | self.cal.parse('- 1 dy', start), 143 | (target, pdtContext(pdtContext.ACU_DAY))) 144 | self.assertExpectedResult( 145 | self.cal.parse('- 1 d', start), 146 | (target, pdtContext(pdtContext.ACU_DAY))) 147 | 148 | self.assertExpectedResult( 149 | self.cal.parse('1 day ago', start), 150 | (target, pdtContext(pdtContext.ACU_DAY))) 151 | self.assertExpectedResult( 152 | self.cal.parse('1 days ago', start), 153 | (target, pdtContext(pdtContext.ACU_DAY))) 154 | 155 | def testWeeks(self): 156 | s = datetime.datetime.now() 157 | t = s + datetime.timedelta(weeks=1) 158 | h = s - datetime.timedelta(weeks=1) 159 | 160 | start = s.timetuple() 161 | target = t.timetuple() 162 | history = h.timetuple() 163 | 164 | self.assertExpectedResult( 165 | self.cal.parse('1 week', start), 166 | (target, pdtContext(pdtContext.ACU_WEEK))) 167 | self.assertExpectedResult( 168 | self.cal.parse('1week', start), 169 | (target, pdtContext(pdtContext.ACU_WEEK))) 170 | self.assertExpectedResult( 171 | self.cal.parse('1 weeks', start), 172 | (target, pdtContext(pdtContext.ACU_WEEK))) 173 | self.assertExpectedResult( 174 | self.cal.parse('1 wk', start), 175 | (target, pdtContext(pdtContext.ACU_WEEK))) 176 | self.assertExpectedResult( 177 | self.cal.parse('1 w', start), 178 | (target, pdtContext(pdtContext.ACU_WEEK))) 179 | self.assertExpectedResult( 180 | self.cal.parse('1w', start), 181 | (target, pdtContext(pdtContext.ACU_WEEK))) 182 | 183 | self.assertExpectedResult( 184 | self.cal.parse('1 week ago', start), 185 | (history, pdtContext(pdtContext.ACU_WEEK))) 186 | self.assertExpectedResult( 187 | self.cal.parse('1 weeks ago', start), 188 | (history, pdtContext(pdtContext.ACU_WEEK))) 189 | 190 | def testMonths(self): 191 | s = datetime.datetime.now() 192 | t = self.cal.inc(s, month=1) 193 | h = self.cal.inc(s, month=-1) 194 | 195 | start = s.timetuple() 196 | target = t.timetuple() 197 | history = h.timetuple() 198 | 199 | self.assertExpectedResult( 200 | self.cal.parse('1 month', start), 201 | (target, pdtContext(pdtContext.ACU_MONTH))) 202 | self.assertExpectedResult( 203 | self.cal.parse('1 months', start), 204 | (target, pdtContext(pdtContext.ACU_MONTH))) 205 | self.assertExpectedResult( 206 | self.cal.parse('1month', start), 207 | (target, pdtContext(pdtContext.ACU_MONTH))) 208 | 209 | self.assertExpectedResult( 210 | self.cal.parse('1 month ago', start), 211 | (history, pdtContext(pdtContext.ACU_MONTH))) 212 | self.assertExpectedResult( 213 | self.cal.parse('1 months ago', start), 214 | (history, pdtContext(pdtContext.ACU_MONTH))) 215 | 216 | def testYears(self): 217 | s = datetime.datetime.now() 218 | t = self.cal.inc(s, year=1) 219 | 220 | start = s.timetuple() 221 | target = t.timetuple() 222 | 223 | self.assertExpectedResult( 224 | self.cal.parse('1 year', start), 225 | (target, pdtContext(pdtContext.ACU_YEAR))) 226 | self.assertExpectedResult( 227 | self.cal.parse('1 years', start), 228 | (target, pdtContext(pdtContext.ACU_YEAR))) 229 | self.assertExpectedResult( 230 | self.cal.parse('1 yr', start), 231 | (target, pdtContext(pdtContext.ACU_YEAR))) 232 | self.assertExpectedResult( 233 | self.cal.parse('1 y', start), 234 | (target, pdtContext(pdtContext.ACU_YEAR))) 235 | self.assertExpectedResult( 236 | self.cal.parse('1y', start), 237 | (target, pdtContext(pdtContext.ACU_YEAR))) 238 | 239 | 240 | if __name__ == "__main__": 241 | unittest.main() 242 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Unit tests for parsedatetime 3 | 4 | The tests can be run as a C{suite} by running:: 5 | 6 | nosetests 7 | 8 | Requires Python 3.0 or later 9 | """ 10 | import logging 11 | 12 | 13 | __author__ = 'Mike Taylor (bear@code-bear.com)' 14 | __copyright__ = 'Copyright (c) 2004 Mike Taylor' 15 | __license__ = 'Apache v2.0' 16 | __version__ = '1.0.0' 17 | __contributors__ = ['Darshana Chhajed', 18 | 'Michael Lim (lim.ck.michael@gmail.com)', 19 | 'Bernd Zeimetz (bzed@debian.org)', 20 | ] 21 | -------------------------------------------------------------------------------- /tests/utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Internal helper functions for unit tests of parsedatetime 4 | """ 5 | 6 | 7 | def assertEqualWithComparator(comparator): 8 | """ 9 | Fail a little less cryptically that unittest.assertTrue when comparing a 10 | result against a target value. Shows the result and the target in the 11 | failure message. 12 | """ 13 | 14 | def decoratedComparator(self, result, check, errMsg=None, **kwargs): 15 | errMsg = errMsg or 'Result does not match target value' 16 | equal = comparator(self, result, check, **kwargs) 17 | failureMessage = ('%s\n\n\t' 18 | 'Result:\n\t%s\n\n\tExpected:\n\t%s') 19 | 20 | if not equal: 21 | self.fail(failureMessage % (errMsg, result, check)) 22 | 23 | return decoratedComparator 24 | 25 | 26 | def compareResultByTimeTuplesAndFlags(result, check, dateOnly=False): 27 | """ 28 | Ensures that flags are an exact match and time tuples a close match when 29 | given data in the format ((timetuple), flag) 30 | """ 31 | return (_compareTimeTuples(result[0], check[0], dateOnly) and 32 | _compareFlags(result[1], check[1])) 33 | 34 | 35 | def compareResultByFlags(result, check, dateOnly=False): 36 | """ 37 | Ensures that flags are an exact match when given data in the format 38 | ((timetuple), flag) 39 | """ 40 | return _compareFlags(result[1], check[1]) 41 | 42 | 43 | def compareResultByTimeTupleRangesAndFlags(result, check, dateOnly=False): 44 | """ 45 | Ensures that flags are an exact match and time tuples a close match when 46 | given data in the format ((timetuple), (timetuple), flag) 47 | """ 48 | return (_compareTimeTuples(result[0], check[0], dateOnly) and 49 | _compareTimeTuples(result[1], check[1], dateOnly) and 50 | _compareFlags(result[2], check[2])) 51 | 52 | 53 | def _compareTimeTuples(target, value, dateOnly=False): 54 | """ 55 | Ignores minutes and seconds as running the test could cross a minute 56 | boundary. Technically the year, month, day, hour, minute, and second could 57 | all change if the test is run on New Year's Eve, but we won't worry about 58 | less than per-hour granularity. 59 | """ 60 | t_yr, t_mth, t_dy, t_hr, t_min, _, _, _, _ = target 61 | v_yr, v_mth, v_dy, v_hr, v_min, _, _, _, _ = value 62 | 63 | if dateOnly: 64 | return ((t_yr == v_yr) and (t_mth == v_mth) and (t_dy == v_dy)) 65 | else: 66 | return ((t_yr == v_yr) and (t_mth == v_mth) and (t_dy == v_dy) and 67 | (t_hr == v_hr) and (t_min == v_min)) 68 | 69 | 70 | def _compareFlags(result, check): 71 | return (result == check) 72 | --------------------------------------------------------------------------------