Exemplo n.º 1
0
    def expand_macro(self, formatter, name, content):
        args, kwargs = parse_args(content)
        pattern = args[0]
        if len(args) > 1 and args[1].strip().upper() == "COMPLETED":
            completed = "AND completed>0"
        else:
            completed = "AND completed=0"
        if len(args) > 2 and args[2].strip().upper() == "ASC":
            ordering = "ASC"
        else:
            ordering = "DESC"

        db = self.env.get_db_cnx()
        cursor = db.cursor()
        print """
            SELECT name FROM milestone WHERE name %s
              %s ORDER BY name %s
            """ % (db.like(), completed, ordering)
        cursor.execute("""
            SELECT name FROM milestone WHERE name %s
              %s ORDER BY name %s
            """ % (db.like(), completed, ordering), (pattern,)
        )

        out = StringIO()
        for name, in cursor.fetchall():
            wikitext = """
                == [milestone:%(milestonename)s %(milestonename)s] ==
                [[TicketQuery(milestone=%(milestonename)s,order=id,desc=0,format=table,col=summary|owner|ticket_status|type|status)]]
                """ % {'milestonename': name}
            Formatter(self.env, formatter.context).format(wikitext, out)

        return Markup(out.getvalue())
Exemplo n.º 2
0
 def expand_macro(self, formatter, name, content):
     
     if not content:
         return ''
     
     args, kwargs = parse_args(content)        
     if len(args) > 1:
         return system_message("Number of args can't be greater than 1")
     
     if args[0] == 'author':
         page = WikiPage(self.env, formatter.context.resource, 1)
         text = page.author 
     elif args[0] == 'version':
         page = WikiPage(self.env, formatter.context.resource)
         text = str(page.version)
     elif args[0] == 'changed_by':
         page = WikiPage(self.env, formatter.context.resource)
         text = page.author
     elif args[0] == 'comment':
         page = WikiPage(self.env, formatter.context.resource)
         text = page.comment
     elif args[0] == 'changed_ts':
         page = WikiPage(self.env, formatter.context.resource)
         text = format_datetime(page.time)
     else:
         return system_message("Unkwown argument %s" % args[0])
     
     return format_to_oneliner(self.env, formatter.context, text)
Exemplo n.º 3
0
    def expand_macro(self, formatter, name, content):
        args, kwargs = parse_args(content)
        pattern = args[0]
        if len(args) > 1 and args[1].strip().upper() == "COMPLETED":
            completed = "AND completed>0"
        else:
            completed = "AND completed=0"
        if len(args) > 2 and args[2].strip().upper() == "ASC":
            ordering = "ASC"
        else:
            ordering = "DESC"

        db = self.env.get_db_cnx()
        cursor = db.cursor()
        print """
            SELECT name FROM milestone WHERE name %s
              %s ORDER BY name %s
            """ % (db.like(), completed, ordering)
        cursor.execute(
            """
            SELECT name FROM milestone WHERE name %s
              %s ORDER BY name %s
            """ % (db.like(), completed, ordering), (pattern, ))

        out = StringIO()
        for name, in cursor.fetchall():
            wikitext = """
                == [milestone:%(milestonename)s %(milestonename)s] ==
                [[TicketQuery(milestone=%(milestonename)s,order=id,desc=0,format=table,col=summary|owner|ticket_status|type|status)]]
                """ % {
                'milestonename': name
            }
            Formatter(self.env, formatter.context).format(wikitext, out)

        return Markup(out.getvalue())
