GPS CLOCK date changing problem "SOLVED"

I put together a GPS clock using an Arduino Pro-Mini, AI-Thinker GP-02-Kit GPS module and LCD display. I display the Time in hours/minutes/seconds AM/PM, plus the Date in day/month/date. The problem is the Day and Date display changes to the next day at 5pm, not at midnight. The sketch is attached.


// *CONSTANTS & DECLARATIONS*
#include <TinyGPSPlus.h>
#include <SoftwareSerial.h>
#include <LiquidCrystal.h>

// *TIMEZONE SWITCH*
const int timeZoneSwitchPin = A3; // Analog pin A3 for PST-DST switch
bool isPST = false; // Flag to track whether in PST or PDT time zone

LiquidCrystal lcd(7, 6, 5, 4, 3, 2); // LCD connections
const int lcdColumns = 16;
const int lcdRows = 2;

static const int RXPin = 9, TXPin = 8;
static const uint32_t GPSBaud = 9600;

TinyGPSPlus gps;                  // The TinyGPSPlus object
SoftwareSerial ss(RXPin, TXPin);  // Serial Connection to GPS module

// UTCTime is the first term in $GNZDA (1)
// *TinyGPSCustom customField(gps, "Nmea Sentence", 1);*
TinyGPSCustom customUTCTime(gps, "GNZDA", 1);

const char* daysOfWeek[] = {"SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"};
const char* months[] = {"", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"};

// *FUNCTION*
void print12HourFormat(int hour, int minute, int second) {
    
  char amPm[] = "AM  ";

  if (hour >= 12) {
    amPm[0] = 'P';
    if (hour > 12) {
      hour -= 12;
    }
  }

  if (hour == 0) {
    hour = 12;
  }

  // *PRINT TIME TO SERIAL PRINTER*
  Serial.print("Pacific Daylight Time: ");
  Serial.print(hour);
  Serial.print(":");
  if (minute < 10) {
    Serial.print("0");
  }
  Serial.print(minute);
  Serial.print(":");
  if (second < 10) {
    Serial.print("0");
  }
  Serial.print(second);
  Serial.print(" ");
  Serial.println(amPm);  // New line

  // *PRINT TIME TO LCD*
  lcd.setCursor(3, 0);
  lcd.print(hour);
  lcd.print(":");
  if (minute < 10) {
    lcd.print("0");
  }
  lcd.print(minute);
  lcd.print(":");
  if (second < 10) {
    lcd.print("0");
  }
  lcd.print(second);
  lcd.print(" ");  // Print AM/PM right beside seconds-SEE LINE 42
  lcd.print(amPm);
}

//Zeller's Congruence algorithm - calculates the day of the week for any canlendar date.
int calculateDayOfWeek(int year, int month, int day) {
    if (month < 3) {
        month += 12;
        year -= 1;
    }
    int k = year % 100;
    int j = year / 100;

    // Adjust for leap year - Turn on later...
    /*if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) {
        day -= 2;
    }
    */
    
    int dayOfWeek = (day + 13 * (month + 1) / 5 + k + k / 4 + j / 4 + 5 * j) % 7;
    return (dayOfWeek + 6) % 7;  
    // Adjust for the weekday array indexing
}

void printDate(int year, int month, int day, int timeZoneOffset) {
    int calculatedDayOfWeek = calculateDayOfWeek(year, month, day);

    lcd.setCursor(3, 1);
    lcd.print(daysOfWeek[calculatedDayOfWeek]);
    lcd.print(" ");
    lcd.print(months[month]);
    lcd.print(" ");
    lcd.print(day);
}

void setup() {

  Serial.begin(115200);
  ss.begin(GPSBaud);

  // *TIMEZONE SWITCH*
  pinMode(timeZoneSwitchPin, INPUT_PULLUP);  // For PST to DST switch on A3

  // *PRINT INTRO TO LCD*
  lcd.begin(lcdColumns, lcdRows);
  lcd.setCursor(0, 0); // Position 1, Top Line
  lcd.print("<< GPS CLOCK >> ");
  lcd.setCursor(0, 1); // Position 0, Bottom Line
  lcd.print("by: Arduino Fan");
  delay(4000); 
  lcd.clear();

  Serial.println("#####################################################");
  Serial.println(F("  Sketch: GPS_NEW_ATOMIC_CLOCK_19AUG2023_LCD_DATE.ino"));
  Serial.println(F("  Compiled by expermenter 20 AUGUST 2023"));
  Serial.print(F("  Using TinyGPSPlus library v. ")); Serial.println(TinyGPSPlus::libraryVersion());
  Serial.println("#####################################################");
  delay(2000);
}

void loop() {
  while (ss.available() > 0) {
    gps.encode(ss.read());
  }

  // Print everyting every time anything is updated.
  if (gps.altitude.isUpdated() || customUTCTime.isUpdated() || gps.date.isUpdated() || gps.satellites.isUpdated()) {

    // ##### *TIMEZONE SWITCH* ##### Read the state of the time zone switch
    bool switchState = digitalRead(timeZoneSwitchPin);

    // Check if the switch state has changed
    if (switchState != isPST) {
      isPST = switchState;
      Serial.print("Time zone switched to: ");
      Serial.println(isPST ? "PST" : "PDT");
    }

    // Determine time zone offset based on the switch state
    int timeZoneOffset = isPST ? 8 : 7;
    // ##### *TIMEZONE SWITCH END* #####

    // ##### Call the 12-hour time print function with PST offset #####
    if (customUTCTime.isUpdated()) {
      int hour, minute, second;
      if (sscanf(customUTCTime.value(), "%2d%2d%2d", &hour, &minute, &second) == 3) {
        // Convert to Pacific Standard Time (PST) from UTC time (UTC - 8 hours)
        hour = (hour - timeZoneOffset + 24) % 24; //Change 7-8 for standard time (PST)

        // ##################### PRINT TIME TO LCD #############################
        print12HourFormat(hour, minute, second);
      }

      int year = gps.date.year();
      int month = gps.date.month();
      int day = gps.date.day();

      //printDate(year, month, day);
      printDate(year, month, day, timeZoneOffset);
    }
  }
}  /* END OF LOOP */

Did you try to adjust a timezone settings?

Maybe, but the TIMEZONE SWITCH on A3 is working fine, changing the Local time between PST (Pacific Standard Time) and PDT (Pacific Daylight Time).
I was wondering if there was a typo in the Zeller's Algorithm code, on these line:
int dayOfWeek = (day + 13 * (month + 1) / 5 + k + k / 4 + j / 4 + 5 * j) % 7;
return (dayOfWeek + 6) % 7;

Your code accounts for the time zone difference for the hour, but uses the straight UTC date as reported by the GPS. If you're in the Pacific time zone, the UTC date will advance to the next day 7 hours before midnight (8 during DST), because that's when it's midnight UTC.

It passes timeZoneOffset to printDate, but doesn't actually do anything with it. Nor could it without also passing some flag indicating whether the time zone adjustment had adjusted the hour prior to 0 or past 24.

Who is F. W. McLennan? Why point out to them that the date isn't right?
Apparently, they care more about the credits for the sketch than about getting the date right.

The date changes because the UTC date changes at 00:00 which is your local 5pm. Same as here on the West Coast of USA when on standard time.

I think you highlighted the issue. We are on Pacific Daylight Time (PDT) so I would need the 8 hour correction like you say. Unfortunately, with my limited coding skills I would not be able to correct this. Is there anyone who could help me out?

This should fix your problem with the date. Unfortunately, I have no way of testing it, so you will have to let me know if it runs correctly, or even if it runs at all.

// *CONSTANTS & DECLARATIONS*
#include <TinyGPSPlus.h>
#include <SoftwareSerial.h>
#include <LiquidCrystal.h>

// *TIMEZONE SWITCH*
const int timeZoneSwitchPin = A3; // Analog pin A3 for PST-DST switch
bool isPST = false; // Flag to track whether in PST or PDT time zone

LiquidCrystal lcd(7, 6, 5, 4, 3, 2); // LCD connections
const int lcdColumns = 16;
const int lcdRows = 2;

static const int RXPin = 9, TXPin = 8;
static const uint32_t GPSBaud = 9600;

TinyGPSPlus gps;                  // The TinyGPSPlus object
SoftwareSerial ss(RXPin, TXPin);  // Serial Connection to GPS module

// UTCTime is the first term in $GNZDA (1)
// *TinyGPSCustom customField(gps, "Nmea Sentence", 1);*
TinyGPSCustom customUTCTime(gps, "GNZDA", 1);

const char* daysOfWeek[] = {"SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"};
const char* months[] = {"", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"};

// *FUNCTION*
void print12HourFormat(int hour, int minute, int second) {
    
  char amPm[] = "AM  ";

  if (hour >= 12) {
    amPm[0] = 'P';
    if (hour > 12) {
      hour -= 12;
    }
  }

  if (hour == 0) {
    hour = 12;
  }

  // *PRINT TIME TO SERIAL PRINTER*
  Serial.print("Pacific Daylight Time: ");
  Serial.print(hour);
  Serial.print(":");
  if (minute < 10) {
    Serial.print("0");
  }
  Serial.print(minute);
  Serial.print(":");
  if (second < 10) {
    Serial.print("0");
  }
  Serial.print(second);
  Serial.print(" ");
  Serial.println(amPm);  // New line

  // *PRINT TIME TO LCD*
  lcd.setCursor(3, 0);
  lcd.print(hour);
  lcd.print(":");
  if (minute < 10) {
    lcd.print("0");
  }
  lcd.print(minute);
  lcd.print(":");
  if (second < 10) {
    lcd.print("0");
  }
  lcd.print(second);
  lcd.print(" ");  // Print AM/PM right beside seconds-SEE LINE 42
  lcd.print(amPm);
}

//Zeller's Congruence algorithm - calculates the day of the week for any canlendar date.
int calculateDayOfWeek(int year, int month, int day) {
    if (month < 3) {
        month += 12;
        year -= 1;
    }
    int k = year % 100;
    int j = year / 100;

    // Adjust for leap year - Turn on later...
    /*if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) {
        day -= 2;
    }
    */
    
    int dayOfWeek = (day + 13 * (month + 1) / 5 + k + k / 4 + j / 4 + 5 * j) % 7;
    return (dayOfWeek + 6) % 7;  
    // Adjust for the weekday array indexing
}

int days_in_month (int y, int m) {
  // Essential helper function for advancing time:
  // it gives the number of days in month m of year y.
  // Fourth, eleventh, ninth, and sixth,
  // thirty days to each we fix. 
  if ((m==4)||(m==11)||(m==9)||(m==6)) return 30; 
  // Every other, thirty-one,
  // except the second month alone,
  if (m!=2) return 31;
  // which hath twenty-eight, in fine,
  // till leap-year give it twenty-nine.
  if ((y%400)==0) return 29; // leap year
  if ((y%100)==0) return 28; // not a leap year
  if ((y%4)==0)   return 29; // leap year
  return 28; // not a leap year 
}

void printDate(int year, int month, int day) {
    int calculatedDayOfWeek = calculateDayOfWeek(year, month, day);

    lcd.setCursor(3, 1);
    lcd.print(daysOfWeek[calculatedDayOfWeek]);
    lcd.print(" ");
    lcd.print(months[month]);
    lcd.print(" ");
    lcd.print(day);
}

void setup() {

  Serial.begin(115200);
  ss.begin(GPSBaud);

  // *TIMEZONE SWITCH*
  pinMode(timeZoneSwitchPin, INPUT_PULLUP);  // For PST to DST switch on A3

  // *PRINT INTRO & CREDITS TO LCD*
  lcd.begin(lcdColumns, lcdRows);
  lcd.setCursor(0, 0); // Position 1, Top Line
  lcd.print("<< GPS CLOCK >> ");
  lcd.setCursor(0, 1); // Position 0, Bottom Line
  lcd.print("by: f.w.mclennan");
  delay(4000);  // fwm credits
  lcd.clear();

  Serial.println("#####################################################");
  Serial.println(F("  Sketch: GPS_NEW_ATOMIC_CLOCK_19AUG2023_LCD_DATE.ino"));
  Serial.println(F("  Compiled by f.mclennan 20 AUGUST 2023"));
  Serial.print(F("  Using TinyGPSPlus library v. ")); Serial.println(TinyGPSPlus::libraryVersion());
  Serial.print(F("  Date code modified by odometer 26 April 2024 "));
  Serial.println("#####################################################");
  delay(2000);
}

void loop() {
  while (ss.available() > 0) {
    gps.encode(ss.read());
  }

  // Print everyting every time anything is updated.
  if (gps.altitude.isUpdated() || customUTCTime.isUpdated() || gps.date.isUpdated() || gps.satellites.isUpdated()) {

    // ##### *TIMEZONE SWITCH* ##### Read the state of the time zone switch
    bool switchState = digitalRead(timeZoneSwitchPin);

    // Check if the switch state has changed
    if (switchState != isPST) {
      isPST = switchState;
      Serial.print("Time zone switched to: ");
      Serial.println(isPST ? "PST" : "PDT");
    }

    // Determine time zone offset based on the switch state
    int timeZoneOffset = isPST ? 8 : 7;
    // ##### *TIMEZONE SWITCH END* #####

    // ##### Call the 12-hour time print function with PST offset #####
    if (customUTCTime.isUpdated()) {
      int hour, minute, second;
      if (sscanf(customUTCTime.value(), "%2d%2d%2d", &hour, &minute, &second) == 3) {
        // Convert to Pacific Standard Time (PST) from UTC time (UTC - 8 hours)
        hour -= timeZoneOffset; //Change 7-8 for standard time (PST)

        // Get the UTC date, which we will then convert into the local date
        int year = gps.date.year();
        int month = gps.date.month();
        int day = gps.date.day();

        // Convert date to local date: Hours carry into days
        if (hour < 0) {
          hour += 24;
          day--;
        }

        // Days carry into months.
        if (day < 1) {
          month--;
          // Months carry into years.
          if (month < 1) {
            month += 12;
            year--;
          }
          day += days_in_month(year, month);
        }

        // ##################### PRINT TIME TO LCD #############################
        print12HourFormat(hour, minute, second);

        printDate(year, month, day);


      }
    }
  }
}  /* END OF LOOP */

So Sorry, I forgot to erase my name!

Ok, I will load your code up tomorrow and let you know asap.
Thanks, Mac

What my code is meant to do is this: First, I subtract 7 or 8 hours to do the timezone conversion. This can possibly result in the hours being less than 0. If that happens, that means we have moved through midnight into the previous day. We check to see if that has happened, and if it has happened, then we fix up the hours by adding 24 hours, and we also fix up the date by subtracting 1 day. I hope that this makes sense to you.

Yes, I understand your explanation. I have loaded your code into the clock this morning and Time & Date are displaying ok. I will have to wait till 5pm this afternoon to see if the code works. If it does, I will need to check it again at midnight to confirm it moves ahead one day. I'll let you know Sunday. Thanks for you time and expertise. Mac

I am pleased to report that odometer's code addition did indeed solve the Day/Date issue with my GPS Clock. Many thanks to 'odometer' and other members.

Thank you for letting me know that the code solved your problem.
Since it solved your problem, you can mark my post with the code as being the solution. There should be a check box you can check off to do that.

Ok, the Solution box has been checked. Thanks again odometer.

1 Like

I just noticed that your printDate() function needs a small fix. Otherwise, you will get this sequence of dates:

MON APR 29
TUE APR 30
WED MAY 10 <-- because the 0 from the previous day will not get erased

You could fix that either like this:

void printDate(int year, int month, int day) {
    int calculatedDayOfWeek = calculateDayOfWeek(year, month, day);

    lcd.setCursor(3, 1);
    lcd.print(daysOfWeek[calculatedDayOfWeek]);
    lcd.print(" ");
    lcd.print(months[month]);
    lcd.print(" ");
    if (day < 10) {
      lcd.print("0");
    }
    lcd.print(day);
}

or else like this:

void printDate(int year, int month, int day) {
    int calculatedDayOfWeek = calculateDayOfWeek(year, month, day);

    lcd.setCursor(3, 1);
    lcd.print(daysOfWeek[calculatedDayOfWeek]);
    lcd.print(" ");
    lcd.print(months[month]);
    lcd.print(" ");
    lcd.print(day);
    if (day < 10) {
      lcd.print(" ");
    }
}

An alternative to coding this "manually" exists.

Look at the Timezone library, it has easy ways to set the time zone and also customize the dates for Daylight Savings Time.

# include <Timezone.h>    // https://github.com/JChristensen/Timezone

From a run in the simulator, we see the RTC is kept on UTC, and the date/time we use elsewhere is always adjusted for time zone and DST.

RTC has set the system time [ISO 8601 "initTime":"2019-11-19T23:59:51"]

23:59:51 Tue 19 Nov 2019 UTC
18:59:51 Tue 19 Nov 2019 EST

00:00:01 Wed 20 Nov 2019 UTC
19:00:01 Tue 19 Nov 2019 EST

00:00:11 Wed 20 Nov 2019 UTC // already tomorrow in Greenwich!
19:00:11 Tue 19 Nov 2019 EST

I used this example from the Timezone library repository. The wokwi RTC part allows setting to an arbitrary time.


No need for Zeller either.

HTH someone.

a7

1 Like

odometer, I just programed the first code sample, but I will have to monitor it for a day to confirm it works. I will let you know. Mac :smiley:

alto777, thanks for the reply of good information. However, if I used a DS1307RTC, I would not need the gps module. I just wanted to make a clock from signals from the gps satellites. I will let everyone know if/when it's working reliably. Mac :smiley:

Hi odometer, the date finally came around to the first of the month, and your update worked great. I originally opted for a blank space, but I think it will look better on the display if I have it print a zero. Many thanks. Mac :sunglasses: