datetime2 overview

The definition of a moment in history is independent from the way it is represented in different cultures. Indeed the same day is represented in different ways by different calendars. The datetime2 module detaches operations on date and time objects from their representation, and allows to add other representations at run time. The module does all this in an efficient and syntactically clear way.

We can create a date object by calling the relevant access attribute of the base class Date:

>>> d1 = Date.gregorian(1965, 3, 1)  # Gregorian: 1965-03-01
>>> d2 = Date.iso(2011, 23, 4)       # ISO: 2011-W23-4

Each of these date objects can be printed and has its own attributes and methods:

>>> print(d1.gregorian)
1965-03-01
>>> print(d1.gregorian.month)
3
>>> print(d1.gregorian.weekday())
1
>>> print(d2.iso)
2011-W23-4
>>> print(d2.iso.week)
23
>>> print(d2.iso.day_of_year())
158

One of the strength of datetime2 is that we can mix these attributes, independently from how the date object is built:

>>> print(d1.iso)
1965-W09-1
>>> print(d1.iso.week)
9
>>> print(d1.iso.day_of_year())
57
>>> print(d2.gregorian)
2011-06-09
>>> print(d2.gregorian.month)
6
>>> print(d2.gregorian.weekday())
4

The same strength is true also for time, where different representations can be mixed like in dates:

>>> t1 = Time.western(15, 47, 16, timezone=-6)
>>> t2 = Time.internet(895)
>>> print(t1.western)
15:47:16-06:00
>>> print(t1.internet)
@949
>>> print(t2.western)
21:28:48+01:00
>>> print(t2.western.minute)
28

The relation between time objects can be either implicit, i.e. depending on implementation, or explicit, which means that the objects know how they relate to each other. Then standard way for the latter is with UTC. An object of the first kind is said to be naive, of the second kind is called aware (like in Aware and Naive Objects of the datetime2 module). For aware objects a second value is used in the constructor to indicate the distance from UTC.

Many representations of the time of day of this module are aware by definition, so in those cases the UTC offset must not be given. E.g., the Internet time representation is based on Basel time zone (UTC+1).

Internal representation

In order to be able to convert between the different calendars and between the different times of day, a generic and culturally independent way of internally representing them has been chosen. For calendars, the idea, inspired by the excellent book “Calendrical Calculations”[1], is to identify each day by simply counting the days starting with 1 for January 1st of year 1, 2 for January 2nd is day 2, and so on. Using the datetime2 module we write:

>>> d3 = Date(1)
>>> d4 = Date(737109)

These dates can then be handled like above:

>>> print(d3.gregorian)
0001-01-01
>>> print(d3.iso)
0001-W01-1
>>> print(d4.gregorian)
2019-02-19
>>> print(d4.iso)
2019-W08-2

Similarly, each Date object can be printed with the internal representation (even if it is of little use):

>>> print(d1)
R.D. 717396
>>> print(d2)
R.D. 734297

Where R.D. stands for Rata Die, the Latin for “fixed date”.

There are similar generic representations for all base classes of the datetime2 module:

  • Time: a moment of the day is represented as a fraction of the day, starting from midnight. Distance from UTC, if given is also given as a fraction of a day.
>>> print(t1)
14209/21600 of a day, -1/4 of a day from UTC
>>> print(t2)
179/200 of a day, 1/24 of a day from UTC
>>> t3 = Time(7, 10)
>>> t4 = Time(0.796875, utcoffset="1/4")
>>> print(t3.western)
16:48:00
>>> print(t4.western)
19:07:30+06:00
>>> print(t4.internet)
@588
  • TimeDelta: a time interval is given in number of days, possibly fractional to account for parts of day.
>>> td1 = TimeDelta(8, 10)
>>> print(td1)
4/5 of a day
>>> print(td1.western)
19 hours, 12 minutes
>>> td2 = TimeDelta(118, 12)
>>> print(td2)
9 days and 5/6 of a day
>>> print(td2.western)
9 days and 20 hours
[1]“Calendrical Calculations: The Ultimate Edition”, E. M. Reingold, N. Dershowitz, Cambridge University Press, 2018

See also

Module datetime
Basic date and time types.
Module calendar
General calendar related functions.
Module time
Time access and conversions.

Index

datetime2 - New date and time types

The heart of the datetime2 module is made of four base classes, each having a very simple definition. All base classes implement operations for date and time independently of the way they are created.

datetime2 class names use the CapitalizedWords convention required by PEP 8, so they differ from the names of their similar counterparts in datetime module.

Date objects

A Date object represents a specific date. To do so it uses an idealized calendar, in which it counts the days elapsed from Gregorian Dec 31st of year 0, i.e. January 1st of year 1 is day number 1, January 2nd of year 1 is day number 2, and so on. This calendar ideally extends indefinitely in both directions.

There are two ways of creating a Date instance:

class datetime2.Date(day_count)

Return an object that represent a date which is day_count days after December 31st of year 0 in an ideal Gregorian calendar that has no limit. The argument day_count is required and it must be an integer, otherwise a TypeError exception is raised. There is no restriction on its numeric value.

classmethod Date.today()

Return a Date object that represents the current local date.

Date instances have one attribute:

Date.day_count

An integer that represents the number of days between the given date and January 1st, year 1. This attribute is read-only: an AttributeError exception is raised when trying to change it.

Date instances are immutable, so they can be used as dictionary keys. When two aware instances indicate the same time, even if they have different UTC offsets, the have the same hash. The hash function takes into consideration also that They can also be pickled and unpickled. In boolean contexts, all Date instances are considered to be true.

Date has one instance method:

Date.__str__()

Return R.D. followed by the day count. R.D. stands for Rata Die, the Latin for “fixed date”.

Available calendars

Currently (version 0.9.4) the calendars listed below are available.

Calendar Access attribute Calendar class Module
Gregorian gregorian GregorianCalendar datetime2.western
ISO iso IsoCalendar datetime2.modern

Supported operations

Operation Result
date2 = date1 + timedelta date2 is timedelta days after date1. Reverse addition (timedelta + date1) is allowed. (1) (2)
date2 = date1 - timedelta date2 is timedelta days before date1. (1) (3)
timedelta = date1 - date2 A TimeDelta object is returned representing the number of days between date1 and date2. (4)
date1 < date2 date1 is less than date2 when it represents a day earlier that that of date2. (5) (6)

Notes:

  1. A ValueError exception is raised if timedelta is not an integral number of days. timedelta object with non-integral number of days must be added or subtracted from DateTime instances.
  2. If timedelta is negative, date2 will be before date1.
  3. If timedelta is negative, date2 will be after date1.
  4. The timedelta instance created when subtracting Date instances will always have an integral number of days, positive if date1 is later than date2, negative otherwise.
  5. In other words, date1 < date2 if and only if date1.day_count < date2.day_count. All other comparison operators (<=, >, >=, == and !=) behave similarly.
  6. When comparing a Date object and an object of another class, if the latter has a day_count attribute, NotImplemented is returned. This allows a Date-like instance to perform reflected comparison if it is the second operator. When the second object doesn’t have a day_count attribute, if the operator is equality (==) or inequality (!=), the value returned is always False and True respectively. If the operator is one of the other four (<=, >, >= or ==), a TypeError exception is raised.

Time objects

An indication of time, independent of any particular day, expressed as a fraction of day. There might be an indication of time difference from UTC, e.g. due to time zone or daylight saving time. This time difference is expressed as fraction of a day and represents the time to be added to local time to get UTC. If there is this indication, the Time object is said to be “aware” and it is used to represent a precise moment (regardless of the day). An object without indication is said to be “naive”, and its interpretation is left to the program that uses it.

There are five Time constructors:

class datetime2.Time(day_frac, *, utcoffset=None)
class datetime2.Time(numerator, denominator, *, utcoffset=None)

Return an object that represents a moment in a day as a fraction of the whole day, given in the day_frac argument. If needed, it is possible to assign to the instance an indication of the time offset from UTC, for whatever political, algorithmic or geographic need (e.g. time zone), using the utcoffset argument, which must be explicitly named.

The day_frac and utcoffset arguments can be anything that can be passed to the fractions.Fraction constructor, i.e. an integer, a float, another Fraction, a Decimal number or a string representing an integer, a float or a fraction. The day_frac argument only can also be passed with two values that represent numerator and denominator of the fraction. A TypeError exception is raised if the type of any argument is not one of the accepted types. A ZeroDivisionError exception is raised if the denominator is 0.

The value for day_frac must be equal or greater than 0 and less than 1. The value for utcoffset in aware objects must be equal or greater than -1 and less or equal to 1. A ValueError exception is raised if values are outside these ranges.

classmethod Time.now(utcoffset=None)

Return an aware Time object that represents the current time. Without argument, the time represented in day_frac will be local standard time and utcoffset will be set to the difference between local standard time and UTC.

If utcoffset is given, the returned object will be the current time at the given time difference from UTC. utcoffset follows the same requirements of the default constructor.

classmethod Time.localnow()

Return a naive Time object that represents the current local standard time.

classmethod Time.utcnow()

Return a naive Time object that represents the current standard UTC.

Two read-only attributes store the day_frac and utcoffset arguments. The former is always a Fraction object, the latter is either a Fraction object or None, for naive time. An attempt to directly set the values of these two attributes will raise an AttributeError exception.

Time objects support comparison, where time1 is considered less than time2 when the former represents a moment earlier than the latter. UTC offset in aware instances is always taken into account. When both objects are Time instances they must have the same naivety, otherwise TypeError is raised if an order comparison is attempted, while for equality comparisons, naive instances are never equal to aware instances.

When comparing a Time object and an object of another class, if the latter has the day_frac and utcoffset attributes, NotImplemented is returned. This allows a Time-like instance to perform reflected comparison if it is the second operator. In this case, the second object is responsible for checking naivety.

Time instances are immutable, so they can be used as dictionary keys. They can also be pickled and unpickled. In boolean contexts, all Time instances are considered to be true.

Instance method:

Time.__str__()

Return the string <fraction> of a day, where fraction is the value of the day_frac attribute. UTC offset, if present, is represented as well:

>>> print(Time(4, 12))
1/3 of a day
>>> print(Time(3, 24, utcoffset="-4/24"))
1/8 of a day, -1/6 of a day from UTC

Available time representations

Currently (version 0.9.4) the time of day listed below are available.

Representation Attribute Time representation class Module
Western western WesternTime datetime2.western
Internet internet InternetTime datetime2.modern

Supported operations

Operation Result
time2 = time1 + timedelta time2 is timedelta time after time1. Reverse addition (timedelta + time1) is allowed. (1) (2)
time2 = time1 - timedelta time2 is timedelta time before time1. (1) (3)
timedelta = time1 - time2 A TimeDelta object is returned representing the day fraction between time1 and time2. (4)
time1 < time2 time1 is less than time2 when the former represents a moment earlier than the latter. UTC offset, if present, is taken into consideration. (5) (6) (7)

Notes:

  1. The result of this operation will always be a valid Time instance. If overflow or underflow occur, the full day part will be truncated so that only the fractional part will remain. Naivety is preserved: if time1 has a UTC offset, this will be copied to time2.
  2. If timedelta is negative, time2 will be before time1.
  3. If timedelta is negative, time2 will be after time1.
  4. The timedelta object created when subtracting two Time instances will always represent a fractional part of a day, with the days attribute value greater than -0.5 and less or equal to 0.5. time1 and time2 must have the same naivety; if they don’t, a ValueError exception is raised. If they are aware, UTC offset of both instances will be taken into account to generate the result.
  5. All other comparison operators (<=, >, >=, == and !=) behave similarly.
  6. If both objects to be compared are Time instances, they must have the same naivety; if they don’t, a ValueError exception is raised.
  7. When comparing a Time object and an object of another class, if the latter has a day_frac attribute, NotImplemented is returned. This allows a Time-like instance to perform reflected comparison if it is the second operator. In this case, the second object is responsible for checking naivety. If the second object doesn’t have a day_frac attribute, if the operator is equality (==) or inequality (!=), the value returned is always False and True respectively. If the operator is one of the other four (<=, >, >= or ==), a TypeError exception is raised.

Note

Given the rules above it, if time1 and time2 are aware instances, time1 + (time2 - time1) compares equal to time2, but it will have the same day_frac value only if the UTC offsets of time1 and time2 are equal.

TimeDelta objects

An interval of time, expressed in fractional days.

There are two TimeDelta constructors:

class datetime2.TimeDelta(fractional_days)
class datetime2.TimeDelta(numerator, denominator)

Return an object that represents a time interval in fractional days, given in the fractional_days argument. This value will be greater than 1 to indicate an interval longer than 1 day.

The fractional_days argument can be anything that can be passed to the fractions.Fraction constructor, i.e. an integer, a float, another Fraction, a Decimal number or a string representing an integer, a float or a fraction. The argument can also be passed with two values that represent numerator and denominator of the fraction. A TypeError exception is raised if the type of any argument is not one of the accepted types. A ZeroDivisionError exception is raised if the denominator is 0. There are no limits on the value of fractional_days.

The read-only attribute fractional_days stores the value, always as a Python Fraction object. An attempt to directly set the values of this attribute will raise an AttributeError exception. It is also possible to access the integral and fractional parts of fractional_days with two calculated attributes: int_part and frac_part. If the time interval is negative, both int_part and frac_part are negative. Given any TimeDelta instance td, it is always: td.fractional_days == td.int_part() + td.frac_part()

>>> td1 = Timedelta(16, 3)
>>> td1.int_part
5
>>> td1.frac_part
Fraction(1, 3)
>>> td2 = TimeDelta(-7.625)
>>> td2.int_part
-7
>>> td2.frac_part
Fraction(-5, 8)

TimeDelta objects support comparison, where timedelta1 is considered greater than timedelta2 when the former represents a time interval longer than the latter. When comparing a TimeDelta object and an object of another class, if the latter has the fractional_days attribute, NotImplemented is returned. This allows a TimeDelta-like instance to perform reflected comparison if it is the second operator.

TimeDelta instances are immutable, so they can be used as dictionary keys. They can also be pickled and unpickled.

In boolean contexts, a TimeDelta instance is considered to be true if and only if it isn’t equal to TimeDelta(0).

Instance methods:

TimeDelta.int()
TimeDelta.frac()

The first method return the same instance with only the integer part. The last method returns the same instance with only the fractional part. All methods return a negative value if the time interval is negative. In this way, given any TimeDelta instance td, it is always: td == td.int() + td.frac()

>>> td1 = Timedelta(16, 3)
>>> td1.int()
TimeDelta(Fraction(5, 1))
>>> td1.frac()
TimeDelta(Fraction(1, 3))
>>> td2 = TimeDelta(-7.625)
>>> int(td2)
TimeDelta(Fraction(-7, 1))
>>> td2.frac()
TimeDelta(Fraction(-5, 8))
TimeDelta.is_integer()

Returns True if the time interval is made of an integer number of days.

>>> Timedelta("3/4").is_integer()
False
>>> TimeDelta(-1).is_integer()
True
TimeDelta.__str__()

Returns a string indicating the number of days and the remaining fraction of a day. Note that whilst in datetime.timedelta the fractional part is always positive, in TimeDelta the fractional part has the same sign of the integer part.

>>> td1 = Timedelta("1/12")
>>> print(td1)
1/12 of a day
>>> td2 = TimeDelta(3)
>>> print(td2)
3 days
>>> td3 = TimeDelta(11, -7)
>>> print(td3)
-1 day and -4/7 of a day

Available time interval representations

The following table lists the available time representations interval and the attributes by which they are reachable:

Representation Attribute Time representation class Module
Western western WesternTimeDelta datetime2.western

Note

Not available in version 0.9.0.

Supported operations

Operation Result
timedelta1 = timedelta2 + timedelta3 Sum of two time intervals.
timedelta1 = timedelta2 - timedelta3 Difference of two time intervals.
timedelta1 = timedelta2 * number or timedelta1 = number * timedelta2 Multiplication of a time interval by a number. (1)
timedelta1 = timedelta2 / number Division of a time interval by a number. (1)
number = timedelta1 / timedelta2 Returns a fraction which is the ratio between the two time intervals.
timedelta1 = timedelta2 // number Floor division. Returns a time interval with an integer number of days. If dividend and divisor are of different sign, the result is negative and, if not integer, it is more negative than the true result.
number = timedelta1 // timedelta2 Integer number of times timedelta2 is contained in timedelta1. If dividend and divisor are of different sign, the result is negative and, if not integer, it is more negative than the true result.
timedelta1 = timedelta2 % divisor Remainder of the division. This result always has the same sign of the divisor.
divmod(timedelta, divisor) Return a tuple made of the integral quotient and remainder of timedelta divided by dividend. (2)
timedelta1 < timedelta2 timedelta1 is less than timedelta2 when the former represents an interval shorter than the latter. (3)

The table above does not include mixed type operations between TimeDelta and Date, Time or DateTime. For more information, see the Supported operations chapter of each of these classes.

Class TimeDelta also upports unary arithmetic operators +, - and abs().

Notes:

  1. The number is first converted to a fraction, then multiplication or division takes place. As such, if number is a float, float to Fraction conversion error may happen, and result may not be exact.
  2. If dividend is a number, see note (1).
  3. All other comparison operators (<=, >, >=, == and !=) behave similarly.

datetime2.western - Gregorian calendar and western time

This module implements the calendar and time representation used in the western world:

Of course, they all conform to the requirements for interface classes listed in Customization.

Gregorian calendar

An instance of the GregorianCalendar class represents a day in the calendar as generally done in western countries. It is a solar calendar dividing day count in years of 365 or 366 days, each year is then divided in 12 months of 28 (or 29), 30 and 31 days.

The default constructor for a Gregorian day is:

class datetime2.western.GregorianCalendar(year, month, day)

Return an object that represents the date given with Gregorian year, month and day. Month is entered as a number, not as a string. All arguments are required and must be integers. Values for month and day must lie in the following ranges:

  • 1 <= month <= 12
  • 1 <= day <= number of days in the given month and year

If an argument is outside those ranges, a ValueError exception is raised.

Another constructor can be used if the day in the year is known:

classmethod GregorianCalendar.year_day(year, day_of_year)

Return an object that represents the day specified by a Gregorian year and the day in that year. Both arguments are required and must be integers. Value for day_of_year must be between 1 and the number of days in the year (either 365 or 366), otherwise a ValueError exception is raised.

A GregorianCalendar object has three attributes:

GregorianCalendar.year
GregorianCalendar.month
GregorianCalendar.day

These attributes are read-only integer numbers. There is no restriction on the value of the year. Month will be between 1 and 12. Day will be between 1 and the number of days in the corresponding month. These attributes are read-only: an AttributeError exception is raised when trying to change any of them.

Two static method have been implemented to return details of a Gregorian year:

static GregorianCalendar.is_leap_year(year)

Return True if year is a leap year in the Gregorian calendar. False otherwise. For example, GregorianCalendar.is_leap_year(2008) == True.

static GregorianCalendar.days_in_year(year)

Return 366 if year is a leap year in the Gregorian calendar, 365 otherwise. For example, GregorianCalendar.days_in_year(2100) == 365.

An instance of the GregorianCalendar class has the following methods:

GregorianCalendar.weekday()

Return the day of the week as an integer, where Monday is 1 and Sunday is 7. For example, GregorianCalendar(2002, 12, 4).weekday() == 3, a Wednesday. Note that this is the ISO convention for weekdays, not the one used by datetime.date.weekday(), where Monday is 0 and Sunday is 6.

GregorianCalendar.day_of_year()

Return the number of days elapsed since January 1st. The result is a number from 1 to 365 or 366 (in leap years). For example, GregorianCalendar(2008, 3, 1).day_of_year() == 61.

GregorianCalendar.replace(year, month, day)

Returns a new GregorianCalendar object with the same value, except for those parameters given new values by whichever keyword arguments are specified. All values are optional; if used, they must be integers. If any argument is outside its validity range or would create an invalid Gregorian date, a ValueError exception is raised. For example:

>>> greg = GregorianCalendar(2002, 12, 31)
>>> print(greg.replace(day=26))
2002-12-26
>>> greg.replace(month=11)         # November has 30 days
Traceback (most recent call last):
  |
ValueError: Day must be between 1 and number of days in month, while it is 31.
GregorianCalendar.__str__()

Return a string representing the date with the ‘YYYY-MM-DD’ format. Years above 9999 are represented adding necessary figures. Negative years are represented prepending the minus sign. For example:

>>> str(GregorianCalendar(2002, 12, 4))
'2002-12-04'
>>> str(GregorianCalendar(-1, 1, 1))
'-0001-01-01'
GregorianCalendar.cformat(format)

Return a string representing the date, controlled by an explicit format string. The formatting directives are a subset of those accepted by datetime.date.strftime(), and their meaning does not depend on the underlying C library (i.e. there are no platform variations). The table below lists the accepted formatting directives, all other character are not interpreted.

Directive Meaning Notes
%a Abbreviated weekday name. (1)
%A Full weekday name. (1)
%b Abbreviated month name. (1)
%B Full month name. (1)
%d Day of the month as a decimal number [01, 31].  
%j Day of the year as a decimal number [001, 366].  
%m Month as a decimal number [01, 12].  
%U Week number of the year (Sunday as the first day of the week) as a decimal number [00, 53]. All days in a new year preceding the first Sunday are considered to be in week 0.  
%w Weekday as a decimal number [1 (Monday), 7 (Sunday)].  
%W Week number of the year (Monday as the first day of the week) as a decimal number [00, 53]. All days in a new year preceding the first Monday are considered to be in week 0.  
%y Year without century as a decimal number [00, 99]. (2)
%Y Year with century as a decimal number. At least four figures will be returned. (3)
%% A literal '%' character.  

