5.2. Datetime ISO Standard

  • ISO 8601 is an International Standard [2]

5.2.1. Dates

  • Year-Month-Day

  • Format: YYYY-mm-dd

  • Example: 1969-07-21

Example:

  • 1961-04-12

  • 1969-07-21

  • 1999-12-31

  • 2000-01-01

5.2.2. Time

  • 24 hour clock

  • Format: HH:MM:SS.ffffff or HH:MM:SS or HH:MM

  • Example: 12:34, 12:34:56, 12:34:56.123456

  • Optional seconds and microseconds

  • 00:00 - midnight, at the beginning of a day

  • 23:59:59.999999 - midnight, at the end of a day

Example:

  • 00:00

  • 02:56:15

  • 13:00:00.123

  • 23:59:59.999999

5.2.3. Date and Time

  • Format: YYYY-mm-ddTHH:MM:SS.ffffff

  • Example: 1969-07-21T02:56:15.123456

  • "T" separates date and time)

  • Optional seconds and microseconds

Example:

  • 1969-07-21T02:56

  • 1969-07-21T02:56:15

  • 1969-07-21T02:56:15.123

  • 1969-07-21T02:56:15.123456

5.2.4. Timezone

  • Format: YYYY-mm-ddTHH:MM:SS.ffffffUTC

  • Format: YYYY-mm-ddTHH:MM:SS.ffffffZ

  • Example: 1969-07-21T02:56:15.123456+0200

  • Optional seconds and microseconds

  • "Z" (Zulu) means UTC

Time zone notation:

  • <time>UTC

  • <time>Z

  • <time>±hh:mm

  • <time>±hhmm

  • <time>±hh

Example:

  • 1969-07-21T02:56:15.123456Z

  • 1969-07-21T02:56:15.123456UTC

  • 1969-07-21T02:56:15.123456CEST

  • 1969-07-21T02:56:15.123456CET

  • 1969-07-21T02:56:15.123456+02:00

  • 1969-07-21T02:56:15.123456+0200

  • 1969-07-21T02:56:15.123456+02

5.2.5. Week

  • Format: YYYY-Www

  • The ISO 8601 definition for week 01 is the week with the first Thursday of the Gregorian year (i.e. of January) in it. [1]

  • 2009-W01 - First week of 2009

  • 2009-W53 - Last week of 2009

5.2.6. Weekday

  • Format: YYYY-Www-dd

  • Week starts on Monday

  • ISO defines Monday as one

  • Note year/month changes during the week

  • 2009-W01-1 - Monday 29 December 2008

  • 2009-W53-7 - Sunday 3 January 2010

>>> from datetime import datetime
>>>
>>>
>>> dt = datetime(1969, 7, 21, 2, 56, 15)
>>>
>>> dt.isoweekday()
1
>>>
>>> dt.weekday()
0

5.2.7. Duration

  • Format: P...Y...M...DT...H...M...S

  • Example: P8Y3M8DT20H49M15S

  • P - period - placed at the start of the duration representation

  • Y - number of years

  • M - number of months

  • W - number of weeks

  • D - number of days

  • T - precedes the time components of the representation

  • H - number of hours

  • M - number of minutes

  • S - number of seconds

P8Y3M8DT20H49M15S

8 years
3 months
8 days
20 hours
49 minutes
15 seconds

5.2.8. To ISO Format

  • datetime.isoformat()

  • date.isoformat()

  • time.isoformat()

Format to string in ISO-8601 standard:

>>> from datetime import date, time, datetime
>>>
>>>
>>> dt = datetime(1969, 7, 21, 2, 56, 15)
>>> d = date(1969, 7, 21)
>>> t = time(2, 56, 15)
>>>
>>> dt.isoformat()
'1969-07-21T02:56:15'
>>>
>>> dt.isoformat(' ')
'1969-07-21 02:56:15'
>>>
>>> d.isoformat()
'1969-07-21'
>>>
>>> t.isoformat()
'02:56:15'

5.2.9. From ISO Format

  • datetime.fromisoformat()

  • date.fromisoformat()

  • time.fromisoformat()

Parse from string in ISO-8601 standard:

>>> from datetime import date, time, datetime
>>>
>>>
>>> datetime.fromisoformat('1969-07-21T02:56:15')
datetime.datetime(1969, 7, 21, 2, 56, 15)
>>>
>>> date.fromisoformat('1969-07-21')
datetime.date(1969, 7, 21)
>>>
>>> time.fromisoformat('02:56:15')
datetime.time(2, 56, 15)

