feat: since and until

This commit is contained in:
Alejandro Gómez
2025-12-16 14:15:16 +01:00
parent a7059c8e8f
commit 24d2640774
3 changed files with 271 additions and 19 deletions

View File

@@ -391,26 +391,266 @@ describe("parseReqCommand", () => {
});
describe("time flags (--since, --until)", () => {
it("should parse unix timestamp for --since", () => {
const result = parseReqCommand(["--since", "1234567890"]);
expect(result.filter.since).toBe(1234567890);
describe("unix timestamps", () => {
it("should parse unix timestamp for --since", () => {
const result = parseReqCommand(["--since", "1234567890"]);
expect(result.filter.since).toBe(1234567890);
});
it("should parse unix timestamp for --until", () => {
const result = parseReqCommand(["--until", "1234567890"]);
expect(result.filter.until).toBe(1234567890);
});
});
it("should parse relative time for --since (hours)", () => {
const result = parseReqCommand(["--since", "2h"]);
expect(result.filter.since).toBeDefined();
expect(result.filter.since).toBeGreaterThan(0);
describe("relative time - seconds (s)", () => {
it("should parse seconds for --since", () => {
const now = Math.floor(Date.now() / 1000);
const result = parseReqCommand(["--since", "30s"]);
expect(result.filter.since).toBeDefined();
expect(result.filter.since).toBeGreaterThan(now - 35);
expect(result.filter.since).toBeLessThan(now - 25);
});
it("should parse 1 second for --since", () => {
const now = Math.floor(Date.now() / 1000);
const result = parseReqCommand(["--since", "1s"]);
expect(result.filter.since).toBeDefined();
expect(result.filter.since).toBeGreaterThan(now - 5);
expect(result.filter.since).toBeLessThan(now + 1);
});
it("should parse seconds for --until", () => {
const now = Math.floor(Date.now() / 1000);
const result = parseReqCommand(["--until", "30s"]);
expect(result.filter.until).toBeDefined();
expect(result.filter.until).toBeGreaterThan(now - 35);
expect(result.filter.until).toBeLessThan(now - 25);
});
});
it("should parse relative time for --since (days)", () => {
const result = parseReqCommand(["--since", "7d"]);
expect(result.filter.since).toBeDefined();
expect(result.filter.since).toBeGreaterThan(0);
describe("relative time - minutes (m)", () => {
it("should parse minutes for --since", () => {
const now = Math.floor(Date.now() / 1000);
const result = parseReqCommand(["--since", "30m"]);
expect(result.filter.since).toBeDefined();
const diff = now - result.filter.since!;
expect(diff).toBeGreaterThan(1795); // 30m - 5s
expect(diff).toBeLessThan(1805); // 30m + 5s
});
it("should parse 1 minute for --since", () => {
const now = Math.floor(Date.now() / 1000);
const result = parseReqCommand(["--since", "1m"]);
expect(result.filter.since).toBeDefined();
const diff = now - result.filter.since!;
expect(diff).toBeGreaterThan(55); // 1m - 5s
expect(diff).toBeLessThan(65); // 1m + 5s
});
});
it("should parse unix timestamp for --until", () => {
const result = parseReqCommand(["--until", "1234567890"]);
expect(result.filter.until).toBe(1234567890);
describe("relative time - hours (h)", () => {
it("should parse hours for --since", () => {
const now = Math.floor(Date.now() / 1000);
const result = parseReqCommand(["--since", "2h"]);
expect(result.filter.since).toBeDefined();
const diff = now - result.filter.since!;
expect(diff).toBeGreaterThan(7195); // 2h - 5s
expect(diff).toBeLessThan(7205); // 2h + 5s
});
it("should parse 24 hours for --since", () => {
const now = Math.floor(Date.now() / 1000);
const result = parseReqCommand(["--since", "24h"]);
expect(result.filter.since).toBeDefined();
const diff = now - result.filter.since!;
expect(diff).toBeGreaterThan(86395); // 24h - 5s
expect(diff).toBeLessThan(86405); // 24h + 5s
});
});
describe("relative time - days (d)", () => {
it("should parse days for --since", () => {
const now = Math.floor(Date.now() / 1000);
const result = parseReqCommand(["--since", "7d"]);
expect(result.filter.since).toBeDefined();
const diff = now - result.filter.since!;
expect(diff).toBeGreaterThan(604795); // 7d - 5s
expect(diff).toBeLessThan(604805); // 7d + 5s
});
it("should parse 1 day for --since", () => {
const now = Math.floor(Date.now() / 1000);
const result = parseReqCommand(["--since", "1d"]);
expect(result.filter.since).toBeDefined();
const diff = now - result.filter.since!;
expect(diff).toBeGreaterThan(86395); // 1d - 5s
expect(diff).toBeLessThan(86405); // 1d + 5s
});
});
describe("relative time - weeks (w)", () => {
it("should parse weeks for --since", () => {
const now = Math.floor(Date.now() / 1000);
const result = parseReqCommand(["--since", "2w"]);
expect(result.filter.since).toBeDefined();
const diff = now - result.filter.since!;
expect(diff).toBeGreaterThan(1209595); // 2w - 5s
expect(diff).toBeLessThan(1209605); // 2w + 5s
});
it("should parse 1 week for --since", () => {
const now = Math.floor(Date.now() / 1000);
const result = parseReqCommand(["--since", "1w"]);
expect(result.filter.since).toBeDefined();
const diff = now - result.filter.since!;
expect(diff).toBeGreaterThan(604795); // 1w - 5s
expect(diff).toBeLessThan(604805); // 1w + 5s
});
});
describe("relative time - months (mo)", () => {
it("should parse months for --since", () => {
const now = Math.floor(Date.now() / 1000);
const result = parseReqCommand(["--since", "3mo"]);
expect(result.filter.since).toBeDefined();
const diff = now - result.filter.since!;
// 3 months = 7776000 seconds (90 days)
expect(diff).toBeGreaterThan(7775995); // 3mo - 5s
expect(diff).toBeLessThan(7776005); // 3mo + 5s
});
it("should parse 1 month for --since", () => {
const now = Math.floor(Date.now() / 1000);
const result = parseReqCommand(["--since", "1mo"]);
expect(result.filter.since).toBeDefined();
const diff = now - result.filter.since!;
// 1 month = 2592000 seconds (30 days)
expect(diff).toBeGreaterThan(2591995); // 1mo - 5s
expect(diff).toBeLessThan(2592005); // 1mo + 5s
});
});
describe("relative time - years (y)", () => {
it("should parse years for --since", () => {
const now = Math.floor(Date.now() / 1000);
const result = parseReqCommand(["--since", "4y"]);
expect(result.filter.since).toBeDefined();
const diff = now - result.filter.since!;
// 4 years = 126144000 seconds (1460 days)
expect(diff).toBeGreaterThan(126143995); // 4y - 5s
expect(diff).toBeLessThan(126144005); // 4y + 5s
});
it("should parse 1 year for --since", () => {
const now = Math.floor(Date.now() / 1000);
const result = parseReqCommand(["--since", "1y"]);
expect(result.filter.since).toBeDefined();
const diff = now - result.filter.since!;
// 1 year = 31536000 seconds (365 days)
expect(diff).toBeGreaterThan(31535995); // 1y - 5s
expect(diff).toBeLessThan(31536005); // 1y + 5s
});
});
describe("special keyword - now", () => {
it("should parse 'now' for --since", () => {
const before = Math.floor(Date.now() / 1000);
const result = parseReqCommand(["--since", "now"]);
const after = Math.floor(Date.now() / 1000);
expect(result.filter.since).toBeDefined();
expect(result.filter.since).toBeGreaterThanOrEqual(before);
expect(result.filter.since).toBeLessThanOrEqual(after);
});
it("should parse 'now' for --until", () => {
const before = Math.floor(Date.now() / 1000);
const result = parseReqCommand(["--until", "now"]);
const after = Math.floor(Date.now() / 1000);
expect(result.filter.until).toBeDefined();
expect(result.filter.until).toBeGreaterThanOrEqual(before);
expect(result.filter.until).toBeLessThanOrEqual(after);
});
it("should be case-insensitive for 'now'", () => {
const result1 = parseReqCommand(["--since", "NOW"]);
const result2 = parseReqCommand(["--since", "Now"]);
const result3 = parseReqCommand(["--since", "now"]);
expect(result1.filter.since).toBeDefined();
expect(result2.filter.since).toBeDefined();
expect(result3.filter.since).toBeDefined();
});
it("should work with other filters", () => {
const now = Math.floor(Date.now() / 1000);
const result = parseReqCommand(["-k", "1", "--since", "7d", "--until", "now"]);
expect(result.filter.kinds).toEqual([1]);
expect(result.filter.since).toBeLessThan(now);
expect(result.filter.until).toBeGreaterThanOrEqual(now - 1);
expect(result.filter.until).toBeLessThanOrEqual(now + 1);
});
});
describe("invalid time formats", () => {
it("should return undefined for invalid time format", () => {
const result = parseReqCommand(["--since", "invalid"]);
expect(result.filter.since).toBeUndefined();
});
it("should return undefined for time unit without number", () => {
const result = parseReqCommand(["--since", "h"]);
expect(result.filter.since).toBeUndefined();
});
it("should return undefined for unsupported unit", () => {
const result = parseReqCommand(["--since", "5x"]);
expect(result.filter.since).toBeUndefined();
});
it("should return undefined for negative time", () => {
const result = parseReqCommand(["--since", "-5h"]);
expect(result.filter.since).toBeUndefined();
});
});
describe("combined time flags", () => {
it("should parse both --since and --until", () => {
const result = parseReqCommand([
"--since",
"7d",
"--until",
"1d",
"-k",
"1",
]);
expect(result.filter.since).toBeDefined();
expect(result.filter.until).toBeDefined();
expect(result.filter.since).toBeLessThan(result.filter.until!);
expect(result.filter.kinds).toEqual([1]);
});
it("should work with unix timestamps for time range", () => {
const result = parseReqCommand([
"--since",
"1600000000",
"--until",
"1700000000",
]);
expect(result.filter.since).toBe(1600000000);
expect(result.filter.until).toBe(1700000000);
});
it("should work with mixed relative and unix timestamps", () => {
const now = Math.floor(Date.now() / 1000);
const result = parseReqCommand(["--since", "7d", "--until", "1234567890"]);
expect(result.filter.since).toBeDefined();
expect(result.filter.since).toBeLessThan(now);
expect(result.filter.until).toBe(1234567890);
});
});
});

View File

@@ -410,18 +410,24 @@ function isRelayDomain(value: string): boolean {
}
/**
* Parse timestamp - supports unix timestamp, relative time (1h, 30m, 7d)
* Parse timestamp - supports unix timestamp, relative time (1s, 30m, 2h, 7d, 2w, 3mo, 1y), or "now"
*/
function parseTimestamp(value: string): number | null {
if (!value) return null;
// Special keyword: "now" - current timestamp
if (value.toLowerCase() === "now") {
return Math.floor(Date.now() / 1000);
}
// Unix timestamp (10 digits)
if (/^\d{10}$/.test(value)) {
return parseInt(value, 10);
}
// Relative time: 1h, 30m, 7d, 2w
const relativeMatch = value.match(/^(\d+)([smhdw])$/);
// Relative time: 30s, 1m, 2h, 7d, 2w, 3mo, 1y
// Note: Using alternation to support multi-character units like "mo"
const relativeMatch = value.match(/^(\d+)(s|m|h|d|w|mo|y)$/);
if (relativeMatch) {
const amount = parseInt(relativeMatch[1], 10);
const unit = relativeMatch[2];
@@ -433,6 +439,8 @@ function parseTimestamp(value: string): number | null {
h: 3600,
d: 86400,
w: 604800,
mo: 2592000, // 30 days (approximate month)
y: 31536000, // 365 days (approximate year)
};
return now - amount * multipliers[unit];

View File

@@ -200,12 +200,12 @@ export const manPages: Record<string, ManPageEntry> = {
{
flag: "--since <time>",
description:
"Events after timestamp (unix timestamp or relative: 1h, 30m, 7d)",
"Events after timestamp (unix timestamp, relative: 30s, 1m, 2h, 7d, 2w, 3mo, 1y, or 'now')",
},
{
flag: "--until <time>",
description:
"Events before timestamp (unix timestamp or relative: 1h, 30m, 7d)",
"Events before timestamp (unix timestamp, relative: 30s, 1m, 2h, 7d, 2w, 3mo, 1y, or 'now')",
},
{
flag: "--search <text>",
@@ -231,6 +231,9 @@ export const manPages: Record<string, ManPageEntry> = {
"req -k 1 -a npub1...,npub2... Get notes from multiple authors (balances across outbox relays)",
"req -a $me Get all your events (queries your outbox relays)",
"req -k 1 -a $contacts --since 24h Get notes from contacts (queries their outbox relays)",
"req -k 1 -a $contacts --since 7d Get notes from contacts in last week",
"req -k 1 -a $contacts --since 3mo Get notes from contacts in last 3 months",
"req -k 1 -a $contacts --since 1y Get notes from contacts in last year",
"req -p $me -k 1,7 Get replies and reactions to you (queries your inbox relays)",
"req -k 1 -a $me -a $contacts Get notes from you and contacts",
"req -k 9735 -p $me --since 7d Get zaps you received (queries your inbox)",
@@ -238,6 +241,7 @@ export const manPages: Record<string, ManPageEntry> = {
"req -k 9735 -P $contacts Get zaps sent by your contacts",
"req -k 1 -p verbiricha@habla.news Get notes mentioning user (queries their inbox)",
"req -k 1 --since 1h relay.damus.io Get notes from last hour (manual relay override)",
"req -k 1 --since 7d --until now Get notes from last week up to now",
"req -k 1 --close-on-eose Get recent notes and close after EOSE",
"req -t nostr,bitcoin -l 50 Get 50 events tagged #nostr or #bitcoin",
"req --tag a 30023:abc...:article Get events referencing addressable event (#a tag)",