I'm trying to parse a user input string for duration (into seconds) with Javascript.
Here are some example inputs that I'd like to be able to deal with:
"1 hour, 2 minutes"
"1 day, 2 hours, 3 minutes"
"1d 2h 37m"
"1 day 2min"
"3days 20hours"
The key components are 1) days, 2) hours 3) minutes, but some components may not always be included.
My plan of attack is to use .match and regex. As long as I can get the first letter of the word, I'll know what the preceding number is for and be able to handle all the different formats of the words (e.g. hours, hour, hr, h). However, since I'm learning regex for this, it's turned out to be much more complicated than I thought.
Any help or suggestions would be greatly appreciated!
Is the order of days/hours/minutes in your string guaranteed? If not, it may be easier to just do a separate RegEx for each. Something like this?
function getSeconds(str) {
var seconds = 0;
var days = str.match(/(\d+)\s*d/);
var hours = str.match(/(\d+)\s*h/);
var minutes = str.match(/(\d+)\s*m/);
if (days) { seconds += parseInt(days[1])*86400; }
if (hours) { seconds += parseInt(hours[1])*3600; }
if (minutes) { seconds += parseInt(minutes[1])*60; }
return seconds;
}
You can use this code to break your string into a number, followed by a unit string:
function getPieces(str) {
var pieces = [];
var re = /(\d+)[\s,]*([a-zA-Z]+)/g, matches;
while (matches = re.exec(str)) {
pieces.push(+matches[1]);
pieces.push(matches[2]);
}
return(pieces);
}
Then function returns an array such as ["1","day","2","hours","3","minutes"] where alternating items in the array are the value and then the unit for that value.
So, for the string:
"1 day, 2 hours, 3 minutes"
the function returns:
[1, "day", 2, "hours", 3, "minutes"]
Then, you can just examine the units for each value to decide how to handle it.
Working demo and test cases here: http://jsfiddle.net/jfriend00/kT9qn/. The function is tolerant of variable amounts of whitespace and will take a comma, a space or neither between the digit and the unit label. It expects either a space or a comma (at least one) after the unit.
You can use optional substrings in the regex to match any combination as you describe:
/(\d+)\s*d(ays?)?\s*(\d+)\s*h(ours?)?\s*(\d+)\s*m(in(utes?)?)?/
This requires at least a d, h, and m but accepts common shortenings.
var str = "1 day, 2 hours, 3 minutes";
var seconds = 0;
str.replace (/\s*,?(\d+)\s*(?:(d)(?:ays?)?|(h)(?:ours?)?|(m)(?:in(?:utes?)?)?)/g, function (m0, n, d, h, m) {
seconds += +n * (d ? 24 * 60 * 60 : h ? 60 * 60 : 60);
return m0;
});
Here I use replace not to change the string but to process the matches one by one. Note days, hours aminutes can be in any order.
A more general version of the accepted answer that accepts months and seconds as well.
const getSeconds = str => {
let seconds = 0;
let months = str.match(/(\d+)\s*M/);
let days = str.match(/(\d+)\s*D/);
let hours = str.match(/(\d+)\s*h/);
let minutes = str.match(/(\d+)\s*m/);
let secs = str.match(/(\d+)\s*s/);
if (months) { seconds += parseInt(months[1])*86400*30; }
if (days) { seconds += parseInt(days[1])*86400; }
if (hours) { seconds += parseInt(hours[1])*3600; }
if (minutes) { seconds += parseInt(minutes[1])*60; }
if (secs) { seconds += parseInt(secs[1]); }
return seconds;
};
Related
I am trying to convert time duration from the format of mm:ss.mss to entirely milliseconds and back.
I've already have a working function for converting from milliseconds to duration but I cannot seem to get it the other way around.
Lets say for instance that I have the duration 32:29.060, I want to convert it to milliseconds. For that I use this function:
function millisecondsToTime(ms, digits) {
digits = digits || 12;
return new Date(ms).toISOString().slice(23-digits, -1);
}
var a = millisecondsToTime(5549060, 9);
but whenever I try to convert back to time duration, I fail. I've tried parsing individually the minutes, seconds and milliseconds but it doesn't seem to work.
Here is the code that I've used for it:
var firstSplit = a.split(':')
var minutes = firstSplit[0]; //1
var secondSplit = firstSplit[1].split('.');
var seconds = secondSplit[0]; //2
var millisec = secondSplit[1]; //3
var conversion = ((+minutes) * 60 + (+seconds) * 60 + (+millisec))*1000;
I have an input bar which takes the format of mm:ss.mss and I need to convert it to milliseconds. How can I do that?
you can just return a
new Date(ms)
to get a date from ms.
And to get the same date as ms,
date.getTime() // returns ms from date object
Full example:
const ms = 5549060
const date = new Date(ms) // get a date from ms
console.log(date.getTime) // logs 5569060
If your input is a string in the format of mm:ss.mss, and you want to get a date from it, you can use moment.
const moment = require('moment')
const date = moment('22:15.143', 'mm:ss.SSS') // get date from pre specified format
You can use the string methods indexOf() and substr() to get the individual numbers out of your string and calculate the time accordingly.
I'm afraid though your millisecondsToTime() function isn't working properly.
5549060 milliseconds are roughly 92 minutes and it's returning 32:29.060
function backToTime(time) {
var index = time.indexOf(":");
var minutes = time.substr(0, index);
var seconds = time.substr(index + 1, time.indexOf(".") - (index + 1));
var milliseconds = time.substr(time.indexOf(".") + 1, time.length);
return parseInt(minutes * 60 * 1000) + parseInt(seconds * 1000) + parseInt(milliseconds);
}
console.log(backToTime("32:29.060"));
Your conversion to milliseconds is not working, this is basic math approach to both conversions:
let input = 5549060
//toDuration
let seconds = Math.floor(input / 1000);
let ms = input - seconds*1000;
let m = Math.floor(seconds / 60);
let s = seconds - m*60;
duration = m + ":" + s + "." + ms
console.log(duration)
//toMilliseconds
let holder = duration.split(":");
m = parseInt(holder[0]);
holder = holder[1].split(".");
s = parseInt(holder[0]);
ms = parseInt(holder[1]);
milliseconds = (m*60 + s)*1000 + ms
console.log(milliseconds)
If needed add check for ms length to add 0s, if you need it to have length of 3
I think your milliseconds to duration converter will be broken for durations above 60 minutes. This is because using Date the minutes field will wrap over into the minutes after 59 seconds have passed. If you want to get good support for values beyond 59 in your first field, I think maybe moving to a regex-based parser and using multiplication and addition, division and modulo to extract and reduce the fields manually might be nice. Something like this maybe:
var duration = ms => `${(ms / 60000) | 0}`.padStart(2, '0') + `:` + `${ms % 60000 / 1000 | 0}`.padStart(2, '0') + `.` + `${ms % 1000}`.padStart(3, '0')
var millisec = durat => (match => match && Number(match[1]) * 60000 + Number(match[2]) * 1000 + Number(match[3]))(/^([0-9]+)\:([0-5][0-9])\.([0-9]{3})$/.exec(durat))
You can see given the input 5549060, this function provides output 92:29.60, which is exactly 60 seconds greater than your own, and I believe to be correct. Maybe it's intentional for your usecase, but I can't imagine that being so desirable generally...
I have function that returns array or values that represent working hours. Array can return one or two elements. In case where only one element is returned this is pretty simple but when I have two that is the problem to find the way how to replace the existing values in the string. Here is example of the original string: 0600-2200 MAY 15-SEP 30; 0600-2100 OCT 1-MAY 14. I have function that finds the time stamp in the string and returns the hours. Example of the returning array is here: [16,15]. That array has two values and I need to replace 0600-2200 with first element in the array 16 and append the word hours to that. So final output should look like this: 16 hours MAY 15-SEP 30; 15 hours OCT 1-MAY 14. Here is example of the function that converts time stamp to string:
var timeSt = "0600-2200 MAY 15-SEP 30; 0600-2100 OCT 1-MAY 14";
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 = timeSt.match(/\d{4}\-\d{4}/g).map(e => calcDifference(e));
console.log(diffs);
The solution I have tried looks like this:
var hours = "";
for(var i=0; i < diffs.length; i++){
hours += timeSt.replace(regex,diffs[i] + " hours ");
}
Here is the output that above example produced:
16 hours MAY 15-SEP 30; 16 hours OCT 1-MAY 1415 hours MAY 15-SEP 30; 15 hours OCT 1-MAY 14
Seems that entire string was appended twice. I understand why that is happening but still can't get a good way how to fix this problem. The other thing that I noticed is that some time stamp values look like this: 0000 - 2359
In that case function that converts hours will return this: [23.983333333333334]. I would like to round up that value to 24 and that is the only case where the value should be rounded up to 24 the higer int his case. I time stamp looks like this: 0500-2330 function returns [18.5] and that value should not round. It should stay as it is. If anyone knows good way on how to fix these two problems please let me know.
For the problem of replacing you can provide a callback to .replace function in string.
const roundMinutes = 15;
const timeSt = "0600-0000 MAY 15-SEP 30; 0600-2145 OCT 1-MAY 14";
const calcDifference = range => {
const time = range.split`-`.map(e => +e.substr(0, 2) * 60 + (+e.substr(2)));
let [start, end] = time;
if (end < start) {
end += 24 * 60;
}
return end - start;
};
const formatted = timeSt.replace(/\d{4}\-\d{4}/g, (range) => {
const diff = calcDifference(range);
const full = Math.round(diff / roundMinutes) * roundMinutes;
const hours = Math.floor(full / 60);
const minutes = full - hours * 60;
const time = minutes === 0 ? `${hours}` : `${hours}.${minutes}`
return `${time} hours`;
})
console.log(formatted)
To change precision you can tweak roundMinutes constant.
Are you aware of a npm module capable of converting human string time to days, hours, seconds or milliseconds preferably using moment.js time units?
It's a little difficult to explain, so here are a few examples:
'1hours' to minutes = 60
'2days' to seconds = 172800
'60seconds' to minutes = 1
'30minutes' to seconds = 1800
Same as above using short hand:
'1h' to minutes = 60
'2d' to seconds = 172800
'60s' to minutes = 1
'30m' to seconds = 1800
These are the string units used by moment.js
Key Shorthand
----------------------
years y
quarters Q
months M
weeks w
days d
hours h
minutes m
seconds s
milliseconds ms
Or expressed as a function:
const convertUnits = (input, format) => {
// Implementation
};
convertUnits('1hours', 'minutes') // 60
convertUnits('1h', 'm') // 60
Or is it possible to do this just using moment? Remember that I don't care about the actual date or time - I merely want the unit conversions mixed in with the human readable units.
Thanks in advance.
I ended up using below:
import * as _ from 'lodash'
import moment from 'moment'
import momentDurationFormat from 'moment-duration-format'
export const convertTime = (time, format = 's') => {
if(!_.isString(time) || !_.isString(format)){
return 0;
}
const components = time.trim().split(/(\d+)/);
const digits = parseInt(components[1]);
const unit = components[2];
return moment
.duration(digits, unit)
.format(format);
};
Try to use timestring library. It parse a human readable time string into a time based value (by default in seconds). As I see, it can all you need:
const timestring = require('timestring')
let str = '1d 3h 25m 18s'
let time = timestring(str)
console.log(time) // will log 98718
str input param can be set without spaces, for example, '1d3h25m18s'
I have two <input type="time">. By default, each input collects a time value as a string. For example, "08:30".
How can I convert this string into an object which would then enable computation? Is it possible to avoid involving the use of date in this approach?
In the end, I would like to compute the difference between two time strings and then return the result in minutes. For example, the expected return value of08:00 and 09:00 would be 60 minutes.
Just do it as if you only had pen and paper:
12:45 => 12 × 60 + 45 = 765 minutes
08:30 => 8 × 60 + 30 = 510 minutes
765 - 510 = 255
Integer division: 255 / 60 = 4 hours
Remainer: 255 - 60 × 4 = 15 minutes
Result: 04:15
You can parse from string using regular expressions:
var parts = "08:45".match(/^(\d+):(\d+)$/);
console.log(+parts[1], +parts[2], +parts[1] * 60 + +parts[2]);
... and formatting back to string should not be very difficult either.
Assuming you want to use a 24h clock:
function minutesBetween (a, b) {
return Math.abs(toMinutes(b) - toMinutes(a))
}
function toMinutes (time) {
time = /^(\d{1,2}):(\d{2})$/.exec(time)
return time[1]*60 + +time[2]
}
console.log(minutesBetween('8:30', '9:30')) //=> 60
Generally speaking I whould suggest using Date insted of using custom functions, but just for your case this is the example function:
const timeStart = "8:00:00";
const timeEnd = "8:10:00";
//Handles only time with format in hh:mm or hh:mm:ss
//For more complicated cases use Date
function diff(start, end) {
const startMinutes = getMinutes(start);
const endMinutes = getMinutes(end);
return endMinutes - startMinutes
}
function getMinutes(strTime) {
const time = strTime.split(':');
return (time[0] * 60 + time[1]*1);
}
alert(diff(timeStart, timeEnd));
Note that this function is not responsible for validation of the time difference only the computation. You should validate you input
I'm working on a web timesheet where users use timepicker to determine start & end times and I'd like to have the form automatically find the difference between the two times and place it in a 3rd input box. I understand that I need to get the values, convert them to milliseconds, then subtract the first number from the second, convert the difference back to human time and display that in the third box. But I can't seem to wrap my head around time conversion in javascript. Here's what I have so far:
function date1math(){
var date1in = document.getElementById("date-1-in").value;
var date1out = document.getElementById("date-1-out").value;
date1in = date1in.split(":");
date1out = date1out.split(":");
var date1inDate = new Date(0, 0, 0, date1in[0], date1in[1], 0);
var date1outDate = new Date(0, 0, 0, date1out[0], date1out[1], 0);
var date1math = date1outDate.getTime() - date1inDate.getTime();
var hours = Math.floor(date1math / 1000 / 60 / 60);
date1math -= hours * 1000 * 60 * 60;
var minutes = Math.floor(date1math / 1000 / 60);
return (hours < 9 ? "0" : "") + hours + ":" + (minutes < 9 ? "0" : "") + minutes;
document.getElementById("date-1-subtotal").value = date1math(date1in, date1out);
}
I want to take the timepicker result (say 9:00am) from the input date-1-in, the timepicker result (say 5:00pm) from the input date-1-out, and then place the difference as a number in date-1-subtotal.
Presumably the input is a string in the format hh:mm (e.g. 09:54) and that the two strings represent a time on the same day. You don't mention whether an am/pm suffix is included, but it's there in the text so I'll assume it might be.
If daylight saving changes can be ignored, the simplest method is to convert the string to minutes, find the difference, then convert back to hours and minutes, e.g.:
// Convert hh:mm[am/pm] to minutes
function timeStringToMins(s) {
s = s.split(':');
s[0] = /m$/i.test(s[1]) && s[0] == 12? 0 : s[0];
return s[0]*60 + parseInt(s[1]) + (/pm$/i.test(s[1])? 720 : 0);
}
// Return difference between two times in hh:mm[am/pm] format as hh:mm
function getTimeDifference(t0, t1) {
// Small helper function to padd single digits
function z(n){return (n<10?'0':'') + n;}
// Get difference in minutes
var diff = timeStringToMins(t1) - timeStringToMins(t0);
// Format difference as hh:mm and return
return z(diff/60 | 0) + ':' + z(diff % 60);
}
var t0 = '09:15am';
var t1 = '05:00pm';
console.log(getTimeDifference('09:15am', '05:00pm')); // 07:45
console.log(getTimeDifference('09:15', '17:00')); // 07:45
If daylight saving is to be incorporated, you'll need to include the date so that date objects can be created and used for the time difference. The above can use either 12 or 24 hr time format.