The Meters That Thought It Was 1920 — JavaScript Bug Hunt

Modelled on New York City's parking meters on January 1, 2020: thousands of the city's meters stopped accepting credit and debit cards on New Year's Day,…

  • Language: JavaScript
  • Layer: Backend
  • Difficulty: Easy
  • Concepts: Time, Parsing
  • Modelled on: NYC parking meters · 2020
  • Visible tests: cards in December 2019 resolve correctly; cards still work on New Year's Day 2020
  • Reward: 50 XP for a complete fix

Briefing

Modelled on New York City's parking meters on January 1, 2020: thousands of the city's meters stopped accepting credit and debit cards on New Year's Day, and each one had to be updated by hand. The city attributed it to a software problem with card expiration dates; the fault was widely reported as a "Y2K20" bug.

This project is an illustrative reconstruction built on the most common way that happens. Many Y2K fixes did not widen years to four digits; they used a windowing pivot — two-digit years below 20 mean 20YY, anything else 19YY. expiry.js still does exactly that, so on New Year's Day 2020 every card expiring in "20" or later suddenly looks like it expired in the 1920s.

Fix resolveYear so the window moves with the current year.

Bug report

BUG-DOT-0101 · Priority: High · Reported by: meter operations

Card expiries are printed "MM/YY". A card is valid through the END of its expiry month.

resolveYear(yy, currentYear) must return the unique year Y such that Y % 100 == yy and currentYear - 80 <= Y < currentYear + 20 e.g. resolveYear(22, 2019) = 2022, resolveYear(20, 2020) = 2020, resolveYear(35, 2031) = 2035, resolveYear(99, 2020) = 1999.

isExpired("MM/YY", { year, month }) is true only when the expiry (year, month) is strictly before today's (year, month).

Observed: since 1 January 2020 every card with an expiry of 20 or later is declined as expired.

Logs

[meter 4417] card ****1881 exp=08/23 -> DECLINED expired (1923-08)
[meter 4417] card ****0342 exp=01/20 -> DECLINED expired (1920-01)
[meter 4417] cash only mode

The code as shipped

src/meter/expiry.js (editable)

// Card expiry handling for the meter's card reader. Expiry is printed "MM/YY".
exports.resolveYear = function (yy, currentYear) {
  return yy < 20 ? 2000 + yy : 1900 + yy;
};

exports.isExpired = function (mmyy, today) {
  var parts = mmyy.split("/");
  var month = parseInt(parts[0], 10);
  var year = exports.resolveYear(parseInt(parts[1], 10), today.year);
  if (year !== today.year) return year < today.year;
  return month < today.month;
};

Read-only context: src/meter/reader.js.

Open the hunt to edit the files, run the visible tests and submit against the hidden ones. More JavaScript bug hunts.