コード例 #1
0
    def __squeeze_in(self, start_time):
        """ tries to put task in the given date
            if there are conflicts, we will only truncate the ongoing task
            and replace it's end part with our activity """

        # we are checking if our start time is in the middle of anything
        # or maybe there is something after us - so we know to adjust end time
        # in the latter case go only few hours ahead. everything else is madness, heh
        query = """
                   SELECT a.*, b.name
                     FROM facts a
                LEFT JOIN activities b on b.id = a.activity_id
                    WHERE ((start_time < ? and end_time > ?)
                           OR (start_time > ? and start_time < ? and end_time is null)
                           OR (start_time > ? and start_time < ?))
                 ORDER BY start_time
                    LIMIT 1
                """
        fact = self.fetchone(
            query,
            (start_time, start_time, start_time - dt.timedelta(hours=12),
             start_time, start_time, start_time + dt.timedelta(hours=12)))
        end_time = None
        if fact:
            if start_time > fact["start_time"]:
                #we are in middle of a fact - truncate it to our start
                self.execute("UPDATE facts SET end_time=? WHERE id=?",
                             (start_time, fact["id"]))

            else:  #otherwise we have found a task that is after us
                end_time = fact["start_time"]

        return end_time
コード例 #2
0
ファイル: stuff_test.py プロジェクト: thatoddmailbox/hamster
 def test_comparison(self):
     fact1 = Fact.parse("12:25-13:25 case@cat,, description #tag #bäg")
     fact2 = fact1.copy()
     self.assertEqual(fact1, fact2)
     fact2 = fact1.copy()
     fact2.activity = "abcd"
     self.assertNotEqual(fact1, fact2)
     fact2 = fact1.copy()
     fact2.category = "abcd"
     self.assertNotEqual(fact1, fact2)
     fact2 = fact1.copy()
     fact2.description = "abcd"
     self.assertNotEqual(fact1, fact2)
     fact2 = fact1.copy()
     fact2.range.start = fact1.range.start + dt.timedelta(minutes=1)
     self.assertNotEqual(fact1, fact2)
     fact2 = fact1.copy()
     fact2.range.end = fact1.range.end + dt.timedelta(minutes=1)
     self.assertNotEqual(fact1, fact2)
     # wrong order
     fact2 = fact1.copy()
     fact2.tags = ["bäg", "tag"]
     self.assertNotEqual(fact1, fact2)
     # correct order
     fact2 = fact1.copy()
     fact2.tags = ["tag", "bäg"]
     self.assertEqual(fact1, fact2)
コード例 #3
0
ファイル: timeinput.py プロジェクト: yannci/hamster
    def show_popup(self):
        if not self._parent_click_watcher:
            self._parent_click_watcher = self.get_toplevel().connect("button-press-event", self._on_focus_out_event)

        # we will be adding things, need datetime
        i_time_0 = dt.datetime.combine(self.start_date or dt.date.today(),
                                       self.start_time or dt.time())

        if self.start_time is None:
            # full 24 hours
            i_time = i_time_0
            interval = dt.timedelta(minutes = 15)
            end_time = i_time_0 + dt.timedelta(days = 1)
        else:
            # from start time to start time + 12 hours
            interval = dt.timedelta(minutes = 15)
            i_time = i_time_0 + interval
            end_time = i_time_0 + dt.timedelta(hours = 12)

        time = self.figure_time(self.get_text())
        focus_time = dt.datetime.combine(dt.date.today(), time) if time else None
        hours = gtk.ListStore(str)

        i, focus_row = 0, None
        while i_time < end_time:
            row_text = self._format_time(i_time)
            if self.start_time is not None:
                delta_text = (i_time - i_time_0).format()

                row_text += " (%s)" % delta_text

            hours.append([row_text])

            if focus_time and i_time <= focus_time < i_time + interval:
                focus_row = i

            i_time += interval

            i += 1

        self.time_tree.set_model(hours)

        #focus on row
        if focus_row != None:
            selection = self.time_tree.get_selection()
            selection.select_path(focus_row)
            self.time_tree.scroll_to_cell(focus_row, use_align = True, row_align = 0.4)

        #move popup under the widget
        alloc = self.get_allocation()
        w = alloc.width
        self.time_tree.set_size_request(w, alloc.height * 5)

        window = self.get_parent_window()
        dmmy, x, y= window.get_origin()

        self.popup.move(x + alloc.x,y + alloc.y + alloc.height)
        self.popup.resize(*self.time_tree.get_size_request())
        self.popup.show_all()
