Chars, Strings and Pointers

Pages: 12
As a long-time VB programmer and neophyte in C++, I am going mad trying to sort out how to manipulate characters in the way that I want!

The requirement is simple: I want to set the time on a DS1307 RTC by getting it from an NTP server via an ESP8266. I can get the time from the NTP server with no problem, in the form of a char of length 32. It is in the form:

uuuuuuuuuu dd/mm hh/nn

where uu... represents Unix time and dd,mm,hh,nn are obvious.

I can successfully reset the time on the clock (for which I am using the RTClib library) using the RTC.adjust(DateTime(__DATE__, __TIME__)) call.

All I want to do is to get my first string into a form (or forms) suitable for substituting in the RTC.adjust call.

I have played with Strings, strings, chars, strcats, sprintfs and goodness knows what but just end up with a list of compile errors!

Can somebody please just point me in the right direction so that I can get past this block?

Thanks!
Last edited on
uuuuuuuuuu dd/mm hh/nn

That's not 32 characters. Given that the time is encoded in the uuuuuuuuuu, and the day in dd and the month in mm , what is hh and nn?

I think hh and nn are probably the hour and minute within the day. Hmm. What about seconds and fractions of seconds??
I can successfully reset the time on the clock (for which I am using the RTClib library) using the RTC.adjust(DateTime(__DATE__, __TIME__)) call.

What are __DATE__ and __TIME__? I don't think these are standard C++ types.
Hello PhilTilson,

For future reference:

PLEASE ALWAYS USE CODE TAGS (the <> formatting button), to the right of this box, when posting code.

It makes it easier to read your code and also easier to respond to your post.

http://www.cplusplus.com/articles/jEywvCM9/
http://www.cplusplus.com/articles/z13hAqkS/

Hint: You can edit your post, highlight your code and press the <> formatting button.
You can use the preview button at the bottom to see how it looks.

I found the second link to be the most help.



I think something like this would work:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream>
#include <iomanip>  //  setw(), fixed, setprecision.
#include <limits>
#include <string>
#include <sstream>

int main()
{

	std::string line{ "45612345 01/02 10/02" }, unixTime, day, month, hour, minute;
	int iDay{}, iMonth{}, iHour{}, iMinute{};

	std::istringstream ss(line);

	std::getline(ss, unixTime, ' ');
	std::getline(ss, day, '/');
	std::getline(ss, month, ' ');
	std::getline(ss, hour, '/');
	std::getline(ss, minute);

	// <--- If you need "day", "month", "hour" and "minute" in an "int".
	iDay = std::stoi(day);

	//std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');  // <--- Requires header file <limits>.
	std::cout << "\n\n Press Enter to continue";
	std::cin.get();

	return 0;
}

It may not be the best code, but it does get the job done. See what you think.

Hope that helps,

Andy
__DATE__ will be turned into something of format "Feb 12 1996" ; eleven characters, left padded with a single space in the month is less than 10.

__TIME__ will be turned into something of format "23:59:01" ; eight characters.

( https://gcc.gnu.org/onlinedocs/cpp/Standard-Predefined-Macros.html )

So whatever you end up trying to feed to this Datetime function you've got, you know that it should match that formatting.
Last edited on
Something like this, perhaps. This code takes the unixtime, ignores everything else, and generates strings matching those produced by the __DATE__ and __TIME__ preprocessor macros. I added a comment showing how to use those strings in the call you want.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <iostream>
#include <string>
#include <sstream>
#include <ctime>

int main()
{

	std::string line{ "1551346610  01/02 10/02" };

	std::string months[] = { "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" };


	std::istringstream ss(line);

	std::string unixTime;
	std::getline(ss, unixTime, ' ');

	std::time_t nixTime = stoi(unixTime);
	std::tm result;
	gmtime_s(&result, &nixTime);


	std::string DATE;
	
	if (result.tm_mday < 10) { DATE += " "; }  // if day is less than 10, entire DATE line is left padded with a single space

	DATE+= months[result.tm_mon] + " " + std::to_string(result.tm_mday) + " " + std::to_string(1900 + result.tm_year);

	std::string TIME;

	if (result.tm_hour < 10) { TIME += '0'; };
	TIME += std::to_string(result.tm_hour) + ":";

	if (result.tm_min < 10)  { TIME += '0'; };
	TIME += std::to_string(result.tm_min) + ":";

	if (result.tm_sec < 10)  { TIME += '0'; };
	TIME += std::to_string(result.tm_sec);

	std::cout << DATE << '\n' << TIME << '\n';

	// RTC.adjust(DateTime(DATE.c_str(), TIME.c_str()))

	return 0;
}


gmtime_s on windows seems to have the parameters in the reverse order expected ( https://en.cppreference.com/w/c/chrono/gmtime ) so that might need care on other implementations.
Last edited on
I am grateful to all responders for their suggestions. I will now try these out!

I know this is probably heresy on this forum, but can you wonder that I decided to work in VB all those years ago when this problem can be solved in just one line of VB code! :-))
Sorry - but this IS the beginners' section!

