1

I have an array of objects which contain an end and a start date. But the period between dates may straddle midnight. If they do I wish to replace the object with two objects, the first with the previous start date and an end date of midnight. The second with a start time of 00:00 the next day and end date of what it was previously.

So for example if the array contains an object such as this,

{
  "start": "2016-11-04 22:00",
  "end": "2016-11-05 03:00"
}

I want to replace that object with two objects,

{
  "start": "2016-11-04 22:00",
  "end": "2016-11-04 24:00"
},
{
  "start": "2016-11-05 00:00",
  "end": "2016-11-05 03:00"
}

Here is my attempt using data.push(). Clearly this is not the way to do it:

d3.json("data.json", function(data) {
  var parseTime = d3.timeParse("%Y-%m-%d %H:%M");
  data.forEach(function(d) {
    d.commence = parseTime(d.start);
    d.conclude = parseTime(d.end);
    if (d.commence.getDay() != d.conclude.getDay()) {
      midnight = d.commence.getFullYear() + "-" + d.commence.getMonth() + "-" + d.commence.getDay() + " 24:00";
      morning = d.conclude.getFullYear() + "-" + d.conclude.getMonth() + "-" + d.conclude.getDay() + " 00:00";
      data.push({
        "start": d.start,
        "end": midnight
      }, {
        "start": morning,
        "end": d.end
      })
    }
  });
...

So how can I add or remove an object as I am iterating through the array of objects?

The data starts like this,

[
  {
    "start": "2016-11-01 12:00",
    "end": "2016-11-01 22:00"
  },
  {
    "start": "2016-11-02 02:00",
    "end": "2016-11-02 18:00"
  },
  {
    "start": "2016-11-03 09:00",
    "end": "2016-11-03 12:00"
  },
  {
    "start": "2016-11-04 22:00",
    "end": "2016-11-05 03:00"
  },
  {
    "start": "2016-11-06 12:00",
    "end": "2016-11-06 23:00"
  }
]

And in the console I can see it ultimately looks like this,

[
  {
    "start": "2016-11-01 12:00",
    "end": "2016-11-01 22:00",
    "commence": "2016-11-01T12:00:00.000Z",
    "conclude": "2016-11-01T22:00:00.000Z"
  },
  {
    "start": "2016-11-02 02:00",
    "end": "2016-11-02 18:00",
    "commence": "2016-11-02T02:00:00.000Z",
    "conclude": "2016-11-02T18:00:00.000Z"
  },
  {
    "start": "2016-11-03 09:00",
    "end": "2016-11-03 12:00",
    "commence": "2016-11-03T09:00:00.000Z",
    "conclude": "2016-11-03T12:00:00.000Z"
  },
  {
    "start": "2016-11-04 22:00",
    "end": "2016-11-05 03:00",
    "commence": "2016-11-04T22:00:00.000Z",
    "conclude": "2016-11-05T03:00:00.000Z"
  },
  {
    "start": "2016-11-06 12:00",
    "end": "2016-11-06 23:00",
    "commence": "2016-11-06T12:00:00.000Z",
    "conclude": "2016-11-06T23:00:00.000Z"
  },
  {
    "start": "2016-11-04 22:00",
    "end": "2016-10-5 24:00"
  },
  {
    "start": "2016-10-6 00:00",
    "end": "2016-11-05 03:00"
  }
]

The full code is on github and a demo is running on gh-pages.

Any suggestions would be greatly appreciated,

Thanks

1 Answer 1

1

Here is a solution using the flatMap concept (functional programming):

Array.prototype.flatMap = function(lambda) { 
  return Array.prototype.concat.apply([], this.map(lambda)); 
};

d3.json("data.json", function(data) {

  var parseTime = d3.timeParse("%Y-%m-%d %H:%M");
  var formatEndOfDay = d3.timeFormat("%Y-%m-%d 24:00");
  var formatStartOfDay = d3.timeFormat("%Y-%m-%d %H:%M");

  var result = data.flatMap( d => {
    var start = parseTime(d.start);
    var end = parseTime(d.end);
    if (start.getDay() == end.getDay())
      return [d];
    else {
      var firstPart = { "start": d.start, "end": formatEndOfDay(start) }
      var secondPart = { "start": formatStartOfDay(end.setHours(0)), "end": d.end }
      return [firstPart, secondPart];
    }
  });

  console.log(result);
});

Array.prototype.flatMap = function(lambda) { 
  return Array.prototype.concat.apply([], this.map(lambda)); 
};

var data = [
  {
    "start": "2016-11-01 12:00",
    "end": "2016-11-01 22:00"
  },
  {
    "start": "2016-11-02 02:00",
    "end": "2016-11-02 18:00"
  },
  {
    "start": "2016-11-03 09:00",
    "end": "2016-11-03 12:00"
  },
  {
    "start": "2016-11-04 22:00",
    "end": "2016-11-05 03:00"
  },
  {
    "start": "2016-11-06 12:00",
    "end": "2016-11-06 23:00"
  }
];

var parseTime = d3.timeParse("%Y-%m-%d %H:%M");
var formatEndOfDay = d3.timeFormat("%Y-%m-%d 24:00");
var formatStartOfDay = d3.timeFormat("%Y-%m-%d %H:%M");

var result = data.flatMap( d => {
  var start = parseTime(d.start);
  var end = parseTime(d.end);
  if (start.getDay() == end.getDay())
    return [d];
  else {
    var firstPart = { "start": d.start, "end": formatEndOfDay(start) }
    var secondPart = { "start": formatStartOfDay(end.setHours(0)), "end": d.end }
    return [firstPart, secondPart];
  }
});

console.log(result);
<script src="https://d3js.org/d3.v4.min.js"></script>


As a reminder, here is an example of flatMap: each element of the list is transformed into multiple elements; and these sub-lists are then flattened to produce a list:

Array.prototype.flatMap = function(lambda) { 
  return Array.prototype.concat.apply([], this.map(lambda)); 
};

console.log([1, 2, 3, 4].flatMap(d => d % 2 == 0 ? [d, d] : [d]))

Which translates in our case to going through each start/end element, and if an element has its start and end on the same day, then we transform it into a list of 1 element (itself); and if an element doesn't start and end on the same day, then we transform it in a list of 2 elements (first day / end day).


As javascript doesn't have a built-in implementation of flatMap, we can create one this way:

Array.prototype.flatMap = function(lambda) { 
  return Array.prototype.concat.apply([], this.map(lambda)); 
};
Sign up to request clarification or add additional context in comments.

1 Comment

Well this is a sophisticated answer, maybe a lot more than need. I'll have to read up on lambda functions. Thanks for looking at this. I got it to almost work at github.com/shanegibney/D3-Date-Hours-Graph but I am having a new problem which I posted about here stackoverflow.com/questions/49989105/… Thank you for your help, much appreciated

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.