jackwener/OpenCLI · error · ArgumentError
twitter collection --until must be an RFC3339 timestamp
Error message
twitter collection --until must be an RFC3339 timestamp
What it means
normalizeUntil validates the --until flag against an RFC3339 timestamp regex before any numeric or Date checks; a string that doesn't match the RFC3339 shape fails immediately with usage guidance. This ensures the flag is parseable as an exact instant before further validation.
Source
Thrown at clis/twitter/collection.js:28
DEFAULT_USER_TWEETS_PAGE_DELAY_SECONDS,
MAX_USER_TWEETS_LIMIT,
MAX_USER_TWEETS_PAGES,
USER_TWEETS_PAGE_SIZE,
fetchUserTimelinePage,
resolveUserTimelineContext,
} from './user-timeline.js';
const RFC3339_TIMESTAMP = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d+))?(Z|([+-])(\d{2}):(\d{2}))$/;
function isLeapYear(year) {
return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
}
function normalizeUntil(raw) {
const value = String(raw ?? '').trim();
const match = value.match(RFC3339_TIMESTAMP);
if (!match) {
throw new ArgumentError(
'twitter collection --until must be an RFC3339 timestamp',
'Example: opencli twitter collection @jack --until 2026-07-23T00:00:00Z',
);
}
const [, yearRaw, monthRaw, dayRaw, hourRaw, minuteRaw, secondRaw, , zone, , offsetHourRaw, offsetMinuteRaw] = match;
const year = Number(yearRaw);
const month = Number(monthRaw);
const day = Number(dayRaw);
const hour = Number(hourRaw);
const minute = Number(minuteRaw);
const second = Number(secondRaw);
const offsetHour = zone === 'Z' ? 0 : Number(offsetHourRaw);
const offsetMinute = zone === 'Z' ? 0 : Number(offsetMinuteRaw);
const daysInMonth = month === 2
? (isLeapYear(year) ? 29 : 28)
: ([4, 6, 9, 11].includes(month) ? 30 : 31);
if (
month < 1 || month > 12View on GitHub (pinned to 49907e53dc)
Solutions
- Pass a full RFC3339 timestamp such as 2026-07-23T00:00:00Z
- Include a timezone (Z or ±hh:mm offset) — date-only strings will not match
- Convert from your local/epoch format first, e.g. new Date(...).toISOString()
- Use the suggested example from the error hint verbatim as a template
Example fix
// before --until 2026-07-23 // after --until 2026-07-23T00:00:00Z
Defensive patterns
Strategy: validation
Validate before calling
function isRfc3339(s) {
return typeof s === 'string' && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/.test(s.trim());
}
if (!isRfc3339(until)) throw new Error(`--until must be RFC3339, got: ${until}`); Type guard
function isRfc3339Timestamp(v) {
return typeof v === 'string' && !Number.isNaN(new Date(v).getTime()) && /^\d{4}-\d{2}-\d{2}T/.test(v);
} Try / catch
try {
await run(['opencli','twitter','collection',handle,'--until',until]);
} catch (err) {
if (String(err.message).includes('--until must be an RFC3339 timestamp')) {
until = new Date(until).toISOString(); // retry with canonical form
} else throw err;
} Prevention
- Always emit timestamps via new Date(...).toISOString() in scripts
- Never pass date-only strings or natural-language times to --until
- Include the timezone designator (Z or ±hh:mm) explicitly
- Validate the flag with a regex before invoking the CLI
When it happens
Trigger: Calling `opencli twitter collection @user --until` with a value that fails the RFC3339_TIMESTAMP regex, e.g. '2026-07-23', 'yesterday', '2026/07/23 00:00', or an empty string.
Common situations: Users typing date-only values or human phrases like 'now'/'last week'; shell scripts interpolating epoch integers; copy-pasted timestamps with locale formats or missing timezone designators.
Related errors
- twitter collection --limit must be an integer between 1 and
- twitter collection --page-delay must be an integer between 0
- twitter_collection_invalid_timestamp
- 12306 whoami failed: ${probe.detail}
- ${label} is required
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/c0352767218dd745.
Report an issue: GitHub.