コード例 #4
0
ファイル: stuff_test.py プロジェクト: thatoddmailbox/hamster
 def test_format_timedelta(self):
     delta = dt.timedelta(minutes=10)
     self.assertEqual(delta.format("human"), "10min")
     delta = dt.timedelta(hours=5, minutes=0)
     self.assertEqual(delta.format("human"), "5h")
     delta = dt.timedelta(hours=5, minutes=10)
     self.assertEqual(delta.format("human"), "5h 10min")
     delta = dt.timedelta(hours=5, minutes=10)
     self.assertEqual(delta.format("HH:MM"), "05:10")
コード例 #5
0
ファイル: db.py プロジェクト: shivanimanchewar/hamster
    def __get_facts(self, date, end_date=None, search_terms=""):
        split_time = conf.day_start
        datetime_from = dt.datetime.combine(date, split_time)

        end_date = end_date or date
        datetime_to = dt.datetime.combine(end_date, split_time) + dt.timedelta(
            days=1, seconds=-1)

        logger.info("searching for facts from {} to {}".format(
            datetime_from, datetime_to))

        query = """
                   SELECT a.id AS id,
                          a.start_time AS start_time,
                          a.end_time AS end_time,
                          a.description as description,
                          b.name AS name, b.id as activity_id,
                          coalesce(c.name, ?) as category,
                          e.name as tag
                     FROM facts a
                LEFT JOIN activities b ON a.activity_id = b.id
                LEFT JOIN categories c ON b.category_id = c.id
                LEFT JOIN fact_tags d ON d.fact_id = a.id
                LEFT JOIN tags e ON e.id = d.tag_id
                    WHERE (a.end_time >= ? OR a.end_time IS NULL) AND a.start_time <= ?
        """

        if search_terms:
            # check if we need changes to the index
            self.__check_index(datetime_from, datetime_to)

            # flip the query around when it starts with "not "
            reverse_search_terms = search_terms.lower().startswith("not ")
            if reverse_search_terms:
                search_terms = search_terms[4:]

            search_terms = search_terms.replace('\\', '\\\\').replace(
                '%', '\\%').replace('_', '\\_').replace("'", "''")
            query += """ AND a.id %s IN (SELECT id
                                         FROM fact_index
                                         WHERE fact_index MATCH '%s')""" % (
                'NOT' if reverse_search_terms else '', search_terms)

        query += " ORDER BY a.start_time, e.name"

        fact_rows = self.fetchall(
            query, (self._unsorted_localized, datetime_from, datetime_to))
        #first let's put all tags in an array
        dbfacts = self.__group_tags(fact_rows)
        # ignore old on-going facts
        return [
            self._dbfact_to_libfact(dbfact) for dbfact in dbfacts
            if dbfact["start_time"] >= datetime_from - dt.timedelta(days=30)
        ]
コード例 #6
0
ファイル: stuff_test.py プロジェクト: thatoddmailbox/hamster
 def test_datetime_patterns(self):
     p = dt.datetime.pattern(1)
     s = "12:03"
     m = re.fullmatch(p, s, re.VERBOSE)
     time = dt.datetime._extract_datetime(m,
                                          d="date1",
                                          h="hour1",
                                          m="minute1",
                                          r="relative1",
                                          default_day=dt.hday.today())
     self.assertEqual(time.strftime("%H:%M"), "12:03")
     s = "2019-12-01 12:36"
     m = re.fullmatch(p, s, re.VERBOSE)
     time = dt.datetime._extract_datetime(m,
                                          d="date1",
                                          h="hour1",
                                          m="minute1",
                                          r="relative1")
     self.assertEqual(time.strftime("%Y-%m-%d %H:%M"), "2019-12-01 12:36")
     s = "-25"
     m = re.fullmatch(p, s, re.VERBOSE)
     relative = dt.datetime._extract_datetime(m,
                                              d="date1",
                                              h="hour1",
                                              m="minute1",
                                              r="relative1",
                                              default_day=dt.hday.today())
     self.assertEqual(relative, dt.timedelta(minutes=-25))
     s = "2019-12-05"
     m = re.search(p, s, re.VERBOSE)
     self.assertEqual(m, None)
コード例 #7
0
ファイル: overview.py プロジェクト: bochecha/hamster
    def _draw(self, context, opacity, matrix):
        g = graphics.Graphics(context)
        g.save_context()
        g.translate(self.x, self.y)
        # arbitrary 3/4 total width for label, 1/4 for histogram
        hist_width = self.alloc_w // 4;
        margin = 10  # pixels
        label_width = self.alloc_w - hist_width - margin
        self.layout.set_width(label_width * pango.SCALE)
        label_h = self.label_height
        bar_start_x = label_width + margin
        for i, (label, value) in enumerate(self.values):
            g.set_color(self.label_color)
            duration_str = value.format(fmt="HH:MM")
            markup_label = stuff.escape_pango(str(label))
            markup_duration = stuff.escape_pango(duration_str)
            self.layout.set_markup("{}, <i>{}</i>".format(markup_label, markup_duration))
            y = int(i * label_h * 1.5)
            g.move_to(0, y)
            pangocairo.show_layout(context, self.layout)
            if self._max > dt.timedelta(0):
                w = ceil(hist_width * value.total_seconds() /
                         self._max.total_seconds())
            else:
                w = 1
            g.rectangle(bar_start_x, y, int(w), int(label_h))
            g.fill(self.bar_color)

        g.restore_context()
コード例 #8
0
    def load_suggestions(self):
        self.todays_facts = self.storage.get_todays_facts()

        # list of facts of last month
        now = dt.datetime.now()
        last_month = self.storage.get_facts(now - dt.timedelta(days=30), now)

        # naive recency and frequency rank
        # score is as simple as you get 30-days_ago points for each occurence
        suggestions = defaultdict(int)
        for fact in last_month:
            days = 30 - (now - dt.datetime.combine(
                fact.date, dt.time())).total_seconds() / 60 / 60 / 24
            label = fact.activity
            if fact.category:
                label += "@%s" % fact.category

            suggestions[label] += days

            if fact.tags:
                label += " #%s" % (" #".join(fact.tags))
                suggestions[label] += days

        for rec in self.storage.get_activities():
            label = rec["name"]
            if rec["category"]:
                label += "@%s" % rec["category"]
            suggestions[label] += 0

        # list of (label, score), higher scores first
        self.suggestions = sorted(suggestions.items(),
                                  key=lambda x: x[1],
                                  reverse=True)
コード例 #9
0
ファイル: stuff_test.py プロジェクト: pradzins/hamster
 def test_range(self):
     day = dt.hday(2020, 2, 2)
     time = dt.time(21, 20)
     base = dt.datetime.from_day_time(day, time)
     range = dt.Range(base, base + dt.timedelta(minutes=30))
     range_str = range.format(default_day=day)
     self.assertEqual(range_str, "21:20 - 21:50")
     range = dt.Range(None, base)
     range_str = range.format(default_day=day)
     self.assertEqual(range_str, "-- - 21:20")
コード例 #10
0
ファイル: reports.py プロジェクト: yannci/hamster
    def _finish(self, facts):

        # group by date
        by_date = []
        for date, date_facts in itertools.groupby(facts,
                                                  lambda fact: fact.date):
            by_date.append((date, [fact.as_dict() for fact in date_facts]))
        by_date = dict(by_date)

        date_facts = []
        date = min(by_date.keys())
        while date <= self.end_date:
            str_date = date.strftime(
                # date column format for each row in HTML report
                # Using python datetime formatting syntax. See:
                # http://docs.python.org/library/time.html#time.strftime
                C_("html report", "%b %d, %Y"))
            date_facts.append([str_date, by_date.get(date, [])])
            date += dt.timedelta(days=1)

        data = dict(
            title=self.title,
            totals_by_day_title=_("Totals by Day"),
            activity_log_title=_("Activity Log"),
            totals_title=_("Totals"),
            activity_totals_heading=_("activities"),
            category_totals_heading=_("categories"),
            tag_totals_heading=_("tags"),
            show_prompt=_("Distinguish:"),
            header_date=_("Date"),
            header_activity=_("Activity"),
            header_category=_("Category"),
            header_tags=_("Tags"),
            header_start=_("Start"),
            header_end=_("End"),
            header_duration=_("Duration"),
            header_description=_("Description"),
            data_dir=runtime.data_dir,
            show_template=_("Show template"),
            template_instructions=
            _("You can override it by storing your version in %(home_folder)s")
            % {'home_folder': runtime.home_data_dir},
            start_date=timegm(self.start_date.timetuple()),
            end_date=timegm(self.end_date.timetuple()),
            facts=json_dumps([fact.as_dict() for fact in facts]),
            date_facts=json_dumps(date_facts),
            all_activities_rows="\n".join(self.fact_rows))

        for key, val in data.items():
            if isinstance(val, str):
                data[key] = val

        self.file.write(Template(self.main_template).safe_substitute(data))

        return
コード例 #11
0
ファイル: stuff_test.py プロジェクト: thatoddmailbox/hamster
    def test_type_stability(self):
        dt1 = dt.datetime(2020, 1, 10, hour=13, minute=30)
        dt2 = dt.datetime(2020, 1, 10, hour=13, minute=40)
        delta = dt2 - dt1
        self.assertEqual(type(delta), dt.timedelta)
        _sum = dt1 + delta
        self.assertEqual(_sum, dt.datetime(2020, 1, 10, hour=13, minute=40))
        self.assertEqual(type(_sum), dt.datetime)
        _sub = dt1 - delta
        self.assertEqual(_sub, dt.datetime(2020, 1, 10, hour=13, minute=20))
        self.assertEqual(type(_sub), dt.datetime)

        opposite = -delta
        self.assertEqual(opposite, dt.timedelta(minutes=-10))
        self.assertEqual(type(opposite), dt.timedelta)
        _sum = delta + delta
        self.assertEqual(_sum, dt.timedelta(minutes=20))
        self.assertEqual(type(_sum), dt.timedelta)
        _sub = delta - delta
        self.assertEqual(_sub, dt.timedelta())
        self.assertEqual(type(_sub), dt.timedelta)
