STM32 RTC Time Zone Conversion with Linux EPOCH Time

Overview

In this tutorial, we are implementing STM32 RTC Time Zone Conversion with Linux EPOCH Time by converting RTC output to local time using TZ. In a previous tutorial, STM32 FreeRTOS SNTP – RTC Time Synchronization, we learned how to configure a network connection using DNS and SNTP to retrieve time from a public NTP server. That response was then used to initialize and keep the internal Real Time Clock (RTC) in sync.

While this approach worked, the project had a few limitations. We could not display Linux EPOCH UTC time, nor could we convert and display time for a specific time zone. In this tutorial, we will address both limitations by adding support for Linux EPOCH UTC time and allowing configurable time zone conversion.

This tutorial also works around several limitations of standard C library time routines. These functions rely on the TZ environment variable, which must be set using setenv(), for example: setenv(“TZ”, “America/Los_Angeles”). Additionally, the system must include a full time zone database, including daylight savings rules and transition timing.

To address these constraints in an embedded environment, this project includes a static time zone list and custom parsing logic. The files tzParse.c and tzParse.h implement functionality derived from standard C library routines to handle time zone parsing and daylight savings calculations.

There are several reasons for duplicating portions of the standard C library:

1) Many time conversion functions depend on the global TZ environment variable. Changing this value affects all subsequent conversions, making it difficult to reliably obtain UTC EPOCH time without resetting TZ.

2) The use of setenv() and unsetenv() is not well-suited for embedded systems. These functions can lead to memory issues and are not thread-safe in an RTOS environment.

3) To avoid these limitations, TZ is set once for the desired local time zone. Conversions from RTC_TimeTypeDef and RTC_DateTypeDef back to UTC are then performed using calculated offsets rather than relying on repeated environment changes.

4) Some required functionality within the C library is private or static and not accessible externally. As a result, portions of this logic were duplicated to enable time zone parsing and daylight savings handling.

Converting RTC output to Local Time with TZ and Linux EPOCH Time allows a designer to display accurate local time while maintaining sub-second RTC updates.

What You Will Learn

  • How to convert STM32 RTC date and time values into Linux EPOCH time.
  • How to display UTC time and local time from the same RTC-based time source.
  • How the TZ environment variable and time zone rules affect local time conversion.
  • Why repeated setenv() and unsetenv() calls can be problematic in an RTOS-based embedded project.
  • How custom timezone parsing files such as tzParse.c and tzParse.h are used to handle offsets and daylight savings time.

Prerequisites

This tutorial assumes you already have the earlier FreeRTOS, LWIP, DNS, SNTP, and RTC synchronization project working on the Nucleo-F439ZI. The RTC should already be initialized from an NTP/SNTP time source before adding the local time and Linux EPOCH conversion logic.

Materials List

Project Structure

Project Structure and Folder Layout

Hardware Configuration / Pinouts

FTDI Console Interface

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.

FTDI USART to USB
FTDI USART to USB pin outs

Nucleo-F439ZI Ethernet Requirement

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.

Nucleo-F439ZI

Schematic

Project Schematic

CubeMX Configuration

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 “*

Project Setup

Start from the existing RTC synchronization project that uses FreeRTOS, LWIP, DNS, and SNTP. Confirm that the board has Ethernet connectivity and that the RTC is being updated from a valid network time source before adding the timezone conversion code.

The generated IOC configuration PDF is included above so the key CubeMX settings can be reviewed before comparing the source code changes.

Code Walkthrough

The source code below shows the project files used to convert RTC output to UTC EPOCH time and local time. The custom timezone parsing logic is used to avoid repeated global environment changes while still supporting time zone offsets and daylight savings time rules.

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

Location of the source file sntp.c in the project where #include <mysntp.h> must be placed at the top of the file.” class=”wp-image-5138″/></figure>


<p><!-- wp:kevinbatdorf/code-block-pro {

If you have questions or run into trouble getting the boards programmed and talking to each other, post in the Tutorial Support forum and I will work through it with you. If project source is not linked in the tutorial, it may be available on request — use the email contact option in the site footer.