fbiego / ESP32Time

An Arduino library for setting and retrieving internal RTC time on ESP32 boards
MIT License
212 stars 36 forks source link

Daylight saving without NTP (german timezone) #14

Open basementmedia2 opened 2 years ago

basementmedia2 commented 2 years ago

Hi,

first of all: Great library! I live in germany and i want to make a offline clock (without the need to connect to an NTP Server).

Is it possible to also have daylight saving without NTP Server? Is there a possibility to set the timezone?

Best wishes Daniel

fbiego commented 2 years ago

the library does not implement daylight savings for now. the ESP RTC will drift over time and therefore will need to be corrected periodically. I would suggest you build one with BLE functionality so that you can update the time from your phone. see example here, https://github.com/fbiego/ESP32_OLED_BLE

I can try implementing the DST functionality but I don't quite understand how it works, if you can explain more that would be great.

basementmedia2 commented 2 years ago

Hi,

i don't know how it is other countries but in germany

on last sunday of march at 2:00 mez the clock is switched to 3:00 mez on last sunday of october at 3:00 mez the clock is switched back to 2:00 mez

But no problem, i can hardcode this in my Arduino Sketch. But would be a nice feature anyway if you plan to implement it in the future.

Best wishes Daniel

JasonRJ commented 2 years ago

I used this example from Espressif: https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/system_time.html#timezones

setenv("TZ", "EST+5EDT,M3.2.0/2,M11.1.0/2", 1); // America/New_York tzset();

The format of the TZ variable is explained here: https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html

Then I use this function to compute the timezone offset in hours. For Eastern US it is -5 normal time and -4 daylight time.

int timezoneOffset() { time_t current_time = time(nullptr); struct tm tm_current_time = gmtime(&current_time); time_t utc = mktime(tm_current_time); tm_current_time = localtime(&current_time); time_t local = mktime(tm_current_time); auto tz_offset = (float) (difftime(utc, local)); if (tm_current_time->tm_isdst) tz_offset -= 3600; return (int(tz_offset / 3600 -1)); }

ngxson commented 8 months ago

It's true that the timezone stuff is quite complicated to calculate dynamically on the MCU. The reason is because each country kinda have a different "standard", you can search for the tzdata.zi file to know why.

I ended up pre-calculating a lookup table on build time (which can determine the time offset of the next 20 years in the future), then later use it on the MCU.

My code to generate the header file can be found here: https://gist.github.com/ngxson/1c8fb6fbb825b07aab8fe199991ca536

MikeyMoMo commented 4 months ago

Although you said it does not work, and I don't know if it is the way I did it but DST is working for me, here. I will post the source code after the comment. The point of this test code is to go into a GPS program. They emit UTC and I need to change it to some local time. I use the POSIX time strings, such as:

const char* time_zone = "PST8PDT,M3.2.0,M11.1.0";

And the output is great:

Fetched now epoch: 1713805466 Mon Apr 22 10:04:26 2024 isdst? 1 1713805466 Monday, April 22 2024 02:04:26 PDT Monday, April 22 2024 02:04:26 PDT

I am printing it two ways to verify everything is working. I am not applying the DST offset myself and it seems to be OK. You mention, in answer to an earlier post in this thread, that you had not implemented DST. Well, either you did and did not take out that answer or you got it for free via the POSIX time string and the

setenv("TZ", time_zone, 1); tzset(); commands. The next step is to trim out the fluff and fit this into the GPS reader program that I wrote 5 years ago and make it show the correct local time. Doing it for a friend who became interested in it. It does that time conversion now except it cannot show DST. I could do it for the U.S., only, and I have written that code but there are so many other place with different dates and times for the changes that it becomes impossible to write and maintain. So I let the environment TZ string decoding take care of it for me.

Thanks for the library. There are a bunch of commented lines left in from various testing. Just ignore them. For this test, you must set UTC manually and compile. No external hardware is needed and no internet connection is used. It needs to be a close as possible to thinking it is in GPS environment with no outside access.

If there are problems, let me know and I will post updates to this test code if I find anything wrong. I do not plan to work on the library. The author did a good job writing it and I won't be responsible for messing it up! My thanks to the author!!!

Here's how to do it:

#include <ESP32Time.h>

const char* time_zone = "PST8PDT,M3.2.0,M11.1.0";
//const char* time_zone = "PHT-8";

time_t workTime, now;
struct tm* timeinfo;
char cCharWork[200];
char TZid[10];

ESP32Time rtc(0);
//ESP32Time rtc(3600);  // offset in seconds GMT+1
/***************************************************************************/
void setup()
/***************************************************************************/
{
  static const char month_names[] = "JanFebMarAprMayJunJulAugSepOctNovDec";

  // The following are for testing only.
  char MyMonth[5] = "Apr";
  // The following must be set in GMT!!!
  int myMonth, myDOM = 22, myYear = 2024, myHour = 8, myMin = 52, mySec = 0;
  // End of testing lines

  int iOffset, iTempOffset;

  Serial.begin(115200); delay(2000);
  setenv("TZ", time_zone, 1); tzset();

  strftime (cCharWork, sizeof(cCharWork), "%z", localtime(&workTime));
  iTempOffset = atoi(cCharWork);
  // The format of the offset is a little strange.  Here's the decode of it.
  iOffset = (iTempOffset / 100) * 3600 + iTempOffset % 100 * 60;
  Serial.printf("Offset = %+i\r\n", iOffset);
  rtc.offset = iOffset; // change offset value
  strftime (TZid, sizeof(TZid), "%Z", localtime(&workTime));

  // January = 1
  Serial.println("Running from:"); Serial.println(__FILE__);

  //  Serial.println(__DATE__); Serial.println(__TIME__); delay(1000);
  //  sscanf(__DATE__, "%s %d %d", sMyMonth, &myDOM, &myYear);
  //  sscanf(__TIME__, "%2d %*c %2d %*c %2d", &myHour, &myMin, &mySec);

  // Change month abbreviation to month number
  myMonth = (strstr(month_names, MyMonth) - month_names) / 3 + 1;
  Serial.printf("Numeric month: %i\r\n", myMonth);

  Serial.printf("Ready to set UTC: %02i/%02i/%02i  %02i:%02i:%02i\r\n",
                myMonth, myDOM, myYear, myHour, myMin, mySec);

  rtc.setTime(mySec, myMin, myHour, myDOM, myMonth, myYear);

  //rtc.setTime(1609459200);  // 1st Jan 2021 00:00:00
  //rtc.offset = 7200; // change offset value

  /*---------set with NTP---------------*/
  //  configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
  //  struct tm timeinfo;
  //  if (getLocalTime(&timeinfo)){
  //    rtc.setTimeStruct(timeinfo);
  //  }
}

void loop() {
  //  Serial.println(rtc.getTime());          //  (String) 15:24:38
  //  Serial.println(rtc.getDate());          //  (String) Sun, Jan 17 2021
  //  Serial.println(rtc.getDate(true));      //  (String) Sunday, January 17 2021
  //  Serial.println(rtc.getDateTime());      //  (String) Sun, Jan 17 2021 15:24:38
  //  Serial.println(rtc.getDateTime(true));  //  (String) Sunday, January 17 2021 15:24:38
  //  Serial.println(rtc.getTimeDate());      //  (String) 15:24:38 Sun, Jan 17 2021
  //  Serial.println(rtc.getTimeDate(true));  //  (String) 15:24:38 Sunday, January 17 2021
  //
  //  Serial.println(rtc.getMicros());        //  (long)    723546
  //  Serial.println(rtc.getMillis());        //  (long)    723
  //  Serial.println(rtc.getEpoch());         //  (long)    1609459200
  //  Serial.println(rtc.getSecond());        //  (int)     38    (0-59)
  //  Serial.println(rtc.getMinute());        //  (int)     24    (0-59)
  //  Serial.println(rtc.getHour());          //  (int)     3     (1-12)
  //  Serial.println(rtc.getHour(true));      //  (int)     15    (0-23)
  //  Serial.println(rtc.getAmPm());          //  (String)  pm
  //  Serial.println(rtc.getAmPm(true));      //  (String)  PM
  //  Serial.println(rtc.getDay());           //  (int)     17    (1-31)
  //  Serial.println(rtc.getDayofWeek());     //  (int)     0     (0-6)
  //  Serial.println(rtc.getDayofYear());     //  (int)     16    (0-365)
  //  Serial.println(rtc.getMonth());         //  (int)     0     (0-11)
  //  Serial.println(rtc.getYear());          //  (int)     2021
  //
  time(&now);
  Serial.print("Fetched now epoch: "); Serial.println(now);
  timeinfo = localtime(&now); Serial.println(timeinfo);
  Serial.print("isdst? "); Serial.println(timeinfo->tm_isdst);

  // Note: Formating options  http://www.cplusplus.com/reference/ctime/strftime/
  //  (long)    1609459200 epoch without offset
  Serial.println(rtc.getLocalEpoch());
  // (String) returns time with specified format
  Serial.println(rtc.getTime("%A, %B %d %Y %H:%M:%S %Z"));

  struct tm timeinfo = rtc.getTimeStruct();
  Serial.println(&timeinfo, "%A, %B %d %Y %H:%M:%S %Z");   //  (tm struct) Sunday, January 17 2021 07:24:38
  Serial.println("----------------------");
  delay(5000);
}
MikeyMoMo commented 2 weeks ago

I need to amend my previous. DST is working if I use sNTP to get the time but I took that success and moved it to GPS time input and I am not getting DST. Or, If I get it, it applies to UTC as well. I can't get UTC right and localtime right at the same time.

  const char *CAtime_zone = "PST8PDT,M3.2.0,M11.1.0";
  rtcUTC.setTime(mySecond, myMinute, myHour, myDOM, myMonth, myYear);
  setenv("TZ", CAtime_zone, 1); tzset();
  strftime(cCharWork, sizeof(cCharWork), "%z", localtime(&workTime));
  iTempOffset = atoi(cCharWork);
  // The format of the offset is a little strange.  Here's the decode of it.
  iOffset = (iTempOffset / 100) * 3600 + iTempOffset % 100 * 60;
  Serial.printf("Offset = %+i\r\n", iOffset);
  strftime(work_char, sizeof(cCharWork), "%Z", localtime(&workTime));
  Serial.printf("Timezone ID: %s\r\n", work_char);
  rtcLocal.offset = iOffset;  // change offset value

This returns DST as the TZ name and standard time for the epoch and all calls to get hour, minute and second. I am in hopes you can find code to properly implement DST for non-NTP uses. My app is almost complete except for this one missing part.

That environment string says Month 3, second Sunday, go into PDT. Month 11, first Sunday, back to PST.

Thanks, Mike