Notes:

  1. The %a, %A, %b and %B directives return a localized name in Standard C++. This is not true for datetime2, which only returns English names.
  2. Since this is a truncated representation, negative years will not have a sign.
  3. Negative years will have a trailing '-'.

Western time

An instance of the WesternTime class represents a moment of a day as generally done in western countries, dividing each day in 24 hours, each hour in 60 minutes and each minute in 60 seconds.

The default western time constructor is:

class datetime2.western.WesternTime(hour, minute, second, timezone=None)

Return an object that represents the moment of a day in hour, minute and second elapsed from midnight. This representation does not take into account the possibility of one or two additional seconds that sometimes are added in specific dates to compensate earth rotation. All arguments except timezone are required. The following requirements must be satisfied:

  • hour must be an integer and 0 <= hour < 24
  • minute must be an integer and 0 <= minute < 60
  • second must be a rational number and its value must be 0 <= second < 60
  • timezone, if present, must be a rational number and its value must be -24 <= timezone <= 24

Here a rational number is anything that can be passed to the fractions.Fraction constructor, i.e. an integer, a float, another Fraction, a Decimal number or a string representing an integer, a float or a fraction.

If an argument is not of the accepted type, a TypeError exception is raised. If an argument is outside its accepted range, a ValueError exception is raised.

The timezone argument, if present, makes the object aware and defines the number of hours that must be added to UTC to get local time.

Note

The timezone parameter is likely to change its values in future.

A WesternTime object has four attributes, all of which are read-only numbers: an attempt to change them will raise an AttributeError exception. These attributes store the corresponding values in the constructor:

WesternTime.hour

An integer with values between 0 and 23.

WesternTime.minute

An integer with values between 0 and 59.

WesternTime.second

A Python Fraction with value grater or equal to 0 and less than 60.

WesternTime.timezone

If this attribute is not None, it a Python Fraction with values between -24 and 24.

An instance of the WesternTime class has the following methods:

WesternTime.replace(hour, minute, second, *, timezone)

Returns a new WesternTime object with the same value, except for those parameters given new values by whichever keyword arguments are specified. The value, if given, they must respect the same requirements of the default constructor, otherwise a TypeError or ValueError exception is raised. timezone parameter can be replaced only for aware instances. For example:

>>> my_time = WesternTime(19, 6, 29)
>>> print(my_time.replace(minute=38))
19:38:29
>>> my_time.replace(hour=24)
Traceback (most recent call last):
  |
ValueError: Hour must be between 0 and 23, while it is 24.
>>> my_time.replace(timezone=1)
Traceback (most recent call last):
  |
TypeError: Can replace timezone only in aware instances.
WesternTime.__str__()

For a naive instance, return a string representing the time with the ‘HH:MM:SS’ format. For an aware instance, the format is ‘HH:MM:SS+HH:MM’. The number of seconds in the time part and the number of minutes in the timezone part will be truncated. For example:

>>> str(WesternTime(12, 44, 14.8))
'12:44:14'
>>> str(WesternTime(12, 34, 56.7, timezone=12.256))
'12:34:56+12:15'
WesternTime.cformat(format)

Return a string representing the time, controlled by an explicit format string. The formatting directives are a subset of those accepted by datetime.date.strftime(), and their meaning does not depend on the underlying C library (i.e. there are no platform variations). The table below lists the accepted formatting directives, all other characters are not interpreted.

Directive Meaning Notes
%H Hour (24-hour clock) as a zero-padded decimal number [00, 23].  
%I Hour (12-hour clock) as a zero-padded decimal number [01, 12].  
%p Returns ‘AM’ if hour is between 0 and 11, ‘PM’ if hour is between 12 and 23. (1)
%M Minute as a zero-padded decimal number [00, 59].  
%S Second as a zero-padded decimal number [00, 59].  
%f Microsecond as a decimal number, zero-padded on the left [000000, 999999].  
%z UTC offset in the form ±HHMM[SS[.ffffff]] (empty string if the object is naive).  
%% A literal '%' character.  

Notes:

  1. The %p directive returns a localized string in Standard C++. This is not true for datetime2, which only returns the English string.

Western time interval

An instance of the WesternTimeDelta class represents a time interval given in days, hours, minutes and seconds.

The default constructor is:

class datetime2.western.WesternTimeDelta(days, hours, minutes, seconds)

Return an object that represents a time interval in hours, minutes and seconds. All arguments are required. hours, minutes and seconds must have the same sign of days. The types and absolute values of each parameter are listed below:

  • days must be an integer of any value
  • hours must be an integer and its absolute value must be 0 <= hours <= 23
  • minutes must be an integer and its absolute value must be 0 <= minutes <= 59
  • seconds must be a rational number and its absolute value must be 0 <= second < 60

Here a rational number is anything that can be passed to the fractions.Fraction constructor, i.e. an integer, a float, another Fraction, a Decimal number or a string representing an integer, a float or a fraction.

If an argument is not of the accepted type, a TypeError exception is raised. If an argument is outside its accepted range or all parameter haven’t the same sign, a ValueError exception is raised.

