Esempio n. 1
0
File: rep_tl.py Progetto: karibou/tl
def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('-l',
                        '--logfile',
                        nargs=1,
                        metavar='LOGFILE',
                        help='Path to the gtimelog logfile to be use')
    args = parser.parse_args()

    if args.logfile is not None:
        LogFile = set_logfile(args.logfile)
    else:
        LogFile = set_logfile()

    (week_first, week_last) = get_time()
    Log = TimeLog(LogFile, virtual_midnight)
    log_entries = Log.window_for(week_first, week_last)
    total_work, _ = log_entries.totals()
    _, totals = log_entries.categorized_work_entries()

    ordered_by_time = [(time, cat) for cat, time in totals.items()]
    ordered_by_time.sort(reverse=True)
    max_cat_length = max([len(cat) for cat in totals.keys()])
    line_format = '  %-' + str(max_cat_length + 4) + 's %+5s\t %.0f%%'
    print("\nTotal work done so far : %s\n" %
          format_duration_short(total_work))
    print('Categories by time spent:')
    for time, cat in ordered_by_time:
        print(line_format %
              (cat, format_duration_short(time), time / total_work * 100))
Esempio n. 2
0
 def test_append_adds_blank_line_on_new_day(self):
     timelog = TimeLog(self.tempfile(), datetime.time(2, 0))
     timelog.append('working on sth', now=datetime.datetime(2014, 11, 12, 18, 0))
     timelog.append('new day **', now=datetime.datetime(2014, 11, 13, 8, 0))
     with open(timelog.filename, 'r') as f:
         self.assertEqual(f.readlines(),
                          ['2014-11-12 18:00: working on sth\n',
                           '\n',
                           '2014-11-13 08:00: new day **\n'])
Esempio n. 3
0
    def test_appending_clears_window_cache(self):
        # Regression test for https://github.com/gtimelog/gtimelog/issues/28
        timelog = TimeLog(self.tempfile(), datetime.time(2, 0))

        w = timelog.window_for_day(datetime.date(2014, 11, 12))
        self.assertEqual(list(w.all_entries()), [])

        timelog.append('started **', now=datetime.datetime(2014, 11, 12, 10, 00))
        w = timelog.window_for_day(datetime.date(2014, 11, 12))
        self.assertEqual(len(list(w.all_entries())), 1)
Esempio n. 4
0
 def __init__(self, config):
     self.settings = Settings()
     self.timelog = TimeLog(self.settings.get_timelog_file(),
                            self.settings.virtual_midnight)
     self.aliases = config.get('aliases', {})
     self.line_format = config.get('line_format', '')
     if self.line_format == 'categorized':
         self.line_format_str = "category: task description | comment"
         self.delimiter = " "
     else:
         self.line_format_str = "task: description | comment"
         self.delimiter = ":"
Esempio n. 5
0
 def window(self, min_dt, max_dt):
     tl = TimeLog(self.filename, self.virtual_midnight)
     return TimeWindow(
         tl,
         min_dt,
         max_dt,
     )
Esempio n. 6
0
File: tl_sum.py Progetto: karibou/tl
def task_summary(category, task=None):

    total_delta = datetime.timedelta()
    today = datetime.datetime.today()
    epoch = datetime.datetime(1970, 10, 1)
    Log = TimeLog(LogFile, virtual_midnight)
    log_entries = Log.window_for(epoch, today)
    entries, _ = log_entries.categorized_work_entries()

    for (_, entry, entry_time) in entries[Categories[category] + ' ']:
        if entry.lstrip() == task:
            total_delta += entry_time
    print("total time spent on %s : %d.%d" % (
        task, int(total_delta.seconds / 3600) + int(total_delta.days * 24),
        int(total_delta.seconds / 60 % 60)))
    return 0
