Konverzia času z NTP servera pre Arduino Uno

Zápisník experimentátora

Tento článok sa venuje problematike konverzie času medzi jednotlivými formátmi. Predpokladá, že máte získaný NTP čas a potrebujete ho konvertovať do podoby, s ktorou môže pracovať Arduino Uno. Tento kód nie je prenositeľný a bude fungovať iba pre 8bitové mikrokontroléry AVR.

Nastavenie časovej zóny

Časová zóna pre Slovensko a Česko je UTC+1. Do premennej t priradím hodnotu 3688838176, čo je NTP čas. Je to počer sekúnd od 1.1.1900. AVR-LIBC popisuje v dokumentácii, že time_t je počet sekúnd od 1.1.2000. Preto musíme NTP čas zmenšiť o hodnotu 3155673600. Mohli by sme použiť aj makro NTP_OFFSET.

Funkciou set_zone si nastavíme našu časovú zónu. A potom môžeme porovnať návratové hodnoty z funkcii localtime a gmtime a vidíme, že výsledok sa líši naozaj o jednu hodinu.

Zdrojový kód.

#include <time.h>

char bufout[256];

void setup() {
  Serial.begin(9600);
  while (!Serial)
    ;

  Serial.println("t = 3688838176; // NTP - Tue Nov 22 21:16:16 2016");
  time_t t = 3688838176; // NTP - Tue Nov 22 21:16:16 2016
 
  // NTP to avr UTC
  Serial.println("t = t - 3155673600L;");
  t = t - 3155673600L;
  
  struct tm _tm;

  // Slovakia timezone
  Serial.println("set_zone(1 * ONE_HOUR);");
  set_zone(1 * ONE_HOUR);

  _tm = *localtime(&t);
  sprintf(bufout, "localtime = %s", asctime(&_tm));
  Serial.println(bufout);
  _tm = *gmtime(&t);
  sprintf(bufout, "gmtime    = %s", asctime(&_tm));
  Serial.println(bufout);
}

void loop() {
}

Výstup z programu.

t = 3688838176; // NTP - Tue Nov 22 21:16:16 2016
t = t - 3155673600L;
set_zone(1 * ONE_HOUR);
localtime = Tue Nov 22 22:16:16 2016
gmtime    = Tue Nov 22 21:16:16 2016

Použitie letného času

Nasledovný kód dáva nesprávne hodnoty vďaka chybe v AVR-LIBC 2.0.

Pokúsme sa ešte o úpravu letného času. Definíciu nájdeme na Wikipédii. AVR-LIBC má na prácu s letným časom pripravenú funkciu set_dst. Poďme otestovať, či bude správne počítať. Spustíme nasledovný program, ktorý v roku 2016 vygeneruje pre každý deň UTC čas o 8:00 ráno a vykoná konverziu na lokálny čas. V marci a októbri by sme mali uvidieť prechod na letný čas.

Príklad je napísaný pre rok 2016 a je možné ho ľahko modifikovať aj pre iné roky. Prechod na letný čas v roku 2016 pripadol na 27.3.2016 a návrat naspäť na 30.10.2016. Pri pohľade na výpis je na prvý pohľad jasné, že celý marec je vypočítaný nesprávne (a zjavne je prehodený presne naopak). Je to chyba knižnice AVR-LIBC 2.0 a pretože momentálne novšia verzia neexistuje, je jasné, že tento kód sa nedá používať.

Zdrojový kód

#include <time.h>
#include <util/eu_dst.h>

char bufout[256];

void setup() {
  Serial.begin(9600);
  while (!Serial)
    ;

  // year 2016
  time_t t = (365L * ONE_DAY) * 16 + 8 * ONE_HOUR;
  struct tm _tm;

  // Slovakia timezone
  Serial.println("set_zone(1 * ONE_HOUR);");
  set_zone(1 * ONE_HOUR);
  set_dst(eu_dst);

  for (; t < (365L * ONE_DAY) * 17; t += ONE_DAY)
  {
    _tm = *localtime(&t);
    sprintf(bufout, "%s", asctime(&_tm));
    Serial.print(bufout);
    _tm = *gmtime(&t);
    sprintf(bufout, " -  %s", asctime(&_tm));
    Serial.println(bufout);
  }
}

void loop() {
}

Výstup z programu

