Converting RTC output to Local Time with TZ and Linux EPOCH Time


Introduction

In this tutorial, we are converting RTC output to Local Time with TZ and Linux EPOCH Time. In a previous tutorial Setting and keeping time on your RTC using freeRTOS, LWIP and SNTP, we learned the fundamentals on setting up a network connect that would use DNS, and SNTP to capture the time from a public NTP server. The response from the NTP server was then used to initialize and keep our internal Real Time Clock (RTC) in sync.

Although, this technically worked, the project had a few short comings in that we could not display the Linux EPOCH UTC time, nor could we display the time in a particular timezone. So in this tutorials we will incorporate the ability to display the Linux EPOCH UTC time and allow you to choose which time zone you would like to display as well.

This tutorial works around a few issues that the standard C Library routines have built in, but first lets talk about how those routines function.

1) You must provide an environment variable called TZ, this is done by invoking the C library function: setenv(). You pass in the name of the variable “TZ”, and then the name of the time zone, like the following: setenv(“TZ, “America/Los Angeles”)
2) Your project must have a list of all of the time zone world wide for every country. I’ve provided a static list for this project in an include file.
3) The Time Zone name, gets translated into additional data which includes whether or not the zone participates in Daylight Savings time, and if so, when does this start and when does it end.

This tutorial includes duplicate code taken from the Standard C Library routines to handle the conversion, as well as handle daylight savings time. They are included in a source file called:
tzParse.c and tzParse.h

Some of the issues we ran into and why we chose to duplicate Standard C Library code:
1) Most C Library time functions use the TZ environment variable to determine conversions. If you have already setenv(“TZ”, “XYZ”), converting any time value to another time value will factor in the previously set TZ variable. If you want to get the current UTC EPOCH time, then any previous setenv(“TZ”, “America/New York”) needs to be unsetenv(“TZ) or just setenv(“TZ”, “America/Los Angeles”)
2) The problem with setenv/unsetenv is that in an embedded environment, it’s not very RTOS friendly. This method ran out of memory using setenv/unsetenv. Also, there are other documents surrounding not being thread-safe when using C Lib time functions.
3) To get around the limitation of #2, there is still the need setenv() once for the time zone to convert to. However, to convert the RTC response structure of RTC_TimeTypeDef and RTC_DateTypeDef, we used calculations to revert back to UTC time by using the calculated offset from the time zone.
4) The code that was duplicated pertained to parsing the Time Zone label into parts that can be used to calculate if daylight savings was involved. If it was, then the start and end time for daylight savings was also parsed. Due to the fact that most of the time routines in Lib C are private/non-exposed or static and they cannot be used outside of Lib C, they needed to be duplicated.

Converting RTC output to Local Time with TZ and Linux EPOCH Time allows a designer to properly show the correct local time and continue to get sub-second update to the RTC.


Materials List needed for converting RTC output


FTDI Pinouts to Display the text on the console for converting RTC output to the local time

FTDI to USB Pinout from right to left

  • Pin 1 – GND
  • Pin 2 – CTS
  • Pin 3 – VCC
  • Pin 4 – TX
  • Pin 5 – RX
  • Pin 6 – DTR
  • USB Mini – Connect to PC via USB cable

Make sure the jumper is set for 5v or 3v which ever is being used to power the FTDI device. If we look below at the schematic, it is showing in this case as 5v.

Converting RTC output
Converting RTC output

Nucleo-F439ZI needed for converting RTC output to Local Time

A standard RJ45 Ethernet connector will need to be inserted into the Nucleo-F439zi board adapter and must be plugged into your home or office networking to be able to make this project work. This board does not have WiFi capabilities, and this project will not work unless a hard wired networking connection is established.

Converting RTC output

Schematic for converting RTC output to Local Time

Converting RTC output

Pinouts & Configurations for converting RTC output to Local Time


This is a generated PDF of all of the settings within the IOC file, which is included below. Parameters that have changed from the default values are highlighted by being in BOLD with an asterisk “*


Exploring the Source Code for converting RTC output to Local Time

Project Structure


main.h

C
/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.h
  * @brief          : Header for main.c file.
  *                   This file contains the common defines of the application.
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2025 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */

/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __MAIN_H
#define __MAIN_H

#ifdef __cplusplus
extern "C" {
#endif

/* Includes ------------------------------------------------------------------*/
#include "stm32f4xx_hal.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */

/* USER CODE END Includes */

/* Exported types ------------------------------------------------------------*/
/* USER CODE BEGIN ET */

/* USER CODE END ET */

/* Exported constants --------------------------------------------------------*/
/* USER CODE BEGIN EC */

/* USER CODE END EC */

/* Exported macro ------------------------------------------------------------*/
/* USER CODE BEGIN EM */

/* USER CODE END EM */

/* Exported functions prototypes ---------------------------------------------*/
void Error_Handler(void);

/* USER CODE BEGIN EFP */

/* USER CODE END EFP */

/* Private defines -----------------------------------------------------------*/
#define MCO_Pin GPIO_PIN_0
#define MCO_GPIO_Port GPIOH
#define RMII_MDC_Pin GPIO_PIN_1
#define RMII_MDC_GPIO_Port GPIOC
#define RMII_REF_CLK_Pin GPIO_PIN_1
#define RMII_REF_CLK_GPIO_Port GPIOA
#define RMII_MDIO_Pin GPIO_PIN_2
#define RMII_MDIO_GPIO_Port GPIOA
#define RMII_CRS_DV_Pin GPIO_PIN_7
#define RMII_CRS_DV_GPIO_Port GPIOA
#define RMII_RXD0_Pin GPIO_PIN_4
#define RMII_RXD0_GPIO_Port GPIOC
#define RMII_RXD1_Pin GPIO_PIN_5
#define RMII_RXD1_GPIO_Port GPIOC
#define RMII_TXD1_Pin GPIO_PIN_13
#define RMII_TXD1_GPIO_Port GPIOB
#define TCK_Pin GPIO_PIN_14
#define TCK_GPIO_Port GPIOA
#define RMII_TX_EN_Pin GPIO_PIN_11
#define RMII_TX_EN_GPIO_Port GPIOG
#define RMII_TXD0_Pin GPIO_PIN_13
#define RMII_TXD0_GPIO_Port GPIOG

/* USER CODE BEGIN Private defines */
#define REDIRECT_PRINTF
/* USER CODE END Private defines */

#ifdef __cplusplus
}
#endif

#endif /* __MAIN_H */

FreeRTOSConfig.h

C
/* USER CODE BEGIN Header */
/*
 * FreeRTOS Kernel V10.3.1
 * Portion Copyright (C) 2017 Amazon.com, Inc. or its affiliates.  All Rights Reserved.
 * Portion Copyright (C) 2019 StMicroelectronics, Inc.  All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * http://www.FreeRTOS.org
 * http://aws.amazon.com/freertos
 *
 * 1 tab == 4 spaces!
 */
/* USER CODE END Header */

#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H

/*-----------------------------------------------------------
 * Application specific definitions.
 *
 * These definitions should be adjusted for your particular hardware and
 * application requirements.
 *
 * These parameters and more are described within the 'configuration' section of the
 * FreeRTOS API documentation available on the FreeRTOS.org web site.
 *
 * See http://www.freertos.org/a00110.html
 *----------------------------------------------------------*/

/* USER CODE BEGIN Includes */
/* Section where include file can be added */
/* USER CODE END Includes */

/* Ensure definitions are only used by the compiler, and not by the assembler. */
#if defined(__ICCARM__) || defined(__CC_ARM) || defined(__GNUC__)
  #include <stdint.h>
  extern uint32_t SystemCoreClock;
#endif
#ifndef CMSIS_device_header
#define CMSIS_device_header "stm32f4xx.h"
#endif /* CMSIS_device_header */

#define configENABLE_FPU                         1
#define configENABLE_MPU                         0

#define configUSE_PREEMPTION                     1
#define configSUPPORT_STATIC_ALLOCATION          1
#define configSUPPORT_DYNAMIC_ALLOCATION         1
#define configUSE_IDLE_HOOK                      0
#define configUSE_TICK_HOOK                      0
#define configCPU_CLOCK_HZ                       ( SystemCoreClock )
#define configTICK_RATE_HZ                       ((TickType_t)1000)
#define configMAX_PRIORITIES                     ( 56 )
#define configMINIMAL_STACK_SIZE                 ((uint16_t)256)
#define configTOTAL_HEAP_SIZE                    ((size_t)16384)
#define configMAX_TASK_NAME_LEN                  ( 16 )
#define configUSE_TRACE_FACILITY                 1
#define configUSE_16_BIT_TICKS                   0
#define configUSE_MUTEXES                        1
#define configQUEUE_REGISTRY_SIZE                8
#define configCHECK_FOR_STACK_OVERFLOW           2
#define configUSE_RECURSIVE_MUTEXES              1
#define configUSE_COUNTING_SEMAPHORES            1
#define configUSE_PORT_OPTIMISED_TASK_SELECTION  0
#define configUSE_POSIX_ERRNO                    1
/* USER CODE BEGIN MESSAGE_BUFFER_LENGTH_TYPE */
/* Defaults to size_t for backward compatibility, but can be changed
   if lengths will always be less than the number of bytes in a size_t. */
#define configMESSAGE_BUFFER_LENGTH_TYPE         size_t
/* USER CODE END MESSAGE_BUFFER_LENGTH_TYPE */

/* Co-routine definitions. */
#define configUSE_CO_ROUTINES                    0
#define configMAX_CO_ROUTINE_PRIORITIES          ( 2 )

/* Software timer definitions. */
#define configUSE_TIMERS                         1
#define configTIMER_TASK_PRIORITY                ( 2 )
#define configTIMER_QUEUE_LENGTH                 10
#define configTIMER_TASK_STACK_DEPTH             512

/* The following flag must be enabled only when using newlib */
#define configUSE_NEWLIB_REENTRANT          1

/* CMSIS-RTOS V2 flags */
#define configUSE_OS2_THREAD_SUSPEND_RESUME  1
#define configUSE_OS2_THREAD_ENUMERATE       1
#define configUSE_OS2_EVENTFLAGS_FROM_ISR    1
#define configUSE_OS2_THREAD_FLAGS           1
#define configUSE_OS2_TIMER                  1
#define configUSE_OS2_MUTEX                  1

/* Set the following definitions to 1 to include the API function, or zero
to exclude the API function. */
#define INCLUDE_vTaskPrioritySet             1
#define INCLUDE_uxTaskPriorityGet            1
#define INCLUDE_vTaskDelete                  1
#define INCLUDE_vTaskCleanUpResources        0
#define INCLUDE_vTaskSuspend                 1
#define INCLUDE_vTaskDelayUntil              1
#define INCLUDE_vTaskDelay                   1
#define INCLUDE_xTaskGetSchedulerState       1
#define INCLUDE_xTimerPendFunctionCall       1
#define INCLUDE_xQueueGetMutexHolder         1
#define INCLUDE_uxTaskGetStackHighWaterMark  1
#define INCLUDE_xTaskGetCurrentTaskHandle    1
#define INCLUDE_eTaskGetState                1

/*
 * The CMSIS-RTOS V2 FreeRTOS wrapper is dependent on the heap implementation used
 * by the application thus the correct define need to be enabled below
 */
#define USE_FreeRTOS_HEAP_4

/* Cortex-M specific definitions. */
#ifdef __NVIC_PRIO_BITS
 /* __BVIC_PRIO_BITS will be specified when CMSIS is being used. */
 #define configPRIO_BITS         __NVIC_PRIO_BITS
#else
 #define configPRIO_BITS         4
#endif

/* The lowest interrupt priority that can be used in a call to a "set priority"
function. */
#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY   15

/* The highest interrupt priority that can be used by any interrupt service
routine that makes calls to interrupt safe FreeRTOS API functions.  DO NOT CALL
INTERRUPT SAFE FREERTOS API FUNCTIONS FROM ANY INTERRUPT THAT HAS A HIGHER
PRIORITY THAN THIS! (higher priorities are lower numeric values. */
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5

/* Interrupt priorities used by the kernel port layer itself.  These are generic
to all Cortex-M ports, and do not rely on any particular library functions. */
#define configKERNEL_INTERRUPT_PRIORITY 		( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
/* !!!! configMAX_SYSCALL_INTERRUPT_PRIORITY must not be set to zero !!!!
See http://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html. */
#define configMAX_SYSCALL_INTERRUPT_PRIORITY 	( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )

/* Normal assert() semantics without relying on the provision of an assert.h
header file. */
/* USER CODE BEGIN 1 */
#define configASSERT( x ) if ((x) == 0) {taskDISABLE_INTERRUPTS(); for( ;; );}
/* USER CODE END 1 */

/* Definitions that map the FreeRTOS port interrupt handlers to their CMSIS
standard names. */
#define vPortSVCHandler    SVC_Handler
#define xPortPendSVHandler PendSV_Handler

/* IMPORTANT: After 10.3.1 update, Systick_Handler comes from NVIC (if SYS timebase = systick), otherwise from cmsis_os2.c */

#define USE_CUSTOM_SYSTICK_HANDLER_IMPLEMENTATION 0

/* USER CODE BEGIN Defines */
/* Section where parameter definitions can be added (for instance, to override default ones in FreeRTOS.h) */
/* USER CODE END Defines */

#endif /* FREERTOS_CONFIG_H */

freertos.c

C
/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * File Name          : freertos.c
  * Description        : Code for freertos applications
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2025 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */

/* Includes ------------------------------------------------------------------*/
#include "FreeRTOS.h"
#include "task.h"
#include "main.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "stdio.h"
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN Variables */

/* USER CODE END Variables */

/* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN FunctionPrototypes */

/* USER CODE END FunctionPrototypes */

/* Hook prototypes */
void vApplicationStackOverflowHook(xTaskHandle xTask, signed char *pcTaskName);

/* USER CODE BEGIN 4 */
void vApplicationStackOverflowHook(xTaskHandle xTask, signed char *pcTaskName)
{
   /* Run time stack overflow checking is performed if
   configCHECK_FOR_STACK_OVERFLOW is defined to 1 or 2. This hook function is
   called if a stack overflow is detected. */
}
/* USER CODE END 4 */

/* Private application code --------------------------------------------------*/
/* USER CODE BEGIN Application */

/*

	printf("vApplicationStackOverflowHook: %s\r\n", pcTaskName);

	Error_Handler();
 */
/* USER CODE END Application */


In this tutorial, we are going to be converting RTC output which is kept up to date by polling the SNTP server, and converting this to local time.

main.c

This is the main code that drives all of the freeRTOS tasks that handle capturing the time from the time server and converting RTC output to the local time.

C
/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2025 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "cmsis_os.h"
#include "lwip.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "semphr.h"
#include "stdbool.h"
#include "dnsLookup.h"
#include "mysntp.h"
#include "string.h"
#include "stdlib.h"
#include "tzInfo.h"
#include "errno.h"
#include "tzParse.h"


/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
RTC_HandleTypeDef hrtc;

UART_HandleTypeDef huart2;

/* Definitions for networkTask */
osThreadId_t networkTaskHandle;
const osThreadAttr_t networkTask_attributes = {
  .name = "networkTask",
  .stack_size = 256 * 4,
  .priority = (osPriority_t) osPriorityNormal,
};
/* Definitions for sntpTask */
osThreadId_t sntpTaskHandle;
const osThreadAttr_t sntpTask_attributes = {
  .name = "sntpTask",
  .stack_size = 512 * 4,
  .priority = (osPriority_t) osPriorityLow,
};
/* Definitions for displayTimeTask */
osThreadId_t displayTimeTaskHandle;
const osThreadAttr_t displayTimeTask_attributes = {
  .name = "displayTimeTask",
  .stack_size = 512 * 4,
  .priority = (osPriority_t) osPriorityLow,
};
/* USER CODE BEGIN PV */
bool waitingOnNetwork = true;
bool dhcpReady = false;
bool waitingOnSNTP = true;
int lastHourSaved = -1;

TZINFO_t *zoneInfo;

SemaphoreHandle_t rtcSemaphore;
SemaphoreHandle_t displayTimeSemaphore;

extern char **environ;

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART2_UART_Init(void);
static void MX_RTC_Init(void);
void networkInitializationTask(void *argument);
void sntpInitializationTask(void *argument);
void displayTimeWorker(void *argument);

/* USER CODE BEGIN PFP */
void link_myCallBack(struct netif *netif);

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
#ifdef REDIRECT_PRINTF
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#endif

#ifdef REDIRECT_PRINTF

PUTCHAR_PROTOTYPE
{
  HAL_UART_Transmit(&huart2, (uint8_t *)&ch, 1, 0xFFFF);

  return ch;
}
#endif

#define ETH_DEVICE	0

void link_myCallBack(struct netif *netif) {
	uint32_t regValue = 0;

    if (HAL_ETH_ReadPHYRegister(&heth, ETH_DEVICE, PHY_BSR, &regValue) == HAL_OK) {
        if((regValue & PHY_LINKED_STATUS) == (uint16_t)RESET) {
			if (!netif_is_link_up(netif)) {	// Link status = disconnected
				netif_set_down(netif);
				printf("Network cable is now disconnected!!!\r\n");
				netif_set_link_down(netif);
			}
        } else {
				if (netif_is_link_up(netif)) {	// Link status = connected
					printf("Network cable is now connected!!!\r\n");
					printf("Rebooting the system!!!\r\n");
					osDelay(2000);

					// We could try to write some sort of recovery logic to handle a reconnection, however.... (Boom!!!)
					NVIC_SystemReset();	// Reboot the microprocessor and start the application over.
				}
			}
        }
}

void displayRTCInfo() {
	struct tm timePtr = {0};
	float secfrac;
	int status = 0;
	char buffer[128] = {0};
	tzinfo_type *tzInfo = NULL;
	bool daylight = 0;
	char *timeZone = NULL;

	if(displayTimeSemaphore != NULL) {
		/* See if we can obtain the semaphore.  If the semaphore is not
		available wait 10 ticks to see if it becomes free. */
		status = xSemaphoreTake(displayTimeSemaphore, (TickType_t) 10);

		if(status == pdTRUE) {
			/* We were able to obtain the semaphore and can now access the
			shared resource. */

			getRtcDateTime(&timePtr, &secfrac);

			char timeBuf[80];

			char microSeconds[16];
			float subSeconds = secfrac / 1000.0;

			sprintf(microSeconds, "%3.3f", subSeconds);

			char *usPtr = NULL;
			usPtr = microSeconds + 1;	// Drop the decimal point in the string to print seconds with microseconds as xx.ususus

			char *str = NULL;
			str = getenv("TZ");

			while (str == NULL) {
				int stat = 0;

				stat = setenv("TZ", zoneInfo->rule, 1);

				if (stat == -1) {
					sprintf(buffer, "setenv returned errno=%d\r\n", errno);
					printf(buffer);
					Error_Handler();
				}

				tzset();

				str = getenv("TZ");

				if (str != NULL) {
					tzParse();
					tzcalc_limits(timePtr.tm_year + 1900);
					printf("\e[?25l");	// Hide Cursor
				}
			}

			time_t t = mktime(&timePtr);

			tzInfo = gettzinfo();

			time_t offset = 0;

			daylight = tzDayLightCheck(t, &timePtr);

			if (daylight) {
				timeZone = tzGetDSName();
				offset = (time_t) (tzInfo->__tzrule[1].offset);
			} else { /* otherwise assume std time */
				timeZone = tzGetStdName();
				offset = (time_t) (tzInfo->__tzrule[0].offset);
			}

			t -= (offset);

			struct tm ltm = {0};

			localtime_r(&t, &ltm);

			printf("\x1b[H");	// Cursor Home

			sprintf(timeBuf, "RTC EPOCH Time UTC: \t%ld\r\n", (ulong) t);
			printf(timeBuf);

			sprintf(timeBuf, "RTC Time UTC: \t\t%02d:%02d:%02d UTC\r\n", timePtr.tm_hour, timePtr.tm_min, timePtr.tm_sec);
			printf(timeBuf);

			sprintf(timeBuf, "RTC Time PNW: \t\t%02d:%02d:%02d%s %s\r\n", ltm.tm_hour, ltm.tm_min, ltm.tm_sec, usPtr,
					timeZone);
			printf(timeBuf);

			fflush(stdout);

			xSemaphoreGive(displayTimeSemaphore);
		} else {
			/* We could not obtain the semaphore and can therefore not access
			the shared resource safely. */
			printf("Could not acquire a semaphore for displayTimeSemaphore.\r\n");
			Error_Handler();
		}
	} else {
		printf("displayTimeSemaphore is NULL.\r\n");
		Error_Handler();
	}
}

bool getTimeZoneInfo(char *locale, TZINFO_t **zInfo) {
	bool notFound = true;
	int tzInfoSize = sizeof(tzDataInfo) - 1;

	for (int i = 0; i <= tzInfoSize; i ++) {
		if (strcmp(locale, tzDataInfo[i].location) == 0) {
			*zInfo = &tzDataInfo[i];
			notFound = false;
			break;
		}
	}

	return(notFound);
}
/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{

  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART2_UART_Init();
  MX_RTC_Init();
  /* USER CODE BEGIN 2 */

  char *timeZoneName = "America/Los Angeles";


  bool notFound = getTimeZoneInfo(timeZoneName, &zoneInfo);

  if (notFound) {
	  Error_Handler();
  }

  /* USER CODE END 2 */

  /* Init scheduler */
  osKernelInitialize();

  /* USER CODE BEGIN RTOS_MUTEX */
  /* add mutexes, ... */
  /* USER CODE END RTOS_MUTEX */

  /* USER CODE BEGIN RTOS_SEMAPHORES */
  /* add semaphores, ... */
  rtcSemaphore = xSemaphoreCreateMutex();
  displayTimeSemaphore = xSemaphoreCreateMutex();
  /* USER CODE END RTOS_SEMAPHORES */

  /* USER CODE BEGIN RTOS_TIMERS */
  /* start timers, add new ones, ... */
  /* USER CODE END RTOS_TIMERS */

  /* USER CODE BEGIN RTOS_QUEUES */
  /* add queues, ... */
  /* USER CODE END RTOS_QUEUES */

  /* Create the thread(s) */
  /* creation of networkTask */
  networkTaskHandle = osThreadNew(networkInitializationTask, NULL, &networkTask_attributes);

  /* creation of sntpTask */
  sntpTaskHandle = osThreadNew(sntpInitializationTask, NULL, &sntpTask_attributes);

  /* creation of displayTimeTask */
  displayTimeTaskHandle = osThreadNew(displayTimeWorker, NULL, &displayTimeTask_attributes);

  /* USER CODE BEGIN RTOS_THREADS */
  /* add threads, ... */
  /* USER CODE END RTOS_THREADS */

  /* USER CODE BEGIN RTOS_EVENTS */
  /* add events, ... */
  /* USER CODE END RTOS_EVENTS */

  /* Start scheduler */
  osKernelStart();

  /* We should never get here as control is now taken by the scheduler */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Configure the main internal regulator output voltage
  */
  __HAL_RCC_PWR_CLK_ENABLE();
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI|RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
  RCC_OscInitStruct.PLL.PLLM = 8;
  RCC_OscInitStruct.PLL.PLLN = 180;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = 4;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** Activate the Over-Drive mode
  */
  if (HAL_PWREx_EnableOverDrive() != HAL_OK)
  {
    Error_Handler();
  }

  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
  {
    Error_Handler();
  }
}

/**
  * @brief RTC Initialization Function
  * @param None
  * @retval None
  */
static void MX_RTC_Init(void)
{

  /* USER CODE BEGIN RTC_Init 0 */

  /* USER CODE END RTC_Init 0 */

  RTC_TimeTypeDef sTime = {0};
  RTC_DateTypeDef sDate = {0};

  /* USER CODE BEGIN RTC_Init 1 */

  /* USER CODE END RTC_Init 1 */

  /** Initialize RTC Only
  */
  hrtc.Instance = RTC;
  hrtc.Init.HourFormat = RTC_HOURFORMAT_24;
  hrtc.Init.AsynchPrediv = 125-1;
  hrtc.Init.SynchPrediv = 8000-1;
  hrtc.Init.OutPut = RTC_OUTPUT_DISABLE;
  hrtc.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH;
  hrtc.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN;
  if (HAL_RTC_Init(&hrtc) != HAL_OK)
  {
    Error_Handler();
  }

  /* USER CODE BEGIN Check_RTC_BKUP */

  /* USER CODE END Check_RTC_BKUP */

  /** Initialize RTC and set the Time and Date
  */
  sTime.Hours = 0;
  sTime.Minutes = 0;
  sTime.Seconds = 0;
  sTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
  sTime.StoreOperation = RTC_STOREOPERATION_RESET;
  if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN) != HAL_OK)
  {
    Error_Handler();
  }
  sDate.WeekDay = RTC_WEEKDAY_MONDAY;
  sDate.Month = RTC_MONTH_JANUARY;
  sDate.Date = 1;
  sDate.Year = 0;

  if (HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BIN) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN RTC_Init 2 */

  /* USER CODE END RTC_Init 2 */

}

/**
  * @brief USART2 Initialization Function
  * @param None
  * @retval None
  */
static void MX_USART2_UART_Init(void)
{

  /* USER CODE BEGIN USART2_Init 0 */

  /* USER CODE END USART2_Init 0 */

  /* USER CODE BEGIN USART2_Init 1 */

  /* USER CODE END USART2_Init 1 */
  huart2.Instance = USART2;
  huart2.Init.BaudRate = 19200;
  huart2.Init.WordLength = UART_WORDLENGTH_8B;
  huart2.Init.StopBits = UART_STOPBITS_1;
  huart2.Init.Parity = UART_PARITY_NONE;
  huart2.Init.Mode = UART_MODE_TX_RX;
  huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart2.Init.OverSampling = UART_OVERSAMPLING_16;
  if (HAL_UART_Init(&huart2) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN USART2_Init 2 */

  /* USER CODE END USART2_Init 2 */

}

/**
  * @brief GPIO Initialization Function
  * @param None
  * @retval None
  */
static void MX_GPIO_Init(void)
{
  /* USER CODE BEGIN MX_GPIO_Init_1 */
  /* USER CODE END MX_GPIO_Init_1 */

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOC_CLK_ENABLE();
  __HAL_RCC_GPIOH_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();
  __HAL_RCC_GPIOB_CLK_ENABLE();
  __HAL_RCC_GPIOD_CLK_ENABLE();
  __HAL_RCC_GPIOG_CLK_ENABLE();

  /* USER CODE BEGIN MX_GPIO_Init_2 */
  /* USER CODE END MX_GPIO_Init_2 */
}

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/* USER CODE BEGIN Header_networkInitializationTask */
/**
  * @brief  Function implementing the networkTask thread.
  * @param  argument: Not used
  * @retval None
  */
/* USER CODE END Header_networkInitializationTask */
void networkInitializationTask(void *argument)
{
  /* init code for LWIP */
  MX_LWIP_Init();
  /* USER CODE BEGIN 5 */
	extern struct netif gnetif;

	osDelay(2000);

	char *ipstr = NULL;

	while (ip4_addr_isany_val(*netif_ip4_addr(&gnetif))) {
	  osDelay(200);
	}

	printf("\x1b[2J\x1b[H");	// Clear the dumb terminal screen
	printf("Starting Network Initialization......\r\n");

	char buffer[128];

	printf("==============================================================================\n\r");

	ipstr = ip4addr_ntoa(netif_ip4_addr(&gnetif));

	sprintf(buffer, "Networking Ready!!!\r\n");
	printf(buffer);

	sprintf(buffer, "IP Address: %s assigned to this device by the DHCP Server\r\n", ipstr);
	printf(buffer);

	const ip_addr_t *ipaddr;

	ipaddr = dns_getserver(0);

	dhcpReady = !(ip4_addr_isany_val(*ipaddr));

	while (!dhcpReady) {
	  osDelay(200);
	  ipaddr = dns_getserver(0);
	  dhcpReady = !(ip4_addr_isany_val(*ipaddr));
	}

	sprintf(buffer, "DHCP Assigned DNS SERVER IP: %s\r\n", ip4addr_ntoa(ipaddr));
	printf(buffer);

	// Here we override the default callback defined in the MX_LWIP_Init() function which defaults to:
	// netif_set_link_callback(&gnetif, ethernet_link_status_updated);
	//
	// Setup our callback to handle ethernet cable disconnect/reconnect and override the default callback of: ethernet_link_status_updated
	netif_set_link_callback(&gnetif, link_myCallBack);

	sprintf(buffer, "Networking Initialization Complete!!!\r\n");
	printf(buffer);

	printf("Task Done!!!\r\n");
	printf("==============================================================================\n\r");

	waitingOnNetwork = false;

  /* Infinite loop */
  for(;;)
  {
    osDelay(1);
  }
  /* USER CODE END 5 */
}

/* USER CODE BEGIN Header_sntpInitializationTask */
/**
* @brief Function implementing the sntpTask thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_sntpInitializationTask */
void sntpInitializationTask(void *argument)
{
  /* USER CODE BEGIN sntpInitializationTask */
	char buffer[64];
	dnsGetHostName_t getHostNameResp;

	while (waitingOnNetwork) {
		osDelay(10);
	}

	printf("Starting SNTP Initialization......\r\n");

	getHostNameResp.name = "pool.ntp.org";
	getHostNameResp.done = false;

	bool found = getHostByName(getHostNameResp.name, &getHostNameResp);

	while (!found || getHostNameResp.done == false) {
		osDelay(10);
	}

	sprintf(buffer, "NTP Pool DNS: %s IP: %s\r\n", getHostNameResp.name, ip4addr_ntoa(&getHostNameResp.addr));
	printf(buffer);

	initSntp(&getHostNameResp.addr);

	waitingOnSNTP = false;

	printf("First SNTP Packet Received, and RTC has been set\n\r");
	printf("Task Done!!!\r\n");
	printf("==============================================================================\n\r");

  /* Infinite loop */
  for(;;)
  {
    osDelay(1);
  }
  /* USER CODE END sntpInitializationTask */
}

/* USER CODE BEGIN Header_displayTimeWorker */
/**
* @brief Function implementing the displayTimeTask thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_displayTimeWorker */
void displayTimeWorker(void *argument)
{
  /* USER CODE BEGIN displayTimeWorker */
	while (waitingOnNetwork || waitingOnSNTP) {
		osDelay(1);
	}

	osDelay(2000);

	printf("\x1b[2J\x1b[H");	// Clear the dumb terminal screen
	printf("\x1b[H");	// Cursor Home

	/* Infinite loop */
	for(;;)
	{
		displayRTCInfo();

		osDelay(100);
	}
  /* USER CODE END displayTimeWorker */
}

/**
  * @brief  Period elapsed callback in non blocking mode
  * @note   This function is called  when TIM6 interrupt took place, inside
  * HAL_TIM_IRQHandler(). It makes a direct call to HAL_IncTick() to increment
  * a global variable "uwTick" used as application time base.
  * @param  htim : TIM handle
  * @retval None
  */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  /* USER CODE BEGIN Callback 0 */

  /* USER CODE END Callback 0 */
  if (htim->Instance == TIM6)
  {
    HAL_IncTick();
  }
  /* USER CODE BEGIN Callback 1 */

  /* USER CODE END Callback 1 */
}

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */

	printf("Wrong parameters value: file %s on line %ld\r\n", file, line);

	while(1) {

	}
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

dnsLookup.h

C
/*
 * dnsLookup.h
 *
 *  Created on: Jun 7, 2024
 *      Author: johng
 */

#ifndef DNSLOOKUP_H_
#define DNSLOOKUP_H_
#include "cmsis_os.h"
#include "stm32f4xx_hal.h"
#include "lwip/dns.h"
#include "err.h"
#include "lwip.h"
#include "stdbool.h"
#include "lwip/apps/sntp.h"


typedef struct dnsGetHostName {
  const char *name;
  ip_addr_t addr;
  bool done;
} dnsGetHostName_t;

//extern dnsGetHostName_t *getHostNameResp;

void hostNameFoundCallBack(const char *, const ip_addr_t *, void *);
bool getHostByName(const char *, dnsGetHostName_t *);

typedef  void (*callBackFuncPtr)(char *, ip_addr_t *, void *);
typedef void (*dns_found_callback)(const char *name, const ip_addr_t *ipaddr, void *callback_arg);

#endif /* DNSLOOKUP_H_ */

dnsLookup.c

This code is needed to find and connect to the SNTP (Time Server) to be able to use SNTP to set the Real Time Clock (RTC) which is then used in converting RTC output to the local time

C
#include "dnsLookup.h"

dnsGetHostName_t *getHostNameRespPtr;

void hostNameFoundCallBack(const char *hostname, const ip_addr_t *ipaddr, void *arg)
{
	char buffer[128];

  if (ipaddr != NULL) {
    /* Address resolved, send request */

	  getHostNameRespPtr->addr = *ipaddr;
	  getHostNameRespPtr->done = true;
  } else {
	  sprintf(buffer, "DNS Failed to resolve. ipaddr == NULL\r\n");
	  printf(buffer);
	  Error_Handler();
  }
}

bool getHostByName(const char *name, dnsGetHostName_t *response) {
	bool retVal = false;
	int counter = 0;
	char buffer[128];

	getHostNameRespPtr = response;

	/*
	 * The following function call is used to get the a host name via a DNS server.  The DNS server had to be previously setup during
	 * static setup or through DHCP initialization.
	 * Once a response from the DNS Server is received, the callback function specified in the 3rd parameter is invoked.
	 * In this case the callback function is called: hostNameFoundCallBack
	 */
	err_enum_t err = dns_gethostbyname(name, &getHostNameRespPtr->addr, (dns_found_callback) hostNameFoundCallBack, NULL);

	if (err == ERR_INPROGRESS) {
	  while(getHostNameRespPtr->done != true) {
		  osDelay(100);
		  counter ++;
		  if (counter > 1000) {
			  sprintf(buffer, "getHosNameRespPtr never returned TRUE after X Number of tries.\r\n");
			  printf(buffer);
			  Error_Handler();
		  }
	  }
	  retVal = true;
	} else if (err == ERR_OK) {
	  getHostNameRespPtr->done = true;
	  retVal = true;
	} else {
		sprintf(buffer, "dns_gethostbyname ERROR: %d", err);
		printf(buffer);
		Error_Handler();
	}

	return retVal;
}

RTC.h

C
/*
 * RTC.h
 *
 *  Created on: Mar 4, 2025
 *      Author: johng
 */

#ifndef RTC_H_
#define RTC_H_

#include "main.h"
#include "cmsis_os.h"
#include "stm32f4xx_hal.h"
#include "time.h"
#include "semphr.h"

void setRtcDateTime (struct tm *timePtr);
HAL_StatusTypeDef getRtcDateTime(struct tm *timePtr, float *secfrac);

#endif /* RTC_H_ */

RTC.c

This code is used to set the Real Time Clock (RTC) which will then be put through additional code for converting RTC output to the local time

C
/*
 * RTC.c
 *
 *  Created on: Mar 5, 2025
 *      Author: johng
 */

#include "RTC.h"
#include "stdio.h"
#include "string.h"

extern RTC_HandleTypeDef hrtc;
extern SemaphoreHandle_t rtcSemaphore;

void setRtcDateTime (struct tm *timePtr)
{
	int status = 0;
	HAL_StatusTypeDef halStatus = HAL_OK;
	RTC_TimeTypeDef sTime = {0};
	RTC_DateTypeDef sDate = {0};

	if(rtcSemaphore != NULL) {
		/* See if we can obtain the semaphore.  If the semaphore is not
		available wait 10 ticks to see if it becomes free. */
		status = xSemaphoreTake(rtcSemaphore, (TickType_t) 10);

		if(status == pdTRUE) {
			/* We were able to obtain the semaphore and can now access the
			shared resource. */

			/** Initialize RTC and set the Time and Date
			*/
			sTime.Hours = timePtr->tm_hour;
			sTime.Minutes = timePtr->tm_min;
			sTime.Seconds = timePtr->tm_sec;
			sTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
			sTime.StoreOperation = RTC_STOREOPERATION_RESET;
			sDate.WeekDay = timePtr->tm_wday + 1;
			sDate.Month = timePtr->tm_mon + 1;
			sDate.Date = timePtr->tm_mday;
			sDate.Year = timePtr->tm_year - 100;

			if ((halStatus = HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN)) != HAL_OK)
			{
				char buf[80];
				sprintf(buf, "HAL_RTC_SetTime returned %d\r\n", halStatus);
				printf(buf);
				Error_Handler();
			}

			if ((halStatus = HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BIN)) != HAL_OK)
			{
				char buf[80];
				sprintf(buf, "HAL_RTC_SetDate returned %d\r\n", halStatus);
				printf(buf);
				Error_Handler();
			}

			xSemaphoreGive(rtcSemaphore);
		} else {
			/* We could not obtain the semaphore and can therefore not access
			the shared resource safely. */
			printf("Could not acquire a semaphore for rtcSemaphore.\r\n");
			Error_Handler();
		}
	} else {
		printf("rtcSemaphore is NULL.\r\n");
		Error_Handler();
	}
}

HAL_StatusTypeDef getRtcDateTime(struct tm *timePtr, float *secfrac) {
	HAL_StatusTypeDef halStatus = HAL_OK;
	RTC_TimeTypeDef sTime;
	RTC_DateTypeDef sDate;
	int status = 0;

	if(rtcSemaphore != NULL) {
		/* See if we can obtain the semaphore.  If the semaphore is not
		available wait 10 ticks to see if it becomes free. */
		status = xSemaphoreTake(rtcSemaphore, (TickType_t) 10);

		if(status == pdTRUE) {
			/* We were able to obtain the semaphore and can now access the
			shared resource. */

			HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
			HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BIN);

			timePtr->tm_hour = sTime.Hours;
			timePtr->tm_min = sTime.Minutes;
			timePtr->tm_sec = sTime.Seconds;
			timePtr->tm_isdst = sTime.DayLightSaving;
			timePtr->tm_wday = sDate.WeekDay - 1;
			timePtr->tm_mon = sDate.Month - 1;
			timePtr->tm_mday = sDate.Date;
			timePtr->tm_year = sDate.Year + 100;

			float frac;
			frac = ((float)((sTime.SecondFraction - sTime.SubSeconds) * 1000.0)) / ((float)(sTime.SecondFraction + 1));

			*secfrac = frac;

			xSemaphoreGive(rtcSemaphore);
		} else {
			/* We could not obtain the semaphore and can therefore not access
			the shared resource safely. */
			printf("Could not acquire a semaphore for rtcSemaphore.\r\n");
			Error_Handler();
		}
	} else {
		printf("rtcSemaphore is NULL.\r\n");
		Error_Handler();
	}

	return(halStatus);
}

This 

tzinfo.h

This files main purpose in converting RTC output to local time is very key, without this file it would not be possible to convert the RTC time which is in UTC to local time via the TZ variable.

C
/*
 * tzinfo.h
 *
 *  Created on: Mar 21, 2025
 *      Author: johng
 */

#ifndef TZINFO_H_
#define TZINFO_H_

typedef struct {
	char *location;
	char *rule;
}TZINFO_t;

