def test_parse_fuzzy_dates(self): """ Parse fuzzy dates like now, soon, later, someday """ self.assertEqual(Date("now"), Date.now()) self.assertEqual(Date("soon"), Date.soon()) self.assertEqual(Date("later"), Date.someday()) self.assertEqual(Date("someday"), Date.someday()) self.assertEqual(Date(""), Date.no_date())
def test_due(self): expected1 = { 'title': 'Do a thing', 'tags': set(), 'start': None, 'due': Date.parse('monday'), 'recurring': None } expected2 = { 'title': 'Do a thing', 'tags': set(), 'start': None, 'due': Date.parse('someday'), 'recurring': None } expected3 = { 'title': 'Do a thing', 'tags': set(), 'start': None, 'due': Date.parse('2099/02/12'), 'recurring': None } text1 = 'Do a thing due:monday' text2 = 'Do a thing due:monday' text3 = 'Do a thing due: monday' text4 = 'Do a thing due: someday' text5 = 'Do a thing due: 2099/02/12' self.assertEqual(expected1, parse(text1)) self.assertEqual(expected1, parse(text2)) self.assertEqual(expected1, parse(text3)) self.assertEqual(expected3, parse(text5))
def set_due_date(self, new_duedate): """Defines the task's due date.""" def __get_defined_parent_list(task): """Recursively fetch a list of parents that have a defined due date which is not fuzzy""" parent_list = [] for par_id in task.parents: par = self.req.get_task(par_id) if par.get_due_date().is_fuzzy(): parent_list += __get_defined_parent_list(par) else: parent_list.append(par) return parent_list def __get_defined_child_list(task): """Recursively fetch a list of children that have a defined due date which is not fuzzy""" child_list = [] for child_id in task.children: child = self.req.get_task(child_id) if child.get_due_date().is_fuzzy(): child_list += __get_defined_child_list(child) else: child_list.append(child) return child_list old_due_date = self.due_date new_duedate_obj = Date(new_duedate) # caching the conversion self.due_date = new_duedate_obj # If the new date is fuzzy or undefined, we don't update related tasks if not new_duedate_obj.is_fuzzy(): # if the task's start date happens later than the # new due date, we update it (except for fuzzy dates) if not self.get_start_date().is_fuzzy() and \ self.get_start_date() > new_duedate_obj: self.set_start_date(new_duedate) # if some ancestors' due dates happen before the task's new # due date, we update them (except for fuzzy dates) for par in __get_defined_parent_list(self): if par.get_due_date() < new_duedate_obj: par.set_due_date(new_duedate) # we must apply the constraints to the defined & non-fuzzy children # as well for sub in __get_defined_child_list(self): sub_duedate = sub.get_due_date() # if the child's due date happens later than the task's: we # update it to the task's new due date if sub_duedate > new_duedate_obj: sub.set_due_date(new_duedate) # if the child's start date happens later than # the task's new due date, we update it # (except for fuzzy start dates) sub_startdate = sub.get_start_date() if not sub_startdate.is_fuzzy() and \ sub_startdate > new_duedate_obj: sub.set_start_date(new_duedate) # If the date changed, we notify the change for the children since the # constraints might have changed if old_due_date != new_duedate_obj: self.recursive_sync()
def __init__(self, task_id, requester, newtask=False): super().__init__(task_id) # the id of this task in the project should be set # tid is a string ! (we have to choose a type and stick to it) assert(isinstance(task_id, str) or isinstance(task_id, str)) self.tid = str(task_id) self.set_uuid(uuid.uuid4()) self.remote_ids = {} self.content = "" self.title = _("My new task") # available status are: Active - Done - Dismiss - Note self.status = self.STA_ACTIVE self.recurring_term = None self.recurring = self.inherit_recursion() self.added_date = Date.no_date() if newtask: self.added_date = datetime.now() 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 = [] self.req = requester self.__main_treeview = requester.get_main_view() # If we don't have a newtask, we will have to load it. self.loaded = newtask # Should not be necessary with the new backends # if self.loaded: # self.req._task_loaded(self.tid) self.attributes = {} self._modified_update()
def set_start_date(self, fulldate): self.start_date = Date(fulldate) if not Date(fulldate).is_fuzzy() and \ not self.due_date.is_fuzzy() and \ Date(fulldate) > self.due_date: self.set_due_date(fulldate) self.sync()
def test_parse_fuzzy_dates_str(self): """ Print fuzzy dates in localized version """ self.assertEqual(Date("now").localized_str, _("now")) self.assertEqual(Date("soon").localized_str, _("soon")) self.assertEqual(Date("later").localized_str, _("someday")) self.assertEqual(Date("someday").localized_str, _("someday")) self.assertEqual(Date("").localized_str, "")
def test_parse_local_fuzzy_dates(self): """ Parse fuzzy dates in their localized version """ self.assertEqual(Date(_("now")), Date.now()) self.assertEqual(Date(_("soon")), Date.soon()) self.assertEqual(Date(_("later")), Date.someday()) self.assertEqual(Date(_("someday")), Date.someday()) self.assertEqual(Date(""), Date.no_date())
def parse(text: str) -> Dict: """Parse contents of the quick add input.""" result = { 'title': '', 'tags': set(), 'start': None, 'due': None, 'recurring': None } for match in re.finditer(TAG_REGEX, text): data = match.group(0) result['tags'].add(data[1:]) for match in re.finditer(TOKEN_REGEX, text): token = match.group(2) data = match.group(3) matched = False if token in TAGS_TOKEN: for tag in data.split(','): if tag: # Strip @ if tag.startswith('@'): tag = tag[1:] result['tags'].add(tag) matched = True elif token in START_TOKEN: try: result['start'] = Date.parse(data) matched = True except ValueError: pass elif token in DUE_TOKEN: try: result['due'] = Date.parse(data) matched = True except ValueError: pass elif token in REPEAT_TOKEN: try: Date.today().parse_from_date(data) result['recurring'] = data matched = True except ValueError: pass # Remove this part from the title if matched: text = text.replace(match[0], '') result['title'] = text return result
def get_gtg(self, task: Task, namespace: str = None): gtg_date = super().get_gtg(task, namespace) if isinstance(gtg_date, Date): if gtg_date.accuracy in {Accuracy.date, Accuracy.timezone, Accuracy.datetime}: return Date(self._normalize(gtg_date.dt_value)) return gtg_date return Date(self._normalize(gtg_date))
def sort_by_duedate(self, task1, task2, order): t1 = task1.get_urgent_date() t2 = task2.get_urgent_date() if t1 == Date.no_date(): t1 = task1.get_due_date_constraint() if t2 == Date.no_date(): t2 = task2.get_due_date_constraint() return self.__date_comp_continue(task1, task2, order, t1, t2)
def get_due_date(self): """ Gets the task due date """ due = self.rtm_task.due if due == "": return Date.no_date() date = self.__time_rtm_to_datetime(due).date() return Date(date)
def _get_dt_for_dav_writing(value): if isinstance(value, Date): if value.accuracy is Accuracy.timezone: return '', value.dt_value if value.accuracy is Accuracy.fuzzy: return str(value), value.dt_by_accuracy(Accuracy.timezone) else: value = Date(value) return '', value.dt_by_accuracy(Accuracy.timezone)
def test_toggle_dismiss_single(self): task = Task2(id=uuid4(), title='A Task') task.toggle_dismiss() self.assertEqual(task.status, Status.DISMISSED) self.assertEqual(task.date_closed, Date.today()) task.toggle_dismiss() self.assertEqual(task.status, Status.ACTIVE) self.assertEqual(task.date_closed, Date.no_date())
def on_date_cleared(self, widget, kind): """ Callback when a date is cleared through the popups. """ if kind == GTGCalendar.DATE_KIND_START: self.task.set_start_date(Date.no_date()) self.start_entry.set_text('') elif kind == GTGCalendar.DATE_KIND_DUE: self.task.set_due_date(Date.no_date()) self.due_entry.set_text('')
def test_toggle_active_single(self): task = Task2(id=uuid4(), title='A Task') self.assertEqual(task.status, Status.ACTIVE) task.toggle_active() self.assertEqual(task.status, Status.DONE) self.assertEqual(task.date_closed, Date.today()) task.toggle_active() self.assertEqual(task.status, Status.ACTIVE) self.assertEqual(task.date_closed, Date.no_date())
def set_complex_title(self, text, tags=[]): if tags: assert (isinstance(tags[0], str)) due_date = Date.no_date() defer_date = Date.no_date() if text: # Get tags in the title for match in extract_tags_from_text(text): tags.append(match) # Get attributes regexp = r'([\s]*)([\w-]+):\s*([^\s]+)' matches = re.findall(regexp, text, re.UNICODE) for spaces, attribute, args in matches: valid_attribute = True if attribute.lower() in ["tags", _("tags"), "tag", _("tag")]: for tag in args.split(","): if not tag.strip() == "@" and not tag.strip() == "": if not tag.startswith("@"): tag = "@" + tag tags.append(tag) elif attribute.lower() in [ "defer", _("defer"), "start", _("start") ]: try: defer_date = Date.parse(args) except ValueError: valid_attribute = False elif attribute.lower() == "due" or \ attribute.lower() == _("due"): try: due_date = Date.parse(args) except: valid_attribute = False else: # attribute is unknown valid_attribute = False if valid_attribute: # remove valid attribute from the task title text = \ text.replace(f"{spaces}{attribute}:{args}", "") for t in tags: self.add_tag(t) if text != "": self.set_title(text.strip()) self.set_to_keep() self.set_due_date(due_date) self.set_start_date(defer_date)
def parse(text: str) -> Dict: """Parse contents of the quick add input.""" result = { 'title': '', 'tags': set(), 'start': None, 'due': None, 'recurring': None } for match in re.finditer(TAG_REGEX, text): data = match.group(0) result['tags'].add(data[1:]) for match in re.finditer(TOKEN_REGEX, text): token = match.group(2) data = match.group(3) if token in TAGS_TOKEN: for t in data.split(','): # Strip @ if t.startswith('@'): t = t[1:] result['tags'].add(t) elif token in START_TOKEN: try: result['start'] = Date.parse(data) except ValueError: continue elif token in DUE_TOKEN: try: result['due'] = Date.parse(data) except ValueError: continue elif token in REPEAT_TOKEN: try: Date.today().parse_from_date(data) result['recurring'] = data except ValueError: continue # Remove this part from the title text = text[:match.start()] + text[match.end():] result['title'] = text return result
def __init__(self, id: UUID, title: str) -> None: self.id = id self.raw_title = title.strip('\t\n') self.content = '' self.tags = [] self.children = [] self.status = Status.ACTIVE self.parent = None self._date_added = Date.no_date() self._date_due = Date.no_date() self._date_start = Date.no_date() self._date_closed = Date.no_date() self._date_modified = Date(datetime.datetime.now())
def __day_selected(self, widget, date_type): if date_type == "RealDate": calendar_date = self.__calendar.get_date() date = self.__from_calendar_date_to_datetime(calendar_date) self.__date = Date(date) else: self.__date = Date(date_type) if self.__is_user_just_browsing_the_calendar: # this day-selected signal was caused by a month/year change self.__is_user_just_browsing_the_calendar = False else: # inform the Editor that the date has changed self.close_calendar() GLib.idle_add(self.emit, "date-changed")
def bgcolor(self, node, standard_color): color = self.get_node_bgcolor(node) def __get_active_child_list(node): """ This function recursively fetches a list of all the children of a task which are active (i.e - the subtasks which are not marked as 'Done' or 'Dismissed' """ child_list = [] for child_id in node.children: child = node.req.get_task(child_id) child_list += __get_active_child_list(child) if child.get_status() in [child.STA_ACTIVE]: child_list.append(child_id) return child_list child_list = __get_active_child_list(node) daysleft = None for child_id in child_list: child = self.req.get_task(child_id) if child.get_due_date() == Date.no_date(): continue daysleft_of_child = child.get_due_date().days_left() if daysleft is None: daysleft = daysleft_of_child color = self.get_node_bgcolor(child) elif daysleft_of_child < daysleft: daysleft = daysleft_of_child color = self.get_node_bgcolor(child) return color
def workview(self, task, parameters=None): wv = (self.active(task) and self.is_started(task) and self.is_workable(task) and self.no_disabled_tag(task) and task.get_due_date() != Date.someday()) return wv
def task_duedate_column(self, node): # We show the most constraining due date for task with no due dates. if node.get_due_date() == Date.no_date(): return node.get_due_date_constraint().to_readable_string() else: # Other tasks show their due date (which *can* be fuzzy) return node.get_due_date().to_readable_string()
def date_focus_out(self, widget, event, date_kind): try: datetoset = Date.parse(widget.get_text()) except ValueError: datetoset = None if datetoset is not None: # TODO: New Core t = self.app.ds.tasks.get(self.task.tid) if date_kind == GTGCalendar.DATE_KIND_START: self.task.set_start_date(datetoset) t.date_start = datetoset self.start_popover.popdown() elif date_kind == GTGCalendar.DATE_KIND_DUE: self.task.set_due_date(datetoset) t.date_due = datetoset self.due_popover.popdown() elif date_kind == GTGCalendar.DATE_KIND_CLOSED: self.task.set_closed_date(datetoset) t.date_closed = datetoset self.closed_popover.popdown() self.refresh_editor()
def __init__(self): super().__init__() self.__builder = Gtk.Builder() self.__builder.add_from_file(GnomeConfig.CALENDAR_UI_FILE) self.__date_kind = None self.__date = Date.no_date() self.__init_gtk__()
def get_dav(self, todo=None, vtodo=None) -> Date: """Transforming to local naive, if original value MAY be naive and IS assuming UTC""" value = super().get_dav(todo, vtodo) if todo: vtodo = todo.instance.vtodo todo_value = vtodo.contents.get(self.dav_name) if todo_value and todo_value[0].params.get(self.FUZZY_MARK): return Date(todo_value[0].params[self.FUZZY_MARK][0]) if isinstance(value, (date, datetime)): value = self._normalize(value) try: return Date(value) except ValueError: logger.error("Coudln't translate value %r", value) return Date.no_date()
def set_status(self, status, donedate=None): old_status = self.status self.can_be_deleted = False # No need to update children or whatever if the task is not loaded if status and self.is_loaded(): # we first modify the status of the children # If Done, we set the done date if status in [self.STA_DONE, self.STA_DISMISSED]: for c in self.get_subtasks(): if c.get_status() in [self.STA_ACTIVE]: c.set_status(status, donedate=donedate) # If the task is recurring, it must be duplicate with # another task id and the next occurence of the task # Furthermore, the duplicated task's parents # should be set the the parent's previous task # as well as the children's. # Because we want every subtask that is recurring # to occur another time after its parent has been set to done or dismiss. # only recurring tasks without any recurring parent can be duplicated. if self.recurring and not self.is_parent_recurring(): nexttask_tid = self.duplicate_recursively() if self.has_parent(): for p_tid in self.get_parents(): par = self.req.get_task(p_tid) if (par.is_loaded() and par.get_status() in (self.STA_ACTIVE)): par.add_child(nexttask_tid) # 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. # (old_status check is necessary to avoid false positive a start) elif status in [self.STA_ACTIVE] and\ old_status in [self.STA_DONE, self.STA_DISMISSED]: if self.has_parent(): for p_tid in self.get_parents(): par = self.req.get_task(p_tid) if par.is_loaded() and par.get_status() in\ [self.STA_DONE, self.STA_DISMISSED]: # we can either break the parent/child relationship # self.remove_parent(p_tid) # or restore the parent too par.set_status(self.STA_ACTIVE) # We dont mark the children as Active because # They might be already completed after all # then the task itself if status: self.status = status # Set closing date if status and status in [self.STA_DONE, self.STA_DISMISSED]: # to the specified date (if any) if donedate: self.closed_date = donedate # or to today else: self.closed_date = Date.today() self.sync()
def toggle_dismiss(self, propagate: bool = True) -> None: """Set this task to be dismissed.""" if self.status is Status.ACTIVE: self.status = Status.DISMISSED self.date_closed = Date.today() elif self.status is Status.DISMISSED: self.status = Status.ACTIVE self.date_closed = Date.no_date() if self.parent and self.parent.status is not Status.ACTIVE: self.parent.toggle_dismiss(propagate=False) if propagate: for child in self.children: child.toggle_dismiss()
def test_toggle_dismiss_children(self): task = Task2(id=uuid4(), title='A Task') task2 = Task2(id=uuid4(), title='A Child Task') task.children.append(task2) task2.parent = task task.toggle_dismiss() self.assertEqual(task.status, Status.DISMISSED) self.assertEqual(task.date_closed, Date.today()) self.assertEqual(task2.status, Status.DISMISSED) self.assertEqual(task2.date_closed, Date.today()) task.toggle_dismiss() self.assertEqual(task.status, Status.ACTIVE) self.assertEqual(task.date_closed, Date.no_date()) self.assertEqual(task2.status, Status.ACTIVE) self.assertEqual(task2.date_closed, Date.no_date())
def toggle_active(self, propagate: bool = True) -> None: """Toggle between possible statuses.""" if self.status is Status.ACTIVE: self.status = Status.DONE self.date_closed = Date.today() else: self.status = Status.ACTIVE self.date_closed = Date.no_date() if self.parent and self.parent.status is not Status.ACTIVE: self.parent.toggle_active(propagate=False) if propagate: for child in self.children: child.toggle_active()
def _due_within(task, danger_zone): """ Determine if a task is the danger zone. Convention: a danger zone of 1 day includes tasks due today. """ ddate = task.get_due_date() if (ddate != Date.no_date()): if ddate.days_left() < danger_zone: return True return False