Note, that .fromisoformat() is fault-tolerant:

>>> from datetime import date, time, datetime
>>>
>>>
>>> datetime.fromisoformat('1969-07-21T02:56:15')
datetime.datetime(1969, 7, 21, 2, 56, 15)
>>>
>>> datetime.fromisoformat('1969-07-21 02:56:15')
datetime.datetime(1969, 7, 21, 2, 56, 15)
>>>
>>> date.fromisoformat('1969-07-21')
datetime.date(1969, 7, 21)
>>>
>>> time.fromisoformat('02:56:15')
datetime.time(2, 56, 15)
>>>
>>> time.fromisoformat('2:56:15')
Traceback (most recent call last):
ValueError: Invalid isoformat string: '2:56:15'
>>>
>>> time.fromisoformat('2:56')
Traceback (most recent call last):
ValueError: Invalid isoformat string: '2:56'

5.2.10. Use Case - 0x01

>>> from datetime import datetime
>>>
>>>
>>> line = '1969-07-21T02:56:15.123 [WARNING] First step on the Moon'
>>>
>>> dt, lvl, msg = line.split(maxsplit=2)
>>>
>>> result = {
...     'when': datetime.fromisoformat(dt),
...     'level': lvl.strip('[]'),
...     'message': msg.strip(),
... }
>>>
>>> print(result)  
{'when': datetime.datetime(1969, 7, 21, 2, 56, 15, 123000),
 'level': 'WARNING',
 'message': 'First step on the Moon'}

5.2.11. References

5.2.12. Assignments

Code 5.12. Solution
"""
* Assignment: Datetime ISO Format
* Complexity: easy
* Lines of code: 1 lines
* Time: 2 min

English:
    1. Define `result: str` with `DATA` converted to ISO-8601 format
    2. Run doctests - all must succeed

Polish:
    1. Zdefiniuj `result: str` z przekonwertowaną `DATA` do formatu ISO-8601
    2. Uruchom doctesty - wszystkie muszą się powieść

Tests:
    >>> import sys; sys.tracebacklimit = 0

    >>> assert type(result) is str, \
    'Variable `result` has invalid type, must be a str'

    >>> result
    '1969-07-21T02:56:15'
"""

from datetime import datetime


DATA = datetime(1969, 7, 21, 2, 56, 15)

# DATA in ISO-8601 format: '1969-07-21T02:56:15'
# type: datetime
result = ...

Code 5.13. Solution
"""
* Assignment: Datetime ISO Parse
* Complexity: easy
* Lines of code: 1 lines
* Time: 2 min

English:
    1. Define `result: datetime` with converted `DATA` from ISO-8601
    2. Run doctests - all must succeed

Polish:
    1. Zdefiniuj `result: datetime` z przekonwertowaną `DATA` z ISO-8601
    2. Uruchom doctesty - wszystkie muszą się powieść

Tests:
    >>> import sys; sys.tracebacklimit = 0

    >>> assert type(result) is datetime, \
    'Variable `result` has invalid type, must be a datetime'

    >>> result
    datetime.datetime(1969, 7, 21, 2, 56, 15, 123000)
"""

from datetime import datetime


DATA = '1969-07-21T02:56:15.123'

# DATA from ISO-8601 format
# type: datetime
result = ...

Code 5.14. Solution
"""
* Assignment: Datetime ISO List
* Complexity: medium
* Lines of code: 1 lines
* Time: 3 min

English:
    1. Define `result: list[datetime]` with parsed `DATA` dates
    2. Use list comprehension and `datetime.fromisoformat()`
    3. Run doctests - all must succeed

Polish:
    1. Zdefiniuj `result: list[datetime]` ze sparsowanymi datami `DATA`
    2. Skorzystaj z rozwinięcia listowego oraz `datetime.fromisoformat()`
    3. Uruchom doctesty - wszystkie muszą się powieść

Hints:
    * `[x for x in DATA]`

Tests:
    >>> import sys; sys.tracebacklimit = 0
    >>> from pprint import pprint
    >>> result = list(result)

    >>> assert type(result) is list, \
    'Variable `result` has invalid type, must be a list'

    >>> assert all(type(element) is datetime for element in result), \
    'All elements in `result` must be a datetime'

    >>> pprint(result, width=30)
    [datetime.datetime(1961, 4, 12, 6, 7),
     datetime.datetime(1961, 4, 12, 6, 7)]
"""

