I am trying to validate a string the way it is done in Jira in Javascript. I'm trying to replicate how it is validated in Jira. I am guessing I could do this with Regex but I am not sure how.
A user can type a string in the format of "1d 6h 30m" which would mean 1 day, 6 hours, 30 minutes. I do not need the weeks for my use case. I want to show an error if the user uses an invalid character (anything except 'd','h','m', or ' '). Also the string must separate the time durations by spaces and ideally I would like to force the user to enter the time durations in descending order meaning '6h 1d' would be invalid because the days should come first. Also the user does not have to enter all information so '30m' would be valid.
This is my code for getting the days, hours and minutes which seems to work. I just need help with the validation part.
let time = '12h 21d 30m'; //example
let times = time.split(' ');
let days = 0;
let hours = 0;
let min = 0;
for(let i = 0; i < times.length; i++) {
if (times[i].includes('d')){
days = times[i].split('d')[0];
}
if (times[i].includes('h')){
hours = times[i].split('h')[0];
}
if (times[i].includes('m')){
min = times[i].split('m')[0];
}
}
console.log(days);
console.log(hours);
console.log(min);
const INPUT = "12h 21d 30s";
checkTimespanFormat(INPUT);
if (checkTimespanKeysOrder(INPUT, true))
console.log(`${INPUT} keys order is valid`);
else console.log(`${INPUT} keys order is NOT valid`);
//******************************************************************
/**
* Ensures that time keys are:
* - Preceeded by one or two digits
* - Separated by one or many spaces
*/
function checkTimespanFormat(timespanStr, maltiSpacesSeparation = false) {
// timespan items must be separated by 1 space
if (maltiSpacesSeparation) timespanStr = timespanStr.toLowerCase().split(" ");
// timespan items must be separated by one or many space
else timespanStr = timespanStr.toLowerCase().split(/ +/);
// timespan items must be formatted correctly
timespanStr.forEach((item) => {
if (!/^\d{1,2}[dhms]$/.test(item)) console.log("invalid", item);
else console.log("valid", item);
});
}
/**
* Validates:
* - Keys order
* - Duplicate keys
*/
function checkTimespanKeysOrder(timespanStr) {
const ORDER = ["d", "h", "m", "s"];
let timeKeysOrder = timespanStr
.replace(/[^dhms]/g, "") // Removing non time keys characters
.split("") // toArray
.map((char) => {
return ORDER.indexOf(char); // Getting the order of time keys
});
for (i = 0; i < timeKeysOrder.length - 1; i++)
if (timeKeysOrder.at(i) >= timeKeysOrder.at(i + 1)) return false;
return true;
}
Based on your comment, I have added a validation regex to be run first before running the match regex.
For validation, you want
/^(\d+[d]\s+)?(\d+[h]\s+)?(\d+[m]\s+)?(\d+[s]\s+|$)?/
For extracting values, you want
/([\d]+[dhms]\s+|$)/g
You can then use String.match with this regular expression, iterating through all the matches add adding time based on the time letter at the end
Here's my take at the problem:
match minutes ([1-5]?[\d])m, with eligible values in range [0,59]
match hours ([1]?[\d]|2[0-3])h , with eligible values in range [0,23]
match days ([1-9]|[1-9][\d])d, with eligible values in range [1,99]
Then we can encapsulate regex for days in hours and regex for hours in minutes to make sure that we have formats like {dd}d {hh}h {mm}m, {hh}h {mm}m, {mm}m:
"(((([1-9]|[1-9][\d])d )?([1]?[\d]|2[0-3])h )*([1-5]?[\d])m)"
Corner cases include inputs with zeros like 00m, 00d 00m, 01h 00d 00m. In order to reject the former two and accept the last one, we can negate the values 00m and 00d when the preceeding patterns are not found.
Final regex to use:
"(?!0m)(((?!0h)(([1-9]|[1-9][\d])d )?([1]?[\d]|2[0-3])h )*([1-5]?[\d])m)"
Check for:
days in Group 4
hours in Group 5
minutes in Group 6
Tested at https://regex101.com.
Does it solve your problem?
Related
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);
I am using Math.random to create a unique value.
However , it looks like after some days , if i run the same script it produces the same value that created earlier.
Is there any way to create unique value every time when ever i run the script.
Below is my code for the random method.
var RandomNo = function (Min,Max){
return Math.floor(Math.random() * (Max - Min + 1)) + Min;
}
module.exports = RandomNo;
The best way to achieve a unique value is to use Date() as milliseconds. This increasing time representation will never repeat.
Do it this way:
var RamdomNo = new Date().getTime();
Done.
Edit
If you are bound to length restrictions, the solution above won't help you as repetition is predictable using an increasing number the shorter it gets.
Then I'd suggest the following approach:
// turn Integer into String. String length = 36
function dec2string (dec) {
return ('0' + dec.toString(36)).substr(-2);
}
// generate a 20 * 2 characters long random string
function generateId () {
var arr = new Uint8Array(20);
window.crypto.getRandomValues(arr);
// return 5 characters of this string starting from position 8.
// here one can increase the quality of randomness by varying
// the position (currently static 8) by another random number <= 35
return Array.from(arr, this.dec2string).join('').substr(8,5);
}
// Test
console.log(generateId());
This pair of methods generates a 40 characters long random string consisting of letters and digits. Then you pick a sequence of 5 consecutive characters off it.
I get a date range from some API (not in my control) which has the following possibilities of date format
dd-Mmm-yy
dd-Mmm-yyyy
dd/mm/yy
dd/mm/yyyy
mm/dd/yy
mm/dd/yyyy
yyyy-mm-dd
So I get the date range as
01-Dec-16-06-Dec-16 (as per dd-Mmm-yy) OR
01/12/16-06/12/16 (as per dd/mm/yy)
So hyphen (-) is the from/to date separator which the API uses (not in my control) & I get this single combined value
Now, in my code, I want to get the from & to dates separately.
So I use
range.split("-")
However, this does not work properly for 01-Dec-16-06-Dec-16
Not sure what is the best way to account for all the possibilities (considering that the from/to date separator would always be -)
Since this is a case of an ugly API, the only way to do this is by using "hacky" solutions.
Use #Rory McCrossan's suggestion: count the number of - in the string. If 1, split. If 5, split by the third.
Since the API uses the same format of date for both the left side and the right side, the total length of the string will always be ("left side" + "-" + "right side"). You can split the text on the middle character.
e.g.
let date = "01-Dec-16-06-Dec-16";
let idx = date.length / 2;
let start = date.substr(0, idx);
let end = date.substr(idx + 1);
Use regex.
From the formats provided, it looks like the from and to dates will always be the same length split by -. In that case, just do this:
var len = (yourDateRange.length - 1) / 2
var dateFrom = yourDateRange.substring(0, len)
var dateTo = yourDateRange.substring(len + 1)
If you have any format where the length is variable (Such as full name for month), this obviously won't work
It's a bit hacky, but gets the job done.
I used a chained indexOf call with the previous call as the fromIndex parameter (which is the 2nd parameter of indexOf). And seeing as there is either / in the string (then split by -) or not (then split by 3rd -), there was no need for any special checks.
function splitDates(date) {
// ~ is just a fancy way to turn indexOf into a
// boolean-equivalent check (-1 is 0, 0 is 1, etc)
if (~date.indexOf('/')) {
return date.split('-');
} else {
var idx = date.indexOf('-', 1 + date.indexOf('-', 1 + date.indexOf('-')));
return [date.slice(0, idx), date.slice(idx + 1)];
}
}
var dates = ['01-Dec-16-06-Dec-16', '01/12/16-06/12/16'];
dates.forEach((date) => {
alert(splitDates(date))
});
I have a task, which is to scramble a single word, whose size is greater than 3 letters.
The scrambled word must not be equal to the original, and the first and last letters of the word must remain the same.
For example, the word stack, could give the one of the following results:
satck
scatk
stcak
sactk
etc
While words like is or hey for example, would remain unchanged for being too small.
My attempt to do this can be seen below. I have a JavaScript function that received a word to scramble, and then I choose random indexes within a certain limit to create a new scrambled word and return it. If the index I have chosen was already picked, then I try again hoping for a new result.
/**
* Returns a scrambled word with the first and last letters unchanged
* that is NOT EQUAL to the given parameter 'word', provided it has
* more than three characters.
*/
function scrambleWord(word){
if(word.length <= 3)
return word;
var selectedIndexes, randomCharIndex, scrambledWord = word;
while(word === scrambledWord){
selectedIndexes = [], randomCharIndex, scrambledWord = '';
scrambledWord += word[0];
for(var j = 0; j < word.length-2; j++){
//select random char index making sure it is not repeated
randomCharIndex = getRandomInt(1, word.length-2);
while(selectedIndexes.indexOf(randomCharIndex) > -1 && selectedIndexes.length != word.length-2){
randomCharIndex = getRandomInt(1, word.length-2);
}
scrambledWord += word[randomCharIndex];
selectedIndexes.push(randomCharIndex);
}
scrambledWord += word[word.length-1];
}
return scrambledWord;
}
/**
* Returns a random integer between min (inclusive) and max (inclusive)
* Using Math.round() will give you a non-uniform distribution!
* See: http://stackoverflow.com/a/1527820/1337392
*/
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
The problem with this approach, is that it is too slow. I fail the tests because I exceed the time limit of 6 seconds, so I definitely need to improve this solution, but I can't see where I can do it.
Any ideas?
There's an answer Here that deals with quick ways of shuffling a string. All you then need to do is take off the first and last letters like you are doing, use this approach to shuffle the string and then tack your first and last letters back on.
For completeness I would leave your checks that the answer you get isn't the same as the original just in case, and the checks that the string is > 3 characters as these are unique to your requirements.
This should be dramatically quicker than what you already have because it's your random shuffling that's taking all the time!
I want to convert two strings to ints to be able to compare them.
The strings are timers so I basically want to convert as below:
timer1 = 00:00:14 // 14
timer2 = 00:00:25 // 25
Step one is to remove the colons:
var str1 = "00:00:14";
str1 = str1.replace (/:/g, "");
Step two is to take the remaining number and turn it into a number. parseInt will return 0 for a string like "000014", so you can use parseFloat:
var result = parseFloat(str1);
Good luck!
UPDATE: I didn't consider the base 10 problem... this would work if you simply wanted to compare which time was greater, but to do a more "proper" comparison of real times you might want to convert both to formal Date objects first.
The below snippet removes all semicolons by means of a regular expression matching all occurences of : and converts it to a number using parseInt. If the string timer1 was invalid, NaN will be the result.
numeric_timer1 = parseInt(timer1.replace(/:/g, ""), 10)
This fulfills your literal request for "converting strings to ints and ignoring colons".
If you're interested in converting hour:minute:seconds to seconds, split the string up with the timer1.split(":") method and apply maths.
You can do something like this:
var s = '00:01:05';
var seg = s.split(':');
var hours = parseInt(seg[0]);
var minutes = parseInt(seg[1]);
var seconds = parseInt(seg[2]);
var total = (hours * 3600) + (minutes * 60) + seconds;
window.alert(total);
Try using regexps and implicit string->number conversion (e.g. "7"*1 = 7)
function timeToSeconds(s)
{
var m = s.match(/(\d{2}):(\d{2}):(\d{2})/);
/* capture time fields in HH:MM:SS format */
if (m == null)
return null;
else
return m[1]*3600 + m[2]*60 + m[3]*1;
}
You can compare those strings as strings and things should work just fine, if those segments really are always 2-digit segments.