20180810T143000Zをtime_tに解析する方法

20180810T143000Z の形式の文字列を time_t に解析するための最短/最も簡潔な方法(つまり、既存のlib関数を使用する方法)は何ですか?リテラルは常にUTCタイムスタンプを表すことに注意してください。

最後に mktime(tm)を実行するために、文字列の解析と struct tm * tm への値の割り当てを開始しました。しかし、それは過度に複雑に感じます。

0
追加された 編集された
ビュー: 2
どのタイムライブラリを使いますか?必要な文字を配列のインデックスとともにバッファにコピーし、年、月、日、時、分に変換します。その後、ライブラリを使用して1970年以降の秒数のtime_tに変換します。ストレートフォワードは私の意見ではそれほど複雑ではありません。あなたは今までに持っているものを見せることができますか? sscanfは動作するかもしれませんが、それはエレガントな機能ではないと考える人もいます。
追加された 著者 Standback,

6 答え

文字列を解析することが本当に唯一の方法です。しかしそれをするには多くの方法があります。

TとZが正しい場所にあることを確認して、最初にフォーマットが正しいことを確認することをお勧めします。

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

そして、構文解析は単に乗算を使って数値を作ることです。

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

あなたが好きならあなたはマクロで物事を片付けることができます:

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

その後:

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

そして、はい、それらを struct tm に入れて(または中間変数を使わずに直接計算結果を代入して) mktime()を呼び出します。

2
追加された
@MarcelStörもちろん - それがあなたがやりたいことであれば。
追加された 著者 Majenko,
それがわかりません。それはあなたのプログラムの残りの部分に依存します。答えは「なにもしない」、または「ユーザーに知らせる」、「新しいタイムスタンプを要求する」、またはその他のさまざまなことです。
追加された 著者 Majenko,
@Jurajええ、でもあなたはあなたの答えに何も答えを入れていません。
追加された 著者 Majenko,
@ Majenkoすみません、私は少し不特定だった。私はまだArduinoに慣れ親しんでいます(あなたが言うことができるように)。私が知っていたのは、私が慣れ親しんでいる他のほとんどの環境/プラットフォームではおそらく例外を投げるということです。そのため、-1を返して関数の外側で処理すると、これがより堅牢になります。
追加された 著者 Krunal Hingu,
「最初にフォーマットが正しいことを確認してください」 - 妥当な点ですが、 else ブランチで何をしますか?
追加された 著者 Krunal Hingu,