from datetime import datetime

DATA = [
    '1961-04-12 06:07',
    '1961-04-12 06:07:00',
]

# parsed DATA
# type: list[datetime]
result = ...

Code 5.15. Solution
"""
* Assignment: Datetime ISO Logs
* Complexity: medium
* Lines of code: 7 lines
* Time: 13 min

English:
    1. Iterate over `DATA` with Apollo 11 timeline [1]
    2. From each line extract date, time, level and message
    3. Collect data to `result: list[dict]`
    4. Run doctests - all must succeed

Polish:
    1. Iteruj po `DATA` z harmonogramem Apollo 11 [1]
    2. Dla każdej linii wyciągnij datę, czas, poziom logowania oraz wiadomość
    3. Zbierz dane do `result: list[dict]`
    4. Uruchom doctesty - wszystkie muszą się powieść

Hint:
    * Note, that last time has no seconds
    * This is not bug, time without seconds is in NASA history records [1]

References:
    [1] National Aeronautics and Space Administration.
        Apollo 11 timeline.
        Year: 1969. Retrieved: 2021-03-25.
        URL: https://history.nasa.gov/SP-4029/Apollo_11i_Timeline.htm

Hints:
    * `str.splitlines()`
    * `str.split()`
    * `str.split(', ', maxsplit=3)`
    * `date.fromisoformat()`
    * `time.fromisoformat()`
    * `datetime.combine()`

Tests:
    >>> import sys; sys.tracebacklimit = 0
    >>> from pprint import pprint
    >>> result = list(result)

    >>> assert result is not Ellipsis, \
    'Assign result to variable: `result`'
    >>> assert type(result) is list, \
    'Variable `result` has invalid type, must be a list'
    >>> assert all(type(row) is dict for row in result), \
    'All elements in result must be dict'

    >>> pprint(result)
    [{'level': 'INFO',
      'message': 'Terminal countdown started',
      'when': datetime.datetime(1969, 7, 14, 21, 0)},
     {'level': 'WARNING',
      'message': 'S-IC engine ignition (#5)',
      'when': datetime.datetime(1969, 7, 16, 13, 31, 53)},
     {'level': 'DEBUG',
      'message': 'Maximum dynamic pressure (735.17 lb/ft^2)',
      'when': datetime.datetime(1969, 7, 16, 13, 33, 23)},
     {'level': 'WARNING',
      'message': 'S-II ignition',
      'when': datetime.datetime(1969, 7, 16, 13, 34, 44)},
     {'level': 'DEBUG',
      'message': 'Launch escape tower jettisoned',
      'when': datetime.datetime(1969, 7, 16, 13, 35, 17)},
     {'level': 'DEBUG',
      'message': 'S-II center engine cutoff',
      'when': datetime.datetime(1969, 7, 16, 13, 39, 40)},
     {'level': 'INFO',
      'message': 'Translunar injection',
      'when': datetime.datetime(1969, 7, 16, 16, 22, 13)},
     {'level': 'INFO',
      'message': 'CSM docked with LM/S-IVB',
      'when': datetime.datetime(1969, 7, 16, 16, 56, 3)},
     {'level': 'INFO',
      'message': 'Lunar orbit insertion ignition',
      'when': datetime.datetime(1969, 7, 16, 17, 21, 50)},
     {'level': 'INFO',
      'message': 'Lunar orbit circularization ignition',
      'when': datetime.datetime(1969, 7, 16, 21, 43, 36)},
     {'level': 'INFO',
      'message': 'CSM/LM undocked',
      'when': datetime.datetime(1969, 7, 20, 17, 44)},
     {'level': 'WARNING',
      'message': 'LM powered descent engine ignition',
      'when': datetime.datetime(1969, 7, 20, 20, 5, 5)},
     {'level': 'ERROR',
      'message': 'LM 1202 alarm',
      'when': datetime.datetime(1969, 7, 20, 20, 10, 22)},
     {'level': 'ERROR',
      'message': 'LM 1201 alarm',
      'when': datetime.datetime(1969, 7, 20, 20, 14, 18)},
     {'level': 'WARNING',
      'message': 'LM lunar landing',
      'when': datetime.datetime(1969, 7, 20, 20, 17, 39)},
     {'level': 'DEBUG',
      'message': 'EVA started (hatch open)',
      'when': datetime.datetime(1969, 7, 21, 2, 39, 33)},
     {'level': 'WARNING',
      'message': '1st step taken lunar surface (CDR)',
      'when': datetime.datetime(1969, 7, 21, 2, 56, 15)},
     {'level': 'WARNING',
      'message': 'Neil Armstrong first words on the Moon',
      'when': datetime.datetime(1969, 7, 21, 2, 56, 15)},
     {'level': 'DEBUG',
      'message': 'Contingency sample collection started (CDR)',
      'when': datetime.datetime(1969, 7, 21, 3, 5, 58)},
     {'level': 'INFO',
      'message': 'LMP on lunar surface',
      'when': datetime.datetime(1969, 7, 21, 3, 15, 16)},
     {'level': 'DEBUG',
      'message': 'EVA ended (hatch closed)',
      'when': datetime.datetime(1969, 7, 21, 5, 11, 13)},
     {'level': 'WARNING',
      'message': 'LM lunar liftoff ignition (LM APS)',
      'when': datetime.datetime(1969, 7, 21, 17, 54)},
     {'level': 'INFO',
      'message': 'CSM/LM docked',
      'when': datetime.datetime(1969, 7, 21, 21, 35)},
     {'level': 'WARNING',
      'message': 'Transearth injection ignition (SPS)',
      'when': datetime.datetime(1969, 7, 22, 4, 55, 42)},
     {'level': 'INFO',
      'message': 'CM/SM separation',
      'when': datetime.datetime(1969, 7, 24, 16, 21, 12)},
     {'level': 'WARNING',
      'message': 'Entry',
      'when': datetime.datetime(1969, 7, 24, 16, 35, 5)},
     {'level': 'WARNING',
      'message': 'Splashdown (went to apex-down)',
      'when': datetime.datetime(1969, 7, 24, 16, 50, 35)},
     {'level': 'INFO',
      'message': 'Crew egress',
      'when': datetime.datetime(1969, 7, 24, 17, 29)}]
"""
from datetime import date, datetime, time


