-
Notifications
You must be signed in to change notification settings - Fork 381
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
A suggestion... for getDate, getMonth, getYear, isLeapYear #113
Comments
Thanks marmat123 for this work. |
Runs here with me. But I won't get a chance to watch it until next week. The +20 is ok, because the constant #define SEC20200101 1577750400UL starts with the year 2020 (no NTP server will give you data before 2020) and you have 20 years difference otherwise. You actually inserted the code in both files? |
Yes, i inserted both files. the added part in .h seems ok. include "NTPClient.h"
NTPClient::NTPClient(UDP& udp) {
this->_udp = &udp;
}
NTPClient::NTPClient(UDP& udp, long timeOffset) {
this->_udp = &udp;
this->_timeOffset = timeOffset;
}
NTPClient::NTPClient(UDP& udp, const char* poolServerName) {
this->_udp = &udp;
this->_poolServerName = poolServerName;
}
NTPClient::NTPClient(UDP& udp, const char* poolServerName, long timeOffset) {
this->_udp = &udp;
this->_timeOffset = timeOffset;
this->_poolServerName = poolServerName;
}
NTPClient::NTPClient(UDP& udp, const char* poolServerName, long timeOffset, unsigned long updateInterval) {
this->_udp = &udp;
this->_timeOffset = timeOffset;
this->_poolServerName = poolServerName;
this->_updateInterval = updateInterval;
}
void NTPClient::begin() {
this->begin(NTP_DEFAULT_LOCAL_PORT);
}
void NTPClient::begin(int port) {
this->_port = port;
this->_udp->begin(this->_port);
this->_udpSetup = true;
}
bool NTPClient::forceUpdate() {
#ifdef DEBUG_NTPClient
Serial.println("Update from NTP Server");
#endif
this->sendNTPPacket();
// Wait till data is there or timeout...
byte timeout = 0;
int cb = 0;
do {
delay ( 10 );
cb = this->_udp->parsePacket();
if (timeout > 100) return false; // timeout after 1000 ms
timeout++;
} while (cb == 0);
this->_lastUpdate = millis() - (10 * (timeout + 1)); // Account for delay in reading the time
this->_udp->read(this->_packetBuffer, NTP_PACKET_SIZE);
unsigned long highWord = word(this->_packetBuffer[40], this->_packetBuffer[41]);
unsigned long lowWord = word(this->_packetBuffer[42], this->_packetBuffer[43]);
// combine the four bytes (two words) into a long integer
// this is NTP time (seconds since Jan 1 1900):
unsigned long secsSince1900 = highWord << 16 | lowWord;
this->_currentEpoc = secsSince1900 - SEVENZYYEARS;
// at the end add:
uint32_t _days2k = (_currentEpoc - SEC20200101) / 86400; // distance to today in days
if (_days2k > DAYSOF4YEARS) { // how many four-year packages + remaining days, from 2050 faster than subtraction on an esp8266 ;)
_year = _days2k / DAYSOF4YEARS;
_days2k = _days2k - _year * DAYSOF4YEARS;
_year *= 4;
}
if (_days2k > DAYSOFAYEAR + 1) { //2020 was a leap year, how many years + remaining days
_days2k--;
while (_days2k > DAYSOFAYEAR) { //three times at most
_days2k -= DAYSOFAYEAR;
_year++;
}
}
else _isLeapYear = true;
//_year += 20; // + 2020
if (_isLeapYear && _days2k > 60) _days2k--; // subtract the possible leap day when February passed
if (_days2k > 334) { _month = 12; _date = _days2k - 334; } //boring but quick
else if (_days2k > 304) { _month = 11; _date = _days2k - 304; }
else if (_days2k > 273) { _month = 10; _date = _days2k - 273; }
else if (_days2k > 243) { _month = 9; _date = _days2k - 243; }
else if (_days2k > 212) { _month = 8; _date = _days2k - 212; }
else if (_days2k > 181) { _month = 7; _date = _days2k - 181; }
else if (_days2k > 151) { _month = 6; _date = _days2k - 151; }
else if (_days2k > 120) { _month = 5; _date = _days2k - 120; }
else if (_days2k > 90) { _month = 4; _date = _days2k - 90; }
else if (_days2k > 59) { _month = 3; _date = _days2k - 59; }
else if (_days2k > 31) { _month = 2; _date = _days2k - 31; }
else { _month = 1; _date = _days2k; }
return true;
}
// and then you can add:
bool NTPClient::isLeapYear() const {
return this->_isLeapYear;
}
int NTPClient::getYear() const {
return this->_year;
}
int NTPClient::getMonth() const {
return this->_month;
}
int NTPClient::getDate() const {
return this->_date;
}
bool NTPClient::update() {
if ((millis() - this->_lastUpdate >= this->_updateInterval) // Update after _updateInterval
|| this->_lastUpdate == 0) { // Update if there was no update yet.
if (!this->_udpSetup) this->begin(); // setup the UDP client if needed
return this->forceUpdate();
}
return true;
}
unsigned long NTPClient::getEpochTime() const {
return this->_timeOffset + // User offset
this->_currentEpoc + // Epoc returned by the NTP server
((millis() - this->_lastUpdate) / 1000); // Time since last update
} |
I'll post the library here as it runs on my ESP8266. The data that is generated is correct here. I add 20 years to it, because I consider the year 2020 as the year zero and use the difference to compensate for the error. Otherwise, today would be the year 0 and not 20. There is no need, because 2000 was also a leap year (2000 mod 400 = 0) but it saves some unnecessary computing time, because no year before 2020 is possible as result. It is "only" about the conversion of current and later timestamps. If you have changed the library in any other way, you have to check if maybe this is the reason. This is not my construction site. If you have a difference of 20 years that can be corrected by omitting lines, that's ok for you too. NTPClient.h #pragma once
#include "Arduino.h"
#include <Udp.h>
#define SEVENZYYEARS 2208988800UL
#define DAYSOF4YEARS 1461UL
#define DAYSOFAYEAR 365UL
#define SEC20200101 1577750400UL
#define NTP_PACKET_SIZE 48
#define NTP_DEFAULT_LOCAL_PORT 1337
class NTPClient {
private:
UDP* _udp;
bool _udpSetup = false;
const char* _poolServerName = "pool.ntp.org"; // Default time server
int _port = NTP_DEFAULT_LOCAL_PORT;
long _timeOffset = 0;
unsigned long _updateInterval = 60000; // In ms
unsigned long _currentEpoc = 0; // In s
unsigned long _lastUpdate = 0; // In ms
uint16_t _daysSincs2020 = 0; // In d
byte _packetBuffer[NTP_PACKET_SIZE];
uint16_t _date = 0;
uint16_t _month = 0;
uint16_t _year = 0;
bool _isLeapYear = false;
void sendNTPPacket();
public:
NTPClient(UDP& udp);
NTPClient(UDP& udp, long timeOffset);
NTPClient(UDP& udp, const char* poolServerName);
NTPClient(UDP& udp, const char* poolServerName, long timeOffset);
NTPClient(UDP& udp, const char* poolServerName, long timeOffset, unsigned long updateInterval);
/**
* Set time server name
*
* @param poolServerName
*/
void setPoolServerName(const char* poolServerName);
/**
* Starts the underlying UDP client with the default local port
*/
void begin();
/**
* Starts the underlying UDP client with the specified local port
*/
void begin(int port);
/**
* This should be called in the main loop of your application. By default an update from the NTP Server is only
* made every 60 seconds. This can be configured in the NTPClient constructor.
*
* @return true on success, false on failure
*/
bool update();
/**
* This will force the update from the NTP Server.
*
* @return true on success, false on failure
*/
bool forceUpdate();
/**
* the following methods are reasonably available after the call of forceUpdate and are explained by the name
*/
bool isLeapYear() const;
int getYear() const;
int getMonth() const;
int getDate() const;
int getDay() const; // Day of the Week, Sunday = 0
int getHours() const;
int getMinutes() const;
int getSeconds() const;
/**
* Changes the time offset. Useful for changing timezones dynamically
*/
void setTimeOffset(int timeOffset);
/**
* Set the update interval to another frequency. E.g. useful when the
* timeOffset should not be set in the constructor
*/
void setUpdateInterval(unsigned long updateInterval);
/**
* @return time formatted like `hh:mm:ss`
*/
String getFormattedTime() const;
/**
* @return time in seconds since Jan. 1, 1970
*/
unsigned long getEpochTime() const;
/**
* Stops the underlying UDP client
*/
void end();
}; NTPClient.cpp /**
* The MIT License (MIT)
* Copyright (c) 2015 by Fabrice Weinberg
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "NTPClient.h"
NTPClient::NTPClient(UDP& udp) {
this->_udp = &udp;
}
NTPClient::NTPClient(UDP& udp, long timeOffset) {
this->_udp = &udp;
this->_timeOffset = timeOffset;
}
NTPClient::NTPClient(UDP& udp, const char* poolServerName) {
this->_udp = &udp;
this->_poolServerName = poolServerName;
}
NTPClient::NTPClient(UDP& udp, const char* poolServerName, long timeOffset) {
this->_udp = &udp;
this->_timeOffset = timeOffset;
this->_poolServerName = poolServerName;
}
NTPClient::NTPClient(UDP& udp, const char* poolServerName, long timeOffset, unsigned long updateInterval) {
this->_udp = &udp;
this->_timeOffset = timeOffset;
this->_poolServerName = poolServerName;
this->_updateInterval = updateInterval;
}
void NTPClient::begin() {
this->begin(NTP_DEFAULT_LOCAL_PORT);
}
void NTPClient::begin(int port) {
this->_port = port;
this->_udp->begin(this->_port);
this->_udpSetup = true;
}
bool NTPClient::forceUpdate() {
#ifdef DEBUG_NTPClient
Serial.println("Update from NTP Server");
#endif
this->sendNTPPacket();
// Wait till data is there or timeout...
byte timeout = 0;
int cb = 0;
do {
delay ( 10 );
cb = this->_udp->parsePacket();
if (timeout > 100) return false; // timeout after 1000 ms
timeout++;
} while (cb == 0);
this->_lastUpdate = millis() - (10 * (timeout + 1)); // Account for delay in reading the time
this->_udp->read(this->_packetBuffer, NTP_PACKET_SIZE);
unsigned long highWord = word(this->_packetBuffer[40], this->_packetBuffer[41]);
unsigned long lowWord = word(this->_packetBuffer[42], this->_packetBuffer[43]);
unsigned long secsSince1900 = highWord << 16 | lowWord; // combine the four bytes (two words) into a long integer
this->_currentEpoc = secsSince1900 - SEVENZYYEARS;
/* new Code for Date */
uint32_t _days2k = (_currentEpoc - SEC20200101)/86400; // distance to today in days
if (_days2k > DAYSOF4YEARS) { // how many four-year packages + remaining days
_year = _days2k / DAYSOF4YEARS;
_days2k = _days2k - _year * DAYSOF4YEARS;
_year *= 4;
}
if (_days2k > DAYSOFAYEAR + 1) { //2020 was a leap year, how many years + remaining days
_days2k--;
while (_days2k > DAYSOFAYEAR) { //three times at most
_days2k -= DAYSOFAYEAR;
_year++;
}
} else _isLeapYear = true;
_year += 20; // + 2020
if (_isLeapYear && _days2k > 60) _days2k--; // subtract the possible leap day when February passed
if (_days2k > 334) { _month = 12; _date = _days2k - 334; } //boring but quick
else if (_days2k > 304) { _month = 11; _date = _days2k - 304; }
else if (_days2k > 273) { _month = 10; _date = _days2k - 273; }
else if (_days2k > 243) { _month = 9; _date = _days2k - 243; }
else if (_days2k > 212) { _month = 8; _date = _days2k - 212; }
else if (_days2k > 181) { _month = 7; _date = _days2k - 181; }
else if (_days2k > 151) { _month = 6; _date = _days2k - 151; }
else if (_days2k > 120) { _month = 5; _date = _days2k - 120; }
else if (_days2k > 90) { _month = 4; _date = _days2k - 90; }
else if (_days2k > 59) { _month = 3; _date = _days2k - 59; }
else if (_days2k > 31) { _month = 2; _date = _days2k - 31; }
else { _month = 1; _date = _days2k; }
return true;
}
bool NTPClient::update() {
if ((millis() - this->_lastUpdate >= this->_updateInterval) // Update after _updateInterval
|| this->_lastUpdate == 0) { // Update if there was no update yet.
if (!this->_udpSetup) this->begin(); // setup the UDP client if needed
return this->forceUpdate();
}
return true;
}
unsigned long NTPClient::getEpochTime() const {
return this->_timeOffset + // User offset
this->_currentEpoc + // Epoc returned by the NTP server
((millis() - this->_lastUpdate) / 1000); // Time since last update
}
bool NTPClient::isLeapYear() const { return this->_isLeapYear; }
int NTPClient::getYear() const { return this->_year; }
int NTPClient::getMonth() const { return this->_month; }
int NTPClient::getDate() const { return this->_date; }
int NTPClient::getDay() 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;
}
void NTPClient::end() {
this->_udp->stop();
this->_udpSetup = false;
}
void NTPClient::setTimeOffset(int timeOffset) {
this->_timeOffset = timeOffset;
}
void NTPClient::setUpdateInterval(unsigned long updateInterval) {
this->_updateInterval = updateInterval;
}
void NTPClient::setPoolServerName(const char* poolServerName) {
this->_poolServerName = poolServerName;
}
void NTPClient::sendNTPPacket() {
// set all bytes in the buffer to 0
memset(this->_packetBuffer, 0, NTP_PACKET_SIZE);
// Initialize values needed to form NTP request
// (see URL above for details on the packets)
this->_packetBuffer[0] = 0b11100011; // LI, Version, Mode
this->_packetBuffer[1] = 0; // Stratum, or type of clock
this->_packetBuffer[2] = 6; // Polling Interval
this->_packetBuffer[3] = 0xEC; // Peer Clock Precision
// 8 bytes of zero for Root Delay & Root Dispersion
this->_packetBuffer[12] = 49;
this->_packetBuffer[13] = 0x4E;
this->_packetBuffer[14] = 49;
this->_packetBuffer[15] = 52;
// all NTP fields have been given values, now
// you can send a packet requesting a timestamp:
this->_udp->beginPacket(this->_poolServerName, 123); //NTP requests are to port 123
this->_udp->write(this->_packetBuffer, NTP_PACKET_SIZE);
this->_udp->endPacket();
} and finally the added keywords.txt
One request
|
Ok for the request. i didn't know about it. it will be more readable. thanks. well, i compared your code and mine. they are similar. I think i got it. your code start to count days from 2020 01 01. am i right ? i added _year=0; at the beginning of your code and i uncommented _year +=20; as you wrote. |
One more time: I set the year zero to 2020 (actually 31.12.2019) and divide by the days (integer division without remainder):
Then I look if four years have already passed (saves the division with result 0 and is faster but in four years the code will be obsolete):
Then multiples of the four-year interval (1 day more than four one-year intervals) are subtracted and then multiples of the one-year interval and counting how many years this corresponds to. However, only from 2020. Example: 2057 --> difference are 37 years, that is 9*4 + 1 year gives 37 and so you have to add 20 years again. The only advantage is that there is no division for the next three years. But since the units (ESPs) will not run until 2030, that is enough for me. And a saved division is a good division. You could just as well have written the routine like this: while (_days2k > DAYSOF4YEARS) {
_days2k -= DAYSOF4YEARS;
_year++;
} without the if-condition, that would have been more consistent. And then you see that you save five loops when you start in 2020. The declaration and initialization of _year is done in the header file:
|
i agree with you at 100% uint16_t _year = 0 ; is it called only when you create a new time variable like : NTPClient time(.......); ? let's assume _year=20 (from the previous update since it's 2020) |
Ah yes, well, then separate declaration (in the header file) and initialization (in the routine). That makes sense then. This is the more secure variant. I myself do not use the object globally, but only within a routine (and then cpp discards it again) and the need to query the year several times in the net does not arise for my problem. |
Ok, i understand too. Thanks for your help anyway. |
Can someone raise Pull Request for this? Or was it rejected This is needed feature... |
First of all: thanks to the NTPClient author(s) for your helpful library! Of course ;) I also had missed date info (in addition to the time info provided), so I rolled my own. (I had not looked into this repository's issues and pull requests before). The only thing I needed was an ISO date string ("yyyy-mm-dd"), so this is the funtion(s) I came up with for my https://github.com/DE-cr/BSBmonCR v0.4.0 (that version has yet to be uploaded to github - after more tests of its functionality):
Feel free to use/adapt this piece of code should you be interested. FYI: I couldn't get it to fail with the following tests (no price here for beauty/readability, sorry):
|
I don't want to fork, so here:
Since the NTP servers should not provide data before 2020:
Add in NTPClient.h:
Add in NTPClient.cpp:
it works to 2099/12/31 ;-)
You can now use the methods
which makes it easier to set an RTC like the DS3231 without time.h - because for some stupid reason it (DS3231) does not understand a timestamp, but has actually stored the "normal" western date format in its registers.
Remark
There is still potential for optimization, because some divisions are redundant now, if you could evaluate temporary variables for getDay, getHours, getMinutes and getSeconds.
The text was updated successfully, but these errors were encountered: