Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 3d61e6d

Browse files
committedMar 10, 2021
add methods to format TimeSpans
Uses iso 8601 period format. Includes a constructor from a string. Makes all toString-like methods const
1 parent bd43840 commit 3d61e6d

File tree

2 files changed

+170
-7
lines changed

2 files changed

+170
-7
lines changed
 

‎RTClib.cpp

+157-2
Original file line numberDiff line numberDiff line change
@@ -462,7 +462,7 @@ bool DateTime::isValid() const {
462462
*/
463463
/**************************************************************************/
464464

465-
char *DateTime::toString(char *buffer) {
465+
char *DateTime::toString(char *buffer) const {
466466
uint8_t apTag =
467467
(strstr(buffer, "ap") != nullptr) || (strstr(buffer, "AP") != nullptr);
468468
uint8_t hourReformatted = 0, isPM = false;
@@ -713,7 +713,7 @@ bool DateTime::operator==(const DateTime &right) const {
713713
@return Timestamp string, e.g. "2020-04-16T18:34:56".
714714
*/
715715
/**************************************************************************/
716-
String DateTime::timestamp(timestampOpt opt) {
716+
String DateTime::timestamp(timestampOpt opt) const {
717717
char buffer[25]; // large enough for any DateTime, including invalid ones
718718

719719
// Generate timestamp according to opt
@@ -765,6 +765,161 @@ TimeSpan::TimeSpan(int16_t days, int8_t hours, int8_t minutes, int8_t seconds)
765765
/**************************************************************************/
766766
TimeSpan::TimeSpan(const TimeSpan &copy) : _seconds(copy._seconds) {}
767767

768+
/*!
769+
@brief Create a new TimeSpan from an iso8601 formatted period string
770+
771+
If the string is entirely malformed (doesn't begin with P or -) this will
772+
construct a TimeSpan of 0 seconds. If parsing fails before the end of the
773+
string, this constuctor will interpret its argument as the longest
774+
well-formed prefix string.
775+
776+
For example, the string P5M10~ will parse the same as P5M
777+
778+
@param iso8601 the formatted string
779+
*/
780+
TimeSpan::TimeSpan(const char *iso8601) : _seconds(0) {
781+
int32_t sign = 1;
782+
const char *cursor = iso8601;
783+
if (*cursor == '-') {
784+
sign = -1;
785+
cursor++;
786+
}
787+
if (*cursor == 'P') {
788+
cursor++;
789+
} else {
790+
return;
791+
}
792+
793+
char *rest;
794+
long num = strtol(cursor, &rest, 10);
795+
cursor = rest;
796+
if (*cursor == 'D') {
797+
_seconds += sign * SECONDS_PER_DAY * num;
798+
cursor++;
799+
}
800+
801+
if (*cursor == 'T') {
802+
cursor++;
803+
num = strtol(cursor, &rest, 10);
804+
cursor = rest;
805+
} else if (*cursor == '\0') {
806+
return;
807+
} else {
808+
// error: malformed iso8601
809+
return;
810+
}
811+
812+
if (*cursor == 'H') {
813+
_seconds += sign * SECS_PER_MIN * MINS_PER_HOUR * num;
814+
cursor++;
815+
num = strtol(cursor, &rest, 10);
816+
cursor = rest;
817+
}
818+
819+
if (*cursor == 'M') {
820+
_seconds += sign * SECS_PER_MIN * num;
821+
cursor++;
822+
num = strtol(cursor, &rest, 10);
823+
cursor = rest;
824+
}
825+
826+
if (*cursor == 'S') {
827+
_seconds += sign * num;
828+
}
829+
}
830+
831+
/*!
832+
@brief Formats this TimeSpan according to iso8601
833+
834+
See toCharArray for more details on the format
835+
836+
@return String of formatted TimeSpan
837+
*/
838+
String TimeSpan::toString() const {
839+
constexpr size_t buflen = 19;
840+
char buf[buflen];
841+
this->toCharArray(buf, buflen);
842+
return String(buf);
843+
}
844+
845+
// Equivalent to left - right unless that would underflow, otherwise 0
846+
//
847+
// At the time of writing, this is only used in logic in TimeSpan::toCharArray
848+
// This function's inclusion is very unfortunate, and if it is used anywhere
849+
// else, should probably use a template
850+
static size_t sat_sub(size_t left, size_t right) {
851+
if (left > right) {
852+
return left - right;
853+
} else {
854+
return 0;
855+
}
856+
}
857+
858+
/*!
859+
@brief Formats this TimeSpan according to iso8601
860+
861+
Fails (returning 0) if buf is too small to store the result. buf size >= 19
862+
will never fail.
863+
Example: TimeSpan t(32, 23, 54, 11) formats to "P32DT23H54M11S".
864+
865+
@param buf char array to write output. Always contains trailing '\0'.
866+
@param len the length of buf. Must be at least 1.
867+
@return number of bytes written excluding trailing '\0' or 0 on failure
868+
*/
869+
size_t TimeSpan::toCharArray(char *buf, size_t len) const {
870+
size_t written = 0;
871+
872+
if (_seconds == 0) {
873+
written += snprintf(buf, len, "PT0S");
874+
if (written >= len) {
875+
return 0;
876+
} else {
877+
return written;
878+
}
879+
}
880+
881+
// Keep sign separate from tmp to prevent overflow caused by -1 * INT_MIN
882+
int8_t sign = _seconds > 0 ? 1 : -1;
883+
int32_t tmp = _seconds;
884+
int8_t seconds = sign * (tmp % SECS_PER_MIN);
885+
// Fold in sign - while dividing by SECS_PER_MIN, tmp can no longer overflow
886+
tmp /= sign * SECS_PER_MIN;
887+
int8_t minutes = tmp % MINS_PER_HOUR;
888+
tmp /= MINS_PER_HOUR;
889+
int8_t hours = tmp % HOURS_PER_DAY;
890+
int16_t days = tmp / HOURS_PER_DAY;
891+
892+
if (_seconds < 0) {
893+
written += snprintf(buf + written, sat_sub(len, written), "-P");
894+
} else {
895+
written += snprintf(buf + written, sat_sub(len, written), "P");
896+
}
897+
898+
if (days > 0) {
899+
written += snprintf(buf + written, sat_sub(len, written), "%dD", days);
900+
}
901+
902+
if (hours > 0 || minutes > 0 || seconds > 0) {
903+
written += snprintf(buf + written, sat_sub(len, written), "%s", "T");
904+
905+
if (hours > 0) {
906+
written += snprintf(buf + written, sat_sub(len, written), "%dH", hours);
907+
}
908+
if (minutes > 0) {
909+
written += snprintf(buf + written, sat_sub(len, written), "%dM", minutes);
910+
}
911+
if (seconds > 0) {
912+
written += snprintf(buf + written, sat_sub(len, written), "%dS", seconds);
913+
}
914+
}
915+
916+
if (written >= len) {
917+
return 0;
918+
} else {
919+
return written;
920+
}
921+
}
922+
768923
/**************************************************************************/
769924
/*!
770925
@brief Add two TimeSpans

‎RTClib.h

+13-5
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ class DateTime {
8888
DateTime(const __FlashStringHelper *date, const __FlashStringHelper *time);
8989
DateTime(const char *iso8601date);
9090
bool isValid() const;
91-
char *toString(char *buffer);
91+
char *toString(char *buffer) const;
9292

9393
/*!
9494
@brief Return the year.
@@ -145,7 +145,7 @@ class DateTime {
145145
TIMESTAMP_TIME, //!< `hh:mm:ss`
146146
TIMESTAMP_DATE //!< `YYYY-MM-DD`
147147
};
148-
String timestamp(timestampOpt opt = TIMESTAMP_FULL);
148+
String timestamp(timestampOpt opt = TIMESTAMP_FULL) const;
149149

150150
DateTime operator+(const TimeSpan &span);
151151
DateTime operator-(const TimeSpan &span);
@@ -215,6 +215,9 @@ class TimeSpan {
215215
TimeSpan(int32_t seconds = 0);
216216
TimeSpan(int16_t days, int8_t hours, int8_t minutes, int8_t seconds);
217217
TimeSpan(const TimeSpan &copy);
218+
TimeSpan(const char *iso8601);
219+
String toString() const;
220+
size_t toCharArray(char *buf, size_t len) const;
218221

219222
/*!
220223
@brief Number of days in the TimeSpan
@@ -228,21 +231,23 @@ class TimeSpan {
228231
e.g. 4 days, 3 hours - NOT 99 hours
229232
@return int8_t hours
230233
*/
231-
int8_t hours() const { return _seconds / 3600 % 24; }
234+
int8_t hours() const {
235+
return _seconds / (SECS_PER_MIN * MINS_PER_HOUR) % HOURS_PER_DAY;
236+
}
232237
/*!
233238
@brief Number of minutes in the TimeSpan
234239
This is not the total minutes, it includes days/hours
235240
e.g. 4 days, 3 hours, 27 minutes
236241
@return int8_t minutes
237242
*/
238-
int8_t minutes() const { return _seconds / 60 % 60; }
243+
int8_t minutes() const { return _seconds / SECS_PER_MIN % MINS_PER_HOUR; }
239244
/*!
240245
@brief Number of seconds in the TimeSpan
241246
This is not the total seconds, it includes the days/hours/minutes
242247
e.g. 4 days, 3 hours, 27 minutes, 7 seconds
243248
@return int8_t seconds
244249
*/
245-
int8_t seconds() const { return _seconds % 60; }
250+
int8_t seconds() const { return _seconds % SECS_PER_MIN; }
246251
/*!
247252
@brief Total number of seconds in the TimeSpan, e.g. 358027
248253
@return int32_t seconds
@@ -254,6 +259,9 @@ class TimeSpan {
254259

255260
protected:
256261
int32_t _seconds; ///< Actual TimeSpan value is stored as seconds
262+
static constexpr int32_t SECS_PER_MIN = 60; ///< Number of seconds in a minute
263+
static constexpr int32_t MINS_PER_HOUR = 60; ///< Number of minutes in an hour
264+
static constexpr int32_t HOURS_PER_DAY = 24; ///< Number of hours in a day
257265
};
258266

259267
/** DS1307 SQW pin mode settings */

0 commit comments

Comments
 (0)
Please sign in to comment.