Daylight Savings Time: A Pain

Hey Folks, just want to bring this to attention see what the work around might be that I am missing.

I am using the time input widget with time zone capability. When I select a time zone of (GMT-6:00) America/Chicago in the mobile app, I get a time zone offset from GMT in seconds of -18,000s. When I do the math, 60s60min6hrs gives me 21,600s, which is an hour off of what the time zone offset should actually be. This error is present in only some of the (GMT-6:00) time zone selections. My best guess is that this is compensated for Daylight Savings Time (DST), but that means my program needs something to know when DST is active in order to compensate.
The unix time I get from the New York Server gives me the local time, not true unix time. So in my program, I adjust for this by finding the difference between the server time zone and the local time zone. Currently in American Central time, I subtract 5 hours in seconds (18,000s) from the local time zone offset of 6 hours, (21,600s). If there is a better way of working around the server time in unix time not adjusting for DST and the time input widget time zones automatically adjusting for DST, let me know. Personally, I think that the unix time stamp provided from the server should be what adjusts for DST, or the time zones should not adjust for DST and be constant.

Currently, all American time zone settings for GMT-6:00 are 18,000s. South American and other ‘forgien’ areas have the ‘correct’ time zone offset of 21,600s.

In this excerpt from my program, I use the Blynk provided handles to parse out the current time and time zone from the timer widget entry:

 // Function called every time time entry changes virtual pin V7 (LightTimeVirtual)
BLYNK_WRITE(L_TIME_V)            
{
  TimeInputParam t(param);                // gather time params
  if(t.hasStartTime())                    // if start time exists
  {
    startTime.hr = t.getStartHour();      // save away hour
    startTime.min = t.getStartMinute();   // save start min
  }                                       // end if start time exists
  else {}                                 // if no start time, do nothing

  if(t.hasStopTime())                     // if stop time exists
  {
    endTime.hr = t.getStopHour();         // save away hour
    endTime.min = t.getStopMinute();      // save away min      
  }                                       // end if stop time exists
  else {}                                 // if no stop time, do nothing
  startTime.tz = t.getTZ_Offset();        // collect time zone
  endTime.tz = t.getTZ_Offset();
  Serial.print("Start TZ:");
  Serial.println(startTime.tz);
  Serial.print("End TZ:");
  Serial.println(endTime.tz);
}                                         // end V7 change function

The prints here provide me with the following when the time widget writes to my virtual pin for the time when the time zone selected is American Central Time or any American local in central time, (GMT-6:00):
image

When the time zone is changed in the time input widget to a different time zone, still denoted by GMT-6:00, the prints provide the following. For this example, I used America/El Salvador as the time zone.
image

Then when the internalRTC pin in Blynk updates, the following code runs to update the software with the current time:

