GTG

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

Proposed by Paul Natsuo Kishimoto
Status: Merged
Approved by: Izidor Matušov
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
Bryce Harrington (community) code Approve
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.
Revision history for this message
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.

Revision history for this message
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.

Revision history for this message
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.

Revision history for this message
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.

Revision history for this message
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)
Revision history for this message
Paul Natsuo Kishimoto (khaeru) wrote :

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

Revision history for this message
Paul Natsuo Kishimoto (khaeru) wrote :

Once again, can this be either committed or rejected?

review: Needs Resubmitting
Revision history for this message
Paul Natsuo Kishimoto (khaeru) :
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: