These types were created to provide a consistent way of
transferring date and time data between Python and databases.
Apart from handling date before the Unix epoch (1.1.1970) they
also correctly work with dates beyond the Unix time limit
(currently with Unix time values being encoded using 32bit
integers, the limit is reached in 2038) and thus is Year
2000 and Year 2038 safe.
The primary absolute date/time type DateTime uses the
following internal format:
- Absolute date
-
This is a C long defined as being the number of
days in the Gregorian calendar since the day before
January 1 in the year 1 (0001-01-01, the Christian Epoch
(CE)), thus the Gregorian date 0001-01-01 corresponds to
absolute date 1. Note that the Julian Epoch lies two days
before the Gregorian one.
- Absolute time
-
This is a C double defined as the number of
seconds since midnight (0:00:00.00) of the day expressed
by the above value.
The Epoch used by the module is January 1st of the
year 1 at midnight (0:00:00.00) in the Gregorian
calendar. This date corresponds to absolute day 1 and
absolute time 0. Dates before the Epoch are handled by
extrapolating the calendars using negative years as basis
(the year 1 BCE corresponds to the year 0, 2 BCE is
represented as year -1 and so on).
For the purpose of storing absolute time differences, the
package provides a second type called
DateTimeDelta. The internal representation for this
type is seconds and stored in a signed C double.
To handle relative time deltas a third object type is
available: RelativeDateTime. This object is currently
implemented in Python and may be used to store relative time
deltas (see below for an exact description). It's main
purpose is providing an intuitive way to calculate e.g. the
"first of next month".
Designing the types wasn't as easy as expected, since many
criteria had to be taken into account. Here are some of them
and their implementation:
Time Zones, Daylight Savings Time (DST) and Leap Seconds
Time zones are among the most difficult to handle issues when it
comes to implementing and using types for date and time. We
chose to move the time zone handling functionality out of the C
implementation and into Python. This means that the types know
nothing about the time zones of the values they store and
calculations are done using the raw data.
If you need to store and use these informations in calculations,
you can "subclass" the types to implement your ideas rather than
having to stick to what the C implementation defines. The
included ODMG submodule is an example of how this can be done.
Leap seconds are not supported either. You can implement classes
respecting these by "subclassing" DateTime and DateTimeDelta and
then overriding the calculation methods with methods that work
on Unix ticks values (provided the underlying C lib knows about
leap seconds -- most don't and the POSIX standard even invorces
not to use leap seconds).
Calendars
The module supports two calendars, the Gregorian (default and
needed for most conversions) and the Julian, which is handy for
dates prior to the year 1582 when the calendar was revised by
Pope Gregory XIII.
Construction of Julian dates can be done using either the
JulianDateTime()
constructor or indirect through
the .Julian()
method of DateTime instances. To
check which calendar a DateTime instance uses, query the
calendar
instance attribute.
Note that Julian dates output the Julian date through the
instances date attributes and broken down values. Not all
conversions are available on instances using the Julian
calendar. Even though in the Julian calendar days start at noon
(12:00:00.0), mxDateTime will use the Gregorian convention of
using the date for the period from 00:00:00.0 to 23:59:59.99 of
that day. (This may change in future versions, though.)
Both calendars use mathematical models as basis -- they do not
account for the many inaccuracies that occurred during their
usage history. For this reason, the .absdate
values
should be interpreted with care, esp. for dates using the Julian
calendar. As a result of the mathematical models, the Epochs in
the calendars differ by a few days. This was needed in order to
synchronize the calendars in the switching year 1582 (I'm still
not 100% sure whether this is correct or not: JulianDate(1,1,1)
lies two days before Date(1,1,1)).
Conversion from and to other formats
For the purpose of converting the stored values to Unix ticks
(number of seconds since the Unix epoch; the C lib also uses
this representation) we assume that the values are given in
local time. This assumption had to be made because the C
lib provides no standard way to convert a broken down date/time
value in any other way into a ticks value.
Conversions to COM dates and tuples are done without any
assumption on the time zone. The raw values are used.
Conversion from other formats to DateTime instances is always
done by first calculating the corresponding absolute time and
date values (which are also used as basis for calculations).
Rounding errors
The internal representation of date/times behaves much like
floats do in Python, i.e. rounding errors can occur when
doing calculations. There is a special compare function included
(cmp()
) in the package that allows you to compare
two date/time values using a given accuracy,
e.g. cmp(date1,date2,0.5)
will allow 12:00:00.5 and
12:00:01.0 to compare equal.
Special care has been taken to prevent these rounding errors
from occurring for COM dates. If you create a DateTime instance
using a COM date, then the value returned by the .COMDate()
method is guaranteed to be exactly the same as the one used for
creation. The same is true for creation using absolute time and
absolute date and broken down values.
Immutability
One other thing to keep in mind when working with DateTime and
DateTimeDelta instances is that they are immutable (like
tuples). Once an instance is created you can not change its
value. Instead, you will have to create a new instance with
modified values. The advantage of having immutable objects is
that they can be used as dictionary keys.
UTC and GMT
UTC (Universal Time Code) and GMT (Greenich Mean Time) are two
names for more or less the same thing: they both refer to the
international universal time which is used throughout the world
to coordinate events in time regardeless of time zone, day light
savings time or other local time alterations. See the Calendar FAQ for
more infos.
The mx.DateTime package uses these two names interchangeably.
Sometimes API only refer to one name for simplicity. The name
preference (GMT or UTC) is often chosen according to common
usage.
Interaction with other types
DateTime and DateTimeDelta instances can be compared and hashed,
making them compatible to the dictionary implementation Python
uses (they can be used as keys). The copy protocol, simple
arithmetic and pickleing are also supported (ee below for
details).
String formats
DateTime and DateTimeDelta instances know how to output
themselves as ISO8601-strings. The format is very simple:
YYYY-MM-DD HH:MM:SS.ss for DateTime instances and
[-][DD:]HH:MM:SS.ss for DateTimeDelta instances (the DD-part
(days) is only given if the absolute delta value is greater than
24 hours). Customized conversion to strings can be done using
the strftime
-methods or the included submodules.
String parsing is supported through the strptime()
constructor which implements a very strict parsing scheme and
the included submodules (e.g. ISO and ARPA), which allow a little more freedom.
Speed and Memory
Comparing the types to time-module based routines is not really
possible, since the used strategies differ. You can compare them
to tuple-based date/time classes though: DateTime[Delta] are
much faster on creation, use less storage and are faster to
convert to the supported other formats than any equivalent
tuple-based implementation written in Python.
Creation of time-module values using time.mktime() is much
slower than doing the same thing with DateTime(). The same holds
for the reverse conversion (using time.localtime()).
The storage size of ticks (floats, which the time module uses)
is about 1/3 of the size a DateTime instance uses. This is
mainly due to the fact that DateTime instances cache the broken
down values for fast access.
To summarize: DateTime[Delta] are faster, but also use more
memory than traditional time-module based techniques.
Background and Sources on the Web
Here is a small list of links I used as starting points to find
some of the date/time related information included in this
package: