diff --git a/.codespellrc b/.codespellrc index 101edae..22d70c2 100644 --- a/.codespellrc +++ b/.codespellrc @@ -1,7 +1,7 @@ # See: https://github.com/codespell-project/codespell#using-a-config-file [codespell] # In the event of a false positive, add the problematic word, in all lowercase, to a comma-separated list here: -ignore-words-list = , +ignore-words-list = leapyears, check-filenames = check-hidden = skip = ./.git diff --git a/NTPClient.cpp b/NTPClient.cpp index b435855..43eaf3f 100755 --- a/NTPClient.cpp +++ b/NTPClient.cpp @@ -21,6 +21,27 @@ #include "NTPClient.h" +const NTPClient::DateLanguageData NTPClient::EnglishData = { + {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}, + {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}, + {"Jan", "Feb", "Mar", "Apr", "May", "June", "July", "Aug", "Sept", "Oct", "Nov", "Dec"}, + {"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"} +}; + +const NTPClient::DateLanguageData NTPClient::SpanishData = { + {"Dom", "Lun", "Mart", "Miérc", "Juev", "Vier", "Sáb"}, + {"Domingo", "Lunes", "Martes", "Miércoles", "Jueves", "Viernes", "Sábado"}, + {"ene", "feb", "mar", "abr", "mayo", "jun", "jul", "ago", "sept", "oct", "nov", "dic"}, + {"enero", "febrero", "marzo", "abril", "mayo", "junio", "julio", "agosto", "septiembre", "octubre", "noviembre", "diciembre"} +}; + +const NTPClient::DateLanguageData NTPClient::PortugueseData = { + {"Dom", "Seg", "Ter", "Qua", "Qui", "Sex", "Sáb"}, + {"Domingo", "Segunda-feira", "Terça-feira", "Quarta-feira", "Quinta-feira", "Sexta-feira", "Sábado"}, + {"jan", "fev", "mar", "abr", "maio", "jun", "jul", "ago", "set", "out", "nov", "dez"}, + {"janeiro", "fevereiro", "março", "abril", "maio", "junho", "julho", "agosto", "setembro", "outubro", "novembro", "dezembro"} +}; + NTPClient::NTPClient(UDP& udp) { this->_udp = &udp; } @@ -126,6 +147,16 @@ bool NTPClient::update() { return false; // return false if update does not occur } +// Function to find language data by code +const NTPClient::DateLanguageData* NTPClient::findLanguageData(const String& code) const { + for (int i = 0; i < languageMapSize; ++i) { + if (code == languageMap[i].code) { + return languageMap[i].data; + } + } + return &EnglishData; // Default to English if not found +} + bool NTPClient::isTimeSet() const { return (this->_lastUpdate != 0); // returns true if the time has been set, else false } @@ -136,31 +167,112 @@ unsigned long NTPClient::getEpochTime() const { ((millis() - this->_lastUpdate) / 1000); // Time since last update } +NTPClient::FullDateComponents NTPClient::calculateFullDateComponents() const { + unsigned long epochTime = this->getEpochTime(); + long days = epochTime / 86400L; // Total days since epoch + int year = 1970; + + while (days > 365) { + if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) { + if (days >= 366) { + days -= 366; + year++; + } else { + break; // Leap year but not enough days to complete the year + } + } else { + days -= 365; + year++; + } + } + + int dayOfYear = static_cast(days) + 1; // +1 to convert from 0-based to 1-based + bool thisYearIsLeap = (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)); + int daysInMonth[12] = {31, 28 + thisYearIsLeap, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + + int month = 0; + for (month = 0; month < 12; month++) { + if (dayOfYear <= daysInMonth[month]) { + break; // Found the current month + } + dayOfYear -= daysInMonth[month]; + } + + return {year, month + 1, dayOfYear}; // Month is 1-based +} + +int NTPClient::getYear() const { + FullDateComponents dateComponents = calculateFullDateComponents(); + return dateComponents.year; +} + +int NTPClient::getMonth() const { + FullDateComponents dateComponents = calculateFullDateComponents(); + return dateComponents.month; +} + int NTPClient::getDay() const { - return (((this->getEpochTime() / 86400L) + 4 ) % 7); //0 is Sunday + FullDateComponents dateComponents = calculateFullDateComponents(); + return dateComponents.day; +} + +int NTPClient::getDayOfWeek() const { + return (((this->getEpochTime() / 86400L) + 4 ) % 7); // 0 is Sunday } + int NTPClient::getHours() const { return ((this->getEpochTime() % 86400L) / 3600); } + int NTPClient::getMinutes() const { return ((this->getEpochTime() % 3600) / 60); } + int NTPClient::getSeconds() const { return (this->getEpochTime() % 60); } -String NTPClient::getFormattedTime() const { - unsigned long rawTime = this->getEpochTime(); - unsigned long hours = (rawTime % 86400L) / 3600; - String hoursStr = hours < 10 ? "0" + String(hours) : String(hours); - - unsigned long minutes = (rawTime % 3600) / 60; - String minuteStr = minutes < 10 ? "0" + String(minutes) : String(minutes); - - unsigned long seconds = rawTime % 60; - String secondStr = seconds < 10 ? "0" + String(seconds) : String(seconds); - - return hoursStr + ":" + minuteStr + ":" + secondStr; +String NTPClient::getFormattedDateTime(const String& format) { + String result; + bool escape = false; + + const DateLanguageData* langData = findLanguageData(this->_dateLanguage); + + for (char c : format) { + if (c == '%') { + if (escape) { + result += c; // Literal '%' character + escape = false; + } else { + escape = true; + } + continue; + } + + if (escape) { + switch (c) { + case 'Y': result += String(this->getYear()); break; + case 'y': result += String(this->getYear()).substring(2); break; + case 'm': result += (this->getMonth() < 10 ? "0" : "") + String(this->getMonth()); break; + case 'd': result += (this->getDay() < 10 ? "0" : "") + String(this->getDay()); break; + case 'H': result += (this->getHours() < 10 ? "0" : "") + String(this->getHours()); break; + case 'M': result += (this->getMinutes() < 10 ? "0" : "") + String(this->getMinutes()); break; + case 'S': result += (this->getSeconds() < 10 ? "0" : "") + String(this->getSeconds()); break; + case 'a': result += langData->shortWeekDays[this->getDayOfWeek()]; break; + case 'A': result += langData->longWeekDays[this->getDayOfWeek()]; break; + case 'w': result += String(this->getDayOfWeek()); break; + case 'b': result += langData->shortMonths[this->getMonth() - 1]; break; + case 'B': result += langData->longMonths[this->getMonth() - 1]; break; + case 'p': result += (this->getHours() < 12 ? "AM" : "PM"); break; // Note: Consider locale for AM/PM + default: result += "%" + String(c); // Unsupported format code + } + escape = false; + } else { + result += c; + } + } + + return result; } void NTPClient::end() { @@ -170,15 +282,19 @@ void NTPClient::end() { } void NTPClient::setTimeOffset(int timeOffset) { - this->_timeOffset = timeOffset; + this->_timeOffset = timeOffset; } void NTPClient::setUpdateInterval(unsigned long updateInterval) { this->_updateInterval = updateInterval; } +void NTPClient::setDateLanguage(const String &dateLanguage) { + this->_dateLanguage = dateLanguage; +} + void NTPClient::setPoolServerName(const char* poolServerName) { - this->_poolServerName = poolServerName; + this->_poolServerName = poolServerName; } void NTPClient::sendNTPPacket() { diff --git a/NTPClient.h b/NTPClient.h index a31d32f..14cf488 100755 --- a/NTPClient.h +++ b/NTPClient.h @@ -11,6 +11,7 @@ class NTPClient { private: UDP* _udp; + String _dateLanguage = "en"; // Default language bool _udpSetup = false; const char* _poolServerName = "pool.ntp.org"; // Default time server @@ -27,6 +28,39 @@ class NTPClient { void sendNTPPacket(); + struct DateLanguageData { + const char* shortWeekDays[7]; + const char* longWeekDays[7]; + const char* shortMonths[12]; + const char* longMonths[12]; + }; + + struct FullDateComponents { + int year; + int month; + int day; + }; + + // Language map + struct LanguageMap { + const char* code; + const DateLanguageData* data; + }; + + static const DateLanguageData EnglishData; + static const DateLanguageData SpanishData; + static const DateLanguageData PortugueseData; + + const LanguageMap languageMap[3] = { + {"en", &EnglishData}, + {"es", &SpanishData}, + {"pt", &PortugueseData} + }; + + const int languageMapSize = sizeof(languageMap) / sizeof(languageMap[0]); + const DateLanguageData* findLanguageData(const String& code) const; + FullDateComponents calculateFullDateComponents() const; + public: NTPClient(UDP& udp); NTPClient(UDP& udp, long timeOffset); @@ -81,7 +115,10 @@ class NTPClient { */ bool isTimeSet() const; + int getDayOfWeek() const; int getDay() const; + int getMonth() const; + int getYear() const; int getHours() const; int getMinutes() const; int getSeconds() const; @@ -98,9 +135,28 @@ class NTPClient { void setUpdateInterval(unsigned long updateInterval); /** - * @return time formatted like `hh:mm:ss` + * @return Date Time string formatted. The available format codes are: + %Y: Full year (e.g., 2023) + %y: Last two digits of the year (e.g., 23 for 2023) + %m: Month as a zero-padded decimal number (01 to 12) + %d: Day of the month as a zero-padded decimal number (01 to 31) + %H: Hour (00 to 23) as a zero-padded decimal number + %M: Minute as a zero-padded decimal number (00 to 59) + %S: Second as a zero-padded decimal number (00 to 59) + %a: Abbreviated weekday name according to the current locale + %A: Full weekday name according to the current locale + %w: Weekday as a decimal number (0 for Sunday through 6 for Saturday) + %b: Abbreviated month name according to the current locale + %B: Full month name according to the current locale + %p: "AM" or "PM" based on the hour (Note: This is locale-sensitive and might not be applicable in all languages) + */ + String getFormattedDateTime(const String &format); + + /** + * Set language for displaying date. Available languages are 'pt', 'es' and 'en' (default) + * @param dateLanguage */ - String getFormattedTime() const; + void setDateLanguage(const String &dateLanguage); /** * @return time in seconds since Jan. 1, 1970 diff --git a/examples/Advanced/Advanced.ino b/examples/Advanced/Advanced.ino index 18a6a97..bb21b16 100644 --- a/examples/Advanced/Advanced.ino +++ b/examples/Advanced/Advanced.ino @@ -30,8 +30,7 @@ void setup(){ void loop() { timeClient.update(); - - Serial.println(timeClient.getFormattedTime()); - + Serial.println(timeClient.getFormattedDateTime("%d %B %Y")); + Serial.println(timeClient.getFormattedDateTime("%Y-%m-%d %H:%M:%S")); delay(1000); } diff --git a/examples/Basic/Basic.ino b/examples/Basic/Basic.ino index f0a2a7c..71d1857 100644 --- a/examples/Basic/Basic.ino +++ b/examples/Basic/Basic.ino @@ -27,7 +27,7 @@ void setup(){ void loop() { timeClient.update(); - Serial.println(timeClient.getFormattedTime()); + Serial.println(timeClient.getFormattedDateTime("%H:%M:%S")); delay(1000); } diff --git a/examples/FormattedDateTime/FormattedDateTime.ino b/examples/FormattedDateTime/FormattedDateTime.ino new file mode 100644 index 0000000..c513626 --- /dev/null +++ b/examples/FormattedDateTime/FormattedDateTime.ino @@ -0,0 +1,62 @@ +#include +// change next line to use with another board/shield +#include +//#include // for WiFi shield +//#include // for WiFi 101 shield or MKR1000 +#include + +const char *ssid = ""; +const char *password = ""; + +WiFiUDP ntpUDP; + +// You can specify the time server pool and the offset (in seconds, can be +// changed later with setTimeOffset() ). Additionally you can specify the +// update interval (in milliseconds, can be changed using setUpdateInterval() ). +NTPClient timeClient(ntpUDP, "europe.pool.ntp.org", 3600, 60000); + +void setup(){ + Serial.begin(115200); + + WiFi.begin(ssid, password); + + while ( WiFi.status() != WL_CONNECTED ) { + delay ( 500 ); + Serial.print ( "." ); + } + + timeClient.begin(); + timeClient.setDateLanguage("pt"); // Available languages: "pt", "es" and "en" (default) +} + +void loop() { + timeClient.update(); + Serial.print("%Y: "); + Serial.println(timeClient.getFormattedDateTime("%Y")); // Full year (e.g., 2023) + Serial.print("%y: "); + Serial.println(timeClient.getFormattedDateTime("%y")); // Last two digits of the year (e.g., 23 for 2023) + Serial.print("%m: "); + Serial.println(timeClient.getFormattedDateTime("%m")); // Month as a zero-padded decimal number (01 to 12) + Serial.print("%d: "); + Serial.println(timeClient.getFormattedDateTime("%d")); // Day of the month as a zero-padded decimal number (01 to 31) + Serial.print("%H: "); + Serial.println(timeClient.getFormattedDateTime("%H")); // Hour (00 to 23) as a zero-padded decimal number + Serial.print("%M: "); + Serial.println(timeClient.getFormattedDateTime("%M")); // Minute as a zero-padded decimal number (00 to 59) + Serial.print("%S: "); + Serial.println(timeClient.getFormattedDateTime("%S")); // Second as a zero-padded decimal number (00 to 59) + Serial.print("%a: "); + Serial.println(timeClient.getFormattedDateTime("%a")); // Abbreviated weekday name according to the current locale + Serial.print("%A: "); + Serial.println(timeClient.getFormattedDateTime("%A")); // Full weekday name according to the current locale + Serial.print("%w: "); + Serial.println(timeClient.getFormattedDateTime("%w")); // Weekday as a decimal number (0 for Sunday through 6 for Saturday) + Serial.print("%b: "); + Serial.println(timeClient.getFormattedDateTime("%b")); // Abbreviated month name according to the current locale + Serial.print("%B: "); + Serial.println(timeClient.getFormattedDateTime("%B")); // Full month name according to the current locale + Serial.print("%p: "); + Serial.println(timeClient.getFormattedDateTime("%p")); // "AM" or "PM" based on the hour (Note: This is locale-sensitive and might not be applicable in all languages) + Serial.println("-------------------"); + delay(1000); +} diff --git a/examples/IsTimeSet/IsTimeSet.ino b/examples/IsTimeSet/IsTimeSet.ino index 619bfde..b8c2492 100644 --- a/examples/IsTimeSet/IsTimeSet.ino +++ b/examples/IsTimeSet/IsTimeSet.ino @@ -42,7 +42,7 @@ void setup(){ void loop() { timeClient.update(); - Serial.println(timeClient.getFormattedTime()); + Serial.println(timeClient.getFormattedDateTime("%H:%M:%S")); if(timeClient.isTimeSet()) { if (hour == timeClient.getHours() && minute == timeClient.getMinutes()) { digitalWrite(led, 0); diff --git a/keywords.txt b/keywords.txt index edce989..1423bd2 100644 --- a/keywords.txt +++ b/keywords.txt @@ -14,11 +14,15 @@ update KEYWORD2 forceUpdate KEYWORD2 isTimeSet KEYWORD2 getDay KEYWORD2 +getDayOfWeek KEYWORD2 +getDay KEYWORD2 +getMonth KEYWORD2 +getYear KEYWORD2 getHours KEYWORD2 getMinutes KEYWORD2 getSeconds KEYWORD2 -getFormattedTime KEYWORD2 +getFormattedDateTime KEYWORD2 getEpochTime KEYWORD2 setTimeOffset KEYWORD2 setUpdateInterval KEYWORD2 -setPoolServerName KEYWORD2 +setPoolServerName KEYWORD2 \ No newline at end of file