コード例 #12
0
 def __touch_fact(self, fact, end_time=None):
     end_time = end_time or dt.datetime.now()
     # tasks under one minute do not count
     if end_time - fact.start_time < dt.timedelta(minutes=1):
         self.__remove_fact(fact.id)
     else:
         query = """
                    UPDATE facts
                       SET end_time = ?
                     WHERE id = ?
         """
         self.execute(query, (end_time, fact.id))
コード例 #13
0
    def check_fact(cls, fact, default_day=None):
        """Check Fact validity for inclusion in the storage.

        Raise FactError(message) on failure.
        """
        if fact.start_time is None:
            raise FactError("Missing start time")

        if fact.end_time and (fact.delta < dt.timedelta(0)):
            fixed_fact = Fact(start_time=fact.start_time,
                              end_time=fact.end_time + dt.timedelta(days=1))
            suggested_range_str = fixed_fact.range.format(default_day=default_day)
            # work around cyclic imports
            from hamster.lib.configuration import conf
            raise FactError(dedent(
                """\
                Duration would be negative.
                Working late ?
                This happens when the activity crosses the
                hamster day start time ({:%H:%M} from tracking settings).

                Suggestion: move the end to the next day; the range would become:
                {}
                (in civil local time)
                """.format(conf.day_start, suggested_range_str)
                ))

        if not fact.activity:
            raise FactError("Missing activity")

        if ',' in fact.category:
            raise FactError(dedent(
                """\
                Forbidden comma in category: '{}'
                Note: The description separator changed
                      from single comma to double comma ',,' (cf. PR #482).
                """.format(fact.category)
                ))
コード例 #14
0
    def set_facts(self, facts, scroll_to_top=False):
        # FactTree adds attributes to its facts. isolate these side effects
        # copy the id too; most of the checks are based on id here.
        self.facts = [fact.copy(id=fact.id) for fact in facts]
        del facts  # make sure facts is not used by inadvertance below.

        # If we get an entirely new set of facts, scroll back to the top
        if scroll_to_top:
            self.y = 0
        self.hover_fact = None
        if self.vadjustment:
            self.vadjustment.set_value(self.y)

        if self.facts:
            start = self.facts[0].date
            end = self.facts[-1].date
        else:
            start = end = dt.hday.today()

        by_date = defaultdict(list)
        delta_by_date = defaultdict(dt.timedelta)
        for fact in self.facts:
            by_date[fact.date].append(fact)
            delta_by_date[fact.date] += fact.delta

        # Add a TotalFact at the end of each day if we are
        # displaying more than one day.
        if len(by_date) > 1:
            for key in by_date:
                total_by_date = TotalFact(_("Total"), delta_by_date[key])
                by_date[key].append(total_by_date)

        days = []
        for i in range((end - start).days + 1):
            current_date = start + dt.timedelta(days=i)
            if current_date in by_date:
                days.append((current_date, by_date[current_date]))

        self.days = days

        self.set_row_heights()

        if (self.current_fact
                and self.current_fact.id in (fact.id for fact in self.facts)
                ):
            self.on_scroll()
        else:
            # will also trigger an on_scroll
            self.unset_current_fact()
コード例 #15
0
def duration_minutes(duration):
    """Returns minutes from duration, otherwise we keep bashing in same math.
    Deprecated, use dt.timedelta.total_minutes instead.
    """
    if isinstance(duration, dt.timedelta):
        return duration.total_seconds() / 60
    elif isinstance(duration, (int, float)):
        return duration
    elif isinstance(duration, list):
        res = dt.timedelta()
        for entry in duration:
            res += entry
        return duration_minutes(res)
    else:
        raise NotImplementedError("received {}".format(type(duration)))
コード例 #16
0
def locale_first_weekday():
    """figure if week starts on monday or sunday"""
    first_weekday = 6 #by default settle on monday

    try:
        process = os.popen("locale first_weekday week-1stday")
        week_offset, week_start = process.read().split('\n')[:2]
        process.close()
        week_start = dt.date(*time.strptime(week_start, "%Y%m%d")[:3])
        week_offset = dt.timedelta(int(week_offset) - 1)
        beginning = week_start + week_offset
        first_weekday = int(beginning.strftime("%w"))
    except:
        logger.warn("WARNING - Failed to get first weekday from locale")

    return first_weekday
