More work to improve documentation generated by sphinx. Also fixes
[pyutils.git] / src / pyutils / datetimez / datetime_utils.py
index c47d38c0aab88da0835cb6919434532c5288ad08..5ddf4b6014f66b171bb88bf3421ab0623cdf1eba 100644 (file)
@@ -19,14 +19,16 @@ logger = logging.getLogger(__name__)
 
 
 def is_timezone_aware(dt: datetime.datetime) -> bool:
 
 
 def is_timezone_aware(dt: datetime.datetime) -> bool:
-    """Returns true if the datetime argument is timezone aware or
-    False if not.
-
-    See: https://docs.python.org/3/library/datetime.html
-    #determining-if-an-object-is-aware-or-naive
+    """
+    Checks whether a datetime is timezone aware or naive.
+    See: https://docs.python.org/3/library/datetime.html#determining-if-an-object-is-aware-or-naive
 
     Args:
 
     Args:
-        dt: The datetime object to check
+        dt: the datetime to check for timezone awareness
+
+    Returns:
+        True if the datetime argument is timezone aware or
+        False if not.
 
     >>> is_timezone_aware(datetime.datetime.now())
     False
 
     >>> is_timezone_aware(datetime.datetime.now())
     False
@@ -39,14 +41,14 @@ def is_timezone_aware(dt: datetime.datetime) -> bool:
 
 
 def is_timezone_naive(dt: datetime.datetime) -> bool:
 
 
 def is_timezone_naive(dt: datetime.datetime) -> bool:
-    """Inverse of is_timezone_aware -- returns true if the dt argument
-    is timezone naive.
-
-    See: https://docs.python.org/3/library/datetime.html
-    #determining-if-an-object-is-aware-or-naive
+    """Inverse of :meth:`is_timezone_aware`.
+    See: https://docs.python.org/3/library/datetime.html#determining-if-an-object-is-aware-or-naive
 
     Args:
 
     Args:
-        dt: The datetime object to check
+        dt: the datetime to check
+
+    Returns:
+        True if the dt argument is timezone naive, False otherwise
 
     >>> is_timezone_naive(datetime.datetime.now())
     True
 
     >>> is_timezone_naive(datetime.datetime.now())
     True
@@ -59,19 +61,33 @@ def is_timezone_naive(dt: datetime.datetime) -> bool:
 
 
 def strip_timezone(dt: datetime.datetime) -> datetime.datetime:
 
 
 def strip_timezone(dt: datetime.datetime) -> datetime.datetime:
-    """Remove the timezone from a datetime.
+    """
+    Remove the timezone from a datetime.  Silently ignores datetimes
+    which are already timezone naive.
+
+    Args:
+        dt: the datetime to remove timezone from
+
+    Returns:
+        A datetime identical to dt, the input argument, except for
+        that the timezone has been removed.
+
+    See also :meth:`add_timezone`, :meth:`replace_timezone`, :meth:`translate_timezone`.
 
     .. warning::
 
         This does not change the hours, minutes, seconds,
 
     .. warning::
 
         This does not change the hours, minutes, seconds,
-        months, days, years, etc... Thus the instant to which this
-        timestamp refers will change.  Silently ignores datetimes
-        which are already timezone naive.
+        months, days, years, etc... Thus, the instant to which this
+        timestamp refers will change when the timezone is added.
+        See examples.
 
     >>> now = now_pacific()
     >>> now.tzinfo == None
     False
 
 
     >>> now = now_pacific()
     >>> now.tzinfo == None
     False
 
+    >>> "US/Pacific" in now.tzinfo.__repr__()
+    True
+
     >>> dt = strip_timezone(now)
     >>> dt == now
     False
     >>> dt = strip_timezone(now)
     >>> dt == now
     False
@@ -81,7 +97,6 @@ def strip_timezone(dt: datetime.datetime) -> datetime.datetime:
 
     >>> dt.hour == now.hour
     True
 
     >>> dt.hour == now.hour
     True
-
     """
     if is_timezone_naive(dt):
         return dt
     """
     if is_timezone_naive(dt):
         return dt
@@ -90,9 +105,24 @@ def strip_timezone(dt: datetime.datetime) -> datetime.datetime:
 
 def add_timezone(dt: datetime.datetime, tz: datetime.tzinfo) -> datetime.datetime:
     """
 
 def add_timezone(dt: datetime.datetime, tz: datetime.tzinfo) -> datetime.datetime:
     """
