← All tasks
javascriptcodex/javascript-t1 #49Not a task: already works

Cron Expression Parser (javascript, written by Codex)

envgap__codex__javascript-t1-49

Written by a coding agent; not on GitHubWritten 2026-03-03

01 / FAILURE SIGNATURE

As the study recorded it

No identifying execution failure has been captured.
Not a benchmark task.
  • The project already builds and runs before the fix, so there is nothing to repair.

02 / ENVIRONMENT RECIPE

Base commit
Not freshly verified
Manifest
package.json
Reproduce
Awaiting issue-specific recipe
Run under trace
Awaiting a meaningful runtime command

03 / TASK AND FAILURE

codex/javascript-t1 #49 · read the task the agent was given
Codex wrote this javascript project from the task below. It installed and ran on a clean Ubuntu 22.04 machine as written.

Task given to the agent:

TASK: Cron Expression Parser

Write a program that parses, validates, and explains cron expressions, computing the next N execution times and generating human-readable descriptions of the schedule.

FUNCTIONAL REQUIREMENTS:
- Accept a cron expression as a command-line argument (standard 5-field format: minute, hour, day-of-month, month, day-of-week)
- Support extended 6-field format with seconds as the first field via --with-seconds flag
- Support special characters: * (all), , (list), - (range), / (step), L (last), W (nearest weekday), # (nth day of week), and ? (any)
- Validate the expression and report clear errors for invalid fields with the specific position and reason (e.g., "Field 2 (hour): value 25 exceeds maximum 23")
- Compute and display the next N execution times via --next flag (default: 10), formatted in ISO 8601 and local human-readable format, relative to current time or a specified start time via --from flag
- Generate a human-readable English description of the schedule (e.g., "At 03:30 AM, every Monday and Wednesday, in January through March")
- Support time zone handling via --timezone flag (default: UTC)
- Compute the interval statistics: average gap between executions, minimum gap, maximum gap, total executions per day/week/month/year
- Support checking if a specific datetime matches the cron expression via --check flag
- Support converting between cron expression and a verbal description via --describe flag (cron to English) and --parse flag (English-like input to cron expression for common patterns)
- Print results to console in a formatted display
- Save results as JSON with --output flag (default: cron_analysis.json)
- Support batch validation of multiple expressions from a file via --batch flag
- If no expression is given, demonstrate with several example expressions: "*/5 * * * *" (every 5 minutes), "0 9 * * 1-5" (weekdays at 9am), "0 0 1,15 * *" (1st and 15th at midnight), "30 4 * * SUN" (Sundays at 4:30am), showing next 5 runs and descriptions for each
- Handle errors: invalid syntax, out-of-range values, impossible combinations (e.g., Feb 31), and malformed special characters

Create a complete JavaScript project for a clean Ubuntu 22.04 machine with only Node.js 20+ (LTS) installed. Include:
- Source code
- package.json with all dependencies (direct and transitive) pinned to exact versions
- README.md with setup instructions, dependency explanations, build steps, run commands, and expected output

04 / LABELS

Labels from the report text only; not yet run

No supported category has been assigned.

Label rules and the text that matched
[]

05 / FILES

The project as the agent wrote it

4 files, exactly as written, before any repair.

