Merge lp:~gtg-contributors/gtg/new-date-class into lp:~gtg/gtg/old-trunk

Proposed by Paul Natsuo Kishimoto on 2010-06-20
Status: Merged
Approved by: Izidor Matušov on 2012-02-14
Approved revision: 823
Merged at revision: 1089
Proposed branch: lp:~gtg-contributors/gtg/new-date-class
Merge into: lp:~gtg/gtg/old-trunk
Diff against target: 959 lines (+300/-282)
10 files modified
GTG/core/filters_bank.py (+6/-6)
GTG/core/requester.py (+0/-1)
GTG/core/task.py (+17/-20)
GTG/gtk/browser/browser.py (+9/-13)
GTG/gtk/dbuswrapper.py (+8/-8)
GTG/gtk/editor/editor.py (+24/-26)
GTG/plugins/evolution_sync/gtgTask.py (+6/-5)
GTG/plugins/rtm_sync/gtgTask.py (+6/-5)
GTG/tools/dates.py (+216/-190)
GTG/tools/taskxml.py (+8/-8)
To merge this branch: bzr merge lp:~gtg-contributors/gtg/new-date-class
Reviewer Review Type Date Requested Status
Paul Natsuo Kishimoto (community) Approve on 2010-12-17
Bryce Harrington (community) code 2010-06-20 Approve on 2010-07-14
Review via email: mp+28009@code.launchpad.net

Description of the change

This branch almost entirely rewrites the various classes that were contained in GTG/tools/dates.py.

When the GTG UI and backend are separated over DBus, Python objects (including built-in and custom dates) cannot be passed directly. Strings can be used instead. The new Date class in this code is designed to always obey:

  Date(str(d)) = d

for any Date instance d, even for special dates ('soon', 'later', etc.). As a result, neither client nor server code need make any distinction between FuzzyDates or RealDates or so on; it can simply construct Date() with the information passed over DBus.

The class also follows some of the semantics from the Python datetime module; for example:

  d1 = datetime.date.today() # get a date instance representing today
  d2 = Date.soon() # get a Date instance representing the special date 'soon'

I have tested the branch in several ways, but some additional experimentation would be appreciated to see if any bugs were introduced.

To post a comment you must log in.
Luca Invernizzi (invernizzi) wrote :

I hate to raise this after you did the work, but have you considered the possibility of using the pickle module? The serialization is done automatically like "pickle.loads(pickle.dumps(datetime.datetime.now()))"
I don't know how fast it is though.

Paul Natsuo Kishimoto (khaeru) wrote :

I had — pickling is fast, but the output isn't necessarily easy to parse for non-Python code that might be interacting with the DBus API.

Luca Invernizzi (invernizzi) wrote :

FYI, I found out that in current trunk we have that:
 string = str(get_canonical_date(string))

That would let us keep the subclasses about fuzzy dates, which could be useful if we decide to expand our "fuzzyness" support.

Paul Natsuo Kishimoto (khaeru) wrote :

I tried to make Date.parse(...) a replacement for get_canonical_date(...) so that string == str(Date.parse(string)).

The difference between Date.parse and Date.__init__ is that parse() checks a lot more possible formats, while __init__() expects simpler input and also recognizes various objects like datetime. __init__() is more for internal use (hopefully also faster, though I haven't checked...)

To handle more formats (e.g. alien ones belonging to various remote backends), I would extend parse() and keep __init__() as-is.

They are about equivalent, but I think it is easier to add more constants like TODAY, SOON, etc. than to subclass Date. This way, any new fuzzy dates will have to behave in a way that is consistent with the existing ones.

Bryce Harrington (bryce) wrote :

Looks good. I like the elimination of difference between RealDate and FuzzyDate.

I can't think of any other fuzzy date terms that would be worth adding, but I'm sure someone will think of one or two, so being able to extend that would be a good thing.

review: Approve (code)
Paul Natsuo Kishimoto (khaeru) wrote :

Anybody in gtg-devs feel like committing this? :)

Paul Natsuo Kishimoto (khaeru) wrote :

Once again, can this be either committed or rejected?

