Getting exact format with momentjs and lodash - javascript

I have a list of times slots here eg:
const times = [
{ start: '2020-07-09T08:00:00.000+02:00', end: '2020-07-09T09:30:00.000+02:00' },
{ start: '2020-07-09T08:30:00.000+02:00', end: '2020-07-09T10:00:00.000+02:00' },
]
While I'm trying to sort them by day using momentjs and lodash to get something like:
{
{startTime: '08:00', endTime: '08:30'}
{startTime: '08:30', endTime: '09:00'}
{startTime: '08:00', endTime: '08:30'}
{startTime: '08:30', endTime: '09:00'}
}
and I ended up with this solution for now:
const groupedAndFormatted = groupBy(times, date => moment(date.start_time).startOf('day').format('MMM Do YY'))
But this one didn't really give me the correct solution, any ideas?

First you need to sort them into the correct order, we will do this with sort and unix time stamps.
Then group them with the dddd
const times = [
{ start_time: '2020-07-09T08:00:00.000+02:00', endTime: '2020-07-09T09:30:00.000+02:00' },
{ start_time: '2020-07-09T08:30:00.000+02:00', endTime: '2020-07-09T10:00:00.000+02:00' },
{ start_time: '2020-07-07T09:00:00.000+02:00', endTime: '2020-07-07T10:30:00.000+02:00' }
];
const sorted_times = times.sort((a,b) => moment(a.start_time).unix() - moment(b.start_time).unix())
const grouped = _.groupBy(sorted_times, date => moment(date.start_time).format("dddd"))
const formatted = _.mapValues(grouped, dates => {
return dates.map(times => {
return {
start_time: moment(times.start_time).format("hh:mma"),
endTime: moment(times.endTime).format("hh:mma"),
}
})
})
However this will not work if you end up having multiple tuesdays on different dates.

Related

Calendar date ordering: Occupation overview

