Hoe u 20180810T143000Z naar time_t kunt ontleden

Wat is de kortste/meest elegante manier (dus bestaande lib-functies gebruiken) om een ​​tekenreeks in de vorm van 20180810T143000Z te parseren in een time_t ? Merk op dat het letterlijke altijd een tijdstempel van UTC is.

Ik begon met het ontleden van de string en het toewijzen van waarden aan een struct tm * tm om aan het einde een mktime (tm) te doen. Dat voelt echter te ingewikkeld aan.

0
Welke tijdsbibliotheek gebruikt u? Ik zou elk nodig karakter met een array-index kopiëren naar een buffer en het omzetten naar jaar, maand, dag, uren, minuten. Daarna zou ik de bibliotheek gebruiken om dat aantal te converteren in een time_t voor het aantal seconden sinds 1970. Rechttoe rechtaan is naar mijn mening niet al te gecompliceerd. Kun je laten zien wat je tot nu toe hebt? sscanf zou kunnen werken, maar sommigen denken dat dat geen elegante functie is.
toegevoegd de auteur Standback, de bron

6 antwoord

Parsing the string is echt de enige manier. Er zijn echter veel manieren om het te doen.

Mijn voorkeursmethode is om eerst te controleren of het formaat correct is, door te kijken of de T en Z op de juiste plaats staan:

if (timeString[8] == 'T' && timeString[15] == 'Z') {
    ... parse in here
}

En het ontleden is gewoon cijfers maken met vermenigvuldiging:

int year = (timeString[0] - '0') * 1000 +
           (timeString[1] - '0') * 100 +
           (timeString[2] - '0') * 10 +
           (timeString[3] - '0');

Je kunt dingen opruimen met een macro als je wilt:

#define NUM(off, mult) ((timeString[(off)] - '0') * (mult))

Dan:

int year =   NUM(0, 1000) + NUM(1, 100) + NUM(2, 10) + NUM(3, 1);
int month =  NUM(4, 10)   + NUM(5, 1);
int day =    NUM(6, 10)   + NUM(7, 1);
int hour =   NUM(9, 10)   + NUM(10, 1);
int minute = NUM(11, 10)  + NUM(12, 1);
int second = NUM(13, 10)  + NUM(14, 1);

En dan, ja, plaats ze in een struct tm (of geef ze direct berekeningsresultaten zonder de tussenliggende variabelen te gebruiken) en bel mktime() .

2
toegevoegd
@ MarcelStör Sure - als dat is wat u wilt doen.
toegevoegd de auteur Majenko, de bron
Dat kan ik je niet vertellen. Het hangt van de rest van je programma af. Het antwoord kan "niets" zijn, of het kan zijn "vertel de gebruiker", het "vraag een nieuw tijdstempel aan", of een aantal andere dingen.
toegevoegd de auteur Majenko, de bron
@Juraj Ja, maar je hebt geen enkel antwoord in je antwoord opgenomen, alleen een blok code;)
toegevoegd de auteur Majenko, de bron
@Majenko sorry, ik was een beetje onspecifiek. Ik word nog steeds vertrouwd met Arduino (zoals je kunt zien). Wat ik bedoelde was dat in de meeste andere omgevingen/platforms die ik ken, ik waarschijnlijk een uitzondering zou werpen. Dus, ik geloof dat het teruggeven van -1 en dit buiten de functie omgaat dit robuuster zal maken.
toegevoegd de auteur Krunal Hingu, de bron
"controleer eerst of het formaat correct is" - een geldig punt, maar wat zou u doen in de vertakking anders ?
toegevoegd de auteur Krunal Hingu, de bron

Hier is nog een andere manier om de tijdstempelreeks in time_t om te zetten. Er zijn hier al verschillende geweldige antwoorden, maar u wilt misschien binaire bestandsgroottes vergelijken. Deze schets is 3884 bytes (IDE versie 1.0.6.2, GCC 4.2.1).

