views:

543

answers:

7

I'm looking for a command line tool or some sort of python library (that I can then wrap), so that I can calculate dates that are specified like "last thursday of the month".

i.e. I want to let people enter human friendly text like that above and it should be able to calculate all the dates for any month/year/whatever that fulfil that.

Any suggestions?

+1  A: 

Well, don't know if such library exists, but date from GNU coreutils has something like that:

$ date -d "last monday"
Mon Mar  2 00:00:00 RST 2009
Eugene Morozov
+3  A: 

I think you can get pretty far using just Python's standard calendar module, maybe by adding some "human-friendly" methods on top of those methods. The module gives you iterators for dates, while taking weeks into consideration, and so on.

First I guess you must answer the question "what are some human-friendly ways to talk about dates?", which I guess is more than half the difficulty of this problem.

unwind
+1  A: 

Use mxDateTime. http://www.egenix.com/products/python/mxBase/mxDateTime/

vartec
A: 

If this is for a web app take a look at Datejs, it may be easier to use that in your form then just pass it's date object's value to Python.

If you end up writing one yourself the source may be helpful too.

kevink
+7  A: 

Neither mxDateTime nor Datejs nor that webservice support "last thursday of the month". The OP wants to know all of the last thursdays of the month for, say, a full year.

mxDateTime supports the operations, but the question must be posed in Python code, not as a string.

The best I could figure is parsedatetime, but that doesn't support "last thursday of the month". It does support:

>>> c.parseDateText("last thursday of april 2001")
(2001, 4, 20, 13, 55, 58, 3, 64, 0)
>>> c.parseDateText("last thursday of may 2001")
(2001, 5, 20, 13, 56, 3, 3, 64, 0)
>>> c.parseDateText("last thursday of may 2010")
(2010, 5, 20, 13, 56, 7, 3, 64, 0)
>>>

(Note that neither DateJS nor that web service support this syntax.)

EDIT: Umm, okay, but while the year and the month are right, the day isn't. The last thursday of april 2001 was the 27th. I think you're going to have to roll your own solution here.

It does not support:

>>> c.parseDateText("last thursday of 2010")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "parsedatetime/parsedatetime.py", line 411, in parseDateText
    mth = self.ptc.MonthOffsets[mth]
KeyError
>>>

So one possibility is text substitution: normalize the string to lowercase, single spaces, etc. then do a string substitution of "the month" for each of the months you're interested in. You'll likely have to tweak any solution you find. For example, in some old code of Skip Montanaro which he wrote for an online music calendering system:

     # someone keeps submitting dates with september spelled wrong...
     'septmber':9,

Or you write your own parser on top of mxDateTime, using all of the above links as references.

Andrew Dalke
+3  A: 

The algorithms aren't hard. They're provided in the following book, which is worth every penny.

http://emr.cs.iit.edu/home/reingold/calendar-book/third-edition/

The general approach is to find first days of a required month. You can then subtract one day to find the last day of a required month. Then you compute an offset for day of week you want relative to the day of the week you found.

Assume you have a datetime.date, d. You can work out the end of the month by computing the start of the next month minus one day.

monIx= d.year*12 + (d.month-1) + 1
endOfMonth= datetime.date( monIx//12, monIx%12+1, 1 ) - datetime.timedelta( days=1 )

At this point, you're looking for an offset to the required day of the week. In this case, the desired day of the week is Thursday, which is 3.

dayOffset =  ((3 - endOfMonth.weekday()) -7 ) % -7 
lastDay = endOfMonth + datetime.timedelta( days=dayOffset )

The general approach of getting the day-of-week offset requires a little bit of thinking, but there are only a few combinations of cases to experiment with.

S.Lott
+1  A: 

I'm not entirely sure, but you might look at the Python DateUtil module.

projecktzero