I have a set of dates in one format and I need to convert those dates into another one.
input: <month>/<day>/<year>
output: <day>/<month>/<year> - additionally I need to pad months and days with 0 if it contain only one character.
I have created regular expression to match given date format. Then I wanted to modify that date using String.prototype.replace and modify captured groups by passing them into function directly inside of template literal as a second argument of replace method.
The problem I am facing is that it doesn't work as I would expect. In some cases function pad correctly pads the date, in other cases it doesn't. More precisely, I would expect second console log to be 12, but the result is 012.
const pad = date => date.length === 2 ? date : '0' + date;
const normalizeDate = date => {
const regex = /(?<month>\d{1,2})\/(?<day>\d{1,2})\/(?<year>\d{4})/;
// pad string of length 1 works correctly (expected '01'/ result '01')
console.log(date.replace(regex, `${pad('$<month>')}`));
// pad sting of length 2 doesn't (expected '12' / result '012')
console.log(date.replace(regex, `${pad('$<day>')}`));
// test shows that <day> = 12
console.log(date.replace(regex, `$<day>`));
// padding 12 directly works (expected '12' / result '12')
console.log(pad('12'));
return date.replace(regex, `${pad('$<month>')}-${pad('$<day>')}-$<year>`);
}
const date = '1/12/2014';
normalizeDate(date);
Does anyone have any idea what is wrong with that code?
The $<day> named backreferences can only be used in string replacement patterns. Since you need to modify the captures, you need to use anonymous methods:
.replace(regex, (_,month,day,year) => `${pad(month)}`)
Here, in parentheses, you must define the variables for the whole match and for the capturing groups. So, basically, you need not the new ECMAScript 2018 regex enhancement since you can use regular numbered capturing groups here, too.
See the updated demo:
const pad = date => date.length === 2 ? date : '0' + date;
const normalizeDate = date => {
const regex = /(?<month>\d{1,2})\/(?<day>\d{1,2})\/(?<year>\d{4})/;
// pad string of length 1 works correctly (expected '01'/ result '01')
console.log(date.replace(regex, (_,month,day,year) => pad(month)));
// pad sting of length 2 doesn't (expected '12' / result '012')
console.log(date.replace(regex, (_,month,day,year) => pad(day)));
// test shows that <day> = 12
console.log(date.replace(regex, "$<day>"));
// padding 12 directly works (expected '12' / result '12')
console.log(pad('12'));
return date.replace(regex, (_,month,day,year) => `${pad(month)}-${pad(day)}-${year}`);
}
const date = '1/12/2014';
console.log(normalizeDate(date));
Related
I'm currently coding a discord bot using javascript and I want to test if a message contains an hour. I managed to do it this way
var content = msg.content.toLowerCase(); // Get the content on the message
const nbs = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
var hours;
nbs.forEach(number => {
if(content.includes(`${number}am`)) hours = `${number}AM`;
else if(content.includes(`${number}pm`)) hours = `${number}PM`;
})
But the problem I have is in this example I can't detect if the hour contains minutes like 10:15am for example. I thought about doing another array containing integers from 1 to 59 and doing another forEach inside the one I already have but I'm afraid it would slow the code down quite a bit.
Is there a way I could test if the message contains an int between 2 values so I could have something like this ?
content.includes(`${hr}:${min}am`)
You can use regular expressions:
function getTime(message) {
const result = message.match(/(\d{1,2}):(\d{2})\s*(am|pm)/i);
if (result) {
return {
hours: Number(result[1]),
minutes: Number(result[2]),
format: result[3],
}
} else {
return null;
}
}
console.log(getTime("hello 2:13AM"));
console.log(getTime("hello 19:55 pm"));
console.log(getTime("hello"));
Use Regular Expressions to get the time.
I would recommend using something like this:
/([0-2][0-9])\:([0-6][0-9])(?:\s*)?(am|pm)?/
I'll now go through all parts of this regular expression:
([0-2][0-9]) - Using the first capturing group you will be able to get values from 00 to 24 (well 29 to be honest) but it will do the trick.
\: - will capture your seperator in this case a double colon
([0-6][0-9]) - will capture a number between 00 to 69
(?:\s*)?(am|pm)? - last but not least capture am or pm. This is completely optional. The regex will also capture times without am or pm if someone writes in a 24 hour format
let content = 'some content 10:00 AM ...';
content = content.toLowerCase();
console.log(content);
const time_regex = /([0-2][0-9])\:([0-6][0-9])(?:\s*)?(am|pm)?/;
if ((m = time_regex.exec(content)) !== null) {
// The result can be accessed through the `m`-variable.
m.forEach((match, groupIndex) => {
document.body.insertAdjacentHTML('beforeBegin', `<p>Found match, group ${groupIndex}: ${match}</p>`);
});
}
If you want to be even more specific and make sure that times like 26:00 or 13:65 are not possible to match you can adjust the regex like this
([0-2][0-9])(?<!25|26|27|28|29)\:([0-6][0-9])(?<!61|62|63|64|65|66|67|68|69)(?:\s*)?(am|pm)?
As you can see after the check for the values 00 to 29 and 00 to 69 I have added negative lookbehinds with all values we want to exclude. This makes sure that the first capturing group can only count up to 24 and the second can only count up to 60.
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);
I have an upcoming project.
I have a validation program to write, beginner's level.
I've written the code most part, but in case the user inputs the year in 2 digit form I need to convert it into 4-digit form.
Then I need to declare a new variable and extract using '.subscript' the 4-digits from the National Identification Number which is a 13 digit number.
I've written most of the code so far ( I won't post it all of it here), but the code won't execute when it reaches yearString = yearString.substring(2,4).
function cnpVal() {
var cnpString = document.getElementById('lblcnp').value; //13 digit number
var dayString = document.getElementById('txtday').value;
var monthString = document.getElementById('txtmonth').value;
var yearString = document.getElementById('txtyear').value; //2 or 4 digit number
arrayCnp = cnpString.split('');
if (yearString.length != 4) {
alert("Year format requires you to enter 4 digits");
return false;
}
else {
yearString = yearString.substring(2,4);
}
Basically I need the yearString variable declared and working for a 4 digit year, i.e. 1987.
Obviously it won't work as 1987 = ["1", "9", "8", "7"] is a 4 string array, but I don't have a string at index 4.
I hope I've made myself understood and sorry for the ignorance. I stand to be corrected.
Greets.
yearString = yearString.substring(2,4); will extract the last 2 digits from yearString. String.prototype.substring accepts a start index as the first argument and an optional end index which is not included in the extraction, so yearString.substring(2,4) will take the character at index 2 and 3, and does not include the character at index 4.
Documentation for substring is available HERE
You could also try String.prototype.substr(start, length) which takes the start index of the sub string you need as the first argument and how many characters you want to extract as the second argument.
Documentation for substr is available HERE
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))
});