#include 
TimeElements myTimeElements;
char timeString[] = "20180810T143000Z";

void setup(){

  Serial.begin(9600);

  myTimeElements.Year = CalendarYrToTm((timeString[0] - '0') * 1000 + (timeString[1] - '0') * 100 + (timeString[2] - '0') * 10 + (timeString[3] - '0'));
  myTimeElements.Month = (timeString[4] - '0') * 10 + (timeString[5] - '0');
  myTimeElements.Day = (timeString[6] - '0') * 10 + (timeString[7] - '0');
  myTimeElements.Hour = (timeString[9] - '0') * 10 + (timeString[10] - '0');
  myTimeElements.Minute = (timeString[11] - '0') * 10 + (timeString[12] - '0');
  myTimeElements.Second = (timeString[13] - '0') * 10 + (timeString[14] - '0');

 //Assemble time elements into time_t.
  time_t t = makeTime(myTimeElements);

 //Print out the contents of "t" one "piece" at a time using the "time_t" functions.
  Serial.println(year(t));
  Serial.println(month(t));
  Serial.println(day(t));
  Serial.println(hour(t));
  Serial.println(minute(t));
  Serial.println(second(t));

}

void loop(){}
1
toegevoegd

Dit is niet compleet zonder sscanf.

Ik nam de schets door @Juraj en verklaarde afzonderlijke integers om er zeker van te zijn dat elke% d zou overeenkomen met een geheel getal.

#include 

void setup() {

  Serial.begin(115200);

  char buff[] = "20180810T143000Z";

  TimeElements tm;

  int yr, mnth, d, h, m, s;
  sscanf( buff, "%4d%2d%2dT%2d%2d%2dZ", &yr, &mnth, &d, &h, &m, &s);

  tm.Year = yr - 1970;
  tm.Month = mnth;
  tm.Day = d;
  tm.Hour = h;
  tm.Minute = m;
  tm.Second = s;

  time_t t = makeTime(tm);

  sprintf(buff, "%02d-%02d-%02d %02d:%02d:%02d", year(t), month(t), day(t), hour(t), minute(t), second(t));

  Serial.println(buff);
}

void loop() {
}

Marcel Stör, er zijn nu vier goede oplossingen. Naar mijn mening zijn ze even goed.

1
toegevoegd
#include 

void setup() {

  Serial.begin(115200);

  char buff[] = "20180810T143000Z";
  for (int i = 0; i < sizeof(buff); i++) {
    buff[i] = buff[i] - '0';
  }
  int yr = buff[0] * 1000 + buff[1] * 100 + buff[2] * 10 + buff[3];
  if (yr > 99)
    yr = yr - 1970;
  else
    yr += 30;
  TimeElements tm;
  tm.Year = yr;
  tm.Month = buff[4] * 10 + buff[5];
  tm.Day = buff[6] * 10 + buff[7];
 //8 T
  tm.Hour = buff[9] * 10 + buff[10];
  tm.Minute = buff[11] * 10 + buff[12];
  tm.Second = buff[13] * 10 + buff[14];

  time_t t = makeTime(tm);

  sprintf(buff, "%02d%02d%02d %02d%02d%02d", year(t), month(t), day(t), hour(t), minute(t), second(t));

  Serial.println(buff);

}

void loop() {

}

U kunt TimeLib installeren in Library Manager. Het werkt op alle Arduino-platforms.