Exemplo n.º 4
0
    def expand_macro(self, formatter, name, content):
        args, kw = parse_args(content)
        page = kw.get('page', '')
        sort = kw.get('sort', 'DESC')
        order = kw.get('order', 'time')

        self.env.log.error('sort %s, order %s' % (sort, order))

        attachment_type = ""
        wiki_path = ""
        if content:
            argv = [arg.strip() for arg in content.split(',')]
            if len(argv) > 0:
                attachment_type = argv[0]

        db = self.env.get_db_cnx()
        if db == None:
            return "No DB connection"

        attachmentFormattedList = ""

        cursor = db.cursor()

        if attachment_type == None or attachment_type == "":
            cursor.execute("SELECT type,id,filename,size,time,description,"
                           "author,ipnr FROM attachment")

        elif page == None or page == "":
            cursor.execute(
                "SELECT type,id,filename,size,time,description,"
                "author,ipnr FROM attachment WHERE type=%s ORDER "
                "BY " + order + " " + sort, (attachment_type, ))

        else:
            cursor.execute(
                "SELECT type,id,filename,size,time,description,"
                "author,ipnr FROM attachment WHERE type=%s and "
                "id=%s ORDER BY " + order + " " + sort,
                (attachment_type, page))

        formatters = {
            "wiki": formatter.href.wiki,
            "ticket": formatter.href.ticket
        }
        types = {"wiki": "", "ticket": "ticket "}

        return tag.ul([
            tag.li(
                tag.a(filename,
                      href=formatter.href.attachment(type + "/" + id + "/" +
                                                     filename)), " (",
                tag.span(pretty_size(size), title=size), ") - ",
                tag.em(format_datetime(time)), " - added by ", tag.em(author),
                " to ", tag.a(types[type] + " " + id,
                              href=formatters[type](id)), " ") for type, id,
            filename, size, time, description, author, ipnr in cursor
        ])

        return attachmentFormattedList
Exemplo n.º 5
0
    def expand_macro(self, formatter, name, content):
        args, kw = parse_args(content)
        page = kw.get('page', '')
        sort = kw.get('sort', 'DESC')
        order = kw.get('order', 'time')

        self.env.log.error('sort %s, order %s' % (sort, order))

        attachment_type = ""
        wiki_path = ""
        if content:
            argv = [arg.strip() for arg in content.split(',')]
            if len(argv) > 0:
                attachment_type = argv[0]

        db = self.env.get_db_cnx()
        if db == None:
           return "No DB connection"

        attachmentFormattedList=""

        cursor = db.cursor()

        if attachment_type == None or attachment_type == "":
            cursor.execute("SELECT type,id,filename,size,time,description,"
                           "author,ipnr FROM attachment")

        elif page == None or page == "":
            cursor.execute("SELECT type,id,filename,size,time,description,"
                           "author,ipnr FROM attachment WHERE type=%s ORDER "
                           "BY " + order + " " + sort, (attachment_type,))

        else:
            cursor.execute("SELECT type,id,filename,size,time,description,"
                           "author,ipnr FROM attachment WHERE type=%s and "
                           "id=%s ORDER BY " + order + " " + sort, 
                           (attachment_type, page))

        formatters={"wiki": formatter.href.wiki, 
                    "ticket": formatter.href.ticket}
        types={"wiki": "", "ticket": "ticket "}

        return tag.ul(
                      [tag.li(
                          tag.a(filename, href=formatter.href.attachment(type + "/" + id + "/" + filename)),
                          " (", tag.span(pretty_size(size), title=size), ") - ", tag.em(format_datetime(time)), " - added by ",
                          tag.em(author), " to ",
                          tag.a(types[type] + " " + id, href=formatters[type](id)), " ")
                    for type,id,filename,size,time,description,author,ipnr in cursor])

        return attachmentFormattedList
Exemplo n.º 6
0
    def expand_macro(self, formatter, name, content):
        args, kwargs = parse_args(content, strict=False)

        milestones = filter(lambda m: not args or any(map(lambda a: m.name.startswith(a), args)), Milestone.select(self.env))

        req = formatter.req
        template = "listmilestones.html"
        data = {'milestones' : milestones}
        content_type = 'text/html'

        dispatcher = RequestDispatcher(self.env)
        response = dispatcher._post_process_request(req, template, data, content_type)

        # API < 1.1.2 does not return a method.
        if (len(response) == 3):
            template, data, content_type = response
            return Markup(Chrome(self.env).render_template(formatter.req, template, data, content_type=content_type, fragment=True))

        template, data, content_type, method = response
        return Markup(Chrome(self.env).render_template(formatter.req, template, data, content_type=content_type, fragment=True, method=method))
Exemplo n.º 7
0
    def expand_macro(self, formatter, name, content):
        args, kwargs = parse_args(content, strict=False)

        milestones = filter(lambda m: not args or any(map(lambda a: m.name.startswith(a), args)), Milestone.select(self.env))

        req = formatter.req
        template = "listmilestones.html"
        data = {'milestones' : milestones}
        content_type = 'text/html'

        dispatcher = RequestDispatcher(self.env)
        response = dispatcher._post_process_request(req, template, data, content_type)

        # API < 1.1.2 does not return a method.
        if (len(response) == 3):
            template, data, content_type = response
            return Markup(Chrome(self.env).render_template(formatter.req, template, data, content_type=content_type, fragment=True))

        template, data, content_type, method = response
        return Markup(Chrome(self.env).render_template(formatter.req, template, data, content_type=content_type, fragment=True, method=method))
