Working with Dates/Times

I’ve been playing around with the time libraries (Time, TimeLib) a bit as well as the DS3232RTC library for working with that specific component/

I confess I’ve found time_t and tmElements_t to be clunky to work with, always going back and forth between them rather than working with a single type directly so I was curious if there was a better way?

Also, all the examples I’ve seen with them involve working with the current device time (be it setting it or synching it or whatever) but what’s the best way to work with arbitrary date/times created on the fly? I’ll give a simple example, say I want a function that will return true if the current device date/time is in-between 2 others that are fed in from some external source as strings that I need to parse out and then create in-memory representations of to do my comparisons?

Time comparisons are hands down easier in Epoch time (time_t). If you want to see "clunky", try doing the same thing with a tmElements_t struct.

You should consider time_t as your internal representation and tmElements_t as your external interface. There are a number of built in functions in the library for extracting fields without converting to tmElements_t.

Why are you "always going back and forth between them"? Sounds like design issues. Can you provide a specific example of something that is creating problems?

aarg: Time comparisons are hands down easier in Epoch time (time_t). If you want to see "clunky", try doing the same thing with a tmElements_t struct.

You should consider time_t as your internal representation and tmElements_t as your external interface. There are a number of built in functions in the library for extracting fields without converting to tmElements_t.

Why are you "always going back and forth between them"? Sounds like design issues. Can you provide a specific example of something that is creating problems?

So then given my example where I need to create dynamic dates, is the best way to do it to creates a tmElements_t, set the values (day, month, etc) and then call: time_t makeTime(tmElements_t &tm); on it?

shralp: So then given my example where I need to create dynamic dates, is the best way to do it to creates a tmElements_t, set the values (day, month, etc) and then call: time_t makeTime(tmElements_t &tm); on it?

What do you mean, "dynamic dates"? Which part of what you posted was an example of that?

If it's a date-time that you plan to do comparisons on, the answer would definitely be "yes". The technique you cite is a really common one for creating Epoch time variables. Example:

// create a fixed UNIX time to test fixed time method
int someS = 0;  //second
int someM = 0;  //minute
int someH = 12; //hour
int someD = 15; //day
int someMo = 4; //month
int someY = 1985; //year
tmElements_t someTime = {someS, someM, someH, 0, someD, someMo, CalendarYrToTm(someY) };
time_t someEpochTime = makeTime(someTime);

I was lazy with the types here, the small ints should be const uint8_t.

aarg: What do you mean, "dynamic dates"? Which part of what you posted was an example of that?

If it's a date-time that you plan to do comparisons on, the answer would definitely be "yes". The technique you cite is a really common one for creating Epoch time variables. Example:

// create a fixed UNIX time to test fixed time method
int someS = 0;  //second
int someM = 0;  //minute
int someH = 12; //hour
int someD = 15; //day
int someMo = 4; //month
int someY = 1985; //year
tmElements_t someTime = {someS, someM, someH, 0, someD, someMo, CalendarYrToTm(someY) };
time_t someEpochTime = makeTime(someTime);

I was lazy with the types here, the small ints should be const uint8_t.

'example' was a poor choice of words perhaps...I meant the scenario I described in my initial post as an example. Dynamic in the sense that they are coming from an external source (perhaps in some obscure, non standard format) and I need to ingest them as raw strings and then create a date/time 'on the fly' based off them to compare against now( ). Looks like I'm on the right track, I'll dig into those libraries deeper and put together some helper methods that will work for my specific needs based around what you've described, thank you for the help.

If all you want to do is compare dates to see which is later, try something like:

long num = (year * 10000L) + (month * 100L) + day;
// for example:
// July 12, 2017 becomes 20170712
// Jan.  1, 2018 becomes 20180101
// Dec. 31, 2019 becomes 20191231

But if you want the exact number of dates between days, try something like this function:

