0

I'm trying to produce a graph like the following:

Tabbed time line graph

From an array of events as follows:

var events = { 
    "0": {"guid": "78926349827546", "created": "2017-07-07 14:14:21" },
    "1": {"guid": "78926349827546", "created": "2017-07-08 15:44:10" },
    "2": {"guid": "20936752065745", "created": "2017-07-09 12:09:24" },
    "3": {"guid": "20936752065745", "created": "2017-07-11 06:55:42" },
    "4": {"guid": "20936752065745", "created": "2017-07-11 22:10:29" },
    ...
};

I'm currently using the Google Line Chart. Although I'm happy with the aesthetic, I still need to find a way to produce a tabbed display of several timescales, e.g. Today, Last 7 Days, Last Month and Total.

Programmatically, this is proving to be a sisyphean task, as I have to count occurrences across (in one instance) every hour in the last day, and then (in another instance) every day in the last week etc.

And there's a lot of date conversion, counting backwards from today and so on.

Is there a way of taking my array and producing a new array of human-readable dates relative from today, across several timescales?

Sean H
  • 1,045
  • 1
  • 14
  • 32
  • I guess you could convert all `created` strings to `momentjs` objects and then use [`Array#filter`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter) to filter those items you really need. – lumio Aug 15 '17 at 10:00
  • If it's just counting backwards from this exact moment, you could create a table of milliseconds of the time periods you want (like 1 day = x milliseconds). Then transform the dates to their millisecond values, and filter the ones where createdDate > currentDate - timePeriod – Jusmpty Aug 15 '17 at 10:06

1 Answers1

0

This is really a duplicate of a couple of questions like Where can I find documentation on formatting a date in JavaScript?, How to add months to a date in JavaScript? and Add days to JavaScript Date. So there are plenty of existing examples to work from.

Also, Google Charts has its own date formatter.

Anyway, you might use a function that takes a start date, end date and increment and returns an array of timestamps in a particular format. Formatting the strings can use a second function or the Google Charts formatter.

A bare bones version is very little code, to add some logic for forward or backward series takes a few more lines.

// Return date string in YYYY-MM-DD HH:mm:ss format
function formatDate(date) {
  function z(n){return (n<10? '0':'') + n}
  return date.getFullYear() + '-' +
         z(date.getMonth() + 1) + '-' +
         z(date.getDate()) + ' ' +
         z(date.getHours()) + ':' +
         z(date.getMinutes()) + ':' +
         z(date.getSeconds());
}

// Return date strings from start date to end date
// with increment inc in hours
function getDateSeries(start, end, inc) {
  var d = new Date(+start);
  inc = +inc;
  var dates = [];
  
  // Deal with backwards sequences
  var reverse = false, t;
  if (start > end) {
    t = start;
    start = end;
    end = t;
    reverse = true;
  }
  if (inc < 0) {
    reverse = true;
    inc *= -1;
  }
  
  while (start <= end) {
    dates.push(formatDate(start));
    start.setHours(start.getHours() + inc);
  }
  return reverse? dates.reverse() : dates;
}

// Hourly intervals over 2 days forwards
console.log(getDateSeries(new Date(2017,7,18), new Date(2017,7,19), 1));

// 6 hourly intervals over 10 days backwards
console.log(getDateSeries(new Date(2017,7,28), new Date(2017,7,18), -6));

// Hourly intervals from now going back 24 hours
var now = new Date();
var end = new Date(+now);
end.setDate(end.getDate() - 1);
console.log(getDateSeries(now, end, -1))

// Daily intervals from today going back 30 days
var now = new Date();
now.setHours(0,0,0,0);
var end = new Date(+now);
end.setDate(end.getDate() - 30);
console.log(getDateSeries(now, end, -24))

There are plenty of libraries around to help with formatting, incrementing and decrementing dates but if this is all you want to do, it doesn't take much to write.

This could be modified so the start is always "now" or "today" and use an interval in days rather than a start and end date with hours.

Where a library would come in handy is if you want say monthly intervals on the last day of the month or similar (since months aren't of equal length). So using moment.js you could do:

function getMonthlySequenceStart(months) {
  var format = 'YYYY-MM-DD HH:mm:ss'
  var now = moment().startOf('month');
  var count = Math.abs(months);
  var direction = months < 0? -1 : 1;
  var result = [];
  while (count--) {
    result.push(now.format(format));
    now.add(direction, 'months');
  }
  return result;
}

console.log(getMonthlySequenceStart(-12));

function getMonthlySequenceEnd(months) {
  var format = 'YYYY-MM-DD HH:mm:ss'
  var now = moment().endOf('month').startOf('day');
  var count = Math.abs(months);
  var direction = months < 0? -1 : 1;
  var result = [];
  while (count--) {
    result.push(now.format(format));
    now.add(direction, 'months');
    now.endOf('month').startOf('day');
  }
  return result;
}

console.log(getMonthlySequenceEnd(-12));
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment.min.js"></script>

Not using a library isn't too hard either. The following sets the date to the first of the month as then it's easy to decrement by 1 month, then get the day before (the last day of the previous month) for the string:

// Sequence of end of months from current month
// back for num months
function getMonthlySequenceEnd(num) {
  var now = new Date();
  now.setHours(0,0,0,0);
  var t, result = [];
  // Set to first day of next month
  now.setMonth(now.getMonth() + 1, 1)
  while (num--) {
    t = new Date(+now);
    t.setDate(t.getDate() - 1);
    result.push(formatDate(t));
    now.setMonth(now.getMonth() - 1);
  }
  return result;
}

function formatDate(date) {
  function z(n) {return (n < 10 ? '0' : '') + n}
  return date.getFullYear() + '-' + z(date.getMonth() + 1) + '-' + z(date.getDate()) + ' ' +
    z(date.getHours()) + ':' + z(date.getMinutes()) + ':' + z(date.getSeconds());
}

console.log(getMonthlySequenceEnd(24));

So I think you now have enough to do whatever is required.

RobG
  • 142,382
  • 31
  • 172
  • 209