Module 13 · Core

Dates, times, and durations

Parse dates explicitly and use calendar-aware operations.

Before you start: You can convert types and select rows by conditions.

Python loads from this site on your first run.

Each run starts with fresh data. Download any exported files before another run. Your written answers and code drafts are saved on this device; export your work to keep a backup.

M13C01 · Explore · predict · practice

Turn date text into dates

A date-looking string is still text until it is parsed. to_datetime() converts it to a date/time type. An explicit format removes ambiguity; impossible dates can be marked as NaT, pandas missing date value.

Read the example

source = pd.Series(["2026-09-01", "2026-09-03", "2026-02-30"], dtype="string")
result = pd.to_datetime(source, format="%Y-%m-%d", errors="coerce")
print(result)
print("Unparsed dates:", result.isna().sum())
Verified example output · pandas 2.2.3
0   2026-09-01
1   2026-09-03
2          NaT
dtype: datetime64[ns]
Unparsed dates: 1

Watch for this. Inspect parsing failures; errors="coerce" does not repair the source date.

Your turn

Parse ["01/09/2026", "03/09/2026", "30/02/2026"] as day/month/year. Count invalid dates.

For the code check, keep these result variable names: result. Different valid approaches are welcome.

Show a hint

Use the explicit format "%d/%m/%Y".

Show one worked solution
source = pd.Series(["01/09/2026", "03/09/2026", "30/02/2026"], dtype="string")
result = pd.to_datetime(source, format="%d/%m/%Y", errors="coerce")
print(result)
print("Unparsed dates:", result.isna().sum())

The first two dates are September 1 and September 3. February 30 remains invalid.

Compare your explanations
  1. Read. Four-digit year, numeric month, and day of month.
  2. Predict. It becomes NaT because it is not a valid calendar date.
  3. Explain. It could mean January 2 or February 1, depending on the intended date convention.

One quick check

What is NaT?

What you should be able to explain. Parse known date formats explicitly.

M13C02 · Explore · predict · practice

Extract useful calendar parts

After parsing dates, the .dt accessor exposes calendar parts such as year, month, and weekday. These features are useful for grouping and checking patterns. Do not try to use .dt on unparsed strings.

Read the example

df = students()
df["Join_Date"] = pd.to_datetime(df["Join_Date"], format="%Y-%m-%d")
result = df[["Student_ID", "Join_Date"]].copy()
result["Month"] = result["Join_Date"].dt.month
result["Weekday"] = result["Join_Date"].dt.day_name()
print(result.head(4))
Verified example output · pandas 2.2.3
  Student_ID  Join_Date  Month    Weekday
0       S001 2026-09-01      9    Tuesday
1       S002 2026-09-02      9  Wednesday
2       S003 2026-09-03      9   Thursday
3       S004 2026-09-04      9     Friday

Watch for this. A weekday pattern in twelve fictional records is a practice result, not evidence about real student behavior.

Your turn

Add Year and Day columns for the same join dates. Store only the first three rows in result, with columns Student_ID, Year, Month, and Day, then print result.

For the code check, keep these result variable names: result. Different valid approaches are welcome.

Show a hint

Use .dt.year, .dt.month, and .dt.day.

Show one worked solution
df = students()
dates = pd.to_datetime(df["Join_Date"], format="%Y-%m-%d")
result = pd.DataFrame({"Student_ID": df["Student_ID"], "Year": dates.dt.year, "Month": dates.dt.month, "Day": dates.dt.day}).head(3)
print(result)

The years are 2026, the months are 9, and the day values are 1, 2, and 3.

Compare your explanations
  1. Read. The column has been converted from text into a datetime type.
  2. Predict. Yes, all join dates are in September, so every Month is 9.
  3. Explain. September from different years would otherwise be grouped together.

One quick check

Which accessor provides calendar components?

What you should be able to explain. Create date features after parsing the date type.

M13C03 · Explore · predict · practice

Filter an explicit date interval

Date comparisons let you select a time interval. between() includes both boundaries by default; naming the inclusive rule makes your intent clearer. Here every date is a midnight date with no separate time-of-day information.

Read the example

df = students()
df["Join_Date"] = pd.to_datetime(df["Join_Date"], format="%Y-%m-%d")
mask = df["Join_Date"].between("2026-09-03", "2026-09-06", inclusive="both")
result = df.loc[mask, ["Student_ID", "Join_Date"]]
print(result)
Verified example output · pandas 2.2.3
  Student_ID  Join_Date
2       S003 2026-09-03
3       S004 2026-09-04
4       S005 2026-09-05
5       S006 2026-09-06

Watch for this. With timestamps containing hours, an end date at midnight is not the whole final day; a next-day exclusive bound is often clearer.

Your turn

Select dates on or after September 3 and strictly before September 6 using two comparisons.

For the code check, keep these result variable names: result. Different valid approaches are welcome.

Show a hint

Combine >= and < with & and parentheses.

Show one worked solution
df = students()
df["Join_Date"] = pd.to_datetime(df["Join_Date"], format="%Y-%m-%d")
mask = (df["Join_Date"] >= "2026-09-03") & (df["Join_Date"] < "2026-09-06")
result = df.loc[mask, ["Student_ID", "Join_Date"]]
print(result)

S003, S004, and S005 remain. The exclusive upper boundary removes S006.

Compare your explanations
  1. Read. Yes; inclusive="both" includes both endpoints.
  2. Predict. S003, S004, S005, and S006.
  3. Explain. An included or excluded endpoint changes which records contribute to the analysis.

One quick check

Which operator excludes the upper boundary?

What you should be able to explain. State the start and end rules of every date filter.

M13C04 · Explore · predict · practice

Calculate elapsed time with a fixed reference

Subtracting dates produces timedeltas, which represent durations. .dt.days extracts whole days from those durations. A fixed reference date makes a classroom result reproducible when the file is opened later.

Read the example

df = students()
joined = pd.to_datetime(df["Join_Date"], format="%Y-%m-%d")
reference = pd.Timestamp("2026-10-01")
result = pd.DataFrame({"Student_ID": df["Student_ID"], "Days_enrolled": (reference - joined).dt.days})
print(result.head(4))
Verified example output · pandas 2.2.3
  Student_ID  Days_enrolled
0       S001             30
1       S002             29
2       S003             28
3       S004             27

Watch for this. For real timestamps, keep time zones consistent before subtracting them.

Your turn

Change the reference to October 8 and select students with at least 30 elapsed days enrolled.

For the code check, keep these result variable names: result. Different valid approaches are welcome.

Show a hint

Recalculate the duration, then filter Days_enrolled >= 30.

Show one worked solution
df = students()
joined = pd.to_datetime(df["Join_Date"], format="%Y-%m-%d")
reference = pd.Timestamp("2026-10-08")
df["Days_enrolled"] = (reference - joined).dt.days
result = df.loc[df["Days_enrolled"] >= 30, ["Student_ID", "Days_enrolled"]]
print(result)

S001 through S008 meet the rule. S008 has exactly 30 elapsed days.

Compare your explanations
  1. Read. A Series of time durations, called timedeltas.
  2. Predict. 30 days from September 1 to October 1.
  3. Explain. Everyone can reproduce the same answer, regardless of when they run the lesson.

One quick check

Does elapsed time count both the start and end date as full extra days?

What you should be able to explain. Use timedelta arithmetic instead of subtracting date strings.

Further detail: official documentation for this topic.