Как разобрать 20180810T143000Z в time_t
Каков самый короткий/элегантный способ (т. е. использовать существующие функции библиотеки) для анализа строки в форме 20180810T143000Z
в time_t
? Обратите внимание, что литерал всегда представляет собой временную метку UTC.
Я начал анализировать строку и присваивать значения struct tm *tm
, чтобы в конце выполнить mktime(tm)
. Однако это кажется слишком сложным.
@Marcel Stör, 👍1
Обсуждение7 ответов
Лучший ответ:
Разбор строки — единственный способ. Однако есть много способов сделать это.
Мой предпочтительный метод — сначала проверить правильность формата, проверив правильность расположения букв 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()
.
@Juraj Да, но ты не включил в свой ответ никакого ответа, только блок кода;), @Majenko
«сначала проверьте правильность формата» — верный пункт, но что бы вы сделали в ветке «else»?, @Marcel Stör
Я не могу вам этого сказать. Это зависит от остальной части вашей программы. Ответом может быть «ничего», «сообщить пользователю», «запросить новую временную метку» или что-то еще., @Majenko
@Маженко, извини, я немного неконкретно выразился. Я все еще знакомлюсь с Arduino (как вы понимаете). Я имел в виду, что в большинстве других сред/платформ, с которыми я знаком, я, вероятно, выдал бы исключение. Итак, я считаю, что возврат -1 и обработка этого вне функции сделают это более надежным., @Marcel Stör
@MarcelStör Конечно, если ты этого хочешь., @Majenko
#include <Time.h>
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 Т
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 в диспетчере библиотек. Он работает на всех платформах Arduino.
Да, я изучал strptime
, но пришел к выводу, что без разделителей он, вероятно, не будет работать. Поэтому я даже не пытался., @Marcel Stör
В buff[i++] * 10 + buff[i++]
порядок вычисления i++
не указан. Он может работать должным образом с любой конкретной комбинацией версии и настроек компилятора и не работать при следующей. gcc 5.4 предупреждает меня, что «операция над 'i' может быть неопределенной». Правильный способ сделать это: tm.Month = buff[i++] * 10; tm.Month += buff[i++];
., @Edgar Bonet
@EdgarBonet, спасибо, я только что предложил это изменение: https://arduino.stackexchange.com/review/suggested-edits/38715, @Marcel Stör
@MarcelStör, я прочитал комментарий Эдгара, но этот код не какой-то фрагмент, а полноценный рабочий пример. Мне не нравится редактировать это без тестирования. И я не могу это проверить сейчас., @Juraj
@MarcelStör: То, что я написал для tm.Month
, справедливо для каждого случая, когда i++
встречается более одного раза в одном и том же выражении., @Edgar Bonet
@EdgarBonet Я знаю, это имеет смысл. Вот почему предложил это редактирование. В конце концов, получить отказ было нормально, потому что на самом деле это затронуло гораздо больше строк., @Marcel Stör
в этом случае использование i++ хорошо. у компилятора нет причин вычислять какое-то подвыражение с i++ позже в выражении перед некоторым подвыражением с i++ ранее в выражении. Я изменю это. Я видел предупреждение, но у меня было на это ограниченное время., @Juraj
Что касается «компилятору нет причин оценивать [...]»: у него также нет причин оценивать их в том порядке, в котором вы ожидаете! Пожалуйста, узнайте о точках последовательности в C++, прежде чем делать такие неверные предположения. Обратите внимание, что «между предыдущей и следующей точкой последовательности сохраненное значение объекта должно быть изменено не более одного раза при вычислении выражения»., @Edgar Bonet
Для полноты картины мой «ответ» работает с String
, а не с 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);
}
Эта бедная, бедная куча. Я сочувствую этому..., @Majenko
Это должно работать, но учтите, что каждый вызов метода substring() выделяет в куче новую строку. Даже если у вас много свободной памяти, множественные вызовы malloc() и free() могут сделать это совершенно неэффективным., @Edgar Bonet
@EdgarBonet да, я знаю, точка зрения принята. В эту функцию передается (частичный) результат HTTP-ответа readStringUntil()
. Я думаю, было бы более эффективно сначала преобразовать «calTimestamp» в «char[]»?, @Marcel Stör
Вполне вероятно. Метод String c_str()
возвращает внутренний указатель char *
, поэтому никакого реального преобразования не требуется., @Edgar Bonet
Без sscanf это будет невозможно.
Я взял скетч @Juraj и объявил отдельные целые числа, чтобы быть уверенным, что каждый %d будет соответствовать целому числу.
#include <Time.h>
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() {
}
Марсель Штёр, теперь есть четыре хороших решения. На мой взгляд, они одинаково хороши.
Альтернативой моему первому ответу было бы использование стандартных функций C из time.h и sscanf. Функция C strptime не может анализировать временную метку без разделителей. Но sscanf может проанализировать ваш ввод.
#include <time.h>
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.
%d означает целое число, а также для микроконтроллера avr. Разница в том, что TimeElements TimeLib использует не целые числа, а байты., @Jot
@Jot, это время C.h, @Juraj
Да, для C time.h tm_year и другие элементы являются целыми числами. Также для микроконтроллера avr %d соответствует целому числу. %d ожидает «int», а не 2- или 4-байтовую переменную., @Jot
%d на AVR работает для sprintf, но sscanf читается неправильно без hh. попробуй, @Juraj
Я пробовал это разными способами с разными данными в стеке, используя Arduino 1.8.5 с платой Arduino Uno. «int» — это два байта, «short int» — тоже два байта, а «short short int» не существует. Формат «%d» работает с «int», а «%hd», похоже, делает то же самое, что и «%d». Формат «%hhd» читает байт со знаком. Все так, как я помню, никаких странностей. Если вы сможете доказать свою правоту с помощью эскиза, можете ли вы создать для этого новую тему?, @Jot
@Jot, это происходит только тогда, когда я использую члены структуры tm в качестве параметров sscanf. Предупреждение: "%d" ожидает аргумент типа "int*", но аргумент 4 имеет тип "int8_t* {он же знаковый символ*}". и отсканированные значения неверны. При использовании обычной переменной int hh не требуется., @Juraj
AVR имеет int8_t tm_hour
, @Juraj
Теперь я вижу это: tm для avr имеет элементы int8_t и int16_t. Извините, это действительно не все целые числа для avr. Я не знал этого. Спасибо. Согласны ли вы, что %d в sscanf соответствует «int» для avr? Я вижу, что вы удалили это запутанное последнее предложение., @Jot
Я использовал hh, потому что компилятор предупреждал, что это int8_t, и это помогло получить правильные данные. Я открыл time.h, но он был для samd или для esp и там был int. У меня было ограниченное время, поэтому я не стал исследовать этот вопрос дальше. Конечно, %d предназначен для int. Я проверил int и удалил заметку. Потом я обнаружил, что в tm действительно есть unt8_t в tm., @Juraj
Вот еще один способ преобразовать строку отметки времени в time_t. Здесь уже есть несколько отличных ответов, но вы можете сравнить размеры двоичных файлов. Размер этого скетча составляет 3884 байта (версия IDE 1.0.6.2, GCC 4.2.1).
#include <Time.h>
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');
// Собираем элементы времени в time_t.
time_t t = makeTime(myTimeElements);
// Распечатываем содержимое "t" по одному "куску" за раз, используя функции "time_t".
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(){}
String datetimestr = "20180810T143000Z";
int splitted = datetimestr.indexOf("T");
String datestr = datetimestr.substring(0, splitted); //20180810
String timestr = datetimestr.substring(splitted + 1, datetimestr.length() - 1); //143000
Вопрос заключался в том, как проанализировать строку ** до time_t
**, и вы не ответили на него., @Edgar Bonet
- Как преобразовать полезную нагрузку byte* в строку
- Пиринговая коммуникация
- Как связаться с ESP8266 ESP01, отправив данные через программный сериал на Arduino Uno?
- ESP8266: ошибка: 'getLocalTime' was not declared in this scope
- Как найти разницу между двумя timestamp
- Установить time() на ESP8266
- ESP8266 ISO 8601 string to tm struct
- Каков идеальный способ проверить, готово ли время на ESP8266 через NTP?
Какую библиотеку времени вы используете? Я копировал каждый необходимый символ с индексом массива в буфер и конвертировал его в год, месяц, день, часы, минуты. После этого я бы использовал библиотеку, чтобы преобразовать это в time_t для количества секунд с 1970 года. На мой взгляд, это не слишком сложно. Можете ли вы показать, что у вас есть на данный момент? sscanf может работать, но некоторые считают, что это не элегантная функция., @Jot