🐍 Ngày 38 - Python 365 ngày: Làm việc với thời gian (datetime)
🎯 Mục tiêu
Tìm hiểu cách sử dụng mô-đun datetime trong Python để xử lý thời gian: ngày, giờ, tính toán khoảng cách giữa hai thời điểm, v.v.
📚 Kiến thức cần biết
Python cung cấp mô-đun datetime để làm việc với:
- Ngày (
date) - Thời gian (
time) - Ngày giờ (
datetime) - Khoảng thời gian (
timedelta)
📌 Một số ví dụ quan trọng
📅 Lấy thời gian hiện tại
from datetime import datetime
now = datetime.now()
print("⏰ Thời gian hiện tại:", now)
print("📅 Ngày:", now.date())
print("🕒 Giờ:", now.time())
🗓️ Tạo một đối tượng datetime cụ thể
from datetime import datetime
new_year = datetime(2025, 1, 1)
print("🎉 Năm mới:", new_year)
➖ Tính khoảng cách giữa hai thời điểm
from datetime import datetime
today = datetime.now()
new_year = datetime(2025, 1, 1)
delta = new_year - today
print("⏳ Còn lại:", delta.days, "ngày")
🎯 Bài tập thực hành
Bài 1: Tính tuổi
Yêu cầu:
Nhập ngày sinh của bạn và in ra tuổi hiện tại.
Gợi ý:
- Sử dụng
input()vàdatetime.strptime()để chuyển đổi ngày tháng nhập vào. - Tính hiệu giữa ngày hiện tại và ngày sinh.
Ví dụ kết quả:
from datetime import datetime, date
def tinh_tuoi(ngay_sinh_str):
ngay_sinh = datetime.strptime(ngay_sinh_str, "%d/%m/%Y").date()
hom_nay = date.today()
tuoi = hom_nay.year - ngay_sinh.year - ((hom_nay.month, hom_nay.day) < (ngay_sinh.month, ngay_sinh.day))
return tuoi
print("Tuổi của bạn là:", tinh_tuoi("10/10/2000"))
✅ Kết quả minh họa:
Tuổi của bạn là: 24
Bài 2: Đếm ngược đến Tết
Yêu cầu:
Tính số ngày còn lại đến ngày Tết Nguyên Đán 2025 (29/01/2025).
Gợi ý:
- Sử dụng
datetime.strptime()để lấy ngày Tết. - Tính hiệu giữa ngày hiện tại và ngày Tết.
Ví dụ kết quả:
def dem_nguoc_tet(tet_str="29/01/2025"):
ngay_tet = datetime.strptime(tet_str, "%d/%m/%Y").date()
hom_nay = date.today()
so_ngay_con_lai = (ngay_tet - hom_nay).days
return so_ngay_con_lai
print("Số ngày còn lại đến Tết: ", dem_nguoc_tet())
✅ Kết quả minh họa:
Số ngày còn lại đến Tết: -80(Tức là Tết đã qua 80 ngày tính đến hôm nay)
⏰ Bảng method datetime hay dùng
| Method | Ý nghĩa | Ví dụ |
|---|---|---|
.now() | Thời điểm hiện tại | datetime.now() |
.date() | Chỉ lấy phần ngày | 2026-10-02 |
.time() | Chỉ lấy phần giờ | 14:30:15.123 |
.strftime(fmt) | Đối tượng → chuỗi | .strftime("%d/%m/%Y") |
.strptime(s, fmt) | Chuỗi → đối tượng | datetime.strptime("02/10/2026", "%d/%m/%Y") |
.timestamp() | → số giây Unix | 1759400215.0 |
.weekday() | Thứ, thứ Hai = 0 | 4 (thứ Sáu) |
.isoweekday() | Thứ, thứ Hai = 1 | 5 |
.replace(...) | Tạo bản sao với phần thay đổi | .replace(year=2027) |
Bảng ký tự định dạng:
| Ký tự | Ý nghĩa | Ví dụ |
|---|---|---|
%d | Ngày (01–31) | 02 |
%m | Tháng (01–12) | 10 |
%Y | Năm 4 chữ số | 2026 |
%y | Năm 2 chữ số | 26 |
%H | Giờ 24h | 14 |
%I | Giờ 12h | 02 |
%M | Phút | 30 |
%S | Giây | 15 |
%p | AM/PM | PM |
%A / %a | Tên thứ đầy đủ / viết tắt | Friday / Fri |
%B / %b | Tên tháng | October / Oct |
⚠️
%d/%m/%Yvs%m/%d/%Ylà lỗi nguy hiểm nhất khi xử lý ngày.03/04/2026là 3 tháng 4 ở Việt Nam, nhưng 4 tháng 3 ở Mỹ — và Python không báo lỗi vì cả hai đều hợp lệ. Luôn xác nhận định dạng của dữ liệu nguồn.
🕐 timedelta — tính toán khoảng thời gian
from datetime import datetime, timedelta, date
hom_nay = date(2026, 10, 2)
# --- Cộng trừ ---
print("Hôm qua:", hom_nay - timedelta(days=1)) # 2026-10-01
print("Tuần sau:", hom_nay + timedelta(weeks=1)) # 2026-10-09
print("3 giờ nữa:", datetime.now() + timedelta(hours=3))
# --- Khoảng cách giữa hai ngày ---
tet_2027 = date(2027, 2, 17)
con_lai = (tet_2027 - hom_nay).days
print(f"Còn {con_lai} ngày đến Tết 2027")
# --- Phân rã khoảng thời gian ---
delta = timedelta(days=45, hours=7, minutes=20)
print(f"{delta.days} ngày, {delta.seconds // 3600} giờ")
Ứng dụng: đếm ngược thời gian
from datetime import datetime, date
def dem_nguoc(ngay_dich: date) -> str:
"""Đếm ngược đến một ngày, xử lý cả trường hợp đã qua."""
delta = (ngay_dich - date.today()).days
if delta > 0:
return f"Còn {delta} ngày đến {ngay_dich:%d/%m/%Y}"
if delta == 0:
return f"🎉 Hôm nay là {ngay_dich:%d/%m/%Y}!"
return f"{ngay_dich:%d/%m/%Y} đã qua {-delta} ngày"
print(dem_nguoc(date(2027, 2, 17)))
print(dem_nguoc(date(2026, 1, 1)))
💡 Sửa lỗi trong code gốc của bài học: ví dụ
Số ngày còn lại đến Tếtcho kết quả âm vì Tết đã qua. Hàmdem_nguocở trên xử lý cả ba trường hợp — đó là cách viết code thực tế: luôn nghĩ đến trường hợp biên.
🌍 Múi giờ — nguồn gốc của mọi lỗi ngày tháng
from datetime import datetime, timezone, timedelta
# ❌ datetime.now() trả về giờ MÁY, không có thông tin múi giờ
gio_may = datetime.now()
print(gio_may.tzinfo) # None — không biết múi giờ nào
# ✅ Dùng timezone-aware khi làm việc qua hệ thống
VN = timezone(timedelta(hours=7))
gio_vn = datetime.now(VN)
print(gio_vn.strftime("%d/%m/%Y %H:%M:%S %Z"))
# ✅ UTC là chuẩn khi lưu database
gio_utc = datetime.now(timezone.utc)
print(gio_utc.isoformat())
# So sánh thời gian ở hai múi giờ
tokyo = timezone(timedelta(hours=9))
print(f"VN: {datetime.now(VN):%H:%M}")
print(f"Tokyo: {datetime.now(tokyo):%H:%M}")
tuong_lai = gio_vn + timedelta(hours=2)
print(f"Sau 2 giờ: {tuong_lai:%H:%M}")
| Loại datetime | Có tzinfo? | Dùng khi |
|---|---|---|
| Naive | Không | Chỉ xử lý giờ địa phương, không so sánh |
| Aware | Có | Lưu DB, API, hệ thống nhiều múi giờ |
⚠️ Quy tắc vàng: lưu database bằng UTC, hiển thị bằng giờ địa phương. Nếu bạn lưu giờ địa phương, ứng dụng sẽ sai ngay khi có người dùng ở múi giờ khác hoặc khi chuyển sang giờ mùa hè.
🐼 Làm việc với ngày tháng bằng pandas
import pandas as pd
chuoi = pd.Series(["2026-10-02", "2026-10-03", "02/10/2026", "không phải ngày"])
# errors="coerce" → giá trị không parse được thành NaT thay vì crash
gay = pd.to_datetime(chuoi, errors="coerce", dayfirst=True)
print(ngay)
print(f"Số giá trị lỗi: {ngay.isna().sum()}")
if ngay.notna().any():
print(f"Sớm nhất: {ngay.min():%d/%m/%Y}")
print(f"Muộn nhất: {ngay.max():%d/%m/%Y}")
Bốn thao tác ngày tháng trên DataFrame:
df = pd.DataFrame({
"ngay": pd.date_range("2026-01-01", periods=200, freq="D"),
"doanh_thu": range(200),
})
# 1. Lọc theo khoảng thời gian
df.loc["2026-03-01":"2026-03-31"]
# 2. Tách thành cột
for ten, gia_tri in [("nam", df["ngay"].dt.year),
("thang", df["ngay"].dt.month),
("thu", df["ngay"].dt.day_name()),
("quy", df["ngay"].dt.quarter)]:
df[ten] = gia_tri
# 3. Nhóm theo tháng
df.groupby(df["ngay"].dt.to_period("M"))["doanh_thu"].sum()
# 4. Dịch chuyển thời gian
df["doanh_thu_hom_qua"] = df["doanh_thu"].shift(1)
df["tang_truong"] = df["doanh_thu"] - df["doanh_thu"].shift(7)
print(df.head(10))
📝 Bài tập mở rộng
Bài 1: Đếm ngày làm việc
Đếm số ngày làm việc (thứ Hai – thứ Sáu) trong một tháng bất kỳ, không tính ngày lễ.
Bài 2: Tuổi chính xác
Tính tuổi theo năm, tháng và ngày từ ngày sinh cho trước.
Bài 3: Lịch trực
Cho danh sách 5 người, tạo lịch trực luân phiên mỗi ngày trong 30 ngày tới.
Bài 4: Phân tích file access log
Đọc file log có timestamp, đếm số request theo từng giờ, tìm giờ cao điểm.
🎯 Tóm tắt ngày 38
✅ Dùng strftime (đối tượng → chuỗi) và strptime (chuỗi → đối tượng)
✅ Nhớ bảng ký tự định dạng: %d %m %Y %H %M %S
✅ Tính toán khoảng thời gian bằng timedelta
✅ Hiểu khác biệt naive vs timezone-aware
✅ Lưu DB bằng UTC, hiển thị bằng giờ địa phương
✅ Dùng pd.to_datetime(..., errors="coerce") xử lý dữ liệu bẩn