DATA = """1969-07-14, 21:00:00, INFO, Terminal countdown started
1969-07-16, 13:31:53, WARNING, S-IC engine ignition (#5)
1969-07-16, 13:33:23, DEBUG, Maximum dynamic pressure (735.17 lb/ft^2)
1969-07-16, 13:34:44, WARNING, S-II ignition
1969-07-16, 13:35:17, DEBUG, Launch escape tower jettisoned
1969-07-16, 13:39:40, DEBUG, S-II center engine cutoff
1969-07-16, 16:22:13, INFO, Translunar injection
1969-07-16, 16:56:03, INFO, CSM docked with LM/S-IVB
1969-07-16, 17:21:50, INFO, Lunar orbit insertion ignition
1969-07-16, 21:43:36, INFO, Lunar orbit circularization ignition
1969-07-20, 17:44:00, INFO, CSM/LM undocked
1969-07-20, 20:05:05, WARNING, LM powered descent engine ignition
1969-07-20, 20:10:22, ERROR, LM 1202 alarm
1969-07-20, 20:14:18, ERROR, LM 1201 alarm
1969-07-20, 20:17:39, WARNING, LM lunar landing
1969-07-21, 02:39:33, DEBUG, EVA started (hatch open)
1969-07-21, 02:56:15, WARNING, 1st step taken lunar surface (CDR)
1969-07-21, 02:56:15, WARNING, Neil Armstrong first words on the Moon
1969-07-21, 03:05:58, DEBUG, Contingency sample collection started (CDR)
1969-07-21, 03:15:16, INFO, LMP on lunar surface
1969-07-21, 05:11:13, DEBUG, EVA ended (hatch closed)
1969-07-21, 17:54:00, WARNING, LM lunar liftoff ignition (LM APS)
1969-07-21, 21:35:00, INFO, CSM/LM docked
1969-07-22, 04:55:42, WARNING, Transearth injection ignition (SPS)
1969-07-24, 16:21:12, INFO, CM/SM separation
1969-07-24, 16:35:05, WARNING, Entry
1969-07-24, 16:50:35, WARNING, Splashdown (went to apex-down)
1969-07-24, 17:29, INFO, Crew egress"""

# representation of DATA; dict keys: when, level, message
# type: list[dict]
result = ...