gtg team mailing list archive
-
gtg team
-
Mailing list archive
-
Message #02731
[Merge] lp:~gtg-contributors/gtg/new-date-class into lp:gtg
Paul Kishimoto has proposed merging lp:~gtg-contributors/gtg/new-date-class into lp:gtg.
Requested reviews:
Gtg developers (gtg)
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.
--
https://code.launchpad.net/~gtg-contributors/gtg/new-date-class/+merge/28009
Your team Gtg developers is requested to review the proposed merge of lp:~gtg-contributors/gtg/new-date-class into lp:gtg.
=== 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 @@
from datetime import datetime
-from GTG.core.task import Task
-from GTG.tools.dates import date_today, no_date, Date
+from GTG.core.task import Task
+from GTG.tools.dates import *
class Filter:
@@ -179,12 +179,12 @@
def is_started(self,task,parameters=None):
'''Filter for tasks that are already started'''
start_date = task.get_start_date()
- if start_date :
+ if start_date:
#Seems like pylint falsely assumes that subtraction always results
#in an object of the same type. The subtraction of dates
#results in a datetime.timedelta object
#that does have a 'days' member.
- difference = date_today() - start_date
+ difference = Date.today() - start_date
if difference.days == 0:
# Don't count today's tasks started until morning
return datetime.now().hour > 4
@@ -202,14 +202,14 @@
def workdue(self,task):
''' Filter for tasks due within the next day '''
wv = self.workview(task) and \
- task.get_due_date() != no_date and \
+ task.get_due_date() != Date.no_date() and \
task.get_days_left() < 2
return wv
def worklate(self,task):
''' Filter for tasks due within the next day '''
wv = self.workview(task) and \
- task.get_due_date() != no_date and \
+ task.get_due_date() != Date.no_date() and \
task.get_days_late() > 0
return wv
=== 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 @@
from GTG.core.filters_bank import FiltersBank
from GTG.core.task import Task
from GTG.core.tagstore import Tag
-from GTG.tools.dates import date_today
from GTG.tools.logger import Log
class Requester(gobject.GObject):
=== 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 @@
"""
task.py contains the Task class which represents (guess what) a task
"""
-
+import cgi
+from datetime import datetime
+import uuid
import xml.dom.minidom
-import uuid
-import cgi
import xml.sax.saxutils as saxutils
from GTG import _
-from GTG.tools.dates import date_today, no_date, Date
-from datetime import datetime
+from GTG.tools.dates import *
+
from GTG.core.tree import TreeNode
from GTG.tools.logger import Log
@@ -55,9 +55,9 @@
self.title = _("My new task")
#available status are: Active - Done - Dismiss - Note
self.status = self.STA_ACTIVE
- self.closed_date = no_date
- self.due_date = no_date
- self.start_date = no_date
+ self.closed_date = Date.no_date()
+ self.due_date = Date.no_date()
+ self.start_date = Date.no_date()
self.can_be_deleted = newtask
# tags
self.tags = []
@@ -134,10 +134,10 @@
c.set_status(status, donedate=donedate)
#to the specified date (if any)
if donedate:
- self.closed_date = donedate
+ self.closed_date = Date(donedate)
#or to today
else:
- self.closed_date = date_today()
+ self.closed_date = Date.today()
#If we mark a task as Active and that some parent are not
#Active, we break the parent/child relation
#It has no sense to have an active subtask of a done parent.
@@ -166,14 +166,13 @@
return self.modified
def get_modified_string(self):
- return self.modified.strftime("%Y-%m-%dT%H:%M:%S")
+ return self.modified.isoformat()
def set_modified(self, modified):
self.modified = modified
def set_due_date(self, fulldate):
- assert(isinstance(fulldate, Date))
- self.due_date = fulldate
+ self.due_date = Date(fulldate)
self.sync()
#Due date return the most urgent date of all parents
@@ -189,16 +188,14 @@
return zedate
def set_start_date(self, fulldate):
- assert(isinstance(fulldate, Date))
- self.start_date = fulldate
+ self.start_date = Date(fulldate)
self.sync()
def get_start_date(self):
return self.start_date
def set_closed_date(self, fulldate):
- assert(isinstance(fulldate, Date))
- self.closed_date = fulldate
+ self.closed_date = Date(fulldate)
self.sync()
def get_closed_date(self):
@@ -206,13 +203,13 @@
def get_days_left(self):
due_date = self.get_due_date()
- if due_date == no_date:
+ if due_date == Date.no_date():
return None
- return due_date.days_left()
+ return (due_date - Date.today()).days
def get_days_late(self):
due_date = self.get_due_date()
- if due_date == no_date:
+ if due_date == Date.no_date():
return None
closed_date = self.get_closed_date()
return (closed_date - due_date).days
=== 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 @@
ClosedTaskTreeView
from GTG.gtk.browser.tagtree import TagTree
from GTG.tools import openurl
-from GTG.tools.dates import strtodate,\
- no_date,\
- FuzzyDate, \
- get_canonical_date
+from GTG.tools.dates import *
from GTG.tools.logger import Log
#from GTG.tools import clipboard
@@ -607,13 +604,12 @@
return s
else:
return -1 * s
-
-
+
if sort == 0:
# Put fuzzy dates below real dates
- if isinstance(t1, FuzzyDate) and not isinstance(t2, FuzzyDate):
+ if t1.is_special and not t2.is_special:
sort = reverse_if_descending(1)
- elif isinstance(t2, FuzzyDate) and not isinstance(t1, FuzzyDate):
+ elif t2.is_special and not t1.is_special:
sort = reverse_if_descending(-1)
if sort == 0: # Group tasks with the same tag together for visual cleanness
@@ -915,8 +911,8 @@
def on_quickadd_activate(self, widget):
text = self.quickadd_entry.get_text()
- due_date = no_date
- defer_date = no_date
+ due_date = Date.no_date()
+ defer_date = Date.no_date()
if text:
tags, notagonly = self.get_selected_tags()
# Get tags in the title
@@ -940,12 +936,12 @@
tags.append(GTG.core.tagstore.Tag(tag, self.req))
elif attribute.lower() == "defer" or \
attribute.lower() == _("defer"):
- defer_date = get_canonical_date(args)
+ defer_date = Date.parse(args)
if not defer_date:
valid_attribute = False
elif attribute.lower() == "due" or \
attribute.lower() == _("due"):
- due_date = get_canonical_date(args)
+ due_date = Date.parse(args)
if not due_date:
valid_attribute = False
else:
@@ -1114,7 +1110,7 @@
tasks = [self.req.get_task(uid) for uid in tasks_uid]
tasks_status = [task.get_status() for task in tasks]
for uid, task, status in zip(tasks_uid, tasks, tasks_status):
- task.set_start_date(get_canonical_date(new_start_date))
+ task.set_start_date(Date.parse(new_start_date))
#FIXME: If the task dialog is displayed, refresh its start_date widget
def on_mark_as_started(self, widget):
=== 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 @@
import dbus.glib
import dbus.service
-from GTG.core import CoreConfig
-from GTG.tools import dates
+from GTG.core import CoreConfig
+from GTG.tools.dates import *
BUSNAME = CoreConfig.BUSNAME
@@ -169,10 +169,10 @@
nt = self.req.new_task(tags=tags)
for sub in subtasks:
nt.add_child(sub)
- nt.set_status(status, donedate=dates.strtodate(donedate))
+ nt.set_status(status, donedate=Date.parse(donedate))
nt.set_title(title)
- nt.set_due_date(dates.strtodate(duedate))
- nt.set_start_date(dates.strtodate(startdate))
+ nt.set_due_date(Date.parse(duedate))
+ nt.set_start_date(Date.parse(startdate))
nt.set_text(text)
return task_to_dict(nt)
@@ -187,10 +187,10 @@
via this function.
"""
task = self.req.get_task(tid)
- task.set_status(task_data["status"], donedate=dates.strtodate(task_data["donedate"]))
+ task.set_status(task_data["status"], donedate=Date.parse(task_data["donedate"]))
task.set_title(task_data["title"])
- task.set_due_date(dates.strtodate(task_data["duedate"]))
- task.set_start_date(dates.strtodate(task_data["startdate"]))
+ task.set_due_date(Date.parse(task_data["duedate"]))
+ task.set_start_date(Date.parse(task_data["startdate"]))
task.set_text(task_data["text"])
for tag in task_data["tags"]:
=== 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 @@
from GTG.core.plugins.engine import PluginEngine
from GTG.core.plugins.api import PluginAPI
from GTG.core.task import Task
-from GTG.tools import dates
+from GTG.tools.dates import *
date_separator = "-"
@@ -307,13 +307,13 @@
#refreshing the due date field
duedate = self.task.get_due_date()
- prevdate = dates.strtodate(self.duedate_widget.get_text())
+ prevdate = Date.parse(self.duedate_widget.get_text())
if duedate != prevdate or type(duedate) is not type(prevdate):
zedate = str(duedate).replace("-", date_separator)
self.duedate_widget.set_text(zedate)
# refreshing the closed date field
closeddate = self.task.get_closed_date()
- prevcldate = dates.strtodate(self.closeddate_widget.get_text())
+ prevcldate = Date.parse(self.closeddate_widget.get_text())
if closeddate != prevcldate or type(closeddate) is not type(prevcldate):
zecldate = str(closeddate).replace("-", date_separator)
self.closeddate_widget.set_text(zecldate)
@@ -348,7 +348,7 @@
self.dayleft_label.set_markup("<span color='"+color+"'>"+txt+"</span>")
startdate = self.task.get_start_date()
- prevdate = dates.strtodate(self.startdate_widget.get_text())
+ prevdate = Date.parse(self.startdate_widget.get_text())
if startdate != prevdate or type(startdate) is not type(prevdate):
zedate = str(startdate).replace("-",date_separator)
self.startdate_widget.set_text(zedate)
@@ -377,9 +377,9 @@
validdate = False
if not text :
validdate = True
- datetoset = dates.no_date
+ datetoset = Date.no_date()
else :
- datetoset = dates.strtodate(text)
+ datetoset = Date.parse(text)
if datetoset :
validdate = True
@@ -400,15 +400,15 @@
widget.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse("#F88"))
def _mark_today_in_bold(self):
- today = dates.date_today()
+ today = Date.today()
#selected is a tuple containing (year, month, day)
selected = self.cal_widget.get_date()
#the following "-1" is because in pygtk calendar the month is 0-based,
# in gtg (and datetime.date) is 1-based.
- if selected[1] == today.month() - 1 and selected[0] == today.year():
- self.cal_widget.mark_day(today.day())
+ if selected[1] == today.month - 1 and selected[0] == today.year:
+ self.cal_widget.mark_day(today.day)
else:
- self.cal_widget.unmark_day(today.day())
+ self.cal_widget.unmark_day(today.day)
def on_date_pressed(self, widget,data):
@@ -440,15 +440,13 @@
gdk.pointer_grab(self.calendar.window, True,gdk.BUTTON1_MASK|gdk.MOD2_MASK)
#we will close the calendar if the user clicks outside
- if not isinstance(toset, dates.FuzzyDate):
- if not toset:
- # we set the widget to today's date if there is not a date defined
- toset = dates.date_today()
-
- y = toset.year()
- m = toset.month()
- d = int(toset.day())
-
+ if not toset:
+ # we set the widget to today's date if there is not a date defined
+ toset = Date.today()
+ elif not toset.is_special:
+ y = toset.year
+ m = toset.month
+ d = int(toset.day)
#We have to select the day first. If not, we might ask for
#February while still being on 31 -> error !
self.cal_widget.select_day(d)
@@ -463,11 +461,11 @@
def day_selected(self,widget) :
y,m,d = widget.get_date()
if self.__opened_date == "due" :
- self.task.set_due_date(dates.strtodate("%s-%s-%s"%(y,m+1,d)))
+ self.task.set_due_date(Date.parse("%s-%s-%s"%(y,m+1,d)))
elif self.__opened_date == "start" :
- self.task.set_start_date(dates.strtodate("%s-%s-%s"%(y,m+1,d)))
+ self.task.set_start_date(Date.parse("%s-%s-%s"%(y,m+1,d)))
elif self.__opened_date == "closed" :
- self.task.set_closed_date(dates.strtodate("%s-%s-%s"%(y,m+1,d)))
+ self.task.set_closed_date(Date.parse("%s-%s-%s"%(y,m+1,d)))
if self.close_when_changed :
#When we select a day, we connect the mouse release to the
#closing of the calendar.
@@ -497,16 +495,16 @@
self.__close_calendar()
def nodate_pressed(self,widget) : #pylint: disable-msg=W0613
- self.set_opened_date(dates.no_date)
+ self.set_opened_date(Date.no_date())
def set_fuzzydate_now(self, widget) : #pylint: disable-msg=W0613
- self.set_opened_date(dates.NOW)
+ self.set_opened_date(Date.today())
def set_fuzzydate_soon(self, widget) : #pylint: disable-msg=W0613
- self.set_opened_date(dates.SOON)
+ self.set_opened_date(Date.soon())
def set_fuzzydate_later(self, widget) : #pylint: disable-msg=W0613
- self.set_opened_date(dates.LATER)
+ self.set_opened_date(Date.later())
def dismiss(self,widget) : #pylint: disable-msg=W0613
stat = self.task.get_status()
=== 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 @@
import datetime
-from GTG.tools.dates import NoDate, RealDate
+from GTG.tools.dates import *
from GTG.plugins.evolution_sync.genericTask import GenericTask
+
class GtgTask(GenericTask):
def __init__(self, gtg_task, plugin_api, gtg_proxy):
@@ -62,15 +63,15 @@
def _get_due_date(self):
due_date = self._gtg_task.get_due_date()
- if due_date == NoDate():
+ if due_date == Date.no_date():
return None
- return due_date.to_py_date()
+ return due_date._date
def _set_due_date(self, due):
if due == None:
- gtg_due = NoDate()
+ gtg_due = Date.no_date()
else:
- gtg_due = RealDate(due)
+ gtg_due = Date(due)
self._gtg_task.set_due_date(gtg_due)
def _get_modified(self):
=== 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 @@
import datetime
-from GTG.tools.dates import NoDate, RealDate
+from GTG.tools.dates import *
from GTG.plugins.rtm_sync.genericTask import GenericTask
+
class GtgTask(GenericTask):
#GtgTask passes only datetime objects with the timezone loaded
# to talk about dates and times
@@ -76,15 +77,15 @@
def _get_due_date(self):
due_date = self._gtg_task.get_due_date()
- if due_date == NoDate():
+ if due_date == Date.no_date():
return None
- return due_date.to_py_date()
+ return due_date._date
def _set_due_date(self, due):
if due == None:
- gtg_due = NoDate()
+ gtg_due = Date.no_date()
else:
- gtg_due = RealDate(due)
+ gtg_due = Date(due)
self._gtg_task.set_due_date(gtg_due)
def _get_modified(self):
=== 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 @@
# this program. If not, see <http://www.gnu.org/licenses/>.
# -----------------------------------------------------------------------------
-from datetime import date, timedelta
+import calendar
+import datetime
import locale
-import calendar
from GTG import _, ngettext
-#setting the locale of gtg to the system locale
-#locale.setlocale(locale.LC_TIME, '')
+
+__all__ = 'Date',
+
+
+## internal constants
+# integers for special dates
+TODAY, SOON, NODATE, LATER = range(4)
+# strings representing special dates
+STRINGS = {
+ TODAY: 'today',
+ SOON: 'soon',
+ NODATE: '',
+ LATER: 'later',
+ }
+# inverse of STRINGS
+LOOKUP = dict([(v, k) for (k, v) in STRINGS.iteritems()])
+# functions giving absolute dates for special dates
+FUNCS = {
+ TODAY: lambda: datetime.date.today(),
+ SOON: lambda: datetime.date.today() + datetime.timedelta(15),
+ NODATE: lambda: datetime.date.max - datetime.timedelta(1),
+ LATER: lambda: datetime.date.max,
+ }
+
+# ISO 8601 date format
+ISODATE = '%Y-%m-%d'
+
+
+locale.setlocale(locale.LC_TIME, '')
+
class Date(object):
+ """A date class that supports fuzzy dates.
+
+ Date supports all the methods of the standard datetime.date class. A Date
+ can be constructed with:
+ * the special strings 'today', 'soon', '' (no date, default), or 'later'
+ * a string containing an ISO format date: YYYY-MM-DD, or
+ * a datetime.date or Date instance.
+
+ """
+ _date = None
+ _special = None
+
+ def __init__(self, value=''):
+ if isinstance(value, datetime.date):
+ self._date = value
+ elif isinstance(value, Date):
+ self._date = value._date
+ self._special = value._special
+ elif isinstance(value, str) or isinstance(value, unicode):
+ try: # an ISO 8601 date
+ self._date = datetime.datetime.strptime(value, ISODATE).date()
+ except ValueError:
+ try: # a special date
+ self.__init__(LOOKUP[value])
+ except KeyError:
+ raise ValueError
+ elif isinstance(value, int):
+ self._date = FUNCS[value]()
+ self._special = value
+ else:
+ raise ValueError
+ assert not (self._date is None and self._special is None)
+
+ def __add__(self, other):
+ """Addition, same usage as datetime.date."""
+ if isinstance(other, datetime.timedelta):
+ return Date(self._date + other)
+ else:
+ raise NotImplementedError
+ __radd__ = __add__
+
+ def __sub__(self, other):
+ """Subtraction, same usage as datetime.date."""
+ if hasattr(other, '_date'):
+ return self._date - other._date
+ else:
+ # if other is a datetime.date, this will work, otherwise let it
+ # raise a NotImplementedError
+ return self._date - other
+
+ def __rsub__(self, other):
+ """Subtraction, same usage as datetime.date."""
+ # opposite of __sub__
+ if hasattr(other, '_date'):
+ return other._date - self._date
+ else:
+ return other - self._date
+
def __cmp__(self, other):
- if other is None: return 1
- return cmp(self.to_py_date(), other.to_py_date())
-
- def __sub__(self, other):
- return self.to_py_date() - other.to_py_date()
-
- def __get_locale_string(self):
- return locale.nl_langinfo(locale.D_FMT)
-
- def xml_str(self): return str(self)
-
- def day(self): return self.to_py_date().day
- def month(self): return self.to_py_date().month
- def year(self): return self.to_py_date().year
+ """Compare with other Date instance."""
+ if hasattr(other, '_date'):
+ return cmp(self._date, other._date)
+ elif isinstance(other, datetime.date):
+ return cmp(self._date, other)
+
+ def __str__(self):
+ """String representation.
+
+ Date(str(d))) == d, always.
+
+ """
+ if self._special:
+ return STRINGS[self._special]
+ else:
+ return self._date.isoformat()
+
+ def __getattr__(self, name):
+ """Provide access to the wrapped datetime.date."""
+ try:
+ return self.__dict__[name]
+ except KeyError:
+ return getattr(self._date, name)
+
+ @property
+ def is_special(self):
+ """True if the Date is one of the special values; False if it is an
+ absolute date."""
+ return not self._special
+
+ @classmethod
+ def today(cls):
+ """Return the special Date 'today'."""
+ return Date(TODAY)
+
+ @classmethod
+ def no_date(cls):
+ """Return the special Date '' (no date)."""
+ return Date(NODATE)
+
+ @classmethod
+ def soon(cls):
+ """Return the special Date 'soon'."""
+ return Date(SOON)
+
+ @classmethod
+ def later(cls):
+ """Return the special Date 'tomorrow'."""
+ return Date(LATER)
+
+ @classmethod
+ def parse(cls, string):
+ """Return a Date corresponding to *string*, or None.
+
+ *string* may be in one of the following formats:
+ * YYYY/MM/DD, YYYYMMDD, MMDD (assumes the current year),
+ * any of the special values for Date, or
+ * 'today', 'tomorrow', 'next week', 'next month' or 'next year' in
+ English or the system locale.
+
+ """
+ # sanitize input
+ if string is None:
+ string = ''
+ else:
+ sting = string.lower()
+ # try the default formats
+ try:
+ return Date(string)
+ except ValueError:
+ pass
+ today = datetime.date.today()
+ # accepted date formats
+ formats = {
+ '%Y/%m/%d': 0,
+ '%Y%m%d': 0,
+ '%m%d': 0,
+ _('today'): 0,
+ 'tomorrow': 1,
+ _('tomorrow'): 1,
+ 'next week': 7,
+ _('next week'): 7,
+ 'next month': calendar.mdays[today.month],
+ _('next month'): calendar.mdays[today.month],
+ 'next year': 365 + int(calendar.isleap(today.year)),
+ _('next year'): 365 + int(calendar.isleap(today.year)),
+ }
+ # add week day names in the current locale
+ for i in range(7):
+ formats[calendar.day_name[i]] = i + 7 - today.weekday()
+ result = None
+ # try all of the formats
+ for fmt, offset in formats.iteritems():
+ try: # attempt to parse the string with known formats
+ result = datetime.datetime.strptime(string, fmt)
+ except ValueError: # parsing didn't work
+ continue
+ else: # parsing did work
+ break
+ if result:
+ r = result.date()
+ if r == datetime.date(1900, 1, 1):
+ # a format like 'next week' was used that didn't get us a real
+ # date value. Offset from today.
+ result = today
+ elif r.year == 1900:
+ # a format like '%m%d' was used that got a real month and day,
+ # but no year. Assume this year, or the next one if the day has
+ # passed.
+ if r.month >= today.month and r.day >= today.day:
+ result = datetime.date(today.year, r.month, r.day)
+ else:
+ result = datetime.date(today.year + 1, r.month, r.day)
+ return Date(result + datetime.timedelta(offset))
+ else: # can't parse this string
+ raise ValueError("can't parse a valid date from %s" % string)
def to_readable_string(self):
- if self.to_py_date() == NoDate().to_py_date():
+ if self._special == NODATE:
return None
- dleft = (self.to_py_date() - date.today()).days
+ dleft = (self - datetime.date.today()).days
if dleft == 0:
- return _("Today")
+ return _('Today')
elif dleft < 0:
abs_days = abs(dleft)
- return ngettext("Yesterday", "%(days)d days ago", abs_days) % \
- {"days": abs_days}
+ return ngettext('Yesterday', '%(days)d days ago', abs_days) % \
+ {'days': abs_days}
elif dleft > 0 and dleft <= 15:
- return ngettext("Tomorrow", "In %(days)d days", dleft) % \
- {"days": dleft}
+ return ngettext('Tomorrow', 'In %(days)d days', dleft) % \
+ {'days': dleft}
else:
- locale_format = self.__get_locale_string()
- if calendar.isleap(date.today().year):
+ locale_format = locale.nl_langinfo(locale.D_FMT)
+ if calendar.isleap(datetime.date.today().year):
year_len = 366
else:
year_len = 365
if float(dleft) / year_len < 1.0:
#if it's in less than a year, don't show the year field
locale_format = locale_format.replace('/%Y','')
- return self.to_py_date().strftime(locale_format)
-
-
-class FuzzyDate(Date):
- def __init__(self, offset, name):
- super(FuzzyDate, self).__init__()
- self.name=name
- self.offset=offset
-
- def to_py_date(self):
- return date.today()+timedelta(self.offset)
-
- def __str__(self):
- return _(self.name)
-
- def to_readable_string(self):
- return _(self.name)
-
- def xml_str(self):
- return self.name
-
- def days_left(self):
- return None
-
-class FuzzyDateFixed(FuzzyDate):
- def to_py_date(self):
- return self.offset
-
-NOW = FuzzyDate(0, _('now'))
-SOON = FuzzyDate(15, _('soon'))
-LATER = FuzzyDateFixed(date.max, _('later'))
-
-class RealDate(Date):
- def __init__(self, dt):
- super(RealDate, self).__init__()
- assert(dt is not None)
- self.proto = dt
-
- def to_py_date(self):
- return self.proto
-
- def __str__(self):
- return str(self.proto)
-
- def days_left(self):
- return (self.proto - date.today()).days
-
-DATE_MAX_MINUS_ONE = date.max-timedelta(1) # sooner than 'later'
-class NoDate(Date):
-
- def __init__(self):
- super(NoDate, self).__init__()
-
- def to_py_date(self):
- return DATE_MAX_MINUS_ONE
-
- def __str__(self):
- return ''
-
- def days_left(self):
- return None
-
- def __nonzero__(self):
- return False
-no_date = NoDate()
-
-#function to convert a string of the form YYYY-MM-DD
-#to a date
-#If the date is not correct, the function returns None
-def strtodate(stri) :
- if stri == _("now") or stri == "now":
- return NOW
- elif stri == _("soon") or stri == "soon":
- return SOON
- elif stri == _("later") or stri == "later":
- return LATER
-
- toreturn = None
- zedate = []
- if stri :
- if '-' in stri :
- zedate = stri.split('-')
- elif '/' in stri :
- zedate = stri.split('/')
-
- if len(zedate) == 3 :
- y = zedate[0]
- m = zedate[1]
- d = zedate[2]
- if y.isdigit() and m.isdigit() and d.isdigit() :
- yy = int(y)
- mm = int(m)
- dd = int(d)
- # we catch exceptions here
- try :
- toreturn = date(yy,mm,dd)
- except ValueError:
- toreturn = None
-
- if not toreturn: return no_date
- else: return RealDate(toreturn)
-
-
-def date_today():
- return RealDate(date.today())
-
-def get_canonical_date(arg):
- """
- Transform "arg" in a valid yyyy-mm-dd date or return None.
- "arg" can be a yyyy-mm-dd, yyyymmdd, mmdd, today, next week,
- next month, next year, or a weekday name.
- Literals are accepted both in english and in the locale language.
- When clashes occur the locale takes precedence.
- """
- today = date.today()
- #FIXME: there surely exist a way to get day names from the datetime
- # or time module.
- day_names = ["monday", "tuesday", "wednesday", \
- "thursday", "friday", "saturday", \
- "sunday"]
- day_names_localized = [_("monday"), _("tuesday"), _("wednesday"), \
- _("thursday"), _("friday"), _("saturday"), \
- _("sunday")]
- delta_day_names = {"today": 0, \
- "tomorrow": 1, \
- "next week": 7, \
- "next month": calendar.mdays[today.month], \
- "next year": 365 + int(calendar.isleap(today.year))}
- delta_day_names_localized = \
- {_("today"): 0, \
- _("tomorrow"): 1, \
- _("next week"): 7, \
- _("next month"): calendar.mdays[today.month], \
- _("next year"): 365 + int(calendar.isleap(today.year))}
- ### String sanitization
- arg = arg.lower()
- ### Conversion
- #yyyymmdd and mmdd
- if arg.isdigit():
- if len(arg) == 4:
- arg = str(date.today().year) + arg
- assert(len(arg) == 8)
- arg = "%s-%s-%s" % (arg[:4], arg[4:6], arg[6:])
- #today, tomorrow, next {week, months, year}
- elif arg in delta_day_names.keys() or \
- arg in delta_day_names_localized.keys():
- if arg in delta_day_names:
- delta = delta_day_names[arg]
- else:
- delta = delta_day_names_localized[arg]
- arg = (today + timedelta(days = delta)).isoformat()
- elif arg in day_names or arg in day_names_localized:
- if arg in day_names:
- arg_day = day_names.index(arg)
- else:
- arg_day = day_names_localized.index(arg)
- today_day = today.weekday()
- next_date = timedelta(days = arg_day - today_day + \
- 7 * int(arg_day <= today_day)) + today
- arg = "%i-%i-%i" % (next_date.year, \
- next_date.month, \
- next_date.day)
- return strtodate(arg)
+ return self._date.strftime(locale_format)
=== 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 @@
import xml.dom.minidom
import xml.sax.saxutils as saxutils
-from GTG.tools import cleanxml
-from GTG.tools import dates
+from GTG.tools import cleanxml
+from GTG.tools.dates import *
#Take an empty task, an XML node and return a Task.
def task_from_xml(task,xmlnode) :
@@ -31,7 +31,7 @@
uuid = "%s" %xmlnode.getAttribute("uuid")
cur_task.set_uuid(uuid)
donedate = cleanxml.readTextNode(xmlnode,"donedate")
- cur_task.set_status(cur_stat,donedate=dates.strtodate(donedate))
+ cur_task.set_status(cur_stat,donedate=Date.parse(donedate))
#we will fill the task with its content
cur_task.set_title(cleanxml.readTextNode(xmlnode,"title"))
#the subtasks
@@ -54,9 +54,9 @@
tas = "<content>%s</content>" %tasktext[0].firstChild.nodeValue
content = xml.dom.minidom.parseString(tas)
cur_task.set_text(content.firstChild.toxml()) #pylint: disable-msg=E1103
- cur_task.set_due_date(dates.strtodate(cleanxml.readTextNode(xmlnode,"duedate")))
+ cur_task.set_due_date(Date.parse(cleanxml.readTextNode(xmlnode,"duedate")))
cur_task.set_modified(cleanxml.readTextNode(xmlnode,"modified"))
- cur_task.set_start_date(dates.strtodate(cleanxml.readTextNode(xmlnode,"startdate")))
+ cur_task.set_start_date(Date.parse(cleanxml.readTextNode(xmlnode,"startdate")))
cur_tags = xmlnode.getAttribute("tags").replace(' ','').split(",")
if "" in cur_tags: cur_tags.remove("")
for tag in cur_tags: cur_task.tag_added(saxutils.unescape(tag))
@@ -74,10 +74,10 @@
tags_str = tags_str + saxutils.escape(str(tag)) + ","
t_xml.setAttribute("tags", tags_str[:-1])
cleanxml.addTextNode(doc,t_xml,"title",task.get_title())
- cleanxml.addTextNode(doc,t_xml,"duedate", task.get_due_date().xml_str())
+ cleanxml.addTextNode(doc,t_xml,"duedate", str(task.get_due_date()))
cleanxml.addTextNode(doc,t_xml,"modified",task.get_modified_string())
- cleanxml.addTextNode(doc,t_xml,"startdate", task.get_start_date().xml_str())
- cleanxml.addTextNode(doc,t_xml,"donedate", task.get_closed_date().xml_str())
+ cleanxml.addTextNode(doc,t_xml,"startdate", str(task.get_start_date()))
+ cleanxml.addTextNode(doc,t_xml,"donedate", str(task.get_closed_date()))
childs = task.get_children()
for c in childs :
cleanxml.addTextNode(doc,t_xml,"subtask",c)
Follow ups