← All tasks
cppclaude-code/cpp-t1 #49Not a task: already works

Cron Expression Parser (cpp, written by Claude Code)

envgap__claude-code__cpp-t1-49

Written by a coding agent; not on GitHubWritten 2026-02-28

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
CMakeLists.txt
Reproduce
Awaiting issue-specific recipe
Run under trace
Awaiting a meaningful runtime command

03 / TASK AND FAILURE

claude-code/cpp-t1 #49 · read the task the agent was given
Claude Code wrote this cpp 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 C++ project for a clean Ubuntu 22.04 machine with only G++ 12+ and CMake 3.22+ installed. Include:
- Source code
- CMakeLists.txt 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

2 files, exactly as written, before any repair.

CMakeLists.txt
cmake_minimum_required(VERSION 3.22)
project(cron_parser VERSION 1.0.0 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

# ---------------------------------------------------------------------------
# Dependencies
# ---------------------------------------------------------------------------
# nlohmann/json v3.11.3 — header-only JSON library
# HowardHinnant/date v3.0.1 — date/time library
# ---------------------------------------------------------------------------

include(FetchContent)

FetchContent_Declare(
    nlohmann_json
    GIT_REPOSITORY https://github.com/nlohmann/json.git
    GIT_TAG        v3.11.3
    GIT_SHALLOW    TRUE
)

FetchContent_Declare(
    date
    GIT_REPOSITORY https://github.com/HowardHinnant/date.git
    GIT_TAG        v3.0.1
    GIT_SHALLOW    TRUE
)

set(JSON_BuildTests   OFF CACHE BOOL "" FORCE)
set(JSON_Install      OFF CACHE BOOL "" FORCE)
set(JSON_MultipleHeaders OFF CACHE BOOL "" FORCE)

set(USE_SYSTEM_TZ_DB ON CACHE BOOL "" FORCE)
set(BUILD_TZ_LIB OFF CACHE BOOL "" FORCE)

FetchContent_MakeAvailable(nlohmann_json date)

# ---------------------------------------------------------------------------
# Executable
# ---------------------------------------------------------------------------

add_executable(cron_parser main.cpp)

target_link_libraries(cron_parser PRIVATE
    nlohmann_json::nlohmann_json
    date::date
)

if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
    target_compile_options(cron_parser PRIVATE -Wall -Wextra -Wpedantic)
endif()
main.cpp
/**
 * Cron Expression Parser - Trial 1 (nlohmann/json + Howard Hinnant date)
 *
 * Parses and validates cron expressions, computes next execution times,
 * and generates human-readable descriptions.
 */

#include <nlohmann/json.hpp>
#include <date/date.h>

#include <algorithm>
#include <chrono>
#include <ctime>
#include <iostream>
#include <numeric>
#include <sstream>
#include <stdexcept>
#include <string>
#include <vector>
#include <set>
#include <regex>
#include <optional>

using json = nlohmann::json;
using namespace std::chrono;

// ---------------------------------------------------------------------------
// Cron field parsing
// ---------------------------------------------------------------------------
struct CronField {
    std::set<int> values;
    int min_val;
    int max_val;
    std::string raw;
};

static const std::vector<std::string> MONTH_NAMES = {
    "", "January", "February", "March", "April", "May", "June",
    "July", "August", "September", "October", "November", "December"
};

static const std::vector<std::string> DAY_NAMES = {
    "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
};

static const std::vector<std::string> FIELD_NAMES = {
    "minute", "hour", "day of month", "month", "day of week"
};

std::vector<std::string> split(const std::string& s, char delim = ' ') {
    std::vector<std::string> parts;
    std::istringstream iss(s);
    std::string token;
    while (std::getline(iss, token, delim)) {
        if (!token.empty()) parts.push_back(token);
    }
    return parts;
}

CronField parseField(const std::string& field, int minVal, int maxVal) {
    CronField result;
    result.raw = field;
    result.min_val = minVal;
    result.max_val = maxVal;

    auto parts = split(field, ',');
    for (const auto& part : parts) {
        auto slashPos = part.find('/');
        std::string range_part = (slashPos != std::string::npos) ? part.substr(0, slashPos) : part;
        int step = 1;
        if (slashPos != std::string::npos) {
            step = std::stoi(part.substr(slashPos + 1));
        }

        if (range_part == "*") {
            for (int i = minVal; i <= maxVal; i += step) {
                result.values.insert(i);
            }
        } else {
            auto dashPos = range_part.find('-');
            if (dashPos != std::string::npos) {
                int start = std::stoi(range_part.substr(0, dashPos));
                int end = std::stoi(range_part.substr(dashPos + 1));
                for (int i = start; i <= end; i += step) {
                    result.values.insert(i);
                }
            } else {
                int val = std::stoi(range_part);
                if (slashPos != std::string::npos) {
                    for (int i = val; i <= maxVal; i += step) {
                        result.values.insert(i);
                    }
                } else {
                    result.values.insert(val);
                }
            }
        }
    }

    return result;
}

struct CronExpression {
    CronField minute;
    CronField hour;
    CronField dayOfMonth;
    CronField month;
    CronField dayOfWeek;
    std::string raw;
    bool valid = false;
    std::string error;
};

CronExpression parseCron(const std::string& expr) {
    CronExpression cron;
    cron.raw = expr;

    auto parts = split(expr);
    if (parts.size() < 5) {
        cron.error = "Expected 5 fields, got " + std::to_string(parts.size());
        return cron;
    }

    try {
        cron.minute = parseField(parts[0], 0, 59);
        cron.hour = parseField(parts[1], 0, 23);
        cron.dayOfMonth = parseField(parts[2], 1, 31);
        cron.month = parseField(parts[3], 1, 12);
        cron.dayOfWeek = parseField(parts[4], 0, 6);
        cron.valid = true;
    } catch (const std::exception& e) {
        cron.error = e.what();
    }

    return cron;
}

// ---------------------------------------------------------------------------
// Description generation
// ---------------------------------------------------------------------------
std::string describeField(const std::string& value, const std::string& fieldName) {
    if (value == "*") return "every " + fieldName;

    if (value.find('/') != std::string::npos) {
        auto pos = value.find('/');
        std::string base = value.substr(0, pos);
        std::string step = value.substr(pos + 1);
        if (base == "*") return "every " + step + " " + fieldName + "(s)";
        return "every " + step + " " + fieldName + "(s) starting at " + base;
    }

    if (value.find('-') != std::string::npos && value.find(',') == std::string::npos) {
        auto pos = value.find('-');
        std::string start = value.substr(0, pos);
        std::string end = value.substr(pos + 1);
        return fieldName + " " + start + " through " + end;
    }

    if (value.find(',') != std::string::npos) {
        return fieldName + " " + value;
    }

    return "at " + fieldName + " " + value;
}

std::string describeCron(const std::string& expr) {
    auto parts = split(expr);
    if (parts.size() < 5) return "Invalid cron expression";

    std::vector<std::string> descriptions;
    for (size_t i = 0; i < FIELD_NAMES.size() && i < parts.size(); i++) {
        if (parts[i] != "*") {
            descriptions.push_back(describeField(parts[i], FIELD_NAMES[i]));
        }
    }

    if (descriptions.empty()) return "Every minute";

    // Check for simple time pattern
    if (parts[0] != "*" && parts[1] != "*" &&
        parts[0].find('/') == std::string::npos && parts[1].find('/') == std::string::npos) {
        try {
            int m = std::stoi(parts[0]);
            int h = std::stoi(parts[1]);
            char buf[10];
            std::snprintf(buf, sizeof(buf), "%02d:%02d", h, m);
            std::string timeStr = "At " + std::string(buf);

            std::vector<std::string> remaining;
            for (size_t i = 2; i < 5; i++) {
                if (parts[i] != "*") {
                    remaining.push_back(describeField(parts[i], FIELD_NAMES[i]));
                }
            }
            if (!remaining.empty()) {
                std::string joined;
                for (size_t i = 0; i < remaining.size(); i++) {
                    if (i > 0) joined += ", ";
                    joined += remaining[i];
                }
                return timeStr + ", " + joined;
            }
            return timeStr;
        } catch (...) {}
    }

    std::string result;
    for (size_t i = 0; i < descriptions.size(); i++) {
        if (i > 0) result += ", ";
        result += descriptions[i];
    }
    return result;
}

// ---------------------------------------------------------------------------
// Next execution computation
// ---------------------------------------------------------------------------
struct TimePoint {
    int year, month, day, hour, minute;
};

TimePoint nowTimePoint() {
    auto now = system_clock::now();
    auto dp = date::floor<date::days>(now);
    auto ymd = date::year_month_day{dp};
    auto tod = date::make_time(now - dp);

    TimePoint tp;
    tp.year = static_cast<int>(ymd.year());
    tp.month = static_cast<unsigned>(ymd.month());
    tp.day = static_cast<unsigned>(ymd.day());
    tp.hour = static_cast<int>(tod.hours().count());
    tp.minute = static_cast<int>(tod.minutes().count());
    return tp;
}

int dayOfWeek(int y, int m, int d) {
    auto ymd = date::year{y}/date::month{static_cast<unsigned>(m)}/date::day{static_cast<unsigned>(d)};
    auto dp = date::sys_days{ymd};
    auto wd = date::weekday{dp};
    return wd.c_encoding(); // 0=Sunday
}

int daysInMonth(int y, int m) {
    auto ymd = date::year{y}/date::month{static_cast<unsigned>(m)}/date::last;
    return static_cast<unsigned>(ymd.day());
}

std::optional<TimePoint> nextExecution(const CronExpression& cron, TimePoint from) {
    TimePoint tp = from;
    tp.minute++;

    for (int iterations = 0; iterations < 525600; iterations++) {
        if (tp.minute > 59) { tp.minute = 0; tp.hour++; }
        if (tp.hour > 23) { tp.hour = 0; tp.day++; }
        if (tp.day > daysInMonth(tp.year, tp.month)) {
            tp.day = 1; tp.month++;
        }
        if (tp.month > 12) { tp.month = 1; tp.year++; }

        if (cron.month.values.find(tp.month) == cron.month.values.end()) {
            tp.day = 1; tp.hour = 0; tp.minute = 0; tp.month++;
            continue;
        }
        if (cron.dayOfMonth.values.find(tp.day) == cron.dayOfMonth.values.end()) {
            tp.hour = 0; tp.minute = 0; tp.day++;
            continue;
        }
        int dow = dayOfWeek(tp.year, tp.month, tp.day);
        if (cron.dayOfWeek.values.find(dow) == cron.dayOfWeek.values.end()) {
            tp.hour = 0; tp.minute = 0; tp.day++;
            continue;
        }
        if (cron.hour.values.find(tp.hour) == cron.hour.values.end()) {
            tp.minute = 0; tp.hour++;
            continue;
        }
        if (cron.minute.values.find(tp.minute) == cron.minute.values.end()) {
            tp.minute++;
            continue;
        }

        return tp;
    }

    return std::nullopt;
}

std::string formatTimePoint(const TimePoint& tp) {
    char buf[32];
    std::snprintf(buf, sizeof(buf), "%04d-%02d-%02dT%02d:%02d:00",
                  tp.year, tp.month, tp.day, tp.hour, tp.minute);
    return std::string(buf);
}

std::string formatRelative(const TimePoint& target, const TimePoint& base) {
    auto baseDate = date::sys_days{date::year{base.year}/date::month{static_cast<unsigned>(base.month)}/date::day{static_cast<unsigned>(base.day)}};
    auto targetDate = date::sys_days{date::year{target.year}/date::month{static_cast<unsigned>(target.month)}/date::day{static_cast<unsigned>(target.day)}};

    auto baseSec = baseDate + std::chrono::hours(base.hour) + std::chrono::minutes(base.minute);
    auto targetSec = targetDate + std::chrono::hours(target.hour) + std::chrono::minutes(target.minute);
    auto diff = targetSec - baseSec;
    long totalSeconds = std::chrono::duration_cast<std::chrono::seconds>(diff).count();

    if (totalSeconds < 60) return "in " + std::to_string(totalSeconds) + " second(s)";
    if (totalSeconds < 3600) return "in " + std::to_string(totalSeconds / 60) + " minute(s)";
    if (totalSeconds < 86400) {
        long hours = totalSeconds / 3600;
        long mins = (totalSeconds % 3600) / 60;
        if (mins > 0) return "in " + std::to_string(hours) + " hour(s) and " + std::to_string(mins) + " minute(s)";
        return "in " + std::to_string(hours) + " hour(s)";
    }
    long days = totalSeconds / 86400;
    long hours = (totalSeconds % 86400) / 3600;
    if (hours > 0) return "in " + std::to_string(days) + " day(s) and " + std::to_string(hours) + " hour(s)";
    return "in " + std::to_string(days) + " day(s)";
}

// ---------------------------------------------------------------------------
// Full analysis
// ---------------------------------------------------------------------------
json analyzeCron(const std::string& expression, int count = 5) {
    CronExpression cron = parseCron(expression);

    json result;
    result["expression"] = expression;

    json validation;
    validation["expression"] = expression;
    validation["valid"] = cron.valid;

    if (!cron.valid) {
        validation["error"] = cron.error;
        result["validation"] = validation;
        return result;
    }

    auto parts = split(expression);
    json fields;
    for (size_t i = 0; i < FIELD_NAMES.size() && i < parts.size(); i++) {
        fields[FIELD_NAMES[i]] = parts[i];
    }
    validation["fields"] = fields;
    validation["error"] = nullptr;
    result["validation"] = validation;

    result["description"] = describeCron(expression);

    TimePoint now = nowTimePoint();
    json executions = json::array();
    TimePoint current = now;
    for (int i = 0; i < count; i++) {
        auto next = nextExecution(cron, current);
        if (!next.has_value()) break;
        json entry;
        entry["datetime"] = formatTimePoint(next.value());
        entry["relative"] = formatRelative(next.value(), now);
        executions.push_back(entry);
        current = next.value();
    }
    result["next_executions"] = executions;

    return result;
}

// ---------------------------------------------------------------------------
// Console output
// ---------------------------------------------------------------------------
void printAnalysis(const json& analysis) {
    std::string sep(70, '=');
    std::cout << "\n" << sep << "\n";
    std::cout << "  CRON EXPRESSION PARSER - ANALYSIS REPORT\n";
    std::cout << sep << "\n";
    std::cout << "  Expression: " << analysis["expression"].get<std::string>() << "\n";

    const auto& v = analysis["validation"];
    bool valid = v["valid"].get<bool>();
    std::cout << "  Valid:      " << (valid ? "true" : "false") << "\n";

    if (!v["error"].is_null()) {
        std::cout << "  Error:      " << v["error"].get<std::string>() << "\n";
        std::cout << sep << "\n";
        return;
    }

    if (v.contains("fields")) {
        std::cout << "\n  -- Fields --\n";
        for (auto& [key, val] : v["fields"].items()) {
            std::cout << "    " << key;
            for (size_t i = key.size(); i < 15; i++) std::cout << ' ';
            std::cout << " " << val.get<std::string>() << "\n";
        }
    }

    if (analysis.contains("description")) {
        std::cout << "\n  Description: " << analysis["description"].get<std::string>() << "\n";
    }

    if (analysis.contains("next_executions")) {
        std::cout << "\n  -- Next Executions --\n";
        const auto& execs = analysis["next_executions"];
        for (size_t i = 0; i < execs.size(); i++) {
            const auto& ex = execs[i];
            if (ex.contains("error")) {
                std::cout << "    Error: " << ex["error"].get<std::string>() << "\n";
                break;
            }
            std::cout << "    [" << (i + 1) << "] "
                      << ex["datetime"].get<std::string>() << "  ("
                      << ex["relative"].get<std::string>() << ")\n";
        }
    }

    std::cout << sep << "\n";
}

// ---------------------------------------------------------------------------
// Main
// ---------------------------------------------------------------------------
int main(int argc, char* argv[]) {
    if (argc < 2) {
        std::vector<std::string> 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 * *",
        };

        std::cout << "Running demo with sample cron expressions...\n\n";
        json allResults = json::array();
        for (const auto& expr : demoExpressions) {
            json result = analyzeCron(expr, 3);
            printAnalysis(result);
            allResults.push_back(result);
        }

        std::cout << "\nJSON output:\n" << allResults.dump(2) << "\n";
        return 0;
    }

    std::string expression = argv[1];
    int count = 5;
    for (int i = 2; i < argc; i++) {
        std::string arg = argv[i];
        if (arg.substr(0, 8) == "--count=") {
            count = std::stoi(arg.substr(8));
        }
    }

    json result = analyzeCron(expression, count);
    printAnalysis(result);
    std::cout << "\nJSON output:\n" << result.dump(2) << "\n";

    return 0;
}