Calendar on TFT display

Hi. I am trying to develop a function that will display a monthly calendar as part of my project.
The calendar is displaying incorrectly and I can't work out exactly what is wrong.
If anyone can give me any ideas of what to do or alternative approaches I would really appreciate it.

Thanks

#include <Arduino.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>
#include <RTClib.h>
#include "button.h"
#include <time.h>

	
const char *days[] = {"Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"};

Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST);

RTC_DS1307 rtc;

int getDaysInMonth(int year, int month)
{
  if (month == 2)
  {
    if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))
    {
      return 29; // February in a leap year
    }
    else
    {
      return 28; // February in a non-leap year
    }
  }
  else if (month == 4 || month == 6 || month == 9 || month == 11)
  {
    return 30; // April, June, September, and November
  }
  else
  {
    return 31; // All other months
  }
}

void drawCalendar(int currentDay, int month, int year) {
 tft.setTextColor(ILI9341_WHITE);
 tft.setTextSize(1);

 char text[30];
 int text_w, pos_x, pos_y;
 int16_t x1, y1;
 uint16_t w, h;

 // Calculate the day of the week for the first day of the month
 struct tm tm = {0};
 tm.tm_mday = 1;
 tm.tm_mon = month - 1; // Months are 0-based in tm structure
 tm.tm_year = year - 1900; // Years are years since 1900 in tm structure
 mktime(&tm); // Update the tm structure with the correct values
 int firstDayOfWeek = tm.tm_wday; // Get the day of the week

 int day = 1;
 String str_day = (String) day;
 int days_of_month = getDaysInMonth(year, month);

 pos_x = 0;
 pos_y = 36;
 for (int i=0; i<7; i++)
 {
  pos_x = 104 + i*(tft.width()-108)/7;
  tft.getTextBounds(days[i], 0, 0, &x1, &y1, &w, &h);
  tft.print(days[i]); 
  tft.setCursor(pos_x+((tft.width()-100)/7-w)/2, pos_y);
 }
  
 tft.drawLine(102, 40, 260, 40, ILI9341_BLUE);

 pos_y = 55;

  // Skip the days before the first day of the month
 for (int i=0; i<firstDayOfWeek; i++)
 {
  pos_x = 104 + i*(tft.width()-108)/7;
  tft.setCursor(pos_x, pos_y);
  tft.print(" "); // Print two spaces for empty days
 }

 while(day <= days_of_month)
 {
  for (int i=0; i<7; i++)
  {
      pos_x = 104 + ((i + firstDayOfWeek - 1) % 7) * (tft.width()-108)/7;

      if ((day >= 1) && (day <= days_of_month))
      {
          tft.getTextBounds(str_day, 0, 0, &x1, &y1, &w, &h);
          pos_x += (tft.width()-108)/7/2 - w/2;
          tft.setCursor(pos_x, pos_y);

          // highlight today with filled red circle
          if (day == currentDay)
          {
             int cur_x = tft.getCursorX();
             int cur_y = tft.getCursorY();

             tft.setTextColor(ILI9341_RED);
             
             tft.fillCircle(cur_x+6, cur_y-5, 7, ILI9341_RED);

             tft.print(day);

             tft.setTextColor(ILI9341_WHITE);
          }
          else
          {
             tft.print(day);
          }
      }
      day++;
  }
  pos_y += 16;
 }
}


void setup() {
  Serial.begin(115200);
  tft.begin();
  rtc.begin();
  
  drawCalendar(1, 11, 2023);

}

void loop() {
}

Explain the problem, and post pictures if they would help.

Explain the problem, and post pictures if they would help.

Sure.
Screenshot_2023-11-01_20-37-54

This is a picture of what the function currently produces.
It is mis-aligning the numbers and wrapping them incorrectly.
The blue line is meant to be a divider between the weekday names and the day numbers. i've placed that in the wrong place and that isn't important right now.

The misalignment is most likely due to incorrect math in date placement. And that is always a slog, so you can't really expect forum members to debug that for you. It does not help that you have no comments explaining the "magic numbers" in the code, for example in this line:

pos_x = 104 + i*(tft.width()-108)/7;

Start small and just get the first week formatted correctly, for all months. The rest should be easy after that.

I would not plan on automatic text wrapping to do anything useful.

You make a good point.
I don't normally use magic numbers in my projects for exactly that reason but I did do a lot of hashing about with the code.

I appreciate the advice and will start by trying to get the first week aligned correctly.

Here, you seem to be printing before you position the cursor, which might explain why the day names don't appear in correct positions:

  tft.print(days[i]); 
  tft.setCursor(pos_x+((tft.width()-100)/7-w)/2, pos_y);

Because you want your week to begin on Monday, I suggest that you use this expression as the basis for computing pos_x and pos_y:
(day + ((firstDayOfWeek + 6) % 7)) - 1

Like this:

int pos_index = (day + ((firstDayOfWeek + 6) % 7)) - 1;
pos_x = left_border_width + (cell_width * (pos_index % 7));
pos_y = header_height + (cell_height * (pos_index / 7));

Let me show you why.
Imagine you want a calendar for January 2024. That month starts on a Monday, so you want day 1 to be in row 0, column 0. Then you want to continue in row 0, up through day 7 which goes in row 0, column 6, and then you want to start a new row, so day 8 goes in row 1, column 0. So, you basically want day N to be in cell (N-1), with the cell numbering starting from 0.

       Cell numbering
       Mo Tu We Th Fr Sa Su
Row 0:  0  1  2  3  4  5  6
Row 1:  7  8  9 10 11 12 13
Row 2: 14 15 16 17 18 19 20
Row 3: 21 22 23 24 25 26 27
Row 4: 28 29 30 31 32 33 34
Row 5: 35 36

       January 2024
       Mo Tu We Th Fr Sa Su
Row 0:  1  2  3  4  5  6  7
Row 1:  8  9 10 11 12 13 14
Row 2: 15 16 17 18 19 20 21
Row 3: 22 23 24 25 26 27 28
Row 4: 29 30 31
Row 5: 

Next, your example, November 2023. This month starts on a Wednesday, so you shift everything two cells to the right. You start in row 0, column 2. This time, it is day 5 that goes in row 0, column 6, and it is day 6 that goes in row 1, column 0.

       November 2023
       Mo Tu We Th Fr Sa Su
Row 0:        1  2  3  4  5
Row 1:  6  7  8  9 10 11 12
Row 2: 13 14 15 16 17 18 19
Row 3: 20 21 22 23 24 25 26
Row 4: 27 28 29 30
Row 5: 

Basically, you count off the correct number of empty cells based on the day of the week on which the first day of the month falls, and then you position everything else accordingly.
For November 14, 2023:
November 2023 begins on a Wednesday, so we shift over 2 cells. We want the 14th day of the month, so we add 14 + 2 = 16, and then we subtract 1 to get 15. We do (15 / 7) to get row 2, and we do (15 % 7) to get column 1. Remember, row 2 is really the third row, and column 1 is really the second column, because we are counting starting from zero.

I hope this was not too confusing for you. It can be hard to translate an idea from a mental picture into words. But anyway, in the process of translating it, I found an off-by-one bug and fixed it.

should it look like this??

cal

code in a sim..

have fun.. ~q

1 Like

@odometer: Thanks for that. I appreciate the clear explanation. It's a difficult thing to get my mind around, for sure.

@qubits-us
Thanks for that! It's exactly what I wanted. I'll have a look at the code and see how you did it. I never use anyone else's code without trying to learn how it works first so I can modify it in the future.
Cut and paste is the devil.
Thanks again.

2 Likes

Thanks for that. Good spot!

I would want to remove this function, if it were my code. It feels like something that the time.h or RTClib.h libraries must already know or calculate internally.

One way could be to calculate the date of the 1st of the next month, then subtract one day from that. The RTClib library can do such date calculations if you use it's Date time and TimeSpan classes.

You're welcome..

you were really close, only changed a few lines..

have fun.. ~q

(@qubits-us - nice calendar!)

Or, in Linux:

cal
1 Like

Here's a screenshot of the intended calendar view using the suggestions given in this thread. I've added additional functionality, so highlighted green days are days off, blue days show days where work is scheduled (not currently a real representation, just an example!).
Some tweaks to do, but it's looking very nice. Thanks to all who assisted with this!

1 Like

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.