-    Adds a timezone to a timezone naive datetime.  This does not
-    change the instant to which the timestamp refers.  See also:
-    replace_timezone.
+    Adds a timezone to a timezone naive datetime.
+
+    Args:
+        dt: the datetime to insert a timezone onto
+        tz: the timezone to insert
+
+    See also :meth:`replace_timezone`, :meth:`strip_timezone`, :meth:`translate_timezone`.
+
+    Returns:
+        A datetime identical to dt, the input datetime, except for
+        that a timezone has been added.
+
+    .. warning::
+
+        This doesn't change the hour, minute, second, day, month, etc...
+        of the input timezone.  It simply adds a timezone to it.  Adding
+        a timezone this way will likely change the instant to which the
+        datetime refers.  See examples.
 
     >>> now = datetime.datetime.now()
     >>> is_timezone_aware(now)
 
     >>> now = datetime.datetime.now()
     >>> is_timezone_aware(now)
@@ -102,6 +132,9 @@ def add_timezone(dt: datetime.datetime, tz: datetime.tzinfo) -> datetime.datetim
     >>> is_timezone_aware(now_pacific)
     True
 
     >>> is_timezone_aware(now_pacific)
     True
 
+    >>> "US/Pacific" in now_pacific.tzinfo.__repr__()
+    True
+
     >>> now.hour == now_pacific.hour
     True
     >>> now.minute == now_pacific.minute
     >>> now.hour == now_pacific.hour
     True
     >>> now.minute == now_pacific.minute
@@ -129,18 +162,36 @@ def add_timezone(dt: datetime.datetime, tz: datetime.tzinfo) -> datetime.datetim
 def replace_timezone(
     dt: datetime.datetime, tz: Optional[datetime.tzinfo]
 ) -> datetime.datetime:
 def replace_timezone(
     dt: datetime.datetime, tz: Optional[datetime.tzinfo]
 ) -> datetime.datetime:
-    """Replaces the timezone on a timezone aware datetime object directly
+    """
+    Replaces the timezone on a timezone aware datetime object directly
     (leaving the year, month, day, hour, minute, second, micro,
     (leaving the year, month, day, hour, minute, second, micro,
-    etc... alone).
+    etc... alone).  The same as calling :meth:`strip_timezone` followed
+    by :meth:`add_timezone`.
 
     Works with timezone aware and timezone naive dts but for the
 
     Works with timezone aware and timezone naive dts but for the
-    latter it is probably better to use add_timezone or just create it
-    with a tz parameter.  Using this can have weird side effects like
-    UTC offsets that are not an even multiple of an hour, etc...
+    latter it is probably better to use :meth:`add_timezone` or just
+    create it with a `tz` parameter.  Using this can have weird side
+    effects like UTC offsets that are not an even multiple of an hour,
+    etc...
+
+    Args:
+        dt: the datetime whose timezone should be changed
+        tz: the new timezone
+
+    Returns:
+        The resulting datetime.  Hour, minute, second, etc... are unmodified.
+        See warning below.
+
+    See also :meth:`add_timezone`, :meth:`strip_timezone`, :meth:`translate_timezone`.
 
     .. warning::
 
 
     .. warning::
 
-        This changes the instant to which this dt refers.
+        This code isn't changing the hour, minute, second, day, month, etc...
+        of the datetime.  It's just messing with the timezone.  Changing
+        the timezone without changing the time causes the instant to which
+        the datetime refers to change.  For example, if passed 7:01pm PST
+        and asked to make it EST, the result will be 7:01pm EST.  See
+        examples.
 
     >>> from pytz import UTC
     >>> d = now_pacific()
 
     >>> from pytz import UTC
     >>> d = now_pacific()