1
toegevoegd
In buff [i ++] * 10 + buff [i ++] is de volgorde van evaluatie van de i ++ niet gespecificeerd. Het werkt mogelijk zoals verwacht met elke specifieke combinatie van compilerversie en instellingen, en faalt bij de volgende. gcc 5.4 waarschuwt me dat "bewerking op 'i' mogelijk ongedefinieerd is". De juiste manier om dat te doen is tm.Month = buff [i ++] * 10; tm.Month + = buff [i ++]; .
toegevoegd de auteur Sprogz, de bron
@ MarcelStör: Wat ik schreef voor tm.Month is geldig voor elk exemplaar waarin i ++ meerdere keren voorkomt in dezelfde expressie.
toegevoegd de auteur Sprogz, de bron
Bij "de compiler heeft geen reden om te evalueren [...]": het heeft ook geen reden om ze te evalueren in de volgorde die u verwacht! Leer alsjeblieft over sequentiepunten in C ++ voordat je zulke verkeerde aannames maakt. Merk in het bijzonder op dat "Tussen het vorige en het volgende volgordepunt een object zijn opgeslagen waarde maximaal eenmaal zal laten modificeren door de evaluatie van een uitdrukking."
toegevoegd de auteur Sprogz, de bron
veranderde i ++ in constanten. (Jaar rang nummer 4. yabba dabba doo)
toegevoegd de auteur Juraj, de bron
in dit geval is het gebruik van i ++ goed. de compiler heeft geen reden om enige subexpressie te evalueren met i ++ later in expressie vóór enige subexpressie met i ++ eerder in expressie. Ik zal het veranderen. Ik zag de waarschuwing, maar ik had hier weinig tijd voor.
toegevoegd de auteur Juraj, de bron
@ MarcelStör, ik las de opmerking van Edgar, maar deze code is geen fragment, maar een volledig werkend voorbeeld. Ik vind het niet leuk om het te bewerken zonder te testen. En ik kan het nu niet testen.
toegevoegd de auteur Juraj, de bron
@ EdgarBonet Ik weet het, het is logisch. Dat is waarom die bewerking is voorgesteld. Uiteindelijk was het ok om te worden afgewezen omdat veel meer regels daadwerkelijk worden beïnvloed.
toegevoegd de auteur Krunal Hingu, de bron
@ EdgarBonet bedankt, ik heb zojuist deze wijziging voorgesteld: arduino.stackexchange.com/review/suggested-edits/38715
toegevoegd de auteur Krunal Hingu, de bron
Ja, ik heb strptime bekeken maar kwam tot de conclusie dat dit waarschijnlijk niet zou werken zonder begrenzers. Daarom heb ik het zelfs niet geprobeerd.
toegevoegd de auteur Krunal Hingu, de bron

Alternatief voor mijn eerste antwoord zou zijn om standaard C-functies te gebruiken van time.h en sscanf. C-functie strptime kan de tijdstempel niet ontleden zonder scheidingstekens. Maar sscanf kan uw input ontleden.

#include 

void setup() {

  Serial.begin(115200);

  const char* buff = "20180810T143000Z";

  tm tms;
  sscanf(buff, "%04d%02d%02dT%02d%02d%02d", &(tms.tm_year), &(tms.tm_mon), &(tms.tm_mday), &(tms.tm_hour), &(tms.tm_min), &(tms.tm_sec));
  tms.tm_year -= 1900;
  tms.tm_mon -= 1;
  tms.tm_isdst = 0;
  time_t t = mktime(&tms);

  Serial.println(ctime(&t));
}

void loop() {
}

In AVR moet "% 02hhd" worden gebruikt omdat de overeenkomstige leden in struct tm int8_t zijn.

