mirror of
https://github.com/esphome/esphome.git
synced 2026-02-08 00:31:58 +00:00
tweak
This commit is contained in:
@@ -2,7 +2,6 @@
|
||||
#include "helpers.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cinttypes>
|
||||
|
||||
namespace esphome {
|
||||
|
||||
@@ -67,58 +66,120 @@ std::string ESPTime::strftime(const char *format) {
|
||||
|
||||
std::string ESPTime::strftime(const std::string &format) { return this->strftime(format.c_str()); }
|
||||
|
||||
bool ESPTime::strptime(const char *time_to_parse, size_t len, ESPTime &esp_time) {
|
||||
uint16_t year;
|
||||
uint8_t month;
|
||||
uint8_t day;
|
||||
uint8_t hour;
|
||||
uint8_t minute;
|
||||
uint8_t second;
|
||||
int num;
|
||||
const int ilen = static_cast<int>(len);
|
||||
|
||||
if (sscanf(time_to_parse, "%04hu-%02hhu-%02hhu %02hhu:%02hhu:%02hhu %n", &year, &month, &day, // NOLINT
|
||||
&hour, // NOLINT
|
||||
&minute, // NOLINT
|
||||
&second, &num) == 6 && // NOLINT
|
||||
num == ilen) {
|
||||
esp_time.year = year;
|
||||
esp_time.month = month;
|
||||
esp_time.day_of_month = day;
|
||||
esp_time.hour = hour;
|
||||
esp_time.minute = minute;
|
||||
esp_time.second = second;
|
||||
} else if (sscanf(time_to_parse, "%04hu-%02hhu-%02hhu %02hhu:%02hhu %n", &year, &month, &day, // NOLINT
|
||||
&hour, // NOLINT
|
||||
&minute, &num) == 5 && // NOLINT
|
||||
num == ilen) {
|
||||
esp_time.year = year;
|
||||
esp_time.month = month;
|
||||
esp_time.day_of_month = day;
|
||||
esp_time.hour = hour;
|
||||
esp_time.minute = minute;
|
||||
esp_time.second = 0;
|
||||
} else if (sscanf(time_to_parse, "%02hhu:%02hhu:%02hhu %n", &hour, &minute, &second, &num) == 3 && // NOLINT
|
||||
num == ilen) {
|
||||
esp_time.hour = hour;
|
||||
esp_time.minute = minute;
|
||||
esp_time.second = second;
|
||||
} else if (sscanf(time_to_parse, "%02hhu:%02hhu %n", &hour, &minute, &num) == 2 && // NOLINT
|
||||
num == ilen) {
|
||||
esp_time.hour = hour;
|
||||
esp_time.minute = minute;
|
||||
esp_time.second = 0;
|
||||
} else if (sscanf(time_to_parse, "%04hu-%02hhu-%02hhu %n", &year, &month, &day, &num) == 3 && // NOLINT
|
||||
num == ilen) {
|
||||
esp_time.year = year;
|
||||
esp_time.month = month;
|
||||
esp_time.day_of_month = day;
|
||||
} else {
|
||||
return false;
|
||||
// Helper to parse exactly N digits, returns false if not enough digits
|
||||
static bool parse_digits(const char *&p, const char *end, int count, uint16_t &value) {
|
||||
value = 0;
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (p >= end || *p < '0' || *p > '9')
|
||||
return false;
|
||||
value = value * 10 + (*p - '0');
|
||||
p++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Helper to check for expected character
|
||||
static bool expect_char(const char *&p, const char *end, char expected) {
|
||||
if (p >= end || *p != expected)
|
||||
return false;
|
||||
p++;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ESPTime::strptime(const char *time_to_parse, size_t len, ESPTime &esp_time) {
|
||||
// Supported formats:
|
||||
// YYYY-MM-DD HH:MM:SS (19 chars)
|
||||
// YYYY-MM-DD HH:MM (16 chars)
|
||||
// YYYY-MM-DD (10 chars)
|
||||
// HH:MM:SS (8 chars)
|
||||
// HH:MM (5 chars)
|
||||
|
||||
const char *p = time_to_parse;
|
||||
const char *end = time_to_parse + len;
|
||||
uint16_t v1, v2, v3, v4, v5, v6;
|
||||
|
||||
// Try date formats first (start with 4-digit year)
|
||||
if (len >= 10 && time_to_parse[4] == '-') {
|
||||
// YYYY-MM-DD...
|
||||
if (!parse_digits(p, end, 4, v1))
|
||||
return false;
|
||||
if (!expect_char(p, end, '-'))
|
||||
return false;
|
||||
if (!parse_digits(p, end, 2, v2))
|
||||
return false;
|
||||
if (!expect_char(p, end, '-'))
|
||||
return false;
|
||||
if (!parse_digits(p, end, 2, v3))
|
||||
return false;
|
||||
|
||||
esp_time.year = v1;
|
||||
esp_time.month = v2;
|
||||
esp_time.day_of_month = v3;
|
||||
|
||||
if (p == end) {
|
||||
// YYYY-MM-DD (date only)
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!expect_char(p, end, ' '))
|
||||
return false;
|
||||
|
||||
// Continue with time part: HH:MM[:SS]
|
||||
if (!parse_digits(p, end, 2, v4))
|
||||
return false;
|
||||
if (!expect_char(p, end, ':'))
|
||||
return false;
|
||||
if (!parse_digits(p, end, 2, v5))
|
||||
return false;
|
||||
|
||||
esp_time.hour = v4;
|
||||
esp_time.minute = v5;
|
||||
|
||||
if (p == end) {
|
||||
// YYYY-MM-DD HH:MM
|
||||
esp_time.second = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!expect_char(p, end, ':'))
|
||||
return false;
|
||||
if (!parse_digits(p, end, 2, v6))
|
||||
return false;
|
||||
|
||||
esp_time.second = v6;
|
||||
return p == end; // YYYY-MM-DD HH:MM:SS
|
||||
}
|
||||
|
||||
// Try time-only formats (HH:MM[:SS])
|
||||
if (len >= 5 && time_to_parse[2] == ':') {
|
||||
if (!parse_digits(p, end, 2, v1))
|
||||
return false;
|
||||
if (!expect_char(p, end, ':'))
|
||||
return false;
|
||||
if (!parse_digits(p, end, 2, v2))
|
||||
return false;
|
||||
|
||||
esp_time.hour = v1;
|
||||
esp_time.minute = v2;
|
||||
|
||||
if (p == end) {
|
||||
// HH:MM
|
||||
esp_time.second = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!expect_char(p, end, ':'))
|
||||
return false;
|
||||
if (!parse_digits(p, end, 2, v3))
|
||||
return false;
|
||||
|
||||
esp_time.second = v3;
|
||||
return p == end; // HH:MM:SS
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ESPTime::increment_second() {
|
||||
this->timestamp++;
|
||||
if (!increment_time_value(this->second, 0, 60))
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// Tests for the POSIX TZ parser implementation
|
||||
// This verifies our custom parser produces identical results to libc's
|
||||
// tzset()/localtime() implementation. The custom parser avoids pulling in scanf (~7.6KB).
|
||||
// Tests for the POSIX TZ parser and ESPTime::strptime implementations
|
||||
// These custom parsers avoid pulling in scanf (~9.8KB on ESP32-IDF).
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <cstdlib>
|
||||
#include <ctime>
|
||||
#include "esphome/components/time/posix_tz.h"
|
||||
#include "esphome/core/time.h"
|
||||
|
||||
namespace esphome::time::testing {
|
||||
|
||||
@@ -721,3 +721,131 @@ TEST(PosixTzParser, DstBoundaryJustBeforeFallBack) {
|
||||
}
|
||||
|
||||
} // namespace esphome::time::testing
|
||||
|
||||
// ============================================================================
|
||||
// ESPTime::strptime tests (replaces sscanf-based parsing)
|
||||
// ============================================================================
|
||||
|
||||
namespace esphome::testing {
|
||||
|
||||
TEST(ESPTimeStrptime, FullDateTime) {
|
||||
ESPTime t{};
|
||||
ASSERT_TRUE(ESPTime::strptime("2026-03-15 14:30:45", 19, t));
|
||||
EXPECT_EQ(t.year, 2026);
|
||||
EXPECT_EQ(t.month, 3);
|
||||
EXPECT_EQ(t.day_of_month, 15);
|
||||
EXPECT_EQ(t.hour, 14);
|
||||
EXPECT_EQ(t.minute, 30);
|
||||
EXPECT_EQ(t.second, 45);
|
||||
}
|
||||
|
||||
TEST(ESPTimeStrptime, DateTimeNoSeconds) {
|
||||
ESPTime t{};
|
||||
ASSERT_TRUE(ESPTime::strptime("2026-03-15 14:30", 16, t));
|
||||
EXPECT_EQ(t.year, 2026);
|
||||
EXPECT_EQ(t.month, 3);
|
||||
EXPECT_EQ(t.day_of_month, 15);
|
||||
EXPECT_EQ(t.hour, 14);
|
||||
EXPECT_EQ(t.minute, 30);
|
||||
EXPECT_EQ(t.second, 0);
|
||||
}
|
||||
|
||||
TEST(ESPTimeStrptime, DateOnly) {
|
||||
ESPTime t{};
|
||||
ASSERT_TRUE(ESPTime::strptime("2026-03-15", 10, t));
|
||||
EXPECT_EQ(t.year, 2026);
|
||||
EXPECT_EQ(t.month, 3);
|
||||
EXPECT_EQ(t.day_of_month, 15);
|
||||
}
|
||||
|
||||
TEST(ESPTimeStrptime, TimeWithSeconds) {
|
||||
ESPTime t{};
|
||||
ASSERT_TRUE(ESPTime::strptime("14:30:45", 8, t));
|
||||
EXPECT_EQ(t.hour, 14);
|
||||
EXPECT_EQ(t.minute, 30);
|
||||
EXPECT_EQ(t.second, 45);
|
||||
}
|
||||
|
||||
TEST(ESPTimeStrptime, TimeNoSeconds) {
|
||||
ESPTime t{};
|
||||
ASSERT_TRUE(ESPTime::strptime("14:30", 5, t));
|
||||
EXPECT_EQ(t.hour, 14);
|
||||
EXPECT_EQ(t.minute, 30);
|
||||
EXPECT_EQ(t.second, 0);
|
||||
}
|
||||
|
||||
TEST(ESPTimeStrptime, Midnight) {
|
||||
ESPTime t{};
|
||||
ASSERT_TRUE(ESPTime::strptime("00:00:00", 8, t));
|
||||
EXPECT_EQ(t.hour, 0);
|
||||
EXPECT_EQ(t.minute, 0);
|
||||
EXPECT_EQ(t.second, 0);
|
||||
}
|
||||
|
||||
TEST(ESPTimeStrptime, EndOfDay) {
|
||||
ESPTime t{};
|
||||
ASSERT_TRUE(ESPTime::strptime("23:59:59", 8, t));
|
||||
EXPECT_EQ(t.hour, 23);
|
||||
EXPECT_EQ(t.minute, 59);
|
||||
EXPECT_EQ(t.second, 59);
|
||||
}
|
||||
|
||||
TEST(ESPTimeStrptime, LeapYearDate) {
|
||||
ESPTime t{};
|
||||
ASSERT_TRUE(ESPTime::strptime("2024-02-29", 10, t));
|
||||
EXPECT_EQ(t.year, 2024);
|
||||
EXPECT_EQ(t.month, 2);
|
||||
EXPECT_EQ(t.day_of_month, 29);
|
||||
}
|
||||
|
||||
TEST(ESPTimeStrptime, NewYearsEve) {
|
||||
ESPTime t{};
|
||||
ASSERT_TRUE(ESPTime::strptime("2026-12-31 23:59:59", 19, t));
|
||||
EXPECT_EQ(t.year, 2026);
|
||||
EXPECT_EQ(t.month, 12);
|
||||
EXPECT_EQ(t.day_of_month, 31);
|
||||
EXPECT_EQ(t.hour, 23);
|
||||
EXPECT_EQ(t.minute, 59);
|
||||
EXPECT_EQ(t.second, 59);
|
||||
}
|
||||
|
||||
TEST(ESPTimeStrptime, EmptyStringFails) {
|
||||
ESPTime t{};
|
||||
EXPECT_FALSE(ESPTime::strptime("", 0, t));
|
||||
}
|
||||
|
||||
TEST(ESPTimeStrptime, InvalidFormatFails) {
|
||||
ESPTime t{};
|
||||
EXPECT_FALSE(ESPTime::strptime("not-a-date", 10, t));
|
||||
}
|
||||
|
||||
TEST(ESPTimeStrptime, PartialDateFails) {
|
||||
ESPTime t{};
|
||||
EXPECT_FALSE(ESPTime::strptime("2026-03", 7, t));
|
||||
}
|
||||
|
||||
TEST(ESPTimeStrptime, PartialTimeFails) {
|
||||
ESPTime t{};
|
||||
EXPECT_FALSE(ESPTime::strptime("14:", 3, t));
|
||||
}
|
||||
|
||||
TEST(ESPTimeStrptime, ExtraCharactersFails) {
|
||||
ESPTime t{};
|
||||
// Full datetime with extra characters should fail
|
||||
EXPECT_FALSE(ESPTime::strptime("2026-03-15 14:30:45x", 20, t));
|
||||
}
|
||||
|
||||
TEST(ESPTimeStrptime, WrongSeparatorFails) {
|
||||
ESPTime t{};
|
||||
EXPECT_FALSE(ESPTime::strptime("2026/03/15", 10, t));
|
||||
}
|
||||
|
||||
TEST(ESPTimeStrptime, LeadingZeroTime) {
|
||||
ESPTime t{};
|
||||
ASSERT_TRUE(ESPTime::strptime("01:05:09", 8, t));
|
||||
EXPECT_EQ(t.hour, 1);
|
||||
EXPECT_EQ(t.minute, 5);
|
||||
EXPECT_EQ(t.second, 9);
|
||||
}
|
||||
|
||||
} // namespace esphome::testing
|
||||
|
||||
Reference in New Issue
Block a user