Esempio n. 7
0
class GtimelogParser(object):
    def __init__(self, config):
        self.settings = Settings()
        self.timelog = TimeLog(self.settings.get_timelog_file(),
                               self.settings.virtual_midnight)
        self.aliases = config.get('aliases', {})

    def skip_entry(self, entry):
        if '**' in entry:
            return True
        if entry.strip() in ('arrive', 'arrived', 'start'):
            return True
        return False

    def get_entries(self, date_window):
        window = self.timelog.window_for(date_window.start, date_window.stop)

        worklogs = []
        attendances = []
        for start, stop, duration, tags, entry in window.all_entries():
            if self.skip_entry(entry):
                continue
            if attendances and attendances[-1][1] == start:
                attendances[-1] = (attendances[-1][0], stop)
            else:
                attendances += [(start, stop)]
            try:
                issue, description = [
                    x.strip() for x in entry.split(':', 1) if x.strip()
                ]
            except ValueError:
                print(
                    'Entry must be in the format `task: description`. '
                    'Got ', entry)
                continue

            # no matter what we find as `issue`:
            # if we have an alias override it takes precedence
            if issue in self.aliases:
                issue = self.aliases[issue]
            worklogs.append(
                MultiLog(None, issue, int(duration.total_seconds()),
                         start.date(), description))

        # Dangling attendance for today
        if attendances and attendances[-1][1].date() == date.today():
            attendances[-1] = (attendances[-1][0], None)

        return attendances, worklogs
Esempio n. 8
0
 def test_reloading(self):
     logfile = self.tempfile()
     timelog = TimeLog(logfile, datetime.time(2, 0))
     # No file - nothing to reload
     self.assertFalse(timelog.check_reload())
     # Create a file - it should be reloaded, once.
     open(logfile, 'w').close()
     self.assertTrue(timelog.check_reload())
     self.assertFalse(timelog.check_reload())
     # Change the timestamp, somehow
     st = os.stat(logfile)
     os.utime(logfile, (st.st_atime, st.st_mtime + 1))
     self.assertTrue(timelog.check_reload())
     self.assertFalse(timelog.check_reload())
     # Disappearance of the file is noticed
     os.unlink(logfile)
     self.assertTrue(timelog.check_reload())
     self.assertFalse(timelog.check_reload())
Esempio n. 9
0
    def test_appending_clears_window_cache(self):
        # Regression test for https://github.com/gtimelog/gtimelog/issues/28
        tempfile = os.path.join(self.mkdtemp(), 'timelog.txt')
        timelog = TimeLog(tempfile, datetime.time(2, 0))

        w = timelog.window_for_day(datetime.date(2014, 11, 12))
        self.assertEqual(list(w.all_entries()), [])

        timelog.append('started **', now=datetime.datetime(2014, 11, 12, 10, 00))
        w = timelog.window_for_day(datetime.date(2014, 11, 12))
        self.assertEqual(len(list(w.all_entries())), 1)
Esempio n. 10
0
 def test_parse_correction_leaves_regular_text_alone(self):
     timelog = TimeLog(StringIO(), datetime.time(2, 0))
     self.assertEqual(timelog.parse_correction("did stuff"),
                      ("did stuff", None))
Esempio n. 11
0
 def test_parse_correction_handles_virtual_midnight_yesterdays_time(self):
     # Regression test for https://github.com/gtimelog/gtimelog/issues/33
     timelog = TimeLog(StringIO(), datetime.time(2, 0))
     self.assertEqual(timelog.parse_correction("15:20 did stuff"),
                      ("did stuff", datetime.datetime(2015, 5, 12, 15, 20)))
Esempio n. 12
0
 def test_valid_time_rejects_times_before_last_entry(self):
     timelog = TimeLog(StringIO("2015-05-12 15:00: did stuff"),
                       datetime.time(2, 0))
     past = datetime.datetime(2015, 5, 12, 14, 20)
     self.assertFalse(timelog.valid_time(past))
Esempio n. 13
0
 def test_append_rounds_the_time(self):
     timelog = TimeLog(self.tempfile(), datetime.time(2, 0))
     timelog.append('now')
     self.assertEqual(timelog.items[-1][0], datetime.datetime(2015, 5, 12, 16, 27))
Esempio n. 14
0
 def test_parse_correction_ignores_bad_relative_times(self):
     timelog = TimeLog(StringIO(), datetime.time(2, 0))
     self.assertEqual(timelog.parse_correction("-200 did stuff"),
                      ("-200 did stuff", None))
Esempio n. 15
0
 def test_valid_time_accepts_any_time_in_the_past_when_log_is_empty(self):
     timelog = TimeLog(StringIO(), datetime.time(2, 0))
     past = datetime.datetime(2015, 5, 12, 14, 20)
     self.assertTrue(timelog.valid_time(past))