Mon Feb 29 09:00:00 2016 -  Mon Feb 29 08:00:00 2016
Tue Mar 01 10:00:00 2016 -  Tue Mar 01 08:00:00 2016
Wed Mar 02 10:00:00 2016 -  Wed Mar 02 08:00:00 2016
Thu Mar 03 10:00:00 2016 -  Thu Mar 03 08:00:00 2016
Fri Mar 04 10:00:00 2016 -  Fri Mar 04 08:00:00 2016
Sat Mar 05 10:00:00 2016 -  Sat Mar 05 08:00:00 2016
Sun Mar 06 10:00:00 2016 -  Sun Mar 06 08:00:00 2016
Mon Mar 07 10:00:00 2016 -  Mon Mar 07 08:00:00 2016
Tue Mar 08 10:00:00 2016 -  Tue Mar 08 08:00:00 2016
Wed Mar 09 10:00:00 2016 -  Wed Mar 09 08:00:00 2016
Thu Mar 10 10:00:00 2016 -  Thu Mar 10 08:00:00 2016
Fri Mar 11 10:00:00 2016 -  Fri Mar 11 08:00:00 2016
Sat Mar 12 10:00:00 2016 -  Sat Mar 12 08:00:00 2016
Sun Mar 13 10:00:00 2016 -  Sun Mar 13 08:00:00 2016
Mon Mar 14 10:00:00 2016 -  Mon Mar 14 08:00:00 2016
Tue Mar 15 10:00:00 2016 -  Tue Mar 15 08:00:00 2016
Wed Mar 16 10:00:00 2016 -  Wed Mar 16 08:00:00 2016
Thu Mar 17 10:00:00 2016 -  Thu Mar 17 08:00:00 2016
Fri Mar 18 10:00:00 2016 -  Fri Mar 18 08:00:00 2016
Sat Mar 19 10:00:00 2016 -  Sat Mar 19 08:00:00 2016
Sun Mar 20 10:00:00 2016 -  Sun Mar 20 08:00:00 2016
Mon Mar 21 10:00:00 2016 -  Mon Mar 21 08:00:00 2016
Tue Mar 22 10:00:00 2016 -  Tue Mar 22 08:00:00 2016
Wed Mar 23 10:00:00 2016 -  Wed Mar 23 08:00:00 2016
Thu Mar 24 10:00:00 2016 -  Thu Mar 24 08:00:00 2016
Fri Mar 25 10:00:00 2016 -  Fri Mar 25 08:00:00 2016
Sat Mar 26 10:00:00 2016 -  Sat Mar 26 08:00:00 2016
Sun Mar 27 10:00:00 2016 -  Sun Mar 27 08:00:00 2016
Mon Mar 28 09:00:00 2016 -  Mon Mar 28 08:00:00 2016
Tue Mar 29 09:00:00 2016 -  Tue Mar 29 08:00:00 2016
Wed Mar 30 09:00:00 2016 -  Wed Mar 30 08:00:00 2016
Thu Mar 31 09:00:00 2016 -  Thu Mar 31 08:00:00 2016
Fri Apr 01 10:00:00 2016 -  Fri Apr 01 08:00:00 2016

Správny algoritmus pre výpočet letného času

Tento kód opravuje predchádzajúcu chybu. S ním nebudete mať žiadne problémy pri práci s letným časom na Slovensku, alebo v celej EU, kde je algoritmus prechodu na letný čas rovnaký.

Našťastie existuje riešenie na tento problém. Edgar Bonet na problém prišiel a na adrese http://http://savannah.nongnu.org/bugs/?44327 rovno aj ponúkol správny algoritmus. Podľa jeho slov otestoval všetky roky od 2000 po 2136 a všetky výpočty sedia. V zdrojovom texte je úplne rovnaký algoritmus ako v predchádzajúcom príklade, zmenená je iba funkcia výpočtu letného času. Algoritmus funkcie asi nepochopíte, ale autor funkcie o jej princípe píše vo svojom vysvetlení. Vysvetlenie je prístupné na hyperlinku alebo aj priamo v zdrojovom texte na GitHub.

Zdrojový kód

#include <time.h>

// fixed version by Edgar Bonet
int eu_dst(const time_t * timer, int32_t * z)
{
  uint32_t t = *timer;
  if ((uint8_t)(t >> 24) >= 194) t -= 3029443200U;
  t = (t + 655513200) / 604800 * 28;
  if ((uint16_t)(t % 1461) < 856) return 3600;
  else return 0;
}

char bufout[256];

void setup() {
  Serial.begin(9600);
  while (!Serial)
    ;

  // year 2016
  time_t t = (365L * ONE_DAY) * 16 + 8 * ONE_HOUR;
  struct tm _tm;

  // Slovakia timezone
  Serial.println("set_zone(1 * ONE_HOUR);");
  set_zone(1 * ONE_HOUR);
  set_dst(eu_dst);

  for (; t < (365L * ONE_DAY) * 17; t += ONE_DAY)
  {
    _tm = *localtime(&t);
    sprintf(bufout, "%s", asctime(&_tm));
    Serial.print(bufout);
    _tm = *gmtime(&t);
    sprintf(bufout, " -  %s", asctime(&_tm));
    Serial.println(bufout);
  }
}

void loop() {
}

Výstup z programu

Thu Mar 24 09:00:00 2016 -  Thu Mar 24 08:00:00 2016
Fri Mar 25 09:00:00 2016 -  Fri Mar 25 08:00:00 2016
Sat Mar 26 09:00:00 2016 -  Sat Mar 26 08:00:00 2016
Sun Mar 27 10:00:00 2016 -  Sun Mar 27 08:00:00 2016
Mon Mar 28 10:00:00 2016 -  Mon Mar 28 08:00:00 2016
Tue Mar 29 10:00:00 2016 -  Tue Mar 29 08:00:00 2016

Thu Oct 27 10:00:00 2016 -  Thu Oct 27 08:00:00 2016
Fri Oct 28 10:00:00 2016 -  Fri Oct 28 08:00:00 2016
Sat Oct 29 10:00:00 2016 -  Sat Oct 29 08:00:00 2016
Sun Oct 30 09:00:00 2016 -  Sun Oct 30 08:00:00 2016
Mon Oct 31 09:00:00 2016 -  Mon Oct 31 08:00:00 2016
Tue Nov 01 09:00:00 2016 -  Tue Nov 01 08:00:00 2016

Zdrojové texty

Všetky zdrojové texty sa nachádzajú na GitHub.


29.12.2016


Menu