Date and Time support

How computers store time?

How computers store time?

System time is measured by a system clock, which is typically implemented as a simple count of the number of ticks that have elapsed since Epoch time (also known as Unix time).
Epoch time is the number of seconds that have elapsed since 00:00:00 (UTC), Thursday, 1 January 1970, minus the leap seconds.
System time usually is converted into a form more suitable for human comprehension.
For example, the Unix system time 1000000000 seconds since the beginning of the Epoch translates into the calendar time 9 September 2001 01:46:40 UTC

                import time

                now = time.time()
                print('number of seconds since the Unix epoch: ', now)

                # number of seconds since the Unix epoch:  1707939036.4268076
            
In programming languages like Python, datetime objects are used to represent dates and times. These objects contain attributes such as year, month, day, hour, minute, second, and microsecond. Datetime objects provide methods for arithmetic operations, formatting and parsing, making them versatile for various applications

Datetime module - Overview

Datetime module - Overview

The built-in datetime module supplies classes for manipulating dates and times in both simple and complex ways.
class datetime.date
An idealized naive date, assuming the current Gregorian calendar always was, and always will be, in effect.
Attributes: year, month, and day.
class datetime.time
An idealized time, independent of any particular day, assuming that every day has exactly 24*60*60 seconds.
Attributes: hour, minute, second, microsecond, and tzinfo.
class datetime.datetime
A combination of a date and a time.
Attributes: year, month, day, hour, minute, second, microsecond, and tzinfo.
class datetime.timedelta
A duration expressing the difference between two date, time, or datetime instances to microsecond resolution.
class datetime.tzinfo
An abstract base class for time zone information objects. These are used by the datetime and time classes to provide a customizable notion of time adjustment (for example, to account for time zone and/or daylight saving time).
class datetime.timezone
A class that implements the tzinfo abstract base class as a fixed offset from the UTC.

Get Current Date and Time

Get Current Date and Time

Using the now() method in datetime class, we can obtain the current date and time:

            from datetime import datetime

            current_datetime = datetime.now()
            print(f'Current Date and Time: {current_datetime}')

            # OUTPUT:
            # Current Date and Time: 2023-10-17 11:54:04.585473
        

Date and Time Components

Date and Time Components

The datetime module provides various attributes to access individual components of a date and time, such as year, month, day, hour, minute, second, and microsecond.

                from datetime import datetime

                now = datetime.now()

                print("Year:", now.year)
                print("Month:", now.month)
                print("Day:", now.day)
                print("Hour:", now.hour)
                print("Minute:", now.minute)
                print("Second:", now.second)
                print("Microsecond:", now.microsecond)

            

Formatting and Parsing Dates and Times

Formatting Dates and Times

datetime.strftime()

You can format dates and times as strings using the strftime() method.
This method allows you to define a custom format for your date and time representation using Format Codes

                from datetime import datetime

                now = datetime.now()

                formatted_date = now.strftime("%d.%m.%Y")
                formatted_time = now.strftime("%H:%M:%S")

                print("Formatted Date:", formatted_date)
                print("Formatted Time:", formatted_time)

                # OUTPUT:
                # Formatted Date: 17.10.2023
                # Formatted Time: 12:45:46
            

Create datetime object from string

datetime.strptime()

With strptime() method we can parse a string into a datetime object given a corresponding format, using the same Format Codes as pattern:

                from datetime import datetime

                user_birthdate_str = "03.04.2000"
                user_birthdate = datetime.strptime(user_birthdate_str, "%m.%d.%Y")

                print(f"user birth year: {user_birthdate.year}")
                print(f"user birth month: {user_birthdate.month}")
                print(f"user birth day: {user_birthdate.day}")

                # user birth year: 2000
                # user birth month: 3
                # user birth day: 4

            

Date Arithmetic

Date Arithmetic with datetime.timedelta

We can perform arithmetic with dates by creating timedelta objects. These objects represent a duration or difference between two dates.

                from datetime import datetime, timedelta

                current_date = datetime.now().date()
                one_week_ahead = current_date + timedelta(weeks=1)
                hundred_days_before = current_date - timedelta(days=100)

                print("One week from today:", one_week_ahead)
                print("100 days before today:", hundred_days_before)

                # OUTPUT:
                # One week from today: 2023-10-24
                # 100 days before today: 2023-07-09
            
Note, that timedelta objects stores internally only days, seconds and microseconds

            from datetime import datetime, date, timedelta

            today = datetime.now().date()
            user_birthdate = date(year=2000,month=1,day=31)
            # print(user_birthdate)

            td = today - user_birthdate

            print(f'User is {td.days} days old') # 'User is 8660 days old'
            print(f'User is {td.weeks} weeks old') # Error will be thrown!
        
For more complicated and accurate date calculations we can use third-party modules, like dateutil

Date Arithmetic with dateutil module

Overview

The third-party dateutil module provides powerful extensions to the standard datetime module.
To use it, you must install it first.
Open a Terminal (Command Promt/Powershell/Bash ...) and write:

                pip install python-dateutil
            

