A historical mistake and how you can solve it
TL;DR: Most languages fail to find the correct behavior for leap year calculation.
Disclaimer: While I’ve tried my best to provide accurate insights across various programming languages, I acknowledge that I may not be an expert in everyone. If you spot an error or disagree with any points, please leave a respectful comment, and I’ll promptly address it.
Determining whether a year is a leap (or not) is a simple mathematical problem.
Every student can solve it as their first programming assignment.
To simplify the problem, let’s assume a Year is leap when it is evenly divisible by 4, except if it’s also divisible by 100, but it is a leap year if it’s divisible by 400.
The real world and cosmic mechanics are a bit more complicated, but that is beyond the scope of this article.
Let’s explore how several programming languages solve this problem:
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;
These languages attempt to create a valid (or invalid) leap day and exploit truthy values.
This hack violates the fail-fast principle and abuses the billion-dollar mistake.
Trying to create an invalid date should throw an exception in serious languages since this happens in the real world domain.
Performing other actions, like concealing errors beneath the surface, breaches the principle of least astonishment.
Ada:
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;
C/C++:
bool isLeapYear(int year) {
return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
}
Go:
package main
import (
"fmt"
"time"
)
func isLeapYear(year int) bool {
return year%4 == 0 && (year%100 != 0 || year%400 == 0)
}
Haskell:
import Data.Time.Calendar (isLeapYear)
let year = 2024
let isLeap = isLeapYear year
JavaScript/TypeScript:
function isLeapYear(year) {
return (year % 4 === 0 && year % 100 !== 0) || (year % 400 === 0);
}
Julia:
using Dates
year = 2024
isleap(year)
Lua:
local year = 2024
local isLeap = (year % 4 == 0 and year % 100 ~= 0) or (year % 400 == 0)
MATLAB:
year = 2024;
isLeap = mod(year, 4) == 0 && (mod(year, 100) ~= 0 || mod(year, 400) == 0);
Objective-C:
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)
Rust:
fn is_leap_year(year: i32) -> bool {
(year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)
}
Smalltalk:
| yearNumber |
yearNumber := 2024.
(yearNumber \\ 4 = 0)
and: [(yearNumber \\ 100 ~= 0) or: [ yearNumber \\ 400 = 0 ]]
The above languages do not provide native support.
You need to define global functions or use helpers.
PHP (Again):
<?php
$yearNumber = 2024;
$isLeap = checkdate(2, 29, $yearNumber);
R:
leap_year(2024)
Ruby:
year = 2024
is_leap = Date.leap?(year)
Swift:
let yearNumber = 2024
let isLeap = Calendar.current.isDateInLeapYear(
Date(timeIntervalSince1970: TimeInterval(yearNumber)))
These languages use global functions to check if a year is a leap.
These utility global methods mistakenly place responsibility in the wrong location (a global access point).
C#:
int yearNumber = 2024;
bool isLeap = System.DateTime.IsLeapYear(yearNumber);
Dart:
import 'package:intl/intl.dart';
var year = 2024;
var isLeap = DateTime(year).isLeapYear;
Perl:
use Time::Piece;
my $yearNumber = 2024;
my $isLeap = Time::Piece
->strptime("$yearNumber-01-01", "%Y-%m-%d")->leapyear;
Python:
import calendar
leap = calendar.isleap(2024)
Visual Basic .NET:
Dim year As Integer = 2024
Dim isLeap As Boolean = DateTime.IsLeapYear(year)
These languages use helpers as libraries to check if a year is a leap.
The misplaced responsibly is not present in a real object but in a bag of DateTime related functions.
Java:
int yearNumber = 2024;
boolean isLeap = java.time.Year.of(yearNumber).isLeap();
Kotlin:
val yearNumber = 2024
val isLeap = java.time.Year.of(yearNumber).isLeap
Scala:
val year = 2024
val isLeap = java.time.Year.of(year).isLeap
These languages rely on the Year to check if it is a leap.
The protocol is closer to the real world in the bijection
Notice they create Year objects and not Integer objects since this would also break the bijection.
A Year has a different protocol than an integer, and modeling a Year as an integer would also be a premature optimization smell and a symptom of mixing the what and the how.
A Year can tell if it is a leap (an integer shouldn’t do it) and can tell you about its months (which are Months, not 0-based integers, 1-based integers, or strings).
Conversely, an Integer‘s capabilities extend to arithmetic operations such as multiplication and exponentiation.
Representing a point in time as a float, integer, or any other data type comes with consequences.
You can break a point in time in the real world in tiny fractions (but not too small)
Using floats is not a valid option.
0.01 + 0.02 is not 0.03, and this has terrible consequences dealing with floating pointpoints in time.
We’ve been talking about leap years.
What are the needs to know if a year is a leap?
The date and time mechanics you model need to know the February 28th, 2024 successor.
But this is NOT your problem.
Following the information hiding principle, you should leave the responsibility as a private protocol.
There is no Silver Bullet.
Use your language wisely.
Today is February 29th, a leap day to pause and reflect on the tools you use daily.
See you in 4 years.
This article was originally published by Maximiliano Contieri on HackerNoon.
As digital transformation accelerates, ensuring accessibility remains crucial for millions of Indians with disabilities. Addressing…
I think OpenAI is not being honest about the diminishing returns of scaling AI with…
S8UL Esports, the Indian esports and gaming content organisation, won the ‘Mobile Organisation of the…
The Tech Panda takes a look at recent funding events in the tech ecosystem, seeking…
Colgate-Palmolive (India) Limited, the oral care brand, launched its Oral Health Movement. The AI-enabled initiative…
This fast-paced business world belongs to the forward thinking organisations that prioritise innovation and fully…