Exemplo n.º 8
0
    def expand_macro(self, formatter, name, content):
        largs, kwargs = parse_args(content)
        try:
            num = int(largs[0]) - 1
            if num < 0:
                raise Exception
        except:
            raise TracError("Argument must be a positive integer!")

        option = self.env.config.get
        if 'raw' in kwargs:
            vraw = kwargs['raw'].lower()
            if vraw in ('yes', 'true', '1', 'on'):
                raw = True
            elif vraw in ('no', 'false', '0', 'off'):
                raw = False
        else:
            raw = self.raw

        res = formatter.resource
        id = res.id
        type = res.realm

        db = self.env.get_db_cnx()
        cursor = db.cursor()
        cursor.execute( "SELECT filename FROM attachment WHERE type=%s " \
                        "AND id=%s ORDER BY time", (type,id) )

        attmnts = cursor.fetchall()
        if len(attmnts) < num + 1 or not attmnts[num] or not attmnts[num][0]:
            raise TracError("Attachment #%i doesn't exists!" % (num + 1))
        filename = attmnts[num][0]

        wikilink = "attachment:'" + filename + "'"
        if raw:
            wikilink = "raw-" + wikilink
        if kwargs.get('format', self.format) == 'short':
            wikilink = "[%s %s]" % (wikilink, filename)
        return format_to_oneliner(self.env, formatter.context, wikilink)
