GPS/UTC: Calender problem in time zone <> 0 - Solved

I made a GPS controlled clock with time and date. The time is UTC.

Now, if I want the clock show local time in, say, time zone +2, I will add 2 hours to the time (and correct if it passes 24).

However, that means that the date changes 2 hours late relative to local midnight.
I have to also correct the date.

I could convert date and time to Julian day, add the 2 hours (= 0,08333 day), and convert back to calendar date and time.

Is there a smarter way to do it?

You could look at how the DateTime and TimeSpan classes in the RTClib library handle it. You could even just use those classes.

1 Like

Which Arduino board?

Use the Arduino TimeLib.h (Time) library to do the conversion.

1 Like

This function might be useful to you:

byte days_in_month (int y, byte m) {
  // Fourth, eleventh, ninth, and sixth,
  // thirty days to each we fix. 
  if ((m==4)||(m==11)||(m==9)||(m==6)) return 30; 
  // Every other, thirty-one,
  // except the second month alone,
  if (m!=2) return 31;
  // which hath twenty-eight, in fine,
  // till leap-year give it twenty-nine.
  if ((y%400)==0) return 29; // leap year
  if ((y%100)==0) return 28; // not a leap year
  if ((y%4)==0)   return 29; // leap year
  return 28; // not a leap year 
}

your time zone adjustment should not only change the hour but should take the UTC timestamp / epoche, add 2h (or 1h during DST) and take the new timestamp as base for hour AND day/month.

Most of the libraries can do that. What library are you using on which microcontroller? Please post a working code.

@all of you Thank you for your replies. I Appreciate it. It will take me some time to study your suggestions.

@noiasca:
Arduino Uno.

your time zone adjustment should not only change the hour

Exactly.
Usually, I would use the Julian day conversion/back conversion for that, but it seems clumsy here, and may take too much time in the time loop.

What do you suggest?

I prefer methods that I can see through and understand as it is a hobby and I am a newbie in a learning situation. The code below could for sure be smarter.

Best regards,
Niels

#include <TinyGPSPlus.h>  // by Mikal Hart
#include <SoftwareSerial.h>
#include <LiquidCrystal.h>

LiquidCrystal lcd(12, 11, 5, 4, 3, 2);  //Arduino pins for communication

static const int RxPin = 8, TxPin = 9;  // TxPin not in use.
static const uint32_t GPSBaud = 9600;
static const int TimeZone = 1;  // dansk tid

// Erklæring af tider og strenge
int day, month, year, hour, minute, second;
String dateStr, dayStr, monthStr, yearStr;
String timeStr, hourStr, minuteStr, secondStr;
String oldTimeStr;

const char *monthName[] = {
  "null", "jan.", "feb.", "mar.", "apr.", "maj ", "juni", "juli", "aug.", "okt.", "nov.", "dec."
};

// The TinyGPSPlus object
TinyGPSPlus gps;

// The serial connection to the GPS device
SoftwareSerial ss(RxPin, TxPin);

// For stats that happen every 5 seconds
unsigned long last = 0UL;

void setup() {
  delay(1000);
  Serial.begin(115200);
  ss.begin(GPSBaud);

  // Display setup
  lcd.begin(16, 2);  //start LCDlibrary and set display size
  lcd.clear();       // Position (0,0)
}

void loop() {
  // Dispatch incoming characters
  while (ss.available() > 0) {
    gps.encode(ss.read());
  }

  if (gps.date.isUpdated()) {
    year = gps.date.year();
    month = gps.date.month();
    day = gps.date.day();
  }

  if (gps.time.isUpdated()) {
    hour = gps.time.hour();
    minute = gps.time.minute();
    second = gps.time.second();

    hour = hour + TimeZone;
    if (hour >= 24) {
      hour = 0;
    }
  }

  // Times to strings
  dayStr = String(day);
  monthStr = String(month);
  yearStr = String(year);
  hourStr = String(hour);
  minuteStr = String(minute);
  secondStr = String(second);

  // Minimum 2 digits in numbers
  if (day < 10) { dayStr = '0' + dayStr; }
  if (month < 10) { monthStr = ' ' + monthStr; }
  if (hour < 10) { hourStr = '0' + hourStr; }
  if (minute < 10) { minuteStr = '0' + minuteStr; }
  if (second < 10) { secondStr = '0' + secondStr; }

  // Build display strings
  dateStr = dayStr + ". " + monthName[month] + " " + yearStr;
  timeStr = hourStr + ":" + minuteStr + ":" + secondStr;

  // To monitor
  Serial.println(dateStr + "   " + timeStr);

  // To LCD. Cleared in setup.
  if (timeStr != oldTimeStr) {  //Avoid unneeded display update
    lcd.setCursor(4, 0);        // (position,line)
    lcd.print(timeStr);
    lcd.setCursor(2, 1);
    lcd.print(dateStr);
    oldTimeStr = timeStr;

    // Mark validity
    lcd.setCursor(0, 0);
    if (gps.location.isValid()) {
      lcd.print("*");
    } else lcd.print("?");
  }
}

it seems TinyGPSPlus doesn't have a interface to get a timestamp/epoche (just an assumption, one can proof me wrong).

