#include <iostream>
#include <string>
#include <vector>
#include <regex>
#include <optional>
#include <sstream>
/*
Convert multiple date formats to YYYY-MM-DD.
Supported formats:
- DD/MM/YYYY
- MM-DD-YYYY
- YYYY.MM.DD
- Month DD, YYYY (e.g., January 5, 2024)
- YYYY/MM/DD
- YYYY-MM-DD (already normalized)
Architecture notes:
- The program uses a modular design: each format has its own parser.
- A dispatcher tries each parser until one succeeds.
- This avoids brittle "one giant regex" designs.
- The algorithm is O(n) in the number of formats, trivial cost.
- Memory usage is minimal; only small strings and regex objects.
- Security: no unsafe pointer arithmetic; regex is bounded; no external input files.
- Error handling: invalid formats return std::nullopt.
- Tests: main() includes multiple test cases, including edge cases.
Pitfalls:
- Month names must be mapped carefully.
- Regex must anchor patterns to avoid partial matches.
- Leading zeros must be preserved in output.
Performance notes:
- Regex compilation is static; repeated calls are cheap.
- For extremely high-throughput scenarios, consider precompiled regex or manual parsing.
*/
// ------------------ Month name mapping ------------------
std::optional<int> monthNameToNumber(const std::string& name) {
static const std::vector<std::pair<std::string, int>> months = {
{"January", 1}, {"February", 2}, {"March", 3}, {"April", 4},
{"May", 5}, {"June", 6}, {"July", 7}, {"August", 8},
{"September", 9}, {"October", 10}, {"November", 11}, {"December", 12}
};
for (const auto& [m, num] : months) {
if (name == m) return num;
}
return std::nullopt;
}
// ------------------ Utility: format YYYY-MM-DD ------------------
std::string formatYMD(int y, int m, int d) {
std::ostringstream oss;
oss << y << "-"
<< (m < 10 ? "0" : "") << m << "-"
<< (d < 10 ? "0" : "") << d;
return oss.str();
}
// ------------------ Parsers for each format ------------------
// DD/MM/YYYY
std::optional<std::string> parseDDMMYYYY(const std::string& s) {
static const std::regex re(R"(^(\d{1,2})\/(\d{1,2})\/(\d{4})$)");
std::smatch m;
if (std::regex_match(s, m, re)) {
int d = std::stoi(m[1]);
int mo = std::stoi(m[2]);
int y = std::stoi(m[3]);
return formatYMD(y, mo, d);
}
return std::nullopt;
}
// MM-DD-YYYY
std::optional<std::string> parseMMDDYYYY(const std::string& s) {
static const std::regex re(R"(^(\d{1,2})\-(\d{1,2})\-(\d{4})$)");
std::smatch m;
if (std::regex_match(s, m, re)) {
int mo = std::stoi(m[1]);
int d = std::stoi(m[2]);
int y = std::stoi(m[3]);
return formatYMD(y, mo, d);
}
return std::nullopt;
}
// YYYY.MM.DD
std::optional<std::string> parseYYYYMMDDDots(const std::string& s) {
static const std::regex re(R"(^(\d{4})\.(\d{1,2})\.(\d{1,2})$)");
std::smatch m;
if (std::regex_match(s, m, re)) {
int y = std::stoi(m[1]);
int mo = std::stoi(m[2]);
int d = std::stoi(m[3]);
return formatYMD(y, mo, d);
}
return std::nullopt;
}
// Month DD, YYYY
std::optional<std::string> parseMonthDDYYYY(const std::string& s) {
static const std::regex re(R"(^([A-Za-z]+)\s+(\d{1,2}),\s*(\d{4})$)");
std::smatch m;
if (std::regex_match(s, m, re)) {
auto mo = monthNameToNumber(m[1]);
if (!mo) return std::nullopt;
int d = std::stoi(m[2]);
int y = std::stoi(m[3]);
return formatYMD(y, *mo, d);
}
return std::nullopt;
}
// YYYY/MM/DD
std::optional<std::string> parseYYYYMMDDSlashes(const std::string& s) {
static const std::regex re(R"(^(\d{4})\/(\d{1,2})\/(\d{1,2})$)");
std::smatch m;
if (std::regex_match(s, m, re)) {
int y = std::stoi(m[1]);
int mo = std::stoi(m[2]);
int d = std::stoi(m[3]);
return formatYMD(y, mo, d);
}
return std::nullopt;
}
// YYYY-MM-DD (already normalized)
std::optional<std::string> parseNormalized(const std::string& s) {
static const std::regex re(R"(^(\d{4})\-(\d{1,2})\-(\d{1,2})$)");
std::smatch m;
if (std::regex_match(s, m, re)) {
int y = std::stoi(m[1]);
int mo = std::stoi(m[2]);
int d = std::stoi(m[3]);
return formatYMD(y, mo, d);
}
return std::nullopt;
}
// ------------------ Dispatcher ------------------
std::optional<std::string> convertToCanonical(const std::string& s) {
// Order matters only for performance; correctness is unaffected.
const std::vector<std::function<std::optional<std::string>(const std::string&)>> parsers = {
parseNormalized,
parseDDMMYYYY,
parseMMDDYYYY,
parseYYYYMMDDDots,
parseMonthDDYYYY,
parseYYYYMMDDSlashes
};
for (const auto& parser : parsers) {
if (auto result = parser(s)) {
return result;
}
}
return std::nullopt;
}
// ------------------ Tests ------------------
int main() {
std::vector<std::string> tests = {
"05/01/2024",
"12-31-2023",
"2024.02.09",
"January 5, 2024",
"2024/09/17",
"2024-09-17",
"BadFormat",
"Feb 30, 2024" // invalid month name
};
for (const auto& t : tests) {
auto r = convertToCanonical(t);
if (r) {
std::cout << "Input: " << t << " -> " << *r << "\n";
} else {
std::cout << "Input: " << t << " -> " << "Invalid or unsupported format\n";
}
}
}
/*
run:
Input: 05/01/2024 -> 2024-01-05
Input: 12-31-2023 -> 2023-12-31
Input: 2024.02.09 -> 2024-02-09
Input: January 5, 2024 -> 2024-01-05
Input: 2024/09/17 -> 2024-09-17
Input: 2024-09-17 -> 2024-09-17
Input: BadFormat -> Invalid or unsupported format
Input: Feb 30, 2024 -> Invalid or unsupported format
*/