Khóa học
Nhiều bộ dữ liệu thực tế bao gồm ngày và giờ, và một thao tác phổ biến trong khoa học dữ liệu là tính chênh lệch thời gian giữa hai thời điểm.
Các đơn vị chúng ta dùng để đo ngày giờ (năm, tháng, ngày, giờ, phút, giây) không hề dễ làm việc, đặc biệt khi các đơn vị này có độ dài không đều.
Tuy nhiên, kiểu dữ liệu timedelta của Python, thuộc mô-đun datetime, xử lý những phức tạp khi tính toán các khoảng thời gian.
Hãy bắt đầu với một câu trả lời thật ngắn gọn về timedelta trong Python là gì, rồi đi vào chi tiết hơn.
Để tìm hiểu thêm về ngày và giờ trong Python, hãy xem khóa học Làm việc với ngày và giờ trong Python.
Câu trả lời ngắn: Python's timedelta là gì
Hai kiểu dữ liệu quan trọng và được dùng thường xuyên nhất trong mô-đun datetime là timedelta và datetime. Trong khi đối tượng datetime biểu diễn các thời điểm cụ thể (ví dụ: ngày 1 tháng 1 năm 2024 lúc 10:30 sáng), đối tượng timedelta biểu diễn các khoảng thời lượng hay quãng thời gian giữa hai thời điểm. Hãy nghĩ về sự khác nhau giữa việc hỏi "Khi nào?" (datetime) và "Bao lâu?" (timedelta).
Xét hai ngày, mà ta có thể biểu diễn bằng hai đối tượng datetime. Trừ một ngày cho ngày kia sẽ tạo ra một đối tượng timedelta biểu diễn khoảng thời gian giữa hai ngày:
import datetime
first_date = datetime.datetime(year=2024, month=1, day=1)
second_date = datetime.datetime(year=2024, month=5, day=27)
interval = second_date - first_date
print(interval)
147 days, 0:00:00
Kết quả cho thấy hai ngày cách nhau đúng 147 ngày. Trong ví dụ sau, ta dùng ngày giờ tại thời điểm mã chạy bằng cách gọi datetime.datetime.now():
first_date = datetime.datetime(year=2024, month=1, day=1)
second_date = datetime.datetime.now()
interval = second_date - first_date
print(second_date)
print(interval)
2024-05-28 08:32:44.871646
148 days, 8:32:44.871646
Đối tượng timedelta giờ cho thấy thời gian đã trôi qua từ đầu năm 2024 đến thời điểm mã được thực thi.
Chúng ta sẽ khám phá sâu hơn về timedelta của Python trong bài viết này.
Tạo đối tượng timedelta
Ở phần trước, ta tạo một đối tượng timedelta từ hai đối tượng datetime, nhưng ta cũng có thể tạo một khoảng thời gian trực tiếp bằng cách gọi hàm dựng timedelta():
interval = datetime.timedelta(days=10, seconds=3600)
print(interval)
10 days, 1:00:00
Khoảng thời gian dài mười ngày và một giờ. Ta cũng có thể dùng các đơn vị thời gian khác khi tạo một thực thể timedelta:
interval = datetime.timedelta(weeks=1, days=3, hours=1)
print(interval)
10 days, 1:00:00
Bất kỳ đơn vị thời gian nào sau đây đều hợp lệ khi gọi timedelta():
- weeks
- days
- hours
- minutes
- seconds
- milliseconds
- microseconds
Hướng dẫn này sẽ đi sâu hơn vào cách các khoảng thời gian được lưu trữ trong một đối tượng timedelta.
Phép toán cơ bản với timedelta
Ta cũng có thể thực hiện các phép toán với đối tượng timedelta. Xét ba đối tượng datetime sau, biểu diễn các ngày phát hành Python 1.0, Python 2.0 và Python 3.0. Trừ các đối tượng datetime cho nhau tạo ra một đối tượng timedelta:
release_date_python_1 = datetime.datetime(year=1991, month=2, day=20)
release_date_python_2 = datetime.datetime(year=2000, month=10, day=16)
release_date_python_3 = datetime.datetime(year=2008, month=12, day=3)
time_between_1_and_2 = release_date_python_2 - release_date_python_1
time_between_2_and_3 = release_date_python_3 - release_date_python_2
print(time_between_1_and_2)
print(time_between_2_and_3)
3526 days, 0:00:00
2970 days, 0:00:00
Kết quả cho thấy số ngày giữa lần phát hành Python 1.0 và 2.0, và giữa lần phát hành Python 2.0 và 3.0. Các khoảng thời gian này là các đối tượng timedelta.
Ta cũng có thể thực hiện phép cộng và trừ giữa đối tượng timedelta và đối tượng datetime. Hãy cộng một timedelta với days=100 vào release_date_python_3:
print(release_date_python_3 + datetime.timedelta(days=100))
2009-03-13 00:00:00
Python 3.0 phát hành vào ngày 3 tháng 12 năm 2008. Cộng thêm một trăm ngày sẽ ra ngày 13 tháng 3 năm 2009.
Tuy vậy, ta cũng có thể thực hiện phép toán trực tiếp giữa các đối tượng timedelta:
print("Difference between gap 1 and gap 2")
print(time_between_1_and_2 - time_between_2_and_3)
print("Sum of gap 1 and gap 2")
print(time_between_1_and_2 + time_between_2_and_3)
Difference between gap 1 and gap 2
556 days, 0:00:00
Sum of gap 1 and gap 2
6496 days, 0:00:00
Kết quả xác nhận có thể trừ và cộng các đối tượng timedelta. Phép nhân và chia cũng khả dụng:
print("Multiply time interval by 3:")
print(time_between_1_and_2 * 3)
print("Divide time interval by 3:")
print(time_between_1_and_2 / 3)
Multiply time interval by 3:
10578 days, 0:00:00
Divide time interval by 3:
1175 days, 8:00:00
Ta cũng có thể so sánh các đối tượng timedelta:
print("Check if gap 1 is greater than gap 2")
print(time_between_1_and_2 > time_between_2_and_3)
True
Kết quả xác nhận khoảng thời gian giữa ngày phát hành Python 1.0 và Python 2.0 dài hơn khoảng giữa Python 2.0 và Python 3.0.
Thuộc tính và phương thức quan trọng của timedelta
Một đối tượng timedelta biểu diễn khoảng thời gian bằng cách lưu số ngày, giây và microsecond. Ba đơn vị này là các đơn vị thời gian duy nhất được biểu diễn bằng thuộc tính trong lớp timedelta:
interval = datetime.timedelta(
weeks=1,
hours=10,
minutes=22,
milliseconds=1042,
)
print(f"{interval = }")
print(f"{interval.days = }")
print(f"{interval.seconds = }")
print(f"{interval.microseconds = }")
interval = datetime.timedelta(days=7, seconds=37321, microseconds=42000)
interval.days = 7
interval.seconds = 37321
interval.microseconds = 42000
Mặc dù lời gọi timedelta() bao gồm các tham số tuần, giờ, phút và millisecond, hàm dựng sẽ chuyển các đơn vị này về ngày, giây và microsecond. Các lệnh print() dùng f-string với dấu bằng = để hiển thị tên biến và giá trị của nó. Bạn có thể tìm hiểu thêm về f-string trong hướng dẫn định dạng f-string trong Python.
Một công cụ hữu ích khác của lớp timedelta là phương thức total_seconds(). Phương thức này trả về toàn bộ khoảng thời gian tính theo giây:
print("Total time interval in seconds:")
print(interval.total_seconds())
Total time interval in seconds:
642121.042
Giá trị này biểu diễn toàn bộ khoảng thời gian tính theo giây. Nó không giống với giá trị của .seconds, vốn chỉ cho thấy thành phần giây của khoảng thời gian mà không tính đến ngày và microsecond.
Ta có thể dùng giá trị trả về từ .total_seconds() để chuyển đổi khoảng thời gian sang bất kỳ đơn vị nào khác:
print("Total time interval in days:")
print(interval.total_seconds() / 3600 / 24)
Total time interval in days:
7.431956504629629
Kết quả hiển thị khoảng thời gian tính theo ngày vì giá trị trả về từ .total_seconds() được chia cho số giây trong một giờ và số giờ trong một ngày.
Ví dụ về timedelta: Tính tăng trưởng
Hãy xem một ví dụ. Cửa hàng trực tuyến của một công ty bán sách Python ghi lại tất cả giao dịch bán hàng. Dưới đây là dữ liệu cho một khách hàng:
data = {
"customer_id": 3542,
"sales": [
{"date": "2024-04-21T13:23:45", "price": 24.99, "quantity": 1},
{"date": "2024-02-13T10:54:12", "price": 24.99, "quantity": 2},
{"date": "2024-01-08T20:32:24", "price": 18.99, "quantity": 1},
],
}
Chúng ta cần tính khoảng thời gian giữa đơn hàng đầu tiên và cuối cùng của một khách hàng. Các đơn hàng luôn theo thứ tự thời gian trong danh sách chứa tất cả giao dịch.
Bước đầu tiên là trích xuất các ngày từ cấu trúc lồng nhau này và chuyển chúng thành đối tượng datetime. May mắn là các dấu thời gian theo chuẩn ISO 8601 (YYYY-MM-DDTHH:MM:SS), nên ta có thể dùng phương thức tiện lợi fromisoformat():
sales = data["sales"]
dates = []
for item in sales:
date = datetime.datetime.fromisoformat(item["date"])
dates.append(date)
print(dates)
[datetime.datetime(2024, 4, 21, 13, 23, 45), datetime.datetime(2024, 2, 13, 10, 54, 12), datetime.datetime(2024, 1, 8, 20, 32, 24)]
Danh sách dates chứa các đối tượng datetime với ngày của từng đơn hàng. Cuối cùng, ta có thể tính khoảng thời gian giữa đơn đầu và đơn cuối:
time_span = dates[0] - dates[-1]
print(time_span)
print(time_span.days)
103 days, 16:51:21
103
Hiệu giữa hai ngày đầu và cuối cho một đối tượng timedelta. Mã hiển thị time_span, cho thấy số ngày, giờ, phút và giây. Ta cũng hiển thị time_span.days để chỉ số ngày nguyên.
Nếu bạn muốn tìm hiểu thêm về chuyển đổi chuỗi sang datetime, hãy xem hướng dẫn Python String to Datetime.
Mẹo nhanh khi dùng timedelta
Hãy xem một vài điều cần lưu ý khi dùng timedelta của Python:
1. Tốt nhất nên dùng tham số từ khóa khi gọi timedelta() để tránh nhầm lẫn đơn vị:
interval = datetime.timedelta(weeks=1, days=3, hours=1, milliseconds=1354)
2. Khi tìm khoảng thời gian giữa hai ngày ở các múi giờ khác nhau, timedelta sẽ chuyển các ngày về UTC trước khi tính chênh lệch:
import zoneinfo
some_date = datetime.datetime.fromisoformat("2024-01-01T00:00:00")
some_date_lon = some_date.replace(tzinfo=zoneinfo.ZoneInfo("Europe/London"))
some_date_nyc = some_date.replace(tzinfo=zoneinfo.ZoneInfo("America/New_York"))
print(some_date_lon)
print(some_date_nyc)
print(some_date_nyc - some_date_lon)
2024-01-01 00:00:00+00:00
2024-01-01 00:00:00-05:00
5:00:00
Ngày trong some_date cho thấy thời điểm bắt đầu năm mới. Đây là một đối tượng datetime ngây thơ (naive) vì không có thông tin về múi giờ. Tuy nhiên, some_date_lon và some_date_nyc là các đối tượng datetime có nhận thức múi giờ (aware) vì chúng biểu diễn thời điểm bắt đầu năm mới tại London và New York tương ứng.
timedelta thu được bằng cách trừ hai đối tượng datetime aware cho thấy khoảng năm giờ, tức là chênh lệch thời gian theo UTC.
3. Các thay đổi giờ mùa (daylight saving) không được timedelta tính đến:
nyc = zoneinfo.ZoneInfo("America/New_York")
first_time = datetime.datetime.strptime("2024-03-10 1:00am", "%Y-%m-%d %I:%M%p")
first_time = first_time.replace(tzinfo=nyc)
second_time = datetime.datetime.strptime("2024-03-10 3:00am", "%Y-%m-%d %I:%M%p")
second_time = second_time.replace(tzinfo=nyc)
time_difference = second_time - first_time
print("Time difference between the first and second time:")
print(time_difference)
print("Checking the DST status of the first and second time:")
print(f"{first_time.dst() = }")
print(f"{second_time.dst() = }")
Time difference between the first and second time:
2:00:00
Checking the DST status of the first and second time:
first_time.dst() = datetime.timedelta(0)
second_time.dst() = datetime.timedelta(seconds=3600)
Hai thời điểm này biểu diễn 1:00 sáng và 3:00 sáng ngày 3 tháng 3 năm 2024 tại New York. Giờ mùa bắt đầu lúc 2:00 sáng cùng ngày khi đồng hồ chuyển sang 3:00 sáng. Hiệu giữa hai thời điểm cho một timedelta là hai giờ dù thực tế chỉ có một giờ trôi qua do bắt đầu giờ mùa.
Ta có thể chuyển sang UTC nếu cần lượng thời gian thực sự đã trôi qua:
first_time_utc = first_time.astimezone(zoneinfo.ZoneInfo("UTC"))
second_time_utc = second_time.astimezone(zoneinfo.ZoneInfo("UTC"))
time_difference_utc = second_time_utc - first_time_utc
print("Actual time difference between the first and second time:")
print(time_difference_utc)
Actual time difference between the first and second time:
1:00:00
Chuyển về UTC trước khi trừ các đối tượng datetime sẽ cho ra thời gian thực tế giữa hai đối tượng datetime.
Kết luận
Lớp timedelta của Python cung cấp các phương thức đơn giản để xử lý khoảng thời gian, giúp làm việc với các giai đoạn thời gian theo nhiều đơn vị trở nên dễ dàng hơn. Chúng ta đã tìm hiểu cách tạo các đối tượng timedelta và thực hiện các phép toán với chúng, bao gồm cộng, trừ, nhân và chia.
Bạn có thể đọc thêm về xử lý ngày giờ trong các bài viết sau:
Tôi học Vật lý và Toán ở bậc đại học tại Đại học Malta. Sau đó, tôi chuyển đến London và lấy bằng Tiến sĩ Vật lý tại Imperial College. Tôi nghiên cứu các kỹ thuật quang học mới để chụp ảnh võng mạc người. Hiện nay, tôi tập trung vào việc viết về Python, truyền đạt về Python và giảng dạy Python.