uint16_t ymdToDays(uint8_t y, uint8_t m, uint8_t d) {
  // Converts date (year, month, day) to day number.
  // Input the year as a number from 0 to 99. (0 means AD 2000)
 
  // some basic sanity checks
  if (y>99) return 0xFFFF;
  if ((m<1)||(m>12)) return 0xFFFF;
  if ((d<1)||(d>31)) return 0xFFFF;
 
  y+=4; // so that the next line can't make it go negative
  if (m<=2) {y--; m+=12;} // treat Jan. and Feb. as part of preceding year
 
  uint16_t n=365*y; // whole years (not counting leap days)
  n+=(y>>2); // add in the leap days
  n+=(31*(uint16_t)m); // take care of months (assuming 31 days per month)
 
  // the next few lines compensate for short months
  if     (m>11) n-=4;
  else if (m>9) n-=3;
  else if (m>6) n-=2;
  else if (m>4) n-=1;
 
  n+=d; // take care of the day of the month
 
  return (n-1495); // make 2000-01-01(Sat) be day 0
}

Example of how to use that function:

// How many days from Dec. 25, 2017 to Jan. 1, 2018 ?
int d1 = ymdToDays(17, 12, 25);  // d1 is 6568
int d2 = ymdToDays(18,  1,  1);  // d2 is 6575
int n  = d2 - d1;                // n  is    7
// answer: 7 days

Cool odometer, thanks for the snippets, a picture is worth a 1000 words when you're learning a new language ;)

I'm not specifically looking to compare dates, that was just for the sake of example.

I do need to calculate DST offsets though, know anything about them? Basically I'll be working with all my times in UTC so I'm always working from a standard base, then just apply timezone/dst etc on the fly. Is that something that's built into a library I can checkout or do I need to write that myself?

I see there is a localtime() and gmtime(), but I cannot see any standard way to set the timezone.

It's great that you're based on UTC. It makes it easier to coordinate time sources. For local time I use: https://github.com/JChristensen/Timezone I have embedded it in several of my clocks and it makes DST and time zone management easy.

Here's an example of the ease with which long or slightly complicated time spans can be handled with the Time library. We are converting NTP time to epoch time (without magic numbers!):

      // Unix time starts on Jan 1 1970.
      // In seconds, that's 2208988800:
      // There were 17 leap days between 1900 and 1970.

      const unsigned long seventyYears = SECS_PER_DAY * (70 * 365 + 17);

      // subtract seventy years
      epoch = (secsSince1900 - seventyYears);

aarg: It's great that you're based on UTC. It makes it easier to coordinate time sources. For local time I use: https://github.com/JChristensen/Timezone I have embedded it in several of my clocks and it makes DST and time zone management easy.

That's exactly what I was looking for, thanks for that! I'll delve into this tomorrow and post some code when I get it working.

Props to odometer and aarg for pointing me in the right direction with this...working with dates in any language is usually a pain in the ass but once you get a handle on how to manipulate and compare them properly it becomes much less of a headache.

I do recommend the Timezone lib: https://github.com/JChristensen/Timezone

I found it to be well written and easy to use. For others who stumble upon this thread, here's the relevant code from my sketch, just a simple routine to format my date/times by my timezone for debugging and display. As long as you have your RTC set to UTC this becomes quite straightforward:

//basic setup 
const TimeChangeRule usEDT = {"EDT", Second, Sun, Mar, 2, -240};  //UTC - 4 hours
const TimeChangeRule usEST = {"EST", First, Sun, Nov, 2, -300};   //UTC - 5 hours
const Timezone usEastern(usEDT, usEST);


void currentDT(String &dateTime){
  time_t utc, est;
  utc = now();
  est = usEastern.toLocal(utc);
  tmElements_t tm;
  breakTime(est, tm);
  formatDateTime(tm, dateTime);
}

void formatDateTime(tmElements_t &tm, String &dateTime){
  char res[14];
  sprintf(res, "%02d-%02d-%02d %02d:%02d", tm.Month, tm.Day, (tm.Year+1970), tm.Hour, tm.Minute, tm.Second); 
  dateTime = res;
}