1

If I have an array of objects like this:

{
    "workPeriods": [
        {
            "user_id": "9345bf",
            "startDate": "2018-02-05T05:00:00.000Z",
            "endDate": "2018-02-09T05:00:00.000Z"
        },
        {
            "user_id": "80c3a9",
            "startDate": "2018-02-12T05:00:00.000Z",
            "endDate": "2018-02-16T05:00:00.000Z"
        },
        {
            "user_id": "35jh87",
            "startDate": "2018-02-19T05:00:00.000Z",
            "endDate": "2018-02-23T05:00:00.000Z"
        }
    ]
}

What is the best way to print all the dates contained on each one of the objects dates range?
This need to be done using Moment jS

The result should be something like this:

'Work days: 02/05, 02/06, 02/07, 02/08, 02/09,   02/12, 02/13, 02/14, 02/15, 02/16,   02/19, 02/20, 02/21, 02/22, 02/23'

Any suggestion? Thanks!

Roman
  • 4,922
  • 3
  • 22
  • 31
Leonardo Uribe
  • 123
  • 1
  • 13

4 Answers4

1

I'm not sure what exactly do you mean by the "best" way to solve your problem, because you have not specified the criteria to evaluate the solution against. Seeing the among other tag makes me write some code which I think is relevant.

In the code below (which you can test out here):

  • First map() translates each work period into a set of days in such period. This will result in "array of arrays".
  • reduce() flattens the arrays into a single one.
  • The map() is then formatting each moment.js Moment object into its string representaion.
  • Finally, join(' ') transforms it in into a single string where all the dates are space separated.

If you write in ES6 or TypeScript:

const sourceObject = {
  "workPeriods": [
    {"user_id":"9345bf", "startDate":"2018-02-05T05:00:00.000Z", "endDate":"2018-02-09T05:00:00.000Z"},
    {"user_id":"80c3a9", "startDate":"2018-02-12T05:00:00.000Z", "endDate":"2018-02-16T05:00:00.000Z"},
    {"user_id":"35jh87", "startDate":"2018-02-19T05:00:00.000Z", "endDate":"2018-02-23T05:00:00.000Z"}
  ]
};

const userFriendlyWorkPeriodsDayList = sourceObject
  .workPeriods
  .map(workPeriod => {
    const allDaysInRange = [];
    const currentDate = moment(workPeriod.startDate);
    const endDate = moment(workPeriod.endDate);

    while (currentDate.isBefore(endDate)) {
      allDaysInRange.push(currentDate.clone());
      currentDate.add(1, 'days');
    }

    return allDaysInRange;
  })
  .reduce((accumulator, current) => (accumulator.push(...current), accumulator), [])
  .map(date => date.format('MM/DD'))
  .join(' ');

const result = `Work days: ${userFriendlyWorkPeriodsDayList}`;

Same code rewritten in older versions of JavaScript require usage of function keyword instead of =>. They may also lack spread operator support (.push(...arrayOfValuesBeingPushed)).

const sourceObject = {
  "workPeriods": [
    {"user_id":"9345bf", "startDate":"2018-02-05T05:00:00.000Z", "endDate":"2018-02-09T05:00:00.000Z"},
    {"user_id":"80c3a9", "startDate":"2018-02-12T05:00:00.000Z", "endDate":"2018-02-16T05:00:00.000Z"},
    {"user_id":"35jh87", "startDate":"2018-02-19T05:00:00.000Z", "endDate":"2018-02-23T05:00:00.000Z"}
  ]
};

const userFriendlyWorkPeriodsDayList = sourceObject
  .workPeriods
  .map(function (workPeriod) {
    const allDaysInRange = [];
    const currentDate = moment(workPeriod.startDate);
    const endDate = moment(workPeriod.endDate);

    while (currentDate.isBefore(endDate)) {
      allDaysInRange.push(currentDate.clone());
      currentDate.add(1, 'days');
    }

    return allDaysInRange;
  })
  .reduce(function (accumulator, current) { accumulator.push(...current); return accumulator; }, [])
  .map(function (date) { return date.format('MM/DD'); })
  .join(' ');

const result = `Work days: ${userFriendlyWorkPeriodsDayList}`;

console.log(result)

Igor Soloydenko
  • 11,067
  • 11
  • 47
  • 90
  • Thanks for your answer Igor! And you're right I was talking about functional-programming! I have an additional question in your answer... Even that I've studied ES6 in the past, just recently I had to start using it. So I'm getting a "Unexpected use of comma operator no-sequences" lint error on the reduce line. I fixed the problem by separating the push line and returning the accumulator in a different line... But can you please explain to me the syntax on `(accumulator.push(...current), accumulator)` to be the same result a: ` accumulator.push(...current); return accumulator;` – Leonardo Uribe Jan 02 '18 at 16:42
  • @LeonardoUribe glad to help. `(a; b; c; ...; x; z)` will execute all the expressions `a`, `b`, `c`, ..., `x` in that precise order, and "return" the value of the last expression, which is `z`. You can read this section of MDN doc: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comma_Operator#Processing_and_then_returning (I recommend to read the entire doc, though). Please consider accepting, up voting my answer if you find it helpful. Thanks – Igor Soloydenko Jan 02 '18 at 16:48
0

You can use array#reduce to get the days between the startDate and endDate for each object in an array. Then get the distinct dates using Set and Array#from and wrap it in an object.

