Linux 'cal' and 'ncal' commands and Easter

Well, it turns out I made that bash script overly complicated by assuming that I had to convert dates to numbers to compare them.

That assumption was false… see
Bash Compare Strings: How to Check if Two Strings Are Equal
So, I can rewrite it more simply as

for i in `seq 2025 2100`
 do 
   j=`ncal -e $i `
   k=`ncal -o $i `
   if test "$k" = "$j"
#  if [ "$k" = "$j" ] alternative to using 'test'
    then
      echo $i
   fi
 done

and that gets rid of the 2010 limit as well.
I still prefer @Rosika’s Python script . It is readable.

3 Likes

Nice, but …:trade_mark:

I assume you meant 2100. :wink:

1 Like

Indeed. My capacity for mistakes is unlimited.
Thank you.

3 Likes

@nevj :

Hi Neville, :waving_hand:

I didn´t have time to try out the python script myself yesterday, so I just threw it at you. Sorry.

Today I ran into the same problem:

pip install python-dateutil orthodox
Requirement already satisfied: python-dateutil in ./venv/lib/python3.10/site-packages (2.9.0.post0)
ERROR: Could not find a version that satisfies the requirement orthodox (from versions: none)
ERROR: No matching distribution found for orthodox

…means that pip can’t find a package named orthodox on PyPI (Python’s official package index).
There is no official package just called orthodox.

The script I was using assumes a package or module that might have been available in some environments or forks, but there is no widely published orthodox package on PyPI. That explains why I couldn´t install it even in my virtual environment.

BTW: I was working with a firejailed instance of Jupyter Lab. :wink:

Yet I could replace the missing orthodox_easter function with one that I included directly in the script (no installation needed).

Here’s an updated version of the script that includes the calculation for Orthodox Easter, based on the Julian calendar:

from dateutil.easter import easter
import datetime

def orthodox_easter(year):
    # Julian calendar Easter (Orthodox)
    a = year % 4
    b = year % 7
    c = year % 19
    d = (19 * c + 15) % 30
    e = (2 * a + 4 * b - d + 34) % 7
    month = (d + e + 114) // 31
    day = ((d + e + 114) % 31) + 1
    # Convert Julian to Gregorian calendar (difference is 13 days in 20th–21st centuries)
    julian_date = datetime.date(year, month, day)
    gregorian_date = julian_date + datetime.timedelta(days=13)
    return gregorian_date

start_year = 2025
end_year = 2100

print("Years when Western and Orthodox Easter fall on the same date:\n")

for year in range(start_year, end_year + 1):
    western = easter(year)
    orthodox = orthodox_easter(year)
    if western == orthodox:
        print(f"{year}: Easter = {western.strftime('%d/%m/%Y')}")

Works like a charm now. :wink:

Here´s its output:

Years when Western and Orthodox Easter fall on the same date:

2025: Easter = 20/04/2025
2028: Easter = 16/04/2028
2031: Easter = 13/04/2031
2034: Easter = 09/04/2034
2037: Easter = 05/04/2037
2038: Easter = 25/04/2038
2041: Easter = 21/04/2041
2045: Easter = 09/04/2045
2048: Easter = 05/04/2048
2052: Easter = 21/04/2052
2055: Easter = 18/04/2055
2058: Easter = 14/04/2058
2061: Easter = 10/04/2061
2069: Easter = 14/04/2069
2071: Easter = 19/04/2071
2072: Easter = 10/04/2072
2075: Easter = 07/04/2075
2079: Easter = 23/04/2079
2082: Easter = 19/04/2082
2085: Easter = 15/04/2085
2091: Easter = 08/04/2091
2095: Easter = 24/04/2095
2096: Easter = 15/04/2096
2099: Easter = 12/04/2099

As for the math part def orthodox_easter(year):

This is a special calculation based on the Julian calendar, which is still used by Orthodox churches for Easter.

a = year % 4
b = year % 7
c = year % 19

These are just remainders when dividing the year by 4, 7, and 19 — that’s used to compute moon phases and weekday alignment.

d = (19 * c + 15) % 30
e = (2 * a + 4 * b - d + 34) % 7

These two values help determine when the full moon and Sunday line up. Again, no need to memorize this; it’s part of a traditional algorithm.

month = (d + e + 114) // 31
day = ((d + e + 114) % 31) + 1

Now that the earlier values have been calculated, this figures out the month and day of Easter Sunday on the Julian calendar. (Hint: it’s always in March or April.)

julian_date = datetime.date(year, month, day)
gregorian_date = julian_date + datetime.timedelta(days=13)

Because the Julian calendar is currently 13 days behind the Gregorian calendar, we just add 13 days to get the modern/Western equivalent.

This, of course, I came up with only by consulting ChatGPT.
I´m no math genius. :laughing:

To sum up:

  • The algorithm figures out the date of Orthodox Easter using astronomical cycles.
  • It’s based on old church math (a mix of lunar cycles and Sunday rules).
  • Using it like a tool — no need to reinvent it. Think of it like a recipe from a cookbook!

Many greetings from Rosika :slightly_smiling_face:

1 Like

Hi Rosika,

Yes that will always work.

Reading about security and python packages is suggesting to me that it would be a good idea to use Python inside firejail.

I guess if you firejail Jupyterlab that will automatically firejail any Python usage from within Jupyterlab?

You are getting quite expert with Python now… way ahead of me

The maths of Easter date is fascinating.

Regards
Neville

2 Likes

Hi Neville, :waving_hand:

thanks for your feedback. :heart:

Yes, that is my understanding.

Here´s what I do to achieve this task:

  1. cd ~/jupyter-env; and sleep 1; and . venv/bin/activate.fish

#This is my personal setup and I use fish as my default shell.

  1. (venv) rosika@rosika-Lenovo-H520e ~/jupyter-env> firejail --profile=~/.config/firejail/jupyter.profile jupyter lab --notebook-dir=/home/rosika/jupyter-test --no-browser

#This fires up my jupyter instance and offers me 3 ways of accessing the server in a browser. Normally I just use the first URL provided to me (which includes a personalzed token)

  1. The part

firejail --profile=~/.config/firejail/jupyter.profile jupyter lab

uses a dedicated jupyter.profile for firejail, which ChatGPT and I worked out in some long sessions. :wink:
If you´re interested in it, just let me know.

  1. As for the browser, I just use a separate process:

firejail --private --dns=1.1.1.1 --dns=9.9.9.9 falkon -no-remote

So both the jupyter process and the borowser process are well sandboxed.

Cheers from Rosika :slightly_smiling_face:

P.S.:

I wish this was true, Neville.
The fact is: I´m just at the beginning of it all. :wink:

By all means.

1 Like

Hi Rosika,
Yes please, I like to see the jupyterlab profile.
Also is there a plain python profile.? I am likely to use python directly, without jupyterlab.

Regards
Neville

1 Like

Hi Neville, :waving_hand:

Doesn´t seem like it:

ll /etc/firejail | grep python
-rw-r--r-- 1 root root  298 Aug 15  2021 allow-python2.inc
-rw-r--r-- 1 root root  330 Aug 15  2021 allow-python3.inc

If I run python3 in `firejail:

firejail python3
Reading profile /etc/firejail/default.profile
Reading profile /etc/firejail/disable-common.inc
Reading profile /etc/firejail/disable-programs.inc
[...]

So it uses the default.file, which surely wouldn´t be good enough.

However: the jupyterlab approach sandboxes everything quite nicely.
If you´re still interested in the profile, here it is.
It´s my personal setup:

include /etc/firejail/whitelist-common.inc
include /etc/firejail/disable-common.inc
include /etc/firejail/disable-programs.inc

# Allow the virtual environment to run
whitelist /home/rosika/jupyter-env/venv/bin/jupyter
whitelist /home/rosika/jupyter-env/venv/bin
whitelist /home/rosika/jupyter-env/venv/lib/python3.10/site-packages
whitelist /home/rosika/jupyter-test

# Set PYTHONPATH for Jupyter to find packages
env PYTHONPATH=/home/rosika/jupyter-env/venv/lib/python3.10/site-packages

# Relax permission for access
noblacklist ${HOME}
noblacklist ${HOME}/.local/share/jupyter
noblacklist ${HOME}/.jupyter
noblacklist ${HOME}/.cache
noblacklist ${HOME}/.config
noblacklist ${HOME}/jupyter-test
noblacklist /dev/pts

# Jupyter frontend assets (likely cause of the issue)
whitelist ${HOME}/.local/share/jupyter/labextensions
whitelist ${HOME}/.local/share/jupyter/nbextensions
whitelist ${HOME}/.local/share/jupyter/static
whitelist ${HOME}/.local/share/jupyter

# Allow access to JupyterLab directories
whitelist /usr/share/jupyter/lab
whitelist /home/rosika/jupyter-env/venv

# Optional for debugging
# trace

# further hardening for balanced alternative
private-dev
nogroups
nonewprivs

Of course you´ll still have to modify it to suit your personal setup.
Some paths still have to be adjusted.

Just put it in ~/.config/firejail and name it jupyter.profile.

Hope it helps.

Many greetings fom Rosika :slightly_smiling_face:

2 Likes

Hi Rosika,
Thank you for the jupyterlab profile.
I note that it needs mods for my setup

I found this for plain python

I will be trying.
Regards
Neville

3 Likes

Hi Neville, :waving_hand:

thank you for the feedback. :heart:

Hey, you found some good sources.
Thanks for the links. I´ll have a closer look at them.

Many greetings from Rosika :slightly_smiling_face:

3 Likes

And so did I. Never knew about this command. But did some research and found:

allows users to copy and paste text and other types of data with ease. This command supports manipulation of the X primary and secondary selections, as well as the system clipboard (**associated with ‘Ctrl + C’ / ‘Ctrl + V’**).

I never knew you could copy/paste with command since Ctrl C & V are not used.

Now that is a useful tool I need to explore. I wonder if that would work for calendar invites for those of us who do not use the MS or Google calendars? Thanks, @abu !

Sheila

3 Likes