A WesternTimeDelta object has four attributes, all of which are read-only numbers: an attempt to change them will raise an AttributeError exception. These attributes store the corresponding values in the constructor:

WesternTime.days

An integer of any value.

WesternTime.hours

An integer of the same sign as days and with absolute value between 0 and 23.

WesternTime.minutes

An integer of the same sign as days and with absolute value between 0 and 59.

WesternTime.seconds

A Python Fraction of the same sign as days and with absolute value grater or equal to 0 and less than 60.

An instance of the WesternTimeDelta class has the following methods:

WesternTimeDelta.replace(days, hours, minutes, seconds)

Returns a new WesternTimeDelta object with the same value, except for those parameters given new values by whichever keyword arguments are specified. The value, if given, they must respect the same requirements of the default constructor, otherwise a TypeError or ValueError exception is raised. For example:

>>> my_td = WesternTimeDelta(1, 23, 45, 6)
>>> print(my_td.replace(minutes=0))
1 day, 23 hours and 6 seconds
>>> my_td.replace(hours=24)
Traceback (most recent call last):
  |
ValueError: Hours must be between 0 and 23, while it is 24.
>>> my_time.replace(secondsì'33')
Traceback (most recent call last):
  |
ValueError: Seconds must be of the same sign of 'days'.
WesternTimeDelta.__str__()

Return a string representing the time interval. When a component of the interval is zero, it is not printed. The number of seconds will be truncated. For example:

>>> str(WesternTimeDelta(9, 8, 7, 6.5))
'9 days, 8 hours, 7 minutes and 6 seconds'
>>> str(WesternTimeDelta(0, 0, -5, -2))
'-5 minutes and -2 seconds'
WesternTimeDelta.cformat(format)

Return a string representing the time, controlled by an explicit format string. The formatting directives are a subset of those accepted by datetime.date.strftime(), and their meaning does not depend on the underlying C library (i.e. there are no platform variations). The table below lists the accepted formatting directives, all other characters are not interpreted.

Directive Meaning
%d Full days as a decimal number.
%H Hours as a zero-padded decimal number [00, 23].
%M Minutes as a zero-padded decimal number [00, 59].
%S Seconds as a zero-padded decimal number [00, 59].
%f Microseconds as a zero-padded decimal number [000000, 999999].
%% A literal '%' character.

datetime2.modern - ISO calendar and Internet time

This module implements a calendar and a time representation that have been defined in the recent years:

Of course, they all conform to the requirements for interface classes listed in Customization.

ISO calendar

The ISO calendar divides the days into weeks, from Monday to Sunday, and groups 52 or 53 whole weeks into a year. The first calendar week of a year is the one that includes the first Thursday of the corresponding Gregorian year. This definition can be seen also as: the first calendar weeks of a ISO year is the week including January, 4th Gregorian.

A good discussion of the ISO calendar can be read at The Mathematics of the ISO 8601 Calendar.

The constructor of an ISO calendar is:

class datetime2.modern.IsoCalendar(year, week, day)

Return an object that represents the date given with ISO year, week number and day. All arguments are required and must be integers. Values for week and day must lie in the following ranges:

  • 1 <= week <= number of weeks in the given year
  • 1 <= day <= 7

If an argument is outside those ranges, a ValueError exception is raised. They day number goes from 1 (Monday) to 7 (Sunday).

An IsoCalendar object has three attributes:

IsoCalendar.year
IsoCalendar.week
IsoCalendar.day

These attributes are read-only integer numbers. Week will be between 1 and the number of weeks in the ISO year (52 or 53), day will be between 1 and 7.

Two static method have been implmented to give details of an ISO year:

classmethod IsoCalendar.is_long_year(year)

Return True if year is a long year, i.e. a year with 53 weeks, in the ISO calendar, False otherwise. For example, IsoCalendar.is_leap_year(2004) == True.

classmethod IsoCalendar.weeks_in_year(year)

Return the number of weeks in a ISO year, either 52 or 53. For example, IsoCalendar.weeks_in_year(2009) == 53.

An instance of the IsoCalendar class has the following methods:

IsoCalendar.day_of_year()

Return the day of the year as an integer, from 1 to 364 (in short years) or 371 (in long years). For example, IsoCalendar(2008, 3, 1).day_of_year() == 62.

IsoCalendar.replace(year, week, day)

Returns a new IsoCalendar object with the same value, except for those parameters given new values by whichever keyword arguments are specified. All values are optional; if used, they must be integers. If any argument is outside its validity range or would create an invalid Gregorian date, a ValueError exception is raised. For example:

>>> iso = IsoCalendar(2004, 53, 3)
>>> print(iso.replace(week=26))
2004-W26-3
>>> iso.replace(year=2003)  # 2003 has 52 weeks
Traceback (most recent call last):
  |
ValueError: Week must be between 1 and number of weeks in year, while it is 53.
IsoCalendar.__str__()

Return a string representing the date with the ‘YYYY-WWW-DD’ format. Years above 9999 are represented adding necessary figures. Negative years are represented prepending the minus sign. For example:

>>> str(IsoCalendar(2002, 12, 4))
'2002-W12-4'
>>> str(IsoCalendar(-1, 1, 1))
'-0001-W01-1'
IsoCalendar.cformat(format)

Return a string representing the ISO date, controlled by an explicit format string. The formatting directives are a subset of those accepted by datetime.date.strftime(), and their meaning does not depend on the underlying C library (i.e. there are no platform variations). The table below lists the accepted formatting directives, all other character are not interpreted.