I am still having problems with 'string'. I have Googled all over to try to find the difference between 'String' (which comes up highlighted in my editor) and 'string' which doesn't. The latter seems to require std:: before it if I don't put 'use namespace std' at the start, and this I can understand.

However, I can Serial.print a String, but not a string! What am I missing here? None of the references seem to make this distinction and I think this is what is making my life so difficult.

Thanks.
I am not too familiar with RTCs, but it sounds like you're coding in Arduino?

The Arduino language has its own implementation of strings,
https://www.arduino.cc/reference/en/language/variables/data-types/stringobject/

If you are interfacing with Arduino built-in functions, and not C++ standard library functions, you need to pass the Arduino "String" objects into them.

You can convert to a String by doing something like this, if I'm understanding the documentation correctly:

1
2
3
std::string cplusplus_string = "This is a string";
String ardString = String(cplusplus_string.c_str());
Serial.print(ardString);


or, just:

1
2
std::string cplusplus_string = "This is a string";
Serial.print(cplusplus_string.c_str());

Last edited on
Hello PhilTilson,

It would help if you post what code you have. Right now everyone is guessing at what you might have done. Also it is hard to correct or explain what is not seen.

"std::string" is a class which comes from the header file "<string>". If you have a problem using "std::cout" with a "std""string" or using it in other ways there is a good chance that you did not include the header file "<string>". Posting your code would help track down problems.

Hope that helps,

Andy
I do understand what you are saying! The reason I haven't posted any code (so far) is because there is rather a lot of it! I know what it's like trying to wade through pages of other people's code to try to find a problem. However, I'll try to give you just the relevant bits.

This routine gets the time from the NTP server:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
String getTimeFromServer() {

  WiFi.hostByName(ntpServerName, timeServerIP);

  sendNTPpacket(timeServerIP); // send an NTP packet to a time server
  delay(1000);  // wait to see if a reply is available
  int cb = udp.parsePacket();

  if (!cb) {
    Serial.println("no packet yet");
    return "";
  }
  else {
    Serial.print("packet received, length=");
    Serial.println(cb);
    // We've received a packet, read the data from it
    udp.read(packetBuffer, NTP_PACKET_SIZE); // read the packet into the buffer

    //the timestamp starts at byte 40 of the received packet and is four bytes,
    // or two words, long. First, esxtract the two words:

    unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
    unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
    unsigned long secsSince1900 = highWord << 16 | lowWord;
    const unsigned long seventyYears = 2208988800UL;
    unsigned long epoch = secsSince1900 - seventyYears;
    time_t secs = epoch;
    tm *t = localtime(&secs);
    char genout[20], ser[32];
    sprintf(ser,"%10d ", secs);
    sprintf(genout, "%02d/%02d/%02d  %02d:%02d:02d  ", t->tm_year, t->tm_mday, t->tm_mon + 1, t->tm_hour, t->tm_min, t->tm_sec);
    strcat(ser, genout);
    return ser;
  }
}


This code is largely 'borrowed' from another source and works perfectly, returning an Arduino String in the form:
"uuuuuuuuuu dd/mm hh/nn " - eg "1551381607 28/02 19:20 "

I am then trying to use Repeater's suggested code (above) to set the RTC. The problem here is that he quite reasonably specified a template:

std::string line{ "1551346610 01/02 10/02" };

and I run into problems when I try to substitute my String.

I realise that this is probably a trivial problem for C++ enthusiasts but I am still working my way through the labyrinth of the language!

My thanks also to Ganado but, as you can see, what I really need is the reverse of what was suggested.
Handling date and time values is never trivial. Some languages like to do things a certain way; others have a lot of junk under the hood to deal with variations and the like.

Fortunately, it appears that the string you are given is all you need. The very first number is simply the Unix Epoch time converted to a decimal. The other four numbers are repeated information.

In other words, "1551381607" is the date and time "28/02 19:20". Better even — it also tells you the year and seconds:

    1551381607 → 02/28/19 19:20:07 Z 

I do not know what value the function RTC.adjust() is expected to take, but I am going to presume (being an Arduino, which likes to keep things simple) that it simply takes the Unix Epoch time value. Check your documentation to be sure.

If that is true, then all you need to do is convert the string digits into a number, and pass it along:

1
2
  std::string time_from_server = "1551381607 28/02 19:20 ";
  RTC.adjust( (std::time_t)std::stoull( time_from_server ) );


If you want to get a printout like I listed, C++ provides an easy way to do this as well:

1
2
3
  std::string time_from_server = "1551381607 28/02 19:20 ";
  std::time_t t = std::stoull( time_from_server );
  std::cout << std::put_time( std::gmtime( &t ), "%c Z" ) << "\n";


BTW, Repeater’s example is incorrect. The leading number does not match the day and time he listed after it. Also, be sure to use the correct string→int function. Above I err on the side of too big.

Hope this helps.
The plot thickens!

I have simplified things considerably and am now using the following code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
void setTime() {

  String dtg = "";

  dtg = getTimeFromServer();
  Serial.println(dtg);
  
  for (int i = 1; i < 10; i++) {
   
    if (dtg == "") {
      delay (1000);  
      dtg = getTimeFromServer();
      Serial.println(dtg);
    }
    else {
      i=10;
    }
  }

  if (dtg == "") {
    Serial.println ("Unable to get time from server!");
  }
  else {    
  // Set time

  String dDate = dtg.substring(11,21);
  String dTime = dtg.substring(23,31);
  Serial.println(dDate);
  Serial.println(dTime);
  
  RTC.adjust(DateTime(dDate.c_str(), dTime.c_str()));
  }
}


This ALMOST works! When I run it, the RTC shows the correct TIME perfectly - but the DATE is some 23 years ahead of now! And the epoch time that is recalculated is a large negative number. For example:

1551482032 01-03-2019 23:13:52
01-03-2019
23:13:52
-2017811655 27/02/2042 23:14:01

The first line shows the string obtained from the getTimeFromServer routine. This is correct.

The second and third lines show the values of dDate and dTime, which are then input to the RTC.adjust call.

The fourth line is the date and time now received from the RTC.

As you can see, the time element is correct (allowing for my keyboard time etc) but the date is way out.

I remain baffled!

I do too.

You made me go look up the documentation.
https://github.com/adafruit/RTClib
https://www.arduino.cc/reference/en/language/variables/data-types/stringobject/

The very first constructor for a DateTime takes the Unix Epoch time. Life is easy.

1
2
3
4
  String dtg = getTimeFromServer();
  int n = dtg.indexOf( ' ' );
  if (n < 0) n = dtg.length();
  RTC.adjust( DateTime( dtg.substring( 0, n ).toInt() ) );

All done!
Brilliant! Thank you!

I had tried to get my head around the library, but I was looking at the RTCLib fork from RTClib and couldn't work out the best way to do things.

Your suggestion works perfectly - and so simple, too.

That's not to say I'm not grateful for the other suggestions: each has given me new information and - hopefully - widened my knowledge.

Time to get a good book on C++ I think! :-))
an Arduino String in the form: "uuuuuuuuuu dd/mm hh/nn " - eg "1551381607 28/02 19:20 "


That example doesn't match the format. Asking questions accurately is a key skill. If the format is actually "uuuuuuuuuu dd/mm hh:mm" and you ask for help working with "uuuuuuuuuu dd/mm hh/nn", you're going to get answers that don't quite do what you need.
Last edited on
When I first posted the question, I was simply trying to indicate the content of the string that I was starting with. To me, to say "uuuuuuuuuu dd/mm hh:mm" is confusing - common sense says that the first mm is month and the second minutes, but it shouldn't be down to common sense! I used "uuuuuuuuuu dd/mm hh/nn" because it avoids that confusion.

The format string was merely offered as an explanation as to the contents; it didn't refer to any particular function, routine or parameters. I thought my meaning was obvious, which was why I said "...and dd,mm,hh,nn are obvious"! I'll try harder next time. :-)


@Repeater
Why are you getting upset?

@PhilTilson
Reading source code is a bit of a skill that comes from doing it and familiarity with the language. So, no worries. It’ll come with just a little time.

One thing: programming an Arduino is a bit different than standard C++. The language itself is very similar (if a little pared-back from current C++17 standards). But the standard Arduino library is a different beast than the Standard Library, owing to size and complexity.

:O)
I used "uuuuuuuuuu dd/mm hh/nn" because it avoids that confusion.


The common convention in datetime formats is to use MM for month and mm for minute. Not knowing that is no crime, of course. "nn" was far more confusing than just using "mm" twice, though.

How come you changed : to / in the hh:mm ?

@Duthomhas
This is text interface. You've mistaken terseness for being upset. I'm more exasperated than anything; changing a specific format of string, and then asking for help parsing it effectively guarantees that any code written for the format presented won't work on the actual format.
Last edited on
@Repeater
Nah, he gave you two examples, and —even if one was incorrect— it is more than enough to design something that will match the input.

The common convention in datetime formats is to use MM for month and mm for minute. Not knowing that is no crime, of course. "nn" was far more confusing than just using "mm" twice, though.
No, “nn” is far more common —and standard across languages— than what you suggest. I suspect it is because it was introduced by Borland about thirty years ago. http://docs.embarcadero.com/products/rad_studio/delphiAndcpp2009/HelpUpdate2/EN/html/delphivclwin32/System__TDateTime__FormatString.html

The reality, however, is that it doesn’t matter. When presented with something you didn’t recognize your response was to become defensive instead of presuming that the OP might actually be giving you good technical information.

How come you changed : to / in the hh:mm ?
Maybe he shouldn’t have in this specific instance; maybe it could be presented either way. You are guessing to prefer only one possibility.

Many, many date/time input routines will accept multiply-delimited objects when parsing data. Date/time handling is much more complicated than people typically believe, and, unfortunately, varied enough that without some external information* about a string it is impossible to do better than make a guess.
*The external information is typically forced: routines reject data that does not meet acceptable inputs. Either that or they require additional flags to select between responses, such as: Will this assume month or day comes first?

This is text interface. You've mistaken terseness for being upset. I'm more exasperated than anything; changing a specific format of string, and then asking for help parsing it effectively guarantees that any code written for the format presented won't work on the actual format.
Exasperation is a form of upset, which you have been demonstrating. Believe it or not, text responses, while often failing to convey a lot of information present in face-to-face speech, is not totally devoid of contextual emotional markers.

I said nothing until you posted after PhilTilson indicated his issue was happily solved to complain at him. Before that you demonstrated only minor dismay at the (lack of) information he was giving you. You even properly asked for clarifications, and formulated a decent solution using what information you had available. Good and reasonable. Until you bit back.

I’ll even admit it now, I’m being a jerk to point it out. At this point I assume OP will leave with a bit of a bitter taste no matter what. You’re usually much more relaxed. I hope everything is going well with you. (My health is not great, so I’ve been doing poorly, alas. I think it shows.)

[edit]
It is right of you to point out that representing information incorrectly (/ vs :) is incongruous with useful help on a forum, though.
Last edited on
Pages: 12