Minguo.tw
Home / ROC dates in JavaScript

Converting ROC (Taiwan) Dates in JavaScript

JavaScript's Date object has no ROC or Minguo calendar. To turn 115/07/29 into a real date you add 1911 to the year yourself, and remember that Date's month argument is zero-indexed.

The conversion, as a function

Two things trip people up here: the +1911 offset, and the fact that new Date() takes January as 0, not 1. Both are easy to get wrong silently, because a wrong month just rolls into the next one instead of throwing.

function rocToWestern(rocYear, month, day) {
  if (rocYear <= 0) {
    throw new RangeError("ROC year must be 1 or greater; there is no ROC year 0");
  }
  return new Date(rocYear + 1911, month - 1, day);
}

rocToWestern(115, 7, 29);  // Wed Jul 29 2026 (local time)
rocToWestern(100, 1, 1);   // Sat Jan 01 2011

Check any single conversion against the ROC year converter if you want a second source before shipping a fix.

Advertisement

Parsing the string formats you'll receive

ROC dates arrive as 115/07/29, 115-07-29, 115.7.29, or the compact 1150729 from fixed-width exports. Never hand any of these to new Date(string) or Date.parse() — both interpret an ambiguous string using the browser's own heuristics, which differ between engines and will silently treat 115 as a literal year:

function parseRocDate(text) {
  const sep = text.trim().match(/^(\d{2,3})[/.\-](\d{1,2})[/.\-](\d{1,2})$/);
  const compact = !sep && text.trim().match(/^(\d{3})(\d{2})(\d{2})$/);
  const m = sep || compact;
  if (!m) throw new Error(`unrecognised ROC date: ${text}`);
  const [, rocYear, month, day] = m.map(Number);
  return rocToWestern(rocYear, month, day);
}

parseRocDate("115/07/29");  // Wed Jul 29 2026
parseRocDate("1150729");    // Wed Jul 29 2026

Formatting a Date back into ROC form

For display, Intl.DateTimeFormat can format an existing Date object as an ROC year directly, using the zh-TW-u-ca-roc locale extension — useful if you just need a label and don't want to hand-build the string:

const d = new Date(2026, 6, 29); // 29 July 2026

new Intl.DateTimeFormat("zh-TW-u-ca-roc", {
  year: "numeric", month: "2-digit", day: "2-digit"
}).format(d);
// "民國115/07/29" — note the 民國 era prefix, and that the exact
// output varies with the JS engine’s ICU data. Use the manual version below
// when you need a bare 115/07/29.

// Manual version if you need an exact, engine-independent format:
function westernToRocString(d) {
  const rocYear = d.getFullYear() - 1911;
  if (rocYear <= 0) throw new RangeError("date is before the ROC era (pre-1912)");
  const mm = String(d.getMonth() + 1).padStart(2, "0");
  const dd = String(d.getDate()).padStart(2, "0");
  return `${String(rocYear).padStart(3, "0")}/${mm}/${dd}`;
}

Intl.DateTimeFormat is read-only in this direction — it formats, it does not parse. There is no built-in way to go from an ROC string back to a Date without writing the parser above.

Node.js and browser behave the same way

This is not a browser quirk or a Node quirk; V8, SpiderMonkey and JavaScriptCore all implement the same ECMA-262 Date semantics, so the +1911 conversion and the zero-indexed month apply identically whether the code runs client-side or on a server. There is no separate server-side ROC date type to reach for.

Edge cases that will bite you in production

There is no ROC year 0. A calculation that produces rocYear <= 0 is a bug in the input or the formula, not a valid pre-1912 date — before-ROC years need their own handling, not a pass into rocToWestern.

Two-digit year fields truncate at ROC 100. Legacy exports or old form widgets that only kept two digits show ROC 100 (2011) as 00. See the Y1C write-up for how to detect and repair this in a dataset.

Timezones affect the day, not the year offset. new Date(rocYear + 1911, month - 1, day) constructs a date in the browser's local timezone. If your users and your server are in different zones, a date near midnight can shift by a day when serialised — that's an ordinary JavaScript date-handling issue, not specific to ROC years, but it compounds with them because government forms rarely include a time component to anchor the day.

Frequently asked questions

How do I convert an ROC year to a Western year in JavaScript?

Add 1911 to the ROC year, then remember JavaScript's Date constructor takes a zero-indexed month: new Date(rocYear + 1911, month - 1, day).

Does Intl.DateTimeFormat support the ROC or Minguo calendar?

Yes, indirectly. Passing the locale extension zh-TW-u-ca-roc to Intl.DateTimeFormat formats a JavaScript Date as an ROC year, but it only formats an existing Date — it cannot parse a ROC-year string back into one, so you still need your own parsing function.

Why does new Date(115, 6, 29) give the wrong year?

Because JavaScript's Date constructor treats a two- or three-digit year as a literal year number, not a ROC year — new Date(115, 6, 29) creates 29 July in the year 115 CE. You must add 1911 yourself before calling the constructor.

How do I parse a ROC date string like 115/07/29 in JavaScript?

Split the string on the separator, parse the first segment as the ROC year, add 1911, and build the date with new Date(year, month - 1, day). Do not pass the raw string to new Date() or Date.parse() — both assume a Western year.

Is there an npm package for ROC date conversion?

Several small unofficial packages exist on npm, but none is a widely-adopted standard, and this page has not audited any of them for correctness. For most projects, the handful of lines in the parseRocDate() example here is simpler and easier to audit than adding a dependency.

Advertisement

Related