Bài 9: Working với Dates và Times
Mục Tiêu Bài Học
Sau khi hoàn thành bài này, bạn sẽ:
- ✅ Sử dụng datetime module
- ✅ Làm việc với timedelta
- ✅ Handle timezones
- ✅ Format dates
- ✅ Thực hiện date arithmetic
- ✅ Best practices cho datetime operations
datetime Module
Module datetime cung cấp classes để work với dates và times.
date - Work with Dates
from datetime import date # Current datetoday = date.today()print(today) # 2025-10-27print(type(today)) # <class 'datetime.date'> # Create specific datebirthday = date(1990, 5, 15)print(birthday) # 1990-05-15 # Access componentsprint(today.year) # 2025print(today.month) # 10print(today.day) # 27 # Day of week (0=Monday, 6=Sunday)print(today.weekday()) # 0 (Monday)print(today.isoweekday()) # 1 (Monday, ISO format) # ISO formatprint(today.isoformat()) # 2025-10-27
time - Work with Times
from datetime import time # Create timemorning = time(9, 30, 0) # 09:30:00print(morning) # 09:30:00 # With microsecondsprecise_time = time(14, 30, 45, 123456)print(precise_time) # 14:30:45.123456 # Access componentsprint(morning.hour) # 9print(morning.minute) # 30print(morning.second) # 0print(precise_time.microsecond) # 123456 # Min and maxprint(time.min) # 00:00:00print(time.max) # 23:59:59.999999
datetime - Work with Both
from datetime import datetime # Current datetimenow = datetime.now()print(now) # 2025-10-27 10:30:45.123456print(type(now)) # <class 'datetime.datetime'> # UTC datetimeutc_now = datetime.utcnow()print(utc_now) # 2025-10-27 03:30:45.123456 # Create specific datetimedt = datetime(2025, 10, 27, 14, 30, 0)print(dt) # 2025-10-27 14:30:00 # Access componentsprint(dt.year) # 2025print(dt.month) # 10print(dt.day) # 27print(dt.hour) # 14print(dt.minute) # 30print(dt.second) # 0 # Get date and time separatelyprint(dt.date()) # 2025-10-27print(dt.time()) # 14:30:00 # Combine date and timefrom datetime import date, timed = date(2025, 10, 27)t = time(14, 30, 0)dt = datetime.combine(d, t)print(dt) # 2025-10-27 14:30:00
timedelta - Time Differences
timedelta represents duration/difference between two dates or times.
Creating timedelta
from datetime import timedelta # Create durationsone_day = timedelta(days=1)one_week = timedelta(weeks=1)one_hour = timedelta(hours=1)thirty_minutes = timedelta(minutes=30) # Complex durationduration = timedelta( days=7, hours=5, minutes=30, seconds=45)print(duration) # 7 days, 5:30:45 # Access componentsprint(duration.days) # 7print(duration.seconds) # 19845 (5*3600 + 30*60 + 45)print(duration.total_seconds()) # 624645.0
Date Arithmetic
from datetime import datetime, timedelta now = datetime.now()print(now) # 2025-10-27 10:30:00 # Add timetomorrow = now + timedelta(days=1)print(tomorrow) # 2025-10-28 10:30:00 next_week = now + timedelta(weeks=1)print(next_week) # 2025-11-03 10:30:00 in_two_hours = now + timedelta(hours=2)print(in_two_hours) # 2025-10-27 12:30:00 # Subtract timeyesterday = now - timedelta(days=1)print(yesterday) # 2025-10-26 10:30:00 # Calculate differencefuture = datetime(2025, 12, 31, 23, 59, 59)diff = future - nowprint(diff) # 65 days, 13:29:59print(f"{diff.days} days remaining") # 65 days remaining # Compare datesif tomorrow > now: print("Tomorrow is in the future") # Real example: Calculate agefrom datetime import date def calculate_age(birth_date): today = date.today() age = today.year - birth_date.year # Adjust if birthday hasn't occurred this year if (today.month, today.day) < (birth_date.month, birth_date.day): age -= 1 return age birthday = date(1990, 5, 15)age = calculate_age(birthday)print(f"Age: {age} years") # Age: 35 years
Formatting Dates
strftime() - Format to String
from datetime import datetime now = datetime.now() # Common formatsprint(now.strftime("%Y-%m-%d")) # 2025-10-27print(now.strftime("%d/%m/%Y")) # 27/10/2025print(now.strftime("%B %d, %Y")) # October 27, 2025print(now.strftime("%Y-%m-%d %H:%M:%S")) # 2025-10-27 10:30:45 # Time formatsprint(now.strftime("%H:%M:%S")) # 10:30:45print(now.strftime("%I:%M %p")) # 10:30 AM # Day of weekprint(now.strftime("%A")) # Mondayprint(now.strftime("%a")) # Mon # Monthprint(now.strftime("%B")) # Octoberprint(now.strftime("%b")) # Oct # Complete formatformatted = now.strftime("%A, %B %d, %Y at %I:%M %p")print(formatted) # Monday, October 27, 2025 at 10:30 AM # Common format codes:# %Y - Year (4 digits)# %y - Year (2 digits)# %m - Month (01-12)# %B - Month name (full)# %b - Month name (abbreviated)# %d - Day (01-31)# %A - Weekday (full)# %a - Weekday (abbreviated)# %H - Hour (00-23)# %I - Hour (01-12)# %M - Minute (00-59)# %S - Second (00-59)# %p - AM/PM
strptime() - Parse from String
from datetime import datetime # Parse date stringsdate_str = "2025-10-27"dt = datetime.strptime(date_str, "%Y-%m-%d")print(dt) # 2025-10-27 00:00:00 # Parse with timedatetime_str = "October 27, 2025 at 10:30 AM"dt = datetime.strptime(datetime_str, "%B %d, %Y at %I:%M %p")print(dt) # 2025-10-27 10:30:00 # Parse different formatsformats = [ ("2025-10-27", "%Y-%m-%d"), ("27/10/2025", "%d/%m/%Y"), ("10/27/2025", "%m/%d/%Y"), ("2025-10-27 14:30:00", "%Y-%m-%d %H:%M:%S")] for date_str, fmt in formats: dt = datetime.strptime(date_str, fmt) print(f"{date_str} -> {dt}") # Handle multiple formatsdef parse_date(date_str): """Try parsing with multiple formats.""" formats = [ "%Y-%m-%d", "%d/%m/%Y", "%m/%d/%Y", "%B %d, %Y", "%Y-%m-%d %H:%M:%S" ] for fmt in formats: try: return datetime.strptime(date_str, fmt) except ValueError: continue raise ValueError(f"Cannot parse date: {date_str}") print(parse_date("2025-10-27")) # Worksprint(parse_date("27/10/2025")) # Works
Timezone Handling
Naive vs Aware Datetimes
from datetime import datetime, timezone # Naive datetime (no timezone info)naive = datetime.now()print(naive) # 2025-10-27 10:30:00print(naive.tzinfo) # None # Aware datetime (with timezone)aware = datetime.now(timezone.utc)print(aware) # 2025-10-27 03:30:00+00:00print(aware.tzinfo) # UTC # Check if awareprint(naive.tzinfo is None) # True (naive)print(aware.tzinfo is None) # False (aware)
Using zoneinfo (Python 3.9+)
from datetime import datetimefrom zoneinfo import ZoneInfo # Create timezone-aware datetimeny_tz = ZoneInfo("America/New_York")tokyo_tz = ZoneInfo("Asia/Tokyo")hanoi_tz = ZoneInfo("Asia/Ho_Chi_Minh") # Current time in different timezonesnow_ny = datetime.now(ny_tz)now_tokyo = datetime.now(tokyo_tz)now_hanoi = datetime.now(hanoi_tz) print(f"New York: {now_ny}")print(f"Tokyo: {now_tokyo}")print(f"Hanoi: {now_hanoi}") # Convert between timezonesutc_time = datetime.now(ZoneInfo("UTC"))print(f"UTC: {utc_time}") # Convert to different timezonehanoi_time = utc_time.astimezone(hanoi_tz)print(f"Hanoi: {hanoi_time}") tokyo_time = utc_time.astimezone(tokyo_tz)print(f"Tokyo: {tokyo_time}")
Working with UTC
from datetime import datetime, timezone # Current UTC timeutc_now = datetime.now(timezone.utc)print(utc_now) # 2025-10-27 03:30:00+00:00 # Convert local to UTClocal_time = datetime.now()utc_time = local_time.replace(tzinfo=timezone.utc) # ISO format with timezoneprint(utc_now.isoformat()) # 2025-10-27T03:30:00+00:00 # Best practice: Always store in UTCdef save_timestamp(): """Always save in UTC.""" return datetime.now(timezone.utc) def display_timestamp(utc_dt, user_tz): """Convert to user's timezone for display.""" from zoneinfo import ZoneInfo local_dt = utc_dt.astimezone(ZoneInfo(user_tz)) return local_dt.strftime("%Y-%m-%d %H:%M:%S %Z")
Real-world Examples
1. Event Scheduler
from datetime import datetime, timedeltafrom zoneinfo import ZoneInfo class Event: def __init__(self, name, start_time, duration_minutes, timezone="UTC"): self.name = name self.start_time = start_time self.duration = timedelta(minutes=duration_minutes) self.timezone = ZoneInfo(timezone) @property def end_time(self): return self.start_time + self.duration def is_happening_now(self): now = datetime.now(self.timezone) return self.start_time <= now < self.end_time def time_until_start(self): now = datetime.now(self.timezone) if now < self.start_time: return self.start_time - now return None def __str__(self): return f"{self.name}: {self.start_time.strftime('%Y-%m-%d %H:%M')} ({self.duration.seconds // 60} min)" # Create eventsmeeting = Event( "Team Meeting", datetime(2025, 10, 27, 14, 0, tzinfo=ZoneInfo("Asia/Ho_Chi_Minh")), 60, "Asia/Ho_Chi_Minh") print(meeting)print(f"Is happening now: {meeting.is_happening_now()}") time_until = meeting.time_until_start()if time_until: print(f"Starts in: {time_until.seconds // 3600} hours, {(time_until.seconds % 3600) // 60} minutes")
2. Booking System
from datetime import datetime, timedelta class Booking: def __init__(self, start, end): self.start = start self.end = end def overlaps(self, other): """Check if two bookings overlap.""" return (self.start < other.end and self.end > other.start) def __repr__(self): return f"Booking({self.start} to {self.end})" class BookingManager: def __init__(self): self.bookings = [] def add_booking(self, start, end): new_booking = Booking(start, end) # Check for overlaps for booking in self.bookings: if new_booking.overlaps(booking): return False, "Time slot already booked" self.bookings.append(new_booking) return True, "Booking successful" def get_available_slots(self, date, slot_duration=60): """Get available time slots for a date.""" work_start = datetime.combine(date, datetime.min.time().replace(hour=9)) work_end = datetime.combine(date, datetime.min.time().replace(hour=17)) available = [] current = work_start while current + timedelta(minutes=slot_duration) <= work_end: slot_end = current + timedelta(minutes=slot_duration) slot = Booking(current, slot_end) # Check if slot is available is_available = True for booking in self.bookings: if slot.overlaps(booking): is_available = False break if is_available: available.append((current, slot_end)) current += timedelta(minutes=slot_duration) return available # Usagemanager = BookingManager() # Add bookingsstart = datetime(2025, 10, 27, 10, 0)end = datetime(2025, 10, 27, 11, 0)success, message = manager.add_booking(start, end)print(message) # Try overlapping bookingstart2 = datetime(2025, 10, 27, 10, 30)end2 = datetime(2025, 10, 27, 11, 30)success, message = manager.add_booking(start2, end2)print(message) # Time slot already booked
3. Age Calculator
from datetime import date, datetime class AgeCalculator: @staticmethod def calculate_age(birth_date): """Calculate age in years.""" today = date.today() age = today.year - birth_date.year if (today.month, today.day) < (birth_date.month, birth_date.day): age -= 1 return age @staticmethod def calculate_detailed_age(birth_date): """Calculate age in years, months, and days.""" today = date.today() years = today.year - birth_date.year months = today.month - birth_date.month days = today.day - birth_date.day if days < 0: months -= 1 # Get days in previous month if today.month == 1: prev_month_days = 31 else: prev_month = today.replace(day=1) - timedelta(days=1) prev_month_days = prev_month.day days += prev_month_days if months < 0: years -= 1 months += 12 return years, months, days @staticmethod def days_until_birthday(birth_date): """Calculate days until next birthday.""" today = date.today() next_birthday = date(today.year, birth_date.month, birth_date.day) if next_birthday < today: next_birthday = date(today.year + 1, birth_date.month, birth_date.day) return (next_birthday - today).days # Usagebirthday = date(1990, 5, 15) age = AgeCalculator.calculate_age(birthday)print(f"Age: {age} years") years, months, days = AgeCalculator.calculate_detailed_age(birthday)print(f"Detailed age: {years} years, {months} months, {days} days") days_until = AgeCalculator.days_until_birthday(birthday)print(f"Days until birthday: {days_until}")
4. Time Tracker
from datetime import datetime, timedelta class TimeTracker: def __init__(self): self.entries = [] self.current_task = None self.start_time = None def start_task(self, task_name): """Start tracking a task.""" if self.current_task: self.stop_task() self.current_task = task_name self.start_time = datetime.now() print(f"Started: {task_name}") def stop_task(self): """Stop tracking current task.""" if not self.current_task: return end_time = datetime.now() duration = end_time - self.start_time self.entries.append({ 'task': self.current_task, 'start': self.start_time, 'end': end_time, 'duration': duration }) print(f"Stopped: {self.current_task} ({self.format_duration(duration)})") self.current_task = None self.start_time = None def format_duration(self, duration): """Format duration as HH:MM:SS.""" total_seconds = int(duration.total_seconds()) hours = total_seconds // 3600 minutes = (total_seconds % 3600) // 60 seconds = total_seconds % 60 return f"{hours:02d}:{minutes:02d}:{seconds:02d}" def get_summary(self): """Get time summary by task.""" summary = {} for entry in self.entries: task = entry['task'] duration = entry['duration'] if task not in summary: summary[task] = timedelta() summary[task] += duration return summary def print_summary(self): """Print time summary.""" summary = self.get_summary() print("\n=== Time Summary ===") total = timedelta() for task, duration in sorted(summary.items()): print(f"{task}: {self.format_duration(duration)}") total += duration print(f"\nTotal: {self.format_duration(total)}") # Usagetracker = TimeTracker() tracker.start_task("Coding")# ... work for some time ...tracker.stop_task() tracker.start_task("Meeting")# ... work for some time ...tracker.stop_task() tracker.print_summary()
5. Reminder System
from datetime import datetime, timedeltafrom zoneinfo import ZoneInfo class Reminder: def __init__(self, message, remind_at, timezone="UTC"): self.message = message self.remind_at = remind_at self.timezone = ZoneInfo(timezone) self.triggered = False def should_trigger(self): """Check if reminder should trigger.""" if self.triggered: return False now = datetime.now(self.timezone) return now >= self.remind_at def trigger(self): """Trigger the reminder.""" self.triggered = True return f"REMINDER: {self.message}" class ReminderManager: def __init__(self): self.reminders = [] def add_reminder(self, message, remind_at, timezone="UTC"): """Add a new reminder.""" reminder = Reminder(message, remind_at, timezone) self.reminders.append(reminder) print(f"Reminder set for {remind_at}") def add_reminder_in(self, message, minutes, timezone="UTC"): """Add reminder that triggers in X minutes.""" tz = ZoneInfo(timezone) remind_at = datetime.now(tz) + timedelta(minutes=minutes) self.add_reminder(message, remind_at, timezone) def check_reminders(self): """Check and trigger due reminders.""" triggered = [] for reminder in self.reminders: if reminder.should_trigger(): message = reminder.trigger() triggered.append(message) return triggered # Usagemanager = ReminderManager() # Remind in 5 minutesmanager.add_reminder_in("Take a break", 5, "Asia/Ho_Chi_Minh") # Remind at specific timeremind_at = datetime(2025, 10, 27, 15, 0, tzinfo=ZoneInfo("Asia/Ho_Chi_Minh"))manager.add_reminder("Team meeting", remind_at, "Asia/Ho_Chi_Minh") # Check reminders (in real app, run this periodically)triggered = manager.check_reminders()for message in triggered: print(message)
Best Practices
from datetime import datetime, timezonefrom zoneinfo import ZoneInfo # 1. Always use timezone-aware datetimes# Goodaware_dt = datetime.now(timezone.utc)# Badnaive_dt = datetime.now() # 2. Store in UTC, display in localdef save_timestamp(): return datetime.now(timezone.utc) def display_timestamp(utc_dt, user_timezone): local_dt = utc_dt.astimezone(ZoneInfo(user_timezone)) return local_dt.strftime("%Y-%m-%d %H:%M:%S") # 3. Use ISO format for serializationdt = datetime.now(timezone.utc)iso_string = dt.isoformat() # 2025-10-27T03:30:00+00:00 # 4. Compare aware datetimes onlyutc_now = datetime.now(timezone.utc)hanoi_now = datetime.now(ZoneInfo("Asia/Ho_Chi_Minh"))# Both are aware, comparison works correctly # 5. Use timedelta for date arithmetictomorrow = datetime.now() + timedelta(days=1)next_week = datetime.now() + timedelta(weeks=1) # 6. Handle timezone conversion properlydef convert_timezone(dt, from_tz, to_tz): """Convert datetime between timezones.""" aware_dt = dt.replace(tzinfo=ZoneInfo(from_tz)) return aware_dt.astimezone(ZoneInfo(to_tz)) # 7. Use date for date-only operationsfrom datetime import datetoday = date.today()birthday = date(1990, 5, 15)
Bài Tập Thực Hành
Bài 1: Meeting Scheduler
Tạo meeting scheduler:
- Schedule meetings with timezone
- Check availability
- Send reminders
- Handle recurring meetings
Bài 2: Time Zone Converter
Tạo CLI tool convert times:
- Convert between timezones
- Show multiple timezones simultaneously
- Handle DST changes
Bài 3: Birthday Tracker
Track birthdays:
- Store birthdays
- Calculate ages
- Show upcoming birthdays
- Send reminders
Bài 4: Work Hours Logger
Log work hours:
- Clock in/out
- Calculate daily/weekly hours
- Generate reports
- Export timesheet
Bài 5: Countdown Timer
Create countdown app:
- Set target date/time
- Show remaining time
- Multiple timers
- Notifications
Tóm Tắt
✅ datetime module: date, time, datetime, timedelta
✅ Date arithmetic: +/- with timedelta
✅ Formatting: strftime() and strptime()
✅ Timezones: zoneinfo, timezone-aware datetimes
✅ UTC: Store in UTC, display in local
✅ Comparisons: Only compare aware datetimes
✅ Best practices: Always use timezone-aware, ISO format
Bài Tiếp Theo
Bài 10: Collections Module - namedtuple, defaultdict, Counter, deque, và advanced data structures! 🚀
Remember:
- Always use timezone-aware datetimes
- Store in UTC, display in local
- Use ISO format for serialization
- Handle timezone conversion properly
- Use timedelta for date arithmetic! 🎯