TZINFO_t tzDataInfo [] =  {\
{"Africa/Abidjan","GMT0"}, \
{"Africa/Accra","GMT0"}, \
{"Africa/Addis Ababa","EAT-3"}, \
{"Africa/Algiers","CET-1"}, \
{"Africa/Asmara","EAT-3"}, \
{"Africa/Asmera","EAT-3"}, \
{"Africa/Bamako","GMT0"}, \
{"Africa/Bangui","WAT-1"}, \
{"Africa/Banjul","GMT0"}, \
{"Africa/Bissau","GMT0"}, \
{"Africa/Blantyre","CAT-2"}, \
{"Africa/Brazzaville","WAT-1"}, \
{"Africa/Bujumbura","CAT-2"}, \
{"Africa/Cairo","EET-2"}, \
{"Africa/Casablanca","<GMT+1>-1"}, \
{"Africa/Ceuta","CET-1CEST,M3.5.0,M10.5.0/3"}, \
{"Africa/Conakry","GMT0"}, \
{"Africa/Dakar","GMT0"}, \
{"Africa/Dar es Salaam","EAT-3"}, \
{"Africa/Djibouti","EAT-3"}, \
{"Africa/Douala","WAT-1"}, \
{"Africa/El Aaiun","<GMT+1>-1"}, \
{"Africa/Freetown","GMT0"}, \
{"Africa/Gaborone","CAT-2"}, \
{"Africa/Harare","CAT-2"}, \
{"Africa/Johannesburg","SAST-2"}, \
{"Africa/Juba","EAT-3"}, \
{"Africa/Kampala","EAT-3"}, \
{"Africa/Khartoum","CAT-2"}, \
{"Africa/Kigali","CAT-2"}, \
{"Africa/Kinshasa","WAT-1"}, \
{"Africa/Lagos","WAT-1"}, \
{"Africa/Libreville","WAT-1"}, \
{"Africa/Lome","GMT0"}, \
{"Africa/Luanda","WAT-1"}, \
{"Africa/Lubumbashi","CAT-2"}, \
{"Africa/Lusaka","CAT-2"}, \
{"Africa/Malabo","WAT-1"}, \
{"Africa/Maputo","CAT-2"}, \
{"Africa/Maseru","SAST-2"}, \
{"Africa/Mbabane","SAST-2"}, \
{"Africa/Mogadishu","EAT-3"}, \
{"Africa/Monrovia","GMT0"}, \
{"Africa/Nairobi","EAT-3"}, \
{"Africa/Ndjamena","WAT-1"}, \
{"Africa/Niamey","WAT-1"}, \
{"Africa/Nouakchott","GMT0"}, \
{"Africa/Ouagadougou","GMT0"}, \
{"Africa/Porto-Novo","WAT-1"}, \
{"Africa/Sao Tome","GMT0"}, \
{"Africa/Timbuktu","GMT0"}, \
{"Africa/Tripoli","EET-2"}, \
{"Africa/Tunis","CET-1"}, \
{"Africa/Windhoek","CAT-2"}, \
{"America/Adak","HST10HDT,M3.2.0,M11.1.0"}, \
{"America/Anchorage","AKST9AKDT,M3.2.0,M11.1.0"}, \
{"America/Anguilla","AST4"}, \
{"America/Antigua","AST4"}, \
{"America/Araguaina","<GMT-3>+3"}, \
{"America/Argentina/Buenos Aires","<GMT-3>+3"}, \
{"America/Argentina/Catamarca","<GMT-3>+3"}, \
{"America/Argentina/Cordoba","<GMT-3>+3"}, \
{"America/Argentina/Jujuy","<GMT-3>+3"}, \
{"America/Argentina/La Rioja","<GMT-3>+3"}, \
{"America/Argentina/Mendoza","<GMT-3>+3"}, \
{"America/Argentina/Rio Gallegos","<GMT-3>+3"}, \
{"America/Argentina/Salta","<GMT-3>+3"}, \
{"America/Argentina/San Juan","<GMT-3>+3"}, \
{"America/Argentina/San Luis","<GMT-3>+3"}, \
{"America/Argentina/Tucuman","<GMT-3>+3"}, \
{"America/Argentina/Ushuaia","<GMT-3>+3"}, \
{"America/Aruba","AST4"}, \
{"America/Asuncion","<GMT-4>+4"}, \
{"America/Atikokan","EST5"}, \
{"America/Bahia","<GMT-3>+3"}, \
{"America/Bahia Banderas","CST6CDT,M4.1.0,M10.5.0"}, \
{"America/Barbados","AST4"}, \
{"America/Belem","<GMT-3>+3"}, \
{"America/Belize","CST6"}, \
{"America/Blanc-Sablon","AST4"}, \
{"America/Boa Vista","<GMT-4>+4"}, \
{"America/Bogota","<GMT-5>+5"}, \
{"America/Boise","MST7MDT,M3.2.0,M11.1.0"}, \
{"America/Cambridge Bay","MST7MDT,M3.2.0,M11.1.0"}, \
{"America/Campo Grande","<GMT-4>+4"}, \
{"America/Cancun","EST5"}, \
{"America/Caracas","<GMT-4>+4"}, \
{"America/Cayenne","<GMT-3>+3"}, \
{"America/Cayman","EST5"}, \
{"America/Chicago","CST6CDT,M3.2.0,M11.1.0"}, \
{"America/Chihuahua","MST7MDT,M4.1.0,M10.5.0"}, \
{"America/Costa Rica","CST6"}, \
{"America/Creston","MST7"}, \
{"America/Cuiaba","<GMT-4>+4"}, \
{"America/Curacao","AST4"}, \
{"America/Danmarkshavn","GMT0"}, \
{"America/Dawson","PST8PDT,M3.2.0,M11.1.0"}, \
{"America/Dawson Creek","MST7"}, \
{"America/Denver","MST7MDT,M3.2.0,M11.1.0"}, \
{"America/Detroit","EST5EDT,M3.2.0,M11.1.0"}, \
{"America/Dominica","AST4"}, \
{"America/Edmonton","MST7MDT,M3.2.0,M11.1.0"}, \
{"America/Eirunepe","<GMT-5>+5"}, \
{"America/El Salvador","CST6"}, \
{"America/Fort Nelson","MST7"}, \
{"America/Fortaleza","<GMT-3>+3"}, \
{"America/Glace Bay","AST4ADT,M3.2.0,M11.1.0"}, \
{"America/Goose Bay","AST4ADT,M3.2.0,M11.1.0"}, \
{"America/Grand Turk","EST5EDT,M3.2.0,M11.1.0"}, \
{"America/Grenada","AST4"}, \
{"America/Guadeloupe","AST4"}, \
{"America/Guatemala","CST6"}, \
{"America/Guayaquil","<GMT-5>+5"}, \
{"America/Guyana","<GMT-4>+4"}, \
{"America/Halifax","AST4ADT,M3.2.0,M11.1.0"}, \
{"America/Havana","CST5CDT,M3.2.0/0,M11.1.0/1"}, \
{"America/Hermosillo","MST7"}, \
{"America/Indiana/Indianapolis","EST5EDT,M3.2.0,M11.1.0"}, \
{"America/Indiana/Knox","CST6CDT,M3.2.0,M11.1.0"}, \
{"America/Indiana/Marengo","EST5EDT,M3.2.0,M11.1.0"}, \
{"America/Indiana/Petersburg","EST5EDT,M3.2.0,M11.1.0"}, \
{"America/Indiana/Tell City","CST6CDT,M3.2.0,M11.1.0"}, \
{"America/Indiana/Vevay","EST5EDT,M3.2.0,M11.1.0"}, \
{"America/Indiana/Vincennes","EST5EDT,M3.2.0,M11.1.0"}, \
{"America/Indiana/Winamac","EST5EDT,M3.2.0,M11.1.0"}, \
{"America/Inuvik","MST7MDT,M3.2.0,M11.1.0"}, \
{"America/Iqaluit","EST5EDT,M3.2.0,M11.1.0"}, \
{"America/Jamaica","EST5"}, \
{"America/Juneau","AKST9AKDT,M3.2.0,M11.1.0"}, \
{"America/Kentucky/Louisville","EST5EDT,M3.2.0,M11.1.0"}, \
{"America/Kentucky/Monticello","EST5EDT,M3.2.0,M11.1.0"}, \
{"America/Kralendijk","AST4"}, \
{"America/La Paz","<GMT-4>+4"}, \
{"America/Lima","<GMT-5>+5"}, \
{"America/Los Angeles","PST8PDT,M3.2.0,M11.1.0"}, \
{"America/Lower Princes","AST4"}, \
{"America/Maceio","<GMT-3>+3"}, \
{"America/Managua","CST6"}, \
{"America/Manaus","<GMT-4>+4"}, \
{"America/Marigot","AST4"}, \
{"America/Martinique","AST4"}, \
{"America/Matamoros","CST6CDT,M3.2.0,M11.1.0"}, \
{"America/Mazatlan","MST7MDT,M4.1.0,M10.5.0"}, \
{"America/Menominee","CST6CDT,M3.2.0,M11.1.0"}, \
{"America/Merida","CST6CDT,M4.1.0,M10.5.0"}, \
{"America/Metlakatla","AKST9AKDT,M3.2.0,M11.1.0"}, \
{"America/Mexico City","CST6CDT,M4.1.0,M10.5.0"}, \
{"America/Miquelon","<GMT-3>+3"}, \
{"America/Moncton","AST4ADT,M3.2.0,M11.1.0"}, \
{"America/Monterrey","CST6CDT,M4.1.0,M10.5.0"}, \
{"America/Montevideo","<GMT-3>+3"}, \
{"America/Montserrat","AST4"}, \
{"America/Nassau","EST5EDT,M3.2.0,M11.1.0"}, \
{"America/New York","EST5EDT,M3.2.0,M11.1.0"}, \
{"America/Nipigon","EST5EDT,M3.2.0,M11.1.0"}, \
{"America/Nome","AKST9AKDT,M3.2.0,M11.1.0"}, \
{"America/Noronha","<GMT-2>+2"}, \
{"America/North Dakota/Beulah","CST6CDT,M3.2.0,M11.1.0"}, \
{"America/North Dakota/Center","CST6CDT,M3.2.0,M11.1.0"}, \
{"America/North Dakota/New Salem","CST6CDT,M3.2.0,M11.1.0"}, \
{"America/Ojinaga","MST7MDT,M3.2.0,M11.1.0"}, \
{"America/Panama","EST5"}, \
{"America/Pangnirtung","EST5EDT,M3.2.0,M11.1.0"}, \
{"America/Paramaribo","<GMT-3>+3"}, \
{"America/Phoenix","MST7"}, \
{"America/Port-au-Prince","EST5EDT,M3.2.0,M11.1.0"}, \
{"America/Port of Spain","AST4"}, \
{"America/Porto Velho","<GMT-4>+4"}, \
{"America/Puerto Rico","AST4"}, \
{"America/Punta Arenas","<GMT-3>+3"}, \
{"America/Rainy River","CST6CDT,M3.2.0,M11.1.0"}, \
{"America/Rankin Inlet","CST6CDT,M3.2.0,M11.1.0"}, \
{"America/Recife","<GMT-3>+3"}, \
{"America/Regina","CST6"}, \
{"America/Resolute","CST6CDT,M3.2.0,M11.1.0"}, \
{"America/Rio Branco","<GMT-5>+5"}, \
{"America/Santarem","<GMT-3>+3"}, \
{"America/Santo Domingo","AST4"}, \
{"America/Sao Paulo","<GMT-3>+3"}, \
{"America/Scoresbysund","<GMT-1>+1"}, \
{"America/Sitka","AKST9AKDT,M3.2.0,M11.1.0"}, \
{"America/St Barthelemy","AST4"}, \
{"America/St Johns","NST3"}, \
{"America/St Kitts","AST4"}, \
{"America/St Lucia","AST4"}, \
{"America/St Thomas","AST4"}, \
{"America/St Vincent","AST4"}, \
{"America/Swift Current","CST6"}, \
{"America/Tegucigalpa","CST6"}, \
{"America/Thule","AST4ADT,M3.2.0,M11.1.0"}, \
{"America/Thunder Bay","EST5EDT,M3.2.0,M11.1.0"}, \
{"America/Tijuana","PST8PDT,M3.2.0,M11.1.0"}, \
{"America/Toronto","EST5EDT,M3.2.0,M11.1.0"}, \
{"America/Tortola","AST4"}, \
{"America/Vancouver","PST8PDT,M3.2.0,M11.1.0"}, \
{"America/Whitehorse","PST8PDT,M3.2.0,M11.1.0"}, \
{"America/Winnipeg","CST6CDT,M3.2.0,M11.1.0"}, \
{"America/Yakutat","AKST9AKDT,M3.2.0,M11.1.0"}, \
{"America/Yellowknife","MST7MDT,M3.2.0,M11.1.0"}, \
{"Antarctica/Casey","<GMT+8>-8"}, \
{"Antarctica/Davis","<GMT+7>-7"}, \
{"Antarctica/DumontDUrville","<GMT+10>-10"}, \
{"Antarctica/Macquarie","<GMT+11>-11"}, \
{"Antarctica/Mawson","<GMT+5>-5"}, \
{"Antarctica/McMurdo","NZST-12NZDT,M9.5.0,M4.1.0/3"}, \
{"Antarctica/Palmer","<GMT-3>+3"}, \
{"Antarctica/Rothera","<GMT-3>+3"}, \
{"Antarctica/Syowa","<GMT+3>-3"}, \
{"Antarctica/Troll","0"}, \
{"Antarctica/Vostok","<GMT+6>-6"}, \
{"Arctic/Longyearbyen","CET-1CEST,M3.5.0,M10.5.0/3"}, \
{"Asia/Aden","<GMT+3>-3"}, \
{"Asia/Almaty","<GMT+6>-6"}, \
{"Asia/Amman","EET-2EEST,M3.5.4/24,M10.5.5/1"}, \
{"Asia/Anadyr","<GMT+12>-12"}, \
{"Asia/Aqtau","<GMT+5>-5"}, \
{"Asia/Aqtobe","<GMT+5>-5"}, \
{"Asia/Ashgabat","<GMT+5>-5"}, \
{"Asia/Atyrau","<GMT+5>-5"}, \
{"Asia/Baghdad","<GMT+3>-3"}, \
{"Asia/Bahrain","<GMT+3>-3"}, \
{"Asia/Baku","<GMT+4>-4"}, \
{"Asia/Bangkok","<GMT+7>-7"}, \
{"Asia/Barnaul","<GMT+7>-7"}, \
{"Asia/Beirut","EET-2EEST,M3.5.0/0,M10.5.0/0"}, \
{"Asia/Bishkek","<GMT+6>-6"}, \
{"Asia/Brunei","<GMT+8>-8"}, \
{"Asia/Chita","<GMT+9>-9"}, \
{"Asia/Choibalsan","<GMT+8>-8"}, \
{"Asia/Colombo","<GMT+5>-5"}, \
{"Asia/Damascus","EET-2EEST,M3.5.5/0,M10.5.5/0"}, \
{"Asia/Dhaka","<GMT+6>-6"}, \
{"Asia/Dili","<GMT+9>-9"}, \
{"Asia/Dubai","<GMT+4>-4"}, \
{"Asia/Dushanbe","<GMT+5>-5"}, \
{"Asia/Famagusta","EET-2EEST,M3.5.0/3,M10.5.0/4"}, \
{"Asia/Gaza","EET-2EEST,M3.5.5/0,M10.5.6/1"}, \
{"Asia/Hebron","EET-2EEST,M3.5.5/0,M10.5.6/1"}, \
{"Asia/Ho Chi Minh","<GMT+7>-7"}, \
{"Asia/Hong Kong","HKT-8"}, \
{"Asia/Hovd","<GMT+7>-7"}, \
{"Asia/Irkutsk","<GMT+8>-8"}, \
{"Asia/Istanbul","<GMT+3>-3"}, \
{"Asia/Jakarta","WIB-7"}, \
{"Asia/Jayapura","WIT-9"}, \
{"Asia/Kabul","<GMT+4>-4"}, \
{"Asia/Kamchatka","<GMT+12>-12"}, \
{"Asia/Karachi","PKT-5"}, \
{"Asia/Kathmandu","<GMT+5>-5"}, \
{"Asia/Khandyga","<GMT+9>-9"}, \
{"Asia/Kolkata","IST-5"}, \
{"Asia/Krasnoyarsk","<GMT+7>-7"}, \
{"Asia/Kuala Lumpur","<GMT+8>-8"}, \
{"Asia/Kuching","<GMT+8>-8"}, \
{"Asia/Kuwait","<GMT+3>-3"}, \
{"Asia/Macau","CST-8"}, \
{"Asia/Magadan","<GMT+11>-11"}, \
{"Asia/Makassar","WITA-8"}, \
{"Asia/Manila","PST-8"}, \
{"Asia/Muscat","<GMT+4>-4"}, \
{"Asia/Nicosia","EET-2EEST,M3.5.0/3,M10.5.0/4"}, \
{"Asia/Novokuznetsk","<GMT+7>-7"}, \
{"Asia/Novosibirsk","<GMT+7>-7"}, \
{"Asia/Omsk","<GMT+6>-6"}, \
{"Asia/Oral","<GMT+5>-5"}, \
{"Asia/Phnom Penh","<GMT+7>-7"}, \
{"Asia/Pontianak","WIB-7"}, \
{"Asia/Pyongyang","KST-9"}, \
{"Asia/Qatar","<GMT+3>-3"}, \
{"Asia/Qostanay","<GMT+6>-6"}, \
{"Asia/Qyzylorda","<GMT+5>-5"}, \
{"Asia/Riyadh","<GMT+3>-3"}, \
{"Asia/Sakhalin","<GMT+11>-11"}, \
{"Asia/Samarkand","<GMT+5>-5"}, \
{"Asia/Seoul","KST-9"}, \
{"Asia/Shanghai","CST-8"}, \
{"Asia/Singapore","<GMT+8>-8"}, \
{"Asia/Srednekolymsk","<GMT+11>-11"}, \
{"Asia/Taipei","CST-8"}, \
{"Asia/Tashkent","<GMT+5>-5"}, \
{"Asia/Tbilisi","<GMT+4>-4"}, \
{"Asia/Tehran","<GMT+3>-3"}, \
{"Asia/Thimphu","<GMT+6>-6"}, \
{"Asia/Tokyo","JST-9"}, \
{"Asia/Tomsk","<GMT+7>-7"}, \
{"Asia/Ulaanbaatar","<GMT+8>-8"}, \
{"Asia/Urumqi","<GMT+6>-6"}, \
{"Asia/Ust-Nera","<GMT+10>-10"}, \
{"Asia/Vientiane","<GMT+7>-7"}, \
{"Asia/Vladivostok","<GMT+10>-10"}, \
{"Asia/Yakutsk","<GMT+9>-9"}, \
{"Asia/Yangon","<GMT+6>-6"}, \
{"Asia/Yekaterinburg","<GMT+5>-5"}, \
{"Asia/Yerevan","<GMT+4>-4"}, \
{"Atlantic/Azores","<GMT-1>+1"}, \
{"Atlantic/Bermuda","AST4ADT,M3.2.0,M11.1.0"}, \
{"Atlantic/Canary","WET0WEST,M3.5.0/1,M10.5.0"}, \
{"Atlantic/Cape Verde","<GMT-1>+1"}, \
{"Atlantic/Faroe","WET0WEST,M3.5.0/1,M10.5.0"}, \
{"Atlantic/Madeira","WET0WEST,M3.5.0/1,M10.5.0"}, \
{"Atlantic/Reykjavik","GMT0"}, \
{"Atlantic/South Georgia","<GMT-2>+2"}, \
{"Atlantic/St Helena","GMT0"}, \
{"Atlantic/Stanley","<GMT-3>+3"}, \
{"Australia/Adelaide","ACST-9"}, \
{"Australia/Brisbane","AEST-10"}, \
{"Australia/Broken Hill","ACST-9"}, \
{"Australia/Currie","AEST-10AEDT,M10.1.0,M4.1.0/3"}, \
{"Australia/Darwin","ACST-9"}, \
{"Australia/Eucla","<GMT+8>-8"}, \
{"Australia/Hobart","AEST-10AEDT,M10.1.0,M4.1.0/3"}, \
{"Australia/Lindeman","AEST-10"}, \
{"Australia/Lord Howe","<GMT+10>-10"}, \
{"Australia/Melbourne","AEST-10AEDT,M10.1.0,M4.1.0/3"}, \
{"Australia/Perth","AWST-8"}, \
{"Australia/Sydney","AEST-10AEDT,M10.1.0,M4.1.0/3"}, \
{"Europe/Amsterdam","CET-1CEST,M3.5.0,M10.5.0/3"}, \
{"Europe/Andorra","CET-1CEST,M3.5.0,M10.5.0/3"}, \
{"Europe/Astrakhan","<GMT+4>-4"}, \
{"Europe/Athens","EET-2EEST,M3.5.0/3,M10.5.0/4"}, \
{"Europe/Belgrade","CET-1CEST,M3.5.0,M10.5.0/3"}, \
{"Europe/Berlin","CET-1CEST,M3.5.0,M10.5.0/3"}, \
{"Europe/Bratislava","CET-1CEST,M3.5.0,M10.5.0/3"}, \
{"Europe/Brussels","CET-1CEST,M3.5.0,M10.5.0/3"}, \
{"Europe/Bucharest","EET-2EEST,M3.5.0/3,M10.5.0/4"}, \
{"Europe/Budapest","CET-1CEST,M3.5.0,M10.5.0/3"}, \
{"Europe/Busingen","CET-1CEST,M3.5.0,M10.5.0/3"}, \
{"Europe/Chisinau","EET-2EEST,M3.5.0,M10.5.0/3"}, \
{"Europe/Copenhagen","CET-1CEST,M3.5.0,M10.5.0/3"}, \
{"Europe/Dublin","IST-1GMT0,M10.5.0,M3.5.0/1"}, \
{"Europe/Gibraltar","CET-1CEST,M3.5.0,M10.5.0/3"}, \
{"Europe/Guernsey","GMT0BST,M3.5.0/1,M10.5.0"}, \
{"Europe/Helsinki","EET-2EEST,M3.5.0/3,M10.5.0/4"}, \
{"Europe/Isle of Man","GMT0BST,M3.5.0/1,M10.5.0"}, \
{"Europe/Istanbul","<GMT+3>-3"}, \
{"Europe/Jersey","GMT0BST,M3.5.0/1,M10.5.0"}, \
{"Europe/Kaliningrad","EET-2"}, \
{"Europe/Kiev","EET-2EEST,M3.5.0/3,M10.5.0/4"}, \
{"Europe/Kirov","<GMT+3>-3"}, \
{"Europe/Lisbon","WET0WEST,M3.5.0/1,M10.5.0"}, \
{"Europe/Ljubljana","CET-1CEST,M3.5.0,M10.5.0/3"}, \
{"Europe/London","GMT0BST,M3.5.0/1,M10.5.0"}, \
{"Europe/Luxembourg","CET-1CEST,M3.5.0,M10.5.0/3"}, \
{"Europe/Madrid","CET-1CEST,M3.5.0,M10.5.0/3"}, \
{"Europe/Malta","CET-1CEST,M3.5.0,M10.5.0/3"}, \
{"Europe/Mariehamn","EET-2EEST,M3.5.0/3,M10.5.0/4"}, \
{"Europe/Minsk","<GMT+3>-3"}, \
{"Europe/Monaco","CET-1CEST,M3.5.0,M10.5.0/3"}, \
{"Europe/Moscow","MSK-3"}, \
{"Europe/Nicosia","EET-2EEST,M3.5.0/3,M10.5.0/4"}, \
{"Europe/Oslo","CET-1CEST,M3.5.0,M10.5.0/3"}, \
{"Europe/Paris","CET-1CEST,M3.5.0,M10.5.0/3"}, \
{"Europe/Podgorica","CET-1CEST,M3.5.0,M10.5.0/3"}, \
{"Europe/Prague","CET-1CEST,M3.5.0,M10.5.0/3"}, \
{"Europe/Riga","EET-2EEST,M3.5.0/3,M10.5.0/4"}, \
{"Europe/Rome","CET-1CEST,M3.5.0,M10.5.0/3"}, \
{"Europe/Samara","<GMT+4>-4"}, \
{"Europe/San Marino","CET-1CEST,M3.5.0,M10.5.0/3"}, \
{"Europe/Sarajevo","CET-1CEST,M3.5.0,M10.5.0/3"}, \
{"Europe/Saratov","<GMT+4>-4"}, \
{"Europe/Simferopol","MSK-3"}, \
{"Europe/Skopje","CET-1CEST,M3.5.0,M10.5.0/3"}, \
{"Europe/Sofia","EET-2EEST,M3.5.0/3,M10.5.0/4"}, \
{"Europe/Stockholm","CET-1CEST,M3.5.0,M10.5.0/3"}, \
{"Europe/Tallinn","EET-2EEST,M3.5.0/3,M10.5.0/4"}, \
{"Europe/Tirane","CET-1CEST,M3.5.0,M10.5.0/3"}, \
{"Europe/Ulyanovsk","<GMT+4>-4"}, \
{"Europe/Uzhgorod","EET-2EEST,M3.5.0/3,M10.5.0/4"}, \
{"Europe/Vaduz","CET-1CEST,M3.5.0,M10.5.0/3"}, \
{"Europe/Vatican","CET-1CEST,M3.5.0,M10.5.0/3"}, \
{"Europe/Vienna","CET-1CEST,M3.5.0,M10.5.0/3"}, \
{"Europe/Vilnius","EET-2EEST,M3.5.0/3,M10.5.0/4"}, \
{"Europe/Volgograd","<GMT+4>-4"}, \
{"Europe/Warsaw","CET-1CEST,M3.5.0,M10.5.0/3"}, \
{"Europe/Zagreb","CET-1CEST,M3.5.0,M10.5.0/3"}, \
{"Europe/Zaporozhye","EET-2EEST,M3.5.0/3,M10.5.0/4"}, \
{"Europe/Zurich","CET-1CEST,M3.5.0,M10.5.0/3"}, \
{"Indian/Antananarivo","EAT-3"}, \
{"Indian/Chagos","<GMT+6>-6"}, \
{"Indian/Christmas","<GMT+7>-7"}, \
{"Indian/Cocos","<GMT+6>-6"}, \
{"Indian/Comoro","EAT-3"}, \
{"Indian/Kerguelen","<GMT+5>-5"}, \
{"Indian/Mahe","<GMT+4>-4"}, \
{"Indian/Maldives","<GMT+5>-5"}, \
{"Indian/Mauritius","<GMT+4>-4"}, \
{"Indian/Mayotte","EAT-3"}, \
{"Indian/Reunion","<GMT+4>-4"}, \
{"Pacific/Apia","<GMT+13>-13"}, \
{"Pacific/Auckland","NZST-12NZDT,M9.5.0,M4.1.0/3"}, \
{"Pacific/Bougainville","<GMT+11>-11"}, \
{"Pacific/Chatham","<GMT+12>-12"}, \
{"Pacific/Chuuk","<GMT+10>-10"}, \
{"Pacific/Efate","<GMT+11>-11"}, \
{"Pacific/Enderbury","<GMT+13>-13"}, \
{"Pacific/Fakaofo","<GMT+13>-13"}, \
{"Pacific/Funafuti","<GMT+12>-12"}, \
{"Pacific/Galapagos","<GMT-6>+6"}, \
{"Pacific/Gambier","<GMT-9>+9"}, \
{"Pacific/Guadalcanal","<GMT+11>-11"}, \
{"Pacific/Honolulu","HST10"}
};


#endif /* TZINFO_H_ */

tzparse.h

C
/*
 * tzParse.h
 *
 *  Created on: Mar 26, 2025
 *      Author: johng
 */

#ifndef INC_TZPARSE_H_
#define INC_TZPARSE_H_

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "time.h"
#include "main.h"
#include "stdbool.h"

#define SECSPERMIN	60L
#define MINSPERHOUR	60L
#define HOURSPERDAY	24L
#define SECSPERHOUR	(SECSPERMIN * MINSPERHOUR)
#define SECSPERDAY	(SECSPERHOUR * HOURSPERDAY)
#define DAYSPERWEEK	7
#define MONSPERYEAR	12

#define YEAR_BASE	1900
#define EPOCH_YEAR      1970
#define EPOCH_WDAY      4
#define EPOCH_YEARS_SINCE_LEAP 2
#define EPOCH_YEARS_SINCE_CENTURY 70
#define EPOCH_YEARS_SINCE_LEAP_CENTURY 370

#define isleap(y) ((((y) % 4) == 0 && ((y) % 100) != 0) || ((y) % 400) == 0)

typedef struct tzrule_struct
{
  char ch;
  int m; /* Month of year if ch=M */
  int n; /* Week of month if ch=M */
  int d; /* Day of week if ch=M, day of year if ch=J or ch=D */
  int s; /* Time of day in seconds */
  time_t change;
  long offset; /* Match type of _timezone. */
} tzrule_type;

typedef struct __tzinfo_struct
{
  int __tznorth;
  int __tzyear;
  tzrule_type __tzrule[2];
} tzinfo_type;


tzinfo_type *gettzinfo (void);
int tzcalc_limits (int __year);
void tzParse(void);
char *tzGetStdName(void);
char *tzGetDSName(void);
bool tzDayLightCheck(time_t t, struct tm *timePtr);

#endif /* INC_TZPARSE_H_ */

tzparse.c

The following code is responsible for taking the UTC output of the SNTP time server response and converting RTC output to the local time.

Note: The tzparse.h and tzparse.c files are actually copies of ones provided by the libraries, but were modified because of a defect.

C
/*
 * tzParse.c
 *
 *  Created on: Mar 26, 2025
 *      Author: johng
 */

#include "tzParse.h"

#define sscanf siscanf	/* avoid to pull in FP functions. */

#define TZNAME_MIN	3	/* POSIX min TZ abbr size local def */
#define TZNAME_MAX	10	/* POSIX max TZ abbr size local def */

static char __tzname_std[TZNAME_MAX + 2];
static char __tzname_dst[TZNAME_MAX + 2];

extern const int __month_lengths[2][MONSPERYEAR];

struct tzrule_struct def_tzrule = {'J', 0, 0, 0, 0, (time_t)0, 0L };

/* Shared timezone information for libc/time functions.  */
static tzinfo_type tzinfo = {1, 0,
    { {'J', 0, 0, 0, 0, (time_t)0, 0L },
      {'J', 0, 0, 0, 0, (time_t)0, 0L }
    }
};

char *tzGetStdName() {
	return(__tzname_std);
}

char *tzGetDSName() {
	return(__tzname_dst);
}
tzinfo_type *gettzinfo (void)
{
  return &tzinfo;
}

bool tzDayLightCheck(time_t t, struct tm *timePtr) {
	bool dayLightSavings = false;
	tzinfo_type *tzInfo = NULL;
	bool daylight = false;

	tzInfo = gettzinfo();

	daylight = tzInfo->__tzrule[0].offset != tzInfo->__tzrule[1].offset;

	if (daylight) {
	  if (timePtr->tm_year == tzInfo->__tzyear || tzcalc_limits (timePtr->tm_year + 1900)) {
		  dayLightSavings = (tzInfo->__tznorth
				  ? (t >= tzInfo->__tzrule[0].change
						  && t < tzInfo->__tzrule[1].change)
						  : (t >= tzInfo->__tzrule[0].change
								  || t < tzInfo->__tzrule[1].change));
	  } else {
		  Error_Handler();
	  }
	} else {
		dayLightSavings = false;
	  }

	return(dayLightSavings);
}

int tzcalc_limits (int year)
{
	int days, year_days, years;
	int i, j;
	tzinfo_type *const tz = gettzinfo();

	if (year < EPOCH_YEAR)
	return 0;

	tz->__tzyear = year;

	years = (year - EPOCH_YEAR);

	year_days = years * 365 +
	(years - 1 + EPOCH_YEARS_SINCE_LEAP) / 4 -
	(years - 1 + EPOCH_YEARS_SINCE_CENTURY) / 100 +
	(years - 1 + EPOCH_YEARS_SINCE_LEAP_CENTURY) / 400;

	for (i = 0; i < 2; ++i) {
		if (tz->__tzrule[i].ch == 'J') {
			/* The Julian day n (1 <= n <= 365). */
			days = year_days + tz->__tzrule[i].d +
			(isleap(year) && tz->__tzrule[i].d >= 60);
			/* Convert to yday */
			--days;
		} else {
			if (tz->__tzrule[i].ch == 'D') {
				days = year_days + tz->__tzrule[i].d;
			} else {
				const int yleap = isleap(year);
				int m_day, m_wday, wday_diff;
				const int *const ip = __month_lengths[yleap];

				days = year_days;

				for (j = 1; j < tz->__tzrule[i].m; ++j)
				days += ip[j-1];

				m_wday = (EPOCH_WDAY + days) % DAYSPERWEEK;

				wday_diff = tz->__tzrule[i].d - m_wday;
				if (wday_diff < 0)
				wday_diff += DAYSPERWEEK;
				m_day = (tz->__tzrule[i].n - 1) * DAYSPERWEEK + wday_diff;

				while (m_day >= ip[j-1])
				m_day -= DAYSPERWEEK;

				days += m_day;
			}
		}

		/* store the change-over time in GMT form by adding offset */
		tz->__tzrule[i].change = (time_t) days * SECSPERDAY +
		tz->__tzrule[i].s + tz->__tzrule[i].offset;
    }

	tz->__tznorth = (tz->__tzrule[0].change < tz->__tzrule[1].change);

	return 1;
}

