<<results=tex>>=
    projects <- list(ant$bugs, jmeter$bugs, jodatime$bugs)
    project_details <- data.frame(
        `Fixes` = sapply(projects, nrow),
        `Bugs` = sapply(projects, function(x) nlevels(factor(x$bug_id)))
    )
    rownames(project_details) <- c('ant', 'jmeter', 'jodatime_2.0')
    rownames(project_details) <- format_name(rownames(project_details))
    # Given a vector of durations in seconds, formats them into the form '3 weeks 6 days 5 hours' etc.
    # The lowest granularity of time shown is controlled by the min parameter, or by the highest granularity for which a non-zero value exists if lower
    # The highest granularity shown is controlled by the max parameter
    # Both min and max may be given as one of 'second', 'minute', 'hour', 'day', 'week', 'year', or by the corresponding numbers 1-6
    # Don't rely on the accuracy of this method, it makes quite simple assumptions about lengths of time and is subject to rounding errors
    format_duration <- function(seconds, min=1, max=6){
        names <- c('second', 'minute', 'hour', 'day', 'week', 'year')
        divisors <- c(NA,60,60,24,7,52)
        durations <- list(seconds,0,0,0,0,0)

        if(is.character(min)){
            min <- grep(min, names)
        }
        if(is.character(max)){
            max <- grep(max, names)
        }

        for(i in seq(2,max)){
            if(i<=min){
                durations[[i]] <- round(durations[[i-1]] / divisors[i])
                durations[[i-1]] <- ifelse(durations[[i]], 0, durations[[i-1]])
            }
            else{
                durations[[i]] <- durations[[i-1]] %/% divisors[i]
                durations[[i-1]] <- durations[[i-1]] %% divisors[i]
            }
        }

        for(i in seq(1,6)){
            durations[[i]] <- ifelse(durations[[i]], paste(durations[[i]], ' ', names[i], ifelse(abs(durations[[i]])>1, 's', ''), sep=''), '')
        }
        paste(durations[[6]], durations[[5]], durations[[4]], durations[[3]], durations[[2]], durations[[1]])
    }

    lifetimes <- sapply(projects, function(x) fivenum(difftime(x$timestamp.fix, x$timestamp.report, units="secs")))
    lifetimes <- matrix(format_duration(lifetimes, 'year'), nrow=nrow(lifetimes))
    rownames(lifetimes) <- c('Min', 'Q1', 'Q2', 'Q3', 'Max')
    project_details <- cbind(project_details, t(lifetimes))

    x <- xtable(t(project_details), caption='Project details and interquartile ranges of bug lifetimes', label='tab:project_details')
    print(x, hline.after=c(-1,0,2,nrow(x)), table.placement='tb')
@
