Pith - wedge
#include "WedgeHeader.h"
#include <QList>
#include <QLocale>

namespace {

	struct Tok { QString text; bool literal; };

	QList<Tok> tokenize(const QString &fmt) {
		QList<Tok> out;
		int i = 0;
		while (i < fmt.length()) {
			if (fmt.mid(i, 6) == "otcDay")  { out.append({"otcDay", false});  i += 6; continue; }
			if (fmt.mid(i, 7) == "otcYear") { out.append({"otcYear", false}); i += 7; continue; }
			const QChar c = fmt.at(i);
			int j = i;
			if (c.isLetter()) {
				while (j < fmt.length() && fmt.at(j) == c) ++j;
				out.append({fmt.mid(i, j - i), false});
			} else {
				while (j < fmt.length() && !fmt.at(j).isLetter()) ++j;
				out.append({fmt.mid(i, j - i), true});
			}
			i = j;
		}
		return out;
	}

	bool isDayTok(const QString &t) {
		return t.compare("dd", Qt::CaseInsensitive) == 0 || t.compare("d", Qt::CaseInsensitive) == 0;
	}

	QString litPattern(const QString &s) {
		QString p;
		int i = 0;
		while (i < s.length()) {
			int j = i;
			if (s.at(i).isSpace()) {
				while (j < s.length() && s.at(j).isSpace()) ++j;
				p += "\\s+";
			} else {
				while (j < s.length() && !s.at(j).isSpace()) ++j;
				p += QRegularExpression::escape(s.mid(i, j - i));
			}
			i = j;
		}
		return p;
	}

	QString tokValue(const QString &t, const QDate &d) {
		const QLocale c = QLocale::c();
		if (t == "otcDay")  return QString::number(WedgeHeader::otcDayOf(d));
		if (t == "otcYear") return QString("0%1").arg(WedgeHeader::otcYearOf(d) % 100, 2, 10, QChar('0'));
		if (t == "EEEE")    return c.toString(d, "dddd").toLower();
		if (t == "EEE")     return c.toString(d, "ddd").toLower();
		if (t == "MMMM")    return c.toString(d, "MMMM").toLower();
		if (t == "MMM")     return c.toString(d, "MMM").toLower();
		if (t == "MM")      return c.toString(d, "MM");
		if (t == "M")       return c.toString(d, "M");
		if (t.compare("dd", Qt::CaseInsensitive) == 0) return c.toString(d, "dd");
		if (t.compare("d",  Qt::CaseInsensitive) == 0) return c.toString(d, "d");
		if (t == "yyyy")    return c.toString(d, "yyyy");
		if (t == "yy")      return c.toString(d, "yy");
		return t;
	}

	QString tokPattern(const QString &t) {
		if (t == "otcDay")  return "(?<otc>\\d+)";
		if (t == "otcYear") return "\\d{1,4}";
		if (t.startsWith('E') || t == "MMM" || t == "MMMM") return "[a-zA-Z]{3,}";
		if (t == "MM" || t == "M") return "\\d{1,2}";
		if (isDayTok(t))    return "0?\\d{1,2}";
		if (t == "yyyy")    return "\\d{4}";
		if (t == "yy")      return "\\d{2}";
		return QRegularExpression::escape(t);
	}

}

QDate WedgeHeader::otcStartFor(const QDate &date) {
	QDate start(date.year(), 3, 20);
	if (date < start) start = start.addYears(-1);
	return start;
}

qint64 WedgeHeader::otcDayOf(const QDate &date) {
	return otcStartFor(date).daysTo(date) + 1;
}

int WedgeHeader::otcYearOf(const QDate &date) {
	return otcStartFor(date).year();
}

QDate WedgeHeader::dateFromOtcDay(qint64 otcDay) {
	return otcStartFor(QDate::currentDate()).addDays(otcDay - 1);
}

QString WedgeHeader::render(const QString &format, const QDate &date) {
	QString out;
	for (const Tok &t : tokenize(format))
		out += t.literal ? t.text : tokValue(t.text, date);
	return out;
}

QRegularExpression WedgeHeader::matchRegex(const QString &format) {
	const QList<Tok> toks = tokenize(format);
	QString p = "^";
	for (int i = 0; i < toks.size(); ++i) {
		const Tok &t = toks.at(i);
		if (!t.literal) { p += tokPattern(t.text); continue; }
		const QString lp = litPattern(t.text);
		p += (i == toks.size() - 1) ? QString("(?:%1)?").arg(lp) : lp;
	}
	p += ".*";
	return QRegularExpression(p, QRegularExpression::CaseInsensitiveOption
		| QRegularExpression::MultilineOption);
}

QRegularExpression WedgeHeader::dateRegex(const QString &format, const QDate &date) {
	const QList<Tok> toks = tokenize(format);
	QString p = "^";
	for (int i = 0; i < toks.size(); ++i) {
		const Tok &t = toks.at(i);
		if (t.literal) {
			const QString lp = litPattern(t.text);
			p += (i == toks.size() - 1) ? QString("(?:%1)?").arg(lp) : lp;
		} else if (isDayTok(t.text)) {
			p += "0?" + QRegularExpression::escape(QString::number(date.day()));
		} else {
			p += QRegularExpression::escape(tokValue(t.text, date));
		}
	}
	p += ".*";
	return QRegularExpression(p, QRegularExpression::CaseInsensitiveOption
		| QRegularExpression::MultilineOption);
}