void tzParse() {
	char *tzenv;
	unsigned short hh, mm, ss, m, w, d;
	int sign, n;
	int i, ch;
	long offset0, offset1;

	tzinfo_type *tz = gettzinfo ();

	tzenv = getenv("TZ");

	/* default to unnamed UTC in case of error */
	_timezone = 0;
	_daylight = 0;
	_tzname[0] = "";
	_tzname[1] = "";
	tz->__tzrule[0] = def_tzrule;
	tz->__tzrule[1] = def_tzrule;

	/* ignore implementation-specific format specifier */
	if (*tzenv == ':') {
		++tzenv;
	}

	/* allow POSIX angle bracket < > quoted signed alphanumeric tz abbr e.g. <MESZ+0330> */
	if (*tzenv == '<') {
	  ++tzenv;

	  /* quit if no items, too few or too many chars, or no close quote '>' */
	  if (sscanf (tzenv, "%11[-+0-9A-Za-z]%n", __tzname_std, &n) <= 0
		|| n < TZNAME_MIN || TZNAME_MAX < n || '>' != tzenv[n])
		return;

	  ++tzenv;	/* bump for close quote '>' */
	}
	else
	{
	  /* allow POSIX unquoted alphabetic tz abbr e.g. MESZ */
	  if (sscanf (tzenv, "%11[A-Za-z]%n", __tzname_std, &n) <= 0
				|| n < TZNAME_MIN || TZNAME_MAX < n)
		return;
	}

	tzenv += n;

	sign = 1;
	if (*tzenv == '-') {
	  sign = -1;
	  ++tzenv;
	} else {
		if (*tzenv == '+') {
			++tzenv;
		}
	}

	mm = 0;
	ss = 0;

	if (sscanf (tzenv, "%hu%n:%hu%n:%hu%n", &hh, &n, &mm, &n, &ss, &n) < 1)
	return;

	offset0 = sign * (ss + SECSPERMIN * mm + SECSPERHOUR * hh);
	tzenv += n;

	/* allow POSIX angle bracket < > quoted signed alphanumeric tz abbr e.g. <MESZ+0330> */
	if (*tzenv == '<') {
	  ++tzenv;

	  /* quit if no items, too few or too many chars, or no close quote '>' */
	  if (sscanf (tzenv, "%11[-+0-9A-Za-z]%n", __tzname_dst, &n) <= 0 && tzenv[0] == '>') { /* No dst */
		_tzname[0] = __tzname_std;
		_tzname[1] = _tzname[0];
		tz->__tzrule[0].offset = offset0;
		_timezone = offset0;
		return;
		} else {
			if (n < TZNAME_MIN || TZNAME_MAX < n || '>' != tzenv[n])	{ /* error */
				return;
			}
		}

	  ++tzenv;	/* bump for close quote '>' */
	}
	else
	{
	  /* allow POSIX unquoted alphabetic tz abbr e.g. MESZ */
	  if (sscanf (tzenv, "%11[A-Za-z]%n", __tzname_dst, &n) <= 0)
	{ /* No dst */
		  _tzname[0] = __tzname_std;
		  _tzname[1] = _tzname[0];
		  tz->__tzrule[0].offset = offset0;
		  _timezone = offset0;
	  return;
		}
	  else if (n < TZNAME_MIN || TZNAME_MAX < n)
	{ /* error */
	  return;
	}
	}

	tzenv += n;

	/* otherwise we have a dst name, look for the offset */
	sign = 1;
	if (*tzenv == '-')
	{
	  sign = -1;
	  ++tzenv;
	}
	else if (*tzenv == '+')
	++tzenv;

	hh = 0;
	mm = 0;
	ss = 0;

	n  = 0;
	if (sscanf (tzenv, "%hu%n:%hu%n:%hu%n", &hh, &n, &mm, &n, &ss, &n) <= 0)
	offset1 = offset0 - 3600;
	else
	offset1 = sign * (ss + SECSPERMIN * mm + SECSPERHOUR * hh);

	tzenv += n;

	for (i = 0; i < 2; ++i)
	{
	  if (*tzenv == ',')
		++tzenv;

	  if (*tzenv == 'M')
	{
	  if (sscanf (tzenv, "M%hu%n.%hu%n.%hu%n", &m, &n, &w, &n, &d, &n) != 3 ||
		  m < 1 || m > 12 || w < 1 || w > 5 || d > 6)
		return;

	  tz->__tzrule[i].ch = 'M';
	  tz->__tzrule[i].m = m;
	  tz->__tzrule[i].n = w;
	  tz->__tzrule[i].d = d;

	  tzenv += n;
	}
	  else
	{
	  char *end;
	  if (*tzenv == 'J')
		{
		  ch = 'J';
		  ++tzenv;
		}
	  else
		ch = 'D';

	  d = strtoul (tzenv, &end, 10);

	  /* if unspecified, default to US settings */
	  /* From 1987-2006, US was M4.1.0,M10.5.0, but starting in 2007 is
	   * M3.2.0,M11.1.0 (2nd Sunday March through 1st Sunday November)  */
	  if (end == tzenv)
		{
		  if (i == 0)
		{
		  tz->__tzrule[0].ch = 'M';
		  tz->__tzrule[0].m = 3;
		  tz->__tzrule[0].n = 2;
		  tz->__tzrule[0].d = 0;
		}
		  else
		{
		  tz->__tzrule[1].ch = 'M';
		  tz->__tzrule[1].m = 11;
		  tz->__tzrule[1].n = 1;
		  tz->__tzrule[1].d = 0;
		}
		}
	  else
		{
		  tz->__tzrule[i].ch = ch;
		  tz->__tzrule[i].d = d;
		}

	  tzenv = end;
	}

	  /* default time is 02:00:00 am */
	  hh = 2;
	  mm = 0;
	  ss = 0;
	  n = 0;

	  if (*tzenv == '/')
	if (sscanf (tzenv, "/%hu%n:%hu%n:%hu%n", &hh, &n, &mm, &n, &ss, &n) <= 0)
	  {
		/* error in time format, restore tz rules to default and return */
		tz->__tzrule[0] = def_tzrule;
		tz->__tzrule[1] = def_tzrule;
			return;
		  }

	  tz->__tzrule[i].s = ss + SECSPERMIN * mm + SECSPERHOUR  * hh;

	  tzenv += n;
	}

	tz->__tzrule[0].offset = offset0;
	tz->__tzrule[1].offset = offset1;
	_tzname[0] = __tzname_std;
	_tzname[1] = __tzname_dst;
	tzcalc_limits (tz->__tzyear);
	_timezone = tz->__tzrule[0].offset;
	_daylight = tz->__tzrule[0].offset != tz->__tzrule[1].offset;

}

mysntp.h

C
/*
 * sntp.h
 *
 *  Created on: Jun 8, 2024
 *      Author: johng
 */

#ifndef MYSNTP_H_
#define MYSNTP_H_
#include "cmsis_os.h"
#include "stm32f4xx_hal.h"

#include "lwip/apps/sntp.h"
#include "RTC.h"
#include "stdbool.h"
//define USE_FULL_LL_DRIVER
//include "stm32f4xx_ll_rtc.h"

#define SNTP_SET_SYSTEM_TIME_NTP(sec, us)	sntp_set_system_time_us(sec, us)
#define SNTP_GET_SYSTEM_TIME_NTP(sec, us)	sntp_get_system_time_us(sec, us)

#define NTP_TIMESTAMP_DIFF     (2208988800)    // 1900 to 1970 in seconds

void sntp_set_system_time_us(u32_t sec, u32_t us);
void sntp_get_system_time_us(s32_t *sec, u32_t *us);
void initSntp(ip_addr_t *);

#endif /* MYSNTP_H_ */

mysntp.c

This code sets up the communications between this client and the (SMTP) time server to allow for the setting of time and update for the internal RTC. Converting RTC output to the local time is the responsiblilty of additional code.

C
/*
 * sntp.c
 *
 *  Created on: Jun 8, 2024
 *      Author: johng
 */

#include "mysntp.h"
#include "stdbool.h"
#include "time.h"

bool sntpFlagDone = false;
bool recvdSNTP_Resp = false;

struct timeval tv = {0};
static struct tm *sntp_tm;

void sntp_set_system_time_us(u32_t sec, u32_t us) {
	time_t ut;

/*
Unix uses an epoch located at 1/1/1970-00:00h (UTC) and NTP uses 1/1/1900-00:00h.
This leads to an offset equivalent to 70 years in seconds (there are 17 leap years between the two dates so the offset is

(70*365 + 17)*86400 = 2208988800

to be substracted from NTP time to get Unix struct timeval.
 */
	ut = ( time_t ) ( sec - NTP_TIMESTAMP_DIFF );

	tv.tv_sec = ut;
	tv.tv_usec = us;

	sntp_tm = gmtime(&ut);
	sntp_tm->tm_isdst = 1;

	setRtcDateTime(sntp_tm);
	sntpFlagDone = true;
}

void sntp_get_system_time_us(s32_t *sec, u32_t *us) {
	struct tm timePtr;
	float secfrac;
	time_t epoch;

	getRtcDateTime(&timePtr, &secfrac);

	epoch = mktime(&timePtr);
	*sec = epoch;
	*us = (u32_t) secfrac;
}

void initSntp(ip_addr_t *ipaddr) {
	sntp_setoperatingmode(SNTP_OPMODE_POLL);
	sntp_setserver(0, ipaddr);
	sntp_init();

	while (!sntpFlagDone) {
		osDelay(1);
	}
}

sntp.c

** NOTE***: This is extremely important!!!! Things will not work if this is missed and converting RTC output will not be possible.

*** Also Note: Every time the configuration/IOC file is update, code is regenerated and this file will revert back to it’s original template. If this happens, the include file will removed.


This source code is auto generated by STM32CubeIDE, if you have selected SNTP in the LWIP Middleware configuration, which is a MUST configuration option. However, you must put YOUR include file at the top of all includes such that YOUR callback will be invoked when a response from the SNTP server is received. Therefore, I’ve added the line: include “mysntp.h”

Below is a project structure image which shows the location of the sntp.c file

C
/**
 * @file
 * SNTP client module
 */

/*
 * Copyright (c) 2007-2009 Frédéric Bernon, Simon Goldschmidt
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
 * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGE.
 *
 * This file is part of the lwIP TCP/IP stack.
 *
 * Author: Frédéric Bernon, Simon Goldschmidt
 */


/**
 * @defgroup sntp SNTP
 * @ingroup apps
 *
 * This is simple "SNTP" client for the lwIP raw API.
 * It is a minimal implementation of SNTPv4 as specified in RFC 4330.
 *
 * For a list of some public NTP servers, see this link:
 * http://support.ntp.org/bin/view/Servers/NTPPoolServers
 *
 * @todo:
 * - complete SNTP_CHECK_RESPONSE checks 3 and 4
 */

#include "mysntp.h"
#include "lwip/apps/sntp.h"

#include "lwip/opt.h"
#include "lwip/timeouts.h"
#include "lwip/udp.h"
#include "lwip/dns.h"
#include "lwip/ip_addr.h"
#include "lwip/pbuf.h"
#include "lwip/dhcp.h"

#include <string.h>
#include <time.h>

#if LWIP_UDP

/* Handle support for more than one server via SNTP_MAX_SERVERS */
#if SNTP_MAX_SERVERS > 1
#define SNTP_SUPPORT_MULTIPLE_SERVERS 1
#else /* NTP_MAX_SERVERS > 1 */
#define SNTP_SUPPORT_MULTIPLE_SERVERS 0
#endif /* NTP_MAX_SERVERS > 1 */

#ifndef SNTP_SUPPRESS_DELAY_CHECK
#if SNTP_UPDATE_DELAY < 15000
#error "SNTPv4 RFC 4330 enforces a minimum update time of 15 seconds (define SNTP_SUPPRESS_DELAY_CHECK to disable this error)!"
#endif
#endif

/* the various debug levels for this file */
#define SNTP_DEBUG_TRACE        (SNTP_DEBUG | LWIP_DBG_TRACE)
#define SNTP_DEBUG_STATE        (SNTP_DEBUG | LWIP_DBG_STATE)
#define SNTP_DEBUG_WARN         (SNTP_DEBUG | LWIP_DBG_LEVEL_WARNING)
#define SNTP_DEBUG_WARN_STATE   (SNTP_DEBUG | LWIP_DBG_LEVEL_WARNING | LWIP_DBG_STATE)
#define SNTP_DEBUG_SERIOUS      (SNTP_DEBUG | LWIP_DBG_LEVEL_SERIOUS)

#define SNTP_ERR_KOD                1

/* SNTP protocol defines */
#define SNTP_MSG_LEN                48

#define SNTP_OFFSET_LI_VN_MODE      0
#define SNTP_LI_MASK                0xC0
#define SNTP_LI_NO_WARNING          (0x00 << 6)
#define SNTP_LI_LAST_MINUTE_61_SEC  (0x01 << 6)
#define SNTP_LI_LAST_MINUTE_59_SEC  (0x02 << 6)
#define SNTP_LI_ALARM_CONDITION     (0x03 << 6) /* (clock not synchronized) */

#define SNTP_VERSION_MASK           0x38
#define SNTP_VERSION                (4/* NTP Version 4*/<<3)

#define SNTP_MODE_MASK              0x07
#define SNTP_MODE_CLIENT            0x03
#define SNTP_MODE_SERVER            0x04
#define SNTP_MODE_BROADCAST         0x05

#define SNTP_OFFSET_STRATUM         1
#define SNTP_STRATUM_KOD            0x00

#define SNTP_OFFSET_ORIGINATE_TIME  24
#define SNTP_OFFSET_RECEIVE_TIME    32
#define SNTP_OFFSET_TRANSMIT_TIME   40

/* Number of seconds between 1970 and Feb 7, 2036 06:28:16 UTC (epoch 1) */
#define DIFF_SEC_1970_2036          ((u32_t)2085978496L)

/** Convert NTP timestamp fraction to microseconds.
 */
#ifndef SNTP_FRAC_TO_US
# if LWIP_HAVE_INT64
#  define SNTP_FRAC_TO_US(f)        ((u32_t)(((u64_t)(f) * 1000000UL) >> 32))
# else
#  define SNTP_FRAC_TO_US(f)        ((u32_t)(f) / 4295)
# endif
#endif /* !SNTP_FRAC_TO_US */

/* Configure behaviour depending on native, microsecond or second precision.
 * Treat NTP timestamps as signed two's-complement integers. This way,
 * timestamps that have the MSB set simply become negative offsets from
 * the epoch (Feb 7, 2036 06:28:16 UTC). Representable dates range from
 * 1968 to 2104.
 */
#ifndef SNTP_SET_SYSTEM_TIME_NTP
# ifdef SNTP_SET_SYSTEM_TIME_US
#  define SNTP_SET_SYSTEM_TIME_NTP(s, f) \
    SNTP_SET_SYSTEM_TIME_US((u32_t)((s) + DIFF_SEC_1970_2036), SNTP_FRAC_TO_US(f))
# else
#  define SNTP_SET_SYSTEM_TIME_NTP(s, f) \
    SNTP_SET_SYSTEM_TIME((u32_t)((s) + DIFF_SEC_1970_2036))
# endif
#endif /* !SNTP_SET_SYSTEM_TIME_NTP */

/* Get the system time either natively as NTP timestamp or convert from
 * Unix time in seconds and microseconds. Take care to avoid overflow if the
 * microsecond value is at the maximum of 999999. Also add 0.5 us fudge to
 * avoid special values like 0, and to mask round-off errors that would
 * otherwise break round-trip conversion identity.
 */
#ifndef SNTP_GET_SYSTEM_TIME_NTP
# define SNTP_GET_SYSTEM_TIME_NTP(s, f) do { \
    u32_t sec_, usec_; \
    SNTP_GET_SYSTEM_TIME(sec_, usec_); \
    (s) = (s32_t)(sec_ - DIFF_SEC_1970_2036); \
    (f) = usec_ * 4295 - ((usec_ * 2143) >> 16) + 2147; \
  } while (0)
#endif /* !SNTP_GET_SYSTEM_TIME_NTP */

/* Start offset of the timestamps to extract from the SNTP packet */
#define SNTP_OFFSET_TIMESTAMPS \
    (SNTP_OFFSET_TRANSMIT_TIME + 8 - sizeof(struct sntp_timestamps))

/* Round-trip delay arithmetic helpers */
#if SNTP_COMP_ROUNDTRIP
# if !LWIP_HAVE_INT64
#  error "SNTP round-trip delay compensation requires 64-bit arithmetic"
# endif
# define SNTP_SEC_FRAC_TO_S64(s, f) \
    ((s64_t)(((u64_t)(s) << 32) | (u32_t)(f)))
# define SNTP_TIMESTAMP_TO_S64(t) \
    SNTP_SEC_FRAC_TO_S64(lwip_ntohl((t).sec), lwip_ntohl((t).frac))
#endif /* SNTP_COMP_ROUNDTRIP */

/**
 * 64-bit NTP timestamp, in network byte order.
 */
struct sntp_time {
  u32_t sec;
  u32_t frac;
};

/**
 * Timestamps to be extracted from the NTP header.
 */
struct sntp_timestamps {
#if SNTP_COMP_ROUNDTRIP || SNTP_CHECK_RESPONSE >= 2
  struct sntp_time orig;
  struct sntp_time recv;
#endif
  struct sntp_time xmit;
};

/**
 * SNTP packet format (without optional fields)
 * Timestamps are coded as 64 bits:
 * - signed 32 bits seconds since Feb 07, 2036, 06:28:16 UTC (epoch 1)
 * - unsigned 32 bits seconds fraction (2^32 = 1 second)
 */
#ifdef PACK_STRUCT_USE_INCLUDES
#  include "arch/bpstruct.h"
#endif
PACK_STRUCT_BEGIN
struct sntp_msg {
  PACK_STRUCT_FLD_8(u8_t  li_vn_mode);
  PACK_STRUCT_FLD_8(u8_t  stratum);
  PACK_STRUCT_FLD_8(u8_t  poll);
  PACK_STRUCT_FLD_8(u8_t  precision);
  PACK_STRUCT_FIELD(u32_t root_delay);
  PACK_STRUCT_FIELD(u32_t root_dispersion);
  PACK_STRUCT_FIELD(u32_t reference_identifier);
  PACK_STRUCT_FIELD(u32_t reference_timestamp[2]);
  PACK_STRUCT_FIELD(u32_t originate_timestamp[2]);
  PACK_STRUCT_FIELD(u32_t receive_timestamp[2]);
  PACK_STRUCT_FIELD(u32_t transmit_timestamp[2]);
} PACK_STRUCT_STRUCT;
PACK_STRUCT_END
#ifdef PACK_STRUCT_USE_INCLUDES
#  include "arch/epstruct.h"
#endif

/* function prototypes */
static void sntp_request(void *arg);

/** The operating mode */
static u8_t sntp_opmode;

/** The UDP pcb used by the SNTP client */
static struct udp_pcb *sntp_pcb;
/** Names/Addresses of servers */
struct sntp_server {
#if SNTP_SERVER_DNS
  const char *name;
#endif /* SNTP_SERVER_DNS */
  ip_addr_t addr;
#if SNTP_MONITOR_SERVER_REACHABILITY
  /** Reachability shift register as described in RFC 5905 */
  u8_t reachability;
#endif /* SNTP_MONITOR_SERVER_REACHABILITY */
};
static struct sntp_server sntp_servers[SNTP_MAX_SERVERS];

#if SNTP_GET_SERVERS_FROM_DHCP
static u8_t sntp_set_servers_from_dhcp;
#endif /* SNTP_GET_SERVERS_FROM_DHCP */
#if SNTP_SUPPORT_MULTIPLE_SERVERS
/** The currently used server (initialized to 0) */
static u8_t sntp_current_server;
#else /* SNTP_SUPPORT_MULTIPLE_SERVERS */
#define sntp_current_server 0
#endif /* SNTP_SUPPORT_MULTIPLE_SERVERS */

#if SNTP_RETRY_TIMEOUT_EXP
#define SNTP_RESET_RETRY_TIMEOUT() sntp_retry_timeout = SNTP_RETRY_TIMEOUT
/** Retry time, initialized with SNTP_RETRY_TIMEOUT and doubled with each retry. */
static u32_t sntp_retry_timeout;
#else /* SNTP_RETRY_TIMEOUT_EXP */
#define SNTP_RESET_RETRY_TIMEOUT()
#define sntp_retry_timeout SNTP_RETRY_TIMEOUT
#endif /* SNTP_RETRY_TIMEOUT_EXP */

#if SNTP_CHECK_RESPONSE >= 1
/** Saves the last server address to compare with response */
static ip_addr_t sntp_last_server_address;
#endif /* SNTP_CHECK_RESPONSE >= 1 */

#if SNTP_CHECK_RESPONSE >= 2
/** Saves the last timestamp sent (which is sent back by the server)
 * to compare against in response. Stored in network byte order. */
static struct sntp_time sntp_last_timestamp_sent;
#endif /* SNTP_CHECK_RESPONSE >= 2 */

#if defined(LWIP_DEBUG) && !defined(sntp_format_time)
/* Debug print helper. */
static const char *
sntp_format_time(s32_t sec)
{
  time_t ut;
  ut = (u32_t)((u32_t)sec + DIFF_SEC_1970_2036);
  return ctime(&ut);
}
#endif /* LWIP_DEBUG && !sntp_format_time */

/**
 * SNTP processing of received timestamp
 */
static void
sntp_process(const struct sntp_timestamps *timestamps)
{
  s32_t sec;
  u32_t frac;

  sec  = (s32_t)lwip_ntohl(timestamps->xmit.sec);
  frac = lwip_ntohl(timestamps->xmit.frac);

#if SNTP_COMP_ROUNDTRIP
# if SNTP_CHECK_RESPONSE >= 2
  if (timestamps->recv.sec != 0 || timestamps->recv.frac != 0)
# endif
  {
    s32_t dest_sec;
    u32_t dest_frac;
    u32_t step_sec;

    /* Get the destination time stamp, i.e. the current system time */
    SNTP_GET_SYSTEM_TIME_NTP(dest_sec, dest_frac);

    step_sec = (dest_sec < sec) ? ((u32_t)sec - (u32_t)dest_sec)
               : ((u32_t)dest_sec - (u32_t)sec);
    /* In order to avoid overflows, skip the compensation if the clock step
     * is larger than about 34 years. */
    if ((step_sec >> 30) == 0) {
      s64_t t1, t2, t3, t4;

      t4 = SNTP_SEC_FRAC_TO_S64(dest_sec, dest_frac);
      t3 = SNTP_SEC_FRAC_TO_S64(sec, frac);
      t1 = SNTP_TIMESTAMP_TO_S64(timestamps->orig);
      t2 = SNTP_TIMESTAMP_TO_S64(timestamps->recv);
      /* Clock offset calculation according to RFC 4330 */
      t4 += ((t2 - t1) + (t3 - t4)) / 2;

      sec  = (s32_t)((u64_t)t4 >> 32);
      frac = (u32_t)((u64_t)t4);
    }
  }
#endif /* SNTP_COMP_ROUNDTRIP */

  SNTP_SET_SYSTEM_TIME_NTP(sec, frac);
  LWIP_UNUSED_ARG(frac); /* might be unused if only seconds are set */
  LWIP_DEBUGF(SNTP_DEBUG_TRACE, ("sntp_process: %s, %" U32_F " us\n",
                                 sntp_format_time(sec), SNTP_FRAC_TO_US(frac)));
}

/**
 * Initialize request struct to be sent to server.
 */
static void
sntp_initialize_request(struct sntp_msg *req)
{
  memset(req, 0, SNTP_MSG_LEN);
  req->li_vn_mode = SNTP_LI_NO_WARNING | SNTP_VERSION | SNTP_MODE_CLIENT;

#if SNTP_CHECK_RESPONSE >= 2 || SNTP_COMP_ROUNDTRIP
  {
    s32_t secs;
    u32_t sec, frac;
    /* Get the transmit timestamp */
    SNTP_GET_SYSTEM_TIME_NTP(secs, frac);
    sec  = lwip_htonl((u32_t)secs);
    frac = lwip_htonl(frac);

# if SNTP_CHECK_RESPONSE >= 2
    sntp_last_timestamp_sent.sec  = sec;
    sntp_last_timestamp_sent.frac = frac;
# endif
    req->transmit_timestamp[0] = sec;
    req->transmit_timestamp[1] = frac;
  }
#endif /* SNTP_CHECK_RESPONSE >= 2 || SNTP_COMP_ROUNDTRIP */
}

/**
 * Retry: send a new request (and increase retry timeout).
 *
 * @param arg is unused (only necessary to conform to sys_timeout)
 */
static void
sntp_retry(void *arg)
{
  LWIP_UNUSED_ARG(arg);

  LWIP_DEBUGF(SNTP_DEBUG_STATE, ("sntp_retry: Next request will be sent in %"U32_F" ms\n",
                                 sntp_retry_timeout));

  /* set up a timer to send a retry and increase the retry delay */
  sys_timeout(sntp_retry_timeout, sntp_request, NULL);

#if SNTP_RETRY_TIMEOUT_EXP
  {
    u32_t new_retry_timeout;
    /* increase the timeout for next retry */
    new_retry_timeout = sntp_retry_timeout << 1;
    /* limit to maximum timeout and prevent overflow */
    if ((new_retry_timeout <= SNTP_RETRY_TIMEOUT_MAX) &&
        (new_retry_timeout > sntp_retry_timeout)) {
      sntp_retry_timeout = new_retry_timeout;
    }
  }
#endif /* SNTP_RETRY_TIMEOUT_EXP */
}

#if SNTP_SUPPORT_MULTIPLE_SERVERS
/**
 * If Kiss-of-Death is received (or another packet parsing error),
 * try the next server or retry the current server and increase the retry
 * timeout if only one server is available.
 * (implicitly, SNTP_MAX_SERVERS > 1)
 *
 * @param arg is unused (only necessary to conform to sys_timeout)
 */
static void
sntp_try_next_server(void *arg)
{
  u8_t old_server, i;
  LWIP_UNUSED_ARG(arg);

  old_server = sntp_current_server;
  for (i = 0; i < SNTP_MAX_SERVERS - 1; i++) {
    sntp_current_server++;
    if (sntp_current_server >= SNTP_MAX_SERVERS) {
      sntp_current_server = 0;
    }
    if (!ip_addr_isany(&sntp_servers[sntp_current_server].addr)
#if SNTP_SERVER_DNS
        || (sntp_servers[sntp_current_server].name != NULL)
#endif
       ) {
      LWIP_DEBUGF(SNTP_DEBUG_STATE, ("sntp_try_next_server: Sending request to server %"U16_F"\n",
                                     (u16_t)sntp_current_server));
      /* new server: reset retry timeout */
      SNTP_RESET_RETRY_TIMEOUT();
      /* instantly send a request to the next server */
      sntp_request(NULL);
      return;
    }
  }
  /* no other valid server found */
  sntp_current_server = old_server;
  sntp_retry(NULL);
}
#else /* SNTP_SUPPORT_MULTIPLE_SERVERS */
/* Always retry on error if only one server is supported */
#define sntp_try_next_server    sntp_retry
#endif /* SNTP_SUPPORT_MULTIPLE_SERVERS */