コード例 #17
0
    def prev_range(self):
        start, end = self.start_date, self.end_date

        if self.current_range == "day":
            start, end = start - dt.timedelta(1), end - dt.timedelta(1)
        elif self.current_range == "week":
            start, end = start - dt.timedelta(7), end - dt.timedelta(7)
        elif self.current_range == "month":
            end = start - dt.timedelta(1)
            first_weekday, days_in_month = calendar.monthrange(end.year, end.month)
            start = end - dt.timedelta(days_in_month - 1)
        else:
            # manual range - just jump to the next window
            days =  (end - start) + dt.timedelta(days = 1)
            start = start - days
            end = end - days
        self.emit_range(self.current_range, start, end)
コード例 #18
0
 def test_range(self):
     day = dt.hday(2020, 2, 2)
     time = dt.time(21, 20)
     base = dt.datetime.from_day_time(day, time)
     range = dt.Range(base, base + dt.timedelta(minutes=30))
     range_str = range.format(default_day=day)
     self.assertEqual(range_str, "21:20 - 21:50")
     range = dt.Range(None, base)
     range_str = range.format(default_day=day)
     self.assertEqual(range_str, "-- - 21:20")
     # issue #576
     start = dt.datetime(2020, 3, 8, 17, 7)
     end = dt.datetime(2020, 3, 8, 18, 6)
     range = dt.Range.from_start_end(start, end)
     self.assertEqual(range.start, start)
     self.assertEqual(range.end, end)
     # check passthrough
     range2 = dt.Range.from_start_end(range)
     self.assertEqual(range2.start, range.start)
     self.assertEqual(range2.end, range.end)
コード例 #19
0
ファイル: overview.py プロジェクト: bochecha/hamster
    def __init__(self, **kwargs):
        graphics.Sprite.__init__(self, **kwargs)
        self.x_align = 0
        self.y_align = 0
        self.values = []

        self._label_context = cairo.Context(cairo.ImageSurface(cairo.FORMAT_A1, 0, 0))
        self.layout = pangocairo.create_layout(self._label_context)
        self.layout.set_font_description(pango.FontDescription(graphics._font_desc))
        self.layout.set_markup("Hamster") # dummy
        # ellipsize the middle because depending on the use case,
        # the distinctive information can be either at the beginning or the end.
        self.layout.set_ellipsize(pango.EllipsizeMode.MIDDLE)
        self.layout.set_justify(True)
        self.layout.set_alignment(pango.Alignment.RIGHT)
        self.label_height = self.layout.get_pixel_size()[1]
        # should be updated by the parent
        self.label_color = gdk.RGBA()
        self.bar_color = gdk.RGBA()

        self._max = dt.timedelta(0)
コード例 #20
0
ファイル: timeinput.py プロジェクト: yannci/hamster
    def time(self):
        """Displayed time.

         None,
         or time type,
         or datetime if start_time() was given a datetime.
         """
        time = self.figure_time(self.get_text())
        if self.start_date and time:
            # recombine (since self.start_time contains only the time part)
            start = dt.datetime.combine(self.start_date, self.start_time)
            new = dt.datetime.combine(self.start_date, time)
            if new < start:
                # a bit hackish, valid only because
                # duration can not be negative if start_time was given,
                # and we accept that it can not exceed 24h.
                # For longer durations,
                # date will have to be changed subsequently.
                return new + dt.timedelta(days=1)
            else:
                return new
        else:
            return time
コード例 #21
0
    def set_facts(self, facts):
        # FactTree adds attributes to its facts. isolate these side effects
        # copy the id too; most of the checks are based on id here.
        self.facts = [fact.copy(id=fact.id) for fact in facts]
        del facts  # make sure facts is not used by inadvertance below.

        self.y = 0
        self.hover_fact = None
        if self.vadjustment:
            self.vadjustment.set_value(0)

        if self.facts:
            start = self.facts[0].date
            end = self.facts[-1].date
        else:
            start = end = dt.hday.today()

        by_date = defaultdict(list)
        for fact in self.facts:
            by_date[fact.date].append(fact)

        days = []
        for i in range((end-start).days + 1):
            current_date = start + dt.timedelta(days=i)
            days.append((current_date, by_date[current_date]))

        self.days = days

        self.set_row_heights()

        if (self.current_fact
            and self.current_fact.id in (fact.id for fact in self.facts)
           ):
            self.on_scroll()
        else:
            # will also trigger an on_scroll
            self.unset_current_fact()