Exemplo n.º 9
0
    def expand_macro(self, formatter, name, content):
        largs, kwargs = parse_args(content)
        try:
            num = int(largs[0]) - 1
            if num < 0:
              raise Exception
        except:
            raise TracError("Argument must be a positive integer!")

        option = self.env.config.get
        if 'raw' in kwargs:
            vraw = kwargs['raw'].lower()
            if vraw in ('yes','true','1','on'):
                raw = True
            elif vraw in ('no','false','0','off'):
                raw = False
        else:
            raw = self.raw

        res  = formatter.resource
        id   = res.id
        type = res.realm

        db = self.env.get_db_cnx()
        cursor = db.cursor()
        cursor.execute( "SELECT filename FROM attachment WHERE type=%s " \
                        "AND id=%s ORDER BY time", (type,id) )

        attmnts = cursor.fetchall()
        if len(attmnts) < num + 1 or not attmnts[num] or not attmnts[num][0]:
            raise TracError("Attachment #%i doesn't exists!" % (num+1))
        filename = attmnts[num][0]

        wikilink = "attachment:'" + filename + "'"
        if raw:
            wikilink = "raw-" + wikilink
        if kwargs.get('format', self.format) == 'short':
            wikilink = "[%s %s]" % (wikilink,filename)
        return format_to_oneliner(self.env, formatter.context, wikilink)
    def expand_macro(self, formatter, name, args):
        args = parse_args(args)
        # args is a tuple with a list of unnamed parameters (which is empty as
        # we don't support them), and a dictionary (named parameters)
        args = _get_args_defaults(formatter.env, args[1])
        timezone = args.pop('timezone')
        if timezone == 'utc' :
            timezone = utc 
        elif timezone == 'local':
            timezone = LocalTimeZone()
        else:
            raise Exception('parameter "timezone" was either "utc" nor '
                    '"local", it was: %s' % timezone)
        title = args.pop('title')
        days = int(args.pop('days'))
        width = int(args.pop('width'))
        height = int(args.pop('height'))
        statuses = args.pop('statuses').split('|')
        init_stat = args.pop('init_status')
        today = datetime.datetime.combine(
                datetime.date.today(),
                # last microsecond :-) of today
                datetime.time(23, 59, 59, 999999, tzinfo=timezone))
        time_start = today - timedelta(days=days)
        ts_start = to_timestamp(time_start)
        sql_start = ts_start * 1000000
        ts_end = to_timestamp(today)
        sql_end = ts_end * 1000000

        # values for the template:
        data = {}
        data['title'] = title
        data['width'] = width
        data['height'] = height
        data['id'] = ''.join(str(random.randint(0,9)) for i in range(15)) 

        # calculate db extra restrictions from extra parameters
        extra_parameters = []
        extra_sql = ''
        not_allowed = re.compile(r'[^a-zA-Z0-9_]')
        for stat in statuses:
            if not_allowed.search(stat):
                raise Exception('a status contained not allowed characters')
        statuses_sql = ','.join("'{0}'".format(stat) for stat in statuses)
        if args:
            extra_sql_constraints = []
            for key, value in args.items():
                if not_allowed.search(key):
                    raise Exception('a paramter contained not allowed characters')
                if value[0] == '!':
                    extra_sql_constraints.append("{0} <> %s".format(key))
                    extra_parameters.append(value[1:])
                else:
                    extra_sql_constraints.append("{0} = %s".format(key))
                    extra_parameters.append(value)
            extra_sql = u' AND '.join(extra_sql_constraints)

        if hasattr(self.env, 'get_read_db'):
            db = self.env.get_read_db()
        else:
            db = self.env.get_db_cnx()
        cursor = db.cursor()

        series = {
            'openedTickets': {},
            'closedTickets': {},
            'reopenedTickets': {},
            'openTickets': {}
        }

        # NOTE on casting times: in the sql statements below, we use:
        #            CAST((time / 86400) AS int) * 86400 AS date
        # which homogenize all times during a day to the same day. Example:
        # The following two values will have the same value
        # 1386280739  (2013-12-05 22:58:59)  ->  2013-12-05 00:00:00
        # 1386270739  (2013-12-05 20:12:19)  ->  2013-12-05 00:00:00

        # number of created tickets for the time period, grouped by day
        # a day has 86400 seconds
        if init_stat in statuses:
            sql = 'SELECT COUNT(DISTINCT id), ' \
                         'CAST((time / 86400000000) AS int) * 86400 AS date ' \
                  'FROM ticket WHERE {0} {1} time BETWEEN %s AND %s ' \
                  'GROUP BY date ORDER BY date ASC'.format(
                        extra_sql, ' AND ' if extra_sql else '', statuses_sql)
            cursor.execute(sql, tuple(extra_parameters) + (sql_start, sql_end))
            for count, timestamp in cursor:
                # flot needs the time in milliseconds, not seconds, see
                # https://github.com/flot/flot/blob/master/API.md#time-series-data
                series['openedTickets'][timestamp*1000] = float(count)

        # number of reopened tickets for the time period, grouped by day
        # a day has 86400 seconds
        cursor.execute("SELECT COUNT(DISTINCT tc.ticket), "
                            "CAST((tc.time / 86400000000) AS int) * 86400 as date "
                       "FROM ticket_change tc JOIN ticket t ON t.id = tc.ticket "
                       "WHERE {0} {1} field = 'status' AND newvalue in ({2}) AND oldvalue NOT IN ({2}) "
                       "AND tc.time BETWEEN %s AND %s "
                       "GROUP BY date ORDER BY date ASC".format(
                           extra_sql, ' AND ' if extra_sql else '', statuses_sql),
                       tuple(extra_parameters) + (sql_start, sql_end))
        for count, timestamp in cursor:
            # flot needs the time in milliseconds, not seconds, see
            # https://github.com/flot/flot/blob/master/API.md#time-series-data
            series['reopenedTickets'][float(timestamp*1000)] = float(count)

        # number of closed tickets for the time period, grouped by day (ms)
        cursor.execute("SELECT COUNT(DISTINCT ticket), "
                            "CAST((tc.time / 86400000000) AS int) * 86400 AS date "
                       "FROM ticket_change tc JOIN ticket t ON t.id = tc.ticket "
                       "WHERE {0} {1} tc.field = 'status' AND tc.newvalue not in ({2}) AND tc.oldvalue in ({2})"
                       "AND tc.time BETWEEN %s AND %s " \
                       "GROUP BY date ORDER BY date ASC".format(
                           extra_sql, ' AND ' if extra_sql else '', statuses_sql),
                       tuple(extra_parameters) + (sql_start, sql_end))
        for count, timestamp in cursor:
            # flot needs the time in milliseconds, not seconds, see
            # https://github.com/flot/flot/blob/master/API.md#time-series-data
            series['closedTickets'][float(timestamp*1000)] = -float(count)

        # calculate number of open tickets for each day
        
        # number of open tickets up to now
        cursor.execute(
            "SELECT COUNT(*) FROM ticket "
            "WHERE {0} {1} status in ({2})".format(
                extra_sql, ' AND ' if extra_sql else '', statuses_sql),
            tuple(extra_parameters))
        open_tickets = cursor.fetchone()[0]
        series['openTickets'][ts_end * 1000] = open_tickets

        formatter.env.log.debug('ts_end = {0}, ts_start = {1}'.format(ts_end, ts_start))
        for day_ms in range(long(math.floor(ts_end / 86400.0) * 86400000), long(ts_start * 1000), -86400000):
            open_tickets -= series['closedTickets'].get(day_ms, 0)
            open_tickets -= series['openedTickets'].get(day_ms, 0)
            open_tickets -= series['reopenedTickets'].get(day_ms, 0)
            series['openTickets'][day_ms] = open_tickets

        # sort all series and put them in data
        for i in series:
            keys = series[i].keys()
            keys.sort()
            data[i] = json.dumps([(k, series[i][k]) for k in keys])

        # generate output
        # NOTE: if you change the namespace below, you also need to change it
        # when using in get_htdocs_dirs
        add_javascript(formatter.req, 'stsm/js/excanvas.min.js')
        add_javascript(formatter.req, 'stsm/js/jquery.flot.min.js')
        add_javascript(formatter.req, 'stsm/js/jquery.flot.stack.min.js')
        add_javascript(formatter.req, 'stsm/js/simpleticketstats.js')
        template = Chrome(self.env).load_template(
                'simpleticketstats_macro.html', method='text')
        formatter.env.log.debug(data)
        return Markup(template.generate(**data))