@@ -152,7 +203,6 @@ def replace_timezone(
     'UTC'
     >>> o.hour == h
     True
     'UTC'
     >>> o.hour == h
     True
-
     """
     if is_timezone_aware(dt):
         logger.warning(
     """
     if is_timezone_aware(dt):
         logger.warning(
@@ -180,10 +230,22 @@ def replace_time_timezone(t: datetime.time, tz: datetime.tzinfo) -> datetime.tim
     """Replaces the timezone on a datetime.time directly without performing
     any translation.
 
     """Replaces the timezone on a datetime.time directly without performing
     any translation.
 
+    Args:
+        t: the time to change the timezone on
+        tz: the new timezone desired
+
+    Returns:
+        A time with hour, minute, second, etc... identical to the input
+        time but with timezone replaced.
+
     .. warning::
 
     .. warning::
 
-        Note that, as above, this will change the instant to
-        which the time refers.
+        This code isn't changing the hour, minute, second, etc...
+        of the time.  It's just messing with the timezone.  Changing
+        the timezone without changing the time causes the instant to which
+        the datetime refers to change.  For example, if passed 7:01pm PST
+        and asked to make it EST, the result will be 7:01pm EST.  See
+        examples.
 
     >>> t = datetime.time(8, 15, 12, 0, pytz.UTC)
     >>> t.tzname()
 
     >>> t = datetime.time(8, 15, 12, 0, pytz.UTC)
     >>> t.tzname()
@@ -202,6 +264,20 @@ def translate_timezone(dt: datetime.datetime, tz: datetime.tzinfo) -> datetime.d
     day, hour, minute, second, micro, etc... appropriately.  The returned
     dt is the same instant in another timezone.
 
     day, hour, minute, second, micro, etc... appropriately.  The returned
     dt is the same instant in another timezone.
 
+    Args:
+        dt: the datetime whose timezone should be translated.
+        tz: the desired timezone
+
+    Returns:
+        A new datetime object that represents the same instant as the
+        input datetime but in the desired timezone.  Modifies hour, minute,
+        seconds, day, etc... as necessary for the instant to be preserved.
+        For example, if you pass 11:01pm PST in and ask for it to be
+        translated to EST you would get 2:01am the next day EST back
+        out.
+
+    See also :meth:`replace_timezone`, :meth:`strip_timezone`.
+
     >>> import pytz
     >>> d = now_pacific()
     >>> d.tzinfo.tzname(d)[0]     # Note: could be PST or PDT
     >>> import pytz
     >>> d = now_pacific()
     >>> d.tzinfo.tzname(d)[0]     # Note: could be PST or PDT
@@ -238,10 +314,16 @@ def date_to_datetime(date: datetime.date) -> datetime.datetime:
     """
     Given a date, return a datetime with hour/min/sec zero (midnight)
 
     """
     Given a date, return a datetime with hour/min/sec zero (midnight)
 
+    Arg:
+        date: the date desired
+
+    Returns:
+        A datetime with the same month, day, and year as the input
+        date and hours, minutes, seconds set to 12:00:00am.
+
     >>> import datetime
     >>> date_to_datetime(datetime.date(2021, 12, 25))
     datetime.datetime(2021, 12, 25, 0, 0)
     >>> import datetime
     >>> date_to_datetime(datetime.date(2021, 12, 25))
     datetime.datetime(2021, 12, 25, 0, 0)
-
     """
     return datetime.datetime(date.year, date.month, date.day, 0, 0, 0, 0)
 
     """
     return datetime.datetime(date.year, date.month, date.day, 0, 0, 0, 0)
 
@@ -253,6 +335,13 @@ def time_to_datetime_today(time: datetime.time) -> datetime.datetime:
     the resulting datetime will also be (and will use the same tzinfo).
     If the time is timezone naive, the datetime returned will be too.
 
     the resulting datetime will also be (and will use the same tzinfo).
     If the time is timezone naive, the datetime returned will be too.
 
+    Args:
+        time: the time desired
+
+    Returns:
+        datetime with hour, minute, second, timezone set to time and
+        day, month, year set to "today".
+
     >>> t = datetime.time(13, 14, 0)
     >>> d = now_pacific().date()
     >>> dt = time_to_datetime_today(t)
     >>> t = datetime.time(13, 14, 0)
     >>> d = now_pacific().date()
     >>> dt = time_to_datetime_today(t)
@@ -287,12 +376,19 @@ def date_and_time_to_datetime(
     """
     Given a date and time, merge them and return a datetime.
 
     """
     Given a date and time, merge them and return a datetime.
 
+    Args:
+        date: the date component
+        time: the time component
+
+    Returns:
+        A datetime with the time component set from time and the date
+        component set from date.
+
     >>> import datetime
     >>> d = datetime.date(2021, 12, 25)
     >>> t = datetime.time(12, 30, 0, 0)
     >>> date_and_time_to_datetime(d, t)
     datetime.datetime(2021, 12, 25, 12, 30)
     >>> import datetime
     >>> d = datetime.date(2021, 12, 25)
     >>> t = datetime.time(12, 30, 0, 0)
     >>> date_and_time_to_datetime(d, t)
     datetime.datetime(2021, 12, 25, 12, 30)
-
     """
     return datetime.datetime(
         date.year,
     """
     return datetime.datetime(
         date.year,
@@ -311,6 +407,15 @@ def datetime_to_date_and_time(
     """Return the component date and time objects of a datetime in a
     Tuple given a datetime.
 
     """Return the component date and time objects of a datetime in a
     Tuple given a datetime.
 
+    Args:
+        dt: the datetime to decompose
+
+    Returns:
+        A tuple whose first element contains a datetime.date that holds
+        the day, month, year, etc... from the input dt and whose second
+        element contains a datetime.time with hour, minute, second, micros,
+        and timezone set from the input dt.
+
     >>> import datetime
     >>> dt = datetime.datetime(2021, 12, 25, 12, 30)
     >>> (d, t) = datetime_to_date_and_time(dt)
     >>> import datetime
     >>> dt = datetime.datetime(2021, 12, 25, 12, 30)
     >>> (d, t) = datetime_to_date_and_time(dt)
@@ -318,7 +423,6 @@ def datetime_to_date_and_time(
     datetime.date(2021, 12, 25)
     >>> t
     datetime.time(12, 30)
     datetime.date(2021, 12, 25)
     >>> t
     datetime.time(12, 30)
-
     """
     return (dt.date(), dt.timetz())
 
     """
     return (dt.date(), dt.timetz())
 
@@ -326,11 +430,16 @@ def datetime_to_date_and_time(
 def datetime_to_date(dt: datetime.datetime) -> datetime.date:
     """Return just the date part of a datetime.
 
 def datetime_to_date(dt: datetime.datetime) -> datetime.date:
     """Return just the date part of a datetime.
 
+    Args:
+        dt: the datetime
+
+    Returns:
+        A datetime.date with month, day and year set from input dt.
+
     >>> import datetime
     >>> dt = datetime.datetime(2021, 12, 25, 12, 30)
     >>> datetime_to_date(dt)
     datetime.date(2021, 12, 25)
     >>> import datetime
     >>> dt = datetime.datetime(2021, 12, 25, 12, 30)
     >>> datetime_to_date(dt)
     datetime.date(2021, 12, 25)
-
     """
     return datetime_to_date_and_time(dt)[0]
 
     """
     return datetime_to_date_and_time(dt)[0]
 
@@ -338,11 +447,17 @@ def datetime_to_date(dt: datetime.datetime) -> datetime.date:
 def datetime_to_time(dt: datetime.datetime) -> datetime.time:
     """Return just the time part of a datetime.
 
 def datetime_to_time(dt: datetime.datetime) -> datetime.time:
     """Return just the time part of a datetime.
 
+    Args:
+        dt: the datetime
+
+    Returns:
+        A datetime.time with hour, minute, second, micros, and
+        timezone set from the input dt.
+
     >>> import datetime
     >>> dt = datetime.datetime(2021, 12, 25, 12, 30)
     >>> datetime_to_time(dt)
     datetime.time(12, 30)
     >>> import datetime
     >>> dt = datetime.datetime(2021, 12, 25, 12, 30)
     >>> datetime_to_time(dt)
     datetime.time(12, 30)
-
     """
     return datetime_to_date_and_time(dt)[1]
 
     """
     return datetime_to_date_and_time(dt)[1]
 
@@ -368,6 +483,13 @@ class TimeUnit(enum.IntEnum):
 
     @classmethod
     def is_valid(cls, value: Any):
 
     @classmethod
     def is_valid(cls, value: Any):
+        """
+        Args:
+            value: a value to be checked
+
+        Returns:
+            True is input value is a valid TimeUnit, False otherwise.
+        """
         if isinstance(value, int):
             return cls(value) is not None
         elif isinstance(value, TimeUnit):
         if isinstance(value, int):
             return cls(value) is not None
         elif isinstance(value, TimeUnit):
@@ -383,10 +505,26 @@ def n_timeunits_from_base(
     count: int, unit: TimeUnit, base: datetime.datetime
 ) -> datetime.datetime:
     """Return a datetime that is N units before/after a base datetime.
     count: int, unit: TimeUnit, base: datetime.datetime
 ) -> datetime.datetime:
     """Return a datetime that is N units before/after a base datetime.
-    e.g.  3 Wednesdays from base datetime, 2 weeks from base date, 10
-    years before base datetime, 13 minutes after base datetime, etc...
-    Note: to indicate before/after the base date, use a positive or
-    negative count.
+    For example:
+
+        - 3 Wednesdays from base datetime,
+        - 2 weeks from base date,
+        - 10 years before base datetime,
+        - 13 minutes after base datetime, etc...
+
+    Args:
+        count: signed number that indicates N units before/after the base.
+        unit: the timeunit that we are counting by.
+        base: a datetime representing the base date the result should be
+            relative to.
+
+    Returns:
+        A datetime that is count units before of after the base datetime.
+
+    .. note::
+
+        To indicate before/after the base date, use a positive or
+        negative count.
 
     >>> base = string_to_datetime("2021/09/10 11:24:51AM-0700")[0]
 
 
     >>> base = string_to_datetime("2021/09/10 11:24:51AM-0700")[0]
 
@@ -442,7 +580,6 @@ def n_timeunits_from_base(
     >>> base = string_to_datetime("2022/03/31 11:24:51AM-0700")[0]
     >>> n_timeunits_from_base(-1, TimeUnit.MONTHS, base)
     datetime.datetime(2022, 2, 28, 11, 24, 51, tzinfo=datetime.timezone(datetime.timedelta(days=-1, seconds=61200)))
     >>> base = string_to_datetime("2022/03/31 11:24:51AM-0700")[0]
     >>> n_timeunits_from_base(-1, TimeUnit.MONTHS, base)
     datetime.datetime(2022, 2, 28, 11, 24, 51, tzinfo=datetime.timezone(datetime.timedelta(days=-1, seconds=61200)))
-
     """
     assert TimeUnit.is_valid(unit)
     if count == 0:
     """
     assert TimeUnit.is_valid(unit)
     if count == 0:
@@ -576,6 +713,22 @@ def get_format_string(
     Helper to return a format string without looking up the documentation
     for strftime.
 
     Helper to return a format string without looking up the documentation
     for strftime.
 
+    Args:
+        date_time_separator: character or string to use between the date
+            and time outputs.
+        include_timezone: whether or not the result should include a timezone
+        include_dayname: whether or not the result should incude the dayname
+            (e.g. Monday, Wednesday, etc...)
+        use_month_abbrevs: whether or not to abbreviate (e.g. Jan) or spell out
+            (e.g. January) month names.
+        include_seconds: whether or not to include seconds in time.
+        include_fractional: whether or not to include micros in time output.
+        twelve_hour: use twelve hour (with am/pm) or twenty four hour time format?
+
+    Returns:
+        The format string for use with strftime that follows the given
+        requirements.
+
     >>> get_format_string()
     '%Y/%m/%d %I:%M:%S%p%z'
 
     >>> get_format_string()
     '%Y/%m/%d %I:%M:%S%p%z'
 
@@ -628,6 +781,19 @@ def datetime_to_string(
     A nice way to convert a datetime into a string; arguably better than
     just printing it and relying on it __repr__().
 
     A nice way to convert a datetime into a string; arguably better than
     just printing it and relying on it __repr__().
 
+    Args:
+        dt: the datetime to represent
+        date_time_separator: the character or string to separate the date and time
+            pieces of the representation.
+        include_timezone: should we include a timezone in the representation?
+        include_dayname: should we include the dayname (e.g. Mon) in
+            the representation or omit it?
+        use_month_abbrevs: should we name the month briefly (e.g. Jan) or spell
+            it out fully (e.g. January) in the representation?
+        include_seconds: should we include seconds in the time?
+        include_fractional: should we include micros in the time?
+        twelve_hour: should we use twelve or twenty-four hour time format?
+
     >>> d = string_to_datetime(
     ...                        "2021/09/10 11:24:51AM-0700",
     ...                       )[0]
     >>> d = string_to_datetime(
     ...                        "2021/09/10 11:24:51AM-0700",
     ...                       )[0]
@@ -637,7 +803,6 @@ def datetime_to_string(
     '2021/09/10 11:24:51AM-0700'
     >>> datetime_to_string(d, include_dayname=True, include_seconds=False)
     'Fri/2021/09/10 11:24AM-0700'
     '2021/09/10 11:24:51AM-0700'
     >>> datetime_to_string(d, include_dayname=True, include_seconds=False)
     'Fri/2021/09/10 11:24AM-0700'
-
     """
     fstring = get_format_string(
         date_time_separator=date_time_separator,
     """
     fstring = get_format_string(
         date_time_separator=date_time_separator,
@@ -664,7 +829,23 @@ def string_to_datetime(
 ) -> Tuple[datetime.datetime, str]:
     """A nice way to convert a string into a datetime.  Returns both the
     datetime and the format string used to parse it.  Also consider
 ) -> Tuple[datetime.datetime, str]:
     """A nice way to convert a string into a datetime.  Returns both the
     datetime and the format string used to parse it.  Also consider
-    dateparse.dateparse_utils for a full parser alternative.
+    :mod:`pyutils.datetimez.dateparse_utils` for a full parser alternative.
+
+    Args:
+        txt: the string to be converted into a datetime
+        date_time_separator: the character or string between the time and date
+            portions.
+        include_timezone: does the string include a timezone?
+        include_dayname: does the string include a dayname?
+        use_month_abbrevs: is the month abbreviated in the string (e.g. Feb)
+            or spelled out completely (e.g. February)?
+        include_seconds: does the string's time include seconds?
+        include_fractional: does the string's time include micros?
+        twelve_hour: is the string's time in twelve or twenty-four hour format?
+
+    Returns:
+        A tuple containing the datetime parsed from string and the formatting
+        string used to parse it.
 
     >>> d = string_to_datetime(
     ...                        "2021/09/10 11:24:51AM-0700",
 
     >>> d = string_to_datetime(
     ...                        "2021/09/10 11:24:51AM-0700",
@@ -686,7 +867,10 @@ def string_to_datetime(
 
 
 def timestamp() -> str:
 
 
 def timestamp() -> str:
-    """Return a timestamp for right now in Pacific timezone."""
+    """
+    Returns:
+        A timestamp for right now in Pacific timezone.
+    """
     ts = datetime.datetime.now(tz=pytz.timezone("US/Pacific"))
     return datetime_to_string(ts, include_timezone=True)
 
     ts = datetime.datetime.now(tz=pytz.timezone("US/Pacific"))
     return datetime_to_string(ts, include_timezone=True)
 
@@ -700,7 +884,17 @@ def time_to_string(
     twelve_hour=True,
 ) -> str:
     """A nice way to convert a datetime into a time (only) string.
     twelve_hour=True,
 ) -> str:
     """A nice way to convert a datetime into a time (only) string.
-    This ignores the date part of the datetime.
+    This ignores the date part of the datetime completely.
+
+    Args:
+        dt: the datetime whose time to represent
+        include_seconds: should seconds be included in the output?
+        include_fractional: should micros be included in the output?
+        include_timezone: should timezone be included in the output?
+        twelve_hour: use twelve or twenty-four hour format?
+
+    Returns:
+        A string representing the time of the input datetime.
 
     >>> d = string_to_datetime(
     ...                        "2021/09/10 11:24:51AM-0700",
 
     >>> d = string_to_datetime(
     ...                        "2021/09/10 11:24:51AM-0700",
@@ -736,7 +930,13 @@ def time_to_string(
 
 
 def seconds_to_timedelta(seconds: int) -> datetime.timedelta:
 
 
 def seconds_to_timedelta(seconds: int) -> datetime.timedelta:
-    """Convert a delta in seconds into a timedelta."""
+    """
+    Args:
+        seconds: a count of seconds
+
+    Returns:
+        A datetime.timedelta representing that count of seconds.
+    """
     return datetime.timedelta(seconds=seconds)
 
 
     return datetime.timedelta(seconds=seconds)
 
 
@@ -745,7 +945,16 @@ MinuteOfDay = NewType("MinuteOfDay", int)
 
 def minute_number(hour: int, minute: int) -> MinuteOfDay:
     """
 
 def minute_number(hour: int, minute: int) -> MinuteOfDay:
     """
-    Convert hour:minute into minute number from start of day.
+    Convert hour:minute into minute number from start of day.  That is,
+    if you imagine a day as a sequence of minutes from minute #0 up
+    to minute #1439, what minute number is, e.g., 6:52am?
+
+    Args:
+        hour: the hour to convert (0 <= hour <= 23)
+        minute: the minute to convert (0 <= minute <= 59)
+
+    Returns:
+        The minute number requested.  Raises `ValueError` on bad input.
 
     >>> minute_number(0, 0)
     0
 
     >>> minute_number(0, 0)
     0
@@ -755,16 +964,26 @@ def minute_number(hour: int, minute: int) -> MinuteOfDay:
 
     >>> minute_number(23, 59)
     1439
 
     >>> minute_number(23, 59)
     1439
-
     """
     """
+    if hour < 0 or hour > 23:
+        raise ValueError(f'Bad hour: {hour}.  Expected 0 <= hour <= 23')
+    if minute < 0 or minute > 59:
+        raise ValueError(f'Bad minute: {minute}.  Expected 0 <= minute <= 59')
     return MinuteOfDay(hour * 60 + minute)
 
 
 def datetime_to_minute_number(dt: datetime.datetime) -> MinuteOfDay:
     """
     return MinuteOfDay(hour * 60 + minute)
 
 
 def datetime_to_minute_number(dt: datetime.datetime) -> MinuteOfDay:
     """
-    Convert a datetime into a minute number (of the day).  Note that
-    this ignores the date part of the datetime and only uses the time
-    part.
+    Convert a datetime's time component into a minute number (of
+    the day).  Note that this ignores the date part of the datetime
+    and only uses the time part.
+
+    Args:
+        dt: the datetime whose time is to be converted
+
+    Returns:
+        The minute number (of the day) that represents the input datetime's
+        time.
 
     >>> d = string_to_datetime(
     ...                        "2021/09/10 11:24:51AM-0700",
 
     >>> d = string_to_datetime(
     ...                        "2021/09/10 11:24:51AM-0700",
@@ -772,7 +991,6 @@ def datetime_to_minute_number(dt: datetime.datetime) -> MinuteOfDay:
 
     >>> datetime_to_minute_number(d)
     684
 
     >>> datetime_to_minute_number(d)
     684
-
     """
     return minute_number(dt.hour, dt.minute)
 
     """
     return minute_number(dt.hour, dt.minute)
 
@@ -781,10 +999,15 @@ def time_to_minute_number(t: datetime.time) -> MinuteOfDay:
     """
     Convert a datetime.time into a minute number.
 
     """
     Convert a datetime.time into a minute number.
 
+    Args:
+        t: a datetime.time to convert into a minute number.
+
+    Returns:
+        The minute number (of the day) of the input time.
+
     >>> t = datetime.time(5, 15)
     >>> time_to_minute_number(t)
     315
     >>> t = datetime.time(5, 15)
     >>> time_to_minute_number(t)
     315
-
     """
     return minute_number(t.hour, t.minute)
 
     """
     return minute_number(t.hour, t.minute)
 
@@ -794,12 +1017,18 @@ def minute_number_to_time_string(minute_num: MinuteOfDay) -> str:
     Convert minute number from start of day into hour:minute am/pm
     string.
 
     Convert minute number from start of day into hour:minute am/pm
     string.
 
+    Args:
+        minute_num: the minute number to convert into a string
+
+    Returns:
+        A string of the format "HH:MM[a|p]" that represents the time
+        that the input minute_num refers to.
+
     >>> minute_number_to_time_string(315)
     ' 5:15a'
 
     >>> minute_number_to_time_string(684)
     '11:24a'
     >>> minute_number_to_time_string(315)
     ' 5:15a'
 
     >>> minute_number_to_time_string(684)
     '11:24a'
-
     """
     hour = minute_num // 60
     minute = minute_num % 60
     """
     hour = minute_num // 60
     minute = minute_num % 60
@@ -818,6 +1047,14 @@ def parse_duration(duration: str, raise_on_error=False) -> int:
     """
     Parse a duration in string form into a delta seconds.
 
     """
     Parse a duration in string form into a delta seconds.
 
+    Args:
+        duration: a string form duration, see examples.
+        raise_on_error: should we raise on invalid input or just
+            return a zero duration?
+
+    Returns:
+        A count of seconds represented by the input string.
+
     >>> parse_duration('15 days, 2 hours')
     1303200
 
     >>> parse_duration('15 days, 2 hours')
     1303200
 
@@ -837,7 +1074,6 @@ def parse_duration(duration: str, raise_on_error=False) -> int:
     Traceback (most recent call last):
     ...
     ValueError: recent is not a valid duration.
     Traceback (most recent call last):
     ...
     ValueError: recent is not a valid duration.
-
     """
     if duration.isdigit():
         return int(duration)
     """
     if duration.isdigit():
         return int(duration)
@@ -869,6 +1105,16 @@ def describe_duration(seconds: int, *, include_seconds=False) -> str:
     """
     Describe a duration represented as a count of seconds nicely.
 
     """
     Describe a duration represented as a count of seconds nicely.
 
+    Args:
+        seconds: the number of seconds in the duration to be represented.
+        include_seconds: should we include or drop the seconds part in
+            the representation?
+
+    .. note::
+
+        Of course if we drop the seconds part the result is not precise.
+        See examples.
+
     >>> describe_duration(182)
     '3 minutes'
 
     >>> describe_duration(182)
     '3 minutes'
 
@@ -880,7 +1126,6 @@ def describe_duration(seconds: int, *, include_seconds=False) -> str:
 
     describe_duration(1303200)
     '15 days, 2 hours'
 
     describe_duration(1303200)
     '15 days, 2 hours'
-
     """
     days = divmod(seconds, constants.SECONDS_PER_DAY)
     hours = divmod(days[1], constants.SECONDS_PER_HOUR)
     """
     days = divmod(seconds, constants.SECONDS_PER_DAY)
     hours = divmod(days[1], constants.SECONDS_PER_HOUR)
@@ -921,10 +1166,22 @@ def describe_timedelta(delta: datetime.timedelta) -> str:
     """
     Describe a duration represented by a timedelta object.
 
     """
     Describe a duration represented by a timedelta object.
 
+    Args:
+        delta: the timedelta object that represents the duration to describe.
+
+    Returns:
+        A string representation of the input duration.
+
+    .. warning::
+
+        Milliseconds are never included in the string representation of
+        durations even through they may be represented by an input
+        `datetime.timedelta`.  Not for use when this level of precision
+        is needed.
+
     >>> d = datetime.timedelta(1, 600)
     >>> describe_timedelta(d)
     '1 day, and 10 minutes'
     >>> d = datetime.timedelta(1, 600)
     >>> describe_timedelta(d)
     '1 day, and 10 minutes'
-
     """
     return describe_duration(int(delta.total_seconds()))  # Note: drops milliseconds
 
     """
     return describe_duration(int(delta.total_seconds()))  # Note: drops milliseconds
 
@@ -933,6 +1190,18 @@ def describe_duration_briefly(seconds: int, *, include_seconds=False) -> str:
     """
     Describe a duration briefly.
 
     """
     Describe a duration briefly.
 
+    Args:
+        seconds: the number of seconds in the duration to describe.
+        include_seconds: should we include seconds in our description or omit?
+
+    Returns:
+        A string describing the duration represented by the input seconds briefly.
+
+    .. note::
+
+        Of course if we drop the seconds part the result is not precise.
+        See examples.
+
     >>> describe_duration_briefly(182)
     '3m'
 
     >>> describe_duration_briefly(182)
     '3m'
 
@@ -962,17 +1231,32 @@ def describe_duration_briefly(seconds: int, *, include_seconds=False) -> str:
     return descr.strip()
 
 
     return descr.strip()
 
 
-def describe_timedelta_briefly(delta: datetime.timedelta) -> str:
+def describe_timedelta_briefly(
+    delta: datetime.timedelta, *, include_seconds=False
+) -> str:
     """
     Describe a duration represented by a timedelta object.
 
     """
     Describe a duration represented by a timedelta object.
 
+    Args:
+        delta: the timedelta to describe briefly
+
+    Returns:
+        A string description of the input timedelta object.
+
+    .. warning::
+
+        Milliseconds are never included in the string representation of
+        durations even through they may be represented by an input
+        `datetime.timedelta`.  Not for use when this level of precision
+        is needed.
+
     >>> d = datetime.timedelta(1, 600)
     >>> describe_timedelta_briefly(d)
     '1d 10m'
     >>> d = datetime.timedelta(1, 600)
     >>> describe_timedelta_briefly(d)
     '1d 10m'
-
     """
     return describe_duration_briefly(
     """
     return describe_duration_briefly(
-        int(delta.total_seconds())
+        int(delta.total_seconds()),
+        include_seconds=include_seconds,
     )  # Note: drops milliseconds
 
 
     )  # Note: drops milliseconds