/** UDP recv callback for the sntp pcb */
static void
sntp_recv(void *arg, struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *addr, u16_t port)
{
  struct sntp_timestamps timestamps;
  u8_t mode;
  u8_t stratum;
  err_t err;

  LWIP_UNUSED_ARG(arg);
  LWIP_UNUSED_ARG(pcb);

  err = ERR_ARG;
#if SNTP_CHECK_RESPONSE >= 1
  /* check server address and port */
  if (((sntp_opmode != SNTP_OPMODE_POLL) || ip_addr_cmp(addr, &sntp_last_server_address)) &&
      (port == SNTP_PORT))
#else /* SNTP_CHECK_RESPONSE >= 1 */
  LWIP_UNUSED_ARG(addr);
  LWIP_UNUSED_ARG(port);
#endif /* SNTP_CHECK_RESPONSE >= 1 */
  {
    /* process the response */
    if (p->tot_len == SNTP_MSG_LEN) {
      mode = pbuf_get_at(p, SNTP_OFFSET_LI_VN_MODE) & SNTP_MODE_MASK;
      /* if this is a SNTP response... */
      if (((sntp_opmode == SNTP_OPMODE_POLL)       && (mode == SNTP_MODE_SERVER)) ||
          ((sntp_opmode == SNTP_OPMODE_LISTENONLY) && (mode == SNTP_MODE_BROADCAST))) {
        stratum = pbuf_get_at(p, SNTP_OFFSET_STRATUM);

        if (stratum == SNTP_STRATUM_KOD) {
          /* Kiss-of-death packet. Use another server or increase UPDATE_DELAY. */
          err = SNTP_ERR_KOD;
          LWIP_DEBUGF(SNTP_DEBUG_STATE, ("sntp_recv: Received Kiss-of-Death\n"));
        } else {
          pbuf_copy_partial(p, &timestamps, sizeof(timestamps), SNTP_OFFSET_TIMESTAMPS);
#if SNTP_CHECK_RESPONSE >= 2
          /* check originate_timetamp against sntp_last_timestamp_sent */
          if (timestamps.orig.sec != sntp_last_timestamp_sent.sec ||
              timestamps.orig.frac != sntp_last_timestamp_sent.frac) {
            LWIP_DEBUGF(SNTP_DEBUG_WARN,
                        ("sntp_recv: Invalid originate timestamp in response\n"));
          } else
#endif /* SNTP_CHECK_RESPONSE >= 2 */
            /* @todo: add code for SNTP_CHECK_RESPONSE >= 3 and >= 4 here */
          {
            /* correct answer */
            err = ERR_OK;
          }
        }
      } else {
        LWIP_DEBUGF(SNTP_DEBUG_WARN, ("sntp_recv: Invalid mode in response: %"U16_F"\n", (u16_t)mode));
        /* wait for correct response */
        err = ERR_TIMEOUT;
      }
    } else {
      LWIP_DEBUGF(SNTP_DEBUG_WARN, ("sntp_recv: Invalid packet length: %"U16_F"\n", p->tot_len));
    }
  }
#if SNTP_CHECK_RESPONSE >= 1
  else {
    /* packet from wrong remote address or port, wait for correct response */
    err = ERR_TIMEOUT;
  }
#endif /* SNTP_CHECK_RESPONSE >= 1 */

  pbuf_free(p);

  if (err == ERR_OK) {
    /* correct packet received: process it it */
    sntp_process(&timestamps);

#if SNTP_MONITOR_SERVER_REACHABILITY
    /* indicate that server responded */
    sntp_servers[sntp_current_server].reachability |= 1;
#endif /* SNTP_MONITOR_SERVER_REACHABILITY */
    /* Set up timeout for next request (only if poll response was received)*/
    if (sntp_opmode == SNTP_OPMODE_POLL) {
      u32_t sntp_update_delay;
      sys_untimeout(sntp_try_next_server, NULL);
      sys_untimeout(sntp_request, NULL);

      /* Correct response, reset retry timeout */
      SNTP_RESET_RETRY_TIMEOUT();

      sntp_update_delay = (u32_t)SNTP_UPDATE_DELAY;
      sys_timeout(sntp_update_delay, sntp_request, NULL);
      LWIP_DEBUGF(SNTP_DEBUG_STATE, ("sntp_recv: Scheduled next time request: %"U32_F" ms\n",
                                     sntp_update_delay));
    }
  } else if (err == SNTP_ERR_KOD) {
    /* KOD errors are only processed in case of an explicit poll response */
    if (sntp_opmode == SNTP_OPMODE_POLL) {
      /* Kiss-of-death packet. Use another server or increase UPDATE_DELAY. */
      sntp_try_next_server(NULL);
    }
  } else {
    /* ignore any broken packet, poll mode: retry after timeout to avoid flooding */
  }
}

/** Actually send an sntp request to a server.
 *
 * @param server_addr resolved IP address of the SNTP server
 */
static void
sntp_send_request(const ip_addr_t *server_addr)
{
  struct pbuf *p;

  LWIP_ASSERT("server_addr != NULL", server_addr != NULL);

  p = pbuf_alloc(PBUF_TRANSPORT, SNTP_MSG_LEN, PBUF_RAM);
  if (p != NULL) {
    struct sntp_msg *sntpmsg = (struct sntp_msg *)p->payload;
    LWIP_DEBUGF(SNTP_DEBUG_STATE, ("sntp_send_request: Sending request to server\n"));
    /* initialize request message */
    sntp_initialize_request(sntpmsg);
    /* send request */
    udp_sendto(sntp_pcb, p, server_addr, SNTP_PORT);
    /* free the pbuf after sending it */
    pbuf_free(p);
#if SNTP_MONITOR_SERVER_REACHABILITY
    /* indicate new packet has been sent */
    sntp_servers[sntp_current_server].reachability <<= 1;
#endif /* SNTP_MONITOR_SERVER_REACHABILITY */
    /* set up receive timeout: try next server or retry on timeout */
    sys_timeout((u32_t)SNTP_RECV_TIMEOUT, sntp_try_next_server, NULL);
#if SNTP_CHECK_RESPONSE >= 1
    /* save server address to verify it in sntp_recv */
    ip_addr_copy(sntp_last_server_address, *server_addr);
#endif /* SNTP_CHECK_RESPONSE >= 1 */
  } else {
    LWIP_DEBUGF(SNTP_DEBUG_SERIOUS, ("sntp_send_request: Out of memory, trying again in %"U32_F" ms\n",
                                     (u32_t)SNTP_RETRY_TIMEOUT));
    /* out of memory: set up a timer to send a retry */
    sys_timeout((u32_t)SNTP_RETRY_TIMEOUT, sntp_request, NULL);
  }
}

#if SNTP_SERVER_DNS
/**
 * DNS found callback when using DNS names as server address.
 */
static void
sntp_dns_found(const char *hostname, const ip_addr_t *ipaddr, void *arg)
{
  LWIP_UNUSED_ARG(hostname);
  LWIP_UNUSED_ARG(arg);

  if (ipaddr != NULL) {
    /* Address resolved, send request */
    LWIP_DEBUGF(SNTP_DEBUG_STATE, ("sntp_dns_found: Server address resolved, sending request\n"));
    sntp_servers[sntp_current_server].addr = *ipaddr;
    sntp_send_request(ipaddr);
  } else {
    /* DNS resolving failed -> try another server */
    LWIP_DEBUGF(SNTP_DEBUG_WARN_STATE, ("sntp_dns_found: Failed to resolve server address resolved, trying next server\n"));
    sntp_try_next_server(NULL);
  }
}
#endif /* SNTP_SERVER_DNS */

/**
 * Send out an sntp request.
 *
 * @param arg is unused (only necessary to conform to sys_timeout)
 */
static void
sntp_request(void *arg)
{
  ip_addr_t sntp_server_address;
  err_t err;

  LWIP_UNUSED_ARG(arg);

  /* initialize SNTP server address */
#if SNTP_SERVER_DNS
  if (sntp_servers[sntp_current_server].name) {
    /* always resolve the name and rely on dns-internal caching & timeout */
    ip_addr_set_zero(&sntp_servers[sntp_current_server].addr);
    err = dns_gethostbyname(sntp_servers[sntp_current_server].name, &sntp_server_address,
                            sntp_dns_found, NULL);
    if (err == ERR_INPROGRESS) {
      /* DNS request sent, wait for sntp_dns_found being called */
      LWIP_DEBUGF(SNTP_DEBUG_STATE, ("sntp_request: Waiting for server address to be resolved.\n"));
      return;
    } else if (err == ERR_OK) {
      sntp_servers[sntp_current_server].addr = sntp_server_address;
    }
  } else
#endif /* SNTP_SERVER_DNS */
  {
    sntp_server_address = sntp_servers[sntp_current_server].addr;
    err = (ip_addr_isany_val(sntp_server_address)) ? ERR_ARG : ERR_OK;
  }

  if (err == ERR_OK) {
    LWIP_DEBUGF(SNTP_DEBUG_TRACE, ("sntp_request: current server address is %s\n",
                                   ipaddr_ntoa(&sntp_server_address)));
    sntp_send_request(&sntp_server_address);
  } else {
    /* address conversion failed, try another server */
    LWIP_DEBUGF(SNTP_DEBUG_WARN_STATE, ("sntp_request: Invalid server address, trying next server.\n"));
    sys_timeout((u32_t)SNTP_RETRY_TIMEOUT, sntp_try_next_server, NULL);
  }
}

/**
 * @ingroup sntp
 * Initialize this module.
 * Send out request instantly or after SNTP_STARTUP_DELAY(_FUNC).
 */
void
sntp_init(void)
{
  /* LWIP_ASSERT_CORE_LOCKED(); is checked by udp_new() */

#ifdef SNTP_SERVER_ADDRESS
#if SNTP_SERVER_DNS
  sntp_setservername(0, SNTP_SERVER_ADDRESS);
#else
#error SNTP_SERVER_ADDRESS string not supported SNTP_SERVER_DNS==0
#endif
#endif /* SNTP_SERVER_ADDRESS */

  if (sntp_pcb == NULL) {
    sntp_pcb = udp_new_ip_type(IPADDR_TYPE_ANY);
    LWIP_ASSERT("Failed to allocate udp pcb for sntp client", sntp_pcb != NULL);
    if (sntp_pcb != NULL) {
      udp_recv(sntp_pcb, sntp_recv, NULL);

      if (sntp_opmode == SNTP_OPMODE_POLL) {
        SNTP_RESET_RETRY_TIMEOUT();
#if SNTP_STARTUP_DELAY
        sys_timeout((u32_t)SNTP_STARTUP_DELAY_FUNC, sntp_request, NULL);
#else
        sntp_request(NULL);
#endif
      } else if (sntp_opmode == SNTP_OPMODE_LISTENONLY) {
        ip_set_option(sntp_pcb, SOF_BROADCAST);
        udp_bind(sntp_pcb, IP_ANY_TYPE, SNTP_PORT);
      }
    }
  }
}

/**
 * @ingroup sntp
 * Stop this module.
 */
void
sntp_stop(void)
{
  LWIP_ASSERT_CORE_LOCKED();
  if (sntp_pcb != NULL) {
#if SNTP_MONITOR_SERVER_REACHABILITY
    u8_t i;
    for (i = 0; i < SNTP_MAX_SERVERS; i++) {
      sntp_servers[i].reachability = 0;
    }
#endif /* SNTP_MONITOR_SERVER_REACHABILITY */
    sys_untimeout(sntp_request, NULL);
    sys_untimeout(sntp_try_next_server, NULL);
    udp_remove(sntp_pcb);
    sntp_pcb = NULL;
  }
}

/**
 * @ingroup sntp
 * Get enabled state.
 */
u8_t sntp_enabled(void)
{
  return (sntp_pcb != NULL) ? 1 : 0;
}

/**
 * @ingroup sntp
 * Sets the operating mode.
 * @param operating_mode one of the available operating modes
 */
void
sntp_setoperatingmode(u8_t operating_mode)
{
  LWIP_ASSERT_CORE_LOCKED();
  LWIP_ASSERT("Invalid operating mode", operating_mode <= SNTP_OPMODE_LISTENONLY);
  LWIP_ASSERT("Operating mode must not be set while SNTP client is running", sntp_pcb == NULL);
  sntp_opmode = operating_mode;
}

/**
 * @ingroup sntp
 * Gets the operating mode.
 */
u8_t
sntp_getoperatingmode(void)
{
  return sntp_opmode;
}

#if SNTP_MONITOR_SERVER_REACHABILITY
/**
 * @ingroup sntp
 * Gets the server reachability shift register as described in RFC 5905.
 *
 * @param idx the index of the NTP server
 */
u8_t
sntp_getreachability(u8_t idx)
{
  if (idx < SNTP_MAX_SERVERS) {
    return sntp_servers[idx].reachability;
  }
  return 0;
}
#endif /* SNTP_MONITOR_SERVER_REACHABILITY */

#if SNTP_GET_SERVERS_FROM_DHCP
/**
 * Config SNTP server handling by IP address, name, or DHCP; clear table
 * @param set_servers_from_dhcp enable or disable getting server addresses from dhcp
 */
void
sntp_servermode_dhcp(int set_servers_from_dhcp)
{
  u8_t new_mode = set_servers_from_dhcp ? 1 : 0;
  LWIP_ASSERT_CORE_LOCKED();
  if (sntp_set_servers_from_dhcp != new_mode) {
    sntp_set_servers_from_dhcp = new_mode;
  }
}
#endif /* SNTP_GET_SERVERS_FROM_DHCP */

/**
 * @ingroup sntp
 * Initialize one of the NTP servers by IP address
 *
 * @param idx the index of the NTP server to set must be < SNTP_MAX_SERVERS
 * @param server IP address of the NTP server to set
 */
void
sntp_setserver(u8_t idx, const ip_addr_t *server)
{
  LWIP_ASSERT_CORE_LOCKED();
  if (idx < SNTP_MAX_SERVERS) {
    if (server != NULL) {
      sntp_servers[idx].addr = (*server);
    } else {
      ip_addr_set_zero(&sntp_servers[idx].addr);
    }
#if SNTP_SERVER_DNS
    sntp_servers[idx].name = NULL;
#endif
  }
}

#if LWIP_DHCP && SNTP_GET_SERVERS_FROM_DHCP
/**
 * Initialize one of the NTP servers by IP address, required by DHCP
 *
 * @param num the index of the NTP server to set must be < SNTP_MAX_SERVERS
 * @param server IP address of the NTP server to set
 */
void
dhcp_set_ntp_servers(u8_t num, const ip4_addr_t *server)
{
  LWIP_DEBUGF(SNTP_DEBUG_TRACE, ("sntp: %s %u.%u.%u.%u as NTP server #%u via DHCP\n",
                                 (sntp_set_servers_from_dhcp ? "Got" : "Rejected"),
                                 ip4_addr1(server), ip4_addr2(server), ip4_addr3(server), ip4_addr4(server), num));
  if (sntp_set_servers_from_dhcp && num) {
    u8_t i;
    for (i = 0; (i < num) && (i < SNTP_MAX_SERVERS); i++) {
      ip_addr_t addr;
      ip_addr_copy_from_ip4(addr, server[i]);
      sntp_setserver(i, &addr);
    }
    for (i = num; i < SNTP_MAX_SERVERS; i++) {
      sntp_setserver(i, NULL);
    }
  }
}
#endif /* LWIP_DHCP && SNTP_GET_SERVERS_FROM_DHCP */

/**
 * @ingroup sntp
 * Obtain one of the currently configured by IP address (or DHCP) NTP servers
 *
 * @param idx the index of the NTP server
 * @return IP address of the indexed NTP server or "ip_addr_any" if the NTP
 *         server has not been configured by address (or at all).
 */
const ip_addr_t *
sntp_getserver(u8_t idx)
{
  if (idx < SNTP_MAX_SERVERS) {
    return &sntp_servers[idx].addr;
  }
  return IP_ADDR_ANY;
}

#if SNTP_SERVER_DNS
/**
 * Initialize one of the NTP servers by name
 *
 * @param idx the index of the NTP server to set must be < SNTP_MAX_SERVERS
 * @param server DNS name of the NTP server to set, to be resolved at contact time
 */
void
sntp_setservername(u8_t idx, const char *server)
{
  LWIP_ASSERT_CORE_LOCKED();
  if (idx < SNTP_MAX_SERVERS) {
    sntp_servers[idx].name = server;
  }
}

/**
 * Obtain one of the currently configured by name NTP servers.
 *
 * @param idx the index of the NTP server
 * @return IP address of the indexed NTP server or NULL if the NTP
 *         server has not been configured by name (or at all)
 */
const char *
sntp_getservername(u8_t idx)
{
  if (idx < SNTP_MAX_SERVERS) {
    return sntp_servers[idx].name;
  }
  return NULL;
}
#endif /* SNTP_SERVER_DNS */

#endif /* LWIP_UDP */