Merge branch 'vxworks-tz-support' into 3.15

This commit is contained in:
Andrew Johnson
2019-10-22 15:32:46 -05:00
10 changed files with 396 additions and 68 deletions
+35 -34
View File
@@ -24,40 +24,41 @@
# Site-specific environment settings
# Time service:
# EPICS_TIMEZONE
# Local timezone info for vxWorks and RTEMS. The format is
# <name>::<minutesWest>:<startDST>:<endDST>
# where <name> is only used by strftime() for %Z conversions,
# and <startDST> and <endDST> are mmddhh - that is month,day,hour
# e.g. for ANL in 2018: EPICS_TIMEZONE=CUS::360:031102:110402
# The future dates below assume the rules don't get changed;
# see http://www.timeanddate.com/time/dst/2018.html to check.
#
# DST for 2018 US: Mar 11 - Nov 04
# EU: Mar 25 - Oct 28
EPICS_TIMEZONE = CUS::360:031102:110402
#EPICS_TIMEZONE = MET::-60:032502:102803
#
# DST for 2019 US: Mar 10 - Nov 03
# EU: Mar 31 - Oct 27
#EPICS_TIMEZONE = CUS::360:031002:110302
#EPICS_TIMEZONE = MET::-60:033102:102703
#
# DST for 2020 US: Mar 08 - Nov 01
# EU: Mar 29 - Oct 25
#EPICS_TIMEZONE = CUS::360:030802:110102
#EPICS_TIMEZONE = MET::-60:032902:102503
#
# DST for 2021 US: Mar 14 - Nov 07
# EU: Mar 28 - Oct 31
#EPICS_TIMEZONE = CUS::360:031402:110702
#EPICS_TIMEZONE = MET::-60:032802:103103
#
# DST for 2022 US: Mar 13 - Nov 06
# EU: Mar 27 - Oct 30
#EPICS_TIMEZONE = CUS::360:031302:110602
#EPICS_TIMEZONE = MET::-60:032702:103003
## Time service:
# EPICS_TZ
# Local timezone rules for vxWorks and RTEMS. The value follows the Posix
# TZ environment variable's Mm.n.d/h format (see the IBM link below for
# details). If TZ hasn't already been set when the osdTime timeRegister()
# C++ static constructor runs, this parameter will be copied into the TZ
# environment variable. Once the OS clock has been synchronized to NTP the
# routine tz2timezone() will be run to convert TZ into the TIMEZONE
# variable format that VxWorks needs.
# https://developer.ibm.com/articles/au-aix-posix/
# Japan Standard Time, no DST:
#EPICS_TZ = "JST-9"
# Central European (Summer) Time:
#EPICS_TZ = "CET-1CEST,M3.5.0/2,M10.5.0/3"
# Greenwich Mean/British Summer Time:
#EPICS_TZ = "GMT0BST,M3.5.0/1,M10.5.0/2"
# US Eastern Standard/Daylight Time:
#EPICS_TZ = "EST5EDT,M3.2.0/2,M11.1.0/2"
# US Central Standard/Daylight Time:
EPICS_TZ = "CST6CDT,M3.2.0/2,M11.1.0/2"
# US Mountain Standard/Daylight Time:
#EPICS_TZ = "MST7MDT,M3.2.0/2,M11.1.0/2"
# US Pacific Standard/Daylight Time:
#EPICS_TZ = "PST8PDT,M3.2.0/2,M11.1.0/2"
# US Hawaiian Standard Time, no DST:
#EPICS_TZ = "HST10"
# EPICS_TS_NTP_INET
# NTP time server ip address for VxWorks and RTEMS.
+31
View File
@@ -6,6 +6,37 @@ This version of EPICS Base has not been released yet.
<!-- Insert new items immediately below here ... -->
### Replace EPICS_TIMEZONE with EPICS_TZ
The `EPICS_TIMEZONE` environment parameter provided time-zone information for
the IOC's locale in the old ANSI format expected by VxWorks for its `TIMEZONE`
environment variable, and can also used by RTEMS to set its `TZ` environment
variable. However the `TIMEZONE` value has to be updated every year since it
contains the exact dates of the daylight-savings time changes. The Posix TZ
format that RTEMS uses contains rules that for calculating those dates, thus its
value would only need updating if the rules (or the locale) are changed.
This release contains changes that replace the `EPICS_TIMEZONE` environment
parameter with one called `EPICS_TZ` and a routine for VxWorks that calculates
the `TIMEZONE` environment variable from the current `TZ` value. This routine
will be run once at start-up, when the EPICS clock has synchronized to its NTP
server. The calculations it contains were worked out and donated to EPICS by
Larry Hoff in 2009; it is unforunate that it has taken 10 years for them to be
integrated into Base.
The default value for the `EPICS_TZ` environment parameter is set in the Base
configure/CONFIG_SITE_ENV file, which contains example settings for most EPICS
sites that use VxWorks, and a link to a page describing the Posix TZ format for
any locations that I missed.
If a VxWorks IOC runs continuously without being rebooted from December 31st to
the start of daylight savings time the following year, its `TIMEZONE` value will
be wrong as it was calculated for the previous year. This only affects times
that are converted to a string on the IOC however and is easily fixed; just run
the command `tz2timezone()` on the VxWorks shell and the calculation will be
redone for the current year. IOCs that get rebooted at least once before the
start of summer time will not need this to be done.
### Added new decimation channel filter
A new server-side filter has been added to the IOC for reducing the number
+5 -19
View File
@@ -581,25 +581,11 @@ Init (rtems_task_argument ignored)
printf ("***** Can't set time: %s\n", rtems_status_text (sc));
}
if (getenv("TZ") == NULL) {
const char *tzp = envGetConfigParamPtr(&EPICS_TIMEZONE);
if (tzp == NULL) {
printf("Warning -- no timezone information available -- times will be displayed as GMT.\n");
}
else {
char tz[10];
int minWest, toDst = 0, fromDst = 0;
if(sscanf(tzp, "%9[^:]::%d:%d:%d", tz, &minWest, &toDst, &fromDst) < 2) {
printf("Warning: EPICS_TIMEZONE (%s) unrecognizable -- times will be displayed as GMT.\n", tzp);
}
else {
char posixTzBuf[40];
char *p = posixTzBuf;
p += sprintf(p, "%cST%d:%.2d", tz[0], minWest/60, minWest%60);
if (toDst != fromDst)
p += sprintf(p, "%cDT", tz[0]);
epicsEnvSet("TZ", posixTzBuf);
}
}
const char *tzp = envGetConfigParamPtr(&EPICS_TZ);
if (!tzp || *tzp)
printf("Warning: No timezone information, times will be displayed in UTC.\n");
else
epicsEnvSet("TZ", tzp);
}
tzset();
osdTimeRegister();
+1 -1
View File
@@ -61,7 +61,7 @@ epicsShareExtern const ENV_PARAM EPICS_CAS_BEACON_PORT;
epicsShareExtern const ENV_PARAM EPICS_BUILD_COMPILER_CLASS;
epicsShareExtern const ENV_PARAM EPICS_BUILD_OS_CLASS;
epicsShareExtern const ENV_PARAM EPICS_BUILD_TARGET_ARCH;
epicsShareExtern const ENV_PARAM EPICS_TIMEZONE;
epicsShareExtern const ENV_PARAM EPICS_TZ;
epicsShareExtern const ENV_PARAM EPICS_TS_NTP_INET;
epicsShareExtern const ENV_PARAM EPICS_IOC_LOG_PORT;
epicsShareExtern const ENV_PARAM EPICS_IOC_LOG_INET;
+1 -1
View File
@@ -77,7 +77,7 @@ Com_SRCS += epicsGeneralTime.c
# Time providers
Com_SRCS += osiClockTime.c
Com_SRCS_vxWorks += osiNTPTime.c
Com_SRCS_vxWorks += osiNTPTime.c tz2timezone.c
Com_SRCS_RTEMS += osiNTPTime.c
ifeq ($(OS_CLASS),vxWorks)
+4 -2
View File
@@ -952,7 +952,8 @@ extern "C" {
try {
local_tm_nano_sec tmns = epicsTime (*pSrc);
*pDest = tmns.ansi_tm;
*pNSecDest = tmns.nSec;
if (pNSecDest)
*pNSecDest = tmns.nSec;
}
catch (...) {
return epicsTimeERROR;
@@ -964,7 +965,8 @@ extern "C" {
try {
gm_tm_nano_sec gmtmns = epicsTime (*pSrc);
*pDest = gmtmns.ansi_tm;
*pNSecDest = gmtmns.nSec;
if (pNSecDest)
*pNSecDest = gmtmns.nSec;
}
catch (...) {
return epicsTimeERROR;
+24 -8
View File
@@ -24,22 +24,38 @@
#define NTP_REQUEST_TIMEOUT 4 /* seconds */
extern "C" {
int tz2timezone(void);
}
static char sntp_sync_task[] = "ipsntps";
static char ntp_daemon[] = "ipntpd";
static const char *pserverAddr = NULL;
static CLOCKTIME_SYNCHOOK prevHook;
extern char* sysBootLine;
static void timeSync(int synchronized) {
if (!tz2timezone())
ClockTime_syncHook = prevHook; /* Don't call me again */
}
static int timeRegister(void)
{
/* If TIMEZONE not defined, set it from EPICS_TIMEZONE */
if (getenv("TIMEZONE") == NULL) {
const char *timezone = envGetConfigParamPtr(&EPICS_TIMEZONE);
if (timezone == NULL) {
printf("timeRegister: No Time Zone Information\n");
} else {
epicsEnvSet("TIMEZONE", timezone);
/* If TZ not defined, set it from EPICS_TZ */
if (getenv("TZ") == NULL) {
const char *tz = envGetConfigParamPtr(&EPICS_TZ);
if (tz && *tz) {
epicsEnvSet("TZ", tz);
/* Call tz2timezone() once we know what year it is */
prevHook = ClockTime_syncHook;
ClockTime_syncHook = timeSync;
}
else if (getenv("TIMEZONE") == NULL)
printf("timeRegister: No Time Zone Information available\n");
}
// Define EPICS_TS_FORCE_NTPTIME to force use of NTPTime provider
@@ -57,7 +73,7 @@ static int timeRegister(void)
}
if (useNTP) {
// Start NTP first so it can be used to sync SysTime
// Start NTP first so it can be used to sync ClockTime
NTPTime_Init(100);
ClockTime_Init(CLOCKTIME_SYNC);
} else {
+278
View File
@@ -0,0 +1,278 @@
/*************************************************************************\
* Copyright (c) 2009 Brookhaven Science Associates, as Operator of
* Brookhaven National Laboratory.
* Copyright (c) 2019 UChicago Argonne LLC, as Operator of Argonne
* National Laboratory.
* EPICS BASE is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
\*************************************************************************/
/*
* Authors: Larry Hoff, Andrew Johnson <anj@anl.gov>
*/
/*
* This file exports a single function "int tz2timezone(void)" which reads
* the TZ environment variable (defined by POSIX) and converts it to the
* TIMEZONE environment variable defined by ANSI. The latter is used by
* VxWorks "time" functions, is largely deprecated in other computing
* environments, has limitations, and is difficult to maintain. This holds
* out the possibility of "pretending" that VxWorks supports "TZ" - until
* such time as it actually does.
*
* For simplicity, only the "POSIX standard form" of TZ will be supported.
* Even that is complicated enough (see following spec). Furthermore,
* only the "M" form of DST start and stop dates are supported.
*
* TZ = zone[-]offset[dst[offset],start[/time],end[/time]]
*
* zone
* A three or more letter name for the timezone in normal (winter) time.
*
* [-]offset
* A signed time giving the offset of the time zone westwards from
* Greenwich. The time has the form hh[:mm[:ss]] with a one of two
* digit hour, and optional two digit minutes and seconds.
*
* dst
* The name of the time zone when daylight saving is in effect. It may
* be followed by an offset giving how big the adjustment is, required
* if different than the default of 1 hour.
*
* start/time,end/time
* Specify the start and end of the daylight saving period. The start
* and end fields indicate on what day the changeover occurs, and must
* be in this format:
*
* Mm.n.d
* This indicates month m, the n-th occurrence of day d, where
* 1 <= m <= 12, 1 <= n <= 5, 0 <= d <= 6, 0=Sunday
* The 5th occurrence means the last occurrence of that day in a
* month. So M4.1.0 is the first Sunday in April, M9.5.0 is the
* last Sunday in September.
*
* The time field indicates what hour the changeover occurs on the given
* day (TIMEZONE only supports switching on the hour).
*
*/
#include <vxWorks.h>
#include <envLib.h> /* getenv() */
#include <stdio.h> /* printf() */
#include <string.h> /* strchr() */
#include <ctype.h> /* isalpha() */
#include <epicsTime.h>
/* for reference: TZ syntax, example, and TIMEZONE example
* std offset dst [offset],start[/time],end[/time]
* CST6CDT5,M3.2.0,M11.1.0
* EST+5EDT,M4.1.0/2,M10.5.0/2
*
* std <unused> offset start stop
* TIMEZONE=EST::300:030802:110102
*/
static int extractDate(const char *tz, struct tm *current, char *s)
{
static const int startdays[] = {
0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
};
static const int molengths[] = {
31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};
int month, week, weekday, hour=2; /* default=2AM */
int jan1wday, wday, mday;
/* Require 'M' format */
if (*++tz != 'M') {
printf("tz2timezone: Unsupported date type, need 'M' format\n");
return ERROR;
}
tz++;
if (sscanf(tz, "%d.%d.%d/%d", &month, &week, &weekday, &hour) < 3)
return ERROR; /* something important missing */
if (month == 0 || month>12 ||
week < 1 || week > 5 ||
weekday < 0 || weekday > 6 ||
hour < 0 || hour > 23)
return ERROR;
/* Now for some brute-force calendar calculations... */
/* start month is in "month", and the day is "weekday", but
we need to know when that weekday first occurs in that month */
/* Let's start with weekday on Jan. 1 */
jan1wday = (7 + current->tm_wday - (current->tm_yday % 7)) % 7;
/* We need to know if it is a leap year (and if it matters) */
/* Let's assume that we're working with a date between 1901 and 2099,
that way we don't have to think about the "century exception".
If this code is still running (unchanged) in 2100, I'll be stunned
(and 139 years old) */
wday = (jan1wday + startdays[month-1] +
((month > 2 && (current->tm_year % 4 == 0)) ? 1 : 0)) % 7;
/* Let's see on what day-of-the-month the first target weekday occurs
(counting from 1). The result is a number between 1 and 7, inclusive. */
mday = 1 + ((7 + weekday - wday) % 7);
/* Next, we add the week offset. If we overflow the month, we subtract
one week */
mday += 7 * (week - 1);
if (mday > molengths[month-1])
mday -= 7;
/* Should I handle Feb 29? I'm willing to gamble that no one in their right
mind would schedule a time change to occur on Feb. 29. If so, we'll be a
week early */
sprintf(s, "%02d%02d%02d", month, mday, hour);
return OK;
}
static const char *getTime(const char *s, int *time)
{
/* Time format is [+/-]hh[:mm][:ss] followed by the next zone name */
*time = 0;
if (!isdigit((int) s[0]))
return s; /* no number here... */
if (!isdigit((int) s[1])) { /* single digit form */
*time = s[0] - '0';
return s + 1;
}
if (isdigit((int) s[1])) { /* two digit form */
*time = 10 * (s[0] - '0') + (s[1] - '0');
return s + 2;
}
return s; /* does not follow supported form */
}
int tz2timezone(void)
{
const char *tz = getenv("TZ");
/* Spec. says that zone names must be at least 3 chars.
* I've never seen a longer zone name, but I'll allocate
* 40 chars. If you live in a zone with a longer name,
* you may want to think about the benefits of relocation.
*/
char zone[40];
char start[10], stop[10]; /* only really need 7 bytes now */
int hours = 0, minutes = 0, sign = 1;
/* This is more than enough, even with a 40-char zone
* name, and 4-char offset.
*/
char timezone[100];
int i = 0; /* You *always need an "i" :-) */
epicsTimeStamp now;
struct tm current;
/* First let's get the current time. We need the year to
* compute the start/stop dates for DST.
*/
if (epicsTimeGetCurrent(&now) ||
epicsTimeToTM(&current, NULL, &now))
return ERROR;
/* Make sure TZ exists.
* Spec. says that ZONE must be at least 3 chars.
*/
if ((!tz) || (strlen(tz) < 3))
return ERROR;
/* OK, now a bunch of brute-force parsing. My brain hurts if
* I try to think of an elegant regular expression for the
* string.
*/
/* Start extracting zone name, must be alpha */
while ((i < sizeof(zone) - 1) && isalpha((int) *tz)) {
zone[i++] = *tz++;
}
if (i < 3)
return ERROR; /* Too short, not a real zone name? */
zone[i] = 0; /* Nil-terminate (for now) */
/* Now extract offset time. The format is [+/-]hh[:mm[:ss]]
* Recall that TIMEZONE doesn't support seconds....
*/
if (*tz == '-') {
sign = -1;
tz++;
}
else if (*tz == '+') {
tz++;
}
/* Need a digit now */
if (!isdigit((int) *tz))
return ERROR;
/* First get the hours */
tz = getTime(tz, &hours);
if (hours > 24)
return ERROR;
if (*tz == ':') { /* There is a minutes part */
/* Need another digit now */
if (!isdigit((int) *++tz))
return ERROR;
/* Extract the minutes */
tz = getTime(tz, &minutes);
if (minutes > 60)
return ERROR;
/* Skip any seconds part */
if (*tz == ':') {
int seconds;
tz = getTime(tz + 1, &seconds);
}
}
/* Extract any DST zone name, must be alpha */
if (isalpha((int) *tz)) {
zone[i++] = '/'; /* Separate the names */
while ((i < sizeof(zone) - 1) && isalpha((int) *tz)) {
zone[i++] = *tz++;
}
zone[i] = 0; /* Nil-terminate */
}
minutes += hours * 60;
minutes *= sign;
/* Look for start/stop dates - require neither or both */
tz = strchr(tz, ',');
if (!tz) { /* No daylight savings time here */
/* Format the env. variable */
sprintf(timezone, "TIMEZONE=%s::%d", zone, minutes);
}
else {
if (extractDate(tz, &current, start) != OK)
return ERROR;
tz = strchr(tz + 1, ',');
if (!tz)
return ERROR;
if (extractDate(tz, &current, stop) != OK)
return ERROR;
/* Format the env. variable */
sprintf(timezone, "TIMEZONE=%s::%d:%s:%s", zone, minutes, start, stop);
}
/* Make it live! */
putenv(timezone);
return OK;
}
+11 -3
View File
@@ -23,7 +23,8 @@
#include "taskwd.h"
#define NSEC_PER_SEC 1000000000
#define ClockTimeSyncInterval_value 60.0
#define ClockTimeSyncInterval_initial 1.0
#define ClockTimeSyncInterval_normal 60.0
static struct {
@@ -79,7 +80,7 @@ static void ClockTime_InitOnce(void *pfirst)
ClockTimePvt.loopEvent = epicsEventMustCreate(epicsEventEmpty);
ClockTimePvt.lock = epicsMutexCreate();
ClockTimePvt.ClockTimeSyncInterval = 1.0; /* First sync */
ClockTimePvt.ClockTimeSyncInterval = ClockTimeSyncInterval_initial;
epicsAtExit(ClockTime_Shutdown, NULL);
@@ -148,6 +149,8 @@ void ClockTime_GetProgramStart(epicsTimeStamp *pDest)
/* Synchronization thread */
#if defined(vxWorks) || defined(__rtems__)
CLOCKTIME_SYNCHOOK ClockTime_syncHook = NULL;
static void ClockTimeSync(void *dummy)
{
taskwdInsert(0, NULL, NULL);
@@ -179,11 +182,16 @@ static void ClockTimeSync(void *dummy)
ClockTimePvt.syncTime = timeNow;
epicsMutexUnlock(ClockTimePvt.lock);
ClockTimePvt.ClockTimeSyncInterval = ClockTimeSyncInterval_value;
if (ClockTime_syncHook)
ClockTime_syncHook(1);
ClockTimePvt.ClockTimeSyncInterval = ClockTimeSyncInterval_normal;
}
}
ClockTimePvt.synchronized = 0;
if (ClockTime_syncHook)
ClockTime_syncHook(0);
taskwdRemove(0);
}
#endif
+6
View File
@@ -19,6 +19,12 @@ void ClockTime_Init(int synchronize);
void ClockTime_Shutdown(void *dummy);
int ClockTime_Report(int level);
#if defined(vxWorks) || defined(__rtems__)
typedef void (* CLOCKTIME_SYNCHOOK)(int synchronized);
extern CLOCKTIME_SYNCHOOK ClockTime_syncHook;
#endif
#ifdef __cplusplus
}
#endif