31. Dates and Times#
Dates and times exist in a vast number of different applications. At a minimum, log messages have datetime values to show when an event occurred.
Datetime representation and functionality seems to be a problem with straightforward solutions, but underneath the surface, implementing datetime has been surprisingly problematic to get correct.
One problem stems from how we represent dates in our daily lives -
October 5, 2005
Oct. 5, 2005
5 Oct 2005
5/10/2005
10/5/2005
20051005
So many formats? Which comes first - the day or the month? Do we start counting at 0 or 1? What about time zones? Does Arizona still not follow daylight savings time? You can not confuse coyotes. When did the cows in Indiana start following daylight savings time? Leap years occur every four years, except at the century mark, unless divisible by 400. What about leap seconds? When are those applied? When was the last one applied? Western civilization follows the Gregorian calendar, but what about the dates before its introduction in 1582? What do other societies use?
Most computer systems track datetime based upon Unix Epoch, the number of seconds since January 1st, 1970 at 00:00:00 UTC. Some implementations will also use finer-grained representations such as milliseconds and nanoseconds. Due to the size of most computer systems when this approach was adopted, systems represented the DateTime type with a signed 32-bit integer, limiting the acceptable date range from December 13, 1901 to January 19, 2038. Using 64-bit numbers in modern systems means these limits are no longer a practical concern.
Reference: View current Epoch time and perform conversions: https://currentmillis.com/
For string representations of dates and times, you should strive to follow ISO 8601. The format expresses dates and times from the largest to the smallest unit. Python’s datetime module uses the following format for ISO (though several alternatives exist): 2005-10-05T22:40:32.123456+00:00 (YYYY-MM-DDTHH:mm:SS.sssuuu+00:00) where
YYYY: 4-digit year
MM: 2-digit month from 01 to 12
DD: 2-digit day from 01 to 31
T: separator of the date and time portions. Time portions can also be separated by :
HH: 2-digit hours from 00 to 23
mm: 2-digit minutes from 00 to 59
SS: 2-digit seconds from 00 to 59
sssuuu: 3-digits for milliseconds and 3-digits for microseconds.
+00:00: offset from the GMT/UTC timezone. This value may be negative. Expressed in hours and then minutes.
Advantages of the ISO 8601 Representation:
Standardization: ISO 8601 provides a consistent and unambiguous way to represent dates and times, which is crucial for communication and data interchange across different systems and cultures.
Readability: The format is intuitive and easy to read for humans, with components arranged from the largest to smallest unit (year, month, day, hour, minute, second).
Sorting: Dates and times represented in ISO 8601 format can be easily sorted alphabetically, which simplifies data organization and retrieval.
Compatibility: Most programming languages and database systems support parsing and formatting ISO 8601 dates and times, making it widely adopted in software development.
Flexibility: ISO 8601 allows for optional components such as milliseconds, time zone offsets, and durations, providing flexibility to accommodate various use cases while maintaining consistency.
31.1. The datetime
Module#
Python’s datetime
module provides functionality to support dates and times. The module defines four primary classes:
date
: represents just datestime
: represents time, but no dates.datetime
: represents the date and time togethertimedelta
: used for the difference/interval between dates and/or times
1import datetime as dt
1# run a command to see what's available within the datetime/dt module
You should have seen at least the four main classes listed in the output. Ideally, you can see the online information available from within Python on these classes.
31.1.1. date
#
Several different methods exist to create a date object:
1from datetime import date
2
3# From the current date
4today = date.today()
5print(today)
6
7# From a specific year, month and date
8someday = date(2005,10,5)
9print(someday)
10
11# From Unix epoch time value
12anotherday = date.fromtimestamp(1656549504)
13print(anotherday)
14
15# From an isoformat string
16aday = date.fromisoformat("2021-12-02")
17print(aday)
18
19# from the number of days since January 1st, 0001.
20oday = date.fromordinal(738340)
21print(oday)
22
23# Show the min and max values possibles for date
24print("Minimum date:",date.min)
25print("Maximum date:",date.max)
2024-04-28
2005-10-05
2022-06-29
2021-12-02
2022-07-04
Minimum date: 0001-01-01
Maximum date: 9999-12-31
The date
object supports the comparison operators: ==, !=, <, <=, >=, >
You can subtract two date objects to get a timedelta
object representing the difference between dates.
1x = today-oday
2print(type(x))
3print(x)
<class 'datetime.timedelta'>
664 days, 0:00:00
31.1.2. time
#
The time
object represents time. As with the date
object, we have several ways to construct a time
object.
1from datetime import time
2
3# from the current datetime, extract the time portion
4#(can do this with any datetime object)
5t = dt.datetime.now().time()
6print(type(t))
7print("Current time:",t)
8
9# from a specific time
10spec_time = time(23,4,30)
11print(spec_time)
12
13# from a specific time, using microseconds
14spec_time = time(11,5,24,123456)
15print(spec_time)
16
17# display the minimum and maximum values
18print("Minimum time:", time.min)
19print("Maximum time:", time.max)
20
21# Access the time attributes
22print("hour:", t.hour)
23print("minute:", t.minute)
24print("second:", t.second)
25print("microsecond:", t.microsecond)
<class 'datetime.time'>
Current time: 21:45:47.245874
23:04:30
11:05:24.123456
Minimum time: 00:00:00
Maximum time: 23:59:59.999999
hour: 21
minute: 45
second: 47
microsecond: 245874
The time
object supports the comparison operators: ==, !=, <, <=, >=, >
The time
object does not support the subtraction operator.
The time
object does support time zones, covered in the next section.
31.1.3. datetime
#
The datetime
class inherits from the date
class, adding the capabilities to represent time.
1# main constructor from
2a = dt.datetime(2014, 11, 28, 17, 10, 30, 342380)
3print("year =", a.year)
4print("month =", a.month)
5print("hour =", a.hour)
6print("minute =", a.minute)
7print("timestamp =", a.timestamp())
8print()
9
10# create datetime from the current date and time
11now = dt.datetime.now()
12utcnow = dt.datetime.utcnow()
13print(now.isoformat())
14print(utcnow.isoformat())
15print()
16
17# Construct date from an iso formatted string
18iso = dt.datetime.fromisoformat("2022-06-30T20:19:56+05:00")
19print(iso.isoformat())
year = 2014
month = 11
hour = 17
minute = 10
timestamp = 1417212630.34238
2024-04-28T21:45:47.250263
2024-04-29T01:45:47.250301
2022-06-30T20:19:56+05:00
/var/folders/f0/69wncpqd02s3j1r_z5sfddd00000gq/T/ipykernel_25203/2957373874.py:12: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
utcnow = dt.datetime.utcnow()
If you notice in the above code, the datetime objects do not have an associated timezone unless you explicitly specify one. While datetime.now() returns the current time in the local timezone, the datetime object is unaware of that timezone by default. Python considers these two types of datetime objects as aware and naive. Aware datetime objects include timezone information. Naive objects do not have timezone information. Aware objects can unambiguously define a specific moment in time, and the interpreter can compute a difference from another aware datetime object. A naive datetime object has neither of those two properties - the program and its logic determine the exact meaning of time. The lesson - always use timezones when you work with datetime objects.
The following code returns the local timezone as a datetime.timezone
object.
1LOCAL_TIMEZONE = dt.datetime.now().astimezone().tzinfo
2print("Time Zone:",LOCAL_TIMEZONE)
3print("Type:",type(LOCAL_TIMEZONE))
Time Zone: EDT
Type: <class 'datetime.timezone'>
A recommended approach to creating UTC current datetime objects:
1utc = dt.datetime.now(dt.timezone.utc)
2print(utc)
2024-04-29 01:45:47.256862+00:00
The next block defines a function that converts a datetime to a specific timezone.
1def utc_to_timezone(utc_dt,time_zone=None):
2 """Converts a datetime assumed to be in UTC timezone to a specific time zone.
3 If the timezone is not defined, then the local timezone is assumed."""
4 return utc_dt.replace(tzinfo=dt.timezone.utc).astimezone(tz=time_zone)
1# get the current time and convert to the local timezone.
2print(utc_to_timezone(dt.datetime.utcnow()))
2024-04-28 21:45:47.262597-04:00
/var/folders/f0/69wncpqd02s3j1r_z5sfddd00000gq/T/ipykernel_25203/1863282035.py:2: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
print(utc_to_timezone(dt.datetime.utcnow()))
In the following code, we use the zoneinfo
package (available in Python versions 3.9 and later).
With this module, we can instantiate timezone objects based upon a time zone name - the available ones for Python can be found through zoneinfo.available_timezones()
.
1import zoneinfo
2
3# produces a set of timezone names
4zones = zoneinfo.available_timezones()
5
6# convert the curent datetime to Hong Kong's timezone
7hk_tz = zoneinfo.ZoneInfo('HongKong')
8print(utc_to_timezone(dt.datetime.utcnow(),hk_tz))
2024-04-29 09:45:47.270830+08:00
/var/folders/f0/69wncpqd02s3j1r_z5sfddd00000gq/T/ipykernel_25203/353357187.py:8: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
print(utc_to_timezone(dt.datetime.utcnow(),hk_tz))
31.2. DateTime Arithmetic#
Python supports some DateTime arithmetic operations. For example, we can subtract two dates to find the period (interval) between them. However, we cannot add two datetime objects. If you reflect on the addition, what does it actually mean to add two dates? What is July 17, 2022 plus August 15, 2022? We can add and subtract periods (intervals) from datetime objects. Python uses the timedelta
to represent periods.
1t4 = dt.datetime(year = 2019, month = 7, day = 12, hour = 7, minute = 9, second = 33)
2t5 = dt.datetime(year = 2019, month = 6, day = 10, hour = 5, minute = 55, second = 13)
3difference = t4 - t5
4print("difference =", difference)
5
6print("Tomorrow: ", dt.datetime.now(dt.timezone.utc) + dt.timedelta(days=1))
7print("Tomorrow: ", dt.datetime.now(dt.timezone.utc) + dt.timedelta(hours=24))
difference = 32 days, 1:14:20
Tomorrow: 2024-04-30 01:45:47.273919+00:00
Tomorrow: 2024-04-30 01:45:47.273947+00:00
31.3. Formatting and Parsing#
As with many other programming languages, Python allows you to define custom formats to display datetime objects and parse strings for those formats.
Use the strftime
function to convert datetime into a specific formatted string. The format string defines a series of format codes that correspond to the different parts of a date as well as different ways to display that part.
1utcnow = dt.datetime.utcnow().replace(tzinfo=dt.timezone.utc)
2print(utcnow.strftime("%m/%d/%Y, %H:%M:%S %Z"))
04/29/2024, 01:45:47 UTC
/var/folders/f0/69wncpqd02s3j1r_z5sfddd00000gq/T/ipykernel_25203/1183752382.py:1: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
utcnow = dt.datetime.utcnow().replace(tzinfo=dt.timezone.utc)
Directive |
Meaning |
Example |
---|---|---|
|
Year without century as a zero-padded decimal number. |
00, 01, …, 99 |
|
Year with century as a decimal number. |
0001, 0002, …, 2013, 2014, …, 9998, 9999 |
|
Month as locale’s abbreviated name. |
Jan, Feb, …, Dec
(en_US);
Jan, Feb, …, Dez
(de_DE)
|
|
Month as locale’s full name. |
January, February,
…, December (en_US);
Januar, Februar, …,
Dezember (de_DE)
|
|
Month as a zero-padded decimal number. |
01, 02, …, 12 |
|
Day of the month as a zero-padded decimal number. |
01, 02, …, 31 |
|
Hour (24-hour clock) as a zero-padded decimal number. |
00, 01, …, 23 |
|
Hour (12-hour clock) as a zero-padded decimal number. |
01, 02, …, 12 |
|
Locale’s equivalent of either AM or PM. |
AM, PM (en_US);
am, pm (de_DE)
|
|
Minute as a zero-padded decimal number. |
00, 01, …, 59 |
|
Second as a zero-padded decimal number. |
00, 01, …, 59 |
|
UTC offset in the form
|
(empty), +0000, -0400, +1030, +063415, -030712.345216 |
|
Time zone name (empty string if the object is naive). |
(empty), UTC, GMT |
|
A literal |
% |
The above table only shows the more commonly used formats. Python adopted these codes from the 1989 C Standard.
Similarly, we can use strptime
to parse datetime objects from a string.
1str = '07/01/2022, 12:20:04 UTC'
2mydate = dt.datetime.strptime(str,"%m/%d/%Y, %H:%M:%S %Z")
3print(mydate, mydate.tzinfo) ## Notice that mydate is not an aware datetime
2022-07-01 12:20:04 None
31.4. Pendulum#
Several other developers and groups have written alternate libraries for datetime functionality within Python
Pendulum is a drop-in replacement for the datetime library. Besides a cleaner API, the Pendulum library creates timezone-aware objects by default.
1# Tests to see if we are running in the Google Colaboratory environment
2# If so, ensure pendulum is installed
3try:
4 import google.colab
5 %pip install pendulum
6except:
7 pass
1import pendulum
2now = pendulum.now()
3print(now)
4print(now.timezone)
5print(pendulum.now('UTC'))
2024-04-28 21:45:47.295070-04:00
America/New_York
2024-04-29 01:45:47.295169+00:00
Using the method resolution order function mro()
, we can see the inheritance hierarchy and notice that Pendulum’s classes extend Python’s datetime
objects.
1print(now.__class__.mro())
2print()
3print(now.timezone.__class__.mro())
[<class 'pendulum.datetime.DateTime'>, <class 'datetime.datetime'>, <class 'pendulum.date.Date'>, <class 'pendulum.mixins.default.FormattableMixin'>, <class 'datetime.date'>, <class 'object'>]
[<class 'pendulum.tz.timezone.Timezone'>, <class 'zoneinfo.ZoneInfo'>, <class 'datetime.tzinfo'>, <class 'pendulum.tz.timezone.PendulumTimezone'>, <class 'abc.ABC'>, <class 'object'>]
1# Converting to iso8601 format
2print(now.to_iso8601_string())
3
4# Date arithimetic
5print(now.add(days=2))
6
7# Definining a duration (length of time)
8dur = pendulum.duration(days=10, hours=5)
9print(dur)
10print("dur.weeks:",dur.weeks)
11print("dur.days:",dur.days)
12print("dur.hours:",dur.hours)
13print(dur.in_hours())
14print(dur.in_words(locale="zh"))
15
16#Using the duration in datetime arithmetic
17print(now - dur)
18
19# A period is the difference between 2 datetime instances.
20# it maintains a reference to those datetimes
21period = now - now.subtract(days=3)
22print(period)
23print(period.in_seconds())
24print(period.in_days())
25print(now - period - period)
2024-04-28T21:45:47.295070-04:00
2024-04-30 21:45:47.295070-04:00
1 week 3 days 5 hours
dur.weeks: 1
dur.days: 10
dur.hours: 5
245
1周 3天 5小时
2024-04-18 16:45:47.295070-04:00
<Interval [2024-04-25 21:45:47.295070-04:00 -> 2024-04-28 21:45:47.295070-04:00]>
259200
3
2024-04-22 21:45:47.295070-04:00
Pendulum also supports an alternative format to convert date time values to objects. Many other languages and libraries use this formatting approach.
Further details and available tokens
1now = pendulum.now()
2print(now)
3now.format('YYYY-MM-DD HH:mm:ssZ')
2024-04-28 21:45:47.303792-04:00
'2024-04-28 21:45:47-04:00'
1parsed = pendulum.from_format('2022-07-01 08:44:39-04:00','YYYY-MM-DD HH:mm:ssZ')
2print(parsed)
2022-07-01 08:44:39-04:00
Arrow is another popular alternative to Python’s datetime module.
31.5. Best Practices#
Generally speaking, you should process datetime objects with the UTC timezone and then display datetime values to users in their local timezone (and show that timezone). For web applications, the timezone conversion should occur within the client’s browser.
Storing datetime objects can be a bit more complicated. UTC with the timezone offset is the most appropriate for dates that have already occurred. For datetime values in the future, the situation is not necessarily unambiguous - the problem arises when changes are made to timezone rules after a datetime has been saved. Saving datetimes for the future You’ll want to store the timezone name in addition to the datetime in UTC/offset. By having the timezone name, you can check if the offset has subsequently changed. Provided you can map the event location back to a timezone, the event location suffices.
For data science applications, most datetime values will have occurred in the past so storing those dates with the relevant timezone is appropriate. That relevant timezone is where the record occurred (or where the given user was). However, you will also want to create additional features for either visualization or machine learning purposes. This includes -
day of the week
weekend flag
holiday flag
local date and time
local hour
time before an event
And last but certainly not least, use a library to process dates and times.
31.6. Studying the Python Source Code#
One way to improve your program is to study other people’s source code. The difficulty with this approach, though, is finding good examples. The various application programming interfaces (APIs) for programming languages are good places to look for examples. Typically these APIs have been implemented by multiple developers and undergone a rigorous review process to be included in the language’s distribution. In the following code block, we have copied portions of Python’s datetime module to examine how the class implementation. Within this class, the developers made many noteworthy choices.
The
datetime
class is immutable. The class uses__new__
not__init__
to initialize the object’s state.The class validates parameters to
__new__
. The code checks that the individual data fields are within the expected bounds (e.g., the month between 1 and 12). The call to_index()
enforces that the parameters are integers.The days_in_months array has been offset by 1. In other words, the array has a dummy value at index 0. This offset avoids having to subtract one for every access to the array.
The function
_days_in_month
has an assertion that used as a sanity check to ensure the month value is in the correct range. Based on the code’s logic, this check does occur before any call to_days_in_month
. The assertion provides an additional safety net.The class lazily initializes the hash value. By lazily initialized, the class does not calculate the hash value until the first time it is requested. After that, the computed value is stored and used for subsequent calls.
The class defines property methods to access the various attributes
The class defines static constructors. These constructors call each other. This pattern reduces the amount of code needed to support multiple ways to define an object. This pattern also allows for constructors with different arguments to be used.
The developers used docstrings for the class itself and then for the different methods within the class.
The
__sub__
method checks the type of arguments passed and alters its behavior based on those types.
Source: python/cpython
1from operator import index as _index
2class date:
3 """Concrete date type.
4 Constructors:
5 __new__()
6 fromtimestamp()
7 today()
8 fromordinal()
9 Operators:
10 __repr__, __str__
11 __eq__, __le__, __lt__, __ge__, __gt__, __hash__
12 __add__, __radd__, __sub__ (add/radd only with timedelta arg)
13 Methods:
14 timetuple(), toordinal(), weekday(), isoweekday(), isocalendar(), isoformat()
15 ctime(), strftime()
16 Properties (readonly):
17 year, month, day
18 """
19 __slots__ = '_year', '_month', '_day', '_hashcode'
20
21 def __new__(cls, year, month=None, day=None):
22 """Constructor.
23 Arguments:
24 year, month, day (required, base 1)
25 """
26 ### removed "pickle code" ...
27 year, month, day = _check_date_fields(year, month, day)
28 self = object.__new__(cls)
29 self._year = year
30 self._month = month
31 self._day = day
32 self._hashcode = -1
33 return self
34
35 # Additional constructors
36 @classmethod
37 def fromtimestamp(cls, t):
38 "Construct a date from a POSIX timestamp (like time.time())."
39 y, m, d, hh, mm, ss, weekday, jday, dst = _time.localtime(t)
40 return cls(y, m, d)
41
42 @classmethod
43 def today(cls):
44 "Construct a date from time.time()."
45 t = _time.time()
46 return cls.fromtimestamp(t)
47
48
49 def isoformat(self):
50 """Return the date formatted according to ISO. This is 'YYYY-MM-DD'."""
51 return "%04d-%02d-%02d" % (self._year, self._month, self._day)
52
53 # Read-only field accessors
54 @property
55 def year(self):
56 """year (1-9999)"""
57 return self._year
58
59 def __sub__(self, other):
60 """Subtract two dates, or a date and a timedelta."""
61 if isinstance(other, timedelta):
62 return self + timedelta(-other.days)
63 if isinstance(other, date):
64 days1 = self.toordinal()
65 days2 = other.toordinal()
66 return timedelta(days1 - days2)
67 return NotImplemented
68
69 def __hash__(self):
70 "Hash."
71 if self._hashcode == -1:
72 self._hashcode = hash(self._getstate())
73 return self._hashcode
74
75 def _getstate(self):
76 yhi, ylo = divmod(self._year, 256)
77 return bytes([yhi, ylo, self._month, self._day])
78
79def _check_date_fields(year, month, day):
80 year = _index(year)
81 month = _index(month)
82 day = _index(day)
83 if not MINYEAR <= year <= MAXYEAR:
84 raise ValueError('year must be in %d..%d' % (MINYEAR, MAXYEAR), year)
85 if not 1 <= month <= 12:
86 raise ValueError('month must be in 1..12', month)
87 dim = _days_in_month(year, month)
88 if not 1 <= day <= dim:
89 raise ValueError('day must be in 1..%d' % dim, day)
90
91def _is_leap(year):
92 "year -> 1 if leap year, else 0."
93 return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
94
95# -1 is a placeholder for indexing purposes.
96_DAYS_IN_MONTH = [-1, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
97
98def _days_in_month(year, month):
99 "year, month -> number of days in that month in that year."
100 assert 1 <= month <= 12, month
101 if month == 2 and _is_leap(year):
102 return 29
103 return _DAYS_IN_MONTH[month]
31.7. Datetime Myths#
Processing dates correctly is much easier said than done. Many “gotchas” exist and relying upon a well implemented, test and used library is critical. The following two pages should many of the “myths” programmers naively believe when looking at dates and times:
31.8. Suggested LLM Prompts#
Provide a detailed description of how dates are typically represented in computers. Discuss the difficulties of date and time such as the complexities with different timezones and calendaring systems.
Provide a detailed explanation and examples of how to convert different string formats representing dates and times into datetime objects in Python. Cover various string formats, including ISO, Unix timestamps, and custom formats. Discuss the use of the
strptime()
method and its counterpartstrftime()
.Explain the ISO-8601 representation for dates and times. Show example strings. Break down the different components of the representation. What are the advantages of this representation?
Explain how to perform arithmetic operations on datetime objects, such as adding or subtracting time intervals. Provide examples of adding or subtracting days, hours, minutes, and seconds. Discuss the
timedelta
class and its usage.Demonstrate how to format datetime objects into human-readable strings using the
strftime()
method. Provide a comprehensive list of format codes and examples of their usage. Explain how to handle different locales and time zones.Explain the concept of time zones and how to handle them in Python’s datetime module. Discuss the
tzinfo
class and its subclasses. Provide examples of converting datetime objects between different time zones and handling daylight saving time. Include discussion of timezone aware datetime object.sIntroduce the pendulum library, which provides a more user-friendly and intuitive interface for working with dates and times in Python. Provide examples of how to use pendulum for common datetime operations, such as parsing, formatting, and manipulating datetime objects.
Discuss the importance of handling datetime objects correctly in web applications. Provide examples of common use cases, such as storing and retrieving dates and times from databases, displaying datetime information in different time zones, and validating user input for datetime fields.
31.9. Review Questions#
What is the difference between the
datetime
anddate
classes in Python’sdatetime
module?How would you create a
datetime
object representing the current date and time?Explain the concept of time zones in Python’s
datetime
module and how to handle them.Explain the difference between aware and naive
datetime
objects in Python.Explain the difference between the
strftime()
andstrptime()
methods in thedatetime
module.How would you calculate the number of days between two
date
objects?What is the purpose of the
pendulum
library in Python, and how does it differ from the built-indatetime
module?Compare and contrast the different representations of a date and time value by using a Unix Epoch value versus ISO 8601?
31.10. Exercises#
Parse Date: Implement the following function -
def parse_date(date_str): """ Parse a date string in the format "YYYY-MM-DD" and return the corresponding datetime.date object. Parameters: date_str (str): Date string in the format "YYYY-MM-DD". Returns: datetime.date: Corresponding date object. Raises: ValueError: invalid date format """
Calculate Business Days: Implement the following function -
def count_business_days(start_date, end_date): """ Calculate the number of business days (excluding weekends) between two given dates. Parameters: start_date (datetime.date): The start date. end_date (datetime.date): The end date. Returns: int: The number of business days between start_date and end_date (inclusive). """
Calculate the Number of Years until Retirement: Write a Python script that prompts the user to enter their date of birth and retirement age. Then, calculate the number of years until they reach retirement age (67 years) based on the current date.
To provide for better code reuse and abstraction, you solution needs to implement these two functions:
def get_date_input(prompt): """ Prompt the user to enter a date in the format "YYYY-MM-DD" and return the corresponding date object. Parameters: prompt (str): The prompt message to display. Returns: datetime.date: Date object entered by the user. """ def calculate_years_until_retirement(date_of_birth, retirement_age=67): """ Calculate the number of years until retirement based on the date of birth and retirement age. Parameters: date_of_birth (datetime.date): Date of birth. retirement_age (int): Retirement age. Default is 67. Returns: int: Number of years until retirement. """
Determine the Time Remaining until Bond Maturity: Write a Python function that takes the issue date and maturity date of a bond and determines the time remaining until maturity based on the current date. The function should two values where the first is the percentage of time passed for maturity and the second is the number of years remaining.
def remaining_time_to_maturity(issue_date, maturity_date): """ Calculate the time remaining until maturity based on the issue date and maturity date of a bond. Parameters: issue_date (datetime.date): The issue date of the bond. maturity_date (datetime.date): The maturity date of the bond. Returns: tuple: A tuple containing two values: - Percentage of time passed for maturity - Number of years remaining until maturity """