Related
I have an array that has a object with two properties, 'location' and 'needs'. The needs property has an array of objects with a date and count {date: "2021-06-15", count: 10} so an array would look like this:
{
"location": "NYC",
"needs": [
{
"date": "2021-04-06",
"count": 56
},
{
"date": "2021-04-13",
"count": 10
}
]
}
What I need to do is to use Momentjs to use today's date, figure out the two week period starting from today, and then map the needs-count to the date in the moment loop. If there is a date missing (like in the example below), it should put a 0 as the count
A final array would look like...
{
"location": "NYC",
"needs": [
{
"date": "2021-04-06",
"count": 56 // this had a count in the initial object
},
{
"date": "2021-04-07",
"count": 0
},
{
"date": "2021-04-08",
"count": 0
},
{
"date": "2021-04-09",
"count": 0
},
{
"date": "2021-04-10",
"count": 0
},
{
"date": "2021-04-11",
"count": 0
},
{
"date": "2021-04-12",
"count": 0
},
{
"date": "2021-04-13",
"count": 10 // this had a count in the initial object
},
...
...
...
]
}
In terms of a function, the closest I have got is
let startDay = moment().format('YYYY-MM-DD');
let endDay = moment().add(14, 'days').format('YYYY-MM-DD');
let startDate = moment(startDay);
let endDate = moment(endDay);
let datesBetween = [];
let startingMoment = startDate;
while(startingMoment <= endDate) {
for (let count = 0; count < 15; count ++) {
// at this point im trying to take 'week' which has the location property and needs property and trying to merge them together... but failed miserably.
if (week.needs[count].date === startingMoment) {
datesBetween.push([startingMoment.clone(), week.needs[count].count]);// clone to add new object
startingMoment.add(1, 'days');
} else {
datesBetween.push([startingMoment.clone(), 0]);// clone to add new object
}
}
}
Can someone see where I went so wrong?
const week = {
"location": "NYC",
"needs": [
{
"date": "2021-04-06",
"count": 56
},
{
"date": "2021-04-13",
"count": 10
}
]
}
let current = moment();
const allDates = [];
const FORMAT = 'YYYY-MM-DD';
for (let count = 0; count < 14; count++) {
const found = week.needs.find(i => i.date === current.format(FORMAT));
if (found) {
allDates.push(found);
} else {
allDates.push({
date: current.format(FORMAT),
count: 0,
});
}
current.add(1, 'day');
}
week.needs = allDates;
console.log(week);
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js" integrity="sha512-qTXRIMyZIFb8iQcfjXWCO8+M5Tbc38Qi5WzdPOYZHIlZpzBHG3L3by84BBBOiRGiEb7KKtAOAs5qYdUiZiQNNQ==" crossorigin="anonymous"></script>
You could do something like this :
let dates = {
"location": "NYC",
"needs": [
{
"date": "2021-04-06",
"count": 56
},
{
"date": "2021-04-13",
"count": 10
}
]
};
let day = moment();
for( let i=0; i< 15; i++){
let date = day.add(1, "days").format("YYYY-MM-DD");
console.log(`Looking if ${date} is in array...`)
if(dates.needs.find(obj => obj.date === date)) continue;
console.log(`Adding ${date}`)
dates.needs.push({ date, count : 0 })
}
dates.needs.sort( (a,b) => a.date > b.date ? 1: -1 );
console.log(dates)
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js"></script>
So I have the data in below format
const data = [
{ date: '01-07-2019' },
{ date: '02-07-2019' },
{ date: '03-07-2019' },
{ date: '04-07-2019' },
{ date: '05-07-2019' },
{ date: '06-07-2019' },
{ date: '07-07-2019' },
{ date: '08-07-2019' },
{ date: '09-07-2019' },
{ date: '10-07-2019' },
{ date: '15-07-2019' },
{ date: '16-07-2019' },
{ date: '20-07-2019' },
{ date: '21-07-2019' },
{ date: '22-07-2019' },
{ date: '23-07-2019' }
]
So I have to count the regular interval dates. For example on date { date: '10-07-2019' }, { date: '20-07-2019' } and on { date: '23-07-2019' } it breaks so count should be again started with 1.
const ouput = [{
startDate: '01-07-2019',
endDate: '10-07-2019',
count: 10
}, {
startDate: '15-07-2019',
endDate: '16-07-2019',
count: 2
}, {
startDate: '20-07-2019',
endDate: '23-07-2019',
count: 4
}]
I did that
const output = Object.values(data.reduce((a, { startDate, endDate }, i) => {
const startTime = moment(data[i].date)
const endTime = moment(data[i + 1] && data[i + 1].date)
if (moment.duration(endTime.diff(startTime)).asDays === 1) {
a.startDate = startDate
a.startDate = endDate
}
a.count++;
return a;
}, {}));
But it is not giving what I expect. Please help.
I would do that with a function generator to handle the desired aggregation.
The below code will loop the dates, take a pair, check whether the start date exists, update the end date and automatically yield the value if necessary.
Comments are directly in the code below, the code assumes the initial array is already sorted as the example you mentioned.
As a side note, you're actually including the last date in the count, while, effectively, it should be one day less than your count. Further comments about that are available below in the function generator code.
const data = [
{ date: '01-07-2019' },
{ date: '02-07-2019' },
{ date: '03-07-2019' },
{ date: '04-07-2019' },
{ date: '05-07-2019' },
{ date: '06-07-2019' },
{ date: '07-07-2019' },
{ date: '08-07-2019' },
{ date: '09-07-2019' },
{ date: '10-07-2019' },
{ date: '15-07-2019' },
{ date: '16-07-2019' },
{ date: '20-07-2019' },
{ date: '21-07-2019' },
{ date: '22-07-2019' },
{ date: '23-07-2019' }
];
// Counts intervals of consecutive dates.
function* countIntervals(dates) {
// declare an initial accumulator.
let acc = {
count: 0
};
for (let i = 0; i < dates.length; i++) {
// get the currently looped value and the next one.
const [curr, next] = [moment(dates[i].date, 'DD-MM-YYYY'), dates[i+1] ? moment(dates[i+1].date, 'DD-MM-YYYY') : null];
// if the current date and next days are valid and if the difference in days between them is 1..
if (curr && next && (next.diff(curr, "days") === 1)) {
// Then keep track of the start date if not set, update the end date and increase the count of days.
acc.startDate = acc.startDate || dates[i].date, acc.endDate = dates[i+1].date, acc.count++;
}
else {
// otherwise, if the accumulator has a start date, yield the value.
if (acc && acc.startDate) {
acc.count++; // <-- comment this if you don't want the last date to be included.
yield Object.assign({}, acc);
// and init again the accumulator.
acc = {
count: 0
};
}
}
}
// if the loop is finished and the progression continued, yield the current accumulator.
if (acc.startDate) yield acc;
}
// usage...
const intervals = [...countIntervals(data)];
console.log(intervals);
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.min.js"></script>
Here you go, Try this
const data = [
{ date: "01-07-2019" },
{ date: "02-07-2019" },
{ date: "03-07-2019" },
{ date: "04-07-2019" },
{ date: "05-07-2019" },
{ date: "06-07-2019" },
{ date: "07-07-2019" },
{ date: "08-07-2019" },
{ date: "09-07-2019" },
{ date: "10-07-2019" },
{ date: "15-07-2019" },
{ date: "16-07-2019" },
{ date: "20-07-2019" },
{ date: "21-07-2019" },
{ date: "22-07-2019" },
{ date: "23-07-2019" }
];
function to parse date
function parseDate(input) {
var parts = input.split("-");
// new Date(year, month [, day [, hours[, minutes[, seconds[, ms]]]]])
return new Date(parts[2], parts[1] - 1, parts[0]); // Note: months are 0-based
}
function to get date difference
function dateDiff(date1, date2) {
date1 = parseDate(date1);
date2 = parseDate(date2);
let diffTime = Math.abs(date2.getTime() - date1.getTime());
let diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
return diffDays;
}
Required output
const output = data.reduce(function(resultSet, currentValue, currentIndex, arr) {
if (resultSet.length == 0) {
resultSet.push({
startDate: currentValue.date,
endDate: currentValue.date,
count: 1
});
}
else{
let dateDiffrence = dateDiff(resultSet[resultSet.length-1].endDate, currentValue.date);
console.log(dateDiffrence);
if(dateDiffrence == 1){
resultSet[resultSet.length-1].endDate = currentValue.date;
resultSet[resultSet.length-1].count++;
}else{
resultSet.push({
startDate: currentValue.date,
endDate: currentValue.date,
count: 1
});
}
}
return resultSet;
}, []);
Yet another possible solution.
const parseDate = (str) => {
const [d, m, y] = str.split('-');
return +new Date(y, m - 1, d)
}
const output = data.reduce((a, {
date
}, i) => {
const cur = parseDate(date);
const lastDate = data[i - 1] && data[i - 1].date || date;
const last = parseDate(lastDate || date);
if (cur - last > 1000 * 60 * 60 * 24) a.push({count: 0});
const {
startDate = date,
count
} = a.pop();
a.push({
startDate,
endDate: date,
count: count + 1
})
return a;
}, [{
count: 0
}])
console.log (output)
<script>
const data = [
{ date: '01-07-2019' },
{ date: '02-07-2019' },
{ date: '03-07-2019' },
{ date: '04-07-2019' },
{ date: '05-07-2019' },
{ date: '06-07-2019' },
{ date: '07-07-2019' },
{ date: '08-07-2019' },
{ date: '09-07-2019' },
{ date: '10-07-2019' },
{ date: '15-07-2019' },
{ date: '16-07-2019' },
{ date: '20-07-2019' },
{ date: '21-07-2019' },
{ date: '22-07-2019' },
{ date: '23-07-2019' }
]</script>
If you construct UTC dates there will be no need to use moment.js. With UTC every day is 24 hours and DST does not apply. This solution features a boilerplate function to handle the creation of the UTC date from your date string format.
const data = [
{ date: '01-07-2019' },
{ date: '02-07-2019' },
{ date: '03-07-2019' },
{ date: '04-07-2019' },
{ date: '05-07-2019' },
{ date: '06-07-2019' },
{ date: '07-07-2019' },
{ date: '08-07-2019' },
{ date: '09-07-2019' },
{ date: '10-07-2019' },
{ date: '15-07-2019' },
{ date: '16-07-2019' },
{ date: '20-07-2019' },
{ date: '21-07-2019' },
{ date: '22-07-2019' },
{ date: '23-07-2019' }
];
const ONE_DAY = 1000 * 60 * 60 * 24;
function dateStrToUTC(dateStr) {
const dateParts = dateStr.split('-');
const utcDate = new Date();
utcDate.setUTCFullYear(dateParts[2]);
utcDate.setUTCMonth(dateParts[1] - 1);
utcDate.setUTCDate(dateParts[0]);
utcDate.setUTCHours(0);
utcDate.setUTCMinutes(0);
utcDate.setUTCSeconds(0);
utcDate.setUTCMilliseconds(0);
return utcDate;
}
function getRegularIntervals(accumulator, currentValue) {
const index = accumulator.length - 1;
let daysPassed = 0;
if (index > -1) {
daysPassed = (dateStrToUTC(currentValue.date) - dateStrToUTC(accumulator[index].endDate)) / ONE_DAY;
}
if (index > -1 && 1 == daysPassed) {
accumulator[index].endDate = currentValue.date;
accumulator[index].count++;
} else {
accumulator.push({
startDate: currentValue.date,
endDate: currentValue.date,
count: 1
});
}
return accumulator;
}
const output = data.reduce(getRegularIntervals, []);
console.log(output);
Output as expected:
[
{
"startDate": "01-07-2019",
"endDate": "10-07-2019",
"count": 10
},
{
"startDate": "15-07-2019",
"endDate": "16-07-2019",
"count": 2
},
{
"startDate": "20-07-2019",
"endDate": "23-07-2019",
"count": 4
}
]
I liked your approach of using reduce function.
Going with the same, I just added some more logic in there and here is the final code.
// initially lets assume first date is the start as well as end date
var dateIntervalObject = {
startDate: data[0].date,
endDate: data[0].date,
count: 1
};
var result = data.reduce((resultArray, obj, i) => {
if(i > 0) {
var startTime = moment(dateIntervalObject.endDate, "DD-MM-YYYY");
var endTime = moment(obj.date, "DD-MM-YYYY");
if (endTime.diff(startTime, 'days') === 1) {
dateIntervalObject.endDate = obj.date;
dateIntervalObject.count += 1;
// remove the latest object in array, to replace with new
resultArray.pop();
} else {
dateIntervalObject = {
startDate: obj.date,
endDate: obj.date,
count: 1
};
}
// push the date Interval object in the array
resultArray.push(dateIntervalObject);
}
return resultArray;
}, [dateIntervalObject]);
console.log('result: ',result);
Note:
When initialState of the accumulator is passed to reduce function it starts iterating from 0th index, which in our case have already been initialized in the dateIntervalObject and therefore the first iteration with index value 0 is skipped.
Also, if the interval is not changing, we don't need to add another object to our result array but instead update the end date of the last element of our result array. Therefore, first pop and then push to just update the end date and count value.
Hope this helps!
I need to add events to the existing date so i think i need to change my json structure something like below. I tried to achieve it via reduce as shown below
I want to convert this array of object
"sunday": [
"...",
7,
14,
21,
28
]
To
{
"sunday":[
{
"7":[
"event1",
"event2",
"event3"
]
},
{
"14":[
"event3",
"event4",
"event5"
]
},
{
"21":[]
},
{
"28":[]
}
]
}
Here is the data that has event's detail,
{
"data": [
{ "date": "7",
"events": ["event1", "event2", "event3"]
},
{ "date": "14",
"events": ["event3", "event4", "event5"]
},
]
}
What i tried and failed,
attachEventsToTheDate(week_days) {
var answer = [week_days].reduce(function(result, item, index) {
var key = Object.keys(item)[0];
var value = item[key][index];
var obj = {};
obj[key] = [obj[key]];
console.log('obj is', JSON.stringify(obj));
// JSON.stringify(obj);
// result.push(obj);
return result;
}, {}); //an empty array
}
map it:
var sunday = [ 7, 14, 21, 28 ]
var data = [ { "date": "7", "events": ["event1", "event2", "event3"] }, { "date": "14", "events": ["event3", "event4", "event5"] }]
console.log(sunday.map(x => {
var actual = data.find(y => y.date == x);
if(actual) {
return {[x]: actual.events}
} else {
return {[x]: []}
}
}))
.as-console-wrapper { max-height: 100% !important; top: 0; }
The object structure you desire to have will not be very efficient to work with, but here it is:
function groupEvents(week_days, events) {
const numToDay = []; // Extra index into the week_days nested objects
// Create a new week_days object
week_days = Object.assign(...Object.keys(week_days).map( day =>
({ [day]: week_days[day].filter(Number).map(num =>
({ [num]: numToDay[num] = [] })
)})
));
// Inject the events data into it
for (const data of events.data) {
// Skip if this day does not exist in the original week_days structure
if (!numToDay[data.date]) continue;
numToDay[data.date].push(...data.events);
}
return week_days;
}
const week_days = {sunday: ["...", 7, 14, 21, 28]},
events = {data: [
{ date: "7", events: ["event1", "event2", "event3"] },
{ date: "14", events: ["event4", "event5"] }
]};
const res = groupEvents(week_days, events);
console.log(res);
.as-console-wrapper { max-height: 100% !important; top: 0; }
A more generic solution
The week_days object seems to be a translation of a certain month. If one has just the month, the week_days structure can be derived from it. So the key information is really your events object. If you can turn that object (as specified in your original question, i.e. with year and month info included) into something grouped by year-month combinations, and then produce for each combination a week_days kind of structure, you'll have all you need.
At the same time it seems more logical to use the day numbers as keys in the week_days nested objects.
Here is a demo on how you can do that:
const events = {
"data": [
{ "year": "2017", "month": "january", "date": "16",
"events": ["event1", "event2", "event3"]
},
{ "year": "2017", "month": "january", "date": "8",
"events": ["event4", "event5"]
},
]
};
function groupEvents(events) {
const dayNames = ['sunday', 'monday', 'tuesday', 'wednesday',
'thursday', 'friday', 'saturday'];
return events.data.reduce( (acc, o) => {
const key = o.month.toLowerCase() + " " + o.year;
if (!acc[key]) {
acc[key] = Object.assign(...dayNames.map(day => ({ [day]: {} })));
const dt = new Date("1 " + key);
const month = dt.getMonth();
while (dt.getMonth() === month) {
acc[key][dayNames[dt.getDay()]][dt.getDate()] = [];
dt.setDate(dt.getDate()+1);
}
}
const dt = new Date(o.date + " " + key);
acc[key][dayNames[dt.getDay()]][dt.getDate()].push(...o.events);
return acc;
}, {});
}
const res = groupEvents(events);
console.log(res);
.as-console-wrapper { max-height: 100% !important; top: 0; }
If now you want to have only the data of the month January 2017 (for example), you can just take it out of the result from the above script as follows:
const january2017_week_days = res["january 2017"];
I have javascript array with a teaching hour of a teacher in a day
{
"time": [
{ "start":"10:00","end":"11:00" },
{ "start":"12:00","end":"01:00" },
{ "start":"04:00","end":"06:00" }
]
}
I want to find out free hour of from above array between 10 AM to 6PM
Output array will like this :
{
"time": [
{ "start":"09:00","end":"10:00" },
{ "start":"11:00","end":"12:30" },
{ "start":"01:00","end":"04:00" }
]
}
please help me out to solve this
I have compared end of one object with the start of next object;
based on their difference I decide whether or not to include it in freetime.
let timeObj = {
"time": [
{ "start":"10:00","end":"11:00" },
{ "start":"12:00","end":"01:00" },
{ "start":"04:00","end":"06:00" }
]
}
let newObj = {"time" : []}
let timeArr= timeObj["time"];
for(i=0;i<timeArr.length; i++) {
if(timeArr[i+1] != undefined) {
if(timeArr[i]["end"] != timeArr[i+1]["start"]) {
let freetime = {
"start" : timeArr[i]["end"],
"end" : timeArr[i+1]["start"]
}
newObj["time"].push(freetime);
}
}
}
console.log(newObj)
Try this approach
var obj = {
"time": [{
"start": "10:00",
"end": "11:00"
},
{
"start": "12:00",
"end": "01:00"
},
{
"start": "04:00",
"end": "06:00"
}
]
};
var numConvert = (t) => (t = Number(t.replace(":", ".")), t += t < 10 ? 12 : 0, t);
var output = [];
var output = obj.time.map(s => [numConvert(s.start), numConvert(s.end)])
.reduce(function(a, b, i, arr) {
(i < arr.length - 1) ? (a || []).push([b[1], arr[i + 1][0]]): [];
return a;
}, []).map(s => ({
start: s[0],
end: s[1]
}));
console.log(output);
I have an array of dates containing a count value. e.g.
[
{
"date": "2014-11-11T08:00:00.000Z",
"count": 8
},
{
"date": "2014-11-13T08:00:00.000Z",
"count": 4
}
{
"date": "2014-11-16T08:00:00.000Z",
"count": 4
}
]
How do I fill in the missing dates with count = 0, to produce the following in javascript:
[
{
"date": "2014-11-11T08:00:00.000Z",
"count": 8
},
{
"date": "2014-11-12T08:00:00.000Z",
"count": 0
},
{
"date": "2014-11-13T08:00:00.000Z",
"count": 4
},
...
]
as you appear to be using momentjs
the first thing that came to mind was use the moment().add(number, units) and moment().diff(input, units, asFloat)
something like
var data = [
{
"date": "2014-11-11T08:00:00.000Z",
"count": 8
}, {
"date": "2014-11-16T08:00:00.000Z",
"count": 4
}
];
var startDate = moment(data[0].date);
var endDate = moment(data[1].date);
var days = endDate.diff(startDate, 'd', false);
alert(days);
for (var i = 1; i < days; i++) {
data.splice(i,0, {"date" : startDate.add(1, 'd').toISOString(), 'count': 0 })
}
for (var i = 0; i < data.length; i++) {
alert(data[i].date);
}
<script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.8.3/moment.min.js"></script>
Try this:
var arr = [
{
"date": "2014-11-11T08:00:00.000Z",
"count": 8
},
{
"date": "2014-11-16T08:00:00.000Z",
"count": 4
}
];
function fillDates(start, end) {
var output = [start];
var date = new Date(start.date);
var endDate = new Date(end.date);
do {
output.push({
"date": date.toISOString(),
"count": 0
});
date = new Date(date.getTime());
date.setDate(date.getDate() + 1);
} while (date < endDate);
output.push(end);
return output;
}
var start = arr[0];
var end = arr[1];
fillDates(start, end);
const models = [
{
date: '2018-10-17',
value: 3,
},
{
date: '2018-10-20',
value: 4,
},
{
date: '2018-10-21',
value: 5,
},
{
date: '2018-10-27',
value: 6,
},
];
const filledInDates = models.reduce((newArray, currentModel, index, originalArray) => {
const nextModel = originalArray[index + 1];
if (nextModel) {
const currentDate = moment(currentModel.date);
const daysBetween = moment(nextModel.date).diff(currentDate, 'days');
const fillerDates = Array.from({length: daysBetween - 1}, (value, dayIndex) => {
return {
value: currentModel.value,
date: moment(currentDate).add(dayIndex + 1, 'days').format('YYYY-MM-DD'),
};
});
newArray.push(currentModel, ...fillerDates);
} else {
newArray.push(currentModel);
}
return newArray;
}, []);
console.log(filledInDates);
Output:
[
{value:3, date:"2018-10-17"},
{value:3, date:"2018-10-18"},
{value:3, date:"2018-10-19"},
{value:4, date:"2018-10-20"},
{value:5, date:"2018-10-21"},
{value:5, date:"2018-10-22"},
{value:5, date:"2018-10-23"},
{value:5, date:"2018-10-24"},
{value:5, date:"2018-10-25"},
{value:5, date:"2018-10-26"},
{value:6, date:"2018-10-27"}
]