1
toegevoegd
Het% d is voor een geheel getal, ook voor een avr microcontroller. Het verschil is dat de TimeLib TimeElements geen gehele getallen, maar bytes gebruiken.
toegevoegd de auteur Standback, de bron
Ja, voor de C-tijd.h zijn het tm_year en andere elementen allemaal gehele getallen. Ook voor een avr microcontroller komt de% d overeen met een geheel getal. Het% d verwacht een 'int', geen 2 of 4 byte variabele.
toegevoegd de auteur Standback, de bron
Ik heb het op verschillende manieren geprobeerd met verschillende gegevens op de stapel, met arduino 1.8.5 met arduino uno board. Een 'int' is twee bytes, een 'short int' is ook twee bytes en 'short short int' bestaat niet. Formaat "% d" werkt met een 'int' en "% hd" lijkt hetzelfde te doen als "% d". Formaat "% hhd" leest een ondertekende byte. Alles is zoals ik het me herinner, geen rare dingen. Als je kunt bewijzen dat je gelijk hebt met een schets, kun je daar een nieuw onderwerp voor beginnen?
toegevoegd de auteur Standback, de bron
Nu zie ik het, de tm voor avr heeft int8_t en int16_t elementen. Sorry, ze zijn inderdaad niet alle gehele getallen voor avr. Dat wist ik niet. Bedankt. Bent u het ermee eens dat% d in sscanf overeenkomt met 'int' voor avr? Ik zie dat je die verwarrende laatste zin hebt verwijderd.
toegevoegd de auteur Standback, de bron
% d op AVR werkt voor sprintf, maar sscanf leest foutloos zonder hh. probeer het
toegevoegd de auteur Juraj, de bron
@Jot, dit is C time.h
toegevoegd de auteur Juraj, de bron
@Jot, alleen als ik de tm struct-leden gebruik als sscanf-parameters. De waarschuwing is '% d' verwacht een argument van het type 'int *', maar argument 4 heeft het type 'int8_t * {aka signed char *}'. en de gescande waarden kloppen niet. Met de reguliere int variabele is de hh niet nodig.
toegevoegd de auteur Juraj, de bron
AVR heeft int8_t tm_hour
toegevoegd de auteur Juraj, de bron
Ik gebruikte hh omdat er een compilerwaarschuwing is die zegt dat het int8_t is en het heeft geholpen om correcte gegevens te krijgen. Ik opende time.h, maar het was voor samd of voor esp en er was int. Ik had een beperkte tijd, dus ik heb het niet verder onderzocht dan. Natuurlijk is% d voor int. Ik heb int getest en de notitie verwijderd. Toen ontdekte ik dat in tm echt unt8_t in tm is.
toegevoegd de auteur Juraj, de bron

Voor de volledigheid is hier mijn "antwoord" dat werkt met String s in plaats van char [] .

time_t convertToTime(String calTimestamp) {
  struct tm tm;
  Serial.println("Parsing " + calTimestamp);
  String year = calTimestamp.substring(0, 4);
  String month = calTimestamp.substring(4, 6);
  if (month.startsWith("0")) {
    month = month.substring(1);
  }
  String day = calTimestamp.substring(6, 8);
  if (day.startsWith("0")) {
    month = day.substring(1);
  }
  tm.tm_year = year.toInt() - 1900;
  tm.tm_mon = month.toInt() - 1;
  tm.tm_mday = day.toInt();
  tm.tm_hour = calTimestamp.substring(9, 11).toInt();
  tm.tm_min = calTimestamp.substring(11, 13).toInt();
  tm.tm_sec = calTimestamp.substring(13, 15).toInt();
  return mktime(&tm);
}
1
toegevoegd
Zeer waarschijnlijk. De String-methode c_str() retourneert de interne char * -aanwijzer, dus er is geen echte conversie nodig.
toegevoegd de auteur Sprogz, de bron
Dit zou moeten werken, maar merk op dat elke aanroep van de methode substring() een nieuwe tekenreeks op de heap toewijst. Zelfs als u voldoende gratis geheugen hebt, kunnen de meerdere aanroepen van malloc() en gratis() dit behoorlijk inefficiënt maken.
toegevoegd de auteur Sprogz, de bron
Die arme arme hoop. Ik voel er voor ...
toegevoegd de auteur Majenko, de bron
@ EdgarBonet Ja, ik weet het, genomen. Wat in deze functie wordt ingevoerd, is het (gedeeltelijke) resultaat van een HTTP-reactie readStringUntil() . Ik denk dat het efficiënter zou zijn om eerst calTimestamp in een char [] te converteren?
toegevoegd de auteur Krunal Hingu, de bron