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! 🎯