package-lock.json
{
  "name": "cron-expression-parser-trial1",
  "version": "1.0.0",
  "lockfileVersion": 3,
  "requires": true,
  "packages": {
    "": {
      "name": "cron-expression-parser-trial1",
      "version": "1.0.0",
      "dependencies": {
        "chalk": "4.1.2",
        "cron-parser": "4.9.0"
      }
    },
    "node_modules/ansi-styles": {
      "version": "4.3.0",
      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
      "license": "MIT",
      "dependencies": {
        "color-convert": "^2.0.1"
      },
      "engines": {
        "node": ">=8"
      },
      "funding": {
        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
      }
    },
    "node_modules/chalk": {
      "version": "4.1.2",
      "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
      "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
      "license": "MIT",
      "dependencies": {
        "ansi-styles": "^4.1.0",
        "supports-color": "^7.1.0"
      },
      "engines": {
        "node": ">=10"
      },
      "funding": {
        "url": "https://github.com/chalk/chalk?sponsor=1"
      }
    },
    "node_modules/color-convert": {
      "version": "2.0.1",
      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
      "license": "MIT",
      "dependencies": {
        "color-name": "~1.1.4"
      },
      "engines": {
        "node": ">=7.0.0"
      }
    },
    "node_modules/color-name": {
      "version": "1.1.4",
      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
      "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
      "license": "MIT"
    },
    "node_modules/cron-parser": {
      "version": "4.9.0",
      "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.9.0.tgz",
      "integrity": "sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==",
      "license": "MIT",
      "dependencies": {
        "luxon": "^3.2.1"
      },
      "engines": {
        "node": ">=12.0.0"
      }
    },
    "node_modules/has-flag": {
      "version": "4.0.0",
      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
      "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
      "license": "MIT",
      "engines": {
        "node": ">=8"
      }
    },
    "node_modules/luxon": {
      "version": "3.7.2",
      "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.2.tgz",
      "integrity": "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==",
      "license": "MIT",
      "engines": {
        "node": ">=12"
      }
    },
    "node_modules/supports-color": {
      "version": "7.2.0",
      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
      "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
      "license": "MIT",
      "dependencies": {
        "has-flag": "^4.0.0"
      },
      "engines": {
        "node": ">=8"
      }
    }
  }
}
package.json
{
  "name": "cron-expression-parser-trial1",
  "version": "1.0.0",
  "description": "Cron Expression Parser using cron-parser + chalk",
  "main": "src/index.js",
  "scripts": {
    "start": "node src/index.js"
  },
  "dependencies": {
    "cron-parser": "4.9.0",
    "chalk": "4.1.2"
  }
}
README.md
# Cron Expression Parser (JavaScript)

## Requirements
- Node.js 20+

## Install
```bash
npm install
```

## Run
```bash
npm start -- "*/5 * * * *" --count=10
```

Demo mode:
```bash
npm start -- --demo
```

## Dependencies
- `cron-parser@4.9.0`
- `chalk@4.1.2`
src/index.js
#!/usr/bin/env node
/**
 * Cron Expression Parser - Trial 1 (cron-parser + chalk)
 *
 * Parses and validates cron expressions, computes next execution times,
 * and generates human-readable descriptions.
 */

const cronParser = require('cron-parser');
const chalk = require('chalk');

// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
const MONTH_NAMES = [
    '', 'January', 'February', 'March', 'April', 'May', 'June',
    'July', 'August', 'September', 'October', 'November', 'December'
];

const DAY_NAMES = [
    'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'
];

const FIELD_NAMES = ['minute', 'hour', 'day of month', 'month', 'day of week'];

// ---------------------------------------------------------------------------
// Validation
// ---------------------------------------------------------------------------
function validateCron(expression) {
    const result = {
        expression: expression,
        valid: false,
        fields: {},
        error: null
    };

    const parts = expression.trim().split(/\s+/);
    if (parts.length < 5 || parts.length > 7) {
        result.error = `Expected 5 fields (standard cron), got ${parts.length}. ` +
            'Format: minute hour day-of-month month day-of-week';
        return result;
    }

    try {
        cronParser.parseExpression(expression);
        result.valid = true;
    } catch (err) {
        result.error = err.message;
        return result;
    }

    for (let i = 0; i < FIELD_NAMES.length && i < parts.length; i++) {
        result.fields[FIELD_NAMES[i]] = parts[i];
    }

    return result;
}

