Skip to content

Commit

Permalink
Add support for date argument types
Browse files Browse the repository at this point in the history
  • Loading branch information
dyedgreen committed Dec 28, 2021
1 parent fea253f commit b05054e
Show file tree
Hide file tree
Showing 3 changed files with 139 additions and 2 deletions.
2 changes: 1 addition & 1 deletion mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ export type { ArgumentOptions, FlagOptions } from "./src/command.ts";

export { Command } from "./src/command.ts";
export { CommandGroup } from "./src/group.ts";
export { boolean, choice, integer, number, string } from "./src/types.ts";
export { boolean, choice, date, integer, number, string } from "./src/types.ts";
56 changes: 56 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,19 @@ export interface ArgumentType<T> {
readonly typeName: string;
}

const ISO_8601 =
/^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(\.\d+)?(([+-]\d\d:\d\d)|Z)?$/i;
const LOCAL_DATE_FORMATS = [
/^(?<year>\d{4})-(?<month>\d\d?)-(?<day>\d\d?)(\s+(?<hours>\d\d?):(?<minutes>\d\d)(:(?<seconds>\d\d))?)?$/i,
/^(?<year>\d{4}).(?<month>\d\d?).(?<day>\d\d?)(\s+(?<hours>\d\d?):(?<minutes>\d\d)(:(?<seconds>\d\d))?)?$/i,
/^(?<year>\d{4}) (?<month>\d\d?) (?<day>\d\d?)(\s+(?<hours>\d\d?):(?<minutes>\d\d)(:(?<seconds>\d\d))?)?$/i,
/^(?<year>\d{4})\/(?<month>\d\d?)\/(?<day>\d\d?)(\s+(?<hours>\d\d?):(?<minutes>\d\d)(:(?<seconds>\d\d))?)?$/i,
/^(?<hours>\d\d?):(?<minutes>\d\d)(:(?<seconds>\d\d))?\s+(?<year>\d{4})-(?<month>\d\d?)-(?<day>\d\d?)$/i,
/^(?<hours>\d\d?):(?<minutes>\d\d)(:(?<seconds>\d\d))?\s+(?<year>\d{4}).(?<month>\d\d?).(?<day>\d\d?)$/i,
/^(?<hours>\d\d?):(?<minutes>\d\d)(:(?<seconds>\d\d))?\s+(?<year>\d{4}) (?<month>\d\d?) (?<day>\d\d?)$/i,
/^(?<hours>\d\d?):(?<minutes>\d\d)(:(?<seconds>\d\d))?\s+(?<year>\d{4})\/(?<month>\d\d)\/(?<day>\d\d)$/i,
];

function escapeRawArgument(string: string) {
return `'${string.replaceAll("'", "\\'")}'`;
}
Expand Down Expand Up @@ -88,6 +101,49 @@ export const boolean: ArgumentType<boolean> = Object.freeze({
typeName: "BOOLEAN",
});

/**
* Returns the provided CLI argument as a `Date`. This supports local
* dates and times, ISO8601 strings, and the special constants `now`
* (current time) and `today` (start of current day in local time-zone).
*/
export const date: ArgumentType<Date> = Object.freeze({
parse: (raw: string): Date => {
const trimmed = raw.trim().toLowerCase();
if (trimmed.match(ISO_8601)) {
return new Date(trimmed);
} else if (trimmed === "now") {
return new Date();
} else if (trimmed === "today") {
const date = new Date();
date.setHours(0);
date.setMinutes(0);
date.setSeconds(0);
date.setMilliseconds(0);
return date;
} else {
for (const format of LOCAL_DATE_FORMATS) {
const match = trimmed.match(format);
if (match != null) {
const year = parseInt(match.groups!.year);
const month = parseInt(match.groups!.month) - 1;
if (month < 0 || 11 < month) continue;
const day = parseInt(match.groups!.day);
if (day < 1 || 31 < day) continue;
const hours = parseInt(match.groups!.hours ?? "0");
if (hours < 0 || 23 < hours) continue;
const minutes = parseInt(match.groups!.minutes ?? "0");
if (minutes < 0 || 59 < minutes) continue;
const seconds = parseInt(match.groups!.seconds ?? "0");
if (seconds < 0 || 59 < seconds) continue;
return new Date(year, month, day, hours, minutes, seconds);
}
}
throw new Error(`${escapeRawArgument(raw)} is not a valid date`);
}
},
typeName: "DATE",
});

/**
* Returns a value from a provided set of options, if the CLI argument
* matches. Options are matched case-insensitively e.g.
Expand Down
83 changes: 82 additions & 1 deletion src/types_test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { boolean, choice, integer, number, string } from "./types.ts";
import { boolean, choice, date, integer, number, string } from "./types.ts";
import {
assert,
assertEquals,
assertThrows,
} from "https://deno.land/std@0.118.0/testing/asserts.ts";
Expand Down Expand Up @@ -114,6 +115,86 @@ Deno.test("boolean type", () => {
for (const raw of bad) assertThrows(() => boolean.parse(raw));
});

Deno.test("date type", () => {
const dateUTC = (
year: number,
month: number,
day: number,
hours: number,
minutes: number,
seconds: number,
): Date => {
const date = new Date();
date.setUTCFullYear(year);
date.setUTCMonth(month - 1);
date.setUTCDate(day);
date.setUTCHours(hours);
date.setUTCMinutes(minutes);
date.setUTCSeconds(seconds);
date.setUTCMilliseconds(0);
return date;
};
const dateLocal = (
year: number,
month: number,
day: number,
hours: number,
minutes: number,
seconds: number,
): Date => {
const date = new Date();
date.setFullYear(year);
date.setMonth(month - 1);
date.setDate(day);
date.setHours(hours);
date.setMinutes(minutes);
date.setSeconds(seconds);
date.setMilliseconds(0);
return date;
};
const good: [string, Date][] = [
[
"today",
new Date(
new Date().getFullYear(),
new Date().getMonth(),
new Date().getDate(),
),
],
["1999-11-20T12:00:00Z", dateUTC(1999, 11, 20, 12, 0, 0)],
["2021/12/24", dateLocal(2021, 12, 24, 0, 0, 0)],
["2021.12.24", dateLocal(2021, 12, 24, 0, 0, 0)],
["2021-12-24", dateLocal(2021, 12, 24, 0, 0, 0)],
["2006-1-9", dateLocal(2006, 1, 9, 0, 0, 0)],
["17:32:11 2006-1-9", dateLocal(2006, 1, 9, 17, 32, 11)],
["2006/1/9 17:32", dateLocal(2006, 1, 9, 17, 32, 0)],
["17:32:11 2006-1-9", dateLocal(2006, 1, 9, 17, 32, 11)],
["2012-8-16 6:19", dateLocal(2012, 8, 16, 6, 19, 0)],
];
const bad = [
"wfwe",
"",
" ",
"1992/13/09",
"18:46",
"2021/11/11 25:11:52",
"2021/11/11 6:60:52",
];

// Don't directly compare, since the exact time can change
// slightly between the two creations.
const now = date.parse("now");
assert(
Math.abs(now.valueOf() - Date.now()) < 0.01,
"now should return current time",
);

for (const [raw, value] of lowerAndUpperCase(addPadding(good))) {
assertEquals(date.parse(raw), value);
}
for (const raw of bad) assertThrows(() => date.parse(raw));
});

Deno.test("choice type", () => {
const choices = ["a", "b", "c", "test", "Hello", "World"];
const alsoChoices: [string, string][] = [
Expand Down

0 comments on commit b05054e

Please sign in to comment.