pd.to_datetime() converts date text, numeric timestamps or separate date components into datetime values. Define the source format and timezone, then inspect invalid inputs before reporting. A valid parse can still use the wrong day/month order or fall outside your reporting period.
Use the top-level pd.to_datetime() function and assign its result to the column. There is no DataFrame.to_datetime() method. These student dates use month/day/year, so 2/1/2020 means February 1. The dtype is datetime rather than text; its resolution can vary across Pandas versions.
import pandas as pd
def student_dates():
return pd.DataFrame({"NAME": ["Ravi", "Raju", "Alex"],
"dt_start": ["1/1/2020", "2/1/2020", "5/1/2020"]})
my_data = student_dates()
my_data["dt_start"] = pd.to_datetime(my_data["dt_start"], format="%m/%d/%Y")
print(my_data)
print(my_data.dtypes)
assert my_data.loc[1, "dt_start"] == pd.Timestamp("2020-02-01")Expected output
NAME dt_start
0 Ravi 2020-01-01
1 Raju 2020-02-01
2 Alex 2020-05-01
NAME str
dt_start datetime64[us]
dtype: objectMatch the source separator in the format. Keeping a known format explicit avoids guessing whether 2-1 means February 1 or January 2. The resulting date values can be sorted and filtered as dates.
my_data = pd.DataFrame({"NAME": ["Ravi", "Raju", "Alex"],
"dt_start": ["1-1-2020", "2-1-2020", "5-1-2020"]})
my_data["dt_start"] = pd.to_datetime(my_data["dt_start"], format="%m-%d-%Y")
print(my_data)Expected output
NAME dt_start
0 Ravi 2020-01-01
1 Raju 2020-02-01
2 Alex 2020-05-01Use the Series .dt accessor for a datetime column. The dates with abbreviated month names are parsed with an explicit format. See Pandas date and time for other date operations.
my_data = pd.DataFrame({"NAME": ["Ravi", "Raju", "Alex"],
"dt_start": ["1-Jan-2020", "2-Feb-2020", "5-Mar-2020"]})
my_data["dt_start"] = pd.to_datetime(my_data["dt_start"], format="%d-%b-%Y")
my_data["year"] = my_data["dt_start"].dt.year
my_data["month"] = my_data["dt_start"].dt.month
my_data["day"] = my_data["dt_start"].dt.day
print(my_data)Expected output
NAME dt_start year month day
0 Ravi 2020-01-01 2020 1 1
1 Raju 2020-02-02 2020 2 2
2 Alex 2020-03-05 2020 3 5With dayfirst=True, Pandas prefers day/month order, but this preference is not strict validation. If the file contract is day/month/year, use format="%d/%m/%Y". A valid but misinterpreted date does not become NaT: both interpretations of 02/01/2020 are valid.
my_data = student_dates()
my_data["dt_start"] = pd.to_datetime(my_data["dt_start"], dayfirst=True)
print(my_data)
strict = pd.to_datetime(student_dates()["dt_start"], format="%d/%m/%Y")
assert my_data["dt_start"].equals(strict)
assert strict.iloc[1] == pd.Timestamp("2020-01-02")Expected output
NAME dt_start
0 Ravi 2020-01-01
1 Raju 2020-01-02
2 Alex 2020-01-05yearfirst=True is also a preference rather than a strict format rule. For these year/month-name/day values, the format expresses the source layout directly. Use a four-digit year when preparing new datasets.
my_data = pd.DataFrame({"NAME": ["Ravi", "Raju", "Alex"],
"dt_start": ["2020-Jan-2", "2021-Feb-5", "2019-Mar-12"]})
preferred = pd.to_datetime(my_data["dt_start"], yearfirst=True)
my_data["dt_start"] = pd.to_datetime(my_data["dt_start"], format="%Y-%b-%d")
assert preferred.equals(my_data["dt_start"])
print(my_data)Expected output
NAME dt_start
0 Ravi 2020-01-02
1 Raju 2021-02-05
2 Alex 2019-03-12A compact date needs a format. Treat identifiers such as 22000105 as date text, rather than accidentally interpreting them as elapsed nanoseconds. Date ranges depend on the chosen datetime resolution.
raw = pd.Series(["22000105", "20200102"])
dates = pd.to_datetime(raw, format="%Y%m%d")
print(dates)
assert dates.iloc[0].year == 2200Expected output
0 2200-01-05
1 2020-01-02
dtype: datetime64[us]The default errors="raise" stops on an invalid date. errors="coerce" gives NaT, the missing datetime value. Current Pandas supports raise and coerce; errors="ignore" is no longer supported. Preserve raw values so that coercion does not hide the reason a record needs review.
raw = pd.Series(["2024-02-29", "2024-02-30", "not a date", None])
try:
pd.to_datetime(raw, format="%Y-%m-%d", errors="raise")
except ValueError:
print("Invalid date detected; inspect source values.")
parsed = pd.to_datetime(raw, format="%Y-%m-%d", errors="coerce")
print(pd.DataFrame({"raw": raw, "parsed": parsed}))
assert parsed.isna().sum() == 3Expected output
Invalid date detected; inspect source values.
raw parsed
0 2024-02-29 2024-02-29
1 2024-02-30 NaT
2 not a date NaT
3 NaN NaTA year such as 1100 is not universally invalid: a second or microsecond resolution can represent dates outside the nanosecond range. To demonstrate a nanosecond limitation explicitly, convert the parsed value with as_unit(). For validation, define your business date range separately.
historical = pd.to_datetime("11000101", format="%Y%m%d")
print("Historical date:", historical.strftime("%Y-%m-%d"))
try:
historical.as_unit("ns")
except pd.errors.OutOfBoundsDatetime:
print("This date cannot be represented at nanosecond resolution.")Expected output
Historical date: 1100-01-01
This date cannot be represented at nanosecond resolution.A scalar gives a Timestamp, whose fields are accessed directly. Use pd.Timestamp.now(tz="UTC") for an explicit timezone. This cell prints a live value; its output will differ each run. Formatting changes the displayed string, not the underlying instant.
present_time = pd.Timestamp.now(tz="UTC")
print("Current UTC:", present_time.isoformat())
print("Display:", present_time.strftime("%d-%b-%Y %H:%M:%S %Z"))
print("Hour:", present_time.hour)
print("Minute:", present_time.minute)
print("Second:", present_time.second)
print("Year:", present_time.year)
print("Month:", present_time.month)
print("Day:", present_time.day)
print("Weekday (Monday=0):", present_time.dayofweek)
print("Day name:", present_time.day_name())Example output (changes each run)
Current UTC: 2026-10-07T06:14:59.758426+00:00
Display: 07-Oct-2026 06:14:59 UTC
Hour: 6
Minute: 14
Second: 59
Year: 2026
Month: 10
Day: 7
Weekday (Monday=0): 2
Day name: WednesdayMatch each component: %m month, %d day, %Y year, %H 24-hour clock, %M minutes and %S seconds. Use strftime() to create a display string after parsing.
present_time = pd.to_datetime("12252020:233445", format="%m%d%Y:%H%M%S")
print("HOUR:", present_time.hour)
print("Minutes:", present_time.minute)
print("Seconds:", present_time.second)
print("Year:", present_time.year)
print("Month:", present_time.month)
print("Day:", present_time.day)
print("dayofweek:", present_time.dayofweek)
print("day_name:", present_time.day_name())
assert present_time == pd.Timestamp("2020-12-25 23:34:45")Expected output
HOUR: 23
Minutes: 34
Seconds: 45
Year: 2020
Month: 12
Day: 25
dayofweek: 4
day_name: FridayPrefer one documented format. format="mixed" infers each item separately and can silently interpret ambiguous dates differently from the source intent. It is appropriate here because the synthetic source declares day-first values. It does not establish that a real ambiguous value is correct.
raw = pd.Series(["2024-01-31", "01/02/2024", "3 Mar 2024"])
parsed = pd.to_datetime(raw, format="mixed", dayfirst=True, errors="raise")
print(parsed)
assert parsed.iloc[1] == pd.Timestamp("2024-02-01")Expected output
0 2024-01-31
1 2024-02-01
2 2024-03-03
dtype: datetime64[us]Use format="ISO8601" for differing ISO layouts. utc=True converts timezone-aware inputs to UTC and avoids mixed-offset problems. If an input has no timezone, utc=True assumes that value already describes UTC; it does not discover the local timezone.
raw = pd.Series(["2024-01-01T10:00:00+05:30", "2024-01-01T04:30:00Z"])
parsed = pd.to_datetime(raw, format="ISO8601", utc=True)
print(parsed)
assert parsed.iloc[0] == parsed.iloc[1]Expected output
0 2024-01-01 04:30:00+00:00
1 2024-01-01 04:30:00+00:00
dtype: datetime64[us, UTC]For a source declared to be India local time, parse the naive clock value, attach Asia/Kolkata, then convert to UTC. Other timezones can have ambiguous or nonexistent daylight-saving times that need an explicit policy. Converting a timezone and attaching a timezone are different operations.
raw = pd.Series(["2024-01-01 10:00:00"])
local_clock = pd.to_datetime(raw, format="%Y-%m-%d %H:%M:%S")
local_time = local_clock.dt.tz_localize("Asia/Kolkata")
print("Source time:")
print(local_time)
print("UTC:")
print(local_time.dt.tz_convert("UTC"))
assert local_time.dt.tz_convert("UTC").iloc[0].hour == 4Expected output
Source time:
0 2024-01-01 10:00:00+05:30
dtype: datetime64[us, Asia/Kolkata]
UTC:
0 2024-01-01 04:30:00+00:00
dtype: datetime64[us, UTC]Seconds and milliseconds since the Unix epoch use different units. Without unit, integers can be interpreted as nanoseconds. Use exact integer values when possible; floating-point timestamps can lose precision. A custom origin is useful for a documented elapsed-day counter.
seconds = pd.to_datetime([1704067200], unit="s", origin="unix", utc=True)
milliseconds = pd.to_datetime([1704067200000], unit="ms", origin="unix", utc=True)
print(seconds)
assert seconds.equals(milliseconds)
print(pd.to_datetime([1, 2, 3], unit="D", origin="1960-01-01"))Expected output
DatetimeIndex(['2024-01-01 00:00:00+00:00'], dtype='datetime64[s, UTC]', freq=None)
DatetimeIndex(['1960-01-02', '1960-01-03', '1960-01-04'], dtype='datetime64[s]', freq=None)Pass year, month and day columns to pd.to_datetime. Column names define the components; format has no effect on DataFrame input. Invalid combinations can be coerced and reviewed.
parts = pd.DataFrame({"year": [2024, 2024], "month": [2, 2], "day": [29, 30]})
parsed = pd.to_datetime(parts, errors="coerce")
print(parsed)
assert parsed.notna().sum() == 1Expected output
0 2024-02-29
1 NaT
dtype: datetime64[us]This synthetic sales import uses day/month/year. Strip surrounding whitespace, distinguish missing input from invalid input, and retain the raw column. A separate business rule accepts only January 2024 dates. See data cleaning for a broader workflow; replacing bad dates with today would invent information.
def orders_data():
return pd.DataFrame({"order_id": [101, 102, 103, 104, 105, 106],
"date_raw": [" 01/01/2024 ", "15/01/2024", "31/02/2024", "", None, "01/01/2099"],
"revenue": [120, 80, 50, 30, 20, 100]})
orders = orders_data()
text = orders["date_raw"].astype("string").str.strip()
missing = text.isna() | text.eq("").fillna(False)
orders["order_date"] = pd.to_datetime(text, format="%d/%m/%Y", errors="coerce")
orders["missing_input"] = missing
orders["invalid_input"] = ~missing & orders["order_date"].isna()
orders["outside_period"] = orders["order_date"].notna() & ~orders["order_date"].between("2024-01-01", "2024-01-31")
orders["needs_review"] = orders[["missing_input", "invalid_input", "outside_period"]].any(axis=1)
print(orders.to_string(index=False))
assert orders["missing_input"].sum() == 2
assert orders["invalid_input"].sum() == 1
assert orders["outside_period"].sum() == 1
assert orders["needs_review"].sum() == 4Expected output
order_id date_raw revenue order_date missing_input invalid_input outside_period needs_review
101 01/01/2024 120 2024-01-01 False False False False
102 15/01/2024 80 2024-01-15 False False False False
103 31/02/2024 50 NaT False True False True
104 30 NaT True False False True
105 NaN 20 NaT True False False True
106 01/01/2099 100 2099-01-01 False False True TrueOnly accepted records contribute to this January revenue report. Keep a review table and reconcile record counts and revenue to the original import. A valid parse alone is insufficient: the year 2099 parses successfully but violates the reporting period. Use resample() for date-based summaries.
accepted = orders.loc[~orders["needs_review"]].copy()
review = orders.loc[orders["needs_review"]].copy()
monthly = accepted.set_index("order_date")["revenue"].resample("MS").sum()
print("January accepted revenue:")
print(monthly)
print("Review IDs:", review["order_id"].tolist())
assert len(accepted) + len(review) == len(orders)
assert accepted["revenue"].sum() == 200
assert accepted["revenue"].sum() + review["revenue"].sum() == orders["revenue"].sum() == 400Expected output
January accepted revenue:
order_date
2024-01-01 200
Freq: MS, Name: revenue, dtype: int64
Review IDs: [103, 104, 105, 106]Month and day swapped: inspect the source date convention and specify format. NaT appeared: compare raw values with isna() and separate blank inputs from invalid dates. .dt fails: ensure the result was assigned to the column and has a datetime dtype. Unexpected 1970 date: inspect numeric unit and origin. Timezone error: define whether the source is UTC, offset-aware or local wall-clock time. Range changed after conversion: check datetime resolution. Parsing formats and display formats solve different problems; strftime returns text.
Parse 05/01/2024, 31/04/2024 and a blank value as day/month/year. Keep raw values and distinguish an invalid nonblank date from missing input. Predict: one valid date, one invalid date and one missing value. Continue with date and time Exercise 3 and Exercise 3-2.
Do not count every NaT as an invalid supplied date: an empty field is a missing input. Validate both flags independently.
raw = pd.Series(["05/01/2024", "31/04/2024", ""], dtype="string")
missing = raw.isna() | raw.str.strip().eq("").fillna(False)
parsed = pd.to_datetime(raw, format="%d/%m/%Y", errors="coerce")
invalid = ~missing & parsed.isna()
print(pd.DataFrame({"raw": raw, "parsed": parsed, "missing": missing, "invalid": invalid}))
assert parsed.notna().sum() == 1
assert missing.sum() == invalid.sum() == 1
assert parsed.iloc[0] == pd.Timestamp("2024-01-05")Expected output
raw parsed missing invalid
0 05/01/2024 2024-01-05 False False
1 31/04/2024 NaT False True
2 NaT True FalseOpen in Google Colab View on GitHub
All synthetic data is included. Run the examples, inspect review flags and try the exercise. Save a copy in Drive before editing.
Continue with Pandas date and time, date_range(), period_range(), strftime() and to_timedelta(). Reference: Pandas to_datetime documentation.
Author & Instructor at plus2net
I write and maintain practical tutorials on Python, PHP, SQL, JavaScript, HTML, jQuery, and web development at plus2net. The tutorials focus on clear explanations, working examples, and code that readers can test and adapt while learning.