I have a list of variable dates that I would like to group together, preferably in javascript.
ie.
2014-08-12
2014-08-10
2014-07-28
2014-07-27
2014-01-27
2013-04-27
2003-02-12
This list of days can be completely dynamic, but here is an example resultset.
Can anyone think of an elegant way to group dates that are considered to be 'near' each other together, which in this case would be:
2014-08-12
2014-08-10
2014-07-28
2014-07-27
2014-01-27
2013-04-27
2003-02-12
A way to do this is:
Convert all date strings to numbers using .getTime()
Sort
Group
Convert back to date string
An example of this:
var dates = ['2014-08-12', '2014-08-10', '2014-07-28', '2014-07-27', '2014-01-27', '2013-04-27', '2003-02-12'],
groups = [],
last = 0
dates.map(function (each) {
return new Date(each).getTime() // 1.
}).sort(function (a, b) {
return b-a // 2.
}).forEach(function (each) {
if (Math.abs(each-last) > 1e10) { // 1e10 ms = 116 days
groups.push([]) // 3.
}
groups[groups.length-1].push(each)
last = each
})
groups.map(function (dates) {
return dates.map(function (each) {
return new Date(each).toISOString().substr(0, 10) // 4.
}).join('\n')
}).join('\n\n')
You can adjust the 1e10 value to whatever nearby dates means to your application
Related
I want to check whether value : 30-08-2019 is present in array :['30-08-2019','25-09-2019','03-12-2019'].
This I could achieve by comparing the dates.
function isInArray(array, value): boolean {
return !!array.find(item => { return item.getTime() == value.getTime() });
}
But if my input date is 31-08-2019,then also the output should be true.
But if it is 31-08-2020, then the output should be false.
So, The check should be on the month and year and not the day.
Any help would be appreciated.
If you want to just check the month and the year, and if you can be sure that there are leading zeroes in all dates, you can simply use a substring of the date; cut off the initial three characters, which are the day and the hyphen.
If the leading zero might be omitted, you can split the string into components on the hyphen, and compare the second and third element of the resulting array instead.
You could for example do something along these lines:
let array = ["30-08-2019", "25-09-2019", "03-12-2019"];
const isMatch = (date) => array.some((e) => e.match(date.slice(2)));
console.log(isMatch("30-08-2019")); // true
console.log(isMatch("31-08-2019")); // true
console.log(isMatch("31-08-2020")); // false
You can compare month and year like that:
function isInArray(array, value): boolean {
return !!array.find(item => { return ((item.getMonth() == value.getMonth())&&( item.getYear() == value.getYear())) });
}
Return a string based on pulled dom elements
I have an object storing months and their index (not dates) monthList = {"jan" : "1", "feb" : "2". etc: etc}
The user can type something like jan or jan,feb,march and I want a string returned as 1 or 1,2,3 (which I use for a get call elsewhere) but haven't been able to do this, ive tried using all sorts of crazy loops to no avail, from incorrect reading to always reading last index, if the user inputs 1,2,3 that should also work.
The input values as simply called using ${document.getElementById('monthInput').value}
User Input Example #1: jan,feb,dec
User Input Output #1: 1,2,12
User Input Example #2: 1,2,5
User Input Output #2: 1,2,5
How can I do this?
I admit to not understanding why you would accept user input in such odd formats. The only way to transform user input that matches no specific syntax/format is to use a manually created matrix.
In the inputMap below you would need to list each user input and the string value that it should be translated to:
const inputMap = {
"jan": "1",
"feb": "2",
"march": "3",
1: "1",
2: "2",
3: "3",
"dec": 12,
5: "5"
}
const input1 = "jan,feb,dec"
const input2 = "1,2,5"
const mapInput = inputString => inputString.split(",").map(i => inputMap[i]).join(",")
console.log(mapInput(input1))
console.log(mapInput(input2))
According to this answer, you can recursively use Date to accomplish this, given input months:
months.split(",").map((month) => {
return (isNaN(month) ? new Date(Date.parse(month +" 1, 2019")).getMonth()+1 : month)
}).join(",")
This function iterates over each code/number string using map, checks whether or not the a given string is not number using isNaN() in a ternary operator, and accordingly returns the given number/converted code.
You can do this a few ways:
Using a simple for..of loop
Using .replace() (keeps the formatting of original string)
Using a mapping method (using: .map)
Going overboard with recursion + ternaries...
Using Loops:
const months = {jan:"1",feb:"2",mar:"3",apr:"4",may:"5",jun:"6",jul:"7",aug:"8",sep:"9",oct:"10",nov:"11",dec:"12"};
const input = "jan,dec,feb";
const dates = input.split(','); // turn your input into an array
let converted = "";
for(let month of dates) { // loop through each month in dates
if(month in months) { // check if the month is a key in months
converted += months[month] +','; // add number version to converted sring
} else { // if the month isn't in the converted month object, then no need to convert it
converted += month+','; // add month to (ie the number) to the converted output
}
}
console.log(converted.slice(0, -1)); // using slice(0, -1) to remove trailing comma
Using .replace() to keep original formatting:
const months = {jan:"1",feb:"2",mar:"3",apr:"4",may:"5",jun:"6",jul:"7",aug:"8",sep:"9",oct:"10",nov:"11",dec:"12"};
let input = "jan, dec, feb, 5";
const dates = input.split(','); // turn your input into an array
for(let month of dates) {
month = month.trim();
if(month in months) {
input = input.replace(month, months[month]);
}
}
console.log(input);
Using map. Here the inner arrow function is called for each month, and then converted to its associated value in the months object. We then use .join(',') to join the array of values:
const months = {jan:"1",feb:"2",mar:"3",apr:"4",may:"5",jun:"6",jul:"7",aug:"8",sep:"9",oct:"10",nov:"11",dec:"12"};
const input = "jan,dec,feb";
const converted = input.split(',')
.map((month) => months[month] || month)
.join(',');
console.log(converted);
Using recursion with the ternary operator:
const months={jan:"1",feb:"2",mar:"3",apr:"4",may:"5",jun:"6",jul:"7",aug:"8",sep:"9",oct:"10",nov:"11",dec:"12"};
const input = "jan,dec,feb";
const res = (f = ([m, ...rest]) => m && m in months ? months[m]+','+f(rest) : m ? m+','+f(rest) : '')(input.split(',')).slice(0,-1);
console.log(res);
In the column where the hours/minutes are stored for some of the business facilities time stamp(s) are presented in this format 0000-0000. The first two digits represent hours and the other two minutes. Here is example of some business hours:
0700-2300 M-F 0700-1700 S&S
0600-2200
0600-2300 Mon-Fri 0700-2200 Sat&Sun
Local 1 0000-2359 Local 2 0630-2230
0700-2100
0600-2345
The original solution that I had was to convert the values in JavaScript and that it was pretty simple. The problem I have is when there is more than one set of time hours/minutes in the string. In the example above that is the case where hours/minutes are different during the weekend or for the different locations. The JS code example is here:
var time = calcDifference("0600-2345");
function calcDifference(time) {
var arr = time.split('-').map(function(str) {
var hours = parseInt(str.substr(0, 2), 10),
minutes = parseInt(str.substr(2, 4), 10);
var result = (hours * 60 + minutes) / 60;
return result.toFixed(2);
});
return arr[1] - arr[0];
}
console.log(time);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
The code above works just fine if I pass the argument with one time stamp. I'm wondering how to handle situation where I have two time stamps? What would be a good solution to search if string has more than one hours/minutes values and then convert each of them and return to the user.
Assuming the HHMM-HHMM format is consistent in the input, and you don't care about discarding the remaining information in the string, regex is probably the simplest approach (and much safer than your current approach of splitting on hyphens, which might easily occur in other parts of the string you don't care about.)
Note that you won't be able to distinguish between "weekend" and "weekday" times, because that information isn't in a consistent format in your input. (This looks like human input, which pretty much guarantees that your HHMM-HHMM format also won't be strictly consistent; consider allowing for optional whitespace around the hyphen for example, and logging strings which show no match so you can check them manually.)
var testinputs = [
"0700-2300 M-F 0700-1700 S&S",
"0600-2200",
"0600-2300 Mon-Fri 0700-2200 Sat&Sun",
"Local 1 0000-2359 Local 2 0630-2230",
"0700-2100",
"0600-2345"
]
var reg = /(\d\d)(\d\d)\-(\d\d)(\d\d)/g; // \d means any digit 0-9; \- matches a literal "-", parens capture the group for easier access later
for (input of testinputs) {
console.log("Input: ", input)
var matches;
while ((matches = reg.exec(input)) !== null) { // loop through every matching instance in the string
// matches[0] is the full HHMM-HHMM string; the remainder is
// the HH and MM for each parenthetical in the regexp:
console.log(matches)
}
}
There are plenty of ways to do this ( based on your point of view ), but this is my favourite one. you can manipulate the text then pass numbers individually.
var date = '0700-2300 M-F 0700-1700 S&S'.match( /[-\d]+/gi ).filter( e => ~e.search( /\d+/gi ) )
now you have an array of multiple timestamps saved on your database and you pass them individually to your function.
date.forEach( each => calcDifference( each ) );
You can use a regex like /\d{4}\-\d{4}/g to extract all of the digits from the string and map them to their time differences or replace text in the original.
const calcDifference = range => {
const time = range.split`-`
.map(e => (+e.substr(0, 2) * 60 + (+e.substr(2))) / 60)
return time[1] - time[0];
};
const diffs = `0700-2300 M-F 0700-1700 S&S
0600-2200
0600-2300 Mon-Fri 0700-2200 Sat&Sun
Local 1 0000-2359 Local 2 0630-2230
0700-2100
0600-2345`.replace(/\d{4}\-\d{4}/g, calcDifference);
console.log(diffs);
the title may be a bit confusing but I'll explain it in detail. I have a table in UI and user can choose date ranges from there like;
monday - {in:"13:00:00",out:"13:59:59"}
tuesday - [{in:"13:00:00",out:"13:59:59"},{in:"14:00:00",out:"14:59:59"}]
user can only choose multiple hour intervals for one day. I already made the grouping the intervals according to their date and combining the intervals like
tuesday- [{in:"13:00:00",out:"14:59:59"},{in:"14:00:00",out:"14:59:59"}]
in the first iteration. But I couldn't figure out how to make it for more than 4 or 5 hour intervals.FYI I'm using lodash for sorting and grouping and moment for converting hours to int.
If user enters 5 intervals for tuesday like [{in:"13:00:00",out:"13:59:59"},{in:"14:00:00",out:"14:59:59"},{in:"15:00:00",out:"15:59:59"},{in:"18:00:00",out:"18:59:59"},{in:"19:00:00",out:"19:59:59"}]
I want ranges to be combined like ;
[{in:"13:00:00",out:"15:59:59"},{in:"18:00:00",out:"19:59:59"}]
Any help or suggestion will be appreciated.
Assuming that your input data is chronological then one way of implementing your reduced time table is this;
var timeSlices = [{in:"13:00:00",out:"13:59:59"},{in:"14:00:00",out:"14:59:59"},{in:"15:00:00",out:"15:59:59"},{in:"18:00:00",out:"18:59:59"},{in:"19:00:00",out:"19:59:59"}],
ts = new Date(),
te = new Date(),
reduced = timeSlices.reduce((p,c) => {p.length ? (ts.setHours(...p[p.length-1].out.split(":")),
te.setHours(...c.in.split(":")),
te-ts <= 1000 ? p[p.length-1].out = c.out
: p.push(c))
: p.push(c);
return p;},[]);
console.log(reduced);
However if the objects with in and out times are located arbitrary in the array then a more conceptual approach like first sorting them according to their in times would be essential. That wouldn't be a big deal though.
Assuming ranges are composed of Moment instances and you wanted to combine any two ranges where the end of one range either overlapped another range or was less than or equal to one second behind the start of another range, this function should be able to combine the ranges
function combineRanges (ranges) {
if (ranges.length <= 1) {
return ranges
}
ranges = ranges.sort(byStart)
var current = ranges[0]
var combined = [current]
for (var i = 1; i < ranges.length; i++) {
var next = ranges[i]
if (current.out.diff(next.in, 'seconds') > 1) {
combined.push(next)
current = next
} else if (current.out.isBefore(next.out)) {
current.out = next.out
}
}
return combined
}
function byStart (a, b) {
return a.in - b.in
}
So I have an array of objects, and I would like to sort them following these two rules (in order of priority):
The numbers must be in numerical order
The times must be in chronological order
So, I want the objects not only to be sorted by numbers, but to be also be sorted by time.
For example, this would be ok.
005: 2am
005: 3am
005: 4am
006: 2am
006: 3am
This is the structure of the part of the objects that interests us:
var notSortedData = {
number: number, // it's a string
scheduled_date: scheduled_date, // the format is "YYYY-MM-DD HH:MM:SS"
}
sortedTrains.push(notSortedData);
So, notSortedData is pushed in sortedTrains via a for loop. Then I do this, but it is not enough (as it doesn't respect my second condition):
// sorts all the numbers numerically
sortedTrains.sort(function(a, b) {
return parseInt(a.number) - parseInt(b.number);
});
What do I need to do to make sure that my second condition is also respected? Thanks!
You can try this:
sortedTrains.sort(function(a, b) {
// We parse the numbers
var num1 = parseInt(a.number), num2 = parseInt(b.number);
if (num1 != num2) return num1 - num2; // Return the difference IF they are not equal
var date1 = new Date(a.scheduled_date), date2 = new Date(b.scheduled_date);
// We only get here if the numbers are equal
return date1 - date2;
});
I would suggest to utilize a library such as underscore or lo-Dash, because later you might add more sort conditions or change them and using the library will greatly reduce the noisiness of your code and save you development effort. Assuming lo-Dash is used, the code will be as elegant as follows:
var sortedTrains = [
{ 'number': 5, 'scheduled_date': '2014-10-12 00:00:00'},
{ 'number': 5, 'scheduled_date': '2014-10-12 01:00:00' },
{ 'number': 5, 'scheduled_date': '2014-10-12 02:00:00'},
{ 'number': 6, 'scheduled_date': '2014-10-12 03:00:00' }
];
var result = _.sortBy(sortedTrains, ['number', 'scheduled_date']);