visit
Huuuff. A cold tightness worms its way down your spine.
Haaaaah. Your boss's frigid breath washes over your neck like an overflowing sewer. But you can’t tell them to hover somewhere else...they've been watching you like a hawk ever since the office time-tracking system broke.
Now, they're just there...hovering…breathing. Huffffff.
Here's how to take matters into your own hands with JavaScript. Haaaaa.
You have a timesheet that looks like the contents of this String
:
const a = `November 30 2023 13:20-15:00, 15:30-16:40
December 4 2023 15:45-16:15,
December 5 2023 08:00-08:30, 18:15-19:30, 21:45-22:15, 22:30-23:00
December 6 2023 19:45-21:45
December 7 2023 14:15-14:45, 15:15-15:45, 16:00-16:30, 16:45-17:15, 18:45-20:15, 20:45-22:45
December 13 2023 03:00-03:50
December 15 2023 09:00-09:30 20:30-21:15 21:45-22:30 23:30-24:00
December 16 2023 23:25-23:55
December 17 2023 19:05-19:50 20:30-21:30 22:15-23:45
December 20 2023 23:30-24:00
December 21 2023 02:20-2:50 03:15-03:30 13:40-14:00 16:00-17:15 17:45-18:45 19:20-20:10 21:30-22:20 23:00 - 24:00
December 22 2023 0:00-0:15`;
This is pretty easy. We start by creating a couple of regular expressions and a helper function. Then, we’ll just…Huuuuffh!
We split the timesheet string into individual lines and over them to create an array of objects
For each iteration, we:
A. Split the line around the year using yearRegex
, recording the date string
B. Collect all time range intervals using intervalsRegex
C. Reduce over the time ranges, parsing each as a JavaScript Date object using the parseDate
helper and the date string from step 2.A
Reduce over the array of objects to find the total hours
const yearRegex = /(\d{4})/gi;
const intervalsRegex = /(\d+\:\d+)\s*-\s*(\d+\:\d+)/gi;
const parseDate = (date, time) => Date.parse(`${date} ${time}`);
let times = [...a.split("\n")].reduce((arr, entryLine) => {
let entryLineSplit = entryLine.split(yearRegex);
let o = {
date: entryLineSplit[0] + entryLineSplit[1],
timeRanges: [...entryLine.matchAll(intervalsRegex)],
};
o.hoursForDay = o.timeRanges.reduce((total, [x, start, end]) => {
let s = parseDate(o.date, start);
let e = parseDate(o.date, end);
return total + (e - s) / 1000 / 60 / 60;
}, 0);
return [...arr, o];
}, []);
console.log(times);
let totalHours = times.reduce((total, { hoursForDay }) => total + hoursForDay, 0);
console.log(totalHours);
If you uncomment the console.log(times);
statement, you'll see an array containing objects like:
{
date: 'November 30 2023',
timeRanges: [
[
'13:20-15:00',
'13:20',
'15:00',
index: 17,
input: 'November 30 2023 13:20-15:00, 15:30-16:40',
groups: undefined
],
[
'15:30-16:40',
'15:30',
'16:40',
index: 30,
input: 'November 30 2023 13:20-15:00, 15:30-16:40',
groups: undefined
]
],
totalHours: 2.8333333333333335
},
Date.parse
.
String.prototype.split
, which might not be unique depending on whether two days included the same time intervals.times
object. To optimize this code, it’d likely be better to use a single reduce over an empty object containing totalHours
and times
as fields.
You now know how to extract time from a messy timesheet. With a little extra effort, you could even build a full-on time clock. But then you wouldn't get to spend all that nice quality time getting to know your boss, would you? Huffff.