Teaser 3281: Square dates
From The Sunday Times, 10th August 2025 [link] [link]
Each year when I get my new diary, I like to look at the dates to see if they have any interesting properties. When I looked at this year’s diary, I was pleased to find two dates that are “square” in the following way. If the date is expressed with one or two digits for the day (i.e., no leading zero is allowed), followed by two digits for the month and then the year in full, then 1.09.2025 is a square date, since 1092025 is the square of 1045.
The only other square date this year is 27.09.2025, since 27092025 is the square of 5205.
Using the same method of expressing the date, what is the first square date after 2025?
[teaser3281]
Jim Randell 7:06 am on 10 August 2025 Permalink |
A straightforward search finds the answer in a reasonable time.
The following Python program runs in 68ms. (Internal runtime is 2.8ms).
from datetime import (date, timedelta) from enigma import (repeat, inc, is_square, printf) # consider increasing dates for d in repeat(inc(timedelta(days=1)), date(2025, 1, 1)): # form the date as a number n = d.year + d.month * 10000 + d.day * 1000000 r = is_square(n) if r: printf("{d:%d.%m.%Y} -> {n} = sq({r})") if d.year > 2025: breakSolution: The first square date after 2025 is: 01.01.2036 (1st January 2036).
There are 15 dates in 20xx years that are square numbers:
The program can be easily modified to answer a similar puzzle with triangular dates:
There are only 5 dates in 20xx years that are triangular numbers:
And the next triangular date after that is New Year’s Eve 2105:
LikeLike
Ruud 4:25 pm on 10 August 2025 Permalink |
Why not start at 1-1-2026? Then you don’t need to test for >2025 .
LikeLike
Jim Randell 4:38 pm on 10 August 2025 Permalink |
@Ruud: Because I also wanted to verify the dates for 2025 that are given in the puzzle text.
LikeLike
Tony Smith 5:11 am on 11 August 2025 Permalink |
In fact since no square ends 026, 027, …… 035 you could just start with the date which is the answer to the teaser.
I assume that Jim’s program could easily be adapted to find all square dates in a given range.
LikeLike
Jim Randell 7:34 am on 11 August 2025 Permalink |
Although my original program was quick to write and fast to run, I did consider doing some rejection of years that couldn’t possibly form squares.
This code rejects years based on their final 2 digits. So it only checks 2025, 2029, 2036, etc.
from datetime import (date, timedelta) from enigma import (irange, repeat, inc, is_square, sq, printf) # reject years that cannot give a square mods = set(sq(n) % 100 for n in irange(0, 99)) # generate solutions in interval [y1 .. y2] def generate(y1, y2): for year in irange(y1, y2): if year % 100 not in mods: continue # consider dates in the year for d in repeat(inc(timedelta(days=1)), date(year, 1, 1)): if d.year > year: break n = d.year + d.month * 10000 + d.day * 1000000 r = is_square(n) if r: yield (d, n, r) # consider dates from 2025 onwards for (d, n, r) in generate(2025, 2099): printf("{d:%d.%m.%Y} -> {n} = sq({r})") if d.year > 2025: breakIn the end though I went with my first program, as it was quick to write, fast to run, short, easily understandable, and can be easily modified to check for other scenarios (e.g. if we want to find dates that are triangular numbers).
LikeLike
Jim Randell 12:57 pm on 16 August 2025 Permalink |
I recently added code to enigma.py to implement the method of finding modular roots of an integer valued polynomial described at Teaser 3117. And we can use it to solve this puzzle by looking for modular square roots.
This code can find square dates in any particular year, so we can start looking for the next year with square dates after 2025.
The following code runs in less than 1ms (and is faster than using the simple implementation of [[
enigma.sqrtmod()]]).from datetime import date from enigma import (enigma, Enumerator, irange, sq, mdivmod, catch, printf) sqrtmod = enigma.poly_roots_mod.sqrtmod # find square dates in year <y> def solve(y): for r in sqrtmod(y, 10, 4): n = sq(r) (dd, mm, yy) = mdivmod(n, 100, 10000) d = catch(date, yy, mm, dd) if d is not None: yield (d, n, r) # look for first year after 2025 with square dates for y in irange(2025, 2099): ss = Enumerator(solve(y)) for (d, n, r) in ss: printf("{d:%d.%m.%Y} -> {n} = sq({r})") if ss.count > 0 and y > 2025: breakLikeLiked by 1 person
Frits 9:56 am on 10 August 2025 Permalink |
Assuming that a solution exists before the year 10000.
from datetime import date from enigma import catch def is_valid_date(n): s = str(n) if (mm := int(s[-6:-4])) > 12: return None if (dd := int(s[:-6])) > 31: return None yy = n % 10000 return catch(date, yy, mm, dd) # dictionary of square endings d = {i: [j for j in range(100) if (j * j) % 100 == i] for i in range(100)} y = 2026 # start with first year after 2025 while True: sols = [] # process numbers that ends on <y> if squared for n in d[y % 100]: # 31130000**.5 = 5579.426493825329 for dmy in [sq for k in range(10, 56) if (sq := (100 * k + n)**2) % 10000 == y]: if (yyyymmdd := is_valid_date(dmy)) is not None: sols.append(yyyymmdd) if not sols and y < 10000: y += 1 else: print("answer:", min(sols)) breakLikeLike
Frits 3:53 pm on 11 August 2025 Permalink |
Fewer iterations.
from datetime import date from enigma import catch def is_valid_date(n): ddmm, yy = divmod(n, 10000) dd, mm = divmod(ddmm, 100) if not (0 < mm < 13): return None if not (0 < dd < 32): return None return catch(date, yy, mm, dd) # numbers where last 2 digits of it's square are equal to <n> # if x is part of sq_end(n) then 50 - x and 100 - x must also be part of it sq_ends = lambda n: [j for j in range(26) if (j * j) % 100 == n] y = 2026 # start with first year after 2025 while True: sols = [] # select numbers that if squared end on last 2 digits of <y> for e in sq_ends(y % 100): for n in {e, 50 - e, e + 50, 100 - e if e else e}: # 31130000**.5 = 5579.426493825329 for dmy in [sq for k in range(10, 56) if (sq := (100 * k + n)**2) % 10000 == y]: if (yyyymmdd := is_valid_date(dmy)) is not None: sols.append(yyyymmdd) if not sols and y < 10000: y += 1 else: if y < 10000: print("answer:", min(sols), "(YYYY-MM-DD)") breakLikeLike
Ruud 4:23 pm on 10 August 2025 Permalink |
import datetime try_date = datetime.datetime(2026, 1, 1) while not int((n := int(f"{try_date:%d%m%Y}")) ** 0.5) ** 2 == n: try_date += datetime.timedelta(days=1) print(try_date)LikeLike