datetime.replace
Objects are immutable, so replace() is how you change a field. The result is validated like a fresh constructor call — and replace(tzinfo=...) relabels the zone without converting the time.
Common call
dt.replace(second=0, microsecond=0)
Returns
the same datetime truncated to the minute
Replaces
Rebuilding with datetime(dt.year, dt.month, …)
Watch out
replace(tzinfo=X) keeps the wall time; astimezone(X) keeps the instant
dt.replace(year=…, month=…, day=…, hour=…, minute=…, second=…, microsecond=…, tzinfotzinfo — datetime and time only. None makes the result naive.type: tzinfo | None · default: unchanged=…, *, foldfold — Keyword-only, 0 or 1 (3.6+).type: int · default: unchanged=…)
→ date | datetime | time
Demo
Live evaluation
Set the day and the hour of a timestamp. The rest is copied.
Try:
Inputs
whenstran ISO 8601 timestamp
dayintnew day
hourintnew hour
Code
from datetime import datetime datetime.fromisoformat('2026-09-29T14:30+02:00').replace(day=1, hour=0)
Result
datetime.datetime(2026, 9, 1, 0, 30, tzinfo=datetime.timezone(datetime.timedelta(seconds=7200)))
The offset survives replace(day=1, hour=0) because tzinfo was not mentioned. February has no 30th, so the result is rejected with the constructor's message. In the second tab replace moves the instant by two hours (14:30 is now UTC), astimezone keeps it (12:30 UTC).
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| year / month / day | int | no (unchanged) | date and datetime only. The new combination must be a real date. |
| hour / minute / second / microsecond | int | no (unchanged) | datetime and time only. |
| tzinfo | tzinfo | None | no (unchanged) | datetime and time only. None makes the result naive. |
| fold | int | no (unchanged) | Keyword-only, 0 or 1 (3.6+). |
Return value
date | datetime | time — A new object of the same type; the original is unchanged.
Common patterns
Truncate to the minute / hour / day
Zero the smaller fields.
minute = dt.replace(second=0, microsecond=0) midnight = dt.replace(hour=0, minute=0, second=0, microsecond=0)
First day of the month
Always valid.
first = d.replace(day=1)
Same date next year, 29 February safe
Fall back to the 28th when the date does not exist.
try: later = d.replace(year=d.year + 1) except ValueError: later = d.replace(year=d.year + 1, day=28)
Drop the timezone
tzinfo=None gives the naive wall-clock value.
naive = aware.replace(tzinfo=None)
Examples
1. Change one field
from datetime import date
date(2026, 1, 31).replace(day=1)
Returns
datetime.date(2026, 1, 1)2. Truncate a datetime
from datetime import datetime
datetime(2026, 9, 29, 14, 30, 5, 123).replace(second=0, microsecond=0)
Returns
datetime.datetime(2026, 9, 29, 14, 30)3. Attach and remove a zone
from datetime import datetime, UTC
aware = datetime(2026, 9, 29, 14, 30).replace(tzinfo=UTC)
(aware, aware.replace(tzinfo=None))
Returns
(datetime.datetime(2026, 9, 29, 14, 30, tzinfo=datetime.timezone.utc), datetime.datetime(2026, 9, 29, 14, 30))4. time.replace
from datetime import time
time(14, 30).replace(hour=15)
Returns
datetime.time(15, 30)5. Validated like a constructor
from datetime import date
date(2026, 1, 31).replace(month=2)
Returns
ValueError: day is out of range for month6. date has no time fields
from datetime import date
date(2026, 9, 29).replace(hour=1)
Returns
TypeError: replace() got an unexpected keyword argument 'hour'7. copy.replace works too (3.13+)
import copy
from datetime import date
copy.replace(date(2026, 1, 31), day=5)
Returns
datetime.date(2026, 1, 5)Pitfalls
1. Converting zones with replace
replace(tzinfo=...) only relabels: 14:30+02:00 becomes 14:30 UTC, two hours later. Use astimezone to convert.
replace
from datetime import datetime, UTC datetime.fromisoformat('2026-09-29T14:30+02:00').replace(tzinfo=UTC)
datetime.datetime(2026, 9, 29, 14, 30, tzinfo=datetime.timezone.utc)
astimezone
from datetime import datetime, UTC datetime.fromisoformat('2026-09-29T14:30+02:00').astimezone(UTC)
datetime.datetime(2026, 9, 29, 12, 30, tzinfo=datetime.timezone.utc)
2. Adding a year to 29 February
The same day does not exist next year. Decide the rule (28 Feb or 1 Mar) and catch the ValueError.
replace(year=…)
from datetime import date date(2024, 2, 29).replace(year=2025)
ValueError: day is out of range for month
clamp
from datetime import date d = date(2024, 2, 29) try: later = d.replace(year=2025) except ValueError: later = d.replace(year=2025, day=28) later
datetime.date(2025, 2, 28)
3. Expecting replace to modify in place
The result must be assigned; the original is untouched.
ignored result
from datetime import date d = date(2026, 9, 29) d.replace(day=1) d
datetime.date(2026, 9, 29)
assign
from datetime import date d = date(2026, 9, 29) d = d.replace(day=1) d
datetime.date(2026, 9, 1)
When to use
Use it
- Setting or truncating fields
- Attaching a known zone to a naive value, or dropping tzinfo
Reach for something else
- Moving an instant to another zone → astimezone
- Adding durations → + timedelta
Notes
CPython impl
Builds a new object through the normal constructor, so every range check applies
Keywords
Positional arguments are also accepted in field order (d.replace(2027) sets the year), but keywords are clearer
copy.replace
Python 3.13 added copy.replace(obj, **changes), which calls the same __replace__ protocol
FAQ
d.replace(day=1) or d.replace(month=10) — it returns a new date. Assign the result; dates cannot be modified in place.