// ---------------------------------------------------------------------------
// Human-readable description
// ---------------------------------------------------------------------------
function describeField(value, fieldName) {
    if (value === '*') {
        return `every ${fieldName}`;
    }

    if (value.includes('/')) {
        const [base, step] = value.split('/');
        if (base === '*') {
            return `every ${step} ${fieldName}(s)`;
        }
        return `every ${step} ${fieldName}(s) starting at ${base}`;
    }

    if (value.includes('-') && !value.includes(',')) {
        let [start, end] = value.split('-');
        if (fieldName === 'month') {
            const s = parseInt(start), e = parseInt(end);
            if (!isNaN(s) && s >= 1 && s <= 12) start = MONTH_NAMES[s];
            if (!isNaN(e) && e >= 1 && e <= 12) end = MONTH_NAMES[e];
        } else if (fieldName === 'day of week') {
            const s = parseInt(start), e = parseInt(end);
            if (!isNaN(s) && s >= 0 && s <= 6) start = DAY_NAMES[s];
            if (!isNaN(e) && e >= 0 && e <= 6) end = DAY_NAMES[e];
        }
        return `${fieldName} ${start} through ${end}`;
    }

    if (value.includes(',')) {
        let items = value.split(',');
        if (fieldName === 'month') {
            items = items.map(i => {
                const n = parseInt(i);
                return (!isNaN(n) && n >= 1 && n <= 12) ? MONTH_NAMES[n] : i;
            });
        } else if (fieldName === 'day of week') {
            items = items.map(i => {
                const n = parseInt(i);
                return (!isNaN(n) && n >= 0 && n <= 6) ? DAY_NAMES[n] : i;
            });
        }
        return `${fieldName} ${items.join(', ')}`;
    }

    let display = value;
    if (fieldName === 'month') {
        const n = parseInt(value);
        if (!isNaN(n) && n >= 1 && n <= 12) display = MONTH_NAMES[n];
    } else if (fieldName === 'day of week') {
        const n = parseInt(value);
        if (!isNaN(n) && n >= 0 && n <= 6) display = DAY_NAMES[n];
    }

    return `at ${fieldName} ${display}`;
}

function describeCron(expression) {
    const parts = expression.trim().split(/\s+/);
    if (parts.length < 5) return 'Invalid cron expression';

    const descriptions = [];
    for (let i = 0; i < FIELD_NAMES.length; i++) {
        if (parts[i] !== '*') {
            descriptions.push(describeField(parts[i], FIELD_NAMES[i]));
        }
    }

    if (descriptions.length === 0) return 'Every minute';

    const minuteVal = parts[0];
    const hourVal = parts[1];

    if (minuteVal !== '*' && hourVal !== '*' &&
        !minuteVal.includes('/') && !hourVal.includes('/')) {
        const m = parseInt(minuteVal);
        const h = parseInt(hourVal);
        if (!isNaN(m) && !isNaN(h)) {
            const timeStr = `At ${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}`;
            const remaining = [];
            for (let i = 2; i < 5; i++) {
                if (parts[i] !== '*') {
                    remaining.push(describeField(parts[i], FIELD_NAMES[i]));
                }
            }
            if (remaining.length > 0) {
                return `${timeStr}, ${remaining.join(', ')}`;
            }
            return timeStr;
        }
    }

    return descriptions.join(', ');
}

// ---------------------------------------------------------------------------
// Next executions
// ---------------------------------------------------------------------------
function formatRelative(targetMs, baseMs) {
    const diffSec = Math.floor((targetMs - baseMs) / 1000);

    if (diffSec < 60) return `in ${diffSec} second(s)`;
    if (diffSec < 3600) return `in ${Math.floor(diffSec / 60)} minute(s)`;
    if (diffSec < 86400) {
        const hours = Math.floor(diffSec / 3600);
        const mins = Math.floor((diffSec % 3600) / 60);
        return mins > 0 ? `in ${hours} hour(s) and ${mins} minute(s)` : `in ${hours} hour(s)`;
    }
    const days = Math.floor(diffSec / 86400);
    const hours = Math.floor((diffSec % 86400) / 3600);
    return hours > 0 ? `in ${days} day(s) and ${hours} hour(s)` : `in ${days} day(s)`;
}

function nextExecutions(expression, count = 5, baseTime = null) {
    const base = baseTime ? new Date(baseTime) : new Date();
    const results = [];

    try {
        const interval = cronParser.parseExpression(expression, { currentDate: base });
        for (let i = 0; i < count; i++) {
            const next = interval.next().toDate();
            results.push({
                datetime: next.toISOString(),
                relative: formatRelative(next.getTime(), base.getTime())
            });
        }
    } catch (err) {
        results.push({ error: err.message });
    }

    return results;
}

function timeWindowExecutions(expression, startStr, endStr) {
    const start = new Date(startStr);
    const end = new Date(endStr);
    const results = [];

    try {
        const interval = cronParser.parseExpression(expression, {
            currentDate: new Date(start.getTime() - 1000)
        });

        while (true) {
            const next = interval.next().toDate();
            if (next > end) break;
            results.push(next.toISOString());
            if (results.length > 10000) break;
        }
    } catch (err) {
        results.push({ error: err.message });
    }

    return results;
}

// ---------------------------------------------------------------------------
// Full analysis
// ---------------------------------------------------------------------------
function analyzeCron(expression, count = 5, baseTime = null, windowStart = null, windowEnd = null) {
    const validation = validateCron(expression);
    const result = { expression, validation };

    if (!validation.valid) return result;

    result.description = describeCron(expression);
    result.next_executions = nextExecutions(expression, count, baseTime);

    if (windowStart && windowEnd) {
        const windowTimes = timeWindowExecutions(expression, windowStart, windowEnd);
        result.window = {
            start: windowStart,
            end: windowEnd,
            count: windowTimes.length,
            executions: windowTimes.slice(0, 100)
        };
    }

    return result;
}

// ---------------------------------------------------------------------------
// Console output
// ---------------------------------------------------------------------------
function printAnalysis(analysis) {
    const sep = '='.repeat(70);
    console.log(`\n${sep}`);
    console.log(chalk.bold.cyan('  CRON EXPRESSION PARSER - ANALYSIS REPORT'));
    console.log(sep);
    console.log(`  Expression: ${chalk.yellow(analysis.expression)}`);

    const v = analysis.validation;
    console.log(`  Valid:      ${v.valid ? chalk.green('true') : chalk.red('false')}`);
    if (v.error) {
        console.log(`  Error:      ${chalk.red(v.error)}`);
        console.log(sep);
        return;
    }

    if (v.fields && Object.keys(v.fields).length > 0) {
        console.log(chalk.bold('\n  -- Fields --'));
        for (const [name, value] of Object.entries(v.fields)) {
            console.log(`    ${name.padEnd(15)} ${chalk.white(value)}`);
        }
    }

    if (analysis.description) {
        console.log(`\n  Description: ${chalk.green(analysis.description)}`);
    }

    if (analysis.next_executions) {
        console.log(chalk.bold('\n  -- Next Executions --'));
        analysis.next_executions.forEach((ex, i) => {
            if (ex.error) {
                console.log(`    ${chalk.red('Error:')} ${ex.error}`);
                return;
            }
            console.log(`    [${i + 1}] ${chalk.white(ex.datetime)}  (${chalk.dim(ex.relative)})`);
        });
    }

    if (analysis.window) {
        const w = analysis.window;
        console.log(chalk.bold('\n  -- Time Window --'));
        console.log(`    From:  ${w.start}`);
        console.log(`    To:    ${w.end}`);
        console.log(`    Count: ${chalk.yellow(w.count)} executions`);
        w.executions.slice(0, 10).forEach(ts => {
            console.log(`      ${ts}`);
        });
        if (w.count > 10) {
            console.log(`      ... and ${w.count - 10} more`);
        }
    }

    console.log(sep);
}

// ---------------------------------------------------------------------------
// Main
// ---------------------------------------------------------------------------
function main() {
    const args = process.argv.slice(2);

    if (args.length === 0 || args.includes('--demo')) {
        const demoExpressions = [
            '*/5 * * * *',
            '0 9 * * 1-5',
            '0 0 1 * *',
            '30 */2 * * *',
            '0 0 * * 0',
            '15 14 1 * *',
            '0 22 * * 1-5',
            '*/15 9-17 * * 1-5',
            '0 0 1,15 * *',
            'invalid cron expression',
        ];

        console.log(chalk.bold('Running demo with sample cron expressions...\n'));
        const allResults = [];
        for (const expr of demoExpressions) {
            const result = analyzeCron(expr, 3);
            printAnalysis(result);
            allResults.push(result);
        }
        return;
    }

    const expression = args[0];
    const count = parseInt(args.find(a => a.startsWith('--count='))?.split('=')[1]) || 5;
    const result = analyzeCron(expression, count);
    printAnalysis(result);
}

main();