Esempio n. 16
0
def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('-l',
                        '--logfile',
                        nargs=1,
                        metavar='LOGFILE',
                        help='Path to the gtimelog logfile to be use')
    parser.add_argument('-u',
                        '--user',
                        nargs=1,
                        metavar='USER',
                        help='User Identification to be used for report')
    parser.add_argument('-n',
                        '--no-time',
                        help='Print weekly report without spent time',
                        action='store_true')
    parser.add_argument('-m',
                        '--minutes',
                        help='Print weekly report with spent time in minutes',
                        action='store_true')
    parser.add_argument('-d',
                        '--days',
                        help='Print weekly report with spent time in days',
                        action='store_true')
    parser.add_argument('-b',
                        '--back',
                        help='Print weekly report back # of weeks',
                        nargs=1)
    args = parser.parse_args()

    if args.logfile is not None:
        LogFile = set_logfile(args.logfile)
    else:
        LogFile = set_logfile()

    if args.user is not None:
        UserId = set_userid(args.user)
    else:
        UserId = set_userid()

    (week_first, week_last) = get_time(args.back)
    Log = TimeLog(LogFile, virtual_midnight)
    log_entries = Log.window_for(week_first, week_last)
    total_work, _ = log_entries.totals()
    entries, totals = log_entries.categorized_work_entries()

    print("[ACTIVITY] %s to %s (%s)" %
          (week_first.isoformat().split("T")[0],
           week_last.isoformat().split("T")[0], UserId))
    if entries:
        if None in entries:
            categories = sorted(entries)
            categories.append('No category')
            entries['No category'] = e
            t = totals.pop(None)
            totals['No category'] = t
        else:
            categories = sorted(entries)
        for cat in categories:
            print('%s:' % cat)

            work = [(entry, duration)
                    for start, entry, duration in entries[cat]]
            work.sort()
            for entry, duration in work:
                if not duration:
                    continue  # skip empty "arrival" entries

                entry = entry[:1].upper() + entry[1:]
                if args.no_time:
                    print(u"  %-61s  " % entry)
                elif args.minutes:
                    print(u"  %-85s  %+5s %+4s" %
                          (entry, format_duration_short(duration),
                           as_minutes(duration)))
                elif args.days:
                    print(u"  %-85s  %+5s %.1f" %
                          (entry, format_duration_short(duration),
                           as_minutes(duration) / 480))
                else:
                    print(u"  %-85s  %+5s" %
                          (entry, format_duration_short(duration)))

            if args.no_time:
                print("")
            else:
                if args.minutes:
                    print('-' * 100)
                    print(u"%+94s %4s" % (format_duration_short(
                        totals[cat]), as_minutes(totals[cat])))
                elif args.days:
                    print('-' * 98)
                    print(u"%+94s %.1f" % (format_duration_short(
                        totals[cat]), as_minutes(totals[cat]) / 480))
                else:
                    print('-' * 94)
                    print(u"%+94s" % (format_duration_short(totals[cat])))

        print("Total work done : %s" % format_duration_short(total_work))
Esempio n. 17
0
 def test_parse_correction_recognizes_relative_times(self):
     timelog = TimeLog(StringIO(), datetime.time(2, 0))
     self.assertEqual(timelog.parse_correction("-20 did stuff"),
                      ("did stuff", datetime.datetime(2015, 5, 12, 16, 7)))
