Fix many issues in calculation and improve output formatting

This commit is contained in:
Marko Korhonen 2023-11-14 17:03:18 +02:00
parent 50144cfc99
commit 6572f95389
Signed by: FunctionalHacker
GPG key ID: A7F78BCB859CD890
3 changed files with 86 additions and 30 deletions

View file

@ -3,7 +3,23 @@ import { Duration } from 'dayjs/plugin/duration.js';
export const formatTimestamp = (timestamp: Dayjs): string => timestamp.format('YYYY-MM-DD HH:mm'); export const formatTimestamp = (timestamp: Dayjs): string => timestamp.format('YYYY-MM-DD HH:mm');
export const formatDuration = (unLogged: Duration): string => unLogged.format('HH[ hours and ]mm [minutes]'); export const formatTime = (time: Dayjs): string => time.format('HH:mm');
export const formatDuration = (duration: Duration, short?: boolean): string => {
if (duration.hours() === 0 && duration.minutes() === 0) {
return 'none';
}
const formatString = short
? 'HH:mm'
: duration.hours() > 0 && duration.minutes() > 0
? `H [hour${duration.hours() > 1 ? 's' : ''} and] m [minute${duration.minutes() > 1 ? 's' : ''}]`
: duration.hours() > 0
? `H [hour${duration.hours() > 1 ? 's' : ''}]`
: `m [minute${duration.minutes() > 1 ? 's' : ''}]`;
return duration.format(formatString);
};
export const getHoursRoundedStr = (duration: Duration) => export const getHoursRoundedStr = (duration: Duration) =>
`(${getHoursRounded(duration)} as hours rounded to next even 15 minutes)`; `(${getHoursRounded(duration)} as hours rounded to next even 15 minutes)`;

View file

@ -1,10 +1,12 @@
import chalk from 'chalk'; import chalk from 'chalk';
import dayjs, { Dayjs } from 'dayjs'; import dayjs, { Dayjs } from 'dayjs';
import * as readline from 'readline/promises'; import * as readline from 'readline/promises';
import { formatDuration, formatTimestamp, getHoursRoundedStr } from './format.js'; import { formatDuration, formatTime, formatTimestamp, getHoursRoundedStr } from './format.js';
import { Duration } from 'dayjs/plugin/duration'; import duration, { Duration } from 'dayjs/plugin/duration.js';
import { parseDuration, parseTimestamp } from './parse.js'; import { parseDuration, parseTimestamp } from './parse.js';
dayjs.extend(duration);
const { log, error } = console; const { log, error } = console;
const defaultStartTime = '08:00'; const defaultStartTime = '08:00';
const lunchBreakDuration = dayjs.duration({ minutes: 30 }); const lunchBreakDuration = dayjs.duration({ minutes: 30 });
@ -16,14 +18,18 @@ const main = async () => {
output: process.stdout, output: process.stdout,
}); });
let started: Dayjs | undefined = undefined; let startedAt: Dayjs | undefined = undefined;
const now = dayjs();
try { try {
// Get work day duration // Get work day duration
let workDayDuration: Duration | undefined = undefined; let workDayDuration: Duration | undefined = undefined;
const durationAnswer = await rl.question( const durationAnswer = await rl.question(
`How long is your work day today, excluding the lunch break? [${formatDuration(defaultWorkDayDuration)}] `, `How long is your work day today, excluding the lunch break? [${formatDuration(
defaultWorkDayDuration,
true,
)}] `,
); );
if (durationAnswer !== '') { if (durationAnswer !== '') {
workDayDuration = parseDuration(durationAnswer); workDayDuration = parseDuration(durationAnswer);
@ -44,8 +50,8 @@ const main = async () => {
// Calculate worked time // Calculate worked time
const startTimeAnswer = await rl.question(`What time did you start work today? [${defaultStartTime}] `); const startTimeAnswer = await rl.question(`What time did you start work today? [${defaultStartTime}] `);
if (startTimeAnswer !== '') { if (startTimeAnswer !== '') {
started = parseTimestamp(startTimeAnswer); startedAt = parseTimestamp(startTimeAnswer);
if (!started.isValid()) { if (!startedAt.isValid()) {
error( error(
chalk.red( chalk.red(
`Failed to parse ${startTimeAnswer} to time, using default start time ${defaultStartTime}`, `Failed to parse ${startTimeAnswer} to time, using default start time ${defaultStartTime}`,
@ -54,54 +60,88 @@ const main = async () => {
} }
} }
if (!started?.isValid()) { if (!startedAt?.isValid()) {
started = parseTimestamp(defaultStartTime); startedAt = parseTimestamp(defaultStartTime);
} }
let stopped: Dayjs | undefined = undefined; let stoppedWorking = false;
let stoppedAt: Dayjs | undefined = undefined;
const stoppedAnswer = await rl.question( const stoppedAnswer = await rl.question(
"What time did you stop working (leave empty if you didn't stop yet)? ", `What time did you stop working (default is current time if you didn't stop yet)? [${formatTime(now)}] `,
); );
if (stoppedAnswer === '') { if (stoppedAnswer === '') {
stopped = dayjs(); stoppedAt = now;
} else { } else {
stopped = parseTimestamp(stoppedAnswer); stoppedWorking = true;
if (!stopped.isValid()) { stoppedAt = parseTimestamp(stoppedAnswer);
if (!stoppedAt.isValid()) {
error(`Failed to parse ${stoppedAnswer} to time, using current time`); error(`Failed to parse ${stoppedAnswer} to time, using current time`);
stopped = dayjs(); stoppedAt = dayjs();
} }
} }
let worked = dayjs.duration(stopped.diff(started)); if (stoppedAt.isSame(startedAt) || stoppedAt.isBefore(startedAt)) {
error(
chalk.red(
`Start time (${formatTime(startedAt)}) needs to be before stop time (${formatTime(
stoppedAt,
)}). Exiting`,
),
);
process.exit(1);
}
let worked = dayjs.duration(stoppedAt.diff(startedAt));
if ((await rl.question('Did you have a lunch break? [Y/n] ')).toLowerCase() !== 'n') { if ((await rl.question('Did you have a lunch break? [Y/n] ')).toLowerCase() !== 'n') {
worked = worked.subtract(lunchBreakDuration); worked = worked.subtract(lunchBreakDuration);
} }
// Calculate unlogged time // Calculate unlogged time
let unLogged: Duration | undefined = undefined;
let loggedAnswer = await rl.question('How many hours did you log already? [00:00] '); let loggedAnswer = await rl.question('How many hours did you log already? [00:00] ');
if (loggedAnswer === '') { if (loggedAnswer === '') {
loggedAnswer = '00:00'; loggedAnswer = '00:00';
} }
const logged = parseDuration(loggedAnswer); const logged = parseDuration(loggedAnswer);
const unLoggedDuration = workDayDuration.subtract(logged); let unLogged: Duration | undefined = undefined;
if (unLoggedDuration.asMinutes() > 0) {
unLogged = unLoggedDuration; if (logged.asMinutes() === worked.asMinutes()) {
unLogged = worked.subtract(logged);
} else {
unLogged = worked.subtract(logged);
} }
// Log result // Log result
log(); log();
log('Started working at', formatTimestamp(started)); log('Started working at:', formatTimestamp(startedAt));
log('Stopped working at', formatTimestamp(stopped)); log((stoppedWorking ? 'Stopped working' : 'Hours calculated') + ' at:', formatTimestamp(stoppedAt));
log('Total worked today:', chalk.green(formatDuration(worked)), chalk.yellow(getHoursRoundedStr(worked))); log('Worked today:', chalk.green(formatDuration(worked)), chalk.yellow(getHoursRoundedStr(worked)));
log(
'Unlogged today:', if (unLogged.asMinutes() == 0) {
unLogged log('Unlogged today:', chalk.green('none'));
? `${chalk.red(formatDuration(unLogged))} ${chalk.yellow(getHoursRoundedStr(unLogged))}` } else if (unLogged.asMinutes() > 0) {
: chalk.green('none'), log('Unlogged today:', chalk.red(formatDuration(unLogged)), chalk.yellow(getHoursRoundedStr(unLogged)));
); } else if (unLogged.asMinutes() < 0) {
log(
chalk.red(`You have logged ${formatDuration(unLogged)} more than you worked today!`),
chalk.yellow(getHoursRoundedStr(unLogged)),
);
}
const workLeft = workDayDuration.subtract(worked);
const workLeftMinutes = workLeft.asMinutes();
if (workLeftMinutes > 0) {
log('You still have to work', chalk.green(formatDuration(workLeft)), 'more today');
} else if (workLeft.asMinutes() < 0) {
log(
'You worked',
chalk.green(
formatDuration(dayjs.duration({ minutes: Math.round(workLeftMinutes * -1) })),
'overtime today',
),
);
}
} finally { } finally {
rl.close(); rl.close();
} }

View file

@ -5,7 +5,7 @@ import duration, { Duration } from 'dayjs/plugin/duration.js';
dayjs.extend(customParseFormat); dayjs.extend(customParseFormat);
dayjs.extend(duration); dayjs.extend(duration);
export const parseTimestamp = (time: string): Dayjs => dayjs(time, 'H:mm', true); export const parseTimestamp = (time: string): Dayjs => dayjs(time, 'HH:mm', true);
export const parseDuration = (time: string): Duration => { export const parseDuration = (time: string): Duration => {
const [hours, minutes] = time.split(':').map(Number); const [hours, minutes] = time.split(':').map(Number);