Историческая ошибка и как ее исправить
TL;DR: Большинство языков не могут найти правильное поведение при вычислении високосного года.
Отказ от ответственности: хотя я изо всех сил старался предоставить точную информацию о различных языках программирования, я признаю, что не могу быть экспертом во всех. Если вы заметили ошибку или не согласны с каким-либо пунктом, оставьте уважительный комментарий, и я оперативно его исправлю.
Определить, является ли год високосным (или нет), представляет собой простую математическую задачу.
Каждый студент может решить ее в качестве своего первого задания по программированию.
Чтобы упростить задачу, предположим, что год является високосным , если он делится на 4 без остатка, за исключением случаев, когда он также делится на 100, но високосным является год, если он делится на 400.
Реальный мир и космическая механика немного сложнее, но это выходит за рамки данной статьи.
Давайте рассмотрим, как несколько языков программирования решают эту проблему:
PHP:
<?php $yearNumber = 2024; $isLeap = date('L', mktime(0, 0, 0, 1, 1, $yearNumber));
SQL (PostgreSQL):
SELECT (EXTRACT(year FROM TIMESTAMP '2024-02-29') IS NOT NULL) AS is_leap_year;
Эти языки пытаются создать действительный (или недопустимый) високосный день и использовать истинные значения .
Этот хак нарушает принцип отказоустойчивости и злоупотребляет ошибкой на миллиард долларов .
Попытка создать неверную дату должна вызвать исключение в серьезных языках, поскольку это происходит в реальном мире .
Выполнение других действий, например сокрытие ошибок, нарушает принцип наименьшего удивления.
Ада:
function Is_Leap_Year (Year : Integer) return Boolean is begin return (Year mod 4 = 0 and then Year mod 100 /= 0) or else (Year mod 400 = 0); end Is_Leap_Year;
Си/С++:
bool isLeapYear(int year) { return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0); }
Идти:
package main import ( "fmt" "time" ) func isLeapYear(year int) bool { return year%4 == 0 && (year%100 != 0 || year%400 == 0) }
Хаскелл:
import Data.Time.Calendar (isLeapYear) let year = 2024 let isLeap = isLeapYear year
JavaScript/Типскрипт:
function isLeapYear(year) { return (year % 4 === 0 && year % 100 !== 0) || (year % 400 === 0); }
Юлия:
using Dates year = 2024 isleap(year)
Луа:
local year = 2024 local isLeap = (year % 4 == 0 and year % 100 ~= 0) or (year % 400 == 0)
МАТЛАБ:
year = 2024; isLeap = mod(year, 4) == 0 && (mod(year, 100) ~= 0 || mod(year, 400) == 0);
Цель-С:
int yearNumber = 2024; BOOL isLeap = (yearNumber % 4 == 0 && yearNumber % 100 != 0) || (yearNumber % 400 == 0);
PowerShell:
$yearNumber = 2024 $isLeap = ($yearNumber % 4 -eq 0 -and $yearNumber % 100 -ne 0) -or ($yearNumber % 400 -eq 0)
Ржавчина:
fn is_leap_year(year: i32) -> bool { (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0) }
Болтовня:
| yearNumber | yearNumber := 2024. (yearNumber \\ 4 = 0) and: [(yearNumber \\ 100 ~= 0) or: [ yearNumber \\ 400 = 0 ]]
Вышеупомянутые языки не имеют встроенной поддержки.
Вам необходимо определить глобальные функции или использовать помощники .
PHP (снова):
<?php $yearNumber = 2024; $isLeap = checkdate(2, 29, $yearNumber);
Р:
leap_year(2024)
Рубин:
year = 2024 is_leap = Date.leap?(year)
Быстрый:
let yearNumber = 2024 let isLeap = Calendar.current.isDateInLeapYear( Date(timeIntervalSince1970: TimeInterval(yearNumber)))
Эти языки используют глобальные функции , чтобы проверить, является ли год високосным.
Эти служебные глобальные методы ошибочно переносят ответственность не в то место (глобальную точку доступа).
С#:
int yearNumber = 2024; bool isLeap = System.DateTime.IsLeapYear(yearNumber);
Дарт:
import 'package:intl/intl.dart'; var year = 2024; var isLeap = DateTime(year).isLeapYear;
Перл:
use Time::Piece; my $yearNumber = 2024; my $isLeap = Time::Piece ->strptime("$yearNumber-01-01", "%Y-%m-%d")->leapyear;
Питон:
import calendar leap = calendar.isleap(2024)
Визуал Бейсик .NET:
Dim year As Integer = 2024 Dim isLeap As Boolean = DateTime.IsLeapYear(year)
Эти языки используют помощники в качестве библиотек, чтобы проверить, является ли год високосным.
Неуместная ответственность присутствует не в реальном объекте, а в наборе функций, связанных с DateTime .
Джава:
int yearNumber = 2024; boolean isLeap = java.time.Year.of(yearNumber).isLeap();
Котлин:
val yearNumber = 2024 val isLeap = java.time.Year.of(yearNumber).isLeap
Скала:
val year = 2024 val isLeap = java.time.Year.of(year).isLeap
Эти языки полагаются на год , чтобы проверить, является ли он скачком.
Протокол ближе к реальному миру в биекции
Обратите внимание, что они создают объекты Year , а не объекты Integer , поскольку это также нарушит биекцию .
У Года другой протокол, чем у целого числа, и моделирование Года как целого числа также было бы запахом преждевременной оптимизации и признаком смешивания того, что и как .
Год может определить, является ли это скачком (целое число не должно этого делать), и может рассказать вам о своих месяцах (которые представляют собой Months , а не целые числа, отсчитываемые от 0 , целые числа, отсчитываемые от 1, или строки).
И наоборот, возможности Integer распространяются на арифметические операции, такие как умножение и возведение в степень.
Представление момента времени в виде числа с плавающей запятой , целого числа или любого другого типа данных имеет последствия.
Вы можете разбить момент времени в реальном мире на крошечные доли (но не слишком маленькие ).
Использование чисел с плавающей запятой не является допустимым вариантом.
0,01 + 0,02 — это не 0,03, и это имеет ужасные последствия , связанные с точками с плавающей запятой во времени.
Мы говорили о високосных годах.
Что нужно знать, если год високосный?
Механика даты и времени, которую вы моделируете, должна знать преемник от 28 февраля 2024 года.
Но это НЕ ваша проблема.
Следуя принципу сокрытия информации, вам следует оставить ответственность в виде частного протокола.
Серебряной пули не существует.
Используйте свой язык с умом.
Сегодня 29 февраля, високосный день, чтобы сделать паузу и поразмышлять над инструментами, которые вы используете ежедневно.
Увидимся через 4 года.