Esempio n. 18
0
    def do_activate(self):
        if self.main_window is not None:
            self.main_window.main_window.present()
            return

        log.addHandler(logging.StreamHandler(sys.stdout))
        if self.opts.debug:
            log.setLevel(logging.DEBUG)
        else:
            log.setLevel(logging.INFO)

        if self.opts.sample_config:
            settings = Settings()
            settings.save("gtimelogrc.sample")
            print("Sample configuration file written to gtimelogrc.sample")
            print("Edit it and save as %s" % settings.get_config_file())
            return

        if self.opts.debug:
            print('GTimeLog version: %s' % gtimelog.__version__)
            print('Gtk+ version: %s.%s.%s' % (Gtk.MAJOR_VERSION, Gtk.MINOR_VERSION, Gtk.MICRO_VERSION))
            print('Config directory: %s' % Settings().get_config_dir())
            print('Data directory: %s' % Settings().get_data_dir())

        settings = Settings()
        configdir = settings.get_config_dir()
        datadir = settings.get_data_dir()
        try:
            # Create configdir if it doesn't exist.
            os.makedirs(configdir)
        except OSError as error:
            if error.errno != errno.EEXIST:
                # XXX: not the most friendly way of error reporting for a GUI app
                raise
        try:
            # Create datadir if it doesn't exist.
            os.makedirs(datadir)
        except OSError as error:
            if error.errno != errno.EEXIST:
                raise

        settings_file = settings.get_config_file()
        if not os.path.exists(settings_file):
            if self.opts.debug:
                print('Saving settings to %s' % settings_file)
            settings.save(settings_file)
        else:
            if self.opts.debug:
                print('Loading settings from %s' % settings_file)
            settings.load(settings_file)
        if self.opts.debug:
            print('Assuming date changes at %s' % settings.virtual_midnight)
            print('Loading time log from %s' % settings.get_timelog_file())
        timelog = TimeLog(settings.get_timelog_file(),
                          settings.virtual_midnight)
        if settings.task_list_url:
            if self.opts.debug:
                print('Loading cached remote tasks from %s' %
                      os.path.join(datadir, 'remote-tasks.txt'))
            tasks = RemoteTaskList(settings.task_list_url,
                                   os.path.join(datadir, 'remote-tasks.txt'))
        else:
            if self.opts.debug:
                print('Loading tasks from %s' % os.path.join(datadir, 'tasks.txt'))
            tasks = TaskList(os.path.join(datadir, 'tasks.txt'))
        self.main_window = MainWindow(timelog, settings, tasks)
        self.add_window(self.main_window.main_window)
        start_in_tray = False

        if settings.show_tray_icon:
            if self.opts.debug:
                print('Tray icon preference: %s' % ('AppIndicator'
                                                    if settings.prefer_app_indicator
                                                    else 'SimpleStatusIcon'))

            if settings.prefer_app_indicator and have_app_indicator:
                tray_icon = AppIndicator(self.main_window)
            else:
                tray_icon = SimpleStatusIcon(self.main_window)

            if tray_icon:
                if self.opts.debug:
                    print('Using: %s' % tray_icon.__class__.__name__)

                start_in_tray = (settings.start_in_tray
                                 if settings.start_in_tray
                                 else self.opts.tray)

        if not start_in_tray:
            self.main_window.on_show_activate()
        else:
            if self.opts.debug:
                print('Starting minimized')

        # This is needed to make ^C terminate gtimelog when we're using
        # gobject-introspection.
        signal.signal(signal.SIGINT, signal.SIG_DFL)
Esempio n. 19
0
def full():
    return TimeLog(Settings().get_timelog_file(),
                   Settings().virtual_midnight).items
Esempio n. 20
0
 def test_window_for_month(self):
     timelog = TimeLog(StringIO(), datetime.time(2, 0))
     for d in range(1, 31):
         window = timelog.window_for_month(datetime.date(2015, 9, d))
         self.assertEqual(window.min_timestamp, datetime.datetime(2015, 9, 1, 2, 0))
         self.assertEqual(window.max_timestamp, datetime.datetime(2015, 10, 1, 2, 0))
Esempio n. 21
0
 def test_window_for_date_range(self):
     timelog = TimeLog(StringIO(), datetime.time(2, 0))
     window = timelog.window_for_date_range(datetime.date(2015, 9, 3),
                                            datetime.date(2015, 9, 24))
     self.assertEqual(window.min_timestamp, datetime.datetime(2015, 9, 3, 2, 0))
     self.assertEqual(window.max_timestamp, datetime.datetime(2015, 9, 25, 2, 0))
Esempio n. 22
0
 def __init__(self, config):
     self.settings = Settings()
     self.timelog = TimeLog(self.settings.get_timelog_file(),
                            self.settings.virtual_midnight)
     self.aliases = config.get('aliases', {})
Esempio n. 23
0
 def test_parse_correction_handles_virtual_midnight_todays_time(self):
     timelog = TimeLog(StringIO(), datetime.time(2, 0))
     self.assertEqual(timelog.parse_correction("00:15 did stuff"),
                      ("did stuff", datetime.datetime(2015, 5, 13, 00, 15)))