var data = {"workPeriods":[ {"user_id":"9345bf", "startDate":"2018-02-05T05:00:00.000Z", "endDate":"2018-02-09T05:00:00.000Z"}, {"user_id":"80c3a9", "startDate":"2018-02-12T05:00:00.000Z", "endDate":"2018-02-16T05:00:00.000Z"}, {"user_id":"35jh87", "startDate":"2018-02-19T05:00:00.000Z",
"endDate":"2018-02-23T05:00:00.000Z"} ]};

var result = data.workPeriods.reduce((r, {startDate, endDate}) => {
  var dates = [], startDate = moment(startDate), endDate = moment(endDate);
  while (startDate.isSameOrBefore(endDate)) {
      dates.push(startDate.format('MM/DD/YYYY').substring(0,5));
      startDate.add(1, 'days');
  }
 return r.concat(dates);
},[]);

var distinctDates = {'work days': Array.from(new Set(result))};

console.log(distinctDates);
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.20.1/moment.min.js"></script>
Hassan Imam
  • 21,956
  • 5
  • 41
  • 51
0

Because you asked for a solution, I used a recurive function called getWorkingdays to get all days between a start and an end day. After this you can use map to iterate over the data.workingdays to execute getWorkingdays on it.

Example

const data = {
  "workPeriods": [{
    "user_id": "9345bf",
    "startDate": "2018-02-05T05:00:00.000Z",
    "endDate": "2018-02-09T05:00:00.000Z"
  }, {
    "user_id": "80c3a9",
    "startDate": "2018-02-12T05:00:00.000Z",
    "endDate": "2018-02-16T05:00:00.000Z"
  }, {
    "user_id": "35jh87",
    "startDate": "2018-02-19T05:00:00.000Z",
    "endDate": "2018-02-23T05:00:00.000Z"
  }]
}

const flatMap = (arr, fn) => 
  [].concat.apply([], arr).map(fn)

const addDays = (day, numberOfDays) =>
  moment(day).add(numberOfDays, 'days')

const format_MM_DD = date =>
  moment(date).format('MM/DD')

const getWorkingdays = (start, end, workingdays = []) =>
  moment(start).isSameOrBefore(end) 
    ? getWorkingdays(addDays(start, 1), end, workingdays.concat(start)) 
    : workingdays

const workingdays = data.workPeriods.map(({startDate, endDate}) => 
  getWorkingdays(startDate, endDate)
)
  
const formattedWorkingdays = flatMap(workingdays, format_MM_DD)

console.log('Work days:', formattedWorkingdays.join(', '))
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.20.1/moment.min.js"></script>

A more Functional Example

This solution uses currying and function composition (pipe). It has some boilerplate functions witch are obsolete if you are using a libary like ramda.

pipe(reduceWorkingdays, flatMap(format_MM_DD), join(', '))(data.workPeriods)

const data = {
  "workPeriods": [{
    "user_id": "9345bf",
    "startDate": "2018-02-05T05:00:00.000Z",
    "endDate": "2018-02-09T05:00:00.000Z"
  }, {
    "user_id": "80c3a9",
    "startDate": "2018-02-12T05:00:00.000Z",
    "endDate": "2018-02-16T05:00:00.000Z"
  }, {
    "user_id": "35jh87",
    "startDate": "2018-02-19T05:00:00.000Z",
    "endDate": "2018-02-23T05:00:00.000Z"
  }]
}

// boilerplate code 
const pipe = (...fns) => x => fns.reduce((v, f) => f(v), x)

const map = fn => arr =>
  arr.map(fn)
  
const flatMap = fn => arr =>
  map(fn)([].concat.apply([], arr))

const reduce = fn => startValue => arr => 
  arr.reduce(fn, startValue)
  
const join = joiner => arr =>
  arr.join(joiner)

// logic
const isSameOrBefore = (compareDate, date) =>
  moment(compareDate).isSameOrBefore(date)

const addDays = (day, numberOfDays) =>
  moment(day).add(numberOfDays, 'days')

const format_MM_DD = date =>
  moment(date).format('MM/DD')

const getWorkingdays = (start, end, workingdays = []) =>
  isSameOrBefore(start, end) 
    ? getWorkingdays(addDays(start, 1), end, workingdays.concat(start)) 
    : workingdays

const reduceWorkingdays = flatMap(
  ({startDate, endDate}) => getWorkingdays(startDate, endDate)
)

console.log(
  'work days:', 
  pipe(reduceWorkingdays, flatMap(format_MM_DD), join(', '))(data.workPeriods)
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.20.1/moment.min.js"></script>
Roman
  • 4,922
  • 3
  • 22
  • 31
-1

Perhaps you could iterate the workperiods object key. JSbin

let data =  {"workPeriods":[

{"user_id":"9345bf", "startDate":"2018-02-05T05:00:00.000Z", "endDate":"2018-02-09T05:00:00.000Z"},

{"user_id":"80c3a9", "startDate":"2018-02-12T05:00:00.000Z", "endDate":"2018-02-16T05:00:00.000Z"},

{"user_id":"35jh87", "startDate":"2018-02-19T05:00:00.000Z", "endDate":"2018-02-23T05:00:00.000Z"}

]}
let result = ['Work days']

data.workPeriods.forEach((time) =>{
  result.push(moment(time.startDate).format('MM/DD'))
})

console.log(result)
Jason Allshorn
  • 1,625
  • 1
  • 18
  • 27