(HELP) esp8266 weather station time zone fix!!

I've been trying to change the time zone to the correct one for days now. I live in Chicago so if anyone can provide the code to change it to the time in Chicago that would be awesome. Code is provided.

#define TZ              -6       // (utc+) TZ in hours
#define DST_MN          60      // use 60mn for summer time in some countries

#define TZ_MN           ((TZ)*60)
#define TZ_SEC          ((TZ)*3600)
#define DST_SEC         ((DST_MN)*60)
time_t now;

This is a NodeMCU esp8266 weather station.

This is the entire code

#include <Arduino.h>

#include <ESPWiFi.h>
#include <ESPHTTPClient.h>
#include <JsonListener.h>

// time
#include <time.h>                       // time() ctime()
#include <sys/time.h>                   // struct timeval
#include <coredecls.h>                  // settimeofday_cb()

#include "SH1106.h"
#include "OLEDDisplayUi.h"
#include "Wire.h"
#include "OpenWeatherMapCurrent.h"
#include "OpenWeatherMapForecast.h"
#include "WeatherStationFonts.h"
#include "WeatherStationImages.h"

// WIFI
const char* WIFI_SSID = "";
const char* WIFI_PWD = ""

#define TZ              -6       // (utc+) TZ in hours
#define DST_MN          60      // use 60mn for summer time in some countries

// Setup
const int UPDATE_INTERVAL_SECS = 20 * 60; // Update every 20 minutes

// Display Settings
const int I2C_DISPLAY_ADDRESS = 0x3c;
#if defined(ESP8266)
const int SDA_PIN = D2;
const int SDC_PIN = D1;
#else
const int SDA_PIN = 5; //D3;
const int SDC_PIN = 4; //D4;
#endif

String OPEN_WEATHER_MAP_APP_ID = "";

String OPEN_WEATHER_MAP_LOCATION_ID = "";

String OPEN_WEATHER_MAP_LANGUAGE = "en";
const uint8_t MAX_FORECASTS = 4;

const boolean IS_METRIC = false;

const String WDAY_NAMES[] = {"SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"};
const String MONTH_NAMES[] = {"JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"};

 SH1106     display(I2C_DISPLAY_ADDRESS, SDA_PIN, SDC_PIN);
 OLEDDisplayUi   ui( &display );

OpenWeatherMapCurrentData currentWeather;
OpenWeatherMapCurrent currentWeatherClient;

OpenWeatherMapForecastData forecasts[MAX_FORECASTS];
OpenWeatherMapForecast forecastClient;

#define TZ_MN           ((TZ)*60)
#define TZ_SEC          ((TZ)*3600)
#define DST_SEC         ((DST_MN)*60)
time_t now;

bool readyForWeatherUpdate = false;

String lastUpdate = "--";

long timeSinceLastWUpdate = 0;

void drawProgress(OLEDDisplay *display, int percentage, String label);
void updateData(OLEDDisplay *display);
void drawDateTime(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y);
void drawCurrentWeather(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y);
void drawForecast(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y);
void drawForecastDetails(OLEDDisplay *display, int x, int y, int dayIndex);
void drawHeaderOverlay(OLEDDisplay *display, OLEDDisplayUiState* state);
void setReadyForWeatherUpdate();

FrameCallback frames[] = { drawDateTime, drawCurrentWeather, drawForecast };
int numberOfFrames = 3;

OverlayCallback overlays[] = { drawHeaderOverlay };
int numberOfOverlays = 1;

void setup() {
  Serial.begin(115200);
  Serial.println();
  Serial.println();

  // initialize dispaly
  display.init();
  display.clear();
  display.display();

  //display.flipScreenVertically();
  display.setFont(ArialMT_Plain_10);
  display.setTextAlignment(TEXT_ALIGN_CENTER);
  display.setContrast(255);

  WiFi.begin(WIFI_SSID, WIFI_PWD);

  int counter = 0;
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
    display.clear();
    display.drawString(64, 10, "Connecting to WiFi");
    display.drawXbm(46, 30, 8, 8, counter % 3 == 0 ? activeSymbole : inactiveSymbole);
    display.drawXbm(60, 30, 8, 8, counter % 3 == 1 ? activeSymbole : inactiveSymbole);
    display.drawXbm(74, 30, 8, 8, counter % 3 == 2 ? activeSymbole : inactiveSymbole);
    display.display();

    counter++;
  }
  // Get time from network time service
  configTime(TZ_SEC, DST_SEC, "pool.ntp.org");

  ui.setTargetFPS(30);

  ui.setActiveSymbol(activeSymbole);
  ui.setInactiveSymbol(inactiveSymbole);

  ui.setIndicatorPosition(BOTTOM);

  ui.setIndicatorDirection(LEFT_RIGHT);

  ui.setFrameAnimation(SLIDE_LEFT);

  ui.setFrames(frames, numberOfFrames);

  ui.setOverlays(overlays, numberOfOverlays);

  // Inital UI takes care of initalising the display too.
  ui.init();

  Serial.println("");

  updateData(&display);

}

void loop() {

  if (millis() - timeSinceLastWUpdate > (1000L*UPDATE_INTERVAL_SECS)) {
    setReadyForWeatherUpdate();
    timeSinceLastWUpdate = millis();
  }

  if (readyForWeatherUpdate && ui.getUiState()->frameState == FIXED) {
    updateData(&display);
  }

  int remainingTimeBudget = ui.update();

  if (remainingTimeBudget > 0) {
    // You can do some work here
    // Don't do stuff if you are below your
    // time budget.
    delay(remainingTimeBudget);
  }


}

void drawProgress(OLEDDisplay *display, int percentage, String label) {
  display->clear();
  display->setTextAlignment(TEXT_ALIGN_CENTER);
  display->setFont(ArialMT_Plain_10);
  display->drawString(64, 10, label);
  display->drawProgressBar(2, 28, 124, 10, percentage);
  display->display();
}

void updateData(OLEDDisplay *display) {
  drawProgress(display, 10, "Updating time...");
  drawProgress(display, 30, "Updating weather...");
  currentWeatherClient.setMetric(IS_METRIC);
  currentWeatherClient.setLanguage(OPEN_WEATHER_MAP_LANGUAGE);
  currentWeatherClient.updateCurrentById(&currentWeather, OPEN_WEATHER_MAP_APP_ID, OPEN_WEATHER_MAP_LOCATION_ID);
  drawProgress(display, 50, "Updating forecasts...");
  forecastClient.setMetric(IS_METRIC);
  forecastClient.setLanguage(OPEN_WEATHER_MAP_LANGUAGE);
  uint8_t allowedHours[] = {12};
  forecastClient.setAllowedHours(allowedHours, sizeof(allowedHours));
  forecastClient.updateForecastsById(forecasts, OPEN_WEATHER_MAP_APP_ID, OPEN_WEATHER_MAP_LOCATION_ID, MAX_FORECASTS);

  readyForWeatherUpdate = false;
  drawProgress(display, 100, "Done...");
  delay(1000);
}



void drawDateTime(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) {
  now = time(nullptr);
  struct tm* timeInfo;
  timeInfo = localtime(&now);
  char buff[16];


  display->setTextAlignment(TEXT_ALIGN_CENTER);
  display->setFont(ArialMT_Plain_10);
  String date = WDAY_NAMES[timeInfo->tm_wday];

  sprintf_P(buff, PSTR("%s, %02d/%02d/%04d"), WDAY_NAMES[timeInfo->tm_wday].c_str(), timeInfo->tm_mday, timeInfo->tm_mon+1, timeInfo->tm_year + 1900);
  display->drawString(64 + x, 5 + y, String(buff));
  display->setFont(ArialMT_Plain_24);

  sprintf_P(buff, PSTR("%02d:%02d:%02d"), timeInfo->tm_hour, timeInfo->tm_min, timeInfo->tm_sec);
  display->drawString(64 + x, 15 + y, String(buff));
  display->setTextAlignment(TEXT_ALIGN_LEFT);
}

void drawCurrentWeather(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) {
  display->setFont(ArialMT_Plain_10);
  display->setTextAlignment(TEXT_ALIGN_CENTER);
  display->drawString(64 + x, 38 + y, currentWeather.description);

  display->setFont(ArialMT_Plain_24);
  display->setTextAlignment(TEXT_ALIGN_LEFT);
  String temp = String(currentWeather.temp, 1) + (IS_METRIC ? "°C" : "°F");
  display->drawString(60 + x, 5 + y, temp);

  display->setFont(Meteocons_Plain_36);
  display->setTextAlignment(TEXT_ALIGN_CENTER);
  display->drawString(32 + x, 0 + y, currentWeather.iconMeteoCon);
}


void drawForecast(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) {
  drawForecastDetails(display, x, y, 0);
  drawForecastDetails(display, x + 44, y, 1);
  drawForecastDetails(display, x + 88, y, 2);
}

void drawForecastDetails(OLEDDisplay *display, int x, int y, int dayIndex) {
  time_t observationTimestamp = forecasts[dayIndex].observationTime;
  struct tm* timeInfo;
  timeInfo = localtime(&observationTimestamp);
  display->setTextAlignment(TEXT_ALIGN_CENTER);
  display->setFont(ArialMT_Plain_10);
  display->drawString(x + 20, y, WDAY_NAMES[timeInfo->tm_wday]);

  display->setFont(Meteocons_Plain_21);
  display->drawString(x + 20, y + 12, forecasts[dayIndex].iconMeteoCon);
  String temp = String(forecasts[dayIndex].temp, 0) + (IS_METRIC ? "°C" : "°F");
  display->setFont(ArialMT_Plain_10);
  display->drawString(x + 20, y + 34, temp);
  display->setTextAlignment(TEXT_ALIGN_LEFT);
}

void drawHeaderOverlay(OLEDDisplay *display, OLEDDisplayUiState* state) {
  now = time(nullptr);
  struct tm* timeInfo;
  timeInfo = localtime(&now);
  char buff[14];
  sprintf_P(buff, PSTR("%02d:%02d"), timeInfo->tm_hour, timeInfo->tm_min);

  display->setColor(WHITE);
  display->setFont(ArialMT_Plain_10);
  display->setTextAlignment(TEXT_ALIGN_LEFT);
  display->drawString(0, 54, String(buff));
  display->setTextAlignment(TEXT_ALIGN_RIGHT);
  String temp = String(currentWeather.temp, 1) + (IS_METRIC ? "°C" : "°F");
  display->drawString(128, 54, temp);
  display->drawHorizontalLine(0, 52, 128);
}

void setReadyForWeatherUpdate() {
  Serial.println("Setting readyForUpdate to true");
  readyForWeatherUpdate = true;
}

Code is provided.

Nowhere near all of it.

Nowhere near all of the answer is: You need to...

justindiaz7474:
I live in Chicago so if anyone can provide the code to change it to the time in Chicago that would be awesome.

Here it is:

and how do i implement that into my code?

justindiaz7474:
and how do i implement that into my code?

Did you look at the example sketches that come with the library?

yes i clicked on the one specifically made for the esp8266 i just dont know where to go or what to do after that

Maybe that is because you didn't write the code you posted, and really don't understand any or very much of it? Am I right?

it's demo code

justindiaz7474:
it's demo code

Can you read and understand it, generally? Usually, if someone has no interest in in modifying the code themselves, it's a request for work which belongs in the Gigs and Collaborations forum.

Sometimes a post will get a lucky response from someone that "just sees" the answer right away, but the complexity of this program makes that somewhat unlikely.

You never said what actually happens. Do you see only UTC time? The wrong time zone? Are minutes and seconds correct?

aarg:
Here it is:
GitHub - JChristensen/Timezone: Arduino library to facilitate time zone conversions and automatic daylight saving (summer) time adjustments.