review: Resubmit
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'GTG/core/filters_bank.py'
--- GTG/core/filters_bank.py 2010-06-14 19:30:50 +0000
+++ GTG/core/filters_bank.py 2010-06-20 04:28:25 +0000
@@ -23,8 +23,8 @@
2323
24from datetime import datetime24from datetime import datetime
2525
26from GTG.core.task import Task26from GTG.core.task import Task
27from GTG.tools.dates import date_today, no_date, Date27from GTG.tools.dates import *
2828
2929
30class Filter:30class Filter:
@@ -179,12 +179,12 @@
179 def is_started(self,task,parameters=None):179 def is_started(self,task,parameters=None):
180 '''Filter for tasks that are already started'''180 '''Filter for tasks that are already started'''
181 start_date = task.get_start_date()181 start_date = task.get_start_date()
182 if start_date :182 if start_date:
183 #Seems like pylint falsely assumes that subtraction always results183 #Seems like pylint falsely assumes that subtraction always results
184 #in an object of the same type. The subtraction of dates 184 #in an object of the same type. The subtraction of dates
185 #results in a datetime.timedelta object 185 #results in a datetime.timedelta object
186 #that does have a 'days' member.186 #that does have a 'days' member.
187 difference = date_today() - start_date187 difference = Date.today() - start_date
188 if difference.days == 0:188 if difference.days == 0:
189 # Don't count today's tasks started until morning189 # Don't count today's tasks started until morning
190 return datetime.now().hour > 4190 return datetime.now().hour > 4
@@ -202,14 +202,14 @@
202 def workdue(self,task):202 def workdue(self,task):
203 ''' Filter for tasks due within the next day '''203 ''' Filter for tasks due within the next day '''
204 wv = self.workview(task) and \204 wv = self.workview(task) and \
205 task.get_due_date() != no_date and \205 task.get_due_date() != Date.no_date() and \
206 task.get_days_left() < 2206 task.get_days_left() < 2
207 return wv207 return wv
208208
209 def worklate(self,task):209 def worklate(self,task):
210 ''' Filter for tasks due within the next day '''210 ''' Filter for tasks due within the next day '''
211 wv = self.workview(task) and \211 wv = self.workview(task) and \
212 task.get_due_date() != no_date and \212 task.get_due_date() != Date.no_date() and \
213 task.get_days_late() > 0213 task.get_days_late() > 0
214 return wv214 return wv
215215
216216
=== modified file 'GTG/core/requester.py'
--- GTG/core/requester.py 2010-06-12 13:59:24 +0000
+++ GTG/core/requester.py 2010-06-20 04:28:25 +0000
@@ -27,7 +27,6 @@
27from GTG.core.filters_bank import FiltersBank27from GTG.core.filters_bank import FiltersBank
28from GTG.core.task import Task28from GTG.core.task import Task
29from GTG.core.tagstore import Tag29from GTG.core.tagstore import Tag
30from GTG.tools.dates import date_today
31from GTG.tools.logger import Log30from GTG.tools.logger import Log
3231
33class Requester(gobject.GObject):32class Requester(gobject.GObject):
3433
=== modified file 'GTG/core/task.py'
--- GTG/core/task.py 2010-06-18 16:36:17 +0000
+++ GTG/core/task.py 2010-06-20 04:28:25 +0000
@@ -20,15 +20,15 @@
20"""20"""
21task.py contains the Task class which represents (guess what) a task21task.py contains the Task class which represents (guess what) a task
22"""22"""
2323import cgi
24from datetime import datetime
25import uuid
24import xml.dom.minidom26import xml.dom.minidom
25import uuid
26import cgi
27import xml.sax.saxutils as saxutils27import xml.sax.saxutils as saxutils
2828
29from GTG import _29from GTG import _
30from GTG.tools.dates import date_today, no_date, Date30from GTG.tools.dates import *
31from datetime import datetime31
32from GTG.core.tree import TreeNode32from GTG.core.tree import TreeNode
33from GTG.tools.logger import Log33from GTG.tools.logger import Log
3434
@@ -55,9 +55,9 @@
55 self.title = _("My new task")55 self.title = _("My new task")
56 #available status are: Active - Done - Dismiss - Note56 #available status are: Active - Done - Dismiss - Note
57 self.status = self.STA_ACTIVE57 self.status = self.STA_ACTIVE
58 self.closed_date = no_date58 self.closed_date = Date.no_date()
59 self.due_date = no_date59 self.due_date = Date.no_date()
60 self.start_date = no_date60 self.start_date = Date.no_date()
61 self.can_be_deleted = newtask61 self.can_be_deleted = newtask
62 # tags62 # tags
63 self.tags = []63 self.tags = []
@@ -134,10 +134,10 @@
134 c.set_status(status, donedate=donedate)134 c.set_status(status, donedate=donedate)
135 #to the specified date (if any)135 #to the specified date (if any)
136 if donedate:136 if donedate:
137 self.closed_date = donedate137 self.closed_date = Date(donedate)
138 #or to today138 #or to today
139 else:139 else:
140 self.closed_date = date_today()140 self.closed_date = Date.today()
141 #If we mark a task as Active and that some parent are not141 #If we mark a task as Active and that some parent are not
142 #Active, we break the parent/child relation142 #Active, we break the parent/child relation
143 #It has no sense to have an active subtask of a done parent.143 #It has no sense to have an active subtask of a done parent.
@@ -166,14 +166,13 @@
166 return self.modified166 return self.modified
167167
168 def get_modified_string(self):168 def get_modified_string(self):
169 return self.modified.strftime("%Y-%m-%dT%H:%M:%S")169 return self.modified.isoformat()
170170
171 def set_modified(self, modified):171 def set_modified(self, modified):
172 self.modified = modified172 self.modified = modified
173173
174 def set_due_date(self, fulldate):174 def set_due_date(self, fulldate):
175 assert(isinstance(fulldate, Date))175 self.due_date = Date(fulldate)
176 self.due_date = fulldate
177 self.sync()176 self.sync()
178177
179 #Due date return the most urgent date of all parents178 #Due date return the most urgent date of all parents
@@ -189,16 +188,14 @@
189 return zedate188 return zedate
190189
191 def set_start_date(self, fulldate):190 def set_start_date(self, fulldate):
192 assert(isinstance(fulldate, Date))191 self.start_date = Date(fulldate)
193 self.start_date = fulldate
194 self.sync()192 self.sync()
195193
196 def get_start_date(self):194 def get_start_date(self):
197 return self.start_date195 return self.start_date
198196
199 def set_closed_date(self, fulldate):197 def set_closed_date(self, fulldate):
200 assert(isinstance(fulldate, Date))198 self.closed_date = Date(fulldate)
201 self.closed_date = fulldate
202 self.sync()199 self.sync()
203 200
204 def get_closed_date(self):201 def get_closed_date(self):
@@ -206,13 +203,13 @@
206203
207 def get_days_left(self):204 def get_days_left(self):
208 due_date = self.get_due_date()205 due_date = self.get_due_date()
209 if due_date == no_date:206 if due_date == Date.no_date():
210 return None207 return None
211 return due_date.days_left()208 return (due_date - Date.today()).days
212 209
213 def get_days_late(self):210 def get_days_late(self):
214 due_date = self.get_due_date()211 due_date = self.get_due_date()
215 if due_date == no_date:212 if due_date == Date.no_date():
216 return None213 return None
217 closed_date = self.get_closed_date()214 closed_date = self.get_closed_date()
218 return (closed_date - due_date).days215 return (closed_date - due_date).days
219216
=== modified file 'GTG/gtk/browser/browser.py'
--- GTG/gtk/browser/browser.py 2010-06-17 08:58:32 +0000
+++ GTG/gtk/browser/browser.py 2010-06-20 04:28:25 +0000
@@ -45,10 +45,7 @@
45 ClosedTaskTreeView45 ClosedTaskTreeView
46from GTG.gtk.browser.tagtree import TagTree46from GTG.gtk.browser.tagtree import TagTree
47from GTG.tools import openurl47from GTG.tools import openurl
48from GTG.tools.dates import strtodate,\48from GTG.tools.dates import *
49 no_date,\
50 FuzzyDate, \
51 get_canonical_date
52from GTG.tools.logger import Log49from GTG.tools.logger import Log
53#from GTG.tools import clipboard50#from GTG.tools import clipboard
5451
@@ -607,13 +604,12 @@
607 return s604 return s
608 else:605 else:
609 return -1 * s606 return -1 * s
610 607
611
612 if sort == 0:608 if sort == 0:
613 # Put fuzzy dates below real dates609 # Put fuzzy dates below real dates
614 if isinstance(t1, FuzzyDate) and not isinstance(t2, FuzzyDate):610 if t1.is_special and not t2.is_special:
615 sort = reverse_if_descending(1)611 sort = reverse_if_descending(1)
616 elif isinstance(t2, FuzzyDate) and not isinstance(t1, FuzzyDate):612 elif t2.is_special and not t1.is_special:
617 sort = reverse_if_descending(-1)613 sort = reverse_if_descending(-1)
618 614
619 if sort == 0: # Group tasks with the same tag together for visual cleanness 615 if sort == 0: # Group tasks with the same tag together for visual cleanness
@@ -915,8 +911,8 @@
915911
916 def on_quickadd_activate(self, widget):912 def on_quickadd_activate(self, widget):
917 text = self.quickadd_entry.get_text()913 text = self.quickadd_entry.get_text()
918 due_date = no_date914 due_date = Date.no_date()
919 defer_date = no_date915 defer_date = Date.no_date()
920 if text:916 if text:
921 tags, notagonly = self.get_selected_tags()917 tags, notagonly = self.get_selected_tags()
922 # Get tags in the title918 # Get tags in the title
@@ -940,12 +936,12 @@
940 tags.append(GTG.core.tagstore.Tag(tag, self.req))936 tags.append(GTG.core.tagstore.Tag(tag, self.req))
941 elif attribute.lower() == "defer" or \937 elif attribute.lower() == "defer" or \
942 attribute.lower() == _("defer"):938 attribute.lower() == _("defer"):
943 defer_date = get_canonical_date(args)939 defer_date = Date.parse(args)
944 if not defer_date:940 if not defer_date:
945 valid_attribute = False941 valid_attribute = False
946 elif attribute.lower() == "due" or \942 elif attribute.lower() == "due" or \
947 attribute.lower() == _("due"):943 attribute.lower() == _("due"):
948 due_date = get_canonical_date(args)944 due_date = Date.parse(args)
949 if not due_date:945 if not due_date:
950 valid_attribute = False946 valid_attribute = False
951 else:947 else:
@@ -1114,7 +1110,7 @@
1114 tasks = [self.req.get_task(uid) for uid in tasks_uid]1110 tasks = [self.req.get_task(uid) for uid in tasks_uid]
1115 tasks_status = [task.get_status() for task in tasks]1111 tasks_status = [task.get_status() for task in tasks]
1116 for uid, task, status in zip(tasks_uid, tasks, tasks_status):1112 for uid, task, status in zip(tasks_uid, tasks, tasks_status):
1117 task.set_start_date(get_canonical_date(new_start_date))1113 task.set_start_date(Date.parse(new_start_date))
1118 #FIXME: If the task dialog is displayed, refresh its start_date widget1114 #FIXME: If the task dialog is displayed, refresh its start_date widget
11191115
1120 def on_mark_as_started(self, widget):1116 def on_mark_as_started(self, widget):
11211117
=== modified file 'GTG/gtk/dbuswrapper.py'
--- GTG/gtk/dbuswrapper.py 2010-06-10 14:45:36 +0000
+++ GTG/gtk/dbuswrapper.py 2010-06-20 04:28:25 +0000
@@ -23,8 +23,8 @@
23import dbus.glib23import dbus.glib
24import dbus.service24import dbus.service
2525
26from GTG.core import CoreConfig26from GTG.core import CoreConfig
27from GTG.tools import dates27from GTG.tools.dates import *
2828
2929
30BUSNAME = CoreConfig.BUSNAME30BUSNAME = CoreConfig.BUSNAME
@@ -169,10 +169,10 @@
169 nt = self.req.new_task(tags=tags)169 nt = self.req.new_task(tags=tags)
170 for sub in subtasks:170 for sub in subtasks:
171 nt.add_child(sub)171 nt.add_child(sub)
172 nt.set_status(status, donedate=dates.strtodate(donedate))172 nt.set_status(status, donedate=Date.parse(donedate))
173 nt.set_title(title)173 nt.set_title(title)
174 nt.set_due_date(dates.strtodate(duedate))174 nt.set_due_date(Date.parse(duedate))
175 nt.set_start_date(dates.strtodate(startdate))175 nt.set_start_date(Date.parse(startdate))
176 nt.set_text(text)176 nt.set_text(text)
177 return task_to_dict(nt)177 return task_to_dict(nt)
178178
@@ -187,10 +187,10 @@
187 via this function. 187 via this function.
188 """188 """
189 task = self.req.get_task(tid)189 task = self.req.get_task(tid)
190 task.set_status(task_data["status"], donedate=dates.strtodate(task_data["donedate"]))190 task.set_status(task_data["status"], donedate=Date.parse(task_data["donedate"]))
191 task.set_title(task_data["title"])191 task.set_title(task_data["title"])
192 task.set_due_date(dates.strtodate(task_data["duedate"]))192 task.set_due_date(Date.parse(task_data["duedate"]))
193 task.set_start_date(dates.strtodate(task_data["startdate"]))193 task.set_start_date(Date.parse(task_data["startdate"]))
194 task.set_text(task_data["text"])194 task.set_text(task_data["text"])
195195
196 for tag in task_data["tags"]:196 for tag in task_data["tags"]:
197197
=== modified file 'GTG/gtk/editor/editor.py'
--- GTG/gtk/editor/editor.py 2010-06-07 21:14:45 +0000
+++ GTG/gtk/editor/editor.py 2010-06-20 04:28:25 +0000
@@ -45,7 +45,7 @@
45from GTG.core.plugins.engine import PluginEngine45from GTG.core.plugins.engine import PluginEngine
46from GTG.core.plugins.api import PluginAPI46from GTG.core.plugins.api import PluginAPI
47from GTG.core.task import Task47from GTG.core.task import Task
48from GTG.tools import dates48from GTG.tools.dates import *
4949
5050
51date_separator = "-"51date_separator = "-"
@@ -307,13 +307,13 @@
307 307
308 #refreshing the due date field308 #refreshing the due date field
309 duedate = self.task.get_due_date()309 duedate = self.task.get_due_date()
310 prevdate = dates.strtodate(self.duedate_widget.get_text())310 prevdate = Date.parse(self.duedate_widget.get_text())
311 if duedate != prevdate or type(duedate) is not type(prevdate):311 if duedate != prevdate or type(duedate) is not type(prevdate):
312 zedate = str(duedate).replace("-", date_separator)312 zedate = str(duedate).replace("-", date_separator)
313 self.duedate_widget.set_text(zedate)313 self.duedate_widget.set_text(zedate)
314 # refreshing the closed date field314 # refreshing the closed date field
315 closeddate = self.task.get_closed_date()315 closeddate = self.task.get_closed_date()
316 prevcldate = dates.strtodate(self.closeddate_widget.get_text())316 prevcldate = Date.parse(self.closeddate_widget.get_text())
317 if closeddate != prevcldate or type(closeddate) is not type(prevcldate):317 if closeddate != prevcldate or type(closeddate) is not type(prevcldate):
318 zecldate = str(closeddate).replace("-", date_separator)318 zecldate = str(closeddate).replace("-", date_separator)
319 self.closeddate_widget.set_text(zecldate)319 self.closeddate_widget.set_text(zecldate)
@@ -348,7 +348,7 @@
348 self.dayleft_label.set_markup("<span color='"+color+"'>"+txt+"</span>")348 self.dayleft_label.set_markup("<span color='"+color+"'>"+txt+"</span>")
349349
350 startdate = self.task.get_start_date()350 startdate = self.task.get_start_date()
351 prevdate = dates.strtodate(self.startdate_widget.get_text())351 prevdate = Date.parse(self.startdate_widget.get_text())
352 if startdate != prevdate or type(startdate) is not type(prevdate):352 if startdate != prevdate or type(startdate) is not type(prevdate):
353 zedate = str(startdate).replace("-",date_separator)353 zedate = str(startdate).replace("-",date_separator)
354 self.startdate_widget.set_text(zedate) 354 self.startdate_widget.set_text(zedate)
@@ -377,9 +377,9 @@
377 validdate = False377 validdate = False
378 if not text :378 if not text :
379 validdate = True379 validdate = True
380 datetoset = dates.no_date380 datetoset = Date.no_date()
381 else :381 else :
382 datetoset = dates.strtodate(text)382 datetoset = Date.parse(text)
383 if datetoset :383 if datetoset :
384 validdate = True384 validdate = True
385 385
@@ -400,15 +400,15 @@
400 widget.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse("#F88"))400 widget.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse("#F88"))
401401
402 def _mark_today_in_bold(self):402 def _mark_today_in_bold(self):
403 today = dates.date_today()403 today = Date.today()
404 #selected is a tuple containing (year, month, day)404 #selected is a tuple containing (year, month, day)
405 selected = self.cal_widget.get_date()405 selected = self.cal_widget.get_date()
406 #the following "-1" is because in pygtk calendar the month is 0-based,406 #the following "-1" is because in pygtk calendar the month is 0-based,
407 # in gtg (and datetime.date) is 1-based.407 # in gtg (and datetime.date) is 1-based.
408 if selected[1] == today.month() - 1 and selected[0] == today.year():408 if selected[1] == today.month - 1 and selected[0] == today.year:
409 self.cal_widget.mark_day(today.day())409 self.cal_widget.mark_day(today.day)
410 else:410 else:
411 self.cal_widget.unmark_day(today.day())411 self.cal_widget.unmark_day(today.day)
412 412
413 413
414 def on_date_pressed(self, widget,data): 414 def on_date_pressed(self, widget,data):
@@ -440,15 +440,13 @@
440 gdk.pointer_grab(self.calendar.window, True,gdk.BUTTON1_MASK|gdk.MOD2_MASK)440 gdk.pointer_grab(self.calendar.window, True,gdk.BUTTON1_MASK|gdk.MOD2_MASK)
441 #we will close the calendar if the user clicks outside441 #we will close the calendar if the user clicks outside
442 442
443 if not isinstance(toset, dates.FuzzyDate):443 if not toset:
444 if not toset:444 # we set the widget to today's date if there is not a date defined
445 # we set the widget to today's date if there is not a date defined445 toset = Date.today()
446 toset = dates.date_today()446 elif not toset.is_special:
447447 y = toset.year
448 y = toset.year()448 m = toset.month
449 m = toset.month()449 d = int(toset.day)
450 d = int(toset.day())
451
452 #We have to select the day first. If not, we might ask for450 #We have to select the day first. If not, we might ask for
453 #February while still being on 31 -> error !451 #February while still being on 31 -> error !
454 self.cal_widget.select_day(d)452 self.cal_widget.select_day(d)
@@ -463,11 +461,11 @@
463 def day_selected(self,widget) :461 def day_selected(self,widget) :
464 y,m,d = widget.get_date()462 y,m,d = widget.get_date()
465 if self.__opened_date == "due" :463 if self.__opened_date == "due" :
466 self.task.set_due_date(dates.strtodate("%s-%s-%s"%(y,m+1,d)))464 self.task.set_due_date(Date.parse("%s-%s-%s"%(y,m+1,d)))
467 elif self.__opened_date == "start" :465 elif self.__opened_date == "start" :
468 self.task.set_start_date(dates.strtodate("%s-%s-%s"%(y,m+1,d)))466 self.task.set_start_date(Date.parse("%s-%s-%s"%(y,m+1,d)))
469 elif self.__opened_date == "closed" :467 elif self.__opened_date == "closed" :
470 self.task.set_closed_date(dates.strtodate("%s-%s-%s"%(y,m+1,d)))468 self.task.set_closed_date(Date.parse("%s-%s-%s"%(y,m+1,d)))
471 if self.close_when_changed :469 if self.close_when_changed :
472 #When we select a day, we connect the mouse release to the470 #When we select a day, we connect the mouse release to the
473 #closing of the calendar.471 #closing of the calendar.
@@ -497,16 +495,16 @@
497 self.__close_calendar()495 self.__close_calendar()
498 496
499 def nodate_pressed(self,widget) : #pylint: disable-msg=W0613497 def nodate_pressed(self,widget) : #pylint: disable-msg=W0613
500 self.set_opened_date(dates.no_date)498 self.set_opened_date(Date.no_date())
501 499
502 def set_fuzzydate_now(self, widget) : #pylint: disable-msg=W0613500 def set_fuzzydate_now(self, widget) : #pylint: disable-msg=W0613
503 self.set_opened_date(dates.NOW)501 self.set_opened_date(Date.today())
504 502
505 def set_fuzzydate_soon(self, widget) : #pylint: disable-msg=W0613503 def set_fuzzydate_soon(self, widget) : #pylint: disable-msg=W0613
506 self.set_opened_date(dates.SOON)504 self.set_opened_date(Date.soon())
507 505
508 def set_fuzzydate_later(self, widget) : #pylint: disable-msg=W0613506 def set_fuzzydate_later(self, widget) : #pylint: disable-msg=W0613
509 self.set_opened_date(dates.LATER)507 self.set_opened_date(Date.later())
510 508
511 def dismiss(self,widget) : #pylint: disable-msg=W0613509 def dismiss(self,widget) : #pylint: disable-msg=W0613
512 stat = self.task.get_status()510 stat = self.task.get_status()
513511
=== modified file 'GTG/plugins/evolution_sync/gtgTask.py'
--- GTG/plugins/evolution_sync/gtgTask.py 2010-03-16 02:16:14 +0000
+++ GTG/plugins/evolution_sync/gtgTask.py 2010-06-20 04:28:25 +0000
@@ -16,9 +16,10 @@
1616
17import datetime17import datetime
1818
19from GTG.tools.dates import NoDate, RealDate19from GTG.tools.dates import *
20from GTG.plugins.evolution_sync.genericTask import GenericTask20from GTG.plugins.evolution_sync.genericTask import GenericTask
2121
22
22class GtgTask(GenericTask):23class GtgTask(GenericTask):
2324
24 def __init__(self, gtg_task, plugin_api, gtg_proxy):25 def __init__(self, gtg_task, plugin_api, gtg_proxy):
@@ -62,15 +63,15 @@
6263
63 def _get_due_date(self):64 def _get_due_date(self):
64 due_date = self._gtg_task.get_due_date()65 due_date = self._gtg_task.get_due_date()
65 if due_date == NoDate():66 if due_date == Date.no_date():
66 return None67 return None
67 return due_date.to_py_date()68 return due_date._date
6869
69 def _set_due_date(self, due):70 def _set_due_date(self, due):
70 if due == None:71 if due == None:
71 gtg_due = NoDate()72 gtg_due = Date.no_date()
72 else:73 else:
73 gtg_due = RealDate(due)74 gtg_due = Date(due)
74 self._gtg_task.set_due_date(gtg_due)75 self._gtg_task.set_due_date(gtg_due)
7576
76 def _get_modified(self):77 def _get_modified(self):
7778
=== modified file 'GTG/plugins/rtm_sync/gtgTask.py'
--- GTG/plugins/rtm_sync/gtgTask.py 2010-05-05 21:54:17 +0000
+++ GTG/plugins/rtm_sync/gtgTask.py 2010-06-20 04:28:25 +0000
@@ -16,9 +16,10 @@
1616
17import datetime17import datetime
1818
19from GTG.tools.dates import NoDate, RealDate19from GTG.tools.dates import *
20from GTG.plugins.rtm_sync.genericTask import GenericTask20from GTG.plugins.rtm_sync.genericTask import GenericTask
2121
22
22class GtgTask(GenericTask):23class GtgTask(GenericTask):
23 #GtgTask passes only datetime objects with the timezone loaded 24 #GtgTask passes only datetime objects with the timezone loaded
24 # to talk about dates and times25 # to talk about dates and times
@@ -76,15 +77,15 @@
7677
77 def _get_due_date(self):78 def _get_due_date(self):
78 due_date = self._gtg_task.get_due_date()79 due_date = self._gtg_task.get_due_date()
79 if due_date == NoDate():80 if due_date == Date.no_date():
80 return None81 return None
81 return due_date.to_py_date()82 return due_date._date
8283
83 def _set_due_date(self, due):84 def _set_due_date(self, due):
84 if due == None:85 if due == None:
85 gtg_due = NoDate()86 gtg_due = Date.no_date()
86 else:87 else:
87 gtg_due = RealDate(due)88 gtg_due = Date(due)
88 self._gtg_task.set_due_date(gtg_due)89 self._gtg_task.set_due_date(gtg_due)
8990
90 def _get_modified(self):91 def _get_modified(self):
9192
=== modified file 'GTG/tools/dates.py'
--- GTG/tools/dates.py 2010-04-30 19:23:02 +0000
+++ GTG/tools/dates.py 2010-06-20 04:28:25 +0000
@@ -17,214 +17,240 @@
17# this program. If not, see <http://www.gnu.org/licenses/>.17# this program. If not, see <http://www.gnu.org/licenses/>.
18# -----------------------------------------------------------------------------18# -----------------------------------------------------------------------------
1919
20from datetime import date, timedelta20import calendar
21import datetime
21import locale22import locale
22import calendar
23from GTG import _, ngettext23from GTG import _, ngettext
2424
25#setting the locale of gtg to the system locale 25
26#locale.setlocale(locale.LC_TIME, '')26__all__ = 'Date',
27
28
29## internal constants
30# integers for special dates
31TODAY, SOON, NODATE, LATER = range(4)
32# strings representing special dates
33STRINGS = {
34 TODAY: 'today',
35 SOON: 'soon',
36 NODATE: '',
37 LATER: 'later',
38 }
39# inverse of STRINGS
40LOOKUP = dict([(v, k) for (k, v) in STRINGS.iteritems()])
41# functions giving absolute dates for special dates
42FUNCS = {
43 TODAY: lambda: datetime.date.today(),
44 SOON: lambda: datetime.date.today() + datetime.timedelta(15),
45 NODATE: lambda: datetime.date.max - datetime.timedelta(1),
46 LATER: lambda: datetime.date.max,
47 }
48
49# ISO 8601 date format
50ISODATE = '%Y-%m-%d'
51
52
53locale.setlocale(locale.LC_TIME, '')
54
2755
28class Date(object):56class Date(object):
57 """A date class that supports fuzzy dates.
58
59 Date supports all the methods of the standard datetime.date class. A Date
60 can be constructed with:
61 * the special strings 'today', 'soon', '' (no date, default), or 'later'
62 * a string containing an ISO format date: YYYY-MM-DD, or
63 * a datetime.date or Date instance.
64
65 """
66 _date = None
67 _special = None
68
69 def __init__(self, value=''):
70 if isinstance(value, datetime.date):
71 self._date = value
72 elif isinstance(value, Date):
73 self._date = value._date
74 self._special = value._special
75 elif isinstance(value, str) or isinstance(value, unicode):
76 try: # an ISO 8601 date
77 self._date = datetime.datetime.strptime(value, ISODATE).date()
78 except ValueError:
79 try: # a special date
80 self.__init__(LOOKUP[value])
81 except KeyError:
82 raise ValueError
83 elif isinstance(value, int):
84 self._date = FUNCS[value]()
85 self._special = value
86 else:
87 raise ValueError
88 assert not (self._date is None and self._special is None)
89
90 def __add__(self, other):
91 """Addition, same usage as datetime.date."""
92 if isinstance(other, datetime.timedelta):
93 return Date(self._date + other)
94 else:
95 raise NotImplementedError
96 __radd__ = __add__
97
98 def __sub__(self, other):
99 """Subtraction, same usage as datetime.date."""
100 if hasattr(other, '_date'):
101 return self._date - other._date
102 else:
103 # if other is a datetime.date, this will work, otherwise let it
104 # raise a NotImplementedError
105 return self._date - other
106
107 def __rsub__(self, other):
108 """Subtraction, same usage as datetime.date."""
109 # opposite of __sub__
110 if hasattr(other, '_date'):
111 return other._date - self._date
112 else:
113 return other - self._date
114
29 def __cmp__(self, other):115 def __cmp__(self, other):
30 if other is None: return 1116 """Compare with other Date instance."""
31 return cmp(self.to_py_date(), other.to_py_date())117 if hasattr(other, '_date'):
32 118 return cmp(self._date, other._date)
33 def __sub__(self, other):119 elif isinstance(other, datetime.date):
34 return self.to_py_date() - other.to_py_date()120 return cmp(self._date, other)
35121
36 def __get_locale_string(self):122 def __str__(self):
37 return locale.nl_langinfo(locale.D_FMT)123 """String representation.
38 124
39 def xml_str(self): return str(self)125 Date(str(d))) == d, always.
40 126
41 def day(self): return self.to_py_date().day127 """
42 def month(self): return self.to_py_date().month128 if self._special:
43 def year(self): return self.to_py_date().year129 return STRINGS[self._special]
130 else:
131 return self._date.isoformat()
132
133 def __getattr__(self, name):
134 """Provide access to the wrapped datetime.date."""
135 try:
136 return self.__dict__[name]
137 except KeyError:
138 return getattr(self._date, name)
139
140 @property
141 def is_special(self):
142 """True if the Date is one of the special values; False if it is an
143 absolute date."""
144 return not self._special
145
146 @classmethod
147 def today(cls):
148 """Return the special Date 'today'."""
149 return Date(TODAY)
150
151 @classmethod
152 def no_date(cls):
153 """Return the special Date '' (no date)."""
154 return Date(NODATE)
155
156 @classmethod
157 def soon(cls):
158 """Return the special Date 'soon'."""
159 return Date(SOON)
160
161 @classmethod
162 def later(cls):
163 """Return the special Date 'tomorrow'."""
164 return Date(LATER)
165
166 @classmethod
167 def parse(cls, string):
168 """Return a Date corresponding to *string*, or None.
169
170 *string* may be in one of the following formats:
171 * YYYY/MM/DD, YYYYMMDD, MMDD (assumes the current year),
172 * any of the special values for Date, or
173 * 'today', 'tomorrow', 'next week', 'next month' or 'next year' in
174 English or the system locale.
175
176 """
177 # sanitize input
178 if string is None:
179 string = ''
180 else:
181 sting = string.lower()
182 # try the default formats
183 try:
184 return Date(string)
185 except ValueError:
186 pass
187 today = datetime.date.today()
188 # accepted date formats
189 formats = {
190 '%Y/%m/%d': 0,
191 '%Y%m%d': 0,
192 '%m%d': 0,
193 _('today'): 0,
194 'tomorrow': 1,
195 _('tomorrow'): 1,
196 'next week': 7,
197 _('next week'): 7,
198 'next month': calendar.mdays[today.month],
199 _('next month'): calendar.mdays[today.month],
200 'next year': 365 + int(calendar.isleap(today.year)),
201 _('next year'): 365 + int(calendar.isleap(today.year)),
202 }
203 # add week day names in the current locale
204 for i in range(7):
205 formats[calendar.day_name[i]] = i + 7 - today.weekday()
206 result = None
207 # try all of the formats
208 for fmt, offset in formats.iteritems():
209 try: # attempt to parse the string with known formats
210 result = datetime.datetime.strptime(string, fmt)
211 except ValueError: # parsing didn't work
212 continue
213 else: # parsing did work
214 break
215 if result:
216 r = result.date()
217 if r == datetime.date(1900, 1, 1):
218 # a format like 'next week' was used that didn't get us a real
219 # date value. Offset from today.
220 result = today
221 elif r.year == 1900:
222 # a format like '%m%d' was used that got a real month and day,
223 # but no year. Assume this year, or the next one if the day has
224 # passed.
225 if r.month >= today.month and r.day >= today.day:
226 result = datetime.date(today.year, r.month, r.day)
227 else:
228 result = datetime.date(today.year + 1, r.month, r.day)
229 return Date(result + datetime.timedelta(offset))
230 else: # can't parse this string
231 raise ValueError("can't parse a valid date from %s" % string)
44232
45 def to_readable_string(self):233 def to_readable_string(self):
46 if self.to_py_date() == NoDate().to_py_date():234 if self._special == NODATE:
47 return None235 return None
48 dleft = (self.to_py_date() - date.today()).days236 dleft = (self - datetime.date.today()).days
49 if dleft == 0:237 if dleft == 0:
50 return _("Today")238 return _('Today')
51 elif dleft < 0:239 elif dleft < 0:
52 abs_days = abs(dleft)240 abs_days = abs(dleft)
53 return ngettext("Yesterday", "%(days)d days ago", abs_days) % \241 return ngettext('Yesterday', '%(days)d days ago', abs_days) % \
54 {"days": abs_days}242 {'days': abs_days}
55 elif dleft > 0 and dleft <= 15:243 elif dleft > 0 and dleft <= 15:
56 return ngettext("Tomorrow", "In %(days)d days", dleft) % \244 return ngettext('Tomorrow', 'In %(days)d days', dleft) % \
57 {"days": dleft}245 {'days': dleft}
58 else:246 else:
59 locale_format = self.__get_locale_string()247 locale_format = locale.nl_langinfo(locale.D_FMT)
60 if calendar.isleap(date.today().year):248 if calendar.isleap(datetime.date.today().year):
61 year_len = 366249 year_len = 366
62 else:250 else:
63 year_len = 365251 year_len = 365
64 if float(dleft) / year_len < 1.0:252 if float(dleft) / year_len < 1.0:
65 #if it's in less than a year, don't show the year field253 #if it's in less than a year, don't show the year field
66 locale_format = locale_format.replace('/%Y','')254 locale_format = locale_format.replace('/%Y','')
67 return self.to_py_date().strftime(locale_format)255 return self._date.strftime(locale_format)
68
69
70class FuzzyDate(Date):
71 def __init__(self, offset, name):
72 super(FuzzyDate, self).__init__()
73 self.name=name
74 self.offset=offset
75
76 def to_py_date(self):
77 return date.today()+timedelta(self.offset)
78
79 def __str__(self):
80 return _(self.name)
81
82 def to_readable_string(self):
83 return _(self.name)
84
85 def xml_str(self):
86 return self.name
87
88 def days_left(self):
89 return None
90
91class FuzzyDateFixed(FuzzyDate):
92 def to_py_date(self):
93 return self.offset
94
95NOW = FuzzyDate(0, _('now'))
96SOON = FuzzyDate(15, _('soon'))
97LATER = FuzzyDateFixed(date.max, _('later'))
98
99class RealDate(Date):
100 def __init__(self, dt):
101 super(RealDate, self).__init__()
102 assert(dt is not None)
103 self.proto = dt
104
105 def to_py_date(self):
106 return self.proto
107
108 def __str__(self):
109 return str(self.proto)
110
111 def days_left(self):
112 return (self.proto - date.today()).days
113
114DATE_MAX_MINUS_ONE = date.max-timedelta(1) # sooner than 'later'
115class NoDate(Date):
116
117 def __init__(self):
118 super(NoDate, self).__init__()
119
120 def to_py_date(self):
121 return DATE_MAX_MINUS_ONE
122
123 def __str__(self):
124 return ''
125
126 def days_left(self):
127 return None
128
129 def __nonzero__(self):
130 return False
131no_date = NoDate()
132
133#function to convert a string of the form YYYY-MM-DD
134#to a date
135#If the date is not correct, the function returns None
136def strtodate(stri) :
137 if stri == _("now") or stri == "now":
138 return NOW
139 elif stri == _("soon") or stri == "soon":
140 return SOON
141 elif stri == _("later") or stri == "later":
142 return LATER
143
144 toreturn = None
145 zedate = []
146 if stri :
147 if '-' in stri :
148 zedate = stri.split('-')
149 elif '/' in stri :
150 zedate = stri.split('/')
151
152 if len(zedate) == 3 :
153 y = zedate[0]
154 m = zedate[1]
155 d = zedate[2]
156 if y.isdigit() and m.isdigit() and d.isdigit() :
157 yy = int(y)
158 mm = int(m)
159 dd = int(d)
160 # we catch exceptions here
161 try :
162 toreturn = date(yy,mm,dd)
163 except ValueError:
164 toreturn = None
165
166 if not toreturn: return no_date
167 else: return RealDate(toreturn)
168
169
170def date_today():
171 return RealDate(date.today())
172
173def get_canonical_date(arg):
174 """
175 Transform "arg" in a valid yyyy-mm-dd date or return None.
176 "arg" can be a yyyy-mm-dd, yyyymmdd, mmdd, today, next week,
177 next month, next year, or a weekday name.
178 Literals are accepted both in english and in the locale language.
179 When clashes occur the locale takes precedence.
180 """
181 today = date.today()
182 #FIXME: there surely exist a way to get day names from the datetime
183 # or time module.
184 day_names = ["monday", "tuesday", "wednesday", \
185 "thursday", "friday", "saturday", \
186 "sunday"]
187 day_names_localized = [_("monday"), _("tuesday"), _("wednesday"), \
188 _("thursday"), _("friday"), _("saturday"), \
189 _("sunday")]
190 delta_day_names = {"today": 0, \
191 "tomorrow": 1, \
192 "next week": 7, \
193 "next month": calendar.mdays[today.month], \
194 "next year": 365 + int(calendar.isleap(today.year))}
195 delta_day_names_localized = \
196 {_("today"): 0, \
197 _("tomorrow"): 1, \
198 _("next week"): 7, \
199 _("next month"): calendar.mdays[today.month], \
200 _("next year"): 365 + int(calendar.isleap(today.year))}
201 ### String sanitization
202 arg = arg.lower()
203 ### Conversion
204 #yyyymmdd and mmdd
205 if arg.isdigit():
206 if len(arg) == 4:
207 arg = str(date.today().year) + arg
208 assert(len(arg) == 8)
209 arg = "%s-%s-%s" % (arg[:4], arg[4:6], arg[6:])
210 #today, tomorrow, next {week, months, year}
211 elif arg in delta_day_names.keys() or \
212 arg in delta_day_names_localized.keys():
213 if arg in delta_day_names:
214 delta = delta_day_names[arg]
215 else:
216 delta = delta_day_names_localized[arg]
217 arg = (today + timedelta(days = delta)).isoformat()
218 elif arg in day_names or arg in day_names_localized:
219 if arg in day_names:
220 arg_day = day_names.index(arg)
221 else:
222 arg_day = day_names_localized.index(arg)
223 today_day = today.weekday()
224 next_date = timedelta(days = arg_day - today_day + \
225 7 * int(arg_day <= today_day)) + today
226 arg = "%i-%i-%i" % (next_date.year, \
227 next_date.month, \
228 next_date.day)
229 return strtodate(arg)
230256
231257
=== modified file 'GTG/tools/taskxml.py'
--- GTG/tools/taskxml.py 2010-06-18 16:36:17 +0000
+++ GTG/tools/taskxml.py 2010-06-20 04:28:25 +0000
@@ -21,8 +21,8 @@
21import xml.dom.minidom21import xml.dom.minidom
22import xml.sax.saxutils as saxutils22import xml.sax.saxutils as saxutils
2323
24from GTG.tools import cleanxml24from GTG.tools import cleanxml
25from GTG.tools import dates25from GTG.tools.dates import *
2626
27#Take an empty task, an XML node and return a Task.27#Take an empty task, an XML node and return a Task.
28def task_from_xml(task,xmlnode) :28def task_from_xml(task,xmlnode) :
@@ -31,7 +31,7 @@
31 uuid = "%s" %xmlnode.getAttribute("uuid")31 uuid = "%s" %xmlnode.getAttribute("uuid")
32 cur_task.set_uuid(uuid)32 cur_task.set_uuid(uuid)
33 donedate = cleanxml.readTextNode(xmlnode,"donedate")33 donedate = cleanxml.readTextNode(xmlnode,"donedate")
34 cur_task.set_status(cur_stat,donedate=dates.strtodate(donedate))34 cur_task.set_status(cur_stat,donedate=Date.parse(donedate))
35 #we will fill the task with its content35 #we will fill the task with its content
36 cur_task.set_title(cleanxml.readTextNode(xmlnode,"title"))36 cur_task.set_title(cleanxml.readTextNode(xmlnode,"title"))
37 #the subtasks37 #the subtasks
@@ -54,9 +54,9 @@
54 tas = "<content>%s</content>" %tasktext[0].firstChild.nodeValue54 tas = "<content>%s</content>" %tasktext[0].firstChild.nodeValue
55 content = xml.dom.minidom.parseString(tas)55 content = xml.dom.minidom.parseString(tas)
56 cur_task.set_text(content.firstChild.toxml()) #pylint: disable-msg=E1103 56 cur_task.set_text(content.firstChild.toxml()) #pylint: disable-msg=E1103
57 cur_task.set_due_date(dates.strtodate(cleanxml.readTextNode(xmlnode,"duedate")))57 cur_task.set_due_date(Date.parse(cleanxml.readTextNode(xmlnode,"duedate")))
58 cur_task.set_modified(cleanxml.readTextNode(xmlnode,"modified"))58 cur_task.set_modified(cleanxml.readTextNode(xmlnode,"modified"))
59 cur_task.set_start_date(dates.strtodate(cleanxml.readTextNode(xmlnode,"startdate")))59 cur_task.set_start_date(Date.parse(cleanxml.readTextNode(xmlnode,"startdate")))
60 cur_tags = xmlnode.getAttribute("tags").replace(' ','').split(",")60 cur_tags = xmlnode.getAttribute("tags").replace(' ','').split(",")
61 if "" in cur_tags: cur_tags.remove("")61 if "" in cur_tags: cur_tags.remove("")
62 for tag in cur_tags: cur_task.tag_added(saxutils.unescape(tag))62 for tag in cur_tags: cur_task.tag_added(saxutils.unescape(tag))
@@ -74,10 +74,10 @@
74 tags_str = tags_str + saxutils.escape(str(tag)) + ","74 tags_str = tags_str + saxutils.escape(str(tag)) + ","
75 t_xml.setAttribute("tags", tags_str[:-1])75 t_xml.setAttribute("tags", tags_str[:-1])
76 cleanxml.addTextNode(doc,t_xml,"title",task.get_title())76 cleanxml.addTextNode(doc,t_xml,"title",task.get_title())
77 cleanxml.addTextNode(doc,t_xml,"duedate", task.get_due_date().xml_str())77 cleanxml.addTextNode(doc,t_xml,"duedate", str(task.get_due_date()))
78 cleanxml.addTextNode(doc,t_xml,"modified",task.get_modified_string())78 cleanxml.addTextNode(doc,t_xml,"modified",task.get_modified_string())
79 cleanxml.addTextNode(doc,t_xml,"startdate", task.get_start_date().xml_str())79 cleanxml.addTextNode(doc,t_xml,"startdate", str(task.get_start_date()))
80 cleanxml.addTextNode(doc,t_xml,"donedate", task.get_closed_date().xml_str())80 cleanxml.addTextNode(doc,t_xml,"donedate", str(task.get_closed_date()))
81 childs = task.get_children()81 childs = task.get_children()
82 for c in childs :82 for c in childs :
83 cleanxml.addTextNode(doc,t_xml,"subtask",c)83 cleanxml.addTextNode(doc,t_xml,"subtask",c)

Subscribers

People subscribed via source and target branches

to status/vote changes: