Too often, programmers attempt to write their own date, time or calendar manipulations. Calendar programming is very complex, and there are a number of shoals that are not clearly charted. The use of the time and datetime modules makes our applications simpler, and much more likely to be correct. The clock and calendar are hopelessly complex data objects, best described by these modules.
In Concepts: Point in Time and Duration, we’ll look at the concepts of a point in time and a duration. Then we’ll look at the datetime module in The datetime Module. The formal definitions for some of the module is in Formal Definitions in datetime.
We’ll look at the time module in The time module, and the formal definitions in Formal Definitions in time.
To make best use of the datetime module, we must separate the closely-related concepts of point, distance and units of measure. We need to apply these concepts to time. The problem we have is that “day” is a duration (24 hours) as well as a unit (the 24th day of September.)
Point In Time. A point in time is also known as a date, timestamp or datetime. A single point in time can be measured to any degree of precision. When measured to the nearest whole day, we call it a date. When measured to the nearest second, we sometimes call it a datetime. Our operating system may measure to the nearest millisecond, and our database may measure to the nearest nanosecond. It is important to note that date, datetime and timestamp all refer to a point in time.
A datetime can be shown in a wide variety of formats, including or omitting any of a number of details. For example, the date 12/7/2005 is in the fourth quarter of the year, is in the 49th week, and is a Wednesday, but we don’t often show these additional details. The Python datetime.datetime object has these additional attributes, but doesn’t show them by default.
Sometimes confusion arises because a datetime can be mistaken for two things jammed together: a date and a time of day. A datetime is not two separate things. A datetime is a very precise point in time measured to the nearest second or minute, which includes a number of units of measurement (years, months, days, hours, minutes and seconds). A date is just a datetime that is only measured to the nearest day instead of the nearest minute.
Time of Day. A time of day (for example, 10:08 AM) can be one of two things. It can be part of a datetime, with the date information assumed; 10:08 AM could be the time portion of “10:08 AM 4/20/2007”. Or, it can be a generic time of day used for scheduling purposes; in this case, there’s no specific date. The generic time of day is a rare situation. A datetime which omits the details of the date is much more common.
Sometimes we display just a time of day to a person because the person can work out the date from the context in which information is displayed.
A point in time, unless it’s a generic time used for scheduling, is part of a complete datetime object. We may show only the time of that more complete datetime object to the person using the software.
Duration. A duration is sometimes called a delta time or offset. Durations can be measured in various units like years, quarters, months, weeks, days, hours, minutes or seconds.
Since both a point in time and a duration can be measured in similar units, this can be confusing. Durations, for example, aren’t a specific time of day (10:08 AM), but a number of hours (10 hours, 8 minutes).
Irregularity. Note that we measure time in units which have gaps and overlaps, mostly because the irregular concepts of month and year. If we only used days and weeks, life would be simpler. Months and years really throw a monkey wrench into the works.
For example, the durations of “90 days” and “3 months” are similar, but not exactly the same. For the simple durations like weeks, days, hours, minutes and seconds, the conversions are simple; we can normalize to days or seconds without any trouble or confusion.
Among the cultural times like months, quarters and years, the conversions are pretty clear. However, there are a lot of special cases. This makes converting back and forth between simple times and cultural times is very difficult.
If we think of a duration being measured in days, then one hour is 1/24 = 0.04166 and one minute is 1/60th of that = 0.0006944. On the other hand, we can think of a duration in seconds, then one day is 86,400 seconds. Both views are equivalent. Our operating systems, generally, like to work in seconds. Other software, like databases, prefer to work in days because that meets people’s needs a little better than working in seconds.
Point and Duration Calculations. You can combine a timedelta and a datetime to compute new point in time. You can also extract a timedelta as the difference between two datetimes. Doing this correctly, however, requires considerable care. That’s why this operation is done best by the datetime package.
There are two overall approaches to date and time processing in Python.
Unless you have a need for C-language compatibility, you use the datetime module for all of your date and time-related computations. We’ll present datetime first.
In addition to the datetime module, the calendar module also contains useful classes and methods to handle our Gregorian calendar. We won’t look at this module, however, since it’s relatively simple.
The Gregorian calendar is extremely complex; some of that complexity reflects the irregularities of our planet’s orbit around the Sun. One of the many complexities is the leap year, which has rules that are intended to create calendar years with integer numbers of days that approximate the astronomical year of about 365.2425 days.
The datetime module contains the objects and methods required to correctly handle the sometimes obscure rules for the Gregorian calendar. It is possible to use date information in a datetime object to usefully convert among the world’s calendars. For details on conversions between calendar systems, see Calendrical Calculations [Dershowitz97]. Additionally, this package also provides for a time delta, which captures the duration between two datetimes.
One of the ingenious tricks to working with the Gregorian calendar is to assign an ordinal number to each day. We start these numbers from an epochal date, and use algorithms to derive the year, month and day information for that ordinal day number. Similarly, this module provides algorithms to convert a calendar date to an ordinal day number. Following the design in [Dershowitz97], this class assigns day numbers starting with January 1, in the (hypothetical) year 1. Since the Gregorian calendar was not defined until 1582, all dates before the official adoption are termed proleptic. This epoch date is a hypothetical date that never really existed on any calendar, but which is used by this class.
There are four classes in this module that help us handle dates and times in a uniform and correct manner.
There are a number of interesting date calculation problems that we can solve with this module. We’ll look at the following recipes:
Getting Days Between Two Dates. Because datetime.datetime objects have the numeric operators defined, we can create datetime.datetime objects and subtract them to get the difference in days and seconds between the two times. The difference between two date or datetime objects is a timedelta object.
>>> import datetime
d>>> d1 = datetime.datetime.now()
>>> d2 = datetime.datetime.today()
>>> d2 - d1
datetime.timedelta(0, 14, 439322)
>>> d1
datetime.datetime(2009, 7, 9, 6, 44, 19, 45987)
>>> d2
datetime.datetime(2009, 7, 9, 6, 44, 33, 485309)
Surf http://stackoverflow.com for about a minute.
>>> d3 = datetime.datetime.now()
>>> d3 - d1
datetime.timedelta(0, 95, 848826)
The difference between d2 and d1 was the object datetime.timedelta(0, 14, 439322), which means zero days, 14 seconds and 439,322 microseconds.
The difference between d3 and d1 was the object datetime.timedelta(0, 95, 848826), which means zero days, 95 seconds and 848,826 microseconds.
If we said td= d3-d1, then td.days is the number of days between two dates or datetimes. td.seconds is the number of seconds within the day, from 0 to 86400. The seconds attribute is zero if you get the difference between two dates, since they have to time information. td.seconds/60/60 is the number of hours between the two datetimes.
If we do td.days/7, we compute the number of weeks between two dates.
Getting Months Between Two Dates. The number of months, quarters or years between two dates uses the instance variables of the datetime.datetime object. If we have two variables, begin and end, we have to compute month numbers from the dates. A month number includes the year and month information.
We compute a month number for a date as follows:
endMonth= end.year*12+end.month
startMonth = begin.year*12+begin.month
endMonth - beginMonth
The result is the months between the two dates. This correctly handles all issues with months in the same or different years.
Computing A Date From An Offset In Days. To computing a date in the future using an offset in days, we can add a timedelta object to a datetime object. The timedelta object can be constructed with a day offset or a seconds offset or both. In the following example, we’ll compute the date which is 5 days in the future.
now= datetime.datetime.now()
now + datetime.timedelta(days=5)
The result of this calculation is a new datetime object.
We can do a similar calculation for weeks, we just use something like 6*7 to compute a date six weeks in the future.
Computing A Date From An Offset In Months. When we want to compute a date that is offset by a number of months, quarters or years, we can use the datetime.replace() do to things like the following example. This will add three months to the current date to compute a future date. We use both year and month in the future date calculation to assure that the date wraps around the end of the year correctly.
>>> import datetime
>>> due = datetime.datetime.today()
>>> thisYM= due.year*12+due.month-1
>>> nextYM= thisYM + 3
>>> due.replace( year=nextYM//12, month=nextYM%12 + 1 )
datetime.datetime(2009, 10, 9, 6, 50, 10, 831933)
This will raise an ValueError if you try to create an invalid date like February 30th.
Note that this parallels our Computing A Date From An Offset In Months example. In both cases, we work with a month number that combines month and year into a single serial number.
Input of Dates and Times. We get date and time information from three soures. We may ask the operating system what the current date or time is. We may ask the person who’s running our program for a date or a time. Most commonly, we often process a file which has date or time information in it. For example, we may be reading a file of stocks with dates on which a trade occurred.
Getting a Date or Time From The OS. We get time from the OS when we want the current time, or the timestamp associated with a system resource like a file or directory. The current time is created by the datetime.datetime.now() object constructor. See Advanced File Exercises for examples of getting file timestamps. When we get a file-related time, we get a floating-point number of seconds past the epoch date. We can convert this to a proper datetime with datetime.datetime.fromtimestamp().
>>> import os
>>> import datetime
>>> mtime= os.path.getmtime( "Makefile" )
>>> datetime.datetime.fromtimestamp( mtime )
datetime.datetime(2009, 6, 9, 21, 10, 26)
The file named Makefile was modified 6/9/2009 at 9:10:26 PM.
Getting A Date or Time From A User. Human-readable time information generally has to be parsed from a string. People write times and dates with an endless variety of formats that have some combination of years, days, months, hours, minutes and seconds.
In this case, we have to first parse the time, using datetime.datetime.strptime().
>>> someInput= "3/18/85" # stand-in for raw_input()
>>> inDT = datetime.datetime.strptime( someInput, "%m/%d/%y" )
>>> inDT
datetime.datetime(1985, 3, 18, 0, 0)
Getting a Date or Time From A File. Files often have human-readable date and time information. However, some files will have dates or times as strings of digits. For example, it might be 20070410 for April 10, 2007. This is still a time parsing problem, and we can use datetime.datetime.strptime().
>>> someInput= "20070410" # stand-in for someFile.read()
>>> aDate = datetime.datetime.strptime( someInput, "%Y%m%d" )
>>> aDate
datetime.datetime(2007, 4, 10, 0, 0)
We’ll present some of the formal definitions of the datetime.datetime class, since it offers the most features. The datetime.date and datetime.time classes are simplifications of the datetime.datetime class.
We’ll also look at formal definitions of the datetime.timedelta class.
In the definitions below, you’ll see a distinction between UTC (Coordinated Universal Time, also known as Zulu Time and Greenwich Mean Time) and local time. Your local time is an offset from UTC time, and that offset varies if you have standard time and daylight time. The rules vary by county around the United States, making the time zone boundaries a rather complex problem.
However, your computer already has the localtime offset. It works internally in UTC, and converts the universal time into local time as needed. You can borrow this design pattern in your programs, also. If you need to share information widely, consider keeping track of dates and times in UTC inside your programs, and converting to local time for display and human input purposes.
Current local date or datetime: same as datetime.datetime.fromtimestamp( time.time() ). See now() and utcnow() for variations that may produce more precise times.
Current local date or datetime. If possible, supplies more precision than using a time.time() floating-point time. See utcnow().
Current UTC date or datetime. If possible, supplies more precision than using a time.time() floating-point time. See now().
Current local date or datetime from the given floating-point time, like those created by time.time().
Current UTC date or datetime from the given floating-point time, like those created by time.time().
Current local date or datetime from the given ordinal day number. The time fields of the datetime will be zero.
Combine date fields from date with time fields from time to create a new datetime object.
The following methods return information about a given datetime object. In the following definitions, dt is a datetime object.
The datetime.timedelta object holds a duration, measured in days, seconds and microseconds. There are a number of ways of creating a datetime.timedelta. Once created, ordinary arithmetic operators like + and - can be used between datetime.dateime and datetime.timedelta objects.
The time module contains a number of portable functions needed to format times and dates. The time module can represent a moment in time in any of three forms.
It is an unfortunate consequence of our calendar and clock that we need to have three representations for a given date and time. There isn’t a lot we can do about simplifying the calendar. All we can do is cope with it through a comprehensive set of Python libraries.
For the most part, the “seconds past epoch” representation of dates and times works well for a broad number of uses. It has the downside of being opaque when you try to look at it the number. What day of the week does “1247137510.6811409” fall on?
Seconds past an epoch time has the advantage of being a standard floating-point number. If you round it off to the nearest second, it is a 10-digit number. If you round it off to the nearest day (there are 86,400 seconds in a day), it’s only a five digit number, for example: 14434. We won’t need a 6th digit until 2282.
The other common formats for date information are strings like '2005-10-10 22:10:07'. These must be converted from a string to one of the two numeric forms (seconds or time.struct_time object) before any useful processing can be done.
Here’s a step-by-step example for displaying the current time (time.time()) using the GNU/Linux standard format for day and time. This shows a standardized and portable way to produce a time stamp.
>>> import time
>>> now= time.time()
>>> lt = time.localtime( now )
>>> time.strftime( "%x %X", lt )
'07/09/09 07:08:14'
>>> time.strftime( "%x %X", time.localtime( time.time() ) )
'07/09/09 07:08:47'
There are a number of interesting date calculation recipes that apply to the time module.
These are some common recipes for date arithmetic.
Getting Days Between Two Dates. To get the number of days between two dates, we calculate the difference between two floating-point timestamp representation of points in time. When we subtract these values we get seconds between two dates. Since there are 86,400 seconds in a day, we can convert this number of seconds to a number of days, weeks, hours or even minutes.
>>> import time
>>> now= time.time()
Surf http://stackoverflow.com for about 5 minutes.
>>> d2 = time.time()
>>> d2 - now
275.53438711166382
>>> (d2 - now) / 60
4.5922397851943968
The difference is in seconds. When we divide by 60, that’s the difference in minutes. When we divide by 86400, that’s the difference in days.
Getting Months Between Two Dates. To get the number of months, quarters or years between two dates, we use the time.struct_time objects.
>>> start = time.localtime( prior_time )
>>> end = time.localtime( time.time() )
>>> start
(2009, 7, 9, 7, 8, 14, 3, 190, 1)
>>> end
(2009, 7, 9, 7, 17, 14, 3, 190, 1)
>>> endMonth= end.tm_year*12+end.tm_mon
>>> startMonth = start.tm_year*12+start.tm_mon
>>> endMonth - startMonth
In this case, we’ve created start and end using time.localtime() conversions. We could also create the time.struct_time objects from parsing user input.
Given two time.struct_time objects, start and end, we must compute month numbers that combine year and month into a single integer value that we an process correctly.
Computing A Date From An Offset In Days. To compute a date in the future using weeks or days, we can add an appropriate offset to a floating-point timestamp value. Since the floating-point timestamp is in seconds, a number of days must be multiplied by 86,400 to convert it to seconds. A week is 7\times 86400 = 604,800 seconds long.
>>> next_week = 7*86400 + time.time()
>>> time.localtime( next_week )
(2009, 7, 16, 7, 23, 21, 3, 197, 1)
Computing A Date From An Offset In Months. To compute a date in the future using a number of months or years, we have to create the time.struct_time object for the base date, and then update selected elements of the tuple. Once we’ve updated the structure, we can then converting it back to a floating-point timestamp value using time.mktime().
Note that we have to be careful to handle the year correctly. The easiest way to be sure this is done correctly is to do the following:
>>> import time
>>> now= time.localtime( time.time() )
>>> thisYM= now.tm_year*12+now.tm_mon-1
>>> nextYM= thisYM+3
>>> dueYear, dueMonth =nextYM//12, nextYM%12+1
>>> nextSec= time.mktime( (dueYear,dueMonth,now.tm_mday,0,0,0,0,0,0) )
>>> time.localtime( nextSec )
(2009, 10, 9, 1, 0, 0, 4, 282, 1)
Input of Dates and Times. When we get a time value, it’s generally in one of two forms. Sometimes a time value is represented as a number, other times it’s represented as a string.
Getting a Date or Time From The OS. We often get the OS time when we want the current time, or the timestamp associated with a system resource like a file or directory. The current time is available as the time.time() function.
See Advanced File Exercises for examples of getting file timestamps. When we get a file-related time, the OS gives us a floating-point number of seconds past the epoch date. There are two kinds of processing: simple display and time calculation.
To display an OS time, we need to convert the floating-point timestamp to a time.struct_time. We use time.localtime() or time.gmtime() to make this conversion. Once we have a time.struct_time, we use time.strftime() or time.asctime() to format and display the time.
>>> import os
>>> import time
>>> mtime= os.path.getmtime( "Makefile" )
>>> time.localtime( mtime )
(2009, 6, 9, 21, 10, 26, 1, 160, 1)
>>> time.strftime( "%x %X", time.localtime(mtime) )
'06/09/09 21:10:26'
Getting A Date or Time From A User. Human-readable time information generally has to be parsed from a string. Human-readable time can include any of the endless variety of formats with some combination of years, days, months, hours, minutes and seconds. In this case, we have to first parse the time, creating time.struct_time. The simplest parsing is done with time.strptime().
Here’s an example of parsing an input string from a person. This will create time.struct_time called theDate.
>>> import time
>>> someInput= "3/18/85" # stand-in for raw_input()
>>> theDate= time.strptime( someInput, "%m/%d/%y" )
>>> theDate
(1985, 3, 18, 0, 0, 0, 0, 77, -1)
Getting a Date or Time From A File. Files often have human-readable date and time information. However, some files will have dates or times as strings of digits. For example, it might be 20070410 for April 10, 2007. This is still a time parsing problem, and we can use time.strptime() to pick apart the various fields. We can parse the 8-character string using
>>> import time
>>> someInput= "20070410" # stand-in for someFile.read()
>>> time.strptime( someInput, "%Y%m%d" )
(2007, 4, 10, 0, 0, 0, 1, 100, -1)
The three representations for time in this module (floating-point seconds after the epoch, a time.struct_time object, and a string) leads to a number of conversations back and forth between these three forms.
We’ll present some of the formal definitions of the time module. We’ll look at the struct_time object, then at functions which work with the struct_time view of a point in time, and the floating-point seconds view of a point in time. We’ll finish with functions that convert back and forth to strings.
The time.struct_time Object. A time.struct_time object is a little bit like a nine-element tuple, and a little bit like a class definition. You create a proper time.struct_time object via a little 2-step dance which we’ll show below. First, we’ll look at the object itself.
A time.struct_time object has the following instance variables. Each of these is a read-only attribute; you can’t update a time.struct_time object.
| tm_year: | Year (four digits, e.g. 1998) |
|---|---|
| tm_mon: | Month (1-12) |
| tm_mday: | Day of the month (1-31) |
| tm_hour: | Hours (0-23) |
| tm_min: | Minutes (0-59) |
| tm_sec: | Seconds (0-61), this can include leap seconds on some platforms |
| tm_wday: | Weekday (0-6, Monday is 0). This can be hard to figure out when you’re creating a new time. Fortunately, you can supply -1, and the time.mktime() function will determine the weekday correctly. |
| tm_yday: | Day of the year (1-366). This can be hard to figure out when you’re creating a new time. Fortunately, you can supply -1, and the time.mktime() function will determine the day of the year correctly. |
| tm_isdst: | Daylight savings time flag: 0 is the regular time zone; 1 is the DST time zone. -1 is a value you can set when you create a time for the mktime(), indicating that mktime() should determine DST based on the date and time. |
Working With time_struct Objects. The time module includes the following functions that create a time.struct_time object. The source timestamp can be a floating-point “seconds-past-the-epoch” value or a formatted string.
Convert a timestamp with seconds since the epoch to a time.struct_time object expressing UTC (a.k.a. GMT).
Convert a timestamp with seconds since the epoch to a time.struct_time expressing local time.
Parse the string using the given format to create a time.struct_time object expressing the given time string. The format parameter uses the same directives as those used by time.strftime(); it defaults to "%a %b %d %H:%M:%S %Y" which matches the formatting returned by the time.ctime() function.
If the input can’t be parsed according to the format, a ValueError is raised.
someInput= "3/18/85" # stand-in for raw_input()
theDate= time.strptime( someInput, "%m/%d/%y" )
print(theDate)
When you want to create a proper time.time_struct object, you’ll find that there are a few fields for which you don’t know the initial values. For example, it’s common to know year, month, day of month, hour, minute and second. It’s rare to know the day of the year or the day of the week. Consequently, you have to do the following little two-step dance to create and initialize a time.struct_time.
In this example, we’ll create a proper structure for 4/21/2007 at 2:51 PM. We can fill in six of the nine values in a time.struct_time tuple. We just throw -1 in for the remaining values.
ts= time.localtime( time.mktime( (2007,4,21,14,51,00,-1,-1,-1) ) )
The value for ts, (2007, 4, 21, 14, 51, 0, 5, 111, 1), has the day of week (0 is Monday, 5 is Saturday) and day of year (111) filled in correctly.
Working With Floating-Point Time. The time module includes the following functions that create a floating-point “seconds-past-the-epoch” value. This value can be generated from the operating system, or converted from a time.struct_time object.
Because a floating-point time value is a simple floating-point number, you can perform any mathematical operations you want on that number. Since it is in seconds, you can divide by 86,400 to convert it to days.
Return the current timestamp in seconds since the Epoch. Fractions of a second may be present if the system clock provides them.
now= time.time()
Convert a time.struct_time object to seconds since the epoch. The weekday and day of the year fields can be set to -1 on input, since they aren’t necessary. However, the DST field is used.
In this example, we convert a time.struct_time object into a list so we can update it. Then we can make a floating-point time from the updated structure.
lt= time.localtime( time.time() )
nt= list( lt )
nt[1] += 3 # add three to months attribute
future= time.mktime( nt )
Working with String Time. The following functions of the time module create time as formatted string, suitable for display or writing to a log file.
Convert the time.struct_time object, structure to a string according to the format specification. A format of “%x %X” produces a date and time.
lt= time.localtime( time.time() )
print(time.strftime( "%Y-%m-%dT%H:%M:%S", lt))
Convert a time.struct_time to a string, e.g. ‘Sat Jun 06 16:26:11 1998’. This is the same as a the format string "%a %b %d %H:%M:%S %Y".
Convert a floating-point time in seconds since the Epoch to a string in local time. This is equivalent to time.asctime(time.localtime(seconds)).
If no time is given, use the current time. time.ctime()` ` does the same thing as ``time.asctime( time.localtime( time.time() ) ).
Additional Functions and Variables. These are some additional functions and variables in the time module.
This function appears in the time module. The sched module reflects a better approach to time-dependent processing.
Delay execution for a given number of seconds. The argument may be a floating-point number for subsecond precision. Operating system scheduling vagaries and interrupt handling make this function imprecise.
Return the CPU time or real time since the start of the process or since the first call to clock(). This has as much precision as the system is capable of recording.
The following variables are part of the time module. They describe the current locale.
| time.accept2dyear: | |
|---|---|
| If non-zero, 2-digit years are accepted. 69-99 is treated as 1969 to 1999, 0 to 68 is treated as 2000 to 2068. This is 1 by default, unless the PYTHONY2K environment variable is set; then this variable will be zero. | |
| time.altzone: | Difference in seconds between UTC and local Daylight Savings time. Often a multiple of 3600 (all US time zones are in whole hours). For example, Eastern Daylight Time is 14400 (4 hours). |
| time.daylight: | Non-zero if the locale uses daylight savings time. Zero if it does not. Your operating system has ways to define your locale. |
| time.timezone: | Difference in seconds between UTC and local Standard time. Often a multiple of 3600 (all US timezones are in whole hours). Your operating system has ways to define your locale. |
| time.tzname: | The name of the timezone. |
Conversion Specifications. When we looked at Strings, in Sequences of Characters : str and Unicode, we looked at the % operator which formats a message using a template and specific values. The strftime() and strptime() functions also use a number of conversion specifications to convert between time.struct_time and strings.
The following examples show a particular date (Satuday, August 4th) formatted with each of the formatting strings.
| %c | Locale’s appropriate full date and time representation | ‘Saturday August 04 17:11:20 2001’ |
| %x | Locale’s appropriate date representation | ‘Saturday August 04 2001’ |
| %X | Locale’s appropriate time representation | ‘17:11:20’ |
| %% | A literal ‘%’ character | ‘%’ |
| %a | Locale’s 3-letter abbreviated weekday name | ‘Sat’ |
| %A | Locale’s full weekday name | ‘Saturday’ |
| %b | Locale’s 3-letter abbreviated month name | ‘Aug’ |
| %B | Locale’s full month name | ‘August’ |
| %d | Day of the month as a 2-digit decimal number | ‘04’ |
| %j | Day of the year as a 3-digit decimal number | ‘216’ |
| %m | Month as a 2-digit decimal number | ‘08’ |
| %U | Week number of the year (Sunday as the first day of the week) | ‘30’ |
| %w | Weekday as a decimal number, 0 = Sunday | ‘6’ |
| %W | Week number of the year (Monday as the first day of the week) | ‘31’ |
| %y | Year without century as a 2-digit decimal number | ‘01’ |
| %Y | Year with century as a decimal number | ‘2001’ |
| %H | Hour (24-hour clock) as a 2-digit decimal number | ‘17’ |
| %I | Hour (12-hour clock) as a 2-digit decimal number | ‘05’ |
| %M | Minute as a 2-digit decimal number | ‘11’ |
| %p | Locale’s equivalent of either AM or PM | ‘pm’ |
| %S | Second as a 2-digit decimal number | ‘20’ |
| %Z | Time zone name (or ‘’ if no time zone exists) | ‘’ |
Tip
Debugging time
Because of the various conversions, it’s easy to get confused by having a floating-point time and a time_struct time. When you get TypeError exceptions, you are missing a conversion between the two representations. You can use the help() function and the Python Library Reference (chapter 6.10) to sort this out.
Annualized ROI.
In order to compare portfolios, we might want to compute an annualized ROI. This is ROI as if the stock were held for exactly one year. In this case, since each block has different ownership period, the ROI must be adjusted to be a full year’s time period. We then have a basis for comparing ROI’s among various positions. We can use this to return an average of each annual ROI weighted by the current value of the position.
See Defining New Objects exercises. This calculation is a collaboration between each block of ShareBlock and Position. As with the value calculations, a block-by-block calculation is added to ShareBlock and a higher-level reduction algorithm is used in Position.
The annualization requires computing the duration of stock ownership. The essential feature here is to parse the date string to create a time object and then get the number of days between two time objects.
Given the sale date, purchase date, sale price, sp, and purchase price, pp.
Compute the period the asset was held. There are two choices:
Use time.mktime() to create floating-point time values for sale date, s, and purchase date, p. The weighting, w, is computed as
w= (86400*365.2425) / ( s - p )
Use datetime.datetime() to create datetime.datetime objects for the sale date, s, and purchase date, p. The weighting, w is computed as the following, which truncates the difference to whole days.
w= ( s - p ).days/365.2425
Here’s another code snippet that does some of what we want.
>>> import time
>>> dt1= "25-JAN-2001"
>>> timeObj1= time.strptime( dt1, "%d-%b-%Y" )
>>> dayNumb1= int(time.mktime( timeObj1 ))/24/60/60
>>> dt2= "25-JUN-2001"
>>> timeObj2= time.strptime( dt2, "%d-%b-%Y" )
>>> dayNumb2= int(time.mktime( timeObj2 ))/24/60/60
>>> dayNumb2 - dayNumb1
151
>>> _ / 365.2425
0.41342395805526466
In this example, timeObj1 and timeObj2 are time structures with details parsed from the date string by time.strptime(). The dayNumb1 and dayNumb2 are a day number that corresponds to this time. Time is measured in seconds after an epoch; typically January 1, 1970. The exact value doesn’t matter, what matters is that the epoch is applied consistently by mktime(). We divide this by 24 hours per day, 60 minutes per hour and 60 seconds per minute to get days after the epoch instead of seconds. Given two day numbers, the difference is the number of days between the two dates. In this case, there are 151 days between the two dates.
If we held the stock for 151 days, that is .413 years. If the return for the 151 days was 3.25%, the return for the whole year could have been 7.86%. In order to provide a rational basis for comparison, we use this annualized ROI instead of ROI’s over different durations.
All of this processing must be encapsulated into a method that computes the ownership duration.
This method computes the days the stock was owned.
We would need to add an annualizedROI() method to the ShareBlock that divides the gross ROI by the duration in years to return the annualized ROI. Similarly, we would add a method to the Position to use the annualizedROI() to compute the a weighted average which is the annualized ROI for the entire position.
Date of Easter.
The following algorithm is based on one by Gauss, and published in Dershowitz and Reingold, Calendrical Calculations [Dershowitz97].
Date of Easter after 1582
Let Y be the year for which the date of Easter is desired.
Century.
Set
.
(When Y is not a multiple of 100,
C is the century number; i.e., 1984 is in
the twentieth century.)
Shifted Epact.
Set
.
(Note that the
mathematical
operation is usually implemented by the a//b operation in
Python. Also, int(a/b) will work.)
Adjust Epact.
If
: add 1 to E.
Paschal Moon.
Set
.
April is month number 4.
(You’ll
need to use a datetime.timedelta( days=e ) object.)
Easter. Locate the Sunday after the Paschal Moon.
Set
.
(For P mod
7, you’ll need to use p.toordinal(). You’ll need to
make a datetime.timedelta out of the
offset,
.)
Return the Date. Q is the date of Easter.
Recently, Easter fell on 4/11/2004 and 3/27/2005.
This is a lesson on history, really. The time module came first, and is a Python wrapper around the underlying C standard library. The datetime module is a better reflection of the things we actually do with dates and times.
The historical evolution shows something of how software captures knowledge. The time module shows a very technical understanding of time: it is a number of seconds, which is easy to gather from the hardware clock. This can be resolved to a date, but any processing has to be done in seconds.
The datetime module, on the other hand, is organized around days, which is the way we look at our calendar. Working in days is more typical of a large number of real-world problems.
As people did more and more useful work with computers, they realized that the time module was too simplistic; it didn’t define a useful abstraction for dates and times. Based on real-world problems, people wrote the datetime module to solve those problems.
It’s true, our calendar is complicated. If we only had 12 months of 30 days each, our lives would be simpler. If someone could just speed up our orbit around the sun by 1.438%, this wouldn’t be so complex.
Sadly, there’s just no simple way to convert between days and months. A datetime object, thankfully, conceals all of the details, allowing us to work with an object that has both day information and month information.
Why is there no formal duration that’s compatible with a time.struct_time object?
The time-as-seconds representation is a duration that’s technically very simple. It turns out that a point in time can be viewed as a duration measured against an epochal date. Everything’s a floating-point number. Not much can go wrong.
However, people like to see their calendar, not a number of seconds past an epochal date. So the time.struct_time was added just to make it easy to display time values or accept time-oriented inputs. Further, for business rules that involve months, the time.struct_time information is useful.
Both time-as-seconds, and time-as-structure are required. Some programs will use one representation more than the other.