NO! NO! NO! Just say no.
While I have used Jack's code before and it can work for some situations,
it is definitely not needed here.
Also, that code is not handling local time (timezone) support properly since it messes with the actual time_t values.
(time_t values should never be modified no matter where you are on the planet and no matter if DST is in affect)
I've had some conversations with Jack about this.

The Arduino esp8266 core has real time library functions that support true local time support including local time (timezones) and DST offsets using the now 50 year old unix time API functions.
It amazing that even after 50 years of having these epoch time based API functions well documented that so many people still don't know about or grasp epoch time tracking, and many that try to implement it often do it incorrectly.

The issue with the exp8266 library code is that they did some stupid things and implemented some things improperly.
As a result, you cannot use configTime() to set an offset to mimic a timezone offset.
If you think about it, having a fixed offset doesn't make any sense as that isn't how local time is supposed to work.
They really screwed up by doing some things at the wrong level and I've talked to them about it.
To really fix it requires breaking some existing code that uses it, which IMO is perfectly ok, since what they have does not work at all with the time API functions.

The good news is that the existing code does support setting the TZ environment variable with a POSIX timezone string and then everything will work as expected, with timezone offsets as well as DST support once you set all the configTime() offets to zero so that the broken code never messs with the time_t values.
This lets the other time library functions do the local time conversions as and when needed.

Here is a link to the github issue I reported where you can read more about it and see some example code of how to use configTime(), setenv(), and tzset()

They seemed to have closed my issue, but I'll bet they didn't fix the broken code doing things in the wrong layer and doing them incorrectly by messing with the time_t values which breaks all the time API functions.

Anyway, it is easy to make it work correctly. Just set all the offsets to zero, and your TZ environment variable.
I use wifiManager and allow the user to configure ntp server and the TZ value there so nothing is hard coded in the actual code.

--- bill

Thank you! I managed to rearrange some variables to fit my time zone. Thank you for the resources.

bperrybap:
While I have used Jack's code before and it can work for some situations,
it is definitely not needed here.
Also, that code is not handling local time (timezone) support properly since it messes with the actual time_t values.
(time_t values should never be modified no matter where you are on the planet and no matter if DST is in affect)
I've had some conversations with Jack about this

I'm genuinely puzzled about this. I have used the same library in dozens of sketches. I don't understand what you mean about it "messing with the actual time_t values". IIRC, it doesn't modify any time variables unless you ask it to. In my implementations, the UTC time is maintained as a variable that is only updated by a time source, be it RTC or GPS or whatever. When I utilize the TimeZone functions, I just pass the UTC time to the function and it returns the local time. It did that perfectly the first time I used it, and every time after that. I've never, ever had any problem with it such as you describe. In that situation, the modified values are temporary anyway, the UTC time is never modified, and the temporary values are recalculated from UTC using the library functions whenever local time is required.

I can see that the ESP core has some special features that permit a different and possibly better approach.

aarg:
I'm genuinely puzzled about this. I have used the same library in dozens of sketches. I don't understand what you mean about it "messing with the actual time_t values". IIRC, it doesn't modify any time variables unless you ask it to.

Just because you have used the TimeZone library , and used it to get acceptable results for a specific application (I have also used it successfully in a few projects) , does not mean it is being done correctly and doesn't have issues.

When tracking epoch based time, the time offset value, in this case a time_t value, is supposed to be a value that represents a time offset from a SINGLE point in time. There is no such thing as a "local" time_t value.

I am reminded of a friend of mine that was working on a military contract for some HUGE automated guns for a navy ship that told me this kind of funny but yet scary story.
As the project neared completion the navy big Whigs were brought in for a demo, where the guns were placed out in field on a pedestal for a "live" test to shoot down a drone.
As the drone flew over, the guns flipped around very rapidly and violently damaging the mounts and ended up aimed at the crowd, fired, and barely missed killing people in the audience.
Needless to say the Navy guys were not impressed.
Turned out there were two coding bugs that cancelled out each other.
The same developer that wrote some of the positioning code also wrote the simulator, and he use cos() and sin() backwards in both the real code and the simulator so they worked together in simulations.
But in real life, the position was 90 degrees out of phase.
It was a near fatal mistake.

Many people do not fully understand the concept of epoch based time tracking and end up doing things like mucking with the time_t values to create things like a "local" time_t value.
Mucking with the time_t value is no different than a person adjusting the time they see their PC by adjusting the time they see instead of setting the timezone properly.
While either can produce a similarly looking (and often usable) date/time they see on their screen, the latter (adjusting the actual time vs the timezone) is incorrect as the internal timekeeping would be tracking the wrong time.
So if things like a meeting is set up with another person using a tool like outlook that tracks times using epoch based time, the meet time for the two people would be different times even they were in the same timezone.

If you look at jack's timezone library, it is messing with time_t values and introduces the concept of a "local" time_t value.
(Actually I see this being done in code quite often, so it isn't unique to the TimeZone library)
This is incorrect as the entire point of using epoch based time is that the time_t value is the the same everywhere on the planet and is not affected nor ever modified by any local or regional timezone or DST offset rules.

Here is a piece of code from the Clock.ino example in the TimeZone library:

void loop()
{
    time_t utc = now();
    time_t local = myTZ.toLocal(utc, &tcr);
    Serial.println();
    printDateTime(utc, "UTC");
    printDateTime(local, tcr -> abbrev);
    delay(10000);
}

What you see here is that the application code is munging a time_t value to create a fake local time_t value that is stored in local.
This "local" time_t value is then passed to a function that is printing out the time, that assumes that the time_t value is a value from the epoch. The "local" time printed will look like the local time, but in reality it is an incorrect UTC time that just happens to look like a valid local time since the time_t value was modified appropriately to trick the conversion/output/formatting routines.
i.e. the application is hacking a time_t value to trick other code into printing something that looks like the desired values.

This is wrong and is not how time_t values are supposed to work.
The time_t value should never be modified. To modify it means that there is no longer a single epoch and that the time_t value is no longer self contained. i.e. you have to have more information than just the time_t value to know what actual time it represents.
This violates the point of doing epoch based time tracking.

When time_t values start to get mucked with, you can start have issues since a time_t value or timestamp is no longer self contained.
This can be an issue if timestamps are being used and/or logged since you have no idea if that time_t value is a real time time_t value or one that is a "local" time_t value.
Any conversion to local time values needs to happens down in the library functions and there are never any modification to a time_t value that is used by the application or presented to time library API function.

If you look at the unix/POSIX time library functions, the time_t values are never modified.
i.e. if you call time() or now() you do not modify the time_t value returned get the current local time.
This was very intentional.
It ensures that time_t values are always consistent and always represent a time offset from the single epoch.

I understand the difficulty jack was facing trying to enhance a non local time aware library to provide some local time functionality.
But to do it correctly, it needed to add a layer that hides the time_t utc offset munging away from the application so that the application was never aware of, ever dealing with, or ever handling modified time_t values.

--- bill

Thank you for your detailed explanation. Would it be accurate to state, "any variable of data type time_t represents some UTC time, and can not represent times in any local time zone"? Is that the principle that is being violated?

If that is the case, what is the appropriate solution, when you need to convert and store a local time for display or other less common reasons? I mean, at the application level, since most people are not interested enough in this difference to modify any libraries.

Also, in my case, I developed a comprehensive table of all known time zones (I think 42 of them), along with the best known "daylight savings" adjustments. I figured out how to put those in flash memory, and pull out only one at a time to use with the TimeZone library. I have a configuration utility that offers the user a choice of zone (which also includes the daylight time offset). How would I implement something like that:

  1. with configTime()? Can it handle daylight time offsets?
  2. with UTC time provided by the DateTime library?

In other words, with the current tools available, what is an application developer to do about this, to be able to handle local time? Would it help to define a new type, 'localTime_t' for example?

aarg:
Thank you for your detailed explanation. Would it be accurate to state, "any variable of data type time_t represents some UTC time, and can not represent times in any local time zone"? Is that the principle that is being violated?

Not really. A time_t value does not represent an actual date or time.
A time_t is just an offset in time from some well defined fixed point in time and that is what people are not grasping.
The unix epoch is Jan 1, 1970 at 00:00 GMT and on unix, the time_t is the offset (number seconds) since that point in time.
By knowing the fixed point in time (the epoch) and knowing the amount of time that has elapsed since that point in time (the time_t value), you can calculate what point in time the time_t represents.
Since a time_t is just an offset from some well defined point in time it is always totally independent of any timezone or DST rules.
Other systems like Windows use a different epoch; however, the concept is still the same.

There must always be a conversion to convert a time_t value to human friendly local time values.
Even GMT or UTC is a "local" time.
If the application wants/needs the local time components of the time_t value for a particular a local region, it can be calculated by other API functions that know how to do the conversion.

The beauty of handling time this way is that time_t timestamps are quick and easy to handle as they are just an integer.
Converting to local time values like month, day, hour, min etc... is the expensive part but it is only done when it is necessary, typically for showing the values to the human.
For calculations, comparisons, and storage, the time_t value is much more efficient.

If that is the case, what is the appropriate solution, when you need to convert and store a local time for display or other less common reasons? I mean, at the application level, since most people are not interested enough in this difference to modify any libraries.

If I understand correctly. The answer is you don't do any conversion when storing.
A time_t or a timestamp is not associated with any timezone.
It is never messed with. There is no conversion that needs to be done, you simply save the time_t value.
When/if you need to show or display the local time values, you call a function that converts a time_t to the local time values.
The conversion function must know how to do the conversion based on knowing knowing which region is desired along with the associated DST offset rules for that region.
i.e. you call localtime() or asctime() as they are the functions that convert the time_t value to localized elements.
The conversions to local time values are all under the hood down in the library.
While a time library may choose to implement it by internally temporarily modifying a time_t value to call some common code for decoding the time elements, the modified time_t value is never exported or returned to the application.
So the application itself never uses, or even sees a time_t value that is anything other than a standard time_t value which is an offset from a single point in time that is unaffected by a timezone or DST rules.

Also, in my case, I developed a comprehensive table of all known time zones (I think 42 of them), along with the best known "daylight savings" adjustments. I figured out how to put those in flash memory, and pull out only one at a time to use with the TimeZone library. I have a configuration utility that offers the user a choice of zone. How would I implement something like that:

  1. with configTime()?
  2. with UTC time provided by the DateTime library?

None of the above.

The esp8266 configTime() is broken if you attempt to use the offsets.
It attempts to use some hooks down in the ntp code to modify the actual time_t values. This breaks most of the of time API functions so things like localtime() no longer work.
On the esp8266 it is very easy to get local time as the core includes several of the standard POSIX and linux time functions and comes bundled with ntp.
You can do what I recommended earlier and this offers support of a configurable timezone and DST rules using a standard POSIX TZ string.
So on the esp platform you don't even need an RTC to have very accurate time tracking as well as local time including DST offsets.

Other Arduino cores also have the same or similar time API library functions that use epoch based time with time_t offset values so at least the upper level code in an application can fairly portable.
The AVR libC comes with full time API functions and has for quite a while.
The core for DUE and core for pic32 do as well.

Michael wrote/ported the time, now TimeLib, library to Arduino a bit over a about a decade ago.
It was an attempt to make the time functions more Arduino friendly and cutesy which still seems to be an Arduino thing.
I did talk to him many years ago about supporting timezones but he didn't seem too interested in adding it.
It wasn't really necessary for him since he lived in London. :slight_smile: which probably drove some of the lack of interest.

But these days, you can often get full standard time API functions support including DST support from most Arduino cores as the functions typically come with the gcc toolset.

--- bill