More work to improve documentation generated by sphinx. Also fixes
[pyutils.git] / src / pyutils / datetimez / dateparse_utils.py
index 89112b4e29fb91a235f13c8f6dbdd74952068fe8..7e8b6d6d01f71fd8f2178b3a9c0d549371551807 100755 (executable)
@@ -5,7 +5,88 @@
 
 # © Copyright 2021-2022, Scott Gasch
 
 
 # © Copyright 2021-2022, Scott Gasch
 
-"""Parse dates in a variety of formats."""
+"""
+Parse dates / datetimes in a variety of formats.  Some examples:
+
+    |    today
+    |    tomorrow
+    |    yesterday
+    |    21:30
+    |    12:01am
+    |    12:01pm
+    |    last Wednesday
+    |    this Wednesday
+    |    next Wed
+    |    this coming Tues
+    |    this past Mon
+    |    4 days ago
+    |    4 Mondays ago
+    |    4 months ago
+    |    3 days back
+    |    13 weeks from now
+    |    1 year from now
+    |    4 weeks from now
+    |    3 saturdays ago
+    |    4 months from today
+    |    5 years from yesterday
+    |    6 weeks from tomorrow
+    |    april 15, 2005
+    |    april 21
+    |    9:30am on last Wednesday
+    |    2005/apr/15
+    |    2005 apr 15
+    |    the 1st wednesday in may
+    |    the last sun of june
+    |    this easter
+    |    last xmas
+    |    Christmas, 1999
+    |    next MLK day
+    |    Halloween, 2020
+    |    5 work days after independence day
+    |    50 working days from last wed
+    |    25 working days before xmas
+    |    today +1 week
+    |    sunday -3 weeks
+    |    3 weeks before xmas, 1999
+    |    3 days before new years eve, 2000
+    |    july 4th
+    |    the ides of march
+    |    the nones of april
+    |    the kalends of may
+    |    4 sundays before veterans' day
+    |    xmas eve
+    |    this friday at 5pm
+    |    presidents day
+    |    memorial day, 1921
+    |    thanksgiving
+    |    2 sun in jun
+    |    easter -40 days
+    |    2 days before last xmas at 3:14:15.92a
+    |    3 weeks after xmas, 1995 at midday
+    |    4 months before easter, 1992 at midnight
+    |    5 months before halloween, 1995 at noon
+    |    4 days before last wednesday
+    |    44 months after today
+    |    44 years before today
+    |    44 weeks ago
+    |    15 minutes to 3am
+    |    quarter past 4pm
+    |    half past 9
+    |    4 seconds to midnight
+    |    4 seconds to midnight, tomorrow
+    |    2021/apr/15T21:30:44.55
+    |    2021/apr/15 at 21:30:44.55
+    |    2021/4/15 at 21:30:44.55
+    |    2021/04/15 at 21:30:44.55Z
+    |    2021/04/15 at 21:30:44.55EST
+    |    13 days after last memorial day at 12 seconds before 4pm
+
+This code is used by other code in the pyutils library; for example,
+when using :file:`argparse_utils.py` to pass an argument of type
+datetime it allows the user to use free form English expressions.
+
+See the `unittest <https://wannabe.guru.org/gitweb/?p=pyutils.git;a=blob_plain;f=tests/datetimez/dateparse_utils_test.py;h=93c7b96e4c19af217fbafcf1ed5dbde13ec599c5;hb=HEAD>`_ for more examples and the `grammar <https://wannabe.guru.org/gitweb/?p=pyutils.git;a=blob_plain;f=src/pyutils/datetimez/dateparse_utils.g4;hb=HEAD>`_ for more details.
+"""
 
 import datetime
 import functools
 
 import datetime
 import functools
@@ -61,6 +142,10 @@ class ParseException(Exception):
     """An exception thrown during parsing because of unrecognized input."""
 
     def __init__(self, message: str) -> None:
     """An exception thrown during parsing because of unrecognized input."""
 
     def __init__(self, message: str) -> None:
+        """
+        Args:
+            message: parse error message description.
+        """
         super().__init__()
         self.message = message
 
         super().__init__()
         self.message = message
 
@@ -100,15 +185,19 @@ class RaisingErrorListener(antlr4.DiagnosticErrorListener):
     ),
 )
 class DateParser(dateparse_utilsListener):
     ),
 )
 class DateParser(dateparse_utilsListener):
-    """A class to parse dates expressed in human language.  Example usage::
+    """A class to parse dates expressed in human language (English).
+    Example usage::
 
         d = DateParser()
         d.parse('next wednesday')
         dt = d.get_datetime()
 
         d = DateParser()
         d.parse('next wednesday')
         dt = d.get_datetime()
+        print(dt)
+        Wednesday 2022/10/26 00:00:00.000000
 
     Note that the interface is somewhat klunky here because this class is
     conforming to interfaces auto-generated by ANTLR as it parses the grammar.
 
     Note that the interface is somewhat klunky here because this class is
     conforming to interfaces auto-generated by ANTLR as it parses the grammar.
-    See also: string_utils.parse_date.
+    See also :meth:`pyutils.string_utils.to_date`.
+
     """
 
     PARSE_TYPE_SINGLE_DATE_EXPR = 1
     """
 
     PARSE_TYPE_SINGLE_DATE_EXPR = 1