I would collect the time/date parts and hand over it to either time.h or the TimeLib.h and then let either one of these calculate the offset.

edit: not nice, but may be a start:

/*
    https://forum.arduino.cc/t/gps-utc-calender-problem-in-time-zone-0/1230832/8
    2024-03-03 by noiasca
    code in thread
*/


#include <time.h> // comes with Arduino Core see https://cplusplus.com/reference/ctime/

void test() {
  // get your time elements in UTC:
  int year = 2024;
  int month = 3;
  int day = 3;
  int hour = 22;
  int minute = 20;
  int second = 30;

  // generate a struct tm with the given data:
  tm timeinfo = {0};
  timeinfo.tm_year = year - 1900;
  timeinfo.tm_mon = month - 1;
  timeinfo.tm_mday = day;
  timeinfo.tm_hour = hour;
  timeinfo.tm_min = minute;
  timeinfo.tm_sec = second;

  time_t utc = mktime(&timeinfo);  // time_t UTC (epoche)
  uint16_t offset = 2 * 60 * 60UL; // difference in seconds
  time_t local = utc + offset;     // time_t in local time (epoche)
  tm * localinfo;                  // struct tm for local time
  localinfo = localtime (&local);  // update local timeinfo based on local 

  char buffer[42] {0};              // a buffer large enough to hold your output
  strftime (buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &timeinfo);
  //Serial.println(utc);
  Serial.println(buffer);

  strftime (buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", localinfo);  // for different formats see https://cplusplus.com/reference/ctime/strftime/
  //Serial.println(local);
  Serial.println(buffer);
}

void setup() {
  Serial.begin(115200);
  Serial.println("time.h");
  test();
}

void loop() {
  // put your main code here, to run repeatedly:

}

@noiasca:

Thank you so much for that code. I'm sure this is the way to go. And I get some insight in time.h.

Best regards,
Niels

Unix time and the Time library use seconds since a particular epoch (e.g. midnight before Jan.1, 1970 = 0). To subtract an hour, just subtract 3600 seconds, and convert to date/time as required.

Working example:

//time_make_and_add
#include <TimeLib.h>

tmElements_t te;  //Time elements structure
time_t unixTime; // a time stamp

void setup() {
  Serial.begin(115200);
   // new internal clock setting.
  // convert a date and time into unix time, offset 1970
  te.Second = 0;
  te.Hour = 23; //11 pm
  te.Minute = 0;
  te.Day = 1;
  te.Month = 1;
  te.Year = 2017-1970; //Y2K, in seconds = 946684800UL
  unixTime =  makeTime(te);
  Serial.print("Example 1/1/2017 23:00 unixTime = ");
  Serial.println(unixTime);
  setTime(unixTime); //set the current time to the above entered
  Serial.print("now() = ");
  Serial.println(now());
  // print as date_time
  print_date_time();
  // add
  unixTime += 7200UL; //add 2 hours
  setTime(unixTime);
  Serial.println("After adding 2 hours");
  Serial.print("now() = ");
  Serial.println(now());
  print_date_time();
}
void print_date_time() { //easy way to print date and time
  char buf[40];
  sprintf(buf, "%02d/%02d/%4d %02d:%02d:%02d", day(), month(), year(), hour(), minute(), second());
  Serial.println(buf);
}

void loop() {}

There is, it’s been suggested in several replies. The PJRC time (aka TimeLib) library can do a lot more than has already mentioned. It will keep time internally using the millis counter and you can specify callback a function to be periodically invoked to true up that count based on an external time reference.

In your case, you’d specify a setSyncProvider() function that updates the time library with the latest GPS time.

It actually does: gps.time.value() return epoch time.

So, all @nfoldager has to do is register a setSyncProvider callback that's invoked every 10-30 minutes or so. It would pull epoch time from TinyGPSPlus and provide it to the TimeLib library.

As far as time zone and DST conversion goes, you could just hack it by add / subtracting the appropriate number of second from the GPS epoch time before sending it to TimeLib. A far more elegant approach would be to use the JChristensen Timezone Library.

The most important thing when dealing with time is to do all manipulations and internal representations using epoch time. The only point at which you should convert to hour, minutes, days, etc is when providing information to a human user.

1 Like

@noiasca

I am studying the code that you kindly provided above.

In the code, you wrote:

strftime (buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", localinfo); 
  Serial.println(buffer);

How could I get the local info (year, month, day, hour, minute, second) out from localinfo as integers?

localinfo is a pointer to a structure tm

so you can use

Serial.println(localinfo->tm_min);

to access the hour.

the datafiels are:

int tm_sec
int tm_min
int tm_hour
int tm_mday
int tm_mon
int tm_year
int tm_wday
int tm_yday
int tm_isdst

Ok. That works here toot.

But there is something that I haven't understood:
How to get the data from localinfo out as integers?

Serial.println(localinfo.tm_hour);

seems not to work here by me.

It's a pointer:

Serial.println(localinfo->tm_hour);
1 Like

sorry, tried the wrong variable. yes you need ->

Serial.println(localinfo->tm_hour);

Great. Thank you so much.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.