This is Unofficial EPICS BASE Doxygen Site
tz2timezone.c
Go to the documentation of this file.
1 /*************************************************************************\
2 * Copyright (c) 2009 Brookhaven Science Associates, as Operator of
3 * Brookhaven National Laboratory.
4 * Copyright (c) 2019 UChicago Argonne LLC, as Operator of Argonne
5 * National Laboratory.
6 * EPICS BASE is distributed subject to a Software License Agreement found
7 * in file LICENSE that is included with this distribution.
8 \*************************************************************************/
9 /*
10  * Authors: Larry Hoff, Andrew Johnson <anj@anl.gov>
11  */
12 
13 /*
14  * This file exports a single function "int tz2timezone(void)" which reads
15  * the TZ environment variable (defined by POSIX) and converts it to the
16  * TIMEZONE environment variable defined by ANSI. The latter is used by
17  * VxWorks "time" functions, is largely deprecated in other computing
18  * environments, has limitations, and is difficult to maintain. This holds
19  * out the possibility of "pretending" that VxWorks supports "TZ" - until
20  * such time as it actually does.
21  *
22  * For simplicity, only the "POSIX standard form" of TZ will be supported.
23  * Even that is complicated enough (see following spec). Furthermore,
24  * only the "M" form of DST start and stop dates are supported.
25  *
26  * TZ = zone[-]offset[dst[offset],start[/time],end[/time]]
27  *
28  * zone
29  * A three or more letter name for the timezone in normal (winter) time.
30  *
31  * [-]offset
32  * A signed time giving the offset of the time zone westwards from
33  * Greenwich. The time has the form hh[:mm[:ss]] with a one of two
34  * digit hour, and optional two digit minutes and seconds.
35  *
36  * dst
37  * The name of the time zone when daylight saving is in effect. It may
38  * be followed by an offset giving how big the adjustment is, required
39  * if different than the default of 1 hour.
40  *
41  * start/time,end/time
42  * Specify the start and end of the daylight saving period. The start
43  * and end fields indicate on what day the changeover occurs, and must
44  * be in this format:
45  *
46  * Mm.n.d
47  * This indicates month m, the n-th occurrence of day d, where
48  * 1 <= m <= 12, 1 <= n <= 5, 0 <= d <= 6, 0=Sunday
49  * The 5th occurrence means the last occurrence of that day in a
50  * month. So M4.1.0 is the first Sunday in April, M9.5.0 is the
51  * last Sunday in September.
52  *
53  * The time field indicates what hour the changeover occurs on the given
54  * day (TIMEZONE only supports switching on the hour).
55  *
56  */
57 
58 #include <vxWorks.h>
59 #include <envLib.h> /* getenv() */
60 #include <stdio.h> /* printf() */
61 #include <string.h> /* strchr() */
62 #include <ctype.h> /* isalpha() */
63 
64 #include <epicsTime.h>
65 
66 /* for reference: TZ syntax, example, and TIMEZONE example
67  * std offset dst [offset],start[/time],end[/time]
68  * CST6CDT5,M3.2.0,M11.1.0
69  * EST+5EDT,M4.1.0/2,M10.5.0/2
70  *
71  * std <unused> offset start stop
72  * TIMEZONE=EST::300:030802:110102
73  */
74 
75 static int extractDate(const char *tz, struct tm *current, char *s)
76 {
77  static const int startdays[] = {
78  0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
79  };
80  static const int molengths[] = {
81  31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
82  };
83 
84  int month, week, weekday, hour=2; /* default=2AM */
85  int jan1wday, wday, mday;
86 
87  /* Require 'M' format */
88  if (*++tz != 'M') {
89  printf("tz2timezone: Unsupported date type, need 'M' format\n");
90  return ERROR;
91  }
92  tz++;
93 
94  if (sscanf(tz, "%d.%d.%d/%d", &month, &week, &weekday, &hour) < 3)
95  return ERROR; /* something important missing */
96 
97  if (month == 0 || month>12 ||
98  week < 1 || week > 5 ||
99  weekday < 0 || weekday > 6 ||
100  hour < 0 || hour > 23)
101  return ERROR;
102 
103  /* Now for some brute-force calendar calculations... */
104  /* start month is in "month", and the day is "weekday", but
105  we need to know when that weekday first occurs in that month */
106  /* Let's start with weekday on Jan. 1 */
107  jan1wday = (7 + current->tm_wday - (current->tm_yday % 7)) % 7;
108 
109  /* We need to know if it is a leap year (and if it matters) */
110  /* Let's assume that we're working with a date between 1901 and 2099,
111  that way we don't have to think about the "century exception".
112  If this code is still running (unchanged) in 2100, I'll be stunned
113  (and 139 years old) */
114  wday = (jan1wday + startdays[month-1] +
115  ((month > 2 && (current->tm_year % 4 == 0)) ? 1 : 0)) % 7;
116 
117  /* Let's see on what day-of-the-month the first target weekday occurs
118  (counting from 1). The result is a number between 1 and 7, inclusive. */
119  mday = 1 + ((7 + weekday - wday) % 7);
120 
121  /* Next, we add the week offset. If we overflow the month, we subtract
122  one week */
123  mday += 7 * (week - 1);
124  if (mday > molengths[month-1])
125  mday -= 7;
126 
127  /* Should I handle Feb 29? I'm willing to gamble that no one in their right
128  mind would schedule a time change to occur on Feb. 29. If so, we'll be a
129  week early */
130  sprintf(s, "%02d%02d%02d", month, mday, hour);
131 
132  return OK;
133 }
134 
135 
136 static const char *getTime(const char *s, int *time)
137 {
138  /* Time format is [+/-]hh[:mm][:ss] followed by the next zone name */
139 
140  *time = 0;
141 
142  if (!isdigit((int) s[0]))
143  return s; /* no number here... */
144 
145  if (!isdigit((int) s[1])) { /* single digit form */
146  *time = s[0] - '0';
147  return s + 1;
148  }
149 
150  if (isdigit((int) s[1])) { /* two digit form */
151  *time = 10 * (s[0] - '0') + (s[1] - '0');
152  return s + 2;
153  }
154 
155  return s; /* does not follow supported form */
156 }
157 
158 int tz2timezone(void)
159 {
160  const char *tz = getenv("TZ");
161  /* Spec. says that zone names must be at least 3 chars.
162  * I've never seen a longer zone name, but I'll allocate
163  * 40 chars. If you live in a zone with a longer name,
164  * you may want to think about the benefits of relocation.
165  */
166  char zone[40];
167  char start[10], stop[10]; /* only really need 7 bytes now */
168  int hours = 0, minutes = 0, sign = 1;
169  /* This is more than enough, even with a 40-char zone
170  * name, and 4-char offset.
171  */
172  char timezone[100];
173  int i = 0; /* You *always need an "i" :-) */
174  epicsTimeStamp now;
175  struct tm current;
176 
177  /* First let's get the current time. We need the year to
178  * compute the start/stop dates for DST.
179  */
180  if (epicsTimeGetCurrent(&now) ||
181  epicsTimeToTM(&current, NULL, &now))
182  return ERROR;
183 
184  /* Make sure TZ exists.
185  * Spec. says that ZONE must be at least 3 chars.
186  */
187  if ((!tz) || (strlen(tz) < 3))
188  return ERROR;
189 
190  /* OK, now a bunch of brute-force parsing. My brain hurts if
191  * I try to think of an elegant regular expression for the
192  * string.
193  */
194 
195  /* Start extracting zone name, must be alpha */
196  while ((i < sizeof(zone) - 1) && isalpha((int) *tz)) {
197  zone[i++] = *tz++;
198  }
199  if (i < 3)
200  return ERROR; /* Too short, not a real zone name? */
201 
202  zone[i] = 0; /* Nil-terminate (for now) */
203 
204  /* Now extract offset time. The format is [+/-]hh[:mm[:ss]]
205  * Recall that TIMEZONE doesn't support seconds....
206  */
207  if (*tz == '-') {
208  sign = -1;
209  tz++;
210  }
211  else if (*tz == '+') {
212  tz++;
213  }
214 
215  /* Need a digit now */
216  if (!isdigit((int) *tz))
217  return ERROR;
218 
219  /* First get the hours */
220  tz = getTime(tz, &hours);
221  if (hours > 24)
222  return ERROR;
223 
224  if (*tz == ':') { /* There is a minutes part */
225  /* Need another digit now */
226  if (!isdigit((int) *++tz))
227  return ERROR;
228 
229  /* Extract the minutes */
230  tz = getTime(tz, &minutes);
231  if (minutes > 60)
232  return ERROR;
233 
234  /* Skip any seconds part */
235  if (*tz == ':') {
236  int seconds;
237  tz = getTime(tz + 1, &seconds);
238  }
239  }
240 
241  /* Extract any DST zone name, must be alpha */
242  if (isalpha((int) *tz)) {
243  zone[i++] = '/'; /* Separate the names */
244 
245  while ((i < sizeof(zone) - 1) && isalpha((int) *tz)) {
246  zone[i++] = *tz++;
247  }
248  zone[i] = 0; /* Nil-terminate */
249  }
250 
251  minutes += hours * 60;
252  minutes *= sign;
253 
254  /* Look for start/stop dates - require neither or both */
255  tz = strchr(tz, ',');
256  if (!tz) { /* No daylight savings time here */
257  /* Format the env. variable */
258  sprintf(timezone, "TIMEZONE=%s::%d", zone, minutes);
259  }
260  else {
261  if (extractDate(tz, &current, start) != OK)
262  return ERROR;
263 
264  tz = strchr(tz + 1, ',');
265  if (!tz)
266  return ERROR;
267  if (extractDate(tz, &current, stop) != OK)
268  return ERROR;
269 
270  /* Format the env. variable */
271  sprintf(timezone, "TIMEZONE=%s::%d:%s:%s", zone, minutes, start, stop);
272  }
273 
274  /* Make it live! */
275  putenv(timezone);
276 
277  return OK;
278 }
int i
Definition: scan.c:967
#define printf
Definition: epicsStdio.h:41
#define NULL
Definition: catime.c:38
int tz2timezone(void)
Definition: tz2timezone.c:158
int epicsStdCall epicsTimeGetCurrent(epicsTimeStamp *pDest)
Get current time into *pDest.
int putenv(char *)
EPICS time stamp, for use from C code.
Definition: epicsTime.h:33
EPICS time-stamps (epicsTimeStamp), epicsTime C++ class and C functions for handling wall-clock times...
LIBCOM_API int epicsStdCall epicsTimeToTM(struct tm *pDest, unsigned long *pNSecDest, const epicsTimeStamp *pSrc)
Convert epicsTimeStamp to struct tm in local time zone.
Definition: epicsTime.cpp:956