BLYNK_WRITE(InternalPinRTC)
{
  // Process Time
  uint32_t unix = param.asLong();         // collect time from RTC
  Serial.print("Unix:");
  Serial.println(unix);
  Serial.print("Start Time:");
  Serial.println(startTime.tz);
  Serial.print("Offset:");
  Serial.println(NY_SERVER_TZ);
  unix += (startTime.tz - NY_SERVER_TZ);  // refrence to local time, Unix time based on server time & server in New York
  Serial.print("Unix Adjust:");
  Serial.println(unix);
  
  stamp.getDateTime(unix);                // pass unix time to UnixTime.h methods

  // Update Time
  nowTime.hr = stamp.hour;     // convert to hour
  nowTime.min = stamp.minute;  // convert to min
  nowTime.sec = stamp.second;  // convert to sec
  ...

The prints here in this portion of the program show me that the Unix time isn’t being adjusted because the time zone I get from the time input widget is the same as the server time zone, which cancels out the adjustment. This was done with the time zone set to (GMT-6:00) America/Chicago. Note the match between “Unix:” and “Unix Adjust:”
image

It seems my options are to either make the user tell the controller if DST is active or not and adjusting the local time zone, the server time zone (New York), or the input unix time from the server. Is there any way to dynamically pull the time zone from the server so that I can have the adjustment for DST done without having the user tell the controller when DST is active? Any other ideas for how to proceed?

Hardware/Software Specs:
• ESP32 Node MCU-32S on WIFI w/ Dynamic Provisioning
• Android
• New York Server, non-local server
• Blynk 1.0.1

Yes, that’s correct, because GMT (AKA Zulu time) stays unaffected by DST rules, whereas Chicago is currently in DST and therefore 5 hours behind GMT.

The “controller” (Blynk device) can work that out for itself using the ezTime library and the Blynk.sendInternal("rtc", "sync"); command to obtain the current GMT time from the server.

https://docs.blynk.io/en/blynk.edgent/api/timezone-location#tracking-local-time

Pete.

Thanks for the link Pete, I it seems whenever I go digging in the documentation, I find everything EXCEPT what I’m looking for, and that’s just Murphy’s Law.

So I solved my issues by integrating the ezTime library for my program. I then call to the Blynk server to pass the time and timezone rule to my controller using BLYNK_CONNECTED() just like the example you linked did.

// Function called every time connection established
BLYNK_CONNECTED()
{
  Blynk.syncAll();                        // sync all virtual pin values with last server values
  Blynk.sendInternal("utc","time");       // update uC with server time and tz
  Blynk.sendInternal("utc","tz_rule");
}                                         // end when connected 

Which in turn calls to the BLYNK_WRITE(InternalPinUTC) function in my program, where I set my instance of the Timezone member, ‘local’, as the default. Not doing this caused my getOffset to not provide the correct time offset. I then, as the example did, collected the passed paramters by testing the string provided by the Blynk.sendInternal() method. If I was passed the time zone rule in Posix form, I would save this away and also call getOffset(), a method in the ezTime library which provides the offset time for a specific time zone, including compensating for DST based on the Posix rule. Calling it with the time setting operation would not provide the correct values. This value is in minutes, so I quick convert it to seconds so I can process it better. Other than the additon of getOffset(), this is still a mirror of the example.

// Function called every time InternalPinUTC updates (new Unix Time)
BLYNK_WRITE(InternalPinUTC)
{
  local.setDefault();                             // sets as default tz                            
  String cmd = param[0].asStr();                  // collects command
  if(cmd == "tz_rule")                            // if tz rule passed to uC
  {
    String tz_rule = param[1].asStr();            // save rule string
    local.setPosix(tz_rule);                      // set rule in ezTime
    nowTime.tz = -60*local.getOffset();
  }

  else if(cmd == "time")                          // if server time passed to uC
  {
    const uint64_t utc_time = param[1].asLongLong();    // save server connection time
    local.setTime(utc_time);                      // set server time to tz 'local'
    UTC.setTime(utc_time/1000, utc_time%1000);    // set UTC time to server time
  }
}                                                 // end if UTC updates (initial connection)  

Now that I had the time zone offset for the server location, I use this at runtime with the user entered time zone to determine the actual user local time zone. A simple difference is calculated and summed with the Unix time provided by the RTC. From here, I get my current local time, which allows me to control my outputs.

// Function called every time InternalPinRTC updates (new Unix Time)
BLYNK_WRITE(InternalPinRTC)
{ // Process Time
  int32_t unix = param.asLong();         // collect time from RTC
  unix += (startTime.tz - nowTime.tz);
  stamp.getDateTime(unix);                // pass unix time to UnixTime.h methods
  
  // Update Time
  nowTime.hr = stamp.hour;     // convert to hour
  nowTime.min = stamp.minute;  // convert to min
  nowTime.sec = stamp.second;  // convert to sec
  ...

Hope this helps anyone else looking for this stuff. The Gethub for the ezTime library is worth a read, as there is a lot of methods available to make DST handling much easier.
[ezTime Library GetHub](GitHub - ropg/ezTime: ezTime — pronounced “Easy Time” — is a very easy to use Arduino time and date library that provides NTP network time lookups, extensive timezone support, formatted time and date strings, user events, millisecond precision and more.)

Also, check out this library if you need to manipulate unix time values for Blynk, was a great help for me initally. Comments are in Russian in the source code, but I am sure there are other versions out there.
[UnixTime Library GetHub](GitHub - GyverLibs/UnixTime: Конвертер unix time stamp в дату и время и наоборот для Arduino)

2 Likes

Thanks @Schmidy
Your write up and GitHub pointer were helpful to me. I use Blynk for time management but need to back out the DST offset (revert to Standard time) during DST for a very handy tide table calculator library I found. The Tide table calculator hates DST, so the ezTime’s isDST() function is perfect, tells me when to make the correction to undo DST.

Thanks again.