Каковы некоторые примеры ошибок високосного года?

Ошибка високосного года - это дефект кода, который создает проблемный, непреднамеренный результат при выполнении в контексте високосного года, как правило, в рамках системы григорианского календаря.

Последним високосным годом был 2016 год. Следующими високосными годами являются 2020 и 2024 годы.

Есть два атрибута, которые являются уникальными для високосных лет:

  • У високосных годов 29 февраля, а у обычных - нет.
  • Всего високосные годы имеют 366 дней, а обычные годы - только 365.

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

Ошибки високосного года обычно делятся на две категории воздействия:

  • Категория 1. Те, которые приводят к ошибочным условиям, таким как исключения, коды возврата ошибок, неинициализированные переменные или бесконечные циклы
  • Категория 2: те, которые приводят к неправильным данным, таким как неконкретные проблемы в запросах диапазона или агрегации

При каждом ответе просьба указывать язык программирования и / или платформу, а также категорию воздействия, определенную выше. (Следуйте шаблону, используемому в существующих ответах, пожалуйста.)

Пожалуйста, создайте один отдельный ответ для каждого языка и типа дефекта и проголосуйте за понравившегося, особенно тех, с которыми вы лично столкнулись (оставляя комментарии с анекдотами, где это возможно).

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

2 ответа

.NET / C# - Конструкция из финиковых деталей

Категория воздействия 1

Дефектный код

DateTime dt = DateTime.Now;
DateTime result = new DateTime(dt.Year + 1, dt.Month, dt.Day);

Этот код будет работать правильно, пока dt становится 29 февраля. Затем он попытается создать 29 февраля обычного года, которого не существует. DateTime конструктор бросит ArgumentOutOfRangeException,

Вариации включают любую форму DateTime или же DateTimeOffset конструктор, который принимает параметры года, месяца и дня, когда эти значения получены из разных источников или обрабатываются без учета достоверности в целом.

Исправленный код

DateTime dt = DateTime.Now;
DateTime result = dt.AddYears(1);

Win32 / C++ - SYSTEMTIME манипулирование структурой

Категория воздействия 1

Дефектный код

SYSTEMTIME st;
FILETIME ft;

GetSystemTime(&st);
st.wYear++;

SystemTimeToFileTime(&st, &ft);

Этот код будет работать правильно, пока st становится 29 февраля. Затем он попытается создать 29 февраля обычного года, которого не существует. Передав это любой функции, которая принимает SYSTEMTIME структура скорее всего потерпит неудачу.

Например, SystemTimeToFileTime Вызов, показанный здесь, вернет код ошибки. Поскольку это возвращаемое значение не проверено (что очень часто встречается), это приведет к ft оставаясь неинициализированным.

Исправленный код

SYSTEMTIME st;
FILETIME ft;

GetSystemTime(&st);
st.wYear++;

bool isLeapYear = st.wYear % 4 == 0 && (st.wYear % 100 != 0 || st.wYear % 400 == 0);
st.wDay = st.wMonth == 2 && st.wDay == 29 && !isLeapYear ? 28 : st.wDay;

bool ok = SystemTimeToFileTime(&st, &ft);
if (!ok)
{
  // handle error
}

Это исправление проверяет 29 февраля обычного года и исправляет его до 28 февраля.

Python - Замена года

Категория воздействия 1

Неисправный код

from datetime import date
today = date.today()
later = today.replace(year = today.year + 1)

Этот код будет работать правильно, пока todayстановится 29 февраля. Затем он попытается создать 29 февраля обычного года, которого не существует. Вdate конструктор поднимет ValueError с сообщением "day is out of range for month".

Варианты:

from datetime import date
today = date.today()
later = date(today.year + 1, today.month, today.day)

Исправленный код

Без дополнительных библиотек можно отловить ошибку:

from datetime import date
today = date.today()
try:
    later = today.replace(year = today.year + 1)
except ValueError:
    later = date(today.year + 1, 2, 28)

Однако обычно лучше использовать такую ​​библиотеку, как dateutil:

from datetime import date
from dateutil.relativedelta import relativedelta
today = date.today()
later = today + relativedelta(years=1)

Как определить, является ли год високосным

C# используется в примерах для иллюстрации, но этот шаблон является общим для всех языков.

Категория воздействия 2

Неисправный код

bool isLeapYear = year % 4 == 0;

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

Исправленный код

Полный алгоритм (из Википедии) выглядит следующим образом:

if (год не делится на 4), то (это обычный год)
else if (год не делится на 100) then (это високосный год)
else if (год не делится на 400) then (это обычный год)
else (это високосный год)

Одна реализация этого алгоритма выглядит следующим образом:

bool isLeapYear = year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);

На многих платформах эта функция встроена. Например, в.Net предпочтительнее следующее:

bool isLeapYear = DateTime.IsLeapyear(year);

Here's an interesting one I just came across. Just another argument to use UTC wherever possible.

// **** Appears to "Leap" forward **** //
moment('2020-02-29T00:00:00Z').toISOString();
// or
moment('2020-02-29T00:00:00Z').toJSON();
// 2020-02-29T00:00:00.000Z

moment('2020-02-29T00:00:00Z').add(1, 'year').toISOString();
// or
moment('2020-02-29T00:00:00Z').add(1, 'year').toJSON();
// 2021-03-01T00:00:00.000Z


// **** Falls back **** //
moment.utc('2020-02-29T00:00:00Z').toISOString();
// or
moment.utc('2020-02-29T00:00:00Z').toJSON();
// 2020-02-29T00:00:00.000Z

moment.utc('2020-02-29T00:00:00Z').add(1, 'year').toISOString();
// or
moment.utc('2020-02-29T00:00:00Z').add(1, 'year').toJSON();
// 2021-02-28T00:00:00.000Z

Given C#'s default behavior is to fall back...

DateTime dt = new DateTime(2020, 02, 29, 0, 0, 0, DateTimeKind.Utc);
DateTime result = dt.AddYears(1);
// 2021-02-28T00:00:00.0000000Z

This could be crucial to ensure the front end and back end agree on whether to fall back or leap forward a day.

JavaScript - добавление года (лет)

Категория воздействия 2

Неисправный код

var dt = new Date();
dt.setFullYear(dt.getFullYear() + 1);

Этот код будет работать правильно, пока dt становится 29 февраля, например, на 2020-02-29. Затем он попытается установить 2021 год.2021-02-29 не существует, Date объект будет перенесен на следующую действительную дату, которая 2020-03-01.

В некоторых случаях это может быть предполагаемое поведение. В других случаях один выходной день может иметь незначительные последствия (например, срок действия).

Однако во многих случаях при продвижении на год цель состоит в том, чтобы оставаться примерно в одном и том же положении в течение месяца и дня. Другими словами, если вы начали в конце февраля (2020-02-29) и продвинувшись на год, вы, вероятно, рассчитывали, что результат также будет и в конце февраля (2021-02-28), а не в начале марта (2021-03-01).

Исправленный код

Чтобы добавить годы в JavaScript, сохранив поведение конца февраля, используйте следующую функцию.

function addYears(dt, n) {
  var m = dt.getMonth();
  dt.setFullYear(dt.getFullYear() + n);
  if (dt.getMonth() !== m)
    dt.setDate(dt.getDate() - 1);
}

// example usage
addYears(dt, 1);

Обычная вариация - неизменная форма

Часто может быть код, который не изменяет исходный объект, например следующий:

Неисправный код

var dt = new Date();
var result = new Date(dt.getFullYear() + 1, dt.getMonth(), dt.getDate());

Неизменяемая форма, которая сохраняет поведение конца февраля, выглядит следующим образом:

Исправленный код

function addYears(dt, n) {
  var result = new Date(dt);
  result.setFullYear(result.getFullYear() + n);
  if (result.getMonth() !== dt.getMonth())
    result.setDate(result.getDate() - 1);
  return result;
}

// example usage
var result = addYears(dt, 1);

Библиотеки

В JavaScript есть много библиотек даты и времени, таких как Luxon, Date-Fns, Moment и js-Joda. Все эти библиотеки уже используют поведение конца февраля для своих функций добавления лет. Никаких дополнительных корректировок не требуется, если вы не хотите, чтобы вместо этого вы действовали в начале марта.

Другие вопросы по тегам