You are given a date (
yyyy/mm/dd
).Assuming, the given date is your date of birth. Write a script to find the mirror dates of the given date.
Dave Cross has built a cool site that does something similar.
Assuming today is
2021/09/22
:
Input: 2021/09/18 Output: 2021/09/14, 2021/09/26
On the date you were born, someone who was your current age, would have been born on
2021/09/14
. Someone born today will be your current age on2021/09/26
.
Input: 1975/10/10 Output: 1929/10/27, 2067/09/05
On the date you were born, someone who was your current age, would have been born on
1929/10/27
. Someone born today will be your current age on2067/09/05
.
Input: 1967/02/14 Output: 1912/07/08, 2076/04/30
On the date you were born, someone who was your current age, would have been born on
1912/07/08
. Someone born today will be your current age on2076/04/30
.
The first thing we need to ask when using dates, in particular dates in the past is Which calendar?. The Gregorian Calendar seems an obvious choice, but only if one is naïeve. The Gregorian calendar starts on different dates, depending on the country. While the calendar was introduced in 1582 in some countries, in other countries, it was introduced less than 100 years ago.
To make the challenge simpler for us, we will be assuming the Proleptic Gregorian calendar. This basically means we are assuming the Gregorian calendar has always existed. We will also be assuming the year 0 existed.
We could use one of the gazillion date modules from CPAN. However, we will not. Instead, of the two given dates ("today", and the input date), we will calculate the Julian Day Number. Given those, we can easily calculate the Julian Day Numbers of the target dates, which we then convert back to (proleptic) Gregorian dates.
We will use the formulas on Wikipedia to convert dates to and from Julian Day Numbers.
It should be noted that division needs to be done as integer division,
with results rounded towards 0. That is, 5 / 2 == 2
, and -5 / 2 == -2
.
For the calculations, we fix "today" as 2021/09/22
(using a fixed date
means our tests are deterministic — and if we fix a date, we may as well
use the date in the given examples.)
We also be reading dates from standard input, one date per line, and for each input date, we output a line with the two target dates.
First, we create a sub which takes a year, month, and date and returns a Julian Day Number:
sub g2j ($Y, $M, $D) {
use integer;
(1461 * ($Y + 4800 + ($M - 14) / 12)) / 4 +
(367 * ($M - 2 - 12 * (($M - 14) / 12))) / 12 -
(3 * (($Y + 4900 + ($M - 14) / 12) / 100)) / 4 + $D - 32075
}
Note the directive use integer;
, which causes Perl to do division
the way C does division between integers.
We also need a sub which takes a Julian Day Number, and returns the date:
sub j2g ($J) {
use integer;
my $e = 4 * ($J + 1401 + (((4 * $J + 274277) / 146097) * 3) / 4 - 38) + 3;
my $D = ((5 * (($e % 1461) / 4) + 2) % 153) / 5 + 1;
my $M = (((5 * (($e % 1461) / 4) + 2) / 153 + 2) % 12) + 1;
my $Y = ($e / 1461) - 4716 + (12 + 2 - $M) / 12;
($Y, $M, $D)
}
Now we calculate todays Julian Day Number:
my @TODAY = (2021, 9, 22);
my $julian_today = g2j @TODAY;
Read a date (from $_
), and calculate its Julian Day Number:
my ($Y, $M, $D) = /[0-9]+/g;
my $julian_then = g2j $Y, $M, $D;
The target Julian Day Numbers are now
2 * $julian_then - $julian_today
and 2 * $julian_today - $julian_then
.
Which means we can output the result as:
printf "%04d/%02d/%02d, %04d/%02d/%02d\n",
j2g (2 * $julian_then - $julian_today),
j2g (2 * $julian_today - $julian_then);
Find the full program on GitHub.
The AWK solution is very similar to the Perl solution, but there are
a few differences. First difference is that AWK doesn't do integer
division. But it does have a int
function which rounds towards 0
.
This makes the g2j
function as follows:
function g2j (Y, M, D) {
return (int ((1461 * (Y + 4800 + int ((M - 14) / 12))) / 4) + \
int ((367 * (M - 2 - 12 * int ((M - 14) / 12))) / 12) - \
int ((3 * int (((Y + 4900 + int ((M - 14) / 12)) / 100))) / 4) + \
D - 32075)
}
Second difference is that AWK cannot return arrays or list from functions.
So, we will return a formatted date from j2g
:
function j2g (J) {
e = 4 * (J + 1401 + int (int ((4 * J + 274277) / 146097) * 3 / 4) - 38) + 3
D = int (((5 * (int ((e % 1461) / 4)) + 2) % 153) / 5) + 1
M = ((int ((5 * (int ((e % 1461) / 4)) + 2) / 153) + 2) % 12) + 1
Y = int (e / 1461) - 4716 + int ((12 + 2 - M) / 12)
return sprintf ("%04d/%02d/%02d", Y, M, D)
}
To set up things, we will set the FS
variable to /
; that way, AWK
will split the input for us, giving use the input year, month and date
in the variables $1
, $2
, and $3
:
BEGIN {
FS = "/"
TODAY_Y = 2021
TODAY_M = 9
TODAY_D = 22
julian_today = g2j(TODAY_Y, TODAY_M, TODAY_D)
}
The main loop is now simple:
{
julian_then = g2j($1, $2, $3)
print j2g(2 * julian_then - julian_today) ", " \
j2g(2 * julian_today - julian_then)
}
Find the full program on GitHub.
Bash does do integer division (it doesn't deal at all with floating point numbers), but to doesn't return values from functions. So we have to use global variables to return values. It also uses numbered variables as the function parameters. This leads to:
function g2j () {
local Y=$1
local M=$2
local D=$3
J=$(( ((1461 * (Y + 4800 + (M - 14) / 12)) / 4 +
(367 * (M - 2 - 12 * ((M - 14) / 12))) / 12 -
(3 * ((Y + 4900 + (M - 14) / 12) / 100)) / 4 + D - 32075) ))
}
and
function j2g () {
local J=$1
local e=$(( 4 * (J + 1401 +
(((4 * J + 274277) / 146097) * 3) / 4 - 38) + 3 ))
D=$(( ((5 * ((e % 1461) / 4) + 2) % 153) / 5 + 1 ))
M=$(( (((5 * ((e % 1461) / 4) + 2) / 153 + 2) % 12) + 1 ))
Y=$(( (e / 1461) - 4716 + (12 + 2 - M) / 12 ))
}
Setting up the Julian Day Number for today shows how we use the output values. Note also that we don't use commas to separate arguments.
g2j 2021 9 22; julian_today=$J
We can use the same trick as in AWK to have the input automatically split;
the variable IFS
plays the role of FS
in AWK. But there is a catch.
An input like 08
or 09
is interpreted as an (illegal) octal number
when used in arithmetic. So we have to strip off any leading 0s.
We do this like this: ${d/#0/}
, which takes the value in $d
, and
replaces the leading 0
with an empty string.
This results in the following main loop:
IFS="/"
while read y m d
do g2j ${y/#0/} ${m/#0/} ${d/#0/}; julian_then=$J
j2g $(( 2 * julian_then - julian_today ))
printf "%04d/%02d/%02d, " $Y $M $D
j2g $(( 2 * julian_today - julian_then ))
printf "%04d/%02d/%02d\n" $Y $M $D
done
Find the full program on GitHub.
The g2j
function in C is very similar to the one in Perl. Division
in C between integer types is integer division:
typedef unsigned short date_type;
long g2j (date_type Y, date_type M, date_type D) {
return ((1461 * (Y + 4800 + (M - 14) / 12)) / 4 +
(367 * (M - 2 - 12 * ((M - 14) / 12))) / 12 -
(3 * ((Y + 4900 + (M - 14) / 12) / 100)) / 4 + D - 32075);
}
For the j2g
function, we take an additional parameter: an appropriate
sized array in which we put the date component parts:
# define idx_Y 0
# define idx_M 1
# define idx_D 2
void j2g (long J, date_type * date) {
long e = 4 * (J + 1401 + (((4 * J + 274277) / 146097) * 3) / 4 - 38) + 3;
date [idx_D] = ((5 * ((e % 1461) / 4) + 2) % 153) / 5 + 1;
date [idx_M] = (((5 * ((e % 1461) / 4) + 2) / 153 + 2) % 12) + 1;
date [idx_Y] = (e / 1461) - 4716 + (12 + 2 - date [idx_M]) / 12;
}
In the main program, we have to allocate memory for the date components of the two dates, and we calculate the Julian Day Number of "today":
unsigned short TODAY [] = {2021, 9, 22};
int main (void) {
date_type Y, M, D;
date_type * date_early;
date_type * date_late;
if ((date_early = (date_type *) malloc (3 * sizeof (date_type))) == NULL) {
perror ("Malloc failed");
exit (1);
}
if ((date_late = (date_type *) malloc (3 * sizeof (date_type))) == NULL) {
perror ("Malloc failed");
exit (1);
}
long julian_today = g2j (TODAY [idx_Y], TODAY [idx_M], TODAY [idx_D]);
We use scanf
to parse the input. The %hu
format indicates we
are reading an unsigned short. This gives use the following main loop:
while (scanf ("%hu/%hu/%hu", &Y, &M, &D) == 3) {
long julian_then = g2j (Y, M, D);
j2g (2 * julian_then - julian_today, date_early);
j2g (2 * julian_today - julian_then, date_late);
printf ("%04d/%02d/%02d, %04d/%02d/%02d\n",
date_early [idx_Y], date_early [idx_M], date_early [idx_D],
date_late [idx_Y], date_late [idx_M], date_late [idx_D]);
}
At the end, we free the allocated memory:
free (date_early);
free (date_late);
Find the full program on GitHub.
Nieuwer version of Lua (nieuwer than the version I run), has //
to
do integer division. Lua also doesn't have an int
function, so we
roll our own:
function int (x)
if x > 0 then
return math . floor (x)
else
return math . ceil (x)
end
end
math . floor
rounds toward \(- \infty\), and math . ceil
rounds towards
\(\infty\). Since we want to round towards 0
, we check whether the value
we want to round is positive or not, and use the appropriate rounding
function.
This leads to
function g2j (Y, M, D)
return (int ((1461 * (Y + 4800 + int ((M - 14) / 12))) / 4) +
int ((367 * (M - 2 - 12 * int ((M - 14) / 12))) / 12) -
int ((3 * int (((Y + 4900 + int ((M - 14) / 12)) / 100))) / 4) +
D - 32075)
end
and
function j2g (J)
local e = 4 * (J + 1401 +
int (int ((4 * J + 274277) / 146097) * 3 / 4) - 38) + 3
local D = int (((5 * (int ((e % 1461) / 4)) + 2) % 153) / 5) + 1
local M = ((int ((5 * (int ((e % 1461) / 4)) + 2) / 153) + 2) % 12) + 1
local Y = int (e / 1461) - 4716 + int ((12 + 2 - M) / 12)
return Y, M, D
end
The rest of the program looks like:
local julian_today = g2j (2021, 9, 22)
local output_format = "%04d/%02d/%02d, %04d/%02d/%02d\n"
for line in io . lines () do
local _, _, Y, M, D = line : find ("([0-9]+)/([0-9]+)/([0-9]+)")
local julian_then = g2j (Y, M, D)
local Y1, M1, D1 = j2g (2 * julian_then - julian_today)
local Y2, M2, D2 = j2g (2 * julian_today - julian_then)
io . write (output_format : format (Y1, M1, D1, Y2, M2, D2))
end
Find the full program on GitHub.
In Node.js, we don't have integer division either, nor does it have
and int
function. So we use the same trick as we used in the Lua
solution (which we don't copy here).
The tricky thing about the Node.js solution is the lack of a usable
printf
functionality. So, we have to create our own.
First, a method to pre-pad numbers with 0
s till the required length:
function pad (num, l) {
let out = num
while (out . length < l) {
out = "0" + out
}
return (out)
}
Then we can create pretty print function, which takes two dates (as arrays), and formats them:
function pp (d1, d2) {
let e1 = d1 . map (x => x . toString ())
let e2 = d2 . map (x => x . toString ())
console . log ("%s/%s/%s, %s/%s/%s",
pad (e1 [0], 4), pad (e1 [1], 2), pad (e1 [2], 2),
pad (e2 [0], 4), pad (e2 [1], 2), pad (e2 [2], 2))
}
This leads to the following main program:
let julian_today = g2j (2021, 9, 22)
require ('readline')
. createInterface ({input: process . stdin})
. on ('line', line => {
let [Y, M, D] = line . split ('/') . map (x => +x)
let julian_then = g2j (Y, M, D)
pp (j2g (2 * julian_then - julian_today),
j2g (2 * julian_today - julian_then))
})
Find the full program on GitHub.
Python has //
to do integer division, but this rounds towards
\(-\infty\), which makes it useless for our purposes. Luckily, its
int
method rounds towards 0
, so we can use that, giving us a
similar g2j
and j2g
as we have seen before.
This gives us the following main program:
for line in fileinput . input ():
Y, M, D = map (lambda x: int (x), line . strip () . split ("/"))
julian_then = g2j (Y, M, D)
Y1, M1, D1 = j2g (2 * julian_then - julian_today)
Y2, M2, D2 = j2g (2 * julian_today - julian_then)
print ('{:04d}/{:02d}/{:02d}, {:04d}/{:02d}/{:02d}' .
format (Y1, M1, D1, Y2, M2, D2))
Find the full program on GitHub.
Ruby uses integer division when divising integers, but it rounds towards
\(-\infty\). We therefore create a div
function which does integer
division with rounding towards 0
:
def div (x, y)
return (x . to_f / y) . to_i
end
In this method, we convert one of the arguments to a floating point
number (using to_f
), then, after division, we use to_i
to force
the result to be an integer. to_i
rounds towards 0
.
This gives us the following g2j
:
def g2j (y, m, d)
return (div(1461 * (y + 4800 + div(m - 14, 12)), 4) +
div( 367 * (m - 2 - 12 * div(m - 14, 12)), 12) -
div( 3 * div(y + 4900 + div(m - 14, 12), 100), 4) + d - 32075)
end
and j2g
:
def j2g (j)
e = 4 * (j + 1401 + div(div(4 * j + 274277, 146097) * 3, 4) - 38) + 3;
d = div((5 * div(e % 1461, 4) + 2) % 153, 5) + 1;
m = ((div( 5 * div(e % 1461, 4) + 2, 153) + 2) % 12) + 1;
y = div(e, 1461) - 4716 + div(12 + 2 - m, 12);
return y, m, d
end
The main program then becomes:
julian_today = g2j 2021, 9, 22
ARGF . each_line do
| line |
y, m, d = line . strip . split("/") . map {|x| x . to_i}
julian_then = g2j y, m, d
puts "%04d/%02d/%02d, %04d/%02d/%02d\n" %
[j2g(2 * julian_then - julian_today),
j2g(2 * julian_today - julian_then)] . flatten
end
Find the full program on GitHub.