コード例 #22
0
ファイル: stuff_test.py プロジェクト: thatoddmailbox/hamster
    def test_parse_datetime_range(self):
        # only match clean
        s = "10.00@cat"
        (start, end), rest = dt.Range.parse(s, position="head")
        self.assertEqual(start, None)
        self.assertEqual(end, None)
        s = "12:02"
        (start, end), rest = dt.Range.parse(s)
        self.assertEqual(start.strftime("%H:%M"), "12:02")
        self.assertEqual(end, None)
        s = "12:03 13:04"
        (start, end), rest = dt.Range.parse(s)
        self.assertEqual(start.strftime("%H:%M"), "12:03")
        self.assertEqual(end.strftime("%H:%M"), "13:04")
        s = "12:35 activity"
        (start, end), rest = dt.Range.parse(s, position="head")
        self.assertEqual(start.strftime("%H:%M"), "12:35")
        self.assertEqual(end, None)
        s = "2019-12-01 12:33 activity"
        (start, end), rest = dt.Range.parse(s, position="head")
        self.assertEqual(start.strftime("%Y-%m-%d %H:%M"), "2019-12-01 12:33")
        self.assertEqual(end, None)

        ref = dt.datetime(2019, 11, 29, 13, 55)  # 2019-11-29 13:55

        s = "-25 activity"
        (start, end), rest = dt.Range.parse(s, position="head", ref=ref)
        self.assertEqual(start.strftime("%Y-%m-%d %H:%M"), "2019-11-29 13:30")
        self.assertEqual(end, None)
        s = "+25 activity"
        (start, end), rest = dt.Range.parse(s, position="head", ref=ref)
        self.assertEqual(start.strftime("%Y-%m-%d %H:%M"), "2019-11-29 14:20")
        self.assertEqual(end, None)
        s = "-55 -25 activity"
        (start, end), rest = dt.Range.parse(s, position="head", ref=ref)
        self.assertEqual(start.strftime("%Y-%m-%d %H:%M"), "2019-11-29 13:00")
        self.assertEqual(end.strftime("%Y-%m-%d %H:%M"), "2019-11-29 13:30")
        s = "+25 +55 activity"
        (start, end), rest = dt.Range.parse(s, position="head", ref=ref)
        self.assertEqual(start.strftime("%Y-%m-%d %H:%M"), "2019-11-29 14:20")
        self.assertEqual(end.strftime("%Y-%m-%d %H:%M"), "2019-11-29 14:50")
        s = "-55 -120 activity"
        (start, end), rest = dt.Range.parse(s, position="head", ref=ref)
        self.assertEqual(start.strftime("%Y-%m-%d %H:%M"), "2019-11-29 13:00")
        self.assertEqual(end.strftime("%Y-%m-%d %H:%M"), "2019-11-29 11:55")
        s = "-50 20 activity"
        (start, end), rest = dt.Range.parse(s, position="head", ref=ref)
        self.assertEqual(start.strftime("%Y-%m-%d %H:%M"), "2019-11-29 13:05")
        self.assertEqual(end.strftime("%Y-%m-%d %H:%M"), "2019-11-29 13:25")

        s = "2019-12-05"  # single hamster day
        (start, end), rest = dt.Range.parse(s, ref=ref)
        just_before = start - dt.timedelta(seconds=1)
        just_after = end + dt.timedelta(seconds=1)
        self.assertEqual(just_before.hday(), pdt.date(2019, 12, 4))
        self.assertEqual(just_after.hday(), pdt.date(2019, 12, 6))
        s = "2019-12-05 2019-12-07"  # hamster days range
        (start, end), rest = dt.Range.parse(s, ref=ref)
        just_before = start - dt.timedelta(seconds=1)
        just_after = end + dt.timedelta(seconds=1)
        self.assertEqual(just_before.hday(), dt.date(2019, 12, 4))
        self.assertEqual(just_after.hday(), dt.date(2019, 12, 8))

        s = "14:30 - --"
        (start, end), rest = dt.Range.parse(s, ref=ref)
        self.assertEqual(start.strftime("%H:%M"), "14:30")
        self.assertEqual(end, None)
コード例 #23
0
ファイル: stuff_test.py プロジェクト: pradzins/hamster
 def test_timedelta(self):
     delta = dt.timedelta(seconds=90)
     self.assertEqual(delta.total_minutes(), 1.5)