Example

The relativedelta class from dateutil takes care of the complex date calculations, including months and leap years:

            from datetime import datetime
            from dateutil.relativedelta import relativedelta

            # User's birthdate
            user_birthdate = datetime(year=2000, month=1, day=31)

            # Current date
            current_date = datetime.now()

            # Calculate the age difference in years, days, and minutes
            age_difference = relativedelta(current_date, user_birthdate)
            print(age_difference)

            years = age_difference.years
            months = age_difference.months
            days = age_difference.days
            minutes = age_difference.hours * 60 + age_difference.minutes

            print(f"The user is {years} years, {months} months, {days} days, and {minutes} minutes old.")

            # OUTPUT:
            #relativedelta(years=+24, days=+14, hours=+22, minutes=+4, seconds=+57, microseconds=+906178)
            #The user is 24 years, 0 months, 14 days, and 1324 minutes old.
        

Working with Timezones

Handling Timezones in Python

Understanding Timezones

A timezone is a region of the globe that observes a uniform standard time for legal, commercial, and social purposes. Timezones are crucial in programming to ensure the correct representation of time across different regions.
Python provides several ways to handle timezones, primarily through the standard datetime module and the third-party pytz library.
Python 3.9 introduced zoneinfo module, which allows for more straightforward handling of timezones without needing an external library.

Working with timezones via pytz module

Overview

The pytz third-party module brings the Olson tz database into Python. This library allows accurate and cross platform timezone calculations using Python 2.4 or higher.
The tz database provides next List of tz database time zones
To use it, we must install it first:

                pip install pytz
            

Find the current time of a certain timezone

In next code we create timezone objects by pytz.timezone().
When a timezone object is passed as an argument to datetime.now(), it returns a timezone-aware datetime object representing the current time in that timezone.

                from datetime import datetime
                import pytz

                # Get local current time:
                current  =datetime.now()

                # Get the timezone object for New York and London
                tz_NY = pytz.timezone('America/New_York')
                tz_London = pytz.timezone('Europe/London')

                # Get the current time in New York
                current_NY = datetime.now(tz_NY)
                # Get the current time in London
                current_London = datetime.now(tz_London)

                # Format the times as a string and print it
                print("Local time:", current.strftime("%H:%M:%S"))
                print("NY time:", current_NY.strftime("%H:%M:%S"))
                print("London time:", current_London.strftime("%H:%M:%S"))

                # Local time: 22:17:41
                # NY time: 15:17:41
                # London time: 20:17:41
            

Converting Between Timezones

Converting between timezones involves two steps: making the datetime object timezone-aware, and then converting it to the target timezone.

                from datetime import datetime
                import pytz

                # Create a timezone-aware datetime object
                sofia_tz = pytz.timezone('Europe/Sofia')
                sofia_now = datetime.now(sofia_tz)

                # Convert to another timezone
                kolkata_tz = pytz.timezone('Asia/Kolkata')
                kolkata_now = sofia_now.astimezone(kolkata_tz)

                print('Current datetime in Sofia', sofia_now.strftime("%d.%m.%Y, %H:%M"))
                print('Current datetime in Kolkata', kolkata_now.strftime("%d.%m.%Y, %H:%M"))


                # Current datetime in Sofia 11.02.2024, 22:22
                # Current datetime in Kolkata 12.02.2024, 01:52
            

Working with timezones via zoneinfo module

Overview

The built-in zoneinfo module, introduced in Python 3.9, integrates the IANA timezone database directly into Python, allowing for easier timezone management.

                from datetime import datetime
                from zoneinfo import ZoneInfo

                # Get the current time in Sofia
                current = datetime.now(ZoneInfo("Europe/Sofia"))
                print(current)
            

Find the current time of a certain timezone

Creating timezone-aware datetime objects is straightforward with zoneinfo.
Simply specify the desired timezone (List of tz database time zones ) when creating a datetime object.

                from datetime import datetime
                from zoneinfo import ZoneInfo

                # Get local current time:
                current =datetime.now()

                # Get the current time in New York
                current_NY = datetime.now(ZoneInfo("America/New_York"))
                # Get the current time in London
                current_London = datetime.now(ZoneInfo("Europe/London"))

                # Format the times as a string and print it
                print("Local time:", current.strftime("%H:%M:%S"))
                print("NY time:", current_NY.strftime("%H:%M:%S"))
                print("London time:", current_London.strftime("%H:%M:%S"))

                # Local time: 22:19:45
                # NY time: 15:19:45
                # London time: 20:19:45

            

Converting Between Timezones