Exemplo n.º 11
0
    def expand_macro(self, formatter, name, content):

        args, kwargs = parse_args(content)

        if not args:
            page_name = get_resource_name(self.env, formatter.resource)
            mode = 'normal'
        elif len(args) == 1:
            page_name = args[0]
            mode = 'normal'
        else:
            page_name = args[0]
            mode = 'delta'

        db = self.env.get_db_cnx()
        cursor = db.cursor()
        cursor.execute("SELECT author, time FROM wiki WHERE name = '%s' "
                       "ORDER BY version DESC LIMIT 1" % page_name)
        row = cursor.fetchone()
        if not row:
            raise TracError('Wiki page "%s" not found.' % page_name)

        username = row[0]
        time_int = row[1]

        # see if there's a fullname associated with username
        cursor.execute("SELECT value FROM session_attribute "
                       "WHERE sid = '%s' AND name = 'name'" % username)
        row = cursor.fetchone()
        if not row:
            author = username
        else:
            author = row[0]

        if mode == 'delta':
            last_mod = to_datetime(time_int)
            now = to_datetime(None)
            elapsed = now - last_mod
            if elapsed.days == 0:
                if elapsed.seconds / 3600 > 1.5:
                    count = elapsed.seconds / 3600
                    unit = 'hour'
                elif elapsed.seconds / 60 > 1.5:
                    count = elapsed.seconds / 60
                    unit = 'minute'
                else:
                    count = elapsed.seconds
                    unit = 'second'
            elif elapsed.days / 3650 > 1.5:
                count = elapsed.days / 3650
                unit = 'decade'
            elif elapsed.days / 365 > 1.5:
                count = elapsed.days / 365
                unit = 'year'
            elif elapsed.days / 30 > 1.5:
                count = elapsed.days / 30
                unit = 'month'
            elif elapsed.days / 7 > 1.5:
                count = elapsed.days / 7
                unit = 'week'
            else:
                count = elapsed.days
                unit = 'day'
            text = "" + repr(count) + " " + unit
            if (count != 1 and count != -1): text += "s"
        else:
            text = format_datetime(time_int, '%c')

        return Markup(text)