コード例 #24
0
    def __add_fact(self, fact, temporary=False):
        """Add fact to database.

        Args:
            fact (Fact)
        Returns:
            int, the new fact id in the database (> 0) on success,
                 0 if nothing needed to be done
                 (e.g. if the same fact was already on-going),
                 note: a sanity check on the given fact is performed first,
                       that would raise an AssertionError.
                       Other errors would also be handled through exceptions.
        """
        logger.info("adding fact {}".format(fact))

        start_time = fact.start_time
        end_time = fact.end_time

        # get tags from database - this will create any missing tags too
        tags = [(tag['id'], tag['name'], tag['autocomplete'])
                for tag in self.get_tag_ids(fact.tags)]

        # now check if maybe there is also a category
        category_id = self.__get_category_id(fact.category)
        if not category_id:
            category_id = self.__add_category(fact.category)

        # try to find activity, resurrect if not temporary
        activity_id = self.__get_activity_by_name(fact.activity,
                                                  category_id,
                                                  resurrect=not temporary)
        if not activity_id:
            activity_id = self.__add_activity(fact.activity, category_id,
                                              temporary)
        else:
            activity_id = activity_id['id']

        # if we are working on +/- current day - check the last_activity
        if (dt.timedelta(days=-1) <= dt.datetime.now() - start_time <=
                dt.timedelta(days=1)):
            # pull in previous facts
            facts = self.__get_todays_facts()

            previous = None
            if facts and facts[-1].end_time is None:
                previous = facts[-1]

            if previous and previous.start_time <= start_time:
                # check if maybe that is the same one, in that case no need to restart
                if (previous.activity_id == activity_id
                        and set(previous.tags) == set([tag[1]
                                                       for tag in tags]) and
                    (previous.description or "") == (fact.description or "")):
                    logger.info("same fact, already on-going, nothing to do")
                    return 0

                # if no description is added
                # see if maybe previous was too short to qualify as an activity
                if (not previous.description and 60 >=
                    (start_time - previous.start_time).seconds >= 0):
                    self.__remove_fact(previous.id)

                    # now that we removed the previous one, see if maybe the one
                    # before that is actually same as the one we want to start
                    # (glueing)
                    if len(facts) > 1 and 60 >= (
                            start_time - facts[-2].end_time).seconds >= 0:
                        before = facts[-2]
                        if (before.activity_id == activity_id and set(
                                before.tags) == set([tag[1] for tag in tags])):
                            # resume and return
                            update = """
                                       UPDATE facts
                                          SET end_time = null
                                        WHERE id = ?
                            """
                            self.execute(update, (before.id, ))

                            return before.id
                else:
                    # otherwise stop
                    update = """
                               UPDATE facts
                                  SET end_time = ?
                                WHERE id = ?
                    """
                    self.execute(update, (start_time, previous.id))

        # done with the current activity, now we can solve overlaps
        if not end_time:
            end_time = self.__squeeze_in(start_time)
        else:
            self.__solve_overlaps(start_time, end_time)

        # finally add the new entry
        insert = """
                    INSERT INTO facts (activity_id, start_time, end_time, description)
                               VALUES (?, ?, ?, ?)
        """
        self.execute(insert,
                     (activity_id, start_time, end_time, fact.description))

        fact_id = self.__last_insert_rowid()

        #now link tags
        insert = ["insert into fact_tags(fact_id, tag_id) values(?, ?)"
                  ] * len(tags)
        params = [(fact_id, tag[0]) for tag in tags]
        self.execute(insert, params)

        self.__remove_index([fact_id])
        logger.info("fact successfully added, with id #{}".format(fact_id))
        return fact_id
コード例 #25
0
ファイル: overview.py プロジェクト: bochecha/hamster
 def set_values(self, values):
     """expects a list of 2-tuples"""
     self.values = values
     self.height = len(self.values) * 14
     self._max = max(rec[1] for rec in values) if values else dt.timedelta(0)
コード例 #26
0
 def increment_date(self, days):
     delta = dt.timedelta(days=days)
     self.date += delta
     self.update_fields()
コード例 #27
0
    def _list(self, start_time, end_time, search=""):
        """Print a listing of activities"""
        facts = self.storage.get_facts(start_time, end_time, search)

        headers = {
            'activity': _("Activity"),
            'category': _("Category"),
            'tags': _("Tags"),
            'description': _("Description"),
            'start': _("Start"),
            'end': _("End"),
            'duration': _("Duration")
        }

        # print date if it is not the same day
        print_with_date = start_time.date() != end_time.date()

        cols = 'start', 'end', 'duration', 'activity', 'category'

        widths = dict([(col, len(headers[col])) for col in cols])
        for fact in facts:
            fact = fact_dict(fact, print_with_date)
            for col in cols:
                widths[col] = max(widths[col], len(fact[col]))

        cols = [
            "{{{col}: <{len}}}".format(col=col, len=widths[col])
            for col in cols
        ]
        fact_line = " | ".join(cols)

        row_width = sum(val + 3 for val in list(widths.values()))

        print()
        print(fact_line.format(**headers))
        print("-" * min(row_width, 80))

        by_cat = {}
        for fact in facts:
            cat = fact.category or _("Unsorted")
            by_cat.setdefault(cat, dt.timedelta(0))
            by_cat[cat] += fact.delta

            pretty_fact = fact_dict(fact, print_with_date)
            print(fact_line.format(**pretty_fact))

            if pretty_fact['description']:
                for line in word_wrap(pretty_fact['description'], 76):
                    print("    {}".format(line))

            if pretty_fact['tags']:
                for line in word_wrap(pretty_fact['tags'], 76):
                    print("    {}".format(line))

        print("-" * min(row_width, 80))

        cats = []
        total_duration = dt.timedelta()
        for cat, duration in sorted(by_cat.items(),
                                    key=lambda x: x[1],
                                    reverse=True):
            cats.append("{}: {}".format(cat, duration.format()))
            total_duration += duration

        for line in word_wrap(", ".join(cats), 80):
            print(line)
        print("Total: ", total_duration.format())

        print()
