Javascript use split on date range - javascript

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))
});

Related

How to find time stamp in the string and then convert to number of hours/minutes?

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);

How to format numeric values with pattern?

I'm finding an equivalent to Java's DecimalFormat in JavaScript. I would like to format given numeric values with given decimal pattern like
"#,###.00"
"###.##"
"#,###.##"
"$#,##0.00"
"###,###.00"
"$###,###.00"
"###,###.###"
"####,####.000"
Has there any way to achieve it?
Part 1 - formatting
I would recommend using the Intl.NumberFormat natively supported in javascript, although it may not be supported by older browsers. Looks like IE11 has it, but not android.
So if you wanted to support US Dollars you would simply use something like this
var dollarFormat = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' });
var amountInDollars = dollarFormat.format(123456.123);
// amountInDollars = "$123,456.12"
But this also rounds up for you, for example
var roundedDollars = dollarFormat.format(555.555);
// roundedDollars = "$555.56";
For the numeric cases just use a different formatter. The default 'en-US' adds commas, a decimal before fractional numbers, and limits to 3 fractional numbers. But this is configurable.
var numberFormat = new Intl.NumberFormat('en-US');
var formatted = numberFormat.format(123456.123456);
// formatted = "123,456.123"
var numberFormat2decimals = new Intl.NumberFormat('en-US', { maximumFractionDigits: 2 });
var formatted2 = numberFormat2decimals.format(123456.123456);
// formatted2 = "123,456.12"
You can set maximum and minimum for fraction, integer, and significant digits, and this also supports international formats. Since it's native javascript, I think it's a good way to go if your platforms support it.
MDN is an excellent reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/NumberFormat
Part 2 - the 0's
To achieve the 0's in your formats you'll have to modify the value before passing to the formatter. If you require a minimum fractional amount, you're fine for things like .00, the currency formatter will do that by default. If you've got fractal numbers you don't want, just use Math.trun() to truncate the values.
var num = Math.trun(1234.1234);
// num = 1234
Now to change something like 12345 to 12340 we'll have to remove some of the numeric value. We can find out much by converting to a string, pulling the last character, and converting back.
var num = 123456.12345;
var wholeNum = Math.trunc(num);
// wholeNum = 123456;
var toRemove = Number.parseInt(wholeNum.toString().slice(-1), 10);
// toRemove = 6
// slice(-1) gives us the right-most character of a string.
// Notice the ', 10' at the end, this is important to indicate which number base to use for parseInt.
var wholeNumEnding0 = wholeNum - toRemove;
// wholeNumEnding0 = 123450
Hopefully that's what you're looking to accomplish? I didn't perform any rounding here.
Note: I typed this at speed, excuse any mistakes, there might be better ways to do it too.
If you don't want to rely on a library, you could do something like the following:
var number = 100000.00000000000012422;
function FormatNumber(no){
no = no.toFixed(2);
no = no.toString().split('.');
var p1 = no[0];
p1 = p1.substring(0, p1.length - 3) + ',' + p1.substring(p1.length - 3);
no = p1 + '.' + no[1];
console.log(no);
}
FormatNumber(number);
The FormatNumber function takes a number as a parameter (you would probably want to expand that to include e.g. decimal places). It converts the number to the required decimal places, the turns it into a string and splits it by the decimal separator '.'.
The next step is to add a thousands separator three characters from the back, then it's just a matter of joining the remaining characters back together.
JSFiddle
If you wanted to get a ',' every 3 characters you could write a little more 'complex' formatter, something along the lines of the following:
no = no.toFixed(2);
no = no.toString().split('.');
var p1 = no[0];
var arr = [];
arr = p1.split("").reverse().join("").match(/[\s\S]{1,3}/g) || [];
arr = arr.reverse();
p1 = "";
for(var i = 0; i < arr.length; i++){
p1 += arr[i].split("").reverse().join("");
if(i != arr.length - 1){
p1 += ',';
}
}
no = p1 + '.' + no[1];
This method splits the number into an array by each number, reverses the array as we need to start from the end of the string to get accurate result.
Then we iterate the array of strings with 3 or less values by splitting the number into an array again, reversing it and joining back together and then appending to p1. If it's the last item, it doesn't add a comma.
Lastly we take the decimal and append to built string.
JSFiddle

Capturing regex group inside of template literal as function argument

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));

next day doesn't work using setDate

Try to let user click next and prev button to adjust date using JS.
This doesn't work when I plus one day and try to get tomorrow's date.
$('#next_date').click(function(e) {
var get_selected_date = "2015-12-14",
nextDay = get_selected_date.split('-')[2] + 1,
selectedDate = new Date(get_selected_date),
date = new Date(selectedDate.setDate(nextDay));
console.log(date);
});
But I manage to get previous date using the same logic, just minus one day like below
$('#prev_date').click(function(e) {
var get_selected_date = "2015-12-14",
nextDay = get_selected_date.split('-')[2] + 1,
selectedDate = new Date(get_selected_date),
date = new Date(selectedDate.setDate(nextDay));
console.log(date);
});
Your problem lies with the date being a string and by adding one you append to the string - the 1 is turned to a string instead of the 14 being turned into an integer. Subtracting from a string coerces the string to a number, however. This is one of JavaScript's quirks and comes from reusing operators. Since the plus sign is also used to combine strings, its behaviour with mixed type operands is somewhat confusing.
Small cheatsheet (assuming string can be coerced to a number):
string + string = string
string + number = string
number + string = string
string - number = number
number - string = number
string - string = number
+string + number = number (read below)
You can either parse the string you get from get_selected_date.split('-')[2] to an int using parseInt(str) or use the common workaround prepending the string with a plus sign. This coerces the string into a number. So your code only needs this change:
nextDay = +get_selected_date.split('-')[2] + 1
For optimization you might also want to write
selectedDate.setDate(nextDay);
and use that selectedDate instead of your
date = new Date(selectedDate.setDate(nextDay));
setDate() returns but it also works in-place, so it changes the date you use it on.
On a sidenote: please use var for all variables. Otherwise they're global and will mess with your other code.
var str = '2015-12-14';
str = str.split(/\D+/);
str = new Date(str[0], str[1] - 1, (parseInt(str[2]) + 1));
str will return '2015-12-15'
// str[2] is date, str[2]) + 1 is add one day
for prev date,
str = new Date(str[0], str[1] - 1, (parseInt(str[2]) - 1));
str will return '2015-12-13'

Converting strings to ints and ignoring colons

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.

Categories