diff --git a/RTClib.cpp b/RTClib.cpp index 8b034182..87dc3a64 100644 --- a/RTClib.cpp +++ b/RTClib.cpp @@ -42,6 +42,8 @@ */ /**************************************************************************/ +#include "RTClib.h" + #ifdef __AVR_ATtiny85__ #include #define Wire TinyWireM @@ -49,19 +51,6 @@ #include #endif -#include "RTClib.h" -#ifdef __AVR__ -#include -#elif defined(ESP8266) -#include -#elif defined(ARDUINO_ARCH_SAMD) -// nothing special needed -#elif defined(ARDUINO_SAM_DUE) -#define PROGMEM -#define pgm_read_byte(addr) (*(const unsigned char *)(addr)) -#define Wire Wire1 -#endif - #if (ARDUINO >= 100) #include // capital A so it is error prone on case-sensitive filesystems // Macro to deal with the difference in I2C write functions from old and new @@ -106,687 +95,6 @@ static void write_i2c_register(uint8_t addr, uint8_t reg, uint8_t val) { Wire.endTransmission(); } -/**************************************************************************/ -// utility code, some of this could be exposed in the DateTime API if needed -/**************************************************************************/ - -/** - Number of days in each month, from January to November. December is not - needed. Omitting it avoids an incompatibility with Paul Stoffregen's Time - library. C.f. https://github.com/adafruit/RTClib/issues/114 -*/ -const uint8_t daysInMonth[] PROGMEM = {31, 28, 31, 30, 31, 30, - 31, 31, 30, 31, 30}; - -/**************************************************************************/ -/*! - @brief Given a date, return number of days since 2000/01/01, - valid for 2000--2099 - @param y Year - @param m Month - @param d Day - @return Number of days -*/ -/**************************************************************************/ -static uint16_t date2days(uint16_t y, uint8_t m, uint8_t d) { - if (y >= 2000U) - y -= 2000U; - uint16_t days = d; - for (uint8_t i = 1; i < m; ++i) - days += pgm_read_byte(daysInMonth + i - 1); - if (m > 2 && y % 4 == 0) - ++days; - return days + 365 * y + (y + 3) / 4 - 1; -} - -/**************************************************************************/ -/*! - @brief Given a number of days, hours, minutes, and seconds, return the - total seconds - @param days Days - @param h Hours - @param m Minutes - @param s Seconds - @return Number of seconds total -*/ -/**************************************************************************/ -static uint32_t time2ulong(uint16_t days, uint8_t h, uint8_t m, uint8_t s) { - return ((days * 24UL + h) * 60 + m) * 60 + s; -} - -/**************************************************************************/ -/*! - @brief Constructor from - [Unix time](https://en.wikipedia.org/wiki/Unix_time). - - This builds a DateTime from an integer specifying the number of seconds - elapsed since the epoch: 1970-01-01 00:00:00. This number is analogous - to Unix time, with two small differences: - - - The Unix epoch is specified to be at 00:00:00 - [UTC](https://en.wikipedia.org/wiki/Coordinated_Universal_Time), - whereas this class has no notion of time zones. The epoch used in - this class is then at 00:00:00 on whatever time zone the user chooses - to use, ignoring changes in DST. - - - Unix time is conventionally represented with signed numbers, whereas - this constructor takes an unsigned argument. Because of this, it does - _not_ suffer from the - [year 2038 problem](https://en.wikipedia.org/wiki/Year_2038_problem). - - If called without argument, it returns the earliest time representable - by this class: 2000-01-01 00:00:00. - - @see The `unixtime()` method is the converse of this constructor. - - @param t Time elapsed in seconds since 1970-01-01 00:00:00. -*/ -/**************************************************************************/ -DateTime::DateTime(uint32_t t) { - t -= SECONDS_FROM_1970_TO_2000; // bring to 2000 timestamp from 1970 - - ss = t % 60; - t /= 60; - mm = t % 60; - t /= 60; - hh = t % 24; - uint16_t days = t / 24; - uint8_t leap; - for (yOff = 0;; ++yOff) { - leap = yOff % 4 == 0; - if (days < 365U + leap) - break; - days -= 365 + leap; - } - for (m = 1; m < 12; ++m) { - uint8_t daysPerMonth = pgm_read_byte(daysInMonth + m - 1); - if (leap && m == 2) - ++daysPerMonth; - if (days < daysPerMonth) - break; - days -= daysPerMonth; - } - d = days + 1; -} - -/**************************************************************************/ -/*! - @brief Constructor from (year, month, day, hour, minute, second). - @warning If the provided parameters are not valid (e.g. 31 February), - the constructed DateTime will be invalid. - @see The `isValid()` method can be used to test whether the - constructed DateTime is valid. - @param year Either the full year (range: 2000--2099) or the offset from - year 2000 (range: 0--99). - @param month Month number (1--12). - @param day Day of the month (1--31). - @param hour,min,sec Hour (0--23), minute (0--59) and second (0--59). -*/ -/**************************************************************************/ -DateTime::DateTime(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, - uint8_t min, uint8_t sec) { - if (year >= 2000U) - year -= 2000U; - yOff = year; - m = month; - d = day; - hh = hour; - mm = min; - ss = sec; -} - -/**************************************************************************/ -/*! - @brief Copy constructor. - @param copy DateTime to copy. -*/ -/**************************************************************************/ -DateTime::DateTime(const DateTime ©) - : yOff(copy.yOff), m(copy.m), d(copy.d), hh(copy.hh), mm(copy.mm), - ss(copy.ss) {} - -/**************************************************************************/ -/*! - @brief Convert a string containing two digits to uint8_t, e.g. "09" returns - 9 - @param p Pointer to a string containing two digits -*/ -/**************************************************************************/ -static uint8_t conv2d(const char *p) { - uint8_t v = 0; - if ('0' <= *p && *p <= '9') - v = *p - '0'; - return 10 * v + *++p - '0'; -} - -/**************************************************************************/ -/*! - @brief Constructor for generating the build time. - - This constructor expects its parameters to be strings in the format - generated by the compiler's preprocessor macros `__DATE__` and - `__TIME__`. Usage: - - ``` - DateTime buildTime(__DATE__, __TIME__); - ``` - - @note The `F()` macro can be used to reduce the RAM footprint, see - the next constructor. - - @param date Date string, e.g. "Apr 16 2020". - @param time Time string, e.g. "18:34:56". -*/ -/**************************************************************************/ -DateTime::DateTime(const char *date, const char *time) { - yOff = conv2d(date + 9); - // Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec - switch (date[0]) { - case 'J': - m = (date[1] == 'a') ? 1 : ((date[2] == 'n') ? 6 : 7); - break; - case 'F': - m = 2; - break; - case 'A': - m = date[2] == 'r' ? 4 : 8; - break; - case 'M': - m = date[2] == 'r' ? 3 : 5; - break; - case 'S': - m = 9; - break; - case 'O': - m = 10; - break; - case 'N': - m = 11; - break; - case 'D': - m = 12; - break; - } - d = conv2d(date + 4); - hh = conv2d(time); - mm = conv2d(time + 3); - ss = conv2d(time + 6); -} - -/**************************************************************************/ -/*! - @brief Memory friendly constructor for generating the build time. - - This version is intended to save RAM by keeping the date and time - strings in program memory. Use it with the `F()` macro: - - ``` - DateTime buildTime(F(__DATE__), F(__TIME__)); - ``` - - @param date Date PROGMEM string, e.g. F("Apr 16 2020"). - @param time Time PROGMEM string, e.g. F("18:34:56"). -*/ -/**************************************************************************/ -DateTime::DateTime(const __FlashStringHelper *date, - const __FlashStringHelper *time) { - char buff[11]; - memcpy_P(buff, date, 11); - yOff = conv2d(buff + 9); - // Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec - switch (buff[0]) { - case 'J': - m = (buff[1] == 'a') ? 1 : ((buff[2] == 'n') ? 6 : 7); - break; - case 'F': - m = 2; - break; - case 'A': - m = buff[2] == 'r' ? 4 : 8; - break; - case 'M': - m = buff[2] == 'r' ? 3 : 5; - break; - case 'S': - m = 9; - break; - case 'O': - m = 10; - break; - case 'N': - m = 11; - break; - case 'D': - m = 12; - break; - } - d = conv2d(buff + 4); - memcpy_P(buff, time, 8); - hh = conv2d(buff); - mm = conv2d(buff + 3); - ss = conv2d(buff + 6); -} - -/**************************************************************************/ -/*! - @brief Constructor for creating a DateTime from an ISO8601 date string. - - This constructor expects its parameters to be a string in the - https://en.wikipedia.org/wiki/ISO_8601 format, e.g: - - "2020-06-25T15:29:37" - - Usage: - - ``` - DateTime dt("2020-06-25T15:29:37"); - ``` - - @note The year must be > 2000, as only the yOff is considered. - - @param iso8601dateTime - A dateTime string in iso8601 format, - e.g. "2020-06-25T15:29:37". - -*/ -/**************************************************************************/ -DateTime::DateTime(const char *iso8601dateTime) { - char ref[] = "2000-01-01T00:00:00"; - memcpy(ref, iso8601dateTime, min(strlen(ref), strlen(iso8601dateTime))); - yOff = conv2d(ref + 2); - m = conv2d(ref + 5); - d = conv2d(ref + 8); - hh = conv2d(ref + 11); - mm = conv2d(ref + 14); - ss = conv2d(ref + 17); -} - -/**************************************************************************/ -/*! - @brief Check whether this DateTime is valid. - @return true if valid, false if not. -*/ -/**************************************************************************/ -bool DateTime::isValid() const { - if (yOff >= 100) - return false; - DateTime other(unixtime()); - return yOff == other.yOff && m == other.m && d == other.d && hh == other.hh && - mm == other.mm && ss == other.ss; -} - -/**************************************************************************/ -/*! - @brief Writes the DateTime as a string in a user-defined format. - - The _buffer_ parameter should be initialized by the caller with a string - specifying the requested format. This format string may contain any of - the following specifiers: - - | specifier | output | - |-----------|--------------------------------------------------------| - | YYYY | the year as a 4-digit number (2000--2099) | - | YY | the year as a 2-digit number (00--99) | - | MM | the month as a 2-digit number (01--12) | - | MMM | the abbreviated English month name ("Jan"--"Dec") | - | DD | the day as a 2-digit number (01--31) | - | DDD | the abbreviated English day of the week ("Mon"--"Sun") | - | AP | either "AM" or "PM" | - | ap | either "am" or "pm" | - | hh | the hour as a 2-digit number (00--23 or 01--12) | - | mm | the minute as a 2-digit number (00--59) | - | ss | the second as a 2-digit number (00--59) | - - If either "AP" or "ap" is used, the "hh" specifier uses 12-hour mode - (range: 01--12). Otherwise it works in 24-hour mode (range: 00--23). - - The specifiers within _buffer_ will be overwritten with the appropriate - values from the DateTime. Any characters not belonging to one of the - above specifiers are left as-is. - - __Example__: The format "DDD, DD MMM YYYY hh:mm:ss" generates an output - of the form "Thu, 16 Apr 2020 18:34:56. - - @see The `timestamp()` method provides similar functionnality, but it - returns a `String` object and supports a limited choice of - predefined formats. - - @param[in,out] buffer Array of `char` for holding the format description - and the formatted DateTime. Before calling this method, the buffer - should be initialized by the user with the format string. The method - will overwrite the buffer with the formatted date and/or time. - - @return A pointer to the provided buffer. This is returned for - convenience, in order to enable idioms such as - `Serial.println(now.toString(buffer));` -*/ -/**************************************************************************/ - -char *DateTime::toString(char *buffer) { - uint8_t apTag = - (strstr(buffer, "ap") != nullptr) || (strstr(buffer, "AP") != nullptr); - uint8_t hourReformatted = 0, isPM = false; - if (apTag) { // 12 Hour Mode - if (hh == 0) { // midnight - isPM = false; - hourReformatted = 12; - } else if (hh == 12) { // noon - isPM = true; - hourReformatted = 12; - } else if (hh < 12) { // morning - isPM = false; - hourReformatted = hh; - } else { // 1 o'clock or after - isPM = true; - hourReformatted = hh - 12; - } - } - - for (size_t i = 0; i < strlen(buffer) - 1; i++) { - if (buffer[i] == 'h' && buffer[i + 1] == 'h') { - if (!apTag) { // 24 Hour Mode - buffer[i] = '0' + hh / 10; - buffer[i + 1] = '0' + hh % 10; - } else { // 12 Hour Mode - buffer[i] = '0' + hourReformatted / 10; - buffer[i + 1] = '0' + hourReformatted % 10; - } - } - if (buffer[i] == 'm' && buffer[i + 1] == 'm') { - buffer[i] = '0' + mm / 10; - buffer[i + 1] = '0' + mm % 10; - } - if (buffer[i] == 's' && buffer[i + 1] == 's') { - buffer[i] = '0' + ss / 10; - buffer[i + 1] = '0' + ss % 10; - } - if (buffer[i] == 'D' && buffer[i + 1] == 'D' && buffer[i + 2] == 'D') { - static PROGMEM const char day_names[] = "SunMonTueWedThuFriSat"; - const char *p = &day_names[3 * dayOfTheWeek()]; - buffer[i] = pgm_read_byte(p); - buffer[i + 1] = pgm_read_byte(p + 1); - buffer[i + 2] = pgm_read_byte(p + 2); - } else if (buffer[i] == 'D' && buffer[i + 1] == 'D') { - buffer[i] = '0' + d / 10; - buffer[i + 1] = '0' + d % 10; - } - if (buffer[i] == 'M' && buffer[i + 1] == 'M' && buffer[i + 2] == 'M') { - static PROGMEM const char month_names[] = - "JanFebMarAprMayJunJulAugSepOctNovDec"; - const char *p = &month_names[3 * (m - 1)]; - buffer[i] = pgm_read_byte(p); - buffer[i + 1] = pgm_read_byte(p + 1); - buffer[i + 2] = pgm_read_byte(p + 2); - } else if (buffer[i] == 'M' && buffer[i + 1] == 'M') { - buffer[i] = '0' + m / 10; - buffer[i + 1] = '0' + m % 10; - } - if (buffer[i] == 'Y' && buffer[i + 1] == 'Y' && buffer[i + 2] == 'Y' && - buffer[i + 3] == 'Y') { - buffer[i] = '2'; - buffer[i + 1] = '0'; - buffer[i + 2] = '0' + (yOff / 10) % 10; - buffer[i + 3] = '0' + yOff % 10; - } else if (buffer[i] == 'Y' && buffer[i + 1] == 'Y') { - buffer[i] = '0' + (yOff / 10) % 10; - buffer[i + 1] = '0' + yOff % 10; - } - if (buffer[i] == 'A' && buffer[i + 1] == 'P') { - if (isPM) { - buffer[i] = 'P'; - buffer[i + 1] = 'M'; - } else { - buffer[i] = 'A'; - buffer[i + 1] = 'M'; - } - } else if (buffer[i] == 'a' && buffer[i + 1] == 'p') { - if (isPM) { - buffer[i] = 'p'; - buffer[i + 1] = 'm'; - } else { - buffer[i] = 'a'; - buffer[i + 1] = 'm'; - } - } - } - return buffer; -} - -/**************************************************************************/ -/*! - @brief Return the hour in 12-hour format. - @return Hour (1--12). -*/ -/**************************************************************************/ -uint8_t DateTime::twelveHour() const { - if (hh == 0 || hh == 12) { // midnight or noon - return 12; - } else if (hh > 12) { // 1 o'clock or later - return hh - 12; - } else { // morning - return hh; - } -} - -/**************************************************************************/ -/*! - @brief Return the day of the week. - @return Day of week as an integer from 0 (Sunday) to 6 (Saturday). -*/ -/**************************************************************************/ -uint8_t DateTime::dayOfTheWeek() const { - uint16_t day = date2days(yOff, m, d); - return (day + 6) % 7; // Jan 1, 2000 is a Saturday, i.e. returns 6 -} - -/**************************************************************************/ -/*! - @brief Return Unix time: seconds since 1 Jan 1970. - - @see The `DateTime::DateTime(uint32_t)` constructor is the converse of - this method. - - @return Number of seconds since 1970-01-01 00:00:00. -*/ -/**************************************************************************/ -uint32_t DateTime::unixtime(void) const { - uint32_t t; - uint16_t days = date2days(yOff, m, d); - t = time2ulong(days, hh, mm, ss); - t += SECONDS_FROM_1970_TO_2000; // seconds from 1970 to 2000 - - return t; -} - -/**************************************************************************/ -/*! - @brief Convert the DateTime to seconds since 1 Jan 2000 - - The result can be converted back to a DateTime with: - - ```cpp - DateTime(SECONDS_FROM_1970_TO_2000 + value) - ``` - - @return Number of seconds since 2000-01-01 00:00:00. -*/ -/**************************************************************************/ -uint32_t DateTime::secondstime(void) const { - uint32_t t; - uint16_t days = date2days(yOff, m, d); - t = time2ulong(days, hh, mm, ss); - return t; -} - -/**************************************************************************/ -/*! - @brief Add a TimeSpan to the DateTime object - @param span TimeSpan object - @return New DateTime object with span added to it. -*/ -/**************************************************************************/ -DateTime DateTime::operator+(const TimeSpan &span) { - return DateTime(unixtime() + span.totalseconds()); -} - -/**************************************************************************/ -/*! - @brief Subtract a TimeSpan from the DateTime object - @param span TimeSpan object - @return New DateTime object with span subtracted from it. -*/ -/**************************************************************************/ -DateTime DateTime::operator-(const TimeSpan &span) { - return DateTime(unixtime() - span.totalseconds()); -} - -/**************************************************************************/ -/*! - @brief Subtract one DateTime from another - - @note Since a TimeSpan cannot be negative, the subtracted DateTime - should be less (earlier) than or equal to the one it is - subtracted from. - - @param right The DateTime object to subtract from self (the left object) - @return TimeSpan of the difference between DateTimes. -*/ -/**************************************************************************/ -TimeSpan DateTime::operator-(const DateTime &right) { - return TimeSpan(unixtime() - right.unixtime()); -} - -/**************************************************************************/ -/*! - @author Anton Rieutskyi - @brief Test if one DateTime is less (earlier) than another. - @warning if one or both DateTime objects are invalid, returned value is - meaningless - @see use `isValid()` method to check if DateTime object is valid - @param right Comparison DateTime object - @return True if the left DateTime is earlier than the right one, - false otherwise. -*/ -/**************************************************************************/ -bool DateTime::operator<(const DateTime &right) const { - return (yOff + 2000U < right.year() || - (yOff + 2000U == right.year() && - (m < right.month() || - (m == right.month() && - (d < right.day() || - (d == right.day() && - (hh < right.hour() || - (hh == right.hour() && - (mm < right.minute() || - (mm == right.minute() && ss < right.second())))))))))); -} - -/**************************************************************************/ -/*! - @author Anton Rieutskyi - @brief Test if two DateTime objects are equal. - @warning if one or both DateTime objects are invalid, returned value is - meaningless - @see use `isValid()` method to check if DateTime object is valid - @param right Comparison DateTime object - @return True if both DateTime objects are the same, false otherwise. -*/ -/**************************************************************************/ -bool DateTime::operator==(const DateTime &right) const { - return (right.year() == yOff + 2000U && right.month() == m && - right.day() == d && right.hour() == hh && right.minute() == mm && - right.second() == ss); -} - -/**************************************************************************/ -/*! - @brief Return a ISO 8601 timestamp as a `String` object. - - The generated timestamp conforms to one of the predefined, ISO - 8601-compatible formats for representing the date (if _opt_ is - `TIMESTAMP_DATE`), the time (`TIMESTAMP_TIME`), or both - (`TIMESTAMP_FULL`). - - @see The `toString()` method provides more general string formatting. - - @param opt Format of the timestamp - @return Timestamp string, e.g. "2020-04-16T18:34:56". -*/ -/**************************************************************************/ -String DateTime::timestamp(timestampOpt opt) { - char buffer[25]; // large enough for any DateTime, including invalid ones - - // Generate timestamp according to opt - switch (opt) { - case TIMESTAMP_TIME: - // Only time - sprintf(buffer, "%02d:%02d:%02d", hh, mm, ss); - break; - case TIMESTAMP_DATE: - // Only date - sprintf(buffer, "%u-%02d-%02d", 2000U + yOff, m, d); - break; - default: - // Full - sprintf(buffer, "%u-%02d-%02dT%02d:%02d:%02d", 2000U + yOff, m, d, hh, mm, - ss); - } - return String(buffer); -} - -/**************************************************************************/ -/*! - @brief Create a new TimeSpan object in seconds - @param seconds Number of seconds -*/ -/**************************************************************************/ -TimeSpan::TimeSpan(int32_t seconds) : _seconds(seconds) {} - -/**************************************************************************/ -/*! - @brief Create a new TimeSpan object using a number of - days/hours/minutes/seconds e.g. Make a TimeSpan of 3 hours and 45 minutes: - new TimeSpan(0, 3, 45, 0); - @param days Number of days - @param hours Number of hours - @param minutes Number of minutes - @param seconds Number of seconds -*/ -/**************************************************************************/ -TimeSpan::TimeSpan(int16_t days, int8_t hours, int8_t minutes, int8_t seconds) - : _seconds((int32_t)days * 86400L + (int32_t)hours * 3600 + - (int32_t)minutes * 60 + seconds) {} - -/**************************************************************************/ -/*! - @brief Copy constructor, make a new TimeSpan using an existing one - @param copy The TimeSpan to copy -*/ -/**************************************************************************/ -TimeSpan::TimeSpan(const TimeSpan ©) : _seconds(copy._seconds) {} - -/**************************************************************************/ -/*! - @brief Add two TimeSpans - @param right TimeSpan to add - @return New TimeSpan object, sum of left and right -*/ -/**************************************************************************/ -TimeSpan TimeSpan::operator+(const TimeSpan &right) { - return TimeSpan(_seconds + right._seconds); -} - -/**************************************************************************/ -/*! - @brief Subtract a TimeSpan - @param right TimeSpan to subtract - @return New TimeSpan object, right subtracted from left -*/ -/**************************************************************************/ -TimeSpan TimeSpan::operator-(const TimeSpan &right) { - return TimeSpan(_seconds - right._seconds); -} - /**************************************************************************/ /*! @brief Convert a binary coded decimal value to binary. RTC stores time/date @@ -810,6 +118,7 @@ static uint8_t bin2bcd(uint8_t val) { return val + 6 * (val / 10); } /*! @brief Start I2C for the DS1307 and test succesful connection @return True if Wire can find DS1307 or false otherwise. + @note Preserves the date/time on the RTC */ /**************************************************************************/ boolean RTC_DS1307::begin(void) { @@ -817,6 +126,25 @@ boolean RTC_DS1307::begin(void) { Wire.beginTransmission(DS1307_ADDRESS); if (Wire.endTransmission() == 0) return true; + + return false; +} + +/**************************************************************************/ +/*! + @brief Start I2C for the DS1307, test succesful connection, then initialise + the date/time + @param dt DateTime object containing desired date/time + @return True if Wire can find DS1307 or false otherwise. +*/ +/**************************************************************************/ +boolean RTC_DS1307::begin(const DateTime &dt) { + Wire.begin(); + Wire.beginTransmission(DS1307_ADDRESS); + if (Wire.endTransmission() == 0) { + adjust(dt); + return true; + } return false; } @@ -826,7 +154,7 @@ boolean RTC_DS1307::begin(void) { @return 1 if the RTC is running, 0 if not */ /**************************************************************************/ -uint8_t RTC_DS1307::isrunning(void) { +boolean RTC_DS1307::isrunning(void) { Wire.beginTransmission(DS1307_ADDRESS); Wire._I2C_WRITE((byte)0); Wire.endTransmission(); @@ -836,6 +164,18 @@ uint8_t RTC_DS1307::isrunning(void) { return !(ss >> 7); } +/**************************************************************************/ +/*! + @brief Check the status register Oscillator Stop flag to see if the DS1307 + stopped due to power loss + @return True if the bit is set (oscillator is or has stopped) and false only + after the bit is cleared, for instance with adjust() +*/ +/**************************************************************************/ +boolean RTC_DS1307::lostPower(void) { + return (read_i2c_register(DS1307_ADDRESS, 0) >> 7); +} + /**************************************************************************/ /*! @brief Set the date and time in the DS1307 @@ -982,6 +322,29 @@ void RTC_DS1307::writenvram(uint8_t address, uint8_t data) { uint32_t RTC_Millis::lastMillis; uint32_t RTC_Millis::lastUnix; +/**************************************************************************/ +/*! + @brief Start the RTC_Millis date/time + @return true + @note Equivalent to `begin(DateTime())` +*/ +/**************************************************************************/ +boolean RTC_Millis::begin(void) { + adjust(DateTime()); + return true; +} +/**************************************************************************/ +/*! + @brief Start the RTC_Millis date/time + @param dt DateTime object containing desired date/time + @return true +*/ +/**************************************************************************/ +boolean RTC_Millis::begin(const DateTime &dt) { + adjust(dt); + return true; +} + /**************************************************************************/ /*! @brief Set the current date/time of the RTC_Millis clock. @@ -1010,12 +373,36 @@ DateTime RTC_Millis::now() { /** Number of microseconds reported by micros() per "true" (calibrated) second. */ -uint32_t RTC_Micros::microsPerSecond = 1000000; +uint32_t RTC_Micros::microsPerSecond = 1000000L; /** The timing logic is identical to RTC_Millis. */ uint32_t RTC_Micros::lastMicros; uint32_t RTC_Micros::lastUnix; +/**************************************************************************/ +/*! + @brief Start the RTC_Micros date/time + @return true + @note Equivalent to `begin(DateTime())` +*/ +/**************************************************************************/ +boolean RTC_Micros::begin(void) { + adjust(DateTime()); + return true; +} + +/**************************************************************************/ +/*! + @brief Start the RTC_Micros date/time + @param dt DateTime object containing desired date/time + @return true +*/ +/**************************************************************************/ +boolean RTC_Micros::begin(const DateTime &dt) { + adjust(dt); + return true; +} + /**************************************************************************/ /*! @brief Set the current date/time of the RTC_Micros clock. @@ -1029,12 +416,13 @@ void RTC_Micros::adjust(const DateTime &dt) { /**************************************************************************/ /*! - @brief Adjust the RTC_Micros clock to compensate for system clock drift - @param ppm Adjustment to make + @brief Adjust the RTC_Micros clock speed to compensate for system clock + drift + @param ppm Parts per million drift rate adjustment + @note Positive values make the clock faster and vice-versa */ /**************************************************************************/ -// A positive adjustment makes the clock faster. -void RTC_Micros::adjustDrift(int ppm) { microsPerSecond = 1000000 - ppm; } +void RTC_Micros::adjustDrift(int ppm) { microsPerSecond = 1000000L - ppm; } /**************************************************************************/ /*! @@ -1053,6 +441,7 @@ DateTime RTC_Micros::now() { /*! @brief Start I2C for the PCF8523 and test succesful connection @return True if Wire can find PCF8523 or false otherwise. + @note Preserves the date/time on the RTC */ /**************************************************************************/ boolean RTC_PCF8523::begin(void) { @@ -1060,6 +449,24 @@ boolean RTC_PCF8523::begin(void) { Wire.beginTransmission(PCF8523_ADDRESS); if (Wire.endTransmission() == 0) return true; + + return false; +} + +/**************************************************************************/ +/*! + @brief Start I2C for the PCF8523 and test succesful connection + @param dt DateTime object containing desired date/time + @return True if Wire can find PCF8523 or false otherwise. +*/ +/**************************************************************************/ +boolean RTC_PCF8523::begin(const DateTime &dt) { + Wire.begin(); + Wire.beginTransmission(PCF8523_ADDRESS); + if (Wire.endTransmission() == 0) { + adjust(dt); + return true; + } return false; } @@ -1145,7 +552,7 @@ DateTime RTC_PCF8523::now() { /**************************************************************************/ /*! - @brief Resets the STOP bit in register Control_1 + @brief resets the stop bit in register control_1 */ /**************************************************************************/ void RTC_PCF8523::start(void) { @@ -1173,7 +580,7 @@ void RTC_PCF8523::stop(void) { @return 1 if the RTC is running, 0 if not */ /**************************************************************************/ -uint8_t RTC_PCF8523::isrunning() { +boolean RTC_PCF8523::isrunning() { uint8_t ctlreg = read_i2c_register(PCF8523_ADDRESS, PCF8523_CONTROL_1); return !((ctlreg >> 5) & 1); } @@ -1381,6 +788,7 @@ void RTC_PCF8523::calibrate(Pcf8523OffsetMode mode, int8_t offset) { /*! @brief Start I2C for the PCF8563 and test succesful connection @return True if Wire can find PCF8563 or false otherwise. + @note Preserves the date/time on the RTC */ /**************************************************************************/ boolean RTC_PCF8563::begin(void) { @@ -1388,6 +796,24 @@ boolean RTC_PCF8563::begin(void) { Wire.beginTransmission(PCF8563_ADDRESS); if (Wire.endTransmission() == 0) return true; + + return false; +} + +/**************************************************************************/ +/*! + @brief Start I2C for the PCF8563 and test succesful connection + @param dt DateTime object containing desired date/time + @return True if Wire can find PCF8563 or false otherwise. +*/ +/**************************************************************************/ +boolean RTC_PCF8563::begin(const DateTime &dt) { + Wire.begin(); + Wire.beginTransmission(PCF8563_ADDRESS); + if (Wire.endTransmission() == 0) { + adjust(dt); + return true; + } return false; } @@ -1481,7 +907,7 @@ void RTC_PCF8563::stop(void) { @return 1 if the RTC is running, 0 if not */ /**************************************************************************/ -uint8_t RTC_PCF8563::isrunning() { +boolean RTC_PCF8563::isrunning() { uint8_t ctlreg = read_i2c_register(PCF8563_ADDRESS, PCF8563_CONTROL_1); return !((ctlreg >> 5) & 1); } @@ -1535,6 +961,7 @@ static uint8_t dowToDS3231(uint8_t d) { return d == 0 ? 7 : d; } /*! @brief Start I2C for the DS3231 and test succesful connection @return True if Wire can find DS3231 or false otherwise. + @note Preserves the date/time on the RTC */ /**************************************************************************/ boolean RTC_DS3231::begin(void) { @@ -1542,9 +969,43 @@ boolean RTC_DS3231::begin(void) { Wire.beginTransmission(DS3231_ADDRESS); if (Wire.endTransmission() == 0) return true; + + return false; +} + +/**************************************************************************/ +/*! + @brief Start I2C for the DS3231 and test succesful connection + @param dt DateTime object containing desired date/time + @return True if Wire can find DS3231 or false otherwise. +*/ +/**************************************************************************/ +boolean RTC_DS3231::begin(const DateTime &dt) { + Wire.begin(); + Wire.beginTransmission(DS3231_ADDRESS); + if (Wire.endTransmission() == 0) { + adjust(dt); + return true; + } return false; } +/**************************************************************************/ +/*! + @brief Is the DS3231 running? Check the Clock Halt bit in register 0 + @return 1 if the RTC is running, 0 if not +*/ +/**************************************************************************/ +bool RTC_DS3231::isrunning(void) { + Wire.beginTransmission(DS3231_ADDRESS); + Wire._I2C_WRITE((byte)0); + Wire.endTransmission(); + + Wire.requestFrom(DS3231_ADDRESS, 1); + uint8_t ss = Wire._I2C_READ(); + return !(ss >> 7); +} + /**************************************************************************/ /*! @brief Check the status register Oscillator Stop Flag to see if the DS3231 diff --git a/RTClib.h b/RTClib.h index e48960ee..5ad08db8 100644 --- a/RTClib.h +++ b/RTClib.h @@ -22,8 +22,9 @@ #ifndef _RTCLIB_H_ #define _RTCLIB_H_ +#include "utility/DateTime.h" +#include "utility/RTC_Base.h" #include -class TimeSpan; /** Registers */ #define PCF8523_ADDRESS 0x68 ///< I2C address for PCF8523 @@ -57,205 +58,6 @@ class TimeSpan; 0x11 ///< Temperature register (high byte - low byte is at 0x12), 10-bit ///< temperature value -/** Constants */ -#define SECONDS_PER_DAY 86400L ///< 60 * 60 * 24 -#define SECONDS_FROM_1970_TO_2000 \ - 946684800 ///< Unixtime for 2000-01-01 00:00:00, useful for initialization - -/**************************************************************************/ -/*! - @brief Simple general-purpose date/time class (no TZ / DST / leap - seconds). - - This class stores date and time information in a broken-down form, as a - tuple (year, month, day, hour, minute, second). The day of the week is - not stored, but computed on request. The class has no notion of time - zones, daylight saving time, or - [leap seconds](http://en.wikipedia.org/wiki/Leap_second): time is stored - in whatever time zone the user chooses to use. - - The class supports dates in the range from 1 Jan 2000 to 31 Dec 2099 - inclusive. -*/ -/**************************************************************************/ -class DateTime { -public: - DateTime(uint32_t t = SECONDS_FROM_1970_TO_2000); - DateTime(uint16_t year, uint8_t month, uint8_t day, uint8_t hour = 0, - uint8_t min = 0, uint8_t sec = 0); - DateTime(const DateTime ©); - DateTime(const char *date, const char *time); - DateTime(const __FlashStringHelper *date, const __FlashStringHelper *time); - DateTime(const char *iso8601date); - bool isValid() const; - char *toString(char *buffer); - - /*! - @brief Return the year. - @return Year (range: 2000--2099). - */ - uint16_t year() const { return 2000U + yOff; } - /*! - @brief Return the month. - @return Month number (1--12). - */ - uint8_t month() const { return m; } - /*! - @brief Return the day of the month. - @return Day of the month (1--31). - */ - uint8_t day() const { return d; } - /*! - @brief Return the hour - @return Hour (0--23). - */ - uint8_t hour() const { return hh; } - - uint8_t twelveHour() const; - /*! - @brief Return whether the time is PM. - @return 0 if the time is AM, 1 if it's PM. - */ - uint8_t isPM() const { return hh >= 12; } - /*! - @brief Return the minute. - @return Minute (0--59). - */ - uint8_t minute() const { return mm; } - /*! - @brief Return the second. - @return Second (0--59). - */ - uint8_t second() const { return ss; } - - uint8_t dayOfTheWeek() const; - - /* 32-bit times as seconds since 2000-01-01. */ - uint32_t secondstime() const; - - /* 32-bit times as seconds since 1970-01-01. */ - uint32_t unixtime(void) const; - - /*! - Format of the ISO 8601 timestamp generated by `timestamp()`. Each - option corresponds to a `toString()` format as follows: - */ - enum timestampOpt { - TIMESTAMP_FULL, //!< `YYYY-MM-DDThh:mm:ss` - TIMESTAMP_TIME, //!< `hh:mm:ss` - TIMESTAMP_DATE //!< `YYYY-MM-DD` - }; - String timestamp(timestampOpt opt = TIMESTAMP_FULL); - - DateTime operator+(const TimeSpan &span); - DateTime operator-(const TimeSpan &span); - TimeSpan operator-(const DateTime &right); - bool operator<(const DateTime &right) const; - - /*! - @brief Test if one DateTime is greater (later) than another. - @warning if one or both DateTime objects are invalid, returned value is - meaningless - @see use `isValid()` method to check if DateTime object is valid - @param right DateTime object to compare - @return True if the left DateTime is later than the right one, - false otherwise - */ - bool operator>(const DateTime &right) const { return right < *this; } - - /*! - @brief Test if one DateTime is less (earlier) than or equal to another - @warning if one or both DateTime objects are invalid, returned value is - meaningless - @see use `isValid()` method to check if DateTime object is valid - @param right DateTime object to compare - @return True if the left DateTime is earlier than or equal to the - right one, false otherwise - */ - bool operator<=(const DateTime &right) const { return !(*this > right); } - - /*! - @brief Test if one DateTime is greater (later) than or equal to another - @warning if one or both DateTime objects are invalid, returned value is - meaningless - @see use `isValid()` method to check if DateTime object is valid - @param right DateTime object to compare - @return True if the left DateTime is later than or equal to the right - one, false otherwise - */ - bool operator>=(const DateTime &right) const { return !(*this < right); } - bool operator==(const DateTime &right) const; - - /*! - @brief Test if two DateTime objects are not equal. - @warning if one or both DateTime objects are invalid, returned value is - meaningless - @see use `isValid()` method to check if DateTime object is valid - @param right DateTime object to compare - @return True if the two objects are not equal, false if they are - */ - bool operator!=(const DateTime &right) const { return !(*this == right); } - -protected: - uint8_t yOff; ///< Year offset from 2000 - uint8_t m; ///< Month 1-12 - uint8_t d; ///< Day 1-31 - uint8_t hh; ///< Hours 0-23 - uint8_t mm; ///< Minutes 0-59 - uint8_t ss; ///< Seconds 0-59 -}; - -/**************************************************************************/ -/*! - @brief Timespan which can represent changes in time with seconds accuracy. -*/ -/**************************************************************************/ -class TimeSpan { -public: - TimeSpan(int32_t seconds = 0); - TimeSpan(int16_t days, int8_t hours, int8_t minutes, int8_t seconds); - TimeSpan(const TimeSpan ©); - - /*! - @brief Number of days in the TimeSpan - e.g. 4 - @return int16_t days - */ - int16_t days() const { return _seconds / 86400L; } - /*! - @brief Number of hours in the TimeSpan - This is not the total hours, it includes the days - e.g. 4 days, 3 hours - NOT 99 hours - @return int8_t hours - */ - int8_t hours() const { return _seconds / 3600 % 24; } - /*! - @brief Number of minutes in the TimeSpan - This is not the total minutes, it includes days/hours - e.g. 4 days, 3 hours, 27 minutes - @return int8_t minutes - */ - int8_t minutes() const { return _seconds / 60 % 60; } - /*! - @brief Number of seconds in the TimeSpan - This is not the total seconds, it includes the days/hours/minutes - e.g. 4 days, 3 hours, 27 minutes, 7 seconds - @return int8_t seconds - */ - int8_t seconds() const { return _seconds % 60; } - /*! - @brief Total number of seconds in the TimeSpan, e.g. 358027 - @return int32_t seconds - */ - int32_t totalseconds() const { return _seconds; } - - TimeSpan operator+(const TimeSpan &right); - TimeSpan operator-(const TimeSpan &right); - -protected: - int32_t _seconds; ///< Actual TimeSpan value is stored as seconds -}; - /** DS1307 SQW pin mode settings */ enum Ds1307SqwPinMode { DS1307_OFF = 0x00, // Low @@ -271,14 +73,16 @@ enum Ds1307SqwPinMode { @brief RTC based on the DS1307 chip connected via I2C and the Wire library */ /**************************************************************************/ -class RTC_DS1307 { +class RTC_DS1307 : public RealTimeClock { public: boolean begin(void); - static void adjust(const DateTime &dt); - uint8_t isrunning(void); - static DateTime now(); - static Ds1307SqwPinMode readSqwPinMode(); - static void writeSqwPinMode(Ds1307SqwPinMode mode); + boolean begin(const DateTime &dt); + void adjust(const DateTime &dt); + bool isrunning(void); + bool lostPower(void); + DateTime now(); + Ds1307SqwPinMode readSqwPinMode(); + void writeSqwPinMode(Ds1307SqwPinMode mode); uint8_t readnvram(uint8_t address); void readnvram(uint8_t *buf, uint8_t size, uint8_t address); void writenvram(uint8_t address, uint8_t data); @@ -323,14 +127,16 @@ enum Ds3231Alarm2Mode { @brief RTC based on the DS3231 chip connected via I2C and the Wire library */ /**************************************************************************/ -class RTC_DS3231 { +class RTC_DS3231 : public RealTimeClock { public: boolean begin(void); - static void adjust(const DateTime &dt); + boolean begin(const DateTime &dt); + void adjust(const DateTime &dt); + bool isrunning(void); bool lostPower(void); - static DateTime now(); - static Ds3231SqwPinMode readSqwPinMode(); - static void writeSqwPinMode(Ds3231SqwPinMode mode); + DateTime now(); + Ds3231SqwPinMode readSqwPinMode(); + void writeSqwPinMode(Ds3231SqwPinMode mode); bool setAlarm1(const DateTime &dt, Ds3231Alarm1Mode alarm_mode); bool setAlarm2(const DateTime &dt, Ds3231Alarm2Mode alarm_mode); void disableAlarm(uint8_t alarm_num); @@ -388,16 +194,17 @@ enum Pcf8523OffsetMode { @brief RTC based on the PCF8523 chip connected via I2C and the Wire library */ /**************************************************************************/ -class RTC_PCF8523 { +class RTC_PCF8523 : public RealTimeClock { public: boolean begin(void); + boolean begin(const DateTime &dt); void adjust(const DateTime &dt); + boolean isrunning(void); boolean lostPower(void); boolean initialized(void); - static DateTime now(); + DateTime now(); void start(void); void stop(void); - uint8_t isrunning(); Pcf8523SqwPinMode readSqwPinMode(); void writeSqwPinMode(Pcf8523SqwPinMode mode); void enableSecondTimer(void); @@ -425,15 +232,16 @@ enum Pcf8563SqwPinMode { */ /**************************************************************************/ -class RTC_PCF8563 { +class RTC_PCF8563 : public RealTimeClock { public: boolean begin(void); + boolean begin(const DateTime &dt); + boolean isrunning(void); boolean lostPower(void); void adjust(const DateTime &dt); - static DateTime now(); + DateTime now(); void start(void); void stop(void); - uint8_t isrunning(); Pcf8563SqwPinMode readSqwPinMode(); void writeSqwPinMode(Pcf8563SqwPinMode mode); }; @@ -444,15 +252,22 @@ class RTC_PCF8563 { use. NOTE: this is immune to millis() rollover events. */ /**************************************************************************/ -class RTC_Millis { +class RTC_Millis : public RealTimeClock { public: + boolean begin(void); + boolean begin(const DateTime &dt); /*! - @brief Start the RTC - @param dt DateTime object with the date/time to set + @brief Simulate if the RTC is running + @return true */ - static void begin(const DateTime &dt) { adjust(dt); } - static void adjust(const DateTime &dt); - static DateTime now(); + boolean isrunning(void) { return true; } + /*! + @brief Simulate if the RTC has lost power + @return false + */ + boolean lostPower(void) { return false; } + void adjust(const DateTime &dt); + DateTime now(); protected: static uint32_t lastUnix; ///< Unix time from the previous call to now() - @@ -470,16 +285,23 @@ class RTC_Millis { approximately 71.6 minutes. */ /**************************************************************************/ -class RTC_Micros { +class RTC_Micros : public RealTimeClock { public: + boolean begin(void); + boolean begin(const DateTime &dt); + /*! + @brief Simulate if the RTC is running + @return true + */ + boolean isrunning(void) { return true; } /*! - @brief Start the RTC - @param dt DateTime object with the date/time to set + @brief Simulate if the RTC has lost power + @return false */ - static void begin(const DateTime &dt) { adjust(dt); } - static void adjust(const DateTime &dt); - static void adjustDrift(int ppm); - static DateTime now(); + boolean lostPower(void) { return false; } + void adjust(const DateTime &dt); + void adjustDrift(int ppm); + DateTime now(); protected: static uint32_t microsPerSecond; ///< Number of microseconds reported by diff --git a/keywords.txt b/keywords.txt index d3082fef..46bc0e9b 100644 --- a/keywords.txt +++ b/keywords.txt @@ -8,6 +8,7 @@ DateTime KEYWORD1 TimeSpan KEYWORD1 +RealTimeClock KEYWORD1 RTC_DS1307 KEYWORD1 RTC_DS3231 KEYWORD1 RTC_PCF8523 KEYWORD1 diff --git a/utility/DateTime.cpp b/utility/DateTime.cpp new file mode 100644 index 00000000..e25c04a2 --- /dev/null +++ b/utility/DateTime.cpp @@ -0,0 +1,641 @@ +#include "DateTime.h" + +#ifdef __AVR__ +#include +#elif defined(ESP8266) +#include +#elif defined(ARDUINO_ARCH_SAMD) +// nothing special needed +#elif defined(ARDUINO_SAM_DUE) +#define PROGMEM +#define pgm_read_byte(addr) (*(const unsigned char *)(addr)) +#define Wire Wire1 +#endif + +/**************************************************************************/ +// utility code, some of this could be exposed in the DateTime API if needed +/**************************************************************************/ + +/** + Number of days in each month, from January to November. December is not + needed. Omitting it avoids an incompatibility with Paul Stoffregen's Time + library. C.f. https://github.com/adafruit/RTClib/issues/114 +*/ +const uint8_t daysInMonth[] PROGMEM = {31, 28, 31, 30, 31, 30, + 31, 31, 30, 31, 30}; + +/**************************************************************************/ +/*! + @brief Given a date, return number of days since 2000/01/01, + valid for 2000--2099 + @param y Year + @param m Month + @param d Day + @return Number of days +*/ +/**************************************************************************/ +static uint16_t date2days(uint16_t y, uint8_t m, uint8_t d) { + if (y >= 2000U) + y -= 2000U; + uint16_t days = d; + for (uint8_t i = 1; i < m; ++i) + days += pgm_read_byte(daysInMonth + i - 1); + if (m > 2 && y % 4 == 0) + ++days; + return days + 365 * y + (y + 3) / 4 - 1; +} + +/**************************************************************************/ +/*! + @brief Given a number of days, hours, minutes, and seconds, return the + total seconds + @param days Days + @param h Hours + @param m Minutes + @param s Seconds + @return Number of seconds total +*/ +/**************************************************************************/ +static uint32_t time2ulong(uint16_t days, uint8_t h, uint8_t m, uint8_t s) { + return ((days * 24UL + h) * 60 + m) * 60 + s; +} + +/**************************************************************************/ +/*! + @brief Constructor from + [Unix time](https://en.wikipedia.org/wiki/Unix_time). + + This builds a DateTime from an integer specifying the number of seconds + elapsed since the epoch: 1970-01-01 00:00:00. This number is analogous + to Unix time, with two small differences: + + - The Unix epoch is specified to be at 00:00:00 + [UTC](https://en.wikipedia.org/wiki/Coordinated_Universal_Time), + whereas this class has no notion of time zones. The epoch used in + this class is then at 00:00:00 on whatever time zone the user chooses + to use, ignoring changes in DST. + + - Unix time is conventionally represented with signed numbers, whereas + this constructor takes an unsigned argument. Because of this, it does + _not_ suffer from the + [year 2038 problem](https://en.wikipedia.org/wiki/Year_2038_problem). + + If called without argument, it returns the earliest time representable + by this class: 2000-01-01 00:00:00. + + @see The `unixtime()` method is the converse of this constructor. + + @param t Time elapsed in seconds since 1970-01-01 00:00:00. +*/ +/**************************************************************************/ +DateTime::DateTime(uint32_t t) { + t -= SECONDS_FROM_1970_TO_2000; // bring to 2000 timestamp from 1970 + + ss = t % 60; + t /= 60; + mm = t % 60; + t /= 60; + hh = t % 24; + uint16_t days = t / 24; + uint8_t leap; + for (yOff = 0;; ++yOff) { + leap = yOff % 4 == 0; + if (days < 365U + leap) + break; + days -= 365 + leap; + } + for (m = 1; m < 12; ++m) { + uint8_t daysPerMonth = pgm_read_byte(daysInMonth + m - 1); + if (leap && m == 2) + ++daysPerMonth; + if (days < daysPerMonth) + break; + days -= daysPerMonth; + } + d = days + 1; +} + +/**************************************************************************/ +/*! + @brief Constructor from (year, month, day, hour, minute, second). + @warning If the provided parameters are not valid (e.g. 31 February), + the constructed DateTime will be invalid. + @see The `isValid()` method can be used to test whether the + constructed DateTime is valid. + @param year Either the full year (range: 2000--2099) or the offset from + year 2000 (range: 0--99). + @param month Month number (1--12). + @param day Day of the month (1--31). + @param hour,min,sec Hour (0--23), minute (0--59) and second (0--59). +*/ +/**************************************************************************/ +DateTime::DateTime(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, + uint8_t min, uint8_t sec) { + if (year >= 2000U) + year -= 2000U; + yOff = year; + m = month; + d = day; + hh = hour; + mm = min; + ss = sec; +} + +/**************************************************************************/ +/*! + @brief Copy constructor. + @param copy DateTime to copy. +*/ +/**************************************************************************/ +DateTime::DateTime(const DateTime ©) + : yOff(copy.yOff), m(copy.m), d(copy.d), hh(copy.hh), mm(copy.mm), + ss(copy.ss) {} + +/**************************************************************************/ +/*! + @brief Convert a string containing two digits to uint8_t, e.g. "09" returns + 9 + @param p Pointer to a string containing two digits +*/ +/**************************************************************************/ +static uint8_t conv2d(const char *p) { + uint8_t v = 0; + if ('0' <= *p && *p <= '9') + v = *p - '0'; + return 10 * v + *++p - '0'; +} + +/**************************************************************************/ +/*! + @brief Constructor for generating the build time. + + This constructor expects its parameters to be strings in the format + generated by the compiler's preprocessor macros `__DATE__` and + `__TIME__`. Usage: + + ``` + DateTime buildTime(__DATE__, __TIME__); + ``` + + @note The `F()` macro can be used to reduce the RAM footprint, see + the next constructor. + + @param date Date string, e.g. "Apr 16 2020". + @param time Time string, e.g. "18:34:56". +*/ +/**************************************************************************/ +DateTime::DateTime(const char *date, const char *time) { + yOff = conv2d(date + 9); + // Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec + switch (date[0]) { + case 'J': + m = (date[1] == 'a') ? 1 : ((date[2] == 'n') ? 6 : 7); + break; + case 'F': + m = 2; + break; + case 'A': + m = date[2] == 'r' ? 4 : 8; + break; + case 'M': + m = date[2] == 'r' ? 3 : 5; + break; + case 'S': + m = 9; + break; + case 'O': + m = 10; + break; + case 'N': + m = 11; + break; + case 'D': + m = 12; + break; + } + d = conv2d(date + 4); + hh = conv2d(time); + mm = conv2d(time + 3); + ss = conv2d(time + 6); +} + +/**************************************************************************/ +/*! + @brief Memory friendly constructor for generating the build time. + + This version is intended to save RAM by keeping the date and time + strings in program memory. Use it with the `F()` macro: + + ``` + DateTime buildTime(F(__DATE__), F(__TIME__)); + ``` + + @param date Date PROGMEM string, e.g. F("Apr 16 2020"). + @param time Time PROGMEM string, e.g. F("18:34:56"). +*/ +/**************************************************************************/ +DateTime::DateTime(const __FlashStringHelper *date, + const __FlashStringHelper *time) { + char buff[11]; + memcpy_P(buff, date, 11); + yOff = conv2d(buff + 9); + // Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec + switch (buff[0]) { + case 'J': + m = (buff[1] == 'a') ? 1 : ((buff[2] == 'n') ? 6 : 7); + break; + case 'F': + m = 2; + break; + case 'A': + m = buff[2] == 'r' ? 4 : 8; + break; + case 'M': + m = buff[2] == 'r' ? 3 : 5; + break; + case 'S': + m = 9; + break; + case 'O': + m = 10; + break; + case 'N': + m = 11; + break; + case 'D': + m = 12; + break; + } + d = conv2d(buff + 4); + memcpy_P(buff, time, 8); + hh = conv2d(buff); + mm = conv2d(buff + 3); + ss = conv2d(buff + 6); +} + +/**************************************************************************/ +/*! + @brief Constructor for creating a DateTime from an ISO8601 date string. + + This constructor expects its parameters to be a string in the + https://en.wikipedia.org/wiki/ISO_8601 format, e.g: + + "2020-06-25T15:29:37" + + Usage: + + ``` + DateTime dt("2020-06-25T15:29:37"); + ``` + + @note The year must be > 2000, as only the yOff is considered. + + @param iso8601dateTime + A dateTime string in iso8601 format, + e.g. "2020-06-25T15:29:37". + +*/ +/**************************************************************************/ +DateTime::DateTime(const char *iso8601dateTime) { + char ref[] = "2000-01-01T00:00:00"; + memcpy(ref, iso8601dateTime, min(strlen(ref), strlen(iso8601dateTime))); + yOff = conv2d(ref + 2); + m = conv2d(ref + 5); + d = conv2d(ref + 8); + hh = conv2d(ref + 11); + mm = conv2d(ref + 14); + ss = conv2d(ref + 17); +} + +/**************************************************************************/ +/*! + @brief Check whether this DateTime is valid. + @return true if valid, false if not. +*/ +/**************************************************************************/ +bool DateTime::isValid() const { + if (yOff >= 100) + return false; + DateTime other(unixtime()); + return yOff == other.yOff && m == other.m && d == other.d && hh == other.hh && + mm == other.mm && ss == other.ss; +} + +/**************************************************************************/ +/*! + @brief Writes the DateTime as a string in a user-defined format. + + The _buffer_ parameter should be initialized by the caller with a string + specifying the requested format. This format string may contain any of + the following specifiers: + + | specifier | output | + |-----------|--------------------------------------------------------| + | YYYY | the year as a 4-digit number (2000--2099) | + | YY | the year as a 2-digit number (00--99) | + | MM | the month as a 2-digit number (01--12) | + | MMM | the abbreviated English month name ("Jan"--"Dec") | + | DD | the day as a 2-digit number (01--31) | + | DDD | the abbreviated English day of the week ("Mon"--"Sun") | + | AP | either "AM" or "PM" | + | ap | either "am" or "pm" | + | hh | the hour as a 2-digit number (00--23 or 01--12) | + | mm | the minute as a 2-digit number (00--59) | + | ss | the second as a 2-digit number (00--59) | + + If either "AP" or "ap" is used, the "hh" specifier uses 12-hour mode + (range: 01--12). Otherwise it works in 24-hour mode (range: 00--23). + + The specifiers within _buffer_ will be overwritten with the appropriate + values from the DateTime. Any characters not belonging to one of the + above specifiers are left as-is. + + __Example__: The format "DDD, DD MMM YYYY hh:mm:ss" generates an output + of the form "Thu, 16 Apr 2020 18:34:56. + + @see The `timestamp()` method provides similar functionnality, but it + returns a `String` object and supports a limited choice of + predefined formats. + + @param[in,out] buffer Array of `char` for holding the format description + and the formatted DateTime. Before calling this method, the buffer + should be initialized by the user with the format string. The method + will overwrite the buffer with the formatted date and/or time. + + @return A pointer to the provided buffer. This is returned for + convenience, in order to enable idioms such as + `Serial.println(now.toString(buffer));` +*/ +/**************************************************************************/ + +char *DateTime::toString(char *buffer) { + uint8_t apTag = + (strstr(buffer, "ap") != nullptr) || (strstr(buffer, "AP") != nullptr); + uint8_t hourReformatted = 0, isPM = false; + if (apTag) { // 12 Hour Mode + if (hh == 0) { // midnight + isPM = false; + hourReformatted = 12; + } else if (hh == 12) { // noon + isPM = true; + hourReformatted = 12; + } else if (hh < 12) { // morning + isPM = false; + hourReformatted = hh; + } else { // 1 o'clock or after + isPM = true; + hourReformatted = hh - 12; + } + } + + for (size_t i = 0; i < strlen(buffer) - 1; i++) { + if (buffer[i] == 'h' && buffer[i + 1] == 'h') { + if (!apTag) { // 24 Hour Mode + buffer[i] = '0' + hh / 10; + buffer[i + 1] = '0' + hh % 10; + } else { // 12 Hour Mode + buffer[i] = '0' + hourReformatted / 10; + buffer[i + 1] = '0' + hourReformatted % 10; + } + } + if (buffer[i] == 'm' && buffer[i + 1] == 'm') { + buffer[i] = '0' + mm / 10; + buffer[i + 1] = '0' + mm % 10; + } + if (buffer[i] == 's' && buffer[i + 1] == 's') { + buffer[i] = '0' + ss / 10; + buffer[i + 1] = '0' + ss % 10; + } + if (buffer[i] == 'D' && buffer[i + 1] == 'D' && buffer[i + 2] == 'D') { + static PROGMEM const char day_names[] = "SunMonTueWedThuFriSat"; + const char *p = &day_names[3 * dayOfTheWeek()]; + buffer[i] = pgm_read_byte(p); + buffer[i + 1] = pgm_read_byte(p + 1); + buffer[i + 2] = pgm_read_byte(p + 2); + } else if (buffer[i] == 'D' && buffer[i + 1] == 'D') { + buffer[i] = '0' + d / 10; + buffer[i + 1] = '0' + d % 10; + } + if (buffer[i] == 'M' && buffer[i + 1] == 'M' && buffer[i + 2] == 'M') { + static PROGMEM const char month_names[] = + "JanFebMarAprMayJunJulAugSepOctNovDec"; + const char *p = &month_names[3 * (m - 1)]; + buffer[i] = pgm_read_byte(p); + buffer[i + 1] = pgm_read_byte(p + 1); + buffer[i + 2] = pgm_read_byte(p + 2); + } else if (buffer[i] == 'M' && buffer[i + 1] == 'M') { + buffer[i] = '0' + m / 10; + buffer[i + 1] = '0' + m % 10; + } + if (buffer[i] == 'Y' && buffer[i + 1] == 'Y' && buffer[i + 2] == 'Y' && + buffer[i + 3] == 'Y') { + buffer[i] = '2'; + buffer[i + 1] = '0'; + buffer[i + 2] = '0' + (yOff / 10) % 10; + buffer[i + 3] = '0' + yOff % 10; + } else if (buffer[i] == 'Y' && buffer[i + 1] == 'Y') { + buffer[i] = '0' + (yOff / 10) % 10; + buffer[i + 1] = '0' + yOff % 10; + } + if (buffer[i] == 'A' && buffer[i + 1] == 'P') { + if (isPM) { + buffer[i] = 'P'; + buffer[i + 1] = 'M'; + } else { + buffer[i] = 'A'; + buffer[i + 1] = 'M'; + } + } else if (buffer[i] == 'a' && buffer[i + 1] == 'p') { + if (isPM) { + buffer[i] = 'p'; + buffer[i + 1] = 'm'; + } else { + buffer[i] = 'a'; + buffer[i + 1] = 'm'; + } + } + } + return buffer; +} + +/**************************************************************************/ +/*! + @brief Return the hour in 12-hour format. + @return Hour (1--12). +*/ +/**************************************************************************/ +uint8_t DateTime::twelveHour() const { + if (hh == 0 || hh == 12) { // midnight or noon + return 12; + } else if (hh > 12) { // 1 o'clock or later + return hh - 12; + } else { // morning + return hh; + } +} + +/**************************************************************************/ +/*! + @brief Return the day of the week. + @return Day of week as an integer from 0 (Sunday) to 6 (Saturday). +*/ +/**************************************************************************/ +uint8_t DateTime::dayOfTheWeek() const { + uint16_t day = date2days(yOff, m, d); + return (day + 6) % 7; // Jan 1, 2000 is a Saturday, i.e. returns 6 +} + +/**************************************************************************/ +/*! + @brief Return Unix time: seconds since 1 Jan 1970. + + @see The `DateTime::DateTime(uint32_t)` constructor is the converse of + this method. + + @return Number of seconds since 1970-01-01 00:00:00. +*/ +/**************************************************************************/ +uint32_t DateTime::unixtime(void) const { + uint32_t t; + uint16_t days = date2days(yOff, m, d); + t = time2ulong(days, hh, mm, ss); + t += SECONDS_FROM_1970_TO_2000; // seconds from 1970 to 2000 + + return t; +} + +/**************************************************************************/ +/*! + @brief Convert the DateTime to seconds since 1 Jan 2000 + + The result can be converted back to a DateTime with: + + ```cpp + DateTime(SECONDS_FROM_1970_TO_2000 + value) + ``` + + @return Number of seconds since 2000-01-01 00:00:00. +*/ +/**************************************************************************/ +uint32_t DateTime::secondstime(void) const { + uint32_t t; + uint16_t days = date2days(yOff, m, d); + t = time2ulong(days, hh, mm, ss); + return t; +} + +/**************************************************************************/ +/*! + @brief Add a TimeSpan to the DateTime object + @param span TimeSpan object + @return New DateTime object with span added to it. +*/ +/**************************************************************************/ +DateTime DateTime::operator+(const TimeSpan &span) { + return DateTime(unixtime() + span.totalseconds()); +} + +/**************************************************************************/ +/*! + @brief Subtract a TimeSpan from the DateTime object + @param span TimeSpan object + @return New DateTime object with span subtracted from it. +*/ +/**************************************************************************/ +DateTime DateTime::operator-(const TimeSpan &span) { + return DateTime(unixtime() - span.totalseconds()); +} + +/**************************************************************************/ +/*! + @brief Subtract one DateTime from another + + @note Since a TimeSpan cannot be negative, the subtracted DateTime + should be less (earlier) than or equal to the one it is + subtracted from. + + @param right The DateTime object to subtract from self (the left object) + @return TimeSpan of the difference between DateTimes. +*/ +/**************************************************************************/ +TimeSpan DateTime::operator-(const DateTime &right) { + return TimeSpan(unixtime() - right.unixtime()); +} + +/**************************************************************************/ +/*! + @author Anton Rieutskyi + @brief Test if one DateTime is less (earlier) than another. + @warning if one or both DateTime objects are invalid, returned value is + meaningless + @see use `isValid()` method to check if DateTime object is valid + @param right Comparison DateTime object + @return True if the left DateTime is earlier than the right one, + false otherwise. +*/ +/**************************************************************************/ +bool DateTime::operator<(const DateTime &right) const { + return (yOff + 2000U < right.year() || + (yOff + 2000U == right.year() && + (m < right.month() || + (m == right.month() && + (d < right.day() || + (d == right.day() && + (hh < right.hour() || + (hh == right.hour() && + (mm < right.minute() || + (mm == right.minute() && ss < right.second())))))))))); +} + +/**************************************************************************/ +/*! + @author Anton Rieutskyi + @brief Test if two DateTime objects are equal. + @warning if one or both DateTime objects are invalid, returned value is + meaningless + @see use `isValid()` method to check if DateTime object is valid + @param right Comparison DateTime object + @return True if both DateTime objects are the same, false otherwise. +*/ +/**************************************************************************/ +bool DateTime::operator==(const DateTime &right) const { + return (right.year() == yOff + 2000U && right.month() == m && + right.day() == d && right.hour() == hh && right.minute() == mm && + right.second() == ss); +} + +/**************************************************************************/ +/*! + @brief Return a ISO 8601 timestamp as a `String` object. + + The generated timestamp conforms to one of the predefined, ISO + 8601-compatible formats for representing the date (if _opt_ is + `TIMESTAMP_DATE`), the time (`TIMESTAMP_TIME`), or both + (`TIMESTAMP_FULL`). + + @see The `toString()` method provides more general string formatting. + + @param opt Format of the timestamp + @return Timestamp string, e.g. "2020-04-16T18:34:56". +*/ +/**************************************************************************/ +String DateTime::timestamp(timestampOpt opt) { + char buffer[25]; // large enough for any DateTime, including invalid ones + + // Generate timestamp according to opt + switch (opt) { + case TIMESTAMP_TIME: + // Only time + sprintf(buffer, "%02d:%02d:%02d", hh, mm, ss); + break; + case TIMESTAMP_DATE: + // Only date + sprintf(buffer, "%u-%02d-%02d", 2000U + yOff, m, d); + break; + default: + // Full + sprintf(buffer, "%u-%02d-%02dT%02d:%02d:%02d", 2000U + yOff, m, d, hh, mm, + ss); + } + return String(buffer); +} diff --git a/utility/DateTime.h b/utility/DateTime.h new file mode 100644 index 00000000..330c2c22 --- /dev/null +++ b/utility/DateTime.h @@ -0,0 +1,157 @@ +#ifndef _DATETIME_H_ +#define _DATETIME_H_ + +#include "TimeSpan.h" +#include + +/** Constants */ +#define SECONDS_PER_DAY 86400L ///< 60 * 60 * 24 +#define SECONDS_FROM_1970_TO_2000 \ + 946684800 ///< Unixtime for 2000-01-01 00:00:00, useful for initialization + +uint8_t getDaysInMonth(uint16_t year, uint8_t month); + +/**************************************************************************/ +/*! + @brief Simple general-purpose date/time class (no TZ / DST / leap + seconds). + + This class stores date and time information in a broken-down form, as a + tuple (year, month, day, hour, minute, second). The day of the week is + not stored, but computed on request. The class has no notion of time + zones, daylight saving time, or + [leap seconds](http://en.wikipedia.org/wiki/Leap_second): time is stored + in whatever time zone the user chooses to use. + + The class supports dates in the range from 1 Jan 2000 to 31 Dec 2099 + inclusive. +*/ +/**************************************************************************/ +class DateTime { +public: + DateTime(uint32_t t = SECONDS_FROM_1970_TO_2000); + DateTime(uint16_t year, uint8_t month, uint8_t day, uint8_t hour = 0, + uint8_t min = 0, uint8_t sec = 0); + DateTime(const DateTime ©); + DateTime(const char *date, const char *time); + DateTime(const __FlashStringHelper *date, const __FlashStringHelper *time); + DateTime(const char *iso8601date); + bool isValid() const; + char *toString(char *buffer); + + /*! + @brief Return the year. + @return Year (range: 2000--2099). + */ + uint16_t year() const { return 2000U + yOff; } + /*! + @brief Return the month. + @return Month number (1--12). + */ + uint8_t month() const { return m; } + /*! + @brief Return the day of the month. + @return Day of the month (1--31). + */ + uint8_t day() const { return d; } + /*! + @brief Return the hour + @return Hour (0--23). + */ + uint8_t hour() const { return hh; } + + uint8_t twelveHour() const; + /*! + @brief Return whether the time is PM. + @return 0 if the time is AM, 1 if it's PM. + */ + uint8_t isPM() const { return hh >= 12; } + /*! + @brief Return the minute. + @return Minute (0--59). + */ + uint8_t minute() const { return mm; } + /*! + @brief Return the second. + @return Second (0--59). + */ + uint8_t second() const { return ss; } + + uint8_t dayOfTheWeek() const; + + /* 32-bit times as seconds since 2000-01-01. */ + uint32_t secondstime() const; + + /* 32-bit times as seconds since 1970-01-01. */ + uint32_t unixtime(void) const; + + /*! + Format of the ISO 8601 timestamp generated by `timestamp()`. Each + option corresponds to a `toString()` format as follows: + */ + enum timestampOpt { + TIMESTAMP_FULL, //!< `YYYY-MM-DDThh:mm:ss` + TIMESTAMP_TIME, //!< `hh:mm:ss` + TIMESTAMP_DATE //!< `YYYY-MM-DD` + }; + String timestamp(timestampOpt opt = TIMESTAMP_FULL); + + DateTime operator+(const TimeSpan &span); + DateTime operator-(const TimeSpan &span); + TimeSpan operator-(const DateTime &right); + bool operator<(const DateTime &right) const; + + /*! + @brief Test if one DateTime is greater (later) than another. + @warning if one or both DateTime objects are invalid, returned value is + meaningless + @see use `isValid()` method to check if DateTime object is valid + @param right DateTime object to compare + @return True if the left DateTime is later than the right one, + false otherwise + */ + bool operator>(const DateTime &right) const { return right < *this; } + + /*! + @brief Test if one DateTime is less (earlier) than or equal to another + @warning if one or both DateTime objects are invalid, returned value is + meaningless + @see use `isValid()` method to check if DateTime object is valid + @param right DateTime object to compare + @return True if the left DateTime is earlier than or equal to the + right one, false otherwise + */ + bool operator<=(const DateTime &right) const { return !(*this > right); } + + /*! + @brief Test if one DateTime is greater (later) than or equal to another + @warning if one or both DateTime objects are invalid, returned value is + meaningless + @see use `isValid()` method to check if DateTime object is valid + @param right DateTime object to compare + @return True if the left DateTime is later than or equal to the right + one, false otherwise + */ + bool operator>=(const DateTime &right) const { return !(*this < right); } + bool operator==(const DateTime &right) const; + + /*! + @brief Test if two DateTime objects are not equal. + @warning if one or both DateTime objects are invalid, returned value is + meaningless + @see use `isValid()` method to check if DateTime object is valid + @param right DateTime object to compare + @return True if the two objects are not equal, false if they are + */ + bool operator!=(const DateTime &right) const { return !(*this == right); } + +protected: + uint8_t yOff; ///< Year offset from 2000 + uint8_t m; ///< Month 1-12 + uint8_t d; ///< Day 1-31 + uint8_t hh; ///< Hours 0-23 + uint8_t mm; ///< Minutes 0-59 + uint8_t ss; ///< Seconds 0-59 +}; + +#endif \ No newline at end of file diff --git a/utility/RTC_Base.h b/utility/RTC_Base.h new file mode 100644 index 00000000..8cc8ef43 --- /dev/null +++ b/utility/RTC_Base.h @@ -0,0 +1,84 @@ +/**************************************************************************/ +/*! + @file RTC_Base.h + + Original library by JeeLabs http://news.jeelabs.org/code/, released to the + public domain + + License: MIT (see LICENSE) + + This is a fork of JeeLab's fantastic real time clock library for Arduino. + + For details on using this library with an RTC module like the DS1307, PCF8523, + or DS3231, see the guide at: + https://learn.adafruit.com/ds1307-real-time-clock-breakout-board-kit/overview + + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! +*/ +/**************************************************************************/ + +#ifndef _RTC_BASE_H +#define _RTC_BASE_H + +#include "DateTime.h" +#include + +/**************************************************************************/ +/*! + @brief RTC base class for all RTC chips + @note Called RealTimeClock as there is already a macro called RTC in + Arduino IDE install files +*/ +/**************************************************************************/ +class RealTimeClock { +public: + /*! + @brief Start the RTC while preserving the RTC's date/time + @return True if successful, false otherwise + */ + virtual boolean begin(void) = 0; + + /*! + @brief Start the RTC and adjust the RTC's date/time + @param dt DateTime object containing desired date/time + @return True if successful, false otherwise + */ + virtual boolean begin(const DateTime &dt); + + /*! + @brief Adjust the RTC to the specified date/time + @param dt DateTime object containing desired date/time + */ + virtual void adjust(const DateTime &dt) = 0; + + /* + @brief Adjust the RTC clock speed to compensate for system clock + drift + @param ppm Parts per million to adjust clock speed by + @note Positive values make the clock faster and vice-versa + @todo Implement function in future version + */ + // virtual void adjustDrift(int ppm); + + /*! + @brief Check if the RTC is running or not + @return True if it is running, false otherwise + */ + virtual boolean isrunning(void); + + /*! + @brief Check if the RTC has lost power since last adjust() + @return True if the RTC has lost power, false otherwise + */ + virtual boolean lostPower(void); + + /*! + @brief Get the current date/time from the RTC + @return The date/time + */ + virtual DateTime now() = 0; +}; + +#endif \ No newline at end of file diff --git a/utility/TimeSpan.cpp b/utility/TimeSpan.cpp new file mode 100644 index 00000000..b0a668ce --- /dev/null +++ b/utility/TimeSpan.cpp @@ -0,0 +1,54 @@ +#include "TimeSpan.h" + +/**************************************************************************/ +/*! + @brief Create a new TimeSpan object in seconds + @param seconds Number of seconds +*/ +/**************************************************************************/ +TimeSpan::TimeSpan(int32_t seconds) : _seconds(seconds) {} + +/**************************************************************************/ +/*! + @brief Create a new TimeSpan object using a number of + days/hours/minutes/seconds e.g. Make a TimeSpan of 3 hours and 45 minutes: + new TimeSpan(0, 3, 45, 0); + @param days Number of days + @param hours Number of hours + @param minutes Number of minutes + @param seconds Number of seconds +*/ +/**************************************************************************/ +TimeSpan::TimeSpan(int16_t days, int8_t hours, int8_t minutes, int8_t seconds) + : _seconds((int32_t)days * 86400L + (int32_t)hours * 3600 + + (int32_t)minutes * 60 + seconds) {} + +/**************************************************************************/ +/*! + @brief Copy constructor, make a new TimeSpan using an existing one + @param copy The TimeSpan to copy +*/ +/**************************************************************************/ +TimeSpan::TimeSpan(const TimeSpan ©) : _seconds(copy._seconds) {} + +/**************************************************************************/ +/*! + @brief Add two TimeSpans + @param right TimeSpan to add + @return New TimeSpan object, sum of left and right +*/ +/**************************************************************************/ +TimeSpan TimeSpan::operator+(const TimeSpan &right) { + return TimeSpan(_seconds + right._seconds); +} + +/**************************************************************************/ +/*! + @brief Subtract a TimeSpan + @param right TimeSpan to subtract + @return New TimeSpan object, right subtracted from left +*/ +/**************************************************************************/ +TimeSpan TimeSpan::operator-(const TimeSpan &right) { + return TimeSpan(_seconds - right._seconds); +} diff --git a/utility/TimeSpan.h b/utility/TimeSpan.h new file mode 100644 index 00000000..924a6b77 --- /dev/null +++ b/utility/TimeSpan.h @@ -0,0 +1,57 @@ +#ifndef _TIMESPAN_H_ +#define _TIMESPAN_H_ + +#include + +/**************************************************************************/ +/*! + @brief Timespan which can represent changes in time with seconds accuracy. +*/ +/**************************************************************************/ +class TimeSpan { +public: + TimeSpan(int32_t seconds = 0); + TimeSpan(int16_t days, int8_t hours, int8_t minutes, int8_t seconds); + TimeSpan(const TimeSpan ©); + + /*! + @brief Number of days in the TimeSpan + e.g. 4 + @return int16_t days + */ + int16_t days() const { return _seconds / 86400L; } + /*! + @brief Number of hours in the TimeSpan + This is not the total hours, it includes the days + e.g. 4 days, 3 hours - NOT 99 hours + @return int8_t hours + */ + int8_t hours() const { return _seconds / 3600 % 24; } + /*! + @brief Number of minutes in the TimeSpan + This is not the total minutes, it includes days/hours + e.g. 4 days, 3 hours, 27 minutes + @return int8_t minutes + */ + int8_t minutes() const { return _seconds / 60 % 60; } + /*! + @brief Number of seconds in the TimeSpan + This is not the total seconds, it includes the days/hours/minutes + e.g. 4 days, 3 hours, 27 minutes, 7 seconds + @return int8_t seconds + */ + int8_t seconds() const { return _seconds % 60; } + /*! + @brief Total number of seconds in the TimeSpan, e.g. 358027 + @return int32_t seconds + */ + int32_t totalseconds() const { return _seconds; } + + TimeSpan operator+(const TimeSpan &right); + TimeSpan operator-(const TimeSpan &right); + +protected: + int32_t _seconds; ///< Actual TimeSpan value is stored as seconds +}; + +#endif \ No newline at end of file