Exemplo n.º 12
0
    def expand_macro(self, formatter, name, content, args=None):
        if content is not None:
            content = content.strip()
        if not args and not content:
            raw_actions = self.config.options('ticket-workflow')
        else:
            is_macro = args is None
            if is_macro:
                kwargs = parse_args(content)[1]
                file = kwargs.get('file')
            else:
                file = args.get('file')
                if not file and not content:
                    raise ProcessorError("Invalid argument(s).")

            if file:
                print(file)
                text = RepositoryManager(self.env).read_file_by_path(file)
                if text is None:
                    raise ProcessorError(
                        tag_("The file %(file)s does not exist.",
                             file=tag.code(file)))
            elif is_macro:
                text = '\n'.join(line.lstrip() for line in content.split(';'))
            else:
                text = content

            if '[ticket-workflow]' not in text:
                text = '[ticket-workflow]\n' + text
            parser = RawConfigParser()
            try:
                parser.readfp(io.StringIO(text))
            except ParsingError as e:
                if is_macro:
                    raise MacroError(exception_to_unicode(e))
                else:
                    raise ProcessorError(exception_to_unicode(e))
            raw_actions = list(parser.items('ticket-workflow'))
        actions = parse_workflow_config(raw_actions)
        states = list(
            {state for action in actions.itervalues()
                   for state in action['oldstates']} |
            {action['newstate'] for action in actions.itervalues()})
        action_labels = [attrs['label'] for attrs in actions.values()]
        action_names = list(actions)
        edges = []
        for name, action in actions.items():
            new_index = states.index(action['newstate'])
            name_index = action_names.index(name)
            for old_state in action['oldstates']:
                old_index = states.index(old_state)
                edges.append((old_index, new_index, name_index))

        args = args or {}
        width = args.get('width', 800)
        height = args.get('height', 600)
        graph = {'nodes': states, 'actions': action_labels, 'edges': edges,
                 'width': width, 'height': height}
        graph_id = '%012x' % id(graph)
        req = formatter.req
        add_script(req, 'common/js/excanvas.js', ie_if='IE')
        add_script(req, 'common/js/workflow_graph.js')
        add_script_data(req, {'graph_%s' % graph_id: graph})
        return tag(
            tag.div('', class_='trac-workflow-graph trac-noscript',
                    id='trac-workflow-graph-%s' % graph_id,
                    style="display:inline-block;width:%spx;height:%spx" %
                          (width, height)),
            tag.noscript(
                tag.div(_("Enable JavaScript to display the workflow graph."),
                        class_='system-message')))
Exemplo n.º 13
0
    def expand_macro(self, formatter, name, content):

        args, kwargs = parse_args(content)
        
        if not args:
            page_name = get_resource_name(self.env, formatter.resource)
            mode = 'normal'
        elif len(args) == 1:
            page_name = args[0]
            mode = 'normal'
        else:
            page_name = args[0]
            mode = 'delta'

        db = self.env.get_db_cnx()
        cursor = db.cursor()
        cursor.execute("SELECT author, time FROM wiki WHERE name = '%s' "
                       "ORDER BY version DESC LIMIT 1" % page_name)
        row = cursor.fetchone()
        if not row:
            raise TracError('Wiki page "%s" not found.' % page_name)

        username = row[0]
        time_int = row[1]
        
        # see if there's a fullname associated with username
        cursor.execute("SELECT value FROM session_attribute "
                       "WHERE sid = '%s' AND name = 'name'" % username)
        row = cursor.fetchone()
        if not row:
            author = username
        else:
            author = row[0]

        if mode == 'delta':
            last_mod = to_datetime(time_int)
            now = to_datetime(None)
            elapsed = now - last_mod
            if elapsed.days == 0:
                if elapsed.seconds / 3600 > 1.5:
                    count = elapsed.seconds / 3600
                    unit = 'hour'
                elif elapsed.seconds / 60 > 1.5:
                    count = elapsed.seconds / 60
                    unit = 'minute'
                else:
                    count = elapsed.seconds
                    unit = 'second'
            elif elapsed.days / 3650 > 1.5:
                count = elapsed.days / 3650
                unit = 'decade'
            elif elapsed.days / 365 > 1.5:
                count = elapsed.days / 365
                unit = 'year'
            elif elapsed.days / 30 > 1.5:
                count = elapsed.days / 30
                unit = 'month'
            elif elapsed.days / 7 > 1.5:
                count = elapsed.days / 7
                unit = 'week'
            else:
                count = elapsed.days
                unit = 'day'
            text = "" + repr(count) + " " + unit
            if (count != 1 and count != -1): text += "s"
        else:
            text = format_datetime(time_int, '%c')

        return Markup(text)