Date time

The datetime class is one of the core classes we encounter when tracking events at a given date and time. By default, creating an instance with datetime.now means that an offset-naive datetime object is produced in the host's local timezone.

An offset-naive datetime object is useful for scripts that are run on a personal device. Once we use datetime objects for web applications that are deployed globally, it's important to know the offset that datetime objects are aligned to before processing them. For more on datetime:

https://docs.python.org/3/library/datetime.html

Backend developers address this by storing time fields with offsets aligned with the UTC (Coordinated Universal Time) timezone. As a result, time fields can be displayed in any timezone - at any moment. For more on UTC:

https://en.wikipedia.org/wiki/Coordinated_Universal_Time

In this module, we will show the difference between offset-naive and offset-aware datetime objects. We will also highlight the builtin UTC timezone and show how it can be used to make the default datetime object more powerful.

from datetime import datetime, timezone


def convert_dt_to_utc_epoch(dt):
    """Convert datetime to UTC epoch seconds.

    Note that the timestamp method assumes that an offset-naive
    datetime instance is in the local timezone and converts its
    offset to UTC before making it a floating point number.
    """
    return dt.timestamp()


def convert_utc_epoch_to_dt(epoch):
    """Convert UTC epoch seconds to datetime."""
    return datetime.fromtimestamp(epoch, tz=timezone.utc)


def convert_dt_timezone(dt, tz):
    """Convert datetime timezone."""
    return dt.astimezone(tz=tz)


def get_utc_now_as_dt():
    """Get current UTC time as datetime."""
    return datetime.now(tz=timezone.utc)


def get_utc_now_as_epoch():
    """Get current UTC time as epoch seconds."""
    return convert_dt_to_utc_epoch(get_utc_now_as_dt())


def main():
    # Create offset-naive datetime
    naive_dt = datetime.now()
    assert naive_dt.tzinfo is None

    # Change offset-naive datetime to epoch seconds
    naive_dt_epoch = convert_dt_to_utc_epoch(naive_dt)
    assert naive_dt_epoch > 0

    # Change epoch seconds to UTC datetime
    utc_dt = convert_utc_epoch_to_dt(naive_dt_epoch)
    assert utc_dt.tzinfo is timezone.utc
    assert convert_dt_to_utc_epoch(utc_dt) == naive_dt_epoch

    # We cannot compute differences between offset-naive and offset-aware
    # datetime objects
    calc_failed = False
    try:
        _ = utc_dt - naive_dt
    except TypeError:
        calc_failed = True
    assert calc_failed is True

    # But we can change the timezone of an offset-naive datetime object
    # first before running operations on them
    assert convert_dt_timezone(naive_dt, timezone.utc) == utc_dt

    # Create new UTC time as datetime
    utc_dt_new_one = get_utc_now_as_dt()
    assert utc_dt_new_one > utc_dt

    # Create another new UTC time as epoch seconds
    utc_epoch_new_two = get_utc_now_as_epoch()
    utc_epoch_new_one = convert_dt_to_utc_epoch(utc_dt_new_one)
    assert utc_epoch_new_two > utc_epoch_new_one > naive_dt_epoch
    utc_dt_new_two = convert_utc_epoch_to_dt(utc_epoch_new_two)
    assert utc_dt_new_two > utc_dt_new_one > utc_dt


if __name__ == "__main__":
    main()