09 March, 2021
#40: How I coded a lunar calendar in C?
A journey into creating a compact lunar calendar app for the Amazfit Bip smartwatch, exploring data compression techniques and efficient programming in C.
![#40: How I coded a lunar calendar in C?](https://cdn-gcs.ngxson.com/nuiblog/2024/07/blog_code_c_lunar_cover.4fs3s5.jpg)
It's been a while since I last wrote a blog post. Partly because I lost inspiration, and largely because I temporarily gave way to art.ngxson.com
Why?
Getting to the main topic, why did I have to create a lunar-solar calendar app, and why was it mandatory to code in C? Well, let me introduce you to a deeper story, which is about the smartwatch I'm wearing: the Amazfit Bip
This watch has been with me for 3 years now, the same amount of time I've been living in France. Its specs aren't much: 80MHz CPU, 128KB RAM, and 8MB flash memory. Although it only has a few basic features like heart rate measurement, running, alarm,... the most special thing about this watch is its hack / reversed engineering community, which is mainly composed of Russian guys. Maxim Volkov is the one who created BipOS - a custom operating system version. This opens up the possibility of self-programming and installing apps on this watch.
Of course, due to limitations in processing speed and memory, all apps must be coded in C.
The Idea
First, I need to outline the special requirements:
- The code must be optimized to avoid excessive processing when running
- The binary file (compiled) must be light (under 20KB is reasonable)
- Display both lunar and solar calendars. Due to the small watch screen, it only needs to fully display the lunar date of the first day of the month.
- Thus, it needs to handle leap months in the lunar calendar (months with 19 days), but doesn't need to consider leap years in the lunar calendar (years with 13 months)
- Should be able to code quickly (within a day would be good), as my time budget is limited.
Initial idea
So, I'll need to research two parts:
- Data source: where will the data about the days in the month come from (for example, 01/02/2021 in the solar calendar will be Monday, coinciding with 20/12/2020 in the lunar calendar)
- Display => easy if just printing to console, but I have to run it on a smartwatch, which means I need code to manage text and number coordinates.
Data Source
The first idea was to rewrite the algorithm for calculating the day of the week, then the algorithm for converting solar-lunar dates, possibly rewriting from the original Python code, or if there's a ready-made C library, even better.
However, after looking at the code at quangvinh86/SolarLunarCalendar, I noticed two issues: 1) To be honest, there's too much code and calculation, I don't understand it. And 2) it uses sin, cos, which means it will take quite a bit of processing time on the 80MHz CPU of the Amazfit Bip.
So I came to idea 2: find a way to "capture" data from an existing calendar, then hardcode it into the code. I planned to "capture" from Ho Ngoc Duc's calendar app written in JS
This method solves the processing problem, but raises another issue: how to use memory effectively? Suppose each month has 30 days, each day takes 1 byte for the lunar day and 1 byte for the solar day. Thus, one month takes 60 bytes, one year takes 720 bytes. So 20KB is only enough to store about 28 years!
The third idea: "compress" the data:
Upon closer observation, I noticed that each month is defined by the following parameters:
- What day of the week is the 1st of that month in the solar calendar? => Calculation is complex, so we'll store it. Data is a number from 0 to 6; 0 = Sunday and 6 = Saturday.
- The 1st of that month in the solar calendar coincides with which day, month, year in the lunar calendar => we'll store this too. day is a number from 1 ≤ d ≤ 30, month 1 ≤ m ≤ 12, for year, let's just store the last two digits for now: 0 ≤ y ≤ 99
- How many days are in that month (solar) => this follows rules, will be coded
- Is that lunar month a leap month => we'll store this too, Data is boolean (0 or 1)
So roughly calculating, we need to store 5 integers in total, all smaller than 255 so they can be stored in 1 byte (also known as char in C). However, I can do better:
The basic idea is to use 1 byte for 2 data points (if possible). For example, day of the week and month are both integers < 2^4, so we can use 4 bits for the day of the week and 4 bits for the month. I only use 3 bytes to store all 5 of these data points, of course it can be done better, but I'm lazy...
Let's Get to Work
Reference code: prototype.html
- First, I use Ho Duc Ngoc's code to print the calendar to the screen
- Then, query the div containing the first day of the month, read the data of that day
- Calculate 3 bytes of data for that month
- Repeat, I do this with every month from 1/1980 to 12/2059, so about 80 years, 960 months => about 2.8KB
For the display part, I won't explain too much because the logic is actually quite simple: I create a function like my own "printf", it remembers the coordinates of the previous line, the previous character that was printed. Coordinates like margins, font size,... are all hardcoded:
The Result
Link to code on github: ngxson/hobby_amazfit_bip_am_lich
This app is the result of a day of programming, optimizing, and bug fixing. The app can display calendars from 1980 to 2060. The most fun part when I developed this app was sitting and optimizing the code: All data and code were optimized to fit into about 10KB.
Nui Nguyen