Merge lp:~epics-core/epics-base/add-epicstime-from-gmtm into lp:~epics-core/epics-base/3.14

Proposed by Ralph Lange
Status: Merged
Merged at revision: 12665
Proposed branch: lp:~epics-core/epics-base/add-epicstime-from-gmtm
Merge into: lp:~epics-core/epics-base/3.14
Diff against target: 186 lines (+104/-9)
3 files modified
src/libCom/osi/epicsTime.cpp (+87/-6)
src/libCom/osi/epicsTime.h (+11/-2)
src/libCom/test/epicsTimeTest.cpp (+6/-1)
To merge this branch: bzr merge lp:~epics-core/epics-base/add-epicstime-from-gmtm
Reviewer Review Type Date Requested Status
Andrew Johnson Approve
Review via email: mp+307579@code.launchpad.net

Description of the change

Adds the missing conversion from GMTM (struct tm without timezone) to epicsTime.

Also adds a simple round trip conversion test (same as done for the conversions to the other time types).

To post a comment you must log in.
Revision history for this message
Ralph Lange (ralph-lange) wrote :
Revision history for this message
Andrew Johnson (anj) wrote :

Looks good & works for me; merging with minor tweaks.

review: Approve
Revision history for this message
Ralph Lange (ralph-lange) wrote :

Well,

The nSec overflow is actually being handled in the epicsTime(sec, nSec) constructor - that's why I was removing the code in the conversion constructors and calling the former. After your tweak we now have the functionally identical overflow handling code in three places (in a single source file). Intentionally?

Revision history for this message
Andrew Johnson (anj) wrote :

The double construction and assignment to *this (first 4 new lines of your patch, also repeated lower down) triggered a code smell response in my brain. BTW I rewrote your seconds_in_day calculation only after doing some comparisons using https://gcc.godbolt.org/ -- a very useful site when doing code optimization.

The epicsTime(sec, nSec) constructor currently always does the division and remainder operations (I'm fixing that), and while I wanted to avoid those when they weren't necessary, I didn't want to complicate the code by calling different constructors depending on whether there's an overflow or not.

However I just realized that both of our code versions are broken on systems where time_t is not an integer, which we take great pains to handle properly elsewhere in epicsTime.cpp and in the previous version of this code. If time_t can hold fractional seconds then the code's assumption that the epicsTime(ansiTimeTicks) constructor always results in the nSec field being zero is wrong.

The best solution seems to be to remove my overflow checks and call this->addNanoSec(tm.nSec), as is done by some other converter-constructors. For performance on systems with only soft-float I have just rewritten the addNanoSec() method to avoid the cast to double and to not use operator +.

Revision history for this message
Ralph Lange (ralph-lange) wrote :

The double construction and assignment to *this were part of the original code.

I was quite suspicious, did also notice the smell, did some reading, and in the end decided that in this specific class this unusual pattern was eventually harmless. (The original might have been safe for non-integer time_t, which I certainly did not recognize and screwed up.)

Your soft-float optimized changes definitely sound useful.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/libCom/osi/epicsTime.cpp'
2--- src/libCom/osi/epicsTime.cpp 2014-01-29 22:52:22 +0000
3+++ src/libCom/osi/epicsTime.cpp 2016-10-04 14:45:07 +0000
4@@ -287,11 +287,80 @@
5 throwWithLocation ( formatProblemWithStructTM () );
6 }
7
8- *this = epicsTime (ansiTimeTicks);
9-
10- unsigned long nSecAdj = tm.nSec % nSecPerSec;
11- unsigned long secAdj = tm.nSec / nSecPerSec;
12- *this = epicsTime ( this->secPastEpoch+secAdj, this->nSec+nSecAdj );
13+ // Do the epoch conversion
14+ *this = epicsTime (ansiTimeTicks);
15+ // Add the nSec part
16+ *this = epicsTime (this->secPastEpoch, tm.nSec);
17+}
18+
19+//
20+// epicsTime (const gm_tm_nano_sec &tm)
21+//
22+
23+// do conversion avoiding the timezone mechanism
24+static inline epicsInt32 is_leap(epicsInt32 year)
25+{
26+ if (year % 400 == 0)
27+ return 1;
28+ if (year % 100 == 0)
29+ return 0;
30+ if (year % 4 == 0)
31+ return 1;
32+ return 0;
33+}
34+
35+static inline epicsInt32 days_from_0(epicsInt32 year)
36+{
37+ year--;
38+ return 365 * year + (year / 400) - (year / 100) + (year / 4);
39+}
40+
41+static inline epicsInt32 days_from_1970(epicsInt32 year)
42+{
43+ static const int days_from_0_to_1970 = days_from_0(1970);
44+ return days_from_0(year) - days_from_0_to_1970;
45+}
46+
47+static inline epicsInt32 days_from_1jan(epicsInt32 year,
48+ epicsInt32 month,
49+ epicsInt32 day)
50+{
51+ static const epicsInt32 days[2][12] =
52+ {
53+ { 0,31,59,90,120,151,181,212,243,273,304,334},
54+ { 0,31,60,91,121,152,182,213,244,274,305,335}
55+ };
56+ return days[is_leap(year)][month-1] + day - 1;
57+}
58+
59+epicsTime::epicsTime (const gm_tm_nano_sec &tm)
60+{
61+ time_t_wrapper ansiTimeTicks;
62+ int year = tm.ansi_tm.tm_year + 1900;
63+ int month = tm.ansi_tm.tm_mon;
64+ if (month > 11) {
65+ year += month / 12;
66+ month %= 12;
67+ } else if (month < 0) {
68+ int years_diff = (-month + 11) / 12;
69+ year -= years_diff;
70+ month += 12 * years_diff;
71+ }
72+ month++;
73+ int day = tm.ansi_tm.tm_mday;
74+ int day_of_year = days_from_1jan(year, month, day);
75+ int days_since_epoch = days_from_1970(year) + day_of_year;
76+
77+ const time_t seconds_in_day = 3600 * 24;
78+ ansiTimeTicks.ts = seconds_in_day * days_since_epoch
79+ + 3600 * tm.ansi_tm.tm_hour
80+ + 60 * tm.ansi_tm.tm_min
81+ + tm.ansi_tm.tm_sec;
82+
83+ // Do the epoch conversion
84+ *this = epicsTime (ansiTimeTicks);
85+ // Add the nSec part
86+ *this = epicsTime (this->secPastEpoch, tm.nSec);
87 }
88
89 //
90@@ -905,6 +974,19 @@
91 }
92 return epicsTimeOK;
93 }
94+ epicsShareFunc int epicsShareAPI epicsTimeFromGMTM (epicsTimeStamp *pDest, const struct tm *pSrc, unsigned long nSecSrc)
95+ {
96+ try {
97+ gm_tm_nano_sec tmns;
98+ tmns.ansi_tm = *pSrc;
99+ tmns.nSec = nSecSrc;
100+ *pDest = epicsTime (tmns);
101+ }
102+ catch (...) {
103+ return epicsTimeERROR;
104+ }
105+ return epicsTimeOK;
106+ }
107 epicsShareFunc int epicsShareAPI epicsTimeToTimespec (struct timespec *pDest, const epicsTimeStamp *pSrc)
108 {
109 try {
110@@ -1036,4 +1118,3 @@
111 }
112 }
113 }
114-
115
116=== modified file 'src/libCom/osi/epicsTime.h'
117--- src/libCom/osi/epicsTime.h 2009-01-28 20:01:41 +0000
118+++ src/libCom/osi/epicsTime.h 2016-10-04 14:45:07 +0000
119@@ -105,10 +105,12 @@
120 epicsTime & operator = ( const local_tm_nano_sec & );
121
122 /*
123- * convert to ANSI Cs "struct tm" (with nano seconds)
124+ * convert to and from ANSI Cs "struct tm" (with nano seconds)
125 * adjusted for GM time (UTC)
126 */
127 operator gm_tm_nano_sec () const;
128+ epicsTime ( const gm_tm_nano_sec & );
129+ epicsTime & operator = ( const gm_tm_nano_sec & );
130
131 /* convert to and from POSIX RTs "struct timespec" */
132 operator struct timespec () const;
133@@ -194,13 +196,15 @@
134 epicsShareFunc int epicsShareAPI epicsTimeFromTime_t (
135 epicsTimeStamp * pDest, time_t src );
136
137-/*convert to and from ANSI C's "struct tm" with nano seconds */
138+/* convert to and from ANSI C's "struct tm" with nano seconds */
139 epicsShareFunc int epicsShareAPI epicsTimeToTM (
140 struct tm * pDest, unsigned long * pNSecDest, const epicsTimeStamp * pSrc );
141 epicsShareFunc int epicsShareAPI epicsTimeToGMTM (
142 struct tm * pDest, unsigned long * pNSecDest, const epicsTimeStamp * pSrc );
143 epicsShareFunc int epicsShareAPI epicsTimeFromTM (
144 epicsTimeStamp * pDest, const struct tm * pSrc, unsigned long nSecSrc );
145+epicsShareFunc int epicsShareAPI epicsTimeFromGMTM (
146+ epicsTimeStamp * pDest, const struct tm * pSrc, unsigned long nSecSrc );
147
148 /* convert to and from POSIX RT's "struct timespec" */
149 epicsShareFunc int epicsShareAPI epicsTimeToTimespec (
150@@ -312,6 +316,11 @@
151 return *this = epicsTime ( rhs );
152 }
153
154+inline epicsTime & epicsTime::operator = ( const gm_tm_nano_sec & rhs )
155+{
156+ return *this = epicsTime ( rhs );
157+}
158+
159 inline epicsTime & epicsTime::operator = ( const struct timespec & rhs )
160 {
161 *this = epicsTime ( rhs );
162
163=== modified file 'src/libCom/test/epicsTimeTest.cpp'
164--- src/libCom/test/epicsTimeTest.cpp 2014-01-29 22:52:22 +0000
165+++ src/libCom/test/epicsTimeTest.cpp 2016-10-04 14:45:07 +0000
166@@ -48,7 +48,7 @@
167 const int wasteTime = 100000;
168 const int nTimes = 10;
169
170- testPlan(15 + nTimes * 18);
171+ testPlan(15 + nTimes * 19);
172
173 try {
174 const epicsTimeStamp epochTS = {0, 0};
175@@ -205,6 +205,11 @@
176 epicsTime beginANSI = ansiDate;
177 testOk1(beginANSI + diff == now);
178
179+ // test struct gmtm round-trip conversion
180+ gm_tm_nano_sec ansiGmDate = begin;
181+ epicsTime beginGMANSI = ansiGmDate;
182+ testOk1(beginGMANSI + diff == now);
183+
184 // test struct timespec round-trip conversion
185 struct timespec ts = begin;
186 epicsTime beginTS = ts;

Subscribers

People subscribed via source and target branches