Двузначные года

Двузначный год — это запись года в формате из двух цифр вместо четырёх. Например:

"99"
"05"
"21"

Подобный формат часто встречается:

  • в устаревших базах данных;
  • в коротких пользовательских формах;
  • в банковских и бухгалтерских системах;
  • при импорте CSV-файлов;
  • в старых API;
  • в форматах даты вида dd/MM/yy.

Главная проблема двузначных годов заключается в неоднозначности. Значение "25" может означать:

  • 1925 год;
  • 2025 год;
  • 2125 год.

Для корректной интернационализации необходимо правильно интерпретировать такие значения в зависимости от локали, календаря и правил приложения.


Работа с двузначными годами в Globalize

Библиотека Globalize использует данные CLDR и предоставляет механизмы форматирования и парсинга дат.

Для работы с двузначными годами используется:

Globalize.dateParser()

или:

Globalize.parseDate()

с шаблонами формата, содержащими:

yy

Формат yy

В шаблонах дат:

Формат Значение
y год без фиксированной длины
yy двузначный год
yyyy четырёхзначный год

Пример:

const parser = Globalize.dateParser({
    skeleton: "yyMd"
});

или:

const parser = Globalize.dateParser({
    raw: "dd/MM/yy"
});

Парсинг двузначного года

Пример

const parser = Globalize.dateParser({
    raw: "dd.MM.yy"
});

const date = parser("15.08.24");

console.log(date);

Результат:

Thu Aug 15 2024

Как Globalize определяет столетие

При парсинге двузначного года библиотека должна определить век.

Обычно используется правило «скользящего окна» — pivot year.

Например:

Ввод Интерпретация
00 2000
24 2024
68 2068
69 1969
99 1999

Точное поведение зависит от реализации underlying parser и ICU-логики.


Проблема Y2K и двузначные года

Исторически двузначные года привели к одной из крупнейших проблем программирования — Y2K.

Старые системы хранили:

98
99
00
01

вместо:

1998
1999
2000
2001

После наступления 2000 года многие программы интерпретировали:

00

как:

1900

Это вызывало:

  • ошибки расчёта возраста;
  • неверные банковские операции;
  • проблемы сортировки;
  • ошибки лицензирования;
  • сбои архивных систем.

Использование parseDate

Базовый пример

const date = Globalize.parseDate(
    "03/11/25",
    {
        raw: "dd/MM/yy"
    }
);

console.log(date);

Использование dateParser

Метод dateParser создаёт переиспользуемую функцию.

Пример

const parser = Globalize.dateParser({
    raw: "MM-dd-yy"
});

console.log(parser("12-31-99"));
console.log(parser("01-01-00"));

Разница между yy и yyyy

Формат yy

15/03/24

Компактный формат, но неоднозначный.

Формат yyyy

15/03/2024

Однозначный и безопасный.


Почему рекомендуется использовать yyyy

Двузначные года создают проблемы:

Неоднозначность

"30"

Может означать:

  • 1930;

Ошибки импорта

Разные системы используют разные pivot year.

Сложности миграции

При переносе старых данных невозможно гарантировать правильность столетия.

Ошибки аналитики

Возраст, статистика и исторические данные могут вычисляться неправильно.


Форматирование двузначного года

Пример

const formatter = Globalize.dateFormatter({
    raw: "dd.MM.yy"
});

console.log(
    formatter(new Date(2025, 4, 12))
);

Результат:

12.05.25

Форматирование четырёхзначного года

const formatter = Globalize.dateFormatter({
    raw: "dd.MM.yyyy"
});

console.log(
    formatter(new Date(2025, 4, 12))
);

Результат:

12.05.2025

Локализация двузначных годов

Разные страны используют разные форматы дат.

США

MM/dd/yy

Пример:

04/25/24

Германия

dd.MM.yy

Пример:

25.04.24

Япония

yy/MM/dd

Пример:

24/04/25

Использование skeleton

Globalize поддерживает skeleton-форматы CLDR.

Пример

const formatter = Globalize.dateFormatter({
    skeleton: "yyMd"
});

В зависимости от локали формат автоматически меняется.


Пример для разных локалей

Globalize.locale("en");

const enFormatter = Globalize.dateFormatter({
    skeleton: "yyMd"
});

console.log(
    enFormatter(new Date(2024, 0, 15))
);

Результат:

1/15/24

Globalize.locale("de");

const deFormatter = Globalize.dateFormatter({
    skeleton: "yyMd"
});

console.log(
    deFormatter(new Date(2024, 0, 15))
);

Результат:

15.1.24

Опасность автоматического определения века

Автоматическое определение столетия удобно, но может приводить к скрытым ошибкам.

Пример

const parser = Globalize.dateParser({
    raw: "yy-MM-dd"
});

console.log(
    parser("40-01-01")
);

Не всегда очевидно:

1940

или:

2040

Явная нормализация года

Во многих проектах двузначные года предварительно преобразуются вручную.

Пример

function normalizeYear(year) {
    return year < 50
        ? 2000 + year
        : 1900 + year;
}

console.log(normalizeYear(24));
console.log(normalizeYear(89));

Результат:

2024
1989

Комбинирование с Globalize

function normalizeDate(input) {
    const parts = input.split("/");

    let year = Number(parts[2]);

    year = year < 50
        ? 2000 + year
        : 1900 + year;

    return `${parts[0]}/${parts[1]}/${year}`;
}

const normalized = normalizeDate("15/08/24");

const parser = Globalize.dateParser({
    raw: "dd/MM/yyyy"
});

console.log(
    parser(normalized)
);

Работа со старыми данными

При миграции legacy-систем часто встречаются таблицы:

id date
1 12/05/87
2 01/11/03
3 15/08/99

Такие данные требуют:

  • определения бизнес-логики;
  • выбора pivot year;
  • анализа исторического диапазона;
  • ручной очистки аномалий.

Валидация двузначных годов

Проверка через регулярное выражение

const regex = /^\d{2}$/;

console.log(regex.test("24"));
console.log(regex.test("2024"));

Проверка полной даты

const regex = /^\d{2}\/\d{2}\/\d{2}$/;

console.log(regex.test("15/08/24"));

Ошибки парсинга

Некорректная дата

const parser = Globalize.dateParser({
    raw: "dd/MM/yy"
});

console.log(
    parser("99/99/99")
);

Результат:

null

Обработка ошибок

const parser = Globalize.dateParser({
    raw: "dd/MM/yy"
});

const result = parser("31/02/24");

if (result === null) {
    console.log("Некорректная дата");
}

Использование ISO-формата вместо двузначных годов

Наиболее безопасный подход:

YYYY-MM-DD

Пример:

2024-08-15

Такой формат:

  • однозначен;
  • легко сортируется;
  • не зависит от локали;
  • совместим с API;
  • удобен для баз данных.

Преобразование двузначного года в ISO

const parser = Globalize.dateParser({
    raw: "dd/MM/yy"
});

const formatter = Globalize.dateFormatter({
    raw: "yyyy-MM-dd"
});

const date = parser("15/08/24");

console.log(
    formatter(date)
);

Особенности работы с часовыми поясами

Двузначный год сам по себе не связан с timezone, однако при преобразовании даты возможно смещение.

Пример

const date = new Date(24, 0, 1);

console.log(date);

Интерпретация конструктора Date отличается от строкового парсинга.


Отличие JavaScript Date от Globalize

JavaScript

new Date(99, 0, 1)

Результат:

1999

Globalize

Globalize.parseDate("01/01/99", {
    raw: "dd/MM/yy"
});

Использует локализованный парсер и CLDR-форматы.


Использование в формах

Короткий пользовательский ввод

<input placeholder="dd/mm/yy">

Удобен для быстрого ввода, но опасен неоднозначностью.


Рекомендуемый подход для UI

Отображение

15.08.24

Хранение

2024-08-15

Внутренние вычисления

Date

Практика корпоративных систем

Во многих enterprise-приложениях двузначные года:

  • запрещаются полностью;
  • разрешаются только для отображения;
  • автоматически конвертируются;
  • валидируются сервером;
  • логируются при импорте.

Производительность парсинга

Создание parser-функции один раз эффективнее многократного вызова parseDate.

Менее эффективно

Globalize.parseDate(value, {
    raw: "dd/MM/yy"
});

внутри цикла.


Более эффективно

const parser = Globalize.dateParser({
    raw: "dd/MM/yy"
});

for (const value of values) {
    parser(value);
}

Поддержка исторических дат

Двузначные года плохо подходят для:

  • архивов;
  • генеалогии;
  • музейных баз;
  • научных данных;
  • юридических документов.

Пример:

12/04/15

Может означать:

  • 1915;
  • 2015;

Тестирование двузначных годов

Особое внимание уделяется граничным значениям.

Пример тестов

[
    "00",
    "01",
    "49",
    "50",
    "68",
    "69",
    "99"
]

Юнит-тестирование

describe("two-digit years", () => {

    const parser = Globalize.dateParser({
        raw: "dd/MM/yy"
    });

    test("parses 24 correctly", () => {
        const date = parser("15/08/24");

        expect(date.getFullYear())
            .toBe(2024);
    });

});

Лучшие практики

Использовать yyyy для хранения

2024

вместо:

24

Использовать yy только для отображения

const formatter = Globalize.dateFormatter({
    raw: "dd.MM.yy"
});

Нормализовать legacy-данные

Перед импортом:

87 → 1987
03 → 2003

Не полагаться на неявный pivot year

Лучше явно задавать правила преобразования.


Валидировать диапазоны

if (year < 1900 || year > 2100) {
    throw new Error("Invalid year");
}

Типичные ошибки

Использование yy для хранения данных

Плохо:

24

Хорошо:

2024

Смешивание локалей

03/04/24

Может означать:

  • 3 апреля;
  • 4 марта.

Использование встроенного Date.parse

Date.parse("01/02/24")

Результат зависит от среды выполнения.


Отсутствие нормализации

"24"

без определения века приводит к нестабильному поведению системы.