20180810T143000Z'nin time_t ile ayrıştırılması

20180810T143000Z biçiminde bir dizgiyi time_t biçiminde ayrıştırmak için en kısa/en zarif yöntem (yani, mevcut lib işlevlerini kullanın) nedir? Değişmez her zaman bir UTC zaman damgasını gösterir.

Sonunda bir mktime (tm) yapmak için dizeyi ayrıştırmaya ve değerleri struct tm * tm değerlerine atamaya başladım. Yine de aşırı derecede karmaşık geliyor.

0
Hangi zaman kütüphanesini kullanıyorsunuz? Bir dizi indisli her gerekli karakteri bir tampon belleğe kopyalar ve yıl, ay, gün, saat, dakikaya dönüştürürdüm. Bundan sonra, kütüphaneyi 1970'den beri saniyeler içinde bir time_t dosyasına dönüştürmek için kullanırdım. Açıkçası bence aşırı karmaşık değil. Şu ana kadar neyin olduğunu gösterebilir misin? sscanf işe yarayabilir, ancak bazıları bunun zarif bir işlev olmadığını düşünüyor.
katma yazar Standback, kaynak

6 cevap

Dizeyi gerçekten ayrıştırmak tek yoldur. Ancak bunu yapmanın birçok yolu vardır.

Tercih ettiğim yöntem, ilk önce T ve Z'nin doğru yerde olup olmadığına bakarak formatın doğru olup olmadığını kontrol etmektir:

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

Ve ayrıştırma sadece çarpma ile sayılar yapıyor:

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

İsterseniz bir makro ile işleri temizleyebilirsiniz:

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

Sonra:

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);

Ve sonra, evet, onları bir struct tm içine koyun (veya ara değişkenleri kullanmadan doğrudan hesaplama sonuçlarını atayın) ve mktime() öğesini çağırın.

2
katma
@ MarcelStör Tabii - eğer yapmak istediğin buysa.
katma yazar Majenko, kaynak
Bunu sana söyleyemem. Programınızın geri kalanına bağlı. Bu sorunun cevabı "hiçbir şey" olabilir veya "kullanıcıya söyle" olabilir, "yeni bir zaman damgası ister" veya herhangi bir sayıda başka şey olabilir.
katma yazar Majenko, kaynak
@Juraj Evet, ama cevabınıza herhangi bir cevap eklemediniz, sadece bir kod bloğu;)
katma yazar Majenko, kaynak
@Majenko üzgünüm, biraz belirsizdim. Hala Arduino ile tanıştım (söylediğin gibi). Demek istediğim, diğer ortamlarda/platformlarda aşina olduğum şey muhtemelen bir istisna atacağımdı. Bu yüzden -1 döndürmenin ve bunu fonksiyonun dışında ele almanın bunu daha sağlam yapacağına inanıyorum.
katma yazar Krunal Hingu, kaynak
"ilk önce biçimin doğru olduğunu kontrol edin" - geçerli bir nokta ancak else dalında ne yapardınız?
katma yazar Krunal Hingu, kaynak

İşte zaman damgası dizesini time_t dosyasına dönüştürmenin başka bir yolu. Burada zaten birkaç harika cevap var, ancak ikili dosya boyutlarını karşılaştırmak isteyebilirsiniz. Bu çizim 3884 bayttır (IDE Sürüm 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
katma

Bu sscanf olmadan tamamlanmadı.

Krokiyi @Juraj tarafından aldım ve her% d'nin bir tamsayı ile eşleşeceğinden emin olmak için ayrı tam sayılar bildirdim.

#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, şimdi dört iyi çözüm var. Bence onlar eşit derecede iyi.

1
katma
#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() {

}

TimeLib'i Kütüphane Yöneticisine kurabilirsiniz. Tüm Arduino platformlarında çalışır.

1
katma
buff [i ++] * 10 + buff [i ++] öğesinde, i ++ değerlendirme sırası belirtilmedi. Herhangi bir derleyici sürüm ve ayar kombinasyonuyla beklendiği gibi çalışabilir ve bir sonrakinde başarısız olabilir. gcc 5.4, “ben üzerindeki işlemin tanımsız olabileceği” konusunda uyardı. Bunu yapmanın doğru yolu: tm.Month = buff [i ++] * 10; tm.Month + = buff [i ++]; .
katma yazar Sprogz, kaynak
@ MarcelStör: tm.Month için yazdıklarım, i ++ 'nin aynı ifadede birden fazla göründüğü her örnek için geçerlidir.
katma yazar Sprogz, kaynak
Re "derleyici değerlendirmek için bir neden yok [...]": ayrıca beklediğiniz sırada onları değerlendirmek için hiçbir sebep yok! Lütfen, böyle yanlış varsayımlarda bulunmadan önce C ++ 'daki sıralama noktalarını öğrenin. Özel olarak, “Önceki ve sonraki sıra noktası arasında, bir nesnenin bir ifadenin değerlendirilmesiyle en çok bir defa değiştirilmiş saklanan değerine sahip olacağını” unutmayın.
katma yazar Sprogz, kaynak
i ++ değişmez olarak değiştirildi. (Yıl rütbe # 4. Yabba dabba doo)
katma yazar Juraj, kaynak
Bu durumda i ++ kullanımı iyidir. derleyicinin ifadede i ++ ile bazı ifadelerde daha önce ifadesinde i ++ ile ifadede bazı alt ifadeleri değerlendirmek için bir nedeni yoktur. Değiştireceğim. Uyarıyı gördüm ama bunun için sınırlı zamanım vardı.
katma yazar Juraj, kaynak
@ MarcelStör, Edgar'ın yorumunu okudum, ancak bu kod biraz pasaj değil, tam bir çalışma örneği. Test etmeden düzenlemeyi sevmiyorum. Ve şimdi test edemiyorum.
katma yazar Juraj, kaynak
@EdgarBonet biliyorum, tamamen mantıklı. Bu yüzden bu düzenlemeyi önerdi. Sonunda, reddedilmek tamam oldu, çünkü daha birçok satır gerçekten etkilendi.
katma yazar Krunal Hingu, kaynak
@EdgarBonet teşekkürler, bu değişikliği önerdim: arduino.stackexchange.com/review/suggested-edits/38715
katma yazar Krunal Hingu, kaynak
Evet, strptime 'a baktım ancak sınırlayıcılar olmadan çalışmayacağına karar verdim. Bu yüzden denemedim bile.
katma yazar Krunal Hingu, kaynak

İlk cevabımın alternatifi, time.h ve sscanf adresindeki standart C fonksiyonlarını kullanmak olabilir. C işlevi strptime, sınırlayıcı olmadan zaman damgasını ayrıştıramaz. Fakat sscanf girişinizi ayrıştırır.

#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() {
}

AVR'de "% 02hhd" kullanılmalıdır çünkü yapı tm içindeki ilgili üyeler int8_t'dir.

1
katma
% D, bir avr mikrodenetleyicisi için de bir tamsayı içindir. Fark, TimeLib TimeElements'ın tamsayıları değil, baytları kullanmasıdır.
katma yazar Standback, kaynak
Evet, C time.h için tm_year ve diğer elemanların hepsi tamsayıdır. Ayrıca bir avr mikrokontrolcüsü için% d bir tamsayı ile eşleşir. % D, 2 veya 4 bayt değişken değil, 'int' bekler.
katma yazar Standback, kaynak
Ben arduino uno kurulu ile arduino 1.8.5 kullanarak, yığın üzerinde farklı verilerle birkaç farklı şekilde denedim. Bir 'int' iki bayttır, 'kısa int' de iki bayttır ve 'kısa kısa int' yoktur. "% D" formatı "int" ile çalışıyor ve "% hd" "% d" ile aynı görünüyor. "% Hhd" formatı işaretli bir baytı okur. Her şey hatırladığım gibi tuhaf şeyler değil. Eskizle doğru olduğunuzu kanıtlayabilirseniz, bunun için yeni bir konu başlatabilir misiniz?
katma yazar Standback, kaynak
Şimdi anlıyorum, avr için tm int8_t ve int16_t elemanlarına sahip. Üzgünüz, aslında avr için tamsayı değil. Bunu bilmiyordum. Teşekkürler. Sscanf'ta% d'nin avr için 'int' ile eşleştiğini kabul ediyor musunuz? Sanırım kafa karıştırıcı son cümleyi karıştırdın.
katma yazar Standback, kaynak
AVR'deki% d sprintf için çalışıyor, ancak sscanf hh olmadan yanlış okunuyor. dene
katma yazar Juraj, kaynak
@Jot, bu C time.h
katma yazar Juraj, kaynak
@Jot, sadece tm struct üyelerini sscanf parametreleri olarak kullandığım zamandı. Uyarı '% d', 'int *' türündeki argümanı bekler, ancak 4 argümanı 'int8_t * {aka signed char *}' türündedir. ve taranan değerler yanlış. Düzenli int değişkeni ile hh gerekli değildir.
katma yazar Juraj, kaynak
AVR int8_t tm_hour değerine sahiptir
katma yazar Juraj, kaynak
Hh kullandım çünkü derleyici uyarısı int8_t olduğunu ve doğru veri elde edilmesine yardımcı olduğunu söyledi. Time.h'yi açtım, fakat Samd veya esp içindi ve int de oldu. Sınırlı bir zamanım vardı, bu yüzden daha fazla araştırmamıştım. Tabii ki% d int içindir. İnt test ettim ve notu çıkardım. Sonra tm'de tm'de gerçekten de 8_t olduğunu keşfettim.
katma yazar Juraj, kaynak

Tamlık uğruna, işte benim "cevap" benim char [] yerine String s.

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
katma
Kuvvetle muhtemel. c_str() String yöntemi, iç char * işaretçisini döndürür, böylece gerçek bir dönüşüm gerekmez.
katma yazar Sprogz, kaynak
Bunun çalışması gerekir, ancak substring() yöntemine yapılan her çağrının öbek üzerinde yeni bir Dize ayırdığını unutmayın. Bol miktarda boş belleğiniz olsa bile, malloc() ve free() 'a yapılan çoklu aramalar bunu oldukça verimsiz kılabilir.
katma yazar Sprogz, kaynak
O zavallı fakir yığın. Bunun için hissediyorum.
katma yazar Majenko, kaynak
@EdgarBonet evet, biliyorum, nokta alınmış. Bu işleve verilen şey, readStringUntil() HTTP yanıtının (kısmi) sonucudur. Sanırım ilk önce calTimestampchar [] ' ya dönüştürmek daha etkili olur mu?
katma yazar Krunal Hingu, kaynak