コード例 #28
0
    def on_enter_frame(self, scene, context):
        g = graphics.Graphics(context)

        self.plot_area.height = self.height - 30


        vertical = min(self.plot_area.height / 5, 7)
        minute_pixel = (self.scope_hours * 60.0 - 15) / self.width

        g.set_line_style(width=1)
        g.translate(0.5, 0.5)


        colors = {
            "normal": self._style.get_color(gtk.StateFlags.NORMAL),
            "normal_bg": self._style.get_background_color(gtk.StateFlags.NORMAL),
            "selected": self._style.get_color(gtk.StateFlags.SELECTED),
            "selected_bg": self._style.get_background_color(gtk.StateFlags.SELECTED),
        }

        bottom = self.plot_area.y + self.plot_area.height

        for bar in self.fact_bars:
            bar.y = vertical * bar.category + 5
            bar.height = vertical

            bar_start_time = bar.fact.start_time - self.view_time
            minutes = bar_start_time.seconds / 60 + bar_start_time.days * self.scope_hours  * 60

            bar.x = round(minutes / minute_pixel) + 0.5
            bar.width = round((bar.fact.delta).seconds / 60 / minute_pixel)


        if self.chosen_selection.start_time and self.chosen_selection.width is None:
            # we have time but no pixels
            minutes = round((self.chosen_selection.start_time - self.view_time).seconds / 60 / minute_pixel) + 0.5
            self.chosen_selection.x = minutes
            if self.chosen_selection.end_time:
                self.chosen_selection.width = round((self.chosen_selection.end_time - self.chosen_selection.start_time).seconds / 60 / minute_pixel)
            else:
                self.chosen_selection.width = 0
            self.chosen_selection.height = self.chosen_selection.parent.height

            # use the oportunity to set proper colors too
            self.chosen_selection.fill = colors['selected_bg']
            self.chosen_selection.duration_label.color = colors['selected']




        #time scale
        g.set_color("#000")

        background = colors["normal_bg"]
        text = colors["normal"]

        tick_color = g.colors.contrast(background, 80)

        layout = g.create_layout(size = 10)
        for i in range(self.scope_hours * 60):
            time = (self.view_time + dt.timedelta(minutes=i))

            g.set_color(tick_color)
            if time.minute == 0:
                g.move_to(round(i / minute_pixel), bottom - 15)
                g.line_to(round(i / minute_pixel), bottom)
                g.stroke()
            elif time.minute % 15 == 0:
                g.move_to(round(i / minute_pixel), bottom - 5)
                g.line_to(round(i / minute_pixel), bottom)
                g.stroke()



            if time.minute == 0 and time.hour % 4 == 0:
                if time.hour == 0:
                    g.move_to(round(i / minute_pixel), self.plot_area.y)
                    g.line_to(round(i / minute_pixel), bottom)
                    label_minutes = time.strftime("%b %d")
                else:
                    label_minutes = time.strftime("%H<small><sup>%M</sup></small>")

                g.set_color(text)
                layout.set_markup(label_minutes)

                g.move_to(round(i / minute_pixel) + 2, 0)
                pangocairo.show_layout(context, layout)

        #current time
        if self.view_time < dt.datetime.now() < self.view_time + dt.timedelta(hours = self.scope_hours):
            minutes = round((dt.datetime.now() - self.view_time).seconds / 60 / minute_pixel)
            g.rectangle(minutes, 0, self.width, self.height)
            g.fill(colors['normal_bg'], 0.7)

            g.move_to(minutes, self.plot_area.y)
            g.line_to(minutes, bottom)
            g.stroke("#f00", 0.4)
コード例 #29
0
def week(view_date):
    # aligns start and end date to week
    start_date = view_date - dt.timedelta(view_date.weekday() + 1)
    start_date = start_date + dt.timedelta(locale_first_weekday())
    end_date = start_date + dt.timedelta(6)
    return start_date, end_date
コード例 #30
0
def month(view_date):
    # aligns start and end date to month
    start_date = view_date - dt.timedelta(view_date.day - 1) #set to beginning of month
    first_weekday, days_in_month = calendar.monthrange(view_date.year, view_date.month)
    end_date = start_date + dt.timedelta(days_in_month - 1)
    return start_date, end_date