タイムスタンプ文字列をtime_tに変換するもう1つの方法があります。ここにはすでにいくつかの素晴らしい答えがありますが、バイナリファイルのサイズを比較することをお勧めします。このスケッチは3884バイトです(IDEバージョン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
追加された

これはsscanfなしでは完了しません。

私は@Jurajによるスケッチを取り、すべての%dが整数と一致することを確認するために別々の整数を宣言しました。

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

MarcelStör、4つの優れた解決策があります。私の意見では、それらは等しく良いです。

1
追加された
#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はLibrary Managerにインストールできます。すべてのArduinoプラットフォームで動作します。

1
追加された
buff [i ++] * 10 + buff [i ++] では、 i ++ の評価の順序は規定されていません。それはコンパイラのバージョンと設定のどんな特定の組み合わせでも期待通りに動くかもしれません、そして、次のもので失敗します。 gcc 5.4は、「iに対する操作は未定義の可能性がある」と警告しています。その正しい方法は tm.Month = buff [i ++] * 10です。 tm.Month + = buff [i ++];
追加された 著者 Sprogz,
@MarcelStör:私が tm.Month のために書いたことは、 i ++ が同じ式の中に複数回現れるすべてのインスタンスに対して有効です。
追加された 著者 Sprogz,
「コンパイラには評価する理由はありません[...]」:期待通りの順序で評価する理由もありません。そのような間違った仮定をする前に、C ++のシーケンスポイントについて学んでください。具体的には、「前のシーケンスポイントと次のシーケンスポイントの間では、オブジェクトは格納されている値を式の評価によってせいぜい1回だけ変更する必要がある」
追加された 著者 Sprogz,
i ++を定数に変更しました。 (年ランク4位。yabba dabba doo)
追加された 著者 Juraj,
この場合、i ++の使用は有効です。コンパイラーは、式の中でi ++を使った部分式の前に式の中でi ++を使った部分式を評価する理由はありません。変更します。私は警告を見ました、しかし私はこれのための限られた時間を過ごしました。
追加された 著者 Juraj,
@MarcelStör、Edgarのコメントを読みましたが、このコードは抜粋ではなく、完全な実用例です。テストせずに編集するのは嫌いです。そして今はテストできません。
追加された 著者 Juraj,
@EdgarBonet私は知っていますが、まったく理にかなっています。それがその編集を提案した理由です。結局、もっと多くの行が実際に影響を受けるので、拒否されても大丈夫でした。
追加された 著者 Krunal Hingu,
@EdgarBonetありがとう、私はこの変更を提案しました。 >
追加された 著者 Krunal Hingu,
うん、私は strptime を調べましたが、区切り文字なしではおそらくうまくいかないと結論しました。したがって、私も試しませんでした。
追加された 著者 Krunal Hingu,

私の最初の答えに代わるものはtime.hとsscanfから標準C関数を使うことでしょう。 C関数strptimeは、区切り文字なしでタイムスタンプを解析できません。しかし、sscanfはあなたの入力を解析することができます。

#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では "%02hhd"が使用されなければなりません。なぜなら構造体tm内の対応する要素がint8_tだからです。

1
追加された
%dは整数用で、avrマイクロコントローラ用です。違いは、TimeLib TimeElementsは整数ではなくバイトを使用することです。
追加された 著者 Standback,
はい、Cの時間です。htm_yearと他の要素はすべて整数です。 avrマイクロコントローラの場合も、%dは整数と一致します。 %dは、2バイトまたは4バイトの変数ではなく、 'int'を期待しています。
追加された 著者 Standback,
私はarduino 1.8.5とarduino uno boardを使って、スタック上のデータを変えていろいろな方法で試しました。 「int」は2バイト、「short int」も2バイトで、「short short int」は存在しません。フォーマット "%d"は 'int'で動作し、 "%hd"は "%d"と同じように動作します。フォーマット "%hhd"は符号付きバイトを読み込みます。私がそれを覚えているようにすべて、奇妙なことではありません。スケッチが正しいことを証明できたら、そのための新しいトピックを始めることができますか。
追加された 著者 Standback,
今、私はそれを見て、avrのためのtmはint8_tとint16_t要素を持っています。申し訳ありませんが、これらはavrの整数ではありません。知らなかったよ。ありがとう。 sscanfの%dがavrの 'int'に一致することに同意しますか?私はあなたがその紛らわしい最後の文を削除したのを見ます。
追加された 著者 Standback,
AVRの%dはsprintfに対して機能しますが、sscanfはhhなしで誤って読みます。それを試してみてください
追加された 著者 Juraj,
@Jot、これはCの時間です。h
追加された 著者 Juraj,
@Jot、それは私がsscanfパラメータとしてtm structメンバを使用するときだけです。警告 '%d'は型 'int *'の引数を必要としていますが、引数4は 'int8_t * {別名signed char *}'型です。そしてスキャンされた値は間違っています。通常のint変数の場合、hhは必要ありません。
追加された 著者 Juraj,
AVRは int8_t tm_hour を持っています
追加された 著者 Juraj,
私はhhを使用しました。なぜなら、コンパイラは、それがint8_tであり、正しいデータを取得するのに役立ったと警告しているからです。私はtime.hを開いたが、それはsamdまたはespのためであり、intがあった。私は時間が限られていたので、それ以上調査しませんでした。もちろん%dはintです。私はintをテストしてメモを削除しました。それから、私はtmでは本当にtmのunt8_tであることを発見しました。
追加された 著者 Juraj,

完全を期すために、 char [] ではなく String を使って作業しています。

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
追加された
非常に可能性が高いです。 Stringメソッド c_str()は内部の char * ポインタを返すので、実際の変換は必要ありません。
追加された 著者 Sprogz,
これはうまくいくはずですが、 substring()メソッドを呼び出すたびに、ヒープに新しいStringが割り当てられます。十分な空きメモリがある場合でも、 malloc()free()を複数回呼び出すと、これが非効率的になる可能性があります。
追加された 著者 Sprogz,
あの貧しい貧しいヒープ。私はそれを感じます...
追加された 著者 Majenko,
@EdgarBonetはい、私は知っています、ポイントを取りました。この関数に渡されるのは、HTTPレスポンス readStringUntil()の(部分的な)結果です。最初に calTimestampchar [] に変換するほうが効率的でしょうか。
追加された 著者 Krunal Hingu,