Esempio n. 24
0
 def test_parse_correction_leaves_regular_text_alone(self):
     timelog = TimeLog(StringIO(), datetime.time(2, 0))
     self.assertEqual(timelog.parse_correction("did stuff"),
                      ("did stuff", None))
Esempio n. 25
0
 def test_parse_correction_ignores_bad_absolute_times(self):
     timelog = TimeLog(StringIO(), datetime.time(2, 0))
     self.assertEqual(timelog.parse_correction("19:60 did stuff"),
                      ("19:60 did stuff", None))
     self.assertEqual(timelog.parse_correction("24:00 did stuff"),
                      ("24:00 did stuff", None))
Esempio n. 26
0
 def test_parse_correction_handles_virtual_midnight_todays_time(self):
     timelog = TimeLog(StringIO(), datetime.time(2, 0))
     self.assertEqual(timelog.parse_correction("00:15 did stuff"),
                      ("did stuff", datetime.datetime(2015, 5, 13, 00, 15)))
Esempio n. 27
0
 def test_parse_correction_ignores_relative_times_before_last_entry(self):
     timelog = TimeLog(StringIO("2015-05-12 16:00: stuff"),
                       datetime.time(2, 0))
     self.assertEqual(timelog.parse_correction("-30 did stuff"),
                      ("-30 did stuff", None))
Esempio n. 28
0
 def test_parse_correction_recognizes_relative_times(self):
     timelog = TimeLog(StringIO(), datetime.time(2, 0))
     self.assertEqual(timelog.parse_correction("-20 did stuff"),
                      ("did stuff", datetime.datetime(2015, 5, 12, 16, 7)))
Esempio n. 29
0
def make_time_window(file=None, min=None, max=None, vm=datetime.time(2)):
    from gtimelog.timelog import TimeLog
    if file is None:
        file = StringIO()
    return TimeLog(file, vm).window_for(min, max)
Esempio n. 30
0
 def test_parse_correction_ignores_bad_relative_times(self):
     timelog = TimeLog(StringIO(), datetime.time(2, 0))
     self.assertEqual(timelog.parse_correction("-200 did stuff"),
                      ("-200 did stuff", None))
Esempio n. 31
0
 def test_valid_time_rejects_times_in_the_future(self):
     timelog = TimeLog(StringIO(), datetime.time(2, 0))
     future = datetime.datetime(2015, 5, 12, 16, 30)
     self.assertFalse(timelog.valid_time(future))
Esempio n. 32
0
 def test_window_for_day(self):
     timelog = TimeLog(StringIO(), datetime.time(2, 0))
     window = timelog.window_for_day(datetime.date(2015, 9, 17))
     self.assertEqual(window.min_timestamp, datetime.datetime(2015, 9, 17, 2, 0))
     self.assertEqual(window.max_timestamp, datetime.datetime(2015, 9, 18, 2, 0))
Esempio n. 33
0
 def test_valid_time_accepts_times_between_last_entry_and_now(self):
     timelog = TimeLog(StringIO("2015-05-12 15:00: did stuff"),
                       datetime.time(2, 0))
     past = datetime.datetime(2015, 5, 12, 15, 20)
     self.assertTrue(timelog.valid_time(past))