I'm working on a calendar plugin that would show my occupation overview ordered as following:
Now I've got the event on the calendar with moment.js, so they are formatted like this (sorted on Start date):
let events = [{
name: 'Event A',
start: '01-11-2021 00:00',
end: '08-11-2021 00:00',
},{
name: 'Event C',
start: '03-11-2021 00:00',
end: '06-11-2021 00:00',
},{
name: 'Event E',
start: '05-11-2021 00:00',
end: '08-11-2021 00:00',
},{
name: 'Event D',
start: '07-11-2021 00:00',
end: '12-11-2021 00:00',
},{
name: 'Event B',
start: '10-11-2021 00:00',
end: '17-11-2021 00:00',
},]
Expected occupationOverview array would be something like:
let ooArray = [
{ // Longest/bottom bar
start: '01-11-2021 00:00',
end: '17-11-2021 00:00',
group: 1
},
{ // Middle bar 1
start: '03-11-2021 00:00',
end: '08-11-2021 00:00',
group: 2
},
{ // Middle bar 2
start: '10-11-2021 00:00',
end: '12-11-2021 00:00',
group: 2
},
{ // Top bar 1
start: '05-11-2021 00:00',
end: '06-11-2021 00:00',
group: 3
},
{ // Top bar 2
start: '07-11-2021 00:00',
end: '08-11-2021 00:00',
group: 3
},]
I have honestly no clue how to group the calendar events so they give back an array with the start and end times as resulted in the red box.
Anybody that can help me figure this out?
Thanks!
Based on the updated question, sort all the dates keeping the attribute for start and end. Process them in order so that the first date (that must be a start) starts level 1, which is single booked.
If the next date is an end, that ends the bar. However, if the next date is a start, that increases the level (i.e. double booked). The following is an implementation, you might want to sort the bars by level or start date.
The function below firstly gets all the dates sorted in ascending order with their type - start or end. It then processes each date - start dates create a new bar, end dates end the most recent bar. When ended, the last bar is popped off starts and added to bars, which is an array of finished bars.
This depends on the source data being valid, i.e. it must start with a start and end with an end, they must be in the right order and of equal number.
One enhancement would be to ensure that where a start and end have the same date, the start is always sorted before the end so zero length events (milestones?) don't get ordered end-start, which would cause the level to be decremented before it's incremented. There may be other issues with starts and ends that have the same date and time, please test.
// Parse date in D-M-Y H:m format
function parseDMY(s) {
let [D, M, Y, H, m] = s.split(/\D/);
return new Date(Y, M - 1, D, H, m);
}
// Format as DD-MM-YYYY HH:mm
function formatDate(d) {
let z = n => ('0'+n).slice(-2);
return `${z(d.getDate())}-${z(d.getMonth()+1)}-${d.getFullYear()} ` +
`${z(d.getHours())}:${z(d.getMinutes())}`;
}
// Generates "occupation" bars
function calcBookingLevels(events) {
// Get sorted array of [{type, Date}]
let dates = events.reduce( (dates, event) => {
dates.push({type: 'start', date: parseDMY(event.start)},
{type: 'end', date: parseDMY(event.end)});
return dates;
}, []).sort((a, b) => a.date - b.date);
// Process dates to get occupation bars with levels
let bars = [];
let starts = [];
let level = 0;
dates.forEach(date => {
// If it's a start, start a new bar
if (date.type == 'start') {
let bar = {level: ++level, start: formatDate(date.date)};
starts.push(bar);
// Otherwise it's an end, close the most recent bar and
// move to bars array
} else {
let t = starts.pop();
t.end = formatDate(date.date);
--level;
bars.push(t);
}
})
return bars;
}
// Sample data
let events = [{
name: 'Event A',
start: '01-11-2021 00:00',
end: '08-11-2021 00:00',
},{
name: 'Event C',
start: '03-11-2021 00:00',
end: '06-11-2021 00:00',
},{
name: 'Event E',
start: '05-11-2021 00:00',
end: '08-11-2021 00:00',
},{
name: 'Event D',
start: '07-11-2021 00:00',
end: '12-11-2021 00:00',
},{
name: 'Event B',
start: '10-11-2021 00:00',
end: '17-11-2021 00:00',
},];
// Run it...
console.log(calcBookingLevels(events));
If the dates were in YYYY-MM-DD HH:mm format then they could be sorted as strings without converting to Dates.
Looks like you need to get the diff in milliseconds, and sort from greatest to least. You'll have to play with this to see which way it sorts (might need to flip the 'a' and 'b' on the diff statement, as I didn't run it against your array)
const sortedArray = array.sort((a, b) => a.diff(b))
With the help of #RobG I figured it out. Here is my answer for interested people:
// Not needed for answer, but result gave warnings so yeah
moment.suppressDeprecationWarnings = true;
let newItems = []
let dateArray = []
let events = [{
name: 'Event A',
start: '01-11-2021 00:00',
end: '08-11-2021 00:00',
},{
name: 'Event C',
start: '03-11-2021 00:00',
end: '06-11-2021 00:00',
},{
name: 'Event E',
start: '05-11-2021 00:00',
end: '08-11-2021 00:00',
},{
name: 'Event D',
start: '07-11-2021 00:00',
end: '12-11-2021 00:00',
},{
name: 'Event B',
start: '10-11-2021 00:00',
end: '17-11-2021 00:00',
},]
// Make one big array with all start and end dates
events.forEach(event=> {
dateArray.push({
type: 'start',
date: event.start
}, {
type: 'end',
date: event.end
})
})
// Sort the dates from first to last
dateArray = dateArray.sort((left, right) => {
return moment(left.date).diff(moment(right.date))
})
let groupID = -1
// Loop through the array with all dates and add them to a group
// based on if the current date is a start or end
for (let i = 0; i < dateArray.length; i++) {
if (dateArray[i].type === 'start') groupID++
else groupID--
for (let ii = 0; ii <= groupID; ii++) {
if (dateArray[i + 1] &&
!moment(dateArray[i].date).isSame(dateArray[i + 1].date)) {
newItems.push({
group: 1,
start: dateArray[i].date,
end: dateArray[i + 1].date,
subgroup: 'sg_' + ii,
subgroupOrder: ii
})
}
}
}
console.log(newItems)
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js"></script>

How to properly map data?

I'm working on a calendar app, which has an Agenda from "react-native-calendars". To display dates it needs the following:
items={{'2012-05-22': [{name: 'item 1 - any js object'}], ...}}
In my firebase backend I'm storing the start and end day of an appointment and also an array, which includes every day of it (so if the appointment started yesterday and ends tomorrow, the array stores yesterday, today and tomorrow as dates. The amount of dates in the array varies!)
The api data structure is the following:
apiData = [
{data: {allDays: ["2021-08-18", "2021-08-19", "2021-08-20"],
start: "2021-08-18",
end: "2021-08-20"
},
id: "string"},
{data: {allDays: ["2021-08-20", "2021-08-21", "2021-08-22", "2021-08-23"],
start: "2021-08-20",
end: "2021-08-23"
},
id: "string"},
//more Data
]
I got the following code to map the documents:
let markedDayAll = {};
{apiData.map(({data: {start, end, allDays}}) => (
markedDayAll[alldays] = [{
start: start,
end: end
}]
))};
which outputs in the correct "items" format, but right now, obviously, it is displayed like this:
items={{
'2021-08-18, 2021-08-19, 2021-08-20': [{start: "2021-08-18, end: "2021-08-20}]
}}
But I need the following:
items={{
'2021-08-18': [{start: "2021-08-18, end: "2021-08-20}],
'2021-08-19': [{start: "2021-08-18, end: "2021-08-20}],
'2021-08-20': [{start: "2021-08-18, end: "2021-08-20}]
}}
How do I properly map the data, so I get the desired result?
You can use array#reduce to iterate through each object and then iterate through each date in allDays and generate your object.
const apiData = [ {data: {allDays: ["2021-08-18", "2021-08-19", "2021-08-20"], start: "2021-08-18", end: "2021-08-20" }, id: "string"}, {data: {allDays: ["2021-08-20", "2021-08-21", "2021-08-22", "2021-08-23"], start: "2021-08-20", end: "2021-08-23" }, id:"string"} ],
result = apiData.reduce((r, {data}) => {
data.allDays.forEach(date => {
r[date] ??= [];
r[date].push({start: data.start, end: data.end});
});
return r;
},{});
console.log(result);

Get common data from multiple arrays of object that containing a unique id and nested array using javascript es6

I am trying to find the common data from multiple arrays of object that contains a unique id (mongoose object id) and an array of string.
for example:
array1 = [{
_id: "60f027f98b55eb2df1f36c04",
date: "2021-07-15T12:18:12.223Z",
time: "30",
hours: ["8:00 AM", "8:30 AM"]
}]
array2 = [{
_id: "60f027f98b55eb2df1f36c05",
date: "2021-07-15T12:18:12.223Z",
time: "60",
hours: ["7:30 AM", "8:30 AM", "9:30AM"]
}]
array3 = [{
_id: "60f027f98b55eb2df1f36c06",
date: "2021-07-16T12:12:12.223Z",
time: "30",
hours: ["7:00 AM", "8:30 AM"]
}]
The output should have maximum common values in the arrays for maximum common dates and that date should have maximum common hour.
So the sample output should look something like this.
common_data = {
date: "2021-07-15T12:18:12.223Z",
time: "30",
hours: "8:30AM"
}
I looked up at other answers and tried something like this:
merged all the arrays and
let result = merged_slots_array.shift().filter(function(v) {
return merged_slots_array.every(function(a) {
const matchDate = a.date === v.date;
const getMatchTime = a.hours.shift().filter(function(x) {
return v.hours.every(function(t) {
return x.indexOf(t) !== -1;
})
});
return matchDate && getMatchTime
});
});
but getting error merged_slots_array.shift(...).filter is not a function
After concatenating the arrays, finding the max common hour can be done through a filter that only keeps duplicates, then gets sorted. Once we have that, we can query each array to make sure it contains the max hour, then extract the max date and time. My output was slightly different than yours because i filtered for the max time, hour and date
array1 = [{
_id: "60f027f98b55eb2df1f36c04",
date: "2021-07-15T12:18:12.223Z",
time: "30",
hours: ["8:00 AM", "8:30 AM"]
}]
array2 = [{
_id: "60f027f98b55eb2df1f36c05",
date: "2021-07-15T12:18:12.223Z",
time: "60",
hours: ["7:30 AM", "8:30 AM", "9:30AM"]
}]
array3 = [{
_id: "60f027f98b55eb2df1f36c06",
date: "2021-07-16T12:12:12.223Z",
time: "30",
hours: ["7:00 AM", "8:30 AM"]
}]
const getCommon = (arrays) => {
let group = [].concat.apply([], [...arrays])
let hour = group.map(e=>e.hours).flat().filter((e,i,a) => a.indexOf(e) !== i).sort((a,b) => a.localeCompare(b))[0]
let common = group.filter(e=>e.hours.includes(hour))
let time = Math.max(...common.map(e => +e.time))
let date = common.map(e => e.date).sort((a,b) => new Date(b) - new Date(a))[0];
return {date: date, time: time, hours: [hour]}
}
let arg = []
arg.push(array1)
arg.push(array2)
arg.push(array3)
console.log(getCommon(arg))
TS Playground

How to use diff method in Luxon

I currently get a date from calendar control and using luxon I add days, minutes to it and change it to LongHours format like below:
newValue : is value i get from frontend(calendar control)
let formattedDate: any;
FormattedDate = DateTime.fromJSDate(new Date(newValue)).plus({ days: 1, hours: 3, minutes: 13, seconds: 10 }).toLocaleString(DateTime.DATETIME_HUGE_WITH_SECONDS)
console.log(formattedDate);
const formattedDateParsed = DateTime.fromJSDate(new Date(formattedDate));
const newValueParsed = DateTime.fromJSDate(new Date(newValue));
var diffInMonths = formattedDateParsed.diff(newValueParsed, ['months', 'days', 'hours', 'minutes', 'seconds']);
diffInMonths.toObject(); //=> { months: 1 }
console.log(diffInMonths.toObject());
Currently the formattedDateParsed is coming as 'Null'
Can I get some help as how to parse the date so that diff can be calculated
A few things going on here.
First, FormattedDate and formattedDate are different variables, so formattedDate isn't being set:
let formattedDate: any;
FormattedDate = DateTime.fromJSDate(new Date(newValue)).plus({ days: 1, hours: 3, minutes: 13, seconds: 10 }).toLocaleString(DateTime.DATETIME_HUGE_WITH_SECONDS)
console.log(formattedDate);
Second, you are converting to a string and then back into a DateTime, using the Date constructor as a parser, which isn't a great idea because a) it's unnecessary, and b) browsers aren't super consistent about which strings they can parse.
Instead, let's just convert once:
const newValueParsed = DateTime.fromJSDate(new Date(newValue));
const laterDate = newValueParsed.plus({ days: 1, hours: 3, minutes: 13, seconds: 10 });
const diffInMonths = laterDate.diff(newValueParsed, ['months', 'days', 'hours', 'minutes', 'seconds']);
diffInMonths.toObject(); // => {months: 0, days: 1, hours: 3, minutes: 13, seconds: 10}

How to use Moment.JS to check whether a timestamp is between 2 other timestamps

I have the following JSON array (note these are only the 5th and 6th elements of the array):
[
{
Day: 'Mon',
EndTime: '{ts \'1970-01-01 18:00:00\'}',
StartTime: '{ts \'1970-01-01 16:30:00\'}',
courseName: 'Computer Science 250: Introduction to Website Design',
Credits: '4'
},
{
Day: 'Mon',
EndTime: '{ts \'1970-01-01 18:30:00\'}',
StartTime: '{ts \'1970-01-01 17:30:00\'}',
courseName: 'Math 220: Differential Equations',
Credits: '3'
}
]
The data in the array is sorted by the values of the 'EndTime'. When I try to check whether the end time of the object at i - 1 (18:00:00) is between the start time and end time of the next object (at i which if 17:30:00 to 18:30:00) I should get true, but instead the isBetween method returns false.
How can I fix this, I know I am making some kind of simple mistake?
Here is my code:
for(let i = 1; i < monday.length-1; i++) {
const year = '1970-01-01';
const format = 'DD/MM/YYYY hh:mm:ss a';
var next_endtime = monday[i].EndTime.substr(16, 8);
var next_starttime = monday[i].StartTime.substr(16, 8);
var prev_endtime = monday[i-1].EndTime.substr(16, 8);
var plesson_e = moment(year + 'T' + prev_endtime, format),
nlesson_start = moment(year + 'T' + next_starttime, format),
nlesson_end = moment(year + 'T' + next_endtime, format);
var testbool = moment(plesson_e).isBetween(nlesson_start, nlesson_end, 'time');
console.log(testbool);
}
The string you pass to moment does not match your specified format :
const year = '1970-01-01'; // => see the '-', and the year is first
const format = 'DD/MM/YYYY hh:mm:ss a'; // => you put '/' and day first
Try changing the format value to:
const format = 'YYYY-MM-DD hh:mm:ss a';
See this fiddle
There is something wrong with the datetime or format, which you pass to moment JS, it works for me:
const monday = [{
Day: 'Mon',
EndTime: '{ts \'1970-01-01 18:00:00\'}',
StartTime: '{ts \'1970-01-01 16:30:00\'}',
courseName: 'Computer Science 250: Introduction to Website Design',
Credits: '4'
},
{
Day: 'Mon',
EndTime: '{ts \'1970-01-01 18:30:00\'}',
StartTime: '{ts \'1970-01-01 17:30:00\'}',
courseName: 'Math 220: Differential Equations',
Credits: '3'
}
]
const format = 'hh:mm:ss a';
var next_endtime = monday[1].EndTime.substr(16, 8);
var next_starttime = monday[1].StartTime.substr(16, 8);
var prev_endtime = monday[0].EndTime.substr(16, 8);
var plesson_e = moment(prev_endtime, format),
nlesson_start = moment(next_starttime, format),
nlesson_end = moment(next_endtime, format);
var testbool = moment(plesson_e).isBetween(nlesson_start, nlesson_end, 'time');
console.log(next_endtime, next_starttime, prev_endtime);
console.log(nlesson_end, nlesson_start, plesson_e);
console.log(testbool);
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment.js"></script>

Categories