Converting Between Timezones
Converting datetime objects from one timezone to another is easily done with the astimezone method.

                from datetime import datetime
                from zoneinfo import ZoneInfo

                # Create a timezone-aware datetime object
                sofia_now = datetime.now(ZoneInfo("Europe/Sofia"))

                # Convert to another Time
                kolkata_now = sofia_now.astimezone(ZoneInfo("Asia/Kolkata"))

                print('Current datetime in Sofia', sofia_now.strftime("%d.%m.%Y, %H:%M"))
                print('Current datetime in Kolkata', kolkata_now.strftime("%d.%m.%Y, %H:%M"))

                # Current datetime in Sofia 11.02.2024, 22:28
                # Current datetime in Kolkata 12.02.2024, 01:58
            

Examples

Examples

Get weekday name for today


            from datetime import datetime
            from zoneinfo import ZoneInfo

            def get_weekday_name(date, lang):
                """Returns the name of the day of the week for a given date in a specified language.

                :param date: A datetime object.
                :param lang: A string indicating the language ('bg' for Bulgarian, 'en' for English).
                :return: A string representing the name of the weekday.
                """
                weekday_names_mapping = {
                    'bg': ["понеделник", "вторник", "сряда", "четвъртък", "петък", "събота", "неделя"],
                    'en': ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
                }

                weekday_name = weekday_names_mapping[lang][date.weekday()]
                return weekday_name

            # Now using timezone-aware datetime
            now = datetime.now(ZoneInfo("Europe/Sofia"))

            print('Днес е', get_weekday_name(now, "bg"))
            print('Today is', get_weekday_name(now, "en"))

            # Днес е сряда
            # Today is Wednesday
        

When will my birthday be on given weekday name?


            import datetime

            def find_years_with_weekday(date, target_weekday, search_period):
                """Finds years within a specified period when a given date falls on a specified weekday.

                Parameters:
                    birth_date (datetime.datetime): The date of interest.
                    target_weekday (str): The name of the weekday to match.
                    search_period (tuple): A tuple specifying the start (inclusive) and end (exclusive) years to search.

                Returns:
                    list: A list of years within the search_period when birth_date falls on target_weekday.
                """
                matching_years = []
                weekdays = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]

                for year in range(*search_period):
                    current_date = datetime.date(year, birth_date.month, birth_date.day)
                    if weekdays[current_date.weekday()] == target_weekday:
                        matching_years.append(year)

                return matching_years



            birth_date = datetime.datetime.strptime("31.12.2000", "%d.%m.%Y")

            years_by_weekday = find_years_with_weekday(birth_date, 'Sunday', (2018,2100))
            print(years_by_weekday)

        

Calculates the Western Easter date for given period

Using the Samuel Butcher Algorithm, published in Computus @ wikipedia.


            import datetime

            def calc_easter_dates(year):
                "Returns Easter as a date object."
                a = year % 19
                b = year // 100
                c = year % 100
                d = (19 * a + b - b // 4 - ((b - (b + 8) // 25 + 1) // 3) + 15) % 30
                e = (32 + 2 * (b % 4) + 2 * (c // 4) - d - (c % 4)) % 7
                f = d + e - 7 * ((a + 11 * d + 22 * e) // 451) + 114
                month = f // 31
                day = f % 31 + 1

                return datetime.date(year, month, day)

            def calc_easter_dates_in_range(start, end):
                easter_dates = []
                for year in range(start, end+1):
                    easter_dates.append(calc_easter_dates(year).strftime("%d.%m.%Y"))

                return easter_dates

            easter_dates = calc_easter_dates_in_range(2020, 2024)
            print('\n'.join(easter_dates))
            print('*'*50)
        

Calculates the Western Easter date for given period using dateutil.easter

The dateutil package provides easter module, supporting Western, Ortodox and Julian Calendars.


            from dateutil.easter import easter

            def calc_easter_dates_in_range(start, end, method):
                easter_dates = []
                for year in range(start, end+1):
                    easter_dates.append(easter(year, method=method).strftime("%d.%m.%Y"))

                return easter_dates


            methods = ['EASTER_JULIAN', 'EASTER_ORTHODOX', 'EASTER_WESTERN']
            for method_idx in range(len(methods)):
                easter_dates = calc_easter_dates_in_range(2020, 2024, method=method_idx+1)
                print(f'Easter dates for {methods[method_idx]}:')
                print('\n'.join(easter_dates))
                print('*'*50)
        

Timing your code

Using the time built-in module


            import time
            from functools import reduce

            #start timer
            start = time.time()


            # do some time intensive stuff:
            num_range = (1, 10_000_000)
            sum = reduce(lambda x,y:x+y, range(num_range[0],num_range[1]+1))
            print(f"The sum of numbers from {num_range[0]} to {num_range[1]} is: ", sum)

            #end timer
            end = time.time()

            print("[Finished in {:.3f}s]".format(end - start))

            #The sum of numbers from 1 to 10000000 is:  50000005000000
            #[Finished in 0.187s]
        

For better precision (to account only the CPU time of your process ) use Python profilers built-in modules

Homework

Homework

The tasks are given in next gist file
You can copy it and work directly on it. Just put your code under "### Your code here".

These slides are based on

customised version of

Hakimel's reveal.js

framework