Timer Program Freezes Intermittently

If so, then millis() rollover is not the issue.

Is it just the lcd display which freezes, or does the underlying program stop. You may need to add a blinking heartbeat to help differentiate.

I would suspect the relays. How is everything powered. Are there flyback diodes on all relay coils? Is there a capacitor across the power input of the display?

The easiest way to trouble shoot is to disconnect the physical outputs to the relays and just run the code. That should separate the unusual millis treatments from the possible hardware issues.

I'm not sure how everything is wired up. I did the wiring a few years back. It should be very simple, I'm using the grove interface. I just have an LCD screen and two relays. I don't remember exactly how the relays were wired in, but I think they just break the circuit in a regular outlet. They make a clicking noise every time the relay state changes. I believe this is the one I used https://www.seeedstudio.com/Grove-Relay.html If the wiring is the issue then fixing this may be beyond me for now...I can't invest too much time into this project.

I did a couple sessions updating the code. I hope it is more readable and maybe conventional now. I did away with the addition as suggested and switched to more of a comparison range (see timerSequenceFunctionality() )

Going to try to run this for a day or so and see if it freezes up again.

int relayOne = 3;  // base shield pin number for Relay 1 // Connect the LED to Arduino digital pin 4, Grove socket D4
int relayTwo = 4;  // base shield pin number for Relay 2

const long faeOnEnd = 60000;
const long humOnEnd = 150000;
const long allOffEnd = 1200000;
const long resetEnd = 1203000;  // reset screen

unsigned long previousDisplayMillis = 0;
int currentDisplayInterval = 1000;

int faeRelayOneState = LOW;

int currentState = 2;  //immediately advances to 0

char *lcdStrings[] = { " fae ON ", " hum ON ", " all OFF" };

// rgb screen

#include <Wire.h>
#include "rgb_lcd.h"

rgb_lcd lcd;

int colorR = 255;
int colorG = 255;
int colorB = 0;

unsigned long currentMillis = 0;

void setup() {

  pinMode(relayOne, OUTPUT);  // setup for Relay 1 "initialize ledPin as an output."
  pinMode(relayTwo, OUTPUT);

  // set up the LCD's number of columns and rows:
  lcd.begin(16, 2);
  // set screen background color
  lcd.setRGB(colorR, colorG, colorB);

  //Serial.begin(9600);
}

void loop() {
  timerSequencerFunctionality();
  displayTimer();
}

void timerSequencerFunctionality() {
  incrementTime();
  if ((currentMillis <= faeOnEnd) && (currentMillis >= 0)) {
    changeState(0);
  } else if ((currentMillis <= humOnEnd) && (currentMillis > faeOnEnd)) {
    changeState(1);
  } else if ((currentMillis <= allOffEnd) && (currentMillis > humOnEnd)) {
    changeState(2);
  } else if ((currentMillis <= resetEnd) && (currentMillis > allOffEnd)) {
    changeState(3);
  }
}

void incrementTime() {
  currentMillis = millis() % resetEnd;
}

void changeState(int c) {
  if (currentState != c) {
    currentState = c;
    currentStateFunction();
  };
}

void currentStateFunction() {
  switch (currentState) {
    case 0:  // fae
      lcdPrintAllEnd(allOffEnd);
      relayChange(relayTwo, LOW, relayOne, HIGH);
      lcdChange(0, faeOnEnd);
      break;
    case 1:                                        //run hum
      relayChange(relayTwo, HIGH, relayOne, LOW);  // turn on hum, turn off fae
      lcdChange(1, humOnEnd);
      break;
    case 2:                                       //all off
      relayChange(relayTwo, LOW, relayOne, LOW);  // turn off hum
      lcdChange(2, allOffEnd);
      break;
    case 3:
      lcd.clear();
  }
}

void relayChange(int relay2, int relay2value, int relay1, int relay1value) {
  digitalWrite(relay2, relay2value);
  digitalWrite(relay1, relay2value);
}

void lcdChange(int z, const long c) {
  lcd.setCursor(8, 0);       // set text print position
  lcd.print(lcdStrings[z]);  // display text

  lcd.setCursor(0, 1);  // display the current states ending time
  lcd.print(c / 1000);
}

void lcdPrintAllEnd(const long c) {
  lcd.setCursor(8, 1);
  lcd.print(c / 1000);
}

void displayTimer() {

  if ((currentMillis - previousDisplayMillis) >= currentDisplayInterval) {

    previousDisplayMillis = currentMillis;

    if (currentState != 3) {
      lcd.setCursor(0, 0);
      lcd.print(currentMillis / 1000);
    }

    lcd.setRGB(random(0, 255), random(0, 255), random(0, 255));  // set screen to random color;

  }
}

Below is my final solution which so far has not freezed. Thank you to everyone for their help with this issue:

unsigned long currentStartTime = 0;
unsigned long currentEndTime = 0;

unsigned long phaseLengths[] = { 60000, 90000, 1050000, 1000 };
int phaseCount = 0;
int numberOfPhaseLengths = 3;  // hard written, should be derived from phaseLengths.length() or similar

unsigned long previousDisplayTime = 0;

#include <Wire.h>
#include "rgb_lcd.h"

rgb_lcd lcd;

void setup() {
  pinMode(3, OUTPUT);  // setup for Relay 1 "initialize ledPin as an output." // base shield pin number for Relay 1 // Connect the LED to Arduino digital pin 4, Grove socket D4
  pinMode(4, OUTPUT);  // base shield pin number for Relay 2

  // set up the LCD's number of columns and rows:
  lcd.begin(16, 2);
  // set screen background color
  lcd.setRGB(255, 255, 255);

  currentEndTime = phaseLengths[phaseCount];
  changePhase(phaseCount);
}

void loop() {
  unsigned long currentMillis;

  currentMillis = incrementTime();
  previousDisplayTime = displayTime(currentMillis, previousDisplayTime, phaseCount);

  incrementPhaseCheck(currentMillis);
}

unsigned long incrementTime() {
  unsigned long tempCurrentMillis;
  tempCurrentMillis = millis();
  return tempCurrentMillis;
}

unsigned long displayTime(unsigned long tempCurrentMillis, unsigned long tempPreviousDisplayTime, int tempPhaseCount) {

  int currentDisplayInterval = 1000;

  unsigned long displayTime = newMap(tempCurrentMillis, currentStartTime, currentEndTime, 0, phaseLengths[tempPhaseCount]);

  int colorR = 0;
  int colorG = 0;
  int colorB = 0;

  if ((tempCurrentMillis - tempPreviousDisplayTime) >= currentDisplayInterval) {

    unsigned long formattedDisplayTime = (displayTime + 1000) / 1000;

    lcd.setCursor(0, 0);
    lcd.print(formattedDisplayTime);

    tempPreviousDisplayTime = tempCurrentMillis;

    lcd.setRGB(random(0, 255), random(0, 255), random(0, 255));  // set screen to random color;
  }

  return tempPreviousDisplayTime;
}

void incrementPhaseCheck(unsigned long tempCurrentMillis) {

  if (tempCurrentMillis > currentEndTime) {

    phaseCount = (phaseCount + 1) % numberOfPhaseLengths;
    changePhase(phaseCount);

    currentStartTime = tempCurrentMillis;
    currentEndTime = currentStartTime + phaseLengths[phaseCount];

  } 
}

void changePhase(int tempPhaseCount) {

  int relayOne = 3;
  int relayTwo = 4;

  switch (tempPhaseCount) {
    case 0:  // fae
      relayChangeHigh(relayOne);
      relayChangeLow(relayTwo);
      lcdPhaseChange(tempPhaseCount, phaseLengths[tempPhaseCount]);
      break;
    case 1:  // run hum
      relayChangeHigh(relayTwo);
      relayChangeLow(relayOne);  // turn on hum, turn off fae
      lcdPhaseChange(tempPhaseCount, phaseLengths[tempPhaseCount]);
      break;
    case 2:                      // all off
      relayChangeLow(relayTwo);  // turn off hum
      lcdPhaseChange(tempPhaseCount, phaseLengths[tempPhaseCount]);
      Serial.print("New Phase Count ");
      Serial.println(tempPhaseCount);
      break;
    case 3:
      break;
  }
}

void relayChangeHigh(int relayNumber) {
  digitalWrite(relayNumber, HIGH);
}

void relayChangeLow(int relayNumber) {
  digitalWrite(relayNumber, LOW);
}

void lcdPhaseChange(int z, const long tempTime) {
  char *lcdStrings[] = { " fae ON ", " hum ON ", " all OFF" };
  const long tempTimeSeconds = tempTime / 1000;

  lcd.clear();
  //lcd.setCursor(0, 0);
  //lcd.print("    ");

  lcd.setCursor(8, 0);       // set text print position
  lcd.print(lcdStrings[z]);  // display text

  lcd.setCursor(0, 1);  // display the current states ending time
  lcd.print(tempTimeSeconds);

  lcdPrintAllEnd();
}

void lcdPrintAllEnd() {
  unsigned long allEndTime = (phaseLengths[0] + phaseLengths[1] + phaseLengths[2]) / 1000;
  lcd.setCursor(8, 1);
  lcd.print(allEndTime);
}

unsigned long newMap(unsigned long x, unsigned long in_min, unsigned long in_max, unsigned long out_min, unsigned long out_max) {
  return (x - in_min) * ((out_max - out_min) / (in_max - in_min)) + out_min;
}