From 3431f12ee029addf6fe8aab37d2cb5ce7df3d586 Mon Sep 17 00:00:00 2001 From: ipaterson Date: Sat, 10 Sep 2016 08:34:24 -0400 Subject: [PATCH 01/12] Added simple logging in nlp to identify which expressions produce a leftmost match --- parsedatetime/__init__.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/parsedatetime/__init__.py b/parsedatetime/__init__.py index 2640dfa..e4d8220 100644 --- a/parsedatetime/__init__.py +++ b/parsedatetime/__init__.py @@ -1960,6 +1960,8 @@ def nlp(self, inputString, sourceTime=None, version=None): were no matches """ + debug and log.debug('nlp()') + orig_inputstring = inputString # replace periods at the end of sentences w/ spaces @@ -1992,6 +1994,9 @@ def nlp(self, inputString, sourceTime=None, version=None): leftmost_match[3] = 0 leftmost_match[4] = 'modifier' + debug and log.debug('CRE_MODIFIER matched [%s]', + leftmost_match[2]) + # Quantity + Units m = self.ptc.CRE_UNITS.search(inputString[startpos:]) if m is not None: @@ -2013,6 +2018,9 @@ def nlp(self, inputString, sourceTime=None, version=None): leftmost_match[0] = leftmost_match[0] - 1 leftmost_match[2] = '-' + leftmost_match[2] + debug and log.debug('CRE_UNITS matched [%s]', + leftmost_match[2]) + # Quantity + Units m = self.ptc.CRE_QUNITS.search(inputString[startpos:]) if m is not None: @@ -2033,6 +2041,9 @@ def nlp(self, inputString, sourceTime=None, version=None): leftmost_match[0] = leftmost_match[0] - 1 leftmost_match[2] = '-' + leftmost_match[2] + debug and log.debug('CRE_QUNITS matched [%s]', + leftmost_match[2]) + m = self.ptc.CRE_DATE3.search(inputString[startpos:]) # NO LONGER NEEDED, THE REGEXP HANDLED MTHNAME NOW # for match in self.ptc.CRE_DATE3.finditer(inputString[startpos:]): @@ -2054,6 +2065,8 @@ def nlp(self, inputString, sourceTime=None, version=None): leftmost_match[2] = m.group('date') leftmost_match[3] = 1 leftmost_match[4] = 'dateStr' + debug and log.debug('CRE_DATE3 matched [%s]', + leftmost_match[2]) # Standard date format m = self.ptc.CRE_DATE.search(inputString[startpos:]) @@ -2065,6 +2078,8 @@ def nlp(self, inputString, sourceTime=None, version=None): leftmost_match[2] = m.group('date') leftmost_match[3] = 1 leftmost_match[4] = 'dateStd' + debug and log.debug('CRE_DATE matched [%s]', + leftmost_match[2]) # Natural language day strings m = self.ptc.CRE_DAY.search(inputString[startpos:]) @@ -2076,6 +2091,8 @@ def nlp(self, inputString, sourceTime=None, version=None): leftmost_match[2] = m.group() leftmost_match[3] = 1 leftmost_match[4] = 'dayStr' + debug and log.debug('CRE_DAY matched [%s]', + leftmost_match[2]) # Weekday m = self.ptc.CRE_WEEKDAY.search(inputString[startpos:]) @@ -2088,6 +2105,8 @@ def nlp(self, inputString, sourceTime=None, version=None): leftmost_match[2] = m.group() leftmost_match[3] = 1 leftmost_match[4] = 'weekdy' + debug and log.debug('CRE_WEEKDAY matched [%s]', + leftmost_match[2]) # Natural language time strings m = self.ptc.CRE_TIME.search(inputString[startpos:]) @@ -2099,6 +2118,8 @@ def nlp(self, inputString, sourceTime=None, version=None): leftmost_match[2] = m.group() leftmost_match[3] = 2 leftmost_match[4] = 'timeStr' + debug and log.debug('CRE_TIME matched [%s]', + leftmost_match[2]) # HH:MM(:SS) am/pm time strings m = self.ptc.CRE_TIMEHMS2.search(inputString[startpos:]) @@ -2111,6 +2132,8 @@ def nlp(self, inputString, sourceTime=None, version=None): leftmost_match[1]] leftmost_match[3] = 2 leftmost_match[4] = 'meridian' + debug and log.debug('CRE_TIMEHMS2 matched [%s]', + leftmost_match[2]) # HH:MM(:SS) time strings m = self.ptc.CRE_TIMEHMS.search(inputString[startpos:]) @@ -2126,6 +2149,8 @@ def nlp(self, inputString, sourceTime=None, version=None): leftmost_match[1]] leftmost_match[3] = 2 leftmost_match[4] = 'timeStd' + debug and log.debug('CRE_TIMEHMS matched [%s]', + leftmost_match[2]) # Units only; must be preceded by a modifier if len(matches) > 0 and matches[-1][3] == 0: @@ -2145,6 +2170,8 @@ def nlp(self, inputString, sourceTime=None, version=None): leftmost_match[2] = m.group() leftmost_match[3] = 3 leftmost_match[4] = 'unitsOnly' + debug and log.debug('CRE_UNITS_ONLY matched [%s]', + leftmost_match[2]) # set the start position to the end pos of the leftmost match startpos = leftmost_match[1] @@ -2162,6 +2189,8 @@ def nlp(self, inputString, sourceTime=None, version=None): leftmost_match[0] = m.start('nlp_prefix') leftmost_match[2] = inputString[leftmost_match[0]: leftmost_match[1]] + debug and log.debug('CRE_NLP_PREFIX matched [%s]', + leftmost_match[2]) matches.append(leftmost_match) # find matches in proximity with one another and From cc3663d965cbf9e888f9580b8557c5e6578bc1c3 Mon Sep 17 00:00:00 2001 From: ipaterson Date: Fri, 9 Sep 2016 08:36:12 -0400 Subject: [PATCH 02/12] Added failing tests for nlp taken directly from simple date time tests for parse --- tests/TestNlp.py | 141 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) diff --git a/tests/TestNlp.py b/tests/TestNlp.py index 1d3ea13..b9ecbdf 100644 --- a/tests/TestNlp.py +++ b/tests/TestNlp.py @@ -128,3 +128,144 @@ def testFalsePositives(self): self.assertExpectedResult(self.cal.nlp("$300", start), None) self.assertExpectedResult(self.cal.nlp("300ml", start), None) self.assertExpectedResult(self.cal.nlp("nice ass", start), None) + + def testTimes(self): + start = datetime.datetime( + self.yr, self.mth, self.dy, self.hr, self.mn, self.sec).timetuple() + targets = { + datetime.datetime(self.yr, self.mth, self.dy, 23, 0, 0): ( + '11:00:00 PM', + '11:00 PM', + '11 PM', + '11PM', + '2300', + '23:00', + '11p', + '11pm', + '11:00:00 P.M.', + '11:00 P.M.', + '11 P.M.', + '11P.M.', + '11p.m.', + '11 p.m.', + ), + datetime.datetime(self.yr, self.mth, self.dy, 11, 0, 0): ( + '11:00:00 AM', + '11:00 AM', + '11 AM', + '11AM', + '1100', + '11:00', + '11a', + '11am', + '11:00:00 A.M.', + '11:00 A.M.', + '11 A.M.', + '11A.M.', + '11a.m.', + '11 a.m.', + ), + datetime.datetime(self.yr, self.mth, self.dy, 7, 30, 0): ( + '730', + '0730', + '0730am', + ), + datetime.datetime(self.yr, self.mth, self.dy, 17, 30, 0): ( + '1730', + '173000', + ) + } + + for dt, phrases in targets.items(): + # Time (2) phrase starting at index 0 + target = (dt, 2, 0) + for phrase in phrases: + self.assertExpectedResult( + self.cal.nlp(phrase, start), + (target + (len(phrase), phrase),) + ) + + # Wrap in quotes + target = (dt, 2, 1) + for phrase in phrases: + self.assertExpectedResult( + self.cal.nlp('"%s"' % phrase, start), + (target + (len(phrase) + 1, phrase),) + ) + + def testFalsePositiveTimes(self): + start = datetime.datetime( + self.yr, self.mth, self.dy, self.hr, self.mn, self.sec).timetuple() + phrases = ( + '$300', + '300ml', + '3:2', + ) + + for phrase in phrases: + self.assertExpectedResult( + self.cal.nlp(phrase, start), None) + + def testDates(self): + # Set month to January to avoid issues with August being interpreted + # as next year when tests are run after Aug 25 + start = datetime.datetime( + self.yr, 1, self.dy, self.hr, self.mn, self.sec).timetuple() + targets = { + datetime.datetime(2006, 8, 25, self.hr, self.mn, self.sec): ( + '08/25/2006', + '08.25.2006', + '2006/08/25', + '2006/8/25', + '2006-08-25', + '8/25/06', + 'August 25, 2006', + 'Aug 25, 2006', + 'Aug. 25, 2006', + 'August 25 2006', + 'Aug 25 2006', + 'Aug. 25 2006', + '25 August 2006', + '25 Aug 2006', + ), + datetime.datetime(self.yr, 8, 25, self.hr, self.mn, self.sec): ( + '8/25', + '8.25', + '08/25', + 'August 25', + 'Aug 25', + 'Aug. 25', + ), + datetime.datetime(2006, 8, 1, self.hr, self.mn, self.sec): ( + 'Aug. 2006', + ) + } + + for dt, phrases in targets.items(): + # Date (1) phrase starting at index 0 + target = (dt, 1, 0) + for phrase in phrases: + self.assertExpectedResult( + self.cal.nlp(phrase, start), + (target + (len(phrase), phrase),) + ) + + # Wrap in quotes + target = (dt, 1, 1) + for phrase in phrases: + self.assertExpectedResult( + self.cal.nlp('"%s"' % phrase, start), + (target + (len(phrase) + 1, phrase),) + ) + + def testFalsePositiveDates(self): + start = datetime.datetime( + self.yr, self.mth, self.dy, self.hr, self.mn, self.sec).timetuple() + phrases = ( + '$1.23', + '$12.34' + ) + + for phrase in phrases: + self.assertExpectedResult( + self.cal.nlp(phrase, start), None) From c0a88d0f4f026d9ea1b97578edd986f4220d7dfc Mon Sep 17 00:00:00 2001 From: ipaterson Date: Sat, 10 Sep 2016 08:43:14 -0400 Subject: [PATCH 03/12] Removed transformations to remove quotes and periods in nlp and parse --- parsedatetime/__init__.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/parsedatetime/__init__.py b/parsedatetime/__init__.py index 2640dfa..16097ee 100644 --- a/parsedatetime/__init__.py +++ b/parsedatetime/__init__.py @@ -1812,10 +1812,6 @@ def parse(self, datetimeString, sourceTime=None, version=None): """ debug and log.debug('parse()') - datetimeString = re.sub(r'(\w)\.(\s)', r'\1\2', datetimeString) - datetimeString = re.sub(r'(\w)[\'"](\s|$)', r'\1 \2', datetimeString) - datetimeString = re.sub(r'(\s|^)[\'"](\w)', r'\1 \2', datetimeString) - if sourceTime: if isinstance(sourceTime, datetime.datetime): debug and log.debug('coercing datetime to timetuple') @@ -1962,13 +1958,7 @@ def nlp(self, inputString, sourceTime=None, version=None): orig_inputstring = inputString - # replace periods at the end of sentences w/ spaces - # opposed to removing them altogether in order to - # retain relative positions (identified by alpha, period, space). - # this is required for some of the regex patterns to match - inputString = re.sub(r'(\w)(\.)(\s)', r'\1 \3', inputString).lower() - inputString = re.sub(r'(\w)(\'|")(\s|$)', r'\1 \3', inputString) - inputString = re.sub(r'(\s|^)(\'|")(\w)', r'\1 \3', inputString) + inputString = inputString.lower() startpos = 0 # the start position in the inputString during the loop From 28ea99b3af47e9da41d0e68bf4bd4d9c3bd03c1d Mon Sep 17 00:00:00 2001 From: ipaterson Date: Sat, 10 Sep 2016 08:44:24 -0400 Subject: [PATCH 04/12] Allow nlp to find P.M. and A.M. formatted meridians Allows meridian to set its own word boundary flag since /a\.m\.\b/ does not match. The \b is now only included for forms without an abbreviation period. --- parsedatetime/__init__.py | 2 +- parsedatetime/pdt_locales/base.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/parsedatetime/__init__.py b/parsedatetime/__init__.py index 16097ee..40bdd4c 100644 --- a/parsedatetime/__init__.py +++ b/parsedatetime/__init__.py @@ -2584,7 +2584,7 @@ def _buildOffsets(offsetDict, localeData, indexStart): )''' if 'meridian' in self.locale.re_values: - self.RE_TIMEHMS2 += (r'\s*(?P{meridian})\b' + self.RE_TIMEHMS2 += (r'\s*(?P{meridian})' .format(**self.locale.re_values)) else: self.RE_TIMEHMS2 += r'\b' diff --git a/parsedatetime/pdt_locales/base.py b/parsedatetime/pdt_locales/base.py index 0191629..5c7587d 100644 --- a/parsedatetime/pdt_locales/base.py +++ b/parsedatetime/pdt_locales/base.py @@ -102,7 +102,7 @@ 'timeseparator': ':', 'rangeseparator': '-', 'daysuffix': 'rd|st|nd|th', - 'meridian': r'am|pm|a\.m\.|p\.m\.|a|p', + 'meridian': r'a\.m\.|p\.m\.|(?:am|pm|a|p)\b', 'qunits': 'h|m|s|d|w|y', 'now': ['now', 'right now'], } From ab951d5fada4b667a73c985801aef580c9dd0ea7 Mon Sep 17 00:00:00 2001 From: ipaterson Date: Sat, 10 Sep 2016 08:46:13 -0400 Subject: [PATCH 05/12] Allow standard characters between nlp prefix and date expression. Fixes nlp test case At '8PM on August 5th' --- parsedatetime/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/parsedatetime/__init__.py b/parsedatetime/__init__.py index 40bdd4c..87288ad 100644 --- a/parsedatetime/__init__.py +++ b/parsedatetime/__init__.py @@ -2574,13 +2574,13 @@ def _buildOffsets(offsetDict, localeData, indexStart): # 1, 2, and 3 here refer to the type of match date, time, or units self.RE_NLP_PREFIX = r'''\b(?P (on) - (\s)+1 + [\s(\["'-]+1 | (at|in) - (\s)+2 + [\s(\["'-]+2 | (in) - (\s)+3 + [\s(\["'-]+3 )''' if 'meridian' in self.locale.re_values: From 43521cb2570a4cc4d1e3558b6d895ad35b1da78e Mon Sep 17 00:00:00 2001 From: ipaterson Date: Sat, 10 Sep 2016 08:49:01 -0400 Subject: [PATCH 06/12] Allow months to be abbreviated with a period --- parsedatetime/__init__.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/parsedatetime/__init__.py b/parsedatetime/__init__.py index 87288ad..effdc75 100644 --- a/parsedatetime/__init__.py +++ b/parsedatetime/__init__.py @@ -2468,9 +2468,9 @@ def _buildOffsets(offsetDict, localeData, indexStart): (,)? (\s)* ) - (?P - \b({months}|{shortmonths})\b - )\s* + \b(?P + {months}|{shortmonths} + )\b\.?\s* (?P\d\d (\d\d)? )? @@ -2489,7 +2489,7 @@ def _buildOffsets(offsetDict, localeData, indexStart): (?:^|\s+) (?P {months}|{shortmonths} - )\b + )\b\.? | (?:^|\s+) (?P[1-9]|[012]\d|3[01]) @@ -2508,9 +2508,9 @@ def _buildOffsets(offsetDict, localeData, indexStart): self.RE_MONTH = r'''(\s+|^) (?P ( - (?P - \b({months}|{shortmonths})\b - ) + \b(?P + {months}|{shortmonths} + )\b\.? (\s* (?P(\d{{4}})) )? From 02f35aa04c7dac43f8e8afcfad423800fea45ab7 Mon Sep 17 00:00:00 2001 From: ipaterson Date: Sat, 10 Sep 2016 08:51:31 -0400 Subject: [PATCH 07/12] Allow dates to begin with a word boundary rather than a space. Fixes parsing of "August 25" with quotes. --- parsedatetime/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/parsedatetime/__init__.py b/parsedatetime/__init__.py index effdc75..ec16e48 100644 --- a/parsedatetime/__init__.py +++ b/parsedatetime/__init__.py @@ -2486,12 +2486,12 @@ def _buildOffsets(offsetDict, localeData, indexStart): # when the day is absent from the string self.RE_DATE3 = r'''(?P (?: - (?:^|\s+) + (?:^|\s+|\b) (?P {months}|{shortmonths} )\b\.? | - (?:^|\s+) + (?:^|\s+|\b) (?P[1-9]|[012]\d|3[01]) (?P{daysuffix}|)\b (?!\s*(?:{timecomponents})) From 81e0dac46f44205fece7f9c8429d06100e520e56 Mon Sep 17 00:00:00 2001 From: Nishant Deshpande Date: Wed, 9 Nov 2016 11:02:58 -0800 Subject: [PATCH 08/12] allow day start hour to be configurable - by default 9 as now --- parsedatetime/__init__.py | 11 +++---- tests/TestDayStartHour.py | 61 ++++++++++++++++++++++++++++++++++++++ tests/TestLocaleBase.py | 7 +++-- tests/TestNlp.py | 6 ++-- tests/TestPhrases.py | 12 +++++--- tests/TestSimpleOffsets.py | 14 ++++++--- 6 files changed, 94 insertions(+), 17 deletions(-) create mode 100644 tests/TestDayStartHour.py diff --git a/parsedatetime/__init__.py b/parsedatetime/__init__.py index ec16e48..b7353f9 100644 --- a/parsedatetime/__init__.py +++ b/parsedatetime/__init__.py @@ -22,7 +22,6 @@ Requires Python 2.6 or later """ - from __future__ import with_statement, absolute_import, unicode_literals import re @@ -243,7 +242,7 @@ def _parse_date_rfc822(dateString): VERSION_FLAG_STYLE = 1 VERSION_CONTEXT_STYLE = 2 - +DEFAULT_DAY_START_HOUR = 9 class Calendar(object): @@ -252,7 +251,8 @@ class Calendar(object): The text can either be 'normal' date values or it can be human readable. """ - def __init__(self, constants=None, version=VERSION_FLAG_STYLE): + def __init__(self, constants=None, version=VERSION_FLAG_STYLE, + day_start_hour=DEFAULT_DAY_START_HOUR): """ Default constructor for the L{Calendar} class. @@ -280,6 +280,7 @@ def __init__(self, constants=None, version=VERSION_FLAG_STYLE): 'with argument `version=parsedatetime.VERSION_CONTEXT_STYLE`.', pdt20DeprecationWarning) self._ctxStack = pdtContextStack() + self.day_start_hour = day_start_hour @contextlib.contextmanager def context(self): @@ -790,7 +791,7 @@ def _evalModifier(self, modifier, chunk1, chunk2, sourceTime): startMinute = mn startSecond = sec else: - startHour = 9 + startHour = self.day_start_hour startMinute = 0 startSecond = 0 @@ -1142,7 +1143,7 @@ def _evalDayStr(self, datetimeString, sourceTime): startMinute = mn startSecond = sec else: - startHour = 9 + startHour = self.day_start_hour startMinute = 0 startSecond = 0 diff --git a/tests/TestDayStartHour.py b/tests/TestDayStartHour.py new file mode 100644 index 0000000..9876582 --- /dev/null +++ b/tests/TestDayStartHour.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- +""" +Test parsing of strings that are phrases +""" +from __future__ import unicode_literals + +import sys +import time +import datetime +import parsedatetime as pdt +from . import utils + +if sys.version_info < (2, 7): + import unittest2 as unittest +else: + import unittest + + +class test(unittest.TestCase): + + @utils.assertEqualWithComparator + def assertExpectedResult(self, result, check, **kwargs): + return utils.compareResultByTimeTuplesAndFlags(result, check, **kwargs) + + def setUp(self): + # Test with a different day start hour. + (self.yr, self.mth, self.dy, self.hr, + self.mn, self.sec, self.wd, self.yd, self.isdst) = time.localtime() + + def testDifferentDayStartHours(self): + for day_start_hour in (0, 6, 9, 12): + cal = pdt.Calendar( + day_start_hour=day_start_hour) + + s = datetime.datetime.now() + t = datetime.datetime( + self.yr, self.mth, self.dy, + day_start_hour, 0, 0) + datetime.timedelta(days=1) + + start = s.timetuple() + target = t.timetuple() + + self.assertExpectedResult( + cal.parse('tomorrow', start), (target, 1)) + self.assertExpectedResult( + cal.parse('next day', start), (target, 1)) + + t = datetime.datetime( + self.yr, self.mth, self.dy, + day_start_hour, 0, 0) + datetime.timedelta(days=-1) + target = t.timetuple() + + self.assertExpectedResult( + cal.parse('yesterday', start), (target, 1)) + + t = datetime.datetime( + self.yr, self.mth, self.dy, + day_start_hour, 0, 0) + target = t.timetuple() + + self.assertExpectedResult(cal.parse('today', start), (target, 1)) diff --git a/tests/TestLocaleBase.py b/tests/TestLocaleBase.py index b3fc77e..f470894 100644 --- a/tests/TestLocaleBase.py +++ b/tests/TestLocaleBase.py @@ -142,10 +142,13 @@ def setUp(self): self.__old_pdtlocale_fr = pdt.pdtLocales.get('fr_FR') # save for later pdt.pdtLocales['fr_FR'] = pdtLocale_fr # override for the test self.ptc = pdt.Constants('fr_FR', usePyICU=False) - self.cal = pdt.Calendar(self.ptc) + self.day_start_hour = 9 + self.cal = pdt.Calendar( + self.ptc, day_start_hour=self.day_start_hour) def test_dayoffsets(self): - start = datetime.datetime(self.yr, self.mth, self.dy, 9) + start = datetime.datetime(self.yr, self.mth, self.dy, + self.day_start_hour) for date_string, expected_day_offset in [ ("Aujourd'hui", 0), ("aujourd'hui", 0), diff --git a/tests/TestNlp.py b/tests/TestNlp.py index b9ecbdf..9ba2abb 100644 --- a/tests/TestNlp.py +++ b/tests/TestNlp.py @@ -57,7 +57,9 @@ def assertExpectedResult(self, result, check, dateOnly=False): return True def setUp(self): - self.cal = pdt.Calendar() + self.day_start_hour = 9 + self.cal = pdt.Calendar( + day_start_hour=self.day_start_hour) (self.yr, self.mth, self.dy, self.hr, self.mn, self.sec, self.wd, self.yd, self.isdst) = time.localtime() @@ -75,7 +77,7 @@ def testLongPhrase(self): 3, 72, 90, 'next Friday at 9PM'), (datetime.datetime(2013, 8, 1, 21, 30, 0), 2, 120, 132, 'in 5 minutes'), - (datetime.datetime(2013, 8, 8, 9, 0), + (datetime.datetime(2013, 8, 8, self.day_start_hour, 0), 1, 173, 182, 'next week')) # positive testing diff --git a/tests/TestPhrases.py b/tests/TestPhrases.py index 8f4a68e..8dafa55 100644 --- a/tests/TestPhrases.py +++ b/tests/TestPhrases.py @@ -23,7 +23,9 @@ def assertExpectedResult(self, result, check, **kwargs): return utils.compareResultByTimeTuplesAndFlags(result, check, **kwargs) def setUp(self): - self.cal = pdt.Calendar() + self.day_start_hour = 9 + self.cal = pdt.Calendar( + day_start_hour=self.day_start_hour) (self.yr, self.mth, self.dy, self.hr, self.mn, self.sec, self.wd, self.yd, self.isdst) = time.localtime() @@ -114,8 +116,9 @@ def testEndOfPhrases(self): mth = 1 yr += 1 - t = datetime.datetime( - yr, mth, 1, 9, 0, 0) + datetime.timedelta(days=-1) + t = (datetime.datetime( + yr, mth, 1, self.day_start_hour, 0, 0) + + datetime.timedelta(days=-1)) start = s.timetuple() target = t.timetuple() @@ -129,7 +132,8 @@ def testEndOfPhrases(self): (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = s.timetuple() - t = datetime.datetime(yr, 12, 31, 9, 0, 0) + t = datetime.datetime( + yr, 12, 31, self.day_start_hour, 0, 0) start = s.timetuple() target = t.timetuple() diff --git a/tests/TestSimpleOffsets.py b/tests/TestSimpleOffsets.py index 3270c94..8ae7906 100644 --- a/tests/TestSimpleOffsets.py +++ b/tests/TestSimpleOffsets.py @@ -39,7 +39,9 @@ def assertExpectedResult(self, result, check, **kwargs): return utils.compareResultByTimeTuplesAndFlags(result, check, **kwargs) def setUp(self): - self.cal = pdt.Calendar() + self.day_start_hour = 9 + self.cal = pdt.Calendar( + day_start_hour=self.day_start_hour) (self.yr, self.mth, self.dy, self.hr, self.mn, self.sec, self.wd, self.yd, self.isdst) = time.localtime() @@ -253,7 +255,8 @@ def testNextMonth(self): def testSpecials(self): s = datetime.datetime.now() t = datetime.datetime( - self.yr, self.mth, self.dy, 9, 0, 0) + datetime.timedelta(days=1) + self.yr, self.mth, self.dy, + self.day_start_hour, 0, 0) + datetime.timedelta(days=1) start = s.timetuple() target = t.timetuple() @@ -264,13 +267,16 @@ def testSpecials(self): self.cal.parse('next day', start), (target, 1)) t = datetime.datetime( - self.yr, self.mth, self.dy, 9, 0, 0) + datetime.timedelta(days=-1) + self.yr, self.mth, self.dy, + self.day_start_hour, 0, 0) + datetime.timedelta(days=-1) target = t.timetuple() self.assertExpectedResult( self.cal.parse('yesterday', start), (target, 1)) - t = datetime.datetime(self.yr, self.mth, self.dy, 9, 0, 0) + t = datetime.datetime( + self.yr, self.mth, self.dy, + self.day_start_hour, 0, 0) target = t.timetuple() self.assertExpectedResult(self.cal.parse('today', start), (target, 1)) From 68e3bfeabd7e55894b22ebfe44c27d6e51dbc7d7 Mon Sep 17 00:00:00 2001 From: Nishant Deshpande Date: Wed, 9 Nov 2016 11:25:46 -0800 Subject: [PATCH 09/12] added parameter specs --- parsedatetime/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/parsedatetime/__init__.py b/parsedatetime/__init__.py index b7353f9..f48f152 100644 --- a/parsedatetime/__init__.py +++ b/parsedatetime/__init__.py @@ -262,6 +262,9 @@ def __init__(self, constants=None, version=VERSION_FLAG_STYLE, @param version: Default style version of current Calendar instance. Valid value can be 1 (L{VERSION_FLAG_STYLE}) or 2 (L{VERSION_CONTEXT_STYLE}). See L{parse()}. + @type day_start_hour: int + @param day_start_hour: Hour to set a datetime when no time has been + specified. @rtype: object @return: L{Calendar} instance From 4af6b6af71a5c330483294358cbab45cb00570d6 Mon Sep 17 00:00:00 2001 From: ipaterson Date: Sun, 27 Nov 2016 10:04:03 -0500 Subject: [PATCH 10/12] Fixed code formatting that was causing pycodestyle 2.2.0 to fail unit tests --- parsedatetime/__init__.py | 1 + parsedatetime/pdt_locales/icu.py | 4 ++-- tests/TestComplexDateTimes.py | 1 + tests/TestConvertUnitAsWords.py | 1 + tests/TestPhrases.py | 4 ++-- tests/TestRussianLocale.py | 1 + tests/TestSimpleDateTimes.py | 1 + tests/TestSimpleOffsets.py | 1 + tests/TestSimpleOffsetsNoon.py | 1 + 9 files changed, 11 insertions(+), 4 deletions(-) diff --git a/parsedatetime/__init__.py b/parsedatetime/__init__.py index f48f152..6725a41 100644 --- a/parsedatetime/__init__.py +++ b/parsedatetime/__init__.py @@ -244,6 +244,7 @@ def _parse_date_rfc822(dateString): VERSION_CONTEXT_STYLE = 2 DEFAULT_DAY_START_HOUR = 9 + class Calendar(object): """ diff --git a/parsedatetime/pdt_locales/icu.py b/parsedatetime/pdt_locales/icu.py index e3aee5a..7d04b1d 100644 --- a/parsedatetime/pdt_locales/icu.py +++ b/parsedatetime/pdt_locales/icu.py @@ -138,10 +138,10 @@ def get_icu(locale): result['dateSep'] = [ds] s = result['dateFormats']['short'] - l = s.lower().split(ds) + formats = s.lower().split(ds) dp_order = [] - for s in l: + for s in formats: if len(s) > 0: dp_order.append(s[:1]) diff --git a/tests/TestComplexDateTimes.py b/tests/TestComplexDateTimes.py index 56132ed..a1afd1f 100644 --- a/tests/TestComplexDateTimes.py +++ b/tests/TestComplexDateTimes.py @@ -159,5 +159,6 @@ def testDatesWithDay(self): self.assertExpectedResult( self.cal.parse('tuesday august 23nd 2016 at 5pm', start), (target, 3)) + if __name__ == "__main__": unittest.main() diff --git a/tests/TestConvertUnitAsWords.py b/tests/TestConvertUnitAsWords.py index daede93..ba15809 100644 --- a/tests/TestConvertUnitAsWords.py +++ b/tests/TestConvertUnitAsWords.py @@ -32,5 +32,6 @@ def testConversions(self): for pair in self.tests: self.assertEqual(self.cal._convertUnitAsWords(pair[0]), pair[1]) + if __name__ == "__main__": unittest.main() diff --git a/tests/TestPhrases.py b/tests/TestPhrases.py index 8dafa55..d6cac4c 100644 --- a/tests/TestPhrases.py +++ b/tests/TestPhrases.py @@ -117,8 +117,8 @@ def testEndOfPhrases(self): yr += 1 t = (datetime.datetime( - yr, mth, 1, self.day_start_hour, 0, 0) - + datetime.timedelta(days=-1)) + yr, mth, 1, self.day_start_hour, 0, 0) + + datetime.timedelta(days=-1)) start = s.timetuple() target = t.timetuple() diff --git a/tests/TestRussianLocale.py b/tests/TestRussianLocale.py index 1624be5..b308044 100644 --- a/tests/TestRussianLocale.py +++ b/tests/TestRussianLocale.py @@ -133,5 +133,6 @@ def testConjugate(self): # now + datetime.timedelta(days=2) # ) + if __name__ == "__main__": unittest.main() diff --git a/tests/TestSimpleDateTimes.py b/tests/TestSimpleDateTimes.py index c768258..7eca820 100644 --- a/tests/TestSimpleDateTimes.py +++ b/tests/TestSimpleDateTimes.py @@ -571,5 +571,6 @@ def testYearParseStyle(self): # self.assertExpectedResult(self.cal.parse('12:00', start), # (target, 2)) + if __name__ == "__main__": unittest.main() diff --git a/tests/TestSimpleOffsets.py b/tests/TestSimpleOffsets.py index 8ae7906..5ad9f8d 100644 --- a/tests/TestSimpleOffsets.py +++ b/tests/TestSimpleOffsets.py @@ -29,6 +29,7 @@ def _truncateResult(result, trunc_seconds=True, trunc_hours=False): dt = dt[:3] + (0,) * 6 return dt, flag + _tr = _truncateResult diff --git a/tests/TestSimpleOffsetsNoon.py b/tests/TestSimpleOffsetsNoon.py index ca0d4c8..6ec4cd8 100644 --- a/tests/TestSimpleOffsetsNoon.py +++ b/tests/TestSimpleOffsetsNoon.py @@ -84,5 +84,6 @@ def testOffsetBeforeModifiedNoon(self): self.assertExpectedResult( self.cal.parse('5 hours before next noon', start), (target, 2)) + if __name__ == "__main__": unittest.main() From bdcb0c6b84fa3212842754b17ee029ec922e0499 Mon Sep 17 00:00:00 2001 From: Mike Taylor Date: Mon, 11 Oct 2021 13:07:25 -0400 Subject: [PATCH 11/12] Removed the setting of DEFAULT_DAY_START_HOUR as it is already being set in Constants In _evalModifier() initialize startHour from the current ptc object if current day_start_hour is NONE, otherwise pull it from the current day_start_hour --- parsedatetime/__init__.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/parsedatetime/__init__.py b/parsedatetime/__init__.py index cb90cd9..beb901d 100644 --- a/parsedatetime/__init__.py +++ b/parsedatetime/__init__.py @@ -241,7 +241,6 @@ def _parse_date_rfc822(dateString): VERSION_FLAG_STYLE = 1 VERSION_CONTEXT_STYLE = 2 -DEFAULT_DAY_START_HOUR = 9 class Calendar(object): @@ -794,9 +793,12 @@ def _evalModifier(self, modifier, chunk1, chunk2, sourceTime): startMinute = mn startSecond = sec else: - startHour = self.day_start_hour startMinute = 0 startSecond = 0 + if self.day_start_hour is None: + startHour = self.ptc.StartHour + else: + startHour = self.day_start_hour # capture the units after the modifier and the remaining # string after the unit From 7e45f77f1a22d3cd46077f90fc5a25194595a9a4 Mon Sep 17 00:00:00 2001 From: Mike Taylor Date: Mon, 11 Oct 2021 13:27:26 -0400 Subject: [PATCH 12/12] Removed explicitly setting "day start hour" in tests except in the tests that were testing that feature The Calendar class will now use the Constants member attribute value for StartHour as the default unless an explicit day_start_hour is passed --- parsedatetime/__init__.py | 12 +++++------- tests/TestDayStartHour.py | 3 +-- tests/TestLocaleBase.py | 7 ++----- tests/TestNlp.py | 6 ++---- tests/TestPhrases.py | 8 +++----- tests/TestSimpleOffsets.py | 10 ++++------ 6 files changed, 17 insertions(+), 29 deletions(-) diff --git a/parsedatetime/__init__.py b/parsedatetime/__init__.py index beb901d..60967ab 100644 --- a/parsedatetime/__init__.py +++ b/parsedatetime/__init__.py @@ -251,7 +251,7 @@ class Calendar(object): """ def __init__(self, constants=None, version=VERSION_FLAG_STYLE, - day_start_hour=DEFAULT_DAY_START_HOUR): + day_start_hour=None): """ Default constructor for the L{Calendar} class. @@ -273,6 +273,8 @@ def __init__(self, constants=None, version=VERSION_FLAG_STYLE, self.ptc = Constants() else: self.ptc = constants + if day_start_hour is not None: + self.ptc.StartHour = day_start_hour self.version = version if version == VERSION_FLAG_STYLE: @@ -282,7 +284,6 @@ def __init__(self, constants=None, version=VERSION_FLAG_STYLE, 'with argument `version=parsedatetime.VERSION_CONTEXT_STYLE`.', pdt20DeprecationWarning) self._ctxStack = pdtContextStack() - self.day_start_hour = day_start_hour @contextlib.contextmanager def context(self): @@ -795,10 +796,7 @@ def _evalModifier(self, modifier, chunk1, chunk2, sourceTime): else: startMinute = 0 startSecond = 0 - if self.day_start_hour is None: - startHour = self.ptc.StartHour - else: - startHour = self.day_start_hour + startHour = self.ptc.StartHour # capture the units after the modifier and the remaining # string after the unit @@ -1205,7 +1203,7 @@ def _evalDayStr(self, datetimeString, sourceTime): startMinute = mn startSecond = sec else: - startHour = self.day_start_hour + startHour = self.ptc.StartHour startMinute = 0 startSecond = 0 diff --git a/tests/TestDayStartHour.py b/tests/TestDayStartHour.py index 9876582..ad1bc7d 100644 --- a/tests/TestDayStartHour.py +++ b/tests/TestDayStartHour.py @@ -29,8 +29,7 @@ def setUp(self): def testDifferentDayStartHours(self): for day_start_hour in (0, 6, 9, 12): - cal = pdt.Calendar( - day_start_hour=day_start_hour) + cal = pdt.Calendar(day_start_hour=day_start_hour) s = datetime.datetime.now() t = datetime.datetime( diff --git a/tests/TestLocaleBase.py b/tests/TestLocaleBase.py index 1ac0aeb..7852130 100644 --- a/tests/TestLocaleBase.py +++ b/tests/TestLocaleBase.py @@ -129,13 +129,10 @@ class TestDayOffsets(test): def setUp(self): super(TestDayOffsets, self).setUp() self.ptc = pdt.Constants('fr_FR', usePyICU=False) - self.day_start_hour = 9 - self.cal = pdt.Calendar( - self.ptc, day_start_hour=self.day_start_hour) + self.cal = pdt.Calendar(self.ptc) def test_dayoffsets(self): - start = datetime.datetime(self.yr, self.mth, self.dy, - self.day_start_hour) + start = datetime.datetime(self.yr, self.mth, self.dy, self.ptc.StartHour) for date_string, expected_day_offset in [ ("Aujourd'hui", 0), ("aujourd'hui", 0), diff --git a/tests/TestNlp.py b/tests/TestNlp.py index 9ba2abb..d9ccc73 100644 --- a/tests/TestNlp.py +++ b/tests/TestNlp.py @@ -57,9 +57,7 @@ def assertExpectedResult(self, result, check, dateOnly=False): return True def setUp(self): - self.day_start_hour = 9 - self.cal = pdt.Calendar( - day_start_hour=self.day_start_hour) + self.cal = pdt.Calendar() (self.yr, self.mth, self.dy, self.hr, self.mn, self.sec, self.wd, self.yd, self.isdst) = time.localtime() @@ -77,7 +75,7 @@ def testLongPhrase(self): 3, 72, 90, 'next Friday at 9PM'), (datetime.datetime(2013, 8, 1, 21, 30, 0), 2, 120, 132, 'in 5 minutes'), - (datetime.datetime(2013, 8, 8, self.day_start_hour, 0), + (datetime.datetime(2013, 8, 8, self.cal.ptc.StartHour, 0), 1, 173, 182, 'next week')) # positive testing diff --git a/tests/TestPhrases.py b/tests/TestPhrases.py index d6cac4c..dfe5ebe 100644 --- a/tests/TestPhrases.py +++ b/tests/TestPhrases.py @@ -23,9 +23,7 @@ def assertExpectedResult(self, result, check, **kwargs): return utils.compareResultByTimeTuplesAndFlags(result, check, **kwargs) def setUp(self): - self.day_start_hour = 9 - self.cal = pdt.Calendar( - day_start_hour=self.day_start_hour) + self.cal = pdt.Calendar() (self.yr, self.mth, self.dy, self.hr, self.mn, self.sec, self.wd, self.yd, self.isdst) = time.localtime() @@ -117,7 +115,7 @@ def testEndOfPhrases(self): yr += 1 t = (datetime.datetime( - yr, mth, 1, self.day_start_hour, 0, 0) + + yr, mth, 1, self.cal.ptc.StartHour, 0, 0) + datetime.timedelta(days=-1)) start = s.timetuple() @@ -133,7 +131,7 @@ def testEndOfPhrases(self): (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = s.timetuple() t = datetime.datetime( - yr, 12, 31, self.day_start_hour, 0, 0) + yr, 12, 31, self.cal.ptc.StartHour, 0, 0) start = s.timetuple() target = t.timetuple() diff --git a/tests/TestSimpleOffsets.py b/tests/TestSimpleOffsets.py index 5ad9f8d..fcc5342 100644 --- a/tests/TestSimpleOffsets.py +++ b/tests/TestSimpleOffsets.py @@ -40,9 +40,7 @@ def assertExpectedResult(self, result, check, **kwargs): return utils.compareResultByTimeTuplesAndFlags(result, check, **kwargs) def setUp(self): - self.day_start_hour = 9 - self.cal = pdt.Calendar( - day_start_hour=self.day_start_hour) + self.cal = pdt.Calendar() (self.yr, self.mth, self.dy, self.hr, self.mn, self.sec, self.wd, self.yd, self.isdst) = time.localtime() @@ -257,7 +255,7 @@ def testSpecials(self): s = datetime.datetime.now() t = datetime.datetime( self.yr, self.mth, self.dy, - self.day_start_hour, 0, 0) + datetime.timedelta(days=1) + self.cal.ptc.StartHour, 0, 0) + datetime.timedelta(days=1) start = s.timetuple() target = t.timetuple() @@ -269,7 +267,7 @@ def testSpecials(self): t = datetime.datetime( self.yr, self.mth, self.dy, - self.day_start_hour, 0, 0) + datetime.timedelta(days=-1) + self.cal.ptc.StartHour, 0, 0) + datetime.timedelta(days=-1) target = t.timetuple() self.assertExpectedResult( @@ -277,7 +275,7 @@ def testSpecials(self): t = datetime.datetime( self.yr, self.mth, self.dy, - self.day_start_hour, 0, 0) + self.cal.ptc.StartHour, 0, 0) target = t.timetuple() self.assertExpectedResult(self.cal.parse('today', start), (target, 1))