Esempio n. 34
0
def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('-l',
                        '--logfile',
                        nargs=1,
                        metavar='LOGFILE',
                        help='Path to the gtimelog logfile to be use')
    parser.add_argument('-u',
                        '--user',
                        nargs=1,
                        metavar='USER',
                        help='User Identification to be used for report')
    parser.add_argument('-t',
                        '--time',
                        help='Print daily report with spent time',
                        action='store_false')
    parser.add_argument('-H',
                        '--header',
                        help='Print category headers',
                        action='store_true')
    parser.add_argument('-m',
                        '--minutes',
                        help='Print daily report with spent time in minutes',
                        action='store_true')
    parser.add_argument('-b',
                        '--back',
                        help='Print daily report back # of days',
                        metavar='BACK',
                        type=int)
    args = parser.parse_args()

    if args.logfile is not None:
        LogFile = set_logfile(args.logfile)
    else:
        LogFile = set_logfile()

    if args.user is not None:
        UserId = set_userid(args.user)
    else:
        UserId = set_userid()

    day_start = datetime.datetime.today().replace(hour=0,
                                                  minute=0,
                                                  second=0,
                                                  microsecond=0)
    if args.back:
        day_start = day_start - datetime.timedelta(days=args.back)
    day_end = day_start.replace(hour=23, minute=59, second=59, microsecond=999)
    Log = TimeLog(LogFile, virtual_midnight)
    log_entries = Log.window_for(day_start, day_end)
    total_work, _ = log_entries.totals()
    entries, totals = log_entries.categorized_work_entries()

    print("Today's work for %s" % UserId)
    if entries:
        if None in entries:
            categories = sorted(entries)
            categories.append('No category')
            entries['No category'] = e
            t = totals.pop(None)
            totals['No category'] = t
        else:
            categories = sorted(entries)
        for cat in categories:
            if args.header:
                print('- %s:' % cat)

            work = [(entry, duration)
                    for start, entry, duration in entries[cat]]
            work.sort()
            for entry, duration in work:
                if not duration:
                    continue  # skip empty "arrival" entries

                entry = entry[:1].upper() + entry[1:]
                if args.time:
                    print(u"  - %-61s  " % entry)
                else:
                    if args.minutes:
                        print(u"  - %-59s  %+5s %+4s" %
                              (entry, format_duration_short(duration),
                               as_minutes(duration)))
                    else:
                        print(u"  - %-59s  %+5s" %
                              (entry, format_duration_short(duration)))

            if args.time:
                pass
                # print("")
            else:
                if args.minutes:
                    print('-' * 75)
                    print(u"%+70s %4s" % (format_duration_short(
                        totals[cat]), as_minutes(totals[cat])))
                else:
                    print('-' * 70)
                    print(u"%+70s" % (format_duration_short(totals[cat])))
        print("Total work done : %s" % format_duration_short(total_work))
Esempio n. 35
0
 def test_parse_correction_handles_virtual_midnight_yesterdays_time(self):
     # Regression test for https://github.com/gtimelog/gtimelog/issues/33
     timelog = TimeLog(StringIO(), datetime.time(2, 0))
     self.assertEqual(timelog.parse_correction("15:20 did stuff"),
                      ("did stuff", datetime.datetime(2015, 5, 12, 15, 20)))
Esempio n. 36
0
 def test_valid_time_accepts_any_time_in_the_past_when_log_is_empty(self):
     timelog = TimeLog(StringIO(), datetime.time(2, 0))
     past = datetime.datetime(2015, 5, 12, 14, 20)
     self.assertTrue(timelog.valid_time(past))
Esempio n. 37
0
 def test_parse_correction_ignores_bad_absolute_times(self):
     timelog = TimeLog(StringIO(), datetime.time(2, 0))
     self.assertEqual(timelog.parse_correction("19:60 did stuff"),
                      ("19:60 did stuff", None))
     self.assertEqual(timelog.parse_correction("24:00 did stuff"),
                      ("24:00 did stuff", None))
Esempio n. 38
0
 def test_valid_time_rejects_times_in_the_future(self):
     timelog = TimeLog(StringIO(), datetime.time(2, 0))
     future = datetime.datetime(2015, 5, 12, 16, 30)
     self.assertFalse(timelog.valid_time(future))
Esempio n. 39
0
 def test_parse_correction_ignores_relative_times_before_last_entry(self):
     timelog = TimeLog(StringIO("2015-05-12 16:00: stuff"),
                       datetime.time(2, 0))
     self.assertEqual(timelog.parse_correction("-30 did stuff"),
                      ("-30 did stuff", None))
Esempio n. 40
0
 def test_valid_time_rejects_times_before_last_entry(self):
     timelog = TimeLog(StringIO("2015-05-12 15:00: did stuff"),
                       datetime.time(2, 0))
     past = datetime.datetime(2015, 5, 12, 14, 20)
     self.assertFalse(timelog.valid_time(past))
Esempio n. 41
0
 def get_time_log(self):
     return TimeLog(self.get_timelog_file(), self.virtual_midnight)
Esempio n. 42
0
 def test_valid_time_accepts_times_between_last_entry_and_now(self):
     timelog = TimeLog(StringIO("2015-05-12 15:00: did stuff"),
                       datetime.time(2, 0))
     past = datetime.datetime(2015, 5, 12, 15, 20)
     self.assertTrue(timelog.valid_time(past))