@@ -462,7 +462,7 @@ bool DateTime::isValid() const {
462
462
*/
463
463
/* *************************************************************************/
464
464
465
- char *DateTime::toString (char *buffer) {
465
+ char *DateTime::toString (char *buffer) const {
466
466
uint8_t apTag =
467
467
(strstr (buffer, " ap" ) != nullptr ) || (strstr (buffer, " AP" ) != nullptr );
468
468
uint8_t hourReformatted = 0 , isPM = false ;
@@ -713,7 +713,7 @@ bool DateTime::operator==(const DateTime &right) const {
713
713
@return Timestamp string, e.g. "2020-04-16T18:34:56".
714
714
*/
715
715
/* *************************************************************************/
716
- String DateTime::timestamp (timestampOpt opt) {
716
+ String DateTime::timestamp (timestampOpt opt) const {
717
717
char buffer[25 ]; // large enough for any DateTime, including invalid ones
718
718
719
719
// Generate timestamp according to opt
@@ -765,6 +765,161 @@ TimeSpan::TimeSpan(int16_t days, int8_t hours, int8_t minutes, int8_t seconds)
765
765
/* *************************************************************************/
766
766
TimeSpan::TimeSpan (const TimeSpan ©) : _seconds(copy._seconds) {}
767
767
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
+
768
923
/* *************************************************************************/
769
924
/* !
770
925
@brief Add two TimeSpans
0 commit comments