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
 
-"""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
@@ -61,6 +142,10 @@ class ParseException(Exception):
     """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
 
@@ -100,15 +185,19 @@ class RaisingErrorListener(antlr4.DiagnosticErrorListener):
     ),
 )
 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()
+        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.
-    See also: string_utils.parse_date.
+    See also :meth:`pyutils.string_utils.to_date`.
+
     """
 
     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:
-        """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,
@@ -188,27 +281,44 @@ class DateParser(dateparse_utilsListener):
         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.
 
+        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.
 
-        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)
@@ -228,22 +338,40 @@ class DateParser(dateparse_utilsListener):
         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 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 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: