Find Difference between two hours?

I want to be able to input two different values for hours (will always be just the hour, no minutes or seconds). Like so:

    #define EARLIEST_HOUR_AM 8
    #define LATEST_HOUR_PM 9
    
    int START_TIME;
    int RUNTIME_HOURS = 8;

From those two Hour values I want to find the midpoint between them and I will then set a start time for the RUNTIME_HOURS that slots it exactly in the middle of the EARLIEST and LATEST times. What is the best way to accomplish this? If it wasn’t for the earliest and latest sometimes ending in decimal values like 13.5 I could just average them. So I either need to find a way to take that HOUR.5 value when it happens and map it to HOUR:30 or rethink my strategy. Thank in advance…

Hi @LukeUSMC

You probably would rather use a float for RUNTIME_HOURS, so that you can have decimals.
This way when you find the mean (average), you can have it still be in decimals.


float RUNTIME_HOURS = ((EARLIEST_HOUR_AM + LATEST_HOUR_PM) / 2);

If you want to convert decimals into minutes, you could try this:

//This function returns the decimal at the end of a float as minutes.  It will do only minutes, not hours.
int decimalToMinutes(float number)
{
int number_int = number;
float minutes = ((number - number_int) * 60);
return minutes;
}

If you were to do this function with 13.5 for example, it would return 30.

I also recommend that you use 24 hour time, as it is what is used on the Photon itself.

1 Like

That’s perfect! Thank you so much. Still learning much of this. Photon/Core and C in general.

1 Like

Glad I could help. :smile:

OK, I need tad more help here. How do I determine if I have a value with a decimal? Then how do feed the Hour and Minutes into that function to get the Minutes and recombine that back into an Hour:Minute format? I have been digging around C++ forums and found floor but that doesn’t seem to work on Particle stuff.

hey @LukeUSMC,

you could try something like this to use Unix timestamps; compiles but not tested:

#define EARLIEST_HOUR_AM 8
#define LATEST_HOUR_PM 21 // you want to use the 24 hour clock here

bool lastCheck = false;

void setup() 
{
  // put your setup code here, to run once:
}

void loop() 
{
  bool pastTheMidpoint = (EARLIEST_HOUR_AM, LATEST_HOUR_PM);  // becomes true right at the moment of the halfway point in seconds...
  if (pastTheMidPoint && !lastCheck)
  {
    //do something
  }
  lastCheck = pastTheMidpoint;
}

bool middleMoment(int startHour, int endHour)
{
  int start_time = tmConvert_t(Time.year(), Time.month(), Time.day(), startHour, 0, 0);
  int end_time = tmConvert_t(Time.year(), Time.month(), Time.day(), endHour, 0, 0);
  int now_time = tmConvert_t(Time.year(), Time.month(), Time.day(), Time.hour(), Time.minute(), Time.second());
  return (now_time > ((endTime + startTime) / 2);
}

inline time_t tmConvert_t(int YYYY, byte MM, byte DD, byte hh, byte mm, byte ss)
{
  struct tm t;
  t.tm_year = YYYY-1900;
  t.tm_mon = MM;
  t.tm_mday = DD;
  t.tm_hour = hh;
  t.tm_min = mm;
  t.tm_sec = ss;
  t.tm_isdst = 0;
  time_t t_of_day = mktime(&t);
  return t_of_day;
}

semper fi

Thanks! I think I wasn’t clear on what I want to do or I’m not understanding your code. I am calculating a RUN_TIME of say 8 hours based on a few different conditions. I want that RUN_TIME to fall directly into the middle of the EARLIEST_TIME_AM and LATEST_TIME_PM. The earliest and latest will be user defined and RUN_TIME is in half hour increments. So I was trying to set START_TIME by splitting EARLIEST and LATEST times down the middle then splitting RUN_TIME in half and setting START_TIME by subtracting the RUN_TIME.half from MIDPOINT. This is what I had cobbled together but I think I need to rethink my strategy…

#define _START_HOUR_AM  9
#define _END_HOUR_PM 19

void pumpcalcs(){
      //1 Cycle is 1 Hour
      TEMPBASED_PUMP_CYCLES = (currentFcastmax/10);
      if (TEMPBASED_PUMP_CYCLES < MIN_PUMP_CYCLES)
        {
        _CYCLES_TODAY = MIN_PUMP_CYCLES;
        }
        else
          _CYCLES_TODAY = TEMPBASED_PUMP_CYCLES;
    
      _OPERATE_MIDPOINT = ((_START_HOUR_AM + _END_HOUR_PM)/2);
      _START_TODAY = ((_OPERATE_MIDPOINT) - (_CYCLES_TODAY / 2));
    
      //FIX Half Hour Midpoint Calcs
      
    }

The next hurdle is to store currentday_RUN_TIME someplace else (and a bunch of other user config data) so that if the device reboots we know how long it has run in that 24 hour period.

Semper Fi!

I am not very smart…I was doing it the way hard way. This is my new approach, cutting all the mean/averaging out.

_START_TODAY = ((RUN_TIME_SPAN - PUMP_CYCLES_TODAY) + _START_HOUR_AM);

This does put me back to the float to time problem but is much easier. If START_HOUR_AM = 8 and END_HOUR_PM = 19 and my TEMPBASED_PUMP_CYCLES = 9.2 I would have a START_TODAY value of 9.8. The RUN_TIME_SPAN is (END_HOUR_PM - START_HOUR_AM).

So how do I take the value of 9.8 and turn that into 9:48?

I will then just add _CYCLES_TODAY to the START_TODAY to set a STOP_TODAY value I can take action on.

OK, we did that with the Unix timestamps.. (I'm trying to win you over here :slight_smile: ) we found the exact middle in seconds rather than dealing with hours, seconds and fractions of hours... yuk.

I'm still trying to understand what you want to do...

I think you want the "middle of the (configurable) run time" or 8hrs in your example, to be in the "middle of the (configurable) range" between 09:00 and 19:00 (in your example). Or do you want run time to start at the "middle of the range" between 09:00 and 19:00? I think the former...

think in unix timestamps. It will be Soooooo much simpler than fractions and floats...

I want the first. For the runtime to be in the middle of the configurable range. I’ll look harder at the seconds piece. I think I see what you mean…sometimes I get stuck into a particular approach, bad habit. I just didn’t understand the code you posted.

In my example of 9 and 19 with a temp of 90 the StartTime should be 9:30 and run until 18:30 essentially evenly spreading the 1 hour of non runtime between the possible start and start then end and possible end.

OK, look at this, and I put some notes in so you can see what’s what. This compiles, but I didn’t test it:

#define EARLIEST_HOUR_AM 8
#define LATEST_HOUR_PM 21 
#define RUN_TIME 6 // hours

bool lastCheck = false;

void setup() 
{
  // put your setup code here, to run once:
}

void loop()
{
  bool isActive = checkTime(EARLIEST_HOUR_AM, LATEST_HOUR_PM, RUN_TIME);
  if (isActive && !lastCheck)
  {
    //do something when flips active
  }
  else if (!isActive && lastCheck)
  {
    // do something when flips inactive
  }
  lastCheck = isActive;

}

bool checkTime(int startHour, int endHour, int runTime)
{
  int start_range = tmConvert_t(Time.year(), Time.month(), Time.day(), startHour, 0, 0);  // create a Unix timestamp representing the beginning of the range
  int end_range = tmConvert_t(Time.year(), Time.month(), Time.day(), endHour, 0, 0);  // create a Unix timestamp representing the ending of the range
  int middle_range = (start_range +  end_range) / 2;  // create a unix timestamp for the middle of the range
  int cycle_start_time = middle_range - (runTime * 60 / 2); // move the start time back by half the run time
  int cycle_end_time = middle_range + (runTime * 60 / 2); // move the end time forward by half the run time
  int now_time = tmConvert_t(Time.year(), Time.month(), Time.day(), Time.hour(), Time.minute(), Time.second());  // current time
  return (now_time > cycle_start_time && now_time < cycle_end_time);  // return true when we are in between cycle_start_time and cycle_end_time
}

inline time_t tmConvert_t(int YYYY, byte MM, byte DD, byte hh, byte mm, byte ss)
{
  struct tm t;
  t.tm_year = YYYY-1900;
  t.tm_mon = MM;
  t.tm_mday = DD;
  t.tm_hour = hh;
  t.tm_min = mm;
  t.tm_sec = ss;
  t.tm_isdst = 0;
  time_t t_of_day = mktime(&t);
  return t_of_day;
}

OK, I think I have it slotted into my app correctly now, will test shortly. Just curious on one thing, I see that the third input for checkTime is an int. I need a float (double is overkill but if no difference I will take it) since runTime will more often than not generate a factional value. Can I just change that or does that make things go crazy? Long term, I would love to support the ability to set any Start and Stop time value. I started with hours only in an attempt to make things easier for my PoC workup.

@LukeUSMC

So you want to create the ability to enter the time in Hours and Minutes… then why make it so onerous using floats and having to do the math “what is the decimal of 8 hours and 25mins?” no that isn’t very friendly.

How about creating a time variable for your project:

struct deviceTime {
  int Hour;
  int Minute;
};

deviceTime runTime = {8,30};  //runtime = 8 hours and 30 minutes

then use that with the code above:

#define EARLIEST_HOUR_AM 8
#define LATEST_HOUR_PM 21

struct deviceTime {
  int Hour;
  int Minute;
};

deviceTime runTime = {8,30};  //runtime = 8 hours and 30 minutes

bool lastCheck = false;

void setup()
{
  // put your setup code here, to run once:
}

void loop()
{
  bool isActive = checkTime(EARLIEST_HOUR_AM, LATEST_HOUR_PM, runTime);
  if (isActive && !lastCheck)
  {
    //do something when flips active
  }
  else if (!isActive && lastCheck)
  {
    // do something when flips inactive
  }
  lastCheck = isActive;

}

bool checkTime(int startHour, int endHour, struct deviceTime operatingTime)
{
  int start_range = tmConvert_t(Time.year(), Time.month(), Time.day(), startHour, 0, 0);  // create a Unix timestamp representing the beginning of the range
  int end_range = tmConvert_t(Time.year(), Time.month(), Time.day(), endHour, 0, 0);  // create a Unix timestamp representing the ending of the range
  int middle_range = (start_range +  end_range) / 2;  // create a unix timestamp for the middle of the range
  int cycle_start_time = middle_range - (operatingTime.Hour * 3600 / 2) - (operatingTime.Minute * 60 / 2); // move the start time back by half the run time
  int cycle_end_time = middle_range + (operatingTime.Hour * 3600 / 2) + (operatingTime.Minute * 60 / 2); // move the end time forward by half the run time
  int now_time = tmConvert_t(Time.year(), Time.month(), Time.day(), Time.hour(), Time.minute(), Time.second());  // current time
  return (now_time > cycle_start_time && now_time < cycle_end_time);  // return true when we are in between cycle_start_time and cycle_end_time
}

inline time_t tmConvert_t(int YYYY, byte MM, byte DD, byte hh, byte mm, byte ss)
{
  struct tm t;
  t.tm_year = YYYY-1900;
  t.tm_mon = MM;
  t.tm_mday = DD;
  t.tm_hour = hh;
  t.tm_min = mm;
  t.tm_sec = ss;
  t.tm_isdst = 0;
  time_t t_of_day = mktime(&t);
  return t_of_day;
}

well then that just begs the question: "why not input all three times as deviceTimes?)

(untested) :wink:

runTime is calculated elsewhere by taking the forecastMaxTemp and dividing it by 10. How do I take that 9.2 for a 92 degree day and get it to a {Hour,Minute) format? If you just want to point me at a tutorial or something that would be fine by me. I learn quickly and mostly by doing but going from no development experience to a working prototype is going to take some effort and patience. I am long on effort but short on patience. I bet you or any number of other people could write what I need in a matter of a few hours but where is the fun in that?!?! I will say I am definitely going to have to find a partner in this little endeavor sooner than later.

@LukeUSMC: Sorry to bring in a completely different idea, that migh trip every thing up, but it seems to make things easier in the long run.

Convert every time to minutes (or seconds if needed) and do all your calculations with that base unit.
At the end you can use integral and modulo divisions to break things up into hours and minutes (and seconds) again.

int secStart = hourStart * 3600 + minStart * 60;
int secEnd = hourEnd * 3600 + minEnd * 60;
int secDiff = (86400 + secEnd - secStart) % 86400; // allow secStart < secEnd (over midnight)

int secMid = (secStart + secDiff) % 86400;

char timeMid[16];
sprintf(timeMid, "%02d:%02d:%02d", secMid / 3600, (secMid / 60) % 60, secMid % 60);

With these tools you should be able to perform all time calculations you can think of and may even be able to extrapolate them to go beyond day boundaries.


BTW: Please avoid using all capital variable names. These are usually only used for macros, enums, special constants and some other “rare” things.
Variables should be mostly lower case with _ (just_like_this) or camelCase with just a few upper case letters at “word boundaries”.
This is not a rule but has been widely adopted and become a “pseudo standard” amongst programmers.
So we are used to reading code and understand it better, when written that way :sunglasses:

1 Like

something like this:

struct deviceTime {
  int Hour;
  int Minute;
};

deviceTime runTime;

void setup()
{
  float temp = 91.1;  // here is your temp
  temp /= 10.0;  //divide it by 10
  runTime.Hour = (int) temp;  //Take the integer part of the decimal
  runTime.Minute = int(60.0*(temp - int(temp))); //take the decimal component of the temperature and convert the fraction to minutes
  Serial.print(runTime.Hour < 10? "0": "");
  Serial.print(runTime.Hour);
  Serial.print(":");
  Serial.print(runTime.Minute < 10? "0": "");
  Serial.println(runTime.Minute);
}
void loop()
{
}

@ScruffR yup, a lot of ways to go at this... I meant to go at it this way because Unix timestamps make checking ranges of times (and assigning associated states) so darned easy, once it 'clicks'.

@BulldogLowell, I’m completely with you when it comes to cenvenience, but when it comes to understanding (for beginners) what’s going on, I tend to stay clear of “black box” libraries.

You and I know the black magic that happens inside these libs (to a certain extent), but not everybody does :wink:

yeah, I kind of look at this time_t function like my ATM, I don't really need to know everything that happens after I enter my PIN to have an expectation of what will come out when I press "make withdrawal". :wink:

but I see your point... if not completely agree.

My code is starting to read like Genisis…this begat that and that begat those. :slight_smile:

I do very much appreciate the help though. It’s becoming clearer as I move forward.

@ScruffR I fixed my casing. Thanks for the tip.

OK…this

void pumpcalcs()
{
  // Find Pump cycles needed based on Today's Max Temp
  // Default to Minimum Runtime if Temperature Forecast 
  // doesn't demand a longer runtime
  RunTimeSpan = (POOL_END_HOUR_PM - POOL_START_HOUR_AM);
  CycleCompare = (currentFcastmax/10);
  if (CycleCompare < minPumpCycles)
  {
    PumpCyclesToday = minPumpCycles;
    Serial.print("Cycles from minPumpCycles!");
  }
  else if (CycleCompare > minPumpCycles)
  {
    PumpCyclesToday = TempbasedPumpCycles;
    Serial.print("Cycles from TempbasedPumpCycles!");
    Serial.print("Number of Pump Cycles: ");
    Serial.println(PumpCyclesToday);
  }
  
  // Convert PumpCyclesToday to Device Time Struct
  {
    runTime.Hour = (int) PumpCyclesToday;  // Take the integer part of the decimal
    // take the decimal component of the temperature and 
    // convert the fraction to minutes
    runTime.Minute = int(60.0*(PumpCyclesToday - int(PumpCyclesToday))); 
    Serial.print(runTime.Hour < 10? "0": "");
    Serial.print(runTime.Hour);
    Serial.print(":");
    Serial.print(runTime.Minute < 10? "0": "");
    Serial.println(runTime.Minute);
  }
}

Gives me 00:00 for runTime
and PumpCyclesToday = 0

What am I doing wrong?