Directive Meaning Notes
%a Abbreviated weekday name. (1)
%A Full weekday name. (1)
%j Day of the year as a decimal number [001,371].  
%w Weekday as a decimal number [1 (Monday), 7 (Sunday)].  
%W Week number in the ISO year as a decimal number [01, 53].  
%y ISO year without century as a decimal number [00, 99]. (2)
%Y ISO year with century as a decimal number. At least four figures will be returned. (3)
%% A literal '%' character.  

Notes:

  1. The %a and %A directives return a localized name in Standard C++. This is not true for datetime2, which only returns English names.
  2. Since this is a truncated representation, negative years will not have a sign.
  3. Negative years will have a trailing '-'.

Internet time

The Internet Time (or beat time) is a decimal time concept introduced in 1998, marketed by a large Swiss watch company, and divides the day in 1000 parts, called “beats”. A beat is equivalent to 1 minute and 26.4 seconds. A Wikipedia article describes the Internet time. The Internet time is aware by definition.

The default constructor for Internet time is:

class datetime2.modern.InternetTime(beat)

Return an object that represents the time in thousandths of a day. The beat argument is required and must be a anything that can be passed to the fractions.Fraction constructor, i.e. an integer, a float, another Fraction, a Decimal number or a string representing an integer, a float or a fraction. Its value must be equal or greater than 0 and less than 1000. If the argument is not of one of the possible types, a TypeError exception is raised. If the argument is outside its accepted range, a ValueError exception is raised.

An InternetTime object has one attribute:

InternetTime.beat

This attribute is a read-only Python Fraction greater than or equal to 0 and less than 1000.

and the following methods:

InternetTime.__str__()

Return a string representing the moment of the day in beats, @BBB’ format. For example:

>>> str(InternetTime(345.25))
'@345'
InternetTime.cformat(format)

Return a string representing the Internet time, controlled by an explicit format string with formatting directives close to that used in C. The table below lists the accepted formatting directives, all other character are not interpreted.

Directive Meaning Notes
%b Integer number of beats [000, 999].  
%f Thousandths of a beat, zero-padded on the left [000, 999]. (1)

Notes:

  1. One thousandth of a beat is a millionth of a day, i.e. 86.4 milliseconds.

datetime2 customization

Interface

Base classes of the datetime2 module have little if no practical use as they natively are: even if it stands out for its simplicity, rata die is not a common way of representing dates in the real world. Same consideration can be done about time as a fraction of a day.

A mechanism based on attributes, here called “access” attributes, has been implemented to give access to a wide variety of calendars and time representations.

When used on the base class, the attribute behaves as a constructor of the base class, but with the arguments of the specific representation:

>>> d = Date.gregorian(2013, 4, 18)
>>> d
datetime2.Date(734976)
>>> t = Time.western(17, 16, 28)
>>> t
datetime2.Time('15547/21600')

When used on a base class instance, the attribute allows to see the instance using a specific representation, or to call methods defined for that specific representation:

>>> d = Date(1)
>>> str(d.gregorian)
'0001-01-01'
>>> d.gregorian.month
1
>>> d.gregorian.weekday()
1
>>> t = Time(Fraction(697, 1440))
>>> str(t.western)
'11:37:00'
>>> t.western.minute
37

The attribute gives access to what is called an “interface class”. The interface class is the one that manages converting a specific representation (e.g. the Gregorian calendar) to the base class. The Available calendars table lists all available interface classes for calendars. The Available time representations chapter lists all available interface classes for time.

The real power of this paradigm is that we can create a base class instance with an access attribute and see its value with another access attribute, or use different access attributes on the same base class instance. In this way, the base class object is unchanged, but it can bee seen in many different ways.

>>> d = Date.gregorian(2013, 4, 22)
>>> d.iso.week
17
>>> t = Time(0.5, utcoffset='-1/6')
>>> str(t.western)
'12:00:00'
>>> t.internet.beat
Fraction(625, 1)

An intended feature of datetime2 is that any representations is computed only once, when first accessed, then remains available to the base class.

Interface class may have, as it is normal, additional constructors. E.g. the GregorianCalendar class has the GregorianCalendar.year_day() and GregorianCalendar.replace() methods that return a GregorianCalendar instance. However, thanks to some magic explained later, when such constructors are accessed via the attribute mechanisms on the base class or an instance of it, constructors of the interface class return instances of the base class instead, as shown in this example:

>>> greg = GregorianCalendar.year_day(2012, 366)
>>> greg
GregorianCalendar(2012, 12, 31)
>>> d1 = Date.gregorian.year_day(2012, 366)
>>> d1
datetime2.Date(734868)
>>> str(d1.gregorian)
'2012-12-31'
>>> d2 = d1.gregorian.replace(year = 2013, month = 7)
>>> d2
datetime2.Date(735080)
>>> str(d2.gregorian)
'2013-07-31'

And, as expected, static methods of the interface classes are unchanged even when invoked via access attribute:

>>> Date.gregorian.is_leap_year(2012)
True

Customization

Base classes provide a mechanism to register new interface classes at run-time. The same mechanism is indeed used to register already available interface classes at module import time. The interface class must respect a few simple requirements shown later.

Before examining these requisites in detail, let’s have a look at a simple example: we want to define a new calendar that defines each day by indicating the week number and the week day, counting the week of January 1st of year 1 as week 1 and so on. In addition, this new calendar has a non-default constructor that takes as argument also thousands of weeks:

>>> class SimpleWeekCalendar():
...     def __init__(self, week, day):
...         self.week = week
...         self.day = day
...     @classmethod
...     def from_rata_die(cls, rata_die):
...         return cls((rata_die - 1) // 7 + 1, (rata_die - 1) % 7 + 1)
...     def to_rata_die(self):
...         return 7 * (self.week - 1) + self.day
...     def __str__(self):
...         return 'W{}-{}'.format(self.week, self.day)
...     @classmethod
...     def with_thousands(cls, thousands, week, day):
...         return cls(1000 * thousands + week, day)
...
>>> Date.register_new_calendar('week_count', SimpleWeekCalendar)
>>> d1 = Date.week_count(1, 1)
>>> d1
datetime2.Date(1)
>>> str(d1.gregorian)
'0001-01-01'
>>> d2 = Date.gregorian(2013, 4, 26)
>>> str(d2.week_count)
'W104998-5'
>>> d3 = Date.week_count.with_thousands(104, 998, 5)
>>> d2 == d3
True

As can be seen in the example, the new interface class completely ignores the way a base class instance works. The requirements for an interface class to be used by the registration module are:

  • Have a non-default forward constructor, that creates an instance of the interface class using the base class attribute.
  • Have a backward method that returns the base class attribute corresponding to the interface class value.
  • All other non-default constructors and all methods returning an interface class instance must use the interface class default constructor.

Once the new interface class is ready, the call of a registration method of the base class does the magic.

Each datetime2 base class has a specific registration function. Required methods also have names depending on the base class they are registered to. The following table lists all these names:

  Base Classes
  Date Time DateTime TimeDelta
Registration function register_new_calendar register_new_time TBD register_new_time_interval
Non-default constructor from_rata_die from_time_pair TBD from_fractional_days
Conversion method to_rata_die to_time_pair TBD to_fractional_days

These methods are detailed below:

classmethod Date.register_new_calendar(access_attribute, CalendarInterface)

Register the CalendarInterface class to the Date class, using the access_attribute identifier to access it. If access_attribute is already defined, an AttributeError exception is raised. If access_attribute isn’t a valid identifier, a ValueError exception is raised.

CalendarInterface must obey the requirements for the datetime2 interface classes, otherwise a TypeError exception is raised.

classmethod Time.register_new_time(access_attribute, TimeInterface)

Register the TimeInterface class to the Time class, using the access_attribute identifier to access it. If access_attribute is already defined, an AttributeError exception is raised. If access_attribute isn’t a valid identifier, a ValueError exception is raised.

TimeInterface must obey the requirements for the datetime2 interface classes, otherwise a TypeError exception is raised.

classmethod TimeDelta.register_new_time_interval(access_attribute, TimeDeltaInterface)

Register the TimeDeltaInterface class to the TimeDelta class, using the access_attribute identifier to access it. If access_attribute is already defined, an AttributeError exception is raised. If access_attribute isn’t a valid identifier, a ValueError exception is raised.

TimeDeltaInterface must obey the requirements for the datetime2 interface classes, otherwise a TypeError exception is raised.

classmethod calendar_class.from_rata_die(day_count)

Return a calendar object that corresponds to the day identified by the given day count.

calendar_obj.to_rata_die()

Return a rata die value that corresponds to the day represented by the calendar instance.

classmethod time_of_day_class.from_time_pair(day_frac, utcoffset)

Return a time of day object that corresponds to the moment identified by the given day fraction, possibly with reference to the given UTC distance.

time_of_day_obj.to_time_pair()

Return a tuple of two values: the first one is the day fraction corresponding to the moment of the day identified by the time object, the second is null for naive time objects, or is a fraction of day to be added to UTC to get the time of day object.

classmethod time_interval_class.from_fractional_days(fractional_days)

Return a time interval object that corresponds to the interval identified by the given number of possibly fractional days.

time_interval_obj.to_fractional_days()

Return a possibly fractional number of days that corresponds to the time interval represented by the object.

Inner workings

At registration time, some magic needs to be performed to obtain the wanted results:

  • A new class is created on the fly, inheriting from the interface class. The new class changes the default constructor so it returns a base class instance when called. Since all other constructors use the default one (see the requirements above), all constructors of the new class return a base class instance.
  • A new attribute is added to the base class. This attribute is special because its semantic depend on whether it is called on the base class or on a base class instance. In the former case, it creates a new base class instance. In the latter case, it uses the methods corresponding to the registered interface class.

The latter is obtained by exploiting the standard attribute lookup mechanisms, implementing a context-dependent attribute retrieval. This is well described in Descriptor HowTo Guide:

  • If the attribute is retrieved directly from the class (e.g. as in Date.week_count(1, 1)), the modified interface class (contained in Date.week_count) is returned, so that when invoked with the interface class signature, it returns a base class instance. The modified interface class was created at registration time, so no additional time is required to create it.
  • If the attribute is retrieved from a base class instance, there are two cases:
    • The instance does not have the attribute: the attribute lookup mechanism looks for it in the corresponding Date class definition, where it is found since it was created at registration time. The attribute is created and added to the instance by monkey patching, so the next time the interface class instance is returned as indicated below.
    • The instance already has the attribute, which is retrieved normally. Note that this attribute is an instance of the modified interface class, not of the original one.

This quite complex implementation has a few advantages:

  • Base class instances do not store access attributes unless they are retrieved.
  • Modified interface classes are built at registration time, which happens only once per program invocation.
  • The registration mechanism is common to built-in and custom calendars.
  • Interface classes are completely independent from each other and from their use in base classes.