<html manifest="timecoder.appcache?v=2">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta name="apple-mobile-web-app-capable" content="yes">
<link rel="shortcut icon" href="favicon.png" type="image/x-icon">
<link rel="apple-touch-icon" href="favicon.png">
-webkit-touch-callout: none;
-webkit-tap-highlight-color: rgba(0,0,0,0);
-webkit-user-select: none;
-khtml-user-select: none;
border-collapse: collapse;
border-top: 1px solid gray;
border-top: 1px solid gray;
<button class="record">Press and hold</button>
<div class="table"></div>
<button class="clear">Clear times</button>
<button class="epoch">Start timing from now</button>
<p>CSV (in seconds after time started): </p>
<script src="d3.min.js" charset="utf-8"></script>
<script src="d3-jetpack.js" charset="utf-8"></script>
// https://bl.ocks.org/gka/17ee676dc59aa752b4e6
if(typeof(Storage) !== "undefined") {
if(!localStorage.getItem("times")) {
if(!localStorage.getItem("epoch")) {
localStorage.setItem("epoch", epoch);
epoch = new Date(localStorage.getItem("epoch"));
{ head: 'Start', cl: 'start', html: ƒ(0, timeFormat) },
{ head: 'End', cl: 'stop', html: ƒ(1, timeFormat) },
{ head: 'Duration', cl: 'duration', html: diffFormat },
var table = d3.select('.table')
table.append('thead').append('tr')
var tbody = table.append('tbody');
.on("touchstart", touchStart)
.on("touchend", touchEnd);
d3.select(".clear").on("click", clear);
d3.select(".epoch").on("click", setEpoch);
d3.event.preventDefault();
times.push([newDate(), null]);
d3.event.preventDefault();
times[times.length-1][1] = newDate();
// create empty rows and cells (enter selection)
// fill cells (update selection)
return columns.map(function(c) {
// compute cell values for this specific row
d3.keys(c).forEach(function(k) {
cell[k] = typeof c[k] == 'function' ? c[k](row,i) : c[k];
var csv = times.map(function(item) {
return item.map(function(endpoint) { return +endpoint / 1000; }).join(",")
d3.select("pre").text("start,end\n" + csv);
if(confirm("Are you sure you want to permanently delete all your recorded times?")) {
if(confirm("New items will show times counting from the moment you hit 'OK'")) {
localStorage.setItem("epoch", epoch);
if(d === null) return '⋯';
if(epoch - new Date(0) !== 0) {
renderTime = new Date(+d + d.getTimezoneOffset()*60*1000);
return d3.time.format('%X')(renderTime);
if(d[1] === null) return '⋯';
return d3.time.format('%M:%S.%L')(new Date(d[1] - d[0]));
function save(key, value) {
return localStorage.setItem(key, JSON.stringify(value));
var json = JSON.parse(localStorage.getItem(key));
return json.map(function(item) {
return item.map(function(endpoint) {
return new Date(endpoint);
return new Date(new Date() - epoch);