@@ -117,10 +206,14 @@ class DateParser(dateparse_utilsListener):
     PARSE_TYPE_BASE_AND_OFFSET_TIME_EXPR = 4
 
     def __init__(self, *, override_now_for_test_purposes=None) -> None:
     PARSE_TYPE_BASE_AND_OFFSET_TIME_EXPR = 4
 
     def __init__(self, *, override_now_for_test_purposes=None) -> None:
-        """C'tor.  Passing a value to override_now_for_test_purposes can be
-        used to force this instance to use a custom date/time for its
-        idea of "now" so that the code can be more easily unittested.
-        Leave as None for real use cases.
+        """Construct a parser.
+
+        Args:
+            override_now_for_test_purposes: passing a value to
+                override_now_for_test_purposes can be used to force
+                this parser instance to use a custom date/time for its
+                idea of "now" so that the code can be more easily
+                unittested.  Leave as None for real use cases.
         """
         self.month_name_to_number = {
             'jan': 1,
         """
         self.month_name_to_number = {
             'jan': 1,
@@ -188,27 +281,44 @@ class DateParser(dateparse_utilsListener):
         self._reset()
 
     def parse(self, date_string: str) -> Optional[datetime.datetime]:
         self._reset()
 
     def parse(self, date_string: str) -> Optional[datetime.datetime]:
-        """Parse a date/time expression and return a timezone agnostic
-        datetime on success.  Also sets self.datetime, self.date and
-        self.time which can each be accessed other methods on the
-        class: get_datetime(), get_date() and get_time().  Raises a
-        ParseException with a helpful(?) message on parse error or
+        """
+        Parse a ~free form date/time expression and return a
+        timezone agnostic datetime on success.  Also sets
+        `self.datetime`, `self.date` and `self.time` which can each be
+        accessed other methods on the class: :meth:`get_datetime`,
+        :meth:`get_date` and :meth:`get_time`.  Raises a
+        `ParseException` with a helpful(?) message on parse error or
         confusion.
 
         confusion.
 
+        This is the main entrypoint to this class for caller code.
+
         To get an idea of what expressions can be parsed, check out
         the unittest and the grammar.
 
         To get an idea of what expressions can be parsed, check out
         the unittest and the grammar.
 
-        Usage:
+        Args:
+            date_string: the string to parse
 
 
-        txt = '3 weeks before last tues at 9:15am'
-        dp = DateParser()
-        dt1 = dp.parse(txt)
-        dt2 = dp.get_datetime(tz=pytz.timezone('US/Pacific'))
+        Returns:
+            A datetime.datetime representing the parsed date/time or
+            None on error.
 
 
-        # dt1 and dt2 will be identical other than the fact that
-        # the latter's tzinfo will be set to PST/PDT.
+        .. note::
+
+            Parsed date expressions without any time part return
+            hours = minutes = seconds = microseconds = 0 (i.e. at
+            midnight that day).  Parsed time expressions without any
+            date part default to date = today.
+
+        Example usage::
+
+            txt = '3 weeks before last tues at 9:15am'
+            dp = DateParser()
+            dt1 = dp.parse(txt)
+            dt2 = dp.get_datetime(tz=pytz.timezone('US/Pacific'))
+
+            # dt1 and dt2 will be identical other than the fact that
+            # the latter's tzinfo will be set to PST/PDT.
 
 
-        This is the main entrypoint to this class for caller code.
         """
         date_string = date_string.strip()
         date_string = re.sub(r'\s+', ' ', date_string)
         """
         date_string = date_string.strip()
         date_string = re.sub(r'\s+', ' ', date_string)
@@ -228,22 +338,40 @@ class DateParser(dateparse_utilsListener):
         return self.datetime
 
     def get_date(self) -> Optional[datetime.date]:
         return self.datetime
 
     def get_date(self) -> Optional[datetime.date]:
-        """Return the date part or None."""
+        """
+        Returns:
+            The date part of the last :meth:`parse` operation again
+            or None.
+        """
         return self.date
 
     def get_time(self) -> Optional[datetime.time]:
         return self.date
 
     def get_time(self) -> Optional[datetime.time]:
-        """Return the time part or None."""
+        """
+        Returns:
+            The time part of the last :meth:`parse` operation again
+            or None.
+        """
         return self.time
 
     def get_datetime(self, *, tz=None) -> Optional[datetime.datetime]:
         return self.time
 
     def get_datetime(self, *, tz=None) -> Optional[datetime.datetime]:
-        """Return as a datetime.  Parsed date expressions without any time
-        part return hours = minutes = seconds = microseconds = 0 (i.e. at
-        midnight that day).  Parsed time expressions without any date part
-        default to date = today.
-
-        The optional tz param allows the caller to request the datetime be
-        timezone aware and sets the tzinfo to the indicated zone.  Defaults
-        to timezone naive (i.e. tzinfo = None).
+        """Get the datetime of the last :meth:`parse` operation again
+        ot None.
+
+        Args:
+            tz: the timezone to set on output times.  By default we
+                return timezone-naive datetime objects.
+
+        Returns:
+            the same datetime that :meth:`parse` last did, optionally
+            overriding the timezone.  Returns None of no calls to
+            :meth:`parse` have yet been made.
+
+        .. note::
+
+            Parsed date expressions without any time part return
+            hours = minutes = seconds = microseconds = 0 (i.e. at
+            midnight that day).  Parsed time expressions without any
+            date part default to date = today.
         """
         dt = self.datetime
         if dt is not None:
         """
         dt = self.datetime
         if dt is not None: