Calendar

Today’s date1 is

Coptic Date

Reinventing the Wheel of Time

In today’s post, we are going to explore the modern day Coptic/Ethiopic calendar. In use both by the Coptic Orthodox Church and as the Civil Calendar of Ethiopia, the only difference between the two calendars is the names of the months and the epoch of the years. Historically this system has also been known as the Egyptian Civil Calendar and the French Revolutionary Calendar. Such is the simplicity and robustness of this calendar, that it is both ancient and frequently reinvented. The calendar system we devise below can be used to implement any of these calendars. We could even use it to devise our own, in keeping with millenia of tradition. We will start with the Egyptian calendar below, as the earliest form of this system, and then move onto the more modern forms. The French Revolutionary Calendar will be covered in a later post, since its rules for leap years are more complicated.

Devising the Calendar

Knowing what we know about the motion of the planets, let’s try and create a calendar. Of course, we already know that this will end up as the ancient Egyptian calendar, but let’s see how such a calendar could come about from first principles.

Firstly, we need come up with a year. The Nile floods annually, replenishing the soil with rich alluvium. This annual flooding was vital to Egyptian agriculture, so we’ll define our year relative to it. After the flood, we have a period of growing our crops, and then the period after harvesting them before the next flood.

So we have the flood each year, the growth period of our crops, and the harvest period. We’ll divide our year into each. Next we note that each period takes about 4 cycles of the moon, each. So we devise a lunar month of 30 days, with each month being 1-4 of that season. This gives us a lunar calendar, 12 months of 30 days each.

But we’re slightly out of sync with the seasons. Our timing relative to the moon is pretty good, but it seems like this cycle of flooding, receding, and drying is more dependant on the sun than the moon. Our calendar is 360 days, the sun’s path takes 365, we’ll tack on 5 days to our year to resynchronise. This does kill our synchronisation with the moon a little, but it’s more useful for growing crops, so it’s worth compromising a little.

Let’s take a look at what we created:

season month days
flood Akhet I 30
flood Akhet II 30
flood Akhet III 30
flood Akhet IV 30
growth Peret I 30
growth Peret II 30
growth Peret III 30
growth Peret IV 30
harvest Shemu I 30
harvest Shemu II 30
harvest Shemu III 30
harvest Shemu IV 30
extra 5

This is pretty much exactly the Ancient Egyptian Civil Calendar. One of the most ancient calendars created, so old it is attributed to the god Thoth himself2. Originally a lunar calendar, the calendar was simplified to have 30 day months, with our intercalated 5 days.

Now let’s look at the Coptic and Ethiopic calendars:

coptic ethiopic days
Thout Meskerem 30
Paopi Tikimt 30
Hathor Hidar 30
Koiak Tahsas 30
Tobi Tir 30
Meshir Yakatit 30
Paremhat Magabit 30
Parmouti Miyazya 30
Pashons Ginbot 30
Paoni Sene 30
Epip Hamle 30
Mesori Nehasa 30
Pi Kogi Enavot Pagumiene 5 / 6

There are two key differences here. First, is the months now have names in Coptic and Ge’ez, and second is the introduction of a 6th day to our intercalated days.

They Were Only Playing Leapfrog

xkcd.com/1514 Intercalation, or embolism, is the process of inserting an extra unit of time into a calendar, in order to realign it with some outside source. In the case of the Egyptian calendar, five days are inserted to realign the calendar year with the tropical one. There is one flaw with this realignment. The tropical year is not 365 days exactly. It’s closer to 365.25 days3. We need an extra quarter of a day.

The solution to this, as you might already be aware, is the leap day. Leap days in solar calendars are extra days intercalated into a year in order to align a 365 day calendar year with the tropical year. In lunar calendars, we can use leap months to achieve the same effect. In either case, a year in which a leap event occurs is called a leap year. The Egyptian civil calendar originally didn’t include the concept of leap years, but by the time the Coptic calendar came about, it was adopted. The rule is that once every 4 years4, an extra intercalary day is added, giving a 366 day year.

Big Date Tonight

The date window is considered the simplest additional complication a mechanical watch can have. It shows the day of the month in a little window near 3 o’clock. In order to implement it, you have a wheel that completes a rotation once every 24 hours, after which it releases the date wheel so that it can turn one unit forward, indicating the next date. The date wheel stops after turning one unit, and needs to be released again by the 24-hour wheel. Slightly more sophisticated “Big Date” indicators have separate ten and unit windows, but the basic principal remains the same.

Our Coptic calendar works pretty well here, since the days of the regular months are all 30 days long, but we have six obstacles to perfect datekeeping, our six intercalary days. With our simple date mechanism, the wearer will have to reset their date once every year after the 5th or 6th intercalary day. This is markedly better than the Gregorian scenario, where 5 months of the year require adjusting. We can add a more complicated mechanism to track the month and year, allowing the watch to never lose the date, but once a year adjustment isn’t too bad for a simple mechanism.

Get Smart

The same obstacles experienced by our mechanical watch also plague our smart watch. Any complication which renders the Coptic date needs to account for the extra 5 days and our leap day. Thankfully bits are cheaper than gears, so we can track the extra information needed for intercalation easily in our digital implementation.

Let’s Go Digital

To avoid making this post more confused, we’ll implement a console app to demonstrate our knowledge of the Coptic calendar. This app will function like the date command, and when run will print today’s date in the Coptic calendar. Because the later posts in this series will cover Android development, I will be using Kotlin as the primary development language, and this app will be no exception.

To compute a date digitally, we need a few critical pieces. The first is an epoch, a fixed point in time from which we calculate all other dates. This epoch does not necessarily have to be the calendar’s own Epoch, i.e. the first day of the first month of the first year of the calendar. Computers often use their own, nearer epochs for calculation. Most common are 01/01/1970 used for Unix time, 01/01/1601 used for SYSTEMTIME used by Windows, and 01/01/2001 used by macOS. JVM languages like Kotlin use an epoch of 01/01/1970, but we can create a new epoch representing the first day of the coptic year 1687, the nearest coptic year to start after the JVM epoch.

Next, we need the current offset from the epoch, in days at most. Most systems provide a method of obtaining the current offset in seconds, or even milliseconds, from midnight of the epoch date. Java.time’s Instant class provides this millisecond-level offset5

Lastly, we need a system to convert this offset into a valid date in our calendar system. This represents the core functionality of our code, and is entirely up to us to implement.

First Date

After creating an empty kotlin project, we’ll create a file called CopticCalendar, containing a class of the same name. This class will contain most of our logic. The first day of Coptic year 1687 takes place 21859200 seconds after the Java Epoch, so we’ll add that to our class as a constant

1
2
3
4
5
class CopticCalendar {
    companion object {
        const val EPOCH = 21859200L
    }
}

Next, we’ll create a private function called getDateFromInstant which takes an Instant object and produces a String representing our calendar date. First we subtract our epoch, so that all calculations will be relative to it, and we get the count of days, and ephemeris years that have elapsed since the epoch.

1
2
3
4
5
fun getDateFromInstant(instant: Instant){
	val offset : Long = instant.minusSeconds(EPOCH).toEpochMilli() / 1000L
	val days : Long = offset / 86400L
	val years : Long = (days / 365.25).toLong()
}

Lastly we calculate the year, month, and day, and emit our string

1
2
3
4
5
val year : Long = 1687 + years
val dayInYear : Int = (days - years * 365.25).toInt()
val month : Int = (dayInYear / 30) + 1
val day : Int = dayInYear - ((month - 1) * 30) + 1
return "$day/$month/$year"

Some housekeeping of our code is next: making constants, adding a public function, and commenting our code. The final class should look something like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import java.time.Instant
import kotlin.math.floor

class CopticCalendar {
    companion object {
        const val EPOCH = 21859200L // Seconds between 01/01/1970 and 11/09/1970
        const val DAY = 86400L // Ephemeris Day
        const val YEAR = 365.25 // Julian Year
        const val EPOCH_YEAR = 1687 // The Coptic year our Epoch occurs
    }
    fun getDateToday() : String {
        return getDateFromInstant(Instant.now())
    }

    private fun getDateFromInstant(instant: Instant) : String {
        val offset : Long = instant.minusSeconds(EPOCH).toEpochMilli() / 1000L // Get number of seconds since our epoch
        val days : Long = offset / DAY // How many days it has been since the epoch
        val years : Long = floor(days / YEAR).toLong() // How many ephemeris years
        val year : Long = EPOCH_YEAR + years // Year in Coptic Calendar
        val dayInYear : Int = (days - years * YEAR).toInt() // Calculate how far into the year we are
        val month : Int = (dayInYear / 30) + 1 // Index from 1 instead of 0
        val day : Int = dayInYear - ((month - 1) * 30) + 1 // No edge case for leap day or intercalated month
        return "$day/$month/$year" // result
    }
}

To make our code into a program, we will create a main function, that calls our getDateToday function and exits gracefully. We can put this in the same file under the CopticCalendar class

1
2
3
4
5
fun main(){
	val calendar = CopticCalendar()
	println(calendar.getDateToday())
	exitProcess(0)
}

Note that you will have to import kotlin.system.exitProcess to use it.

Beauty and the Beast

If you compile and run our new program, you’ll get a very unappealing shorthand format like so.

1
5/6/1740

Compare with the output of the date command, whose output looks more like

1
Tue 13 Feb 2024

Weekdays are slightly beyond the scope of our program6 but we can at least manage to print the month correctly.

A Prettier Output

First we add a static array of month names to our companion object

1
2
3
val MONTH_NAMES = listOf("Thout", "Paopi", "Hathor", "Koiak",
	"Tobi", "Meshir", "Paremhat", "Parmouti",
   "Pashons", "Paoni", "Epip", "Mesori", "Pi Kogi Enavot")

Then we index that with our month value and update our string output to use it

1
2
val monthName : String = MONTH_NAMES[month - 1]
return "$day $monthName $year"

Now, our output looks like

1
5 Meshir 1740

which is much better

What’s In a Name?

It’s here that we can quickly adapt this code to represent the Ethiopic and other derived calendars. The date and leap year calculations are identical between the Ethiopic and Coptic calendars, so that leaves the epoch from which to calculate our year, and the names of months. Let’s update our class to be more generic.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
open class CivilCalendar(val epoch: Long, val epochYear: Int, val monthNames: List<String>) {

    companion object {
        const val DAY = 86400L
        const val YEAR = 365.25
    }
    fun getDateToday() : String {
        return getDateFromInstant(Instant.now())
    }

    private fun getDateFromInstant(instant: Instant) : Triple<Int, String, Int> {
        val offset : Long = instant.minusSeconds(epoch).toEpochMilli() / 1000L // Get number of seconds since epoch
        val days : Long = offset / DAY // How many days it has been since the epoch
        val years : Long = floor(days / YEAR).toLong() // How many ephemeris years
        val year : Long = epochYear + years // Year in Era
        val dayInYear : Int = (days - years * YEAR).toInt() // Calculate how far into the year we are
        val month : Int = (dayInYear / 30) // 0-indexed for list access
        val day : Int = dayInYear - (month * 30) + 1 // No edge case for leap day or intercalated month
        val monthName : String = monthNames[month] // Get month from list
            return Triple(day,monthName, year.toInt())
    }

    protected open fun formatDate(triple: Triple<Int, String, Int>) : String{
        val (day, monthName, year) = triple
        return "$day $monthName $year"
    }
}

This open class allows us to generate any calendar of this system, just by specifying an epoch, the year of said epoch, and a list of month names. By separating the formatting into its own protected function, we can make text display overridable without impacting calculation. We’ll make use of this later in this post. To use this new abstract class for either the Coptic or Ethiopic calendars, we need to make a class that extends this base class for each.

1
2
3
4
5
6
7
class CopticCalendar : CivilCalendar(21859200L, 1687, listOf("Thout", "Paopi", "Hathor", "Koiak",
    "Tobi", "Meshir", "Paremhat", "Parmouti",
    "Pashons", "Paoni", "Epip", "Mesori", "Pi Kogi Enavot"))

class EthiopicCalendar : CivilCalendar(21859200L, 1963, listOf("Meskerem", "Tikimt", "Hidar", "Tahsas",
    "Tir", "Yakatit", "Magabit", "Miyazya",
    "Ginbot", "Sene", "Hamle", "Nehasa", "Pagumiene"))

We can now use these in our main function:

1
2
3
4
5
6
7
fun main() {
    val coptic = CopticCalendar()
    val ethiopic = EthiopicCalendar()
    println(coptic.getDateToday())
    println(ethiopic.getDateToday())
    exitProcess(0)
}

Running our code now gives us the following output:

1
2
11 Meshir 1740
11 Yakatit 2016

When not in Rome

Our text is so far entirely in the Roman alphabet, but with Unicode support, we can add a few more display formats.

1
2
3
4
5
6
class CopticGreekCalendar : CivilCalendar(21859200L, 1687, listOf("Ⲑⲱⲟⲩⲧ", "Ⲡⲁⲱⲡⲉ", "Ϩⲁⲑⲱⲣ", "Ⲕⲟⲓⲁⲕ",
    "Ⲧⲱⲃⲓ", "Ⲙⲉϣⲓⲣ", "Ⲡⲁⲣⲉⲙϩⲁⲧ", "Ⲡⲁⲣⲙⲟⲩⲧⲉ",
    "Ⲡⲁϣⲟⲛⲥ", "Ⲡⲁⲱⲛⲓ", "Ⲉⲡⲓⲡ", "Ⲙⲉⲥⲱⲣⲓ", "Ⲡⲓⲕⲟⲩϫⲓ ⲛ̀ⲁⲃⲟⲧ"))
class EthiopicGeezCalendar : CivilCalendar(21859200L, 1963, listOf("መስከረም", "ጥቅምት", "ኅዳር", "ታኅሣሥ",
    "ጥር", "የካቲት", "መጋቢት", "ሚያዝያ",
    "ግንቦት", "ሰኔ", "ሐምሌ", "ነሐሴ", "ጳጉሜ"))

which prints

1
2
11 Ⲙⲉϣⲓⲣ 1740
11 የካቲት 2016

If you’re confident in handling RTL and bidirectional text, you can override the formatDate function to display the Arabic form of the Coptic calendar.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class CopticArabicCalendar : CivilCalendar(21859200L, 1687, listOf("توت",
    "بابه",
    "هاتور",
    "كياك",
    "طوبه",
    "أمشير",
    "برمهات",
    "برموده",
    "بشنس",
    "بأونه",
    "أبيب",
    "مسرا",
    "نسيئ")){

    private val arabic = "٠١٢٣٤٥٦٧٨٩"

    private fun convertNumber(number: String) : String{
        val sb = StringBuilder()
        for(i in number){
            sb.append(arabic["$i".toInt()])
        }
        return sb.toString()
    }
    override fun formatDate(triple: Triple<Int, String, Int>): String {
        val (year, month, day) = triple
        return "${convertNumber(year.toString())} $month ${convertNumber(day.toString())}" // Switch year and day because the console is an LTR context with poor BiDi support
    }
}

Our subclass uses arabic month names, and overrides the formatDate function to use arabic numerals for the digits, along with swapping the order of units around to mimic RTL output.

Note that the above may render strangely on some browsers depending on how well they handle BiDi text. Running our new function gives us the following output in console:

1
١١ أمشير ١٧٤٠

Our final code looks like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
import java.time.Instant
import kotlin.math.floor
import kotlin.system.exitProcess

open class CivilCalendar(val epoch: Long, val epochYear: Int, val monthNames: List<String>) {

    companion object {
        const val DAY = 86400L
        const val YEAR = 365.25
    }
    fun getDateToday() : String {
        return formatDate(getDateFromInstant(Instant.now()))
    }

    private fun getDateFromInstant(instant: Instant) : Triple<Int, String, Int> {
        val offset : Long = instant.minusSeconds(epoch).toEpochMilli() / 1000L
        val days : Long = offset / DAY
        val years : Long = floor(days / YEAR).toLong()
        val year : Long = epochYear + years
        val dayInYear : Int = (days - years * YEAR).toInt()
        val month : Int = (dayInYear / 30)
        val day : Int = dayInYear - (month * 30) + 1
        val monthName : String = monthNames[month]
        return Triple(day,monthName, year.toInt())
    }

    protected open fun formatDate(triple: Triple<Int, String, Int>) : String{
        val (day, monthName, year) = triple
        return "$day $monthName $year"
    }
}
class CopticCalendar : CivilCalendar(21859200L, 1687, listOf("Thout", "Paopi", "Hathor", "Koiak",
    "Tobi", "Meshir", "Paremhat", "Parmouti",
    "Pashons", "Paoni", "Epip", "Mesori", "Pi Kogi Enavot"))

class EthiopicCalendar : CivilCalendar(21859200L, 1963, listOf("Meskerem", "Tikimt", "Hidar", "Tahsas",
    "Tir", "Yakatit", "Magabit", "Miyazya",
    "Ginbot", "Sene", "Hamle", "Nehasa", "Pagumiene"))

class CopticGreekCalendar : CivilCalendar(21859200L, 1687, listOf("Ⲑⲱⲟⲩⲧ", "Ⲡⲁⲱⲡⲉ", "Ϩⲁⲑⲱⲣ", "Ⲕⲟⲓⲁⲕ",
    "Ⲧⲱⲃⲓ", "Ⲙⲉϣⲓⲣ", "Ⲡⲁⲣⲉⲙϩⲁⲧ", "Ⲡⲁⲣⲙⲟⲩⲧⲉ",
    "Ⲡⲁϣⲟⲛⲥ", "Ⲡⲁⲱⲛⲓ", "Ⲉⲡⲓⲡ", "Ⲙⲉⲥⲱⲣⲓ", "Ⲡⲓⲕⲟⲩϫⲓ ⲛ̀ⲁⲃⲟⲧ"))
class EthiopicGeezCalendar : CivilCalendar(21859200L, 1963, listOf("መስከረም", "ጥቅምት", "ኅዳር", "ታኅሣሥ",
    "ጥር", "የካቲት", "መጋቢት", "ሚያዝያ",
    "ግንቦት", "ሰኔ", "ሐምሌ", "ነሐሴ", "ጳጉሜ"))

class CopticArabicCalendar : CivilCalendar(21859200L, 1687, listOf("توت", "بابه", "هاتور", "كياك",
    "طوبه", "أمشير", "برمهات", "برموده",
    "بشنس", "بأونه", "أبيب", "مسرا", "نسيئ")){

    private val arabic = "٠١٢٣٤٥٦٧٨٩"

    private fun convertNumber(number: String) : String{
        val sb = StringBuilder()
        for(i in number){
            sb.append(arabic["$i".toInt()])
        }
        return sb.toString()
    }
    override fun formatDate(triple: Triple<Int, String, Int>): String {
        val (year, month, day) = triple
        return "${convertNumber(year.toString())} $month ${convertNumber(day.toString())}"
    }
}
fun main() {
    val coptic = CopticCalendar()
    val ethiopic = EthiopicCalendar()
    val greek = CopticGreekCalendar()
    val geez = EthiopicGeezCalendar()
    val arab = CopticArabicCalendar()
    println(coptic.getDateToday())
    println(ethiopic.getDateToday())
    println(greek.getDateToday())
    println(geez.getDateToday())
    println(arab.getDateToday())
    exitProcess(0)
}

Upcoming

The next two posts will center around Android and Android Wear development as it pertains to our goals. We will first look at producing date complications for the Ethiopic and Coptic calendars using code like what we’ve produced above. In the second post, we will look at what Android, Java, and Kotlin provide to make the task easier, and rewrite our complications to use the ICU Coptic and Ethiopic Calendar implementations, which will greatly simplify our code.

Other posts in this series:


  1. The month name in this date display may differ from the names listed below. This is because there are multiple competing romanisations of the names of these months. ↩︎

  2. As the story goes, the goddess Nut could not give birth on any day within the calendar year. So the god Thoth gambled with the moon for a portion of its light, enough for 5 days outside the year, which became the birthdays of the gods descended from Nut. ↩︎

  3. This is in fact, the Julian year, and not the tropical, which is instead 365.24217 civil days. ↩︎

  4. This will still slowly drift out of alignment with the tropical year, but we won’t see a more accurate leap calculation until we discuss the Julian and Gregorian calendars. ↩︎

  5. Instant actually supports nanosecond resolution, which is complete overkill for calendrics, but useful for realtime applications ↩︎

  6. They are just $$ (days - epoch + weekday_{epoch}) \mod 7 $$ but we’ll cover weeks and weekdays more fully in a later post ↩︎