File: | libsynthesis/src/sysync/iso8601.cpp |
Warning: | line 191, column 5 Value stored to 'aISOString' is never read |
1 | /* |
2 | * File: iso8601.cpp |
3 | * |
4 | * Author: Lukas Zeller (luz@plan44.ch) |
5 | * |
6 | * conversion from/to linear time scale. |
7 | * |
8 | * Copyright (c) 2002-2011 by Synthesis AG + plan44.ch |
9 | * |
10 | * 2002-05-02 : luz : extracted from sysync_utils |
11 | * |
12 | */ |
13 | |
14 | #include "prefix_file.h" |
15 | |
16 | #include "iso8601.h" |
17 | #include "stringutils.h" |
18 | #include "timezones.h" |
19 | |
20 | #if defined(EXPIRES_AFTER_DATE) && !defined(FULLY_STANDALONE) |
21 | // only if used in sysync context |
22 | #include "sysync.h" |
23 | #endif |
24 | |
25 | |
26 | namespace sysync { |
27 | |
28 | /// @brief convert ISO8601 to timestamp and timezone |
29 | /// @return number of successfully converted characters or 0 if no valid ISO8601 specification could be decoded |
30 | /// @param[in] aISOString input string in ISO8601 |
31 | /// @param[out] aTimestamp representation of ISO time spec as is (no time zone conversions) |
32 | /// @param[out] aWithTime if set, time specification was found in input string |
33 | /// @param[out] aTimeContext: |
34 | /// TCTX_DURATION if ISO8601 is a duration format (PnDTnHnMnS.MS) |
35 | /// TCTX_UNKNOWN if ISO8601 does not include a time zone specification |
36 | /// TCTX_UTC if ISO8601 ends with the "Z" specifier |
37 | /// TCTX_DATEONLY if ISO8601 only contains a date, but no time |
38 | /// TCTX_OFFSCONTEXT(xx) if ISO8601 explicitly specifies a UTC offset |
39 | sInt16 ISO8601StrToTimestamp(cAppCharP aISOString, lineartime_t &aTimestamp, timecontext_t &aTimeContext) |
40 | { |
41 | uInt16 y,m,d,hr,mi,s,ms; // unsigned because these can't be signed when parsing |
42 | bool isExtended=false; |
43 | sInt16 n,h,sg; |
44 | |
45 | n=0; |
46 | // check if it might be duration |
47 | sg=0; // duration sign |
48 | // - sign |
49 | if (*aISOString=='-') { |
50 | sg=-1; // negative duration |
51 | aISOString++; n++; |
52 | } |
53 | else if(*aISOString=='+') { |
54 | sg=1; // positive duration |
55 | aISOString++; n++; |
56 | } |
57 | if (*aISOString=='P') { |
58 | if (sg==0) sg=1; |
59 | aISOString++; n++; |
60 | } |
61 | else if (sg!=0) |
62 | return 0; // we had a sign, but no 'P' -> invalid ISO8601 date |
63 | // if y!=0 here, this is a duration |
64 | if (sg!=0) { |
65 | // duration |
66 | aTimestamp = 0; |
67 | aTimeContext = TCTX_UNKNOWN((timecontext_t) ((tctx_tz_unknown) | TCTX_SYMBOLIC_TZ)) + TCTX_DURATION; |
68 | bool timepart = false; |
69 | // parse duration parts |
70 | while (true) { |
71 | if (*aISOString=='T' && !timepart) { |
72 | timepart = true; |
73 | aISOString++; n++; |
74 | } |
75 | #ifdef NO_FLOATS |
76 | sInt32 part; |
77 | h = StrToLong(aISOString,part); |
78 | #else |
79 | double part; |
80 | h = StrToDouble(aISOString,part); |
81 | #endif |
82 | if (h>0) { |
83 | aISOString+=h; n+=h; |
84 | // this is a part, must be followed by a designator |
85 | switch (*aISOString) { |
86 | case 'Y': |
87 | part*=365; goto days; // approximate year |
88 | case 'M': // month or minute |
89 | if (timepart) { |
90 | aTimestamp += (lineartime_t)(part*sg*secondToLinearTimeFactor*SecsPerMin); // minute |
91 | break; |
92 | } |
93 | else { |
94 | part*=30; // approximate month |
95 | goto days; |
96 | } |
97 | case 'W': |
98 | part *= 7; goto days; // one week |
99 | case 'D': |
100 | days: |
101 | aTimestamp += (lineartime_t)(part*sg*linearDateToTimeFactor); |
102 | break; |
103 | case 'H': |
104 | aTimestamp += (lineartime_t)(part*sg*secondToLinearTimeFactor*SecsPerHour); |
105 | break; |
106 | case 'S': |
107 | aTimestamp += (lineartime_t)(part*sg*secondToLinearTimeFactor); |
108 | break; |
109 | default: |
110 | return 0; // bad designator, error |
111 | } |
112 | aISOString++; n++; |
113 | } // if number |
114 | else { |
115 | // no more digits -> end of duration string |
116 | return n; // number of chars processed |
117 | } |
118 | } // while |
119 | } // if duration |
120 | // try to parse date |
121 | // - first should be 4 digit year |
122 | h = StrToUShort(aISOString,y,4); |
123 | if (h!=4) return 0; // no ISO8601 date |
124 | aISOString+=h; n+=h; |
125 | // - test for format |
126 | if (*aISOString=='-') { |
127 | isExtended=true; |
128 | aISOString++; n++; |
129 | } |
130 | // - next must be 2 digit month |
131 | h = StrToUShort(aISOString,m,2); |
132 | if (h!=2) return 0; // no ISO8601 date |
133 | aISOString+=h; n+=h; |
134 | // - check separator in case of extended format |
135 | if (isExtended) { |
136 | if (*aISOString != '-') return 0; // missing separator, no ISO8601 date |
137 | aISOString++; n++; |
138 | } |
139 | // - next must be 2 digit day |
140 | h = StrToUShort(aISOString,d,2); |
141 | if (h!=2) return 0; // no ISO8601 date |
142 | aISOString+=h; n+=h; |
143 | // convert date to timestamp |
144 | aTimestamp = date2lineartime(y,m,d); |
145 | // Now next must be "T" if time spec is included |
146 | if (*aISOString!='T') { |
147 | // date-only, floating |
148 | aTimeContext = TCTX_DATEONLY|TCTX_UNKNOWN((timecontext_t) ((tctx_tz_unknown) | TCTX_SYMBOLIC_TZ)); |
149 | } |
150 | else { |
151 | // parse time as well |
152 | aISOString++; n++; // skip "T" |
153 | mi=0; s=0; ms=0; // reset optional time components |
154 | // - next must be 2 digit hour |
155 | h = StrToUShort(aISOString,hr,2); |
156 | if (h!=2) return 0; // no ISO8601 time, we need the hour, minimally |
157 | aISOString+=h; n+=h; |
158 | // - check separator in case of extended format |
159 | if (isExtended) { |
160 | if (*aISOString != ':') return 0; // missing separator, no ISO8601 time (Note: hour-only reduced precision does not exist for extended format) |
161 | aISOString++; n++; |
162 | } |
163 | // - next must be 2 digit minute (or nothing for hour-only reduced precision basic format) |
164 | h = StrToUShort(aISOString,mi,2); |
165 | if (!isExtended && h==0) goto timeok; |
166 | if (h!=2) return 0; // no ISO8601 time, must be 2 digits here |
167 | aISOString+=h; n+=h; |
168 | // - check separator in case of extended format |
169 | if (isExtended) { |
170 | if (*aISOString != ':') goto timeok; // no separator means hour:minute reduced precision for extended format |
171 | aISOString++; n++; |
172 | } |
173 | // - next must be 2 digit second (or nothing for reduced precision without seconds) |
174 | h = StrToUShort(aISOString,s,2); |
175 | if (!isExtended && h==0) goto timeok; // no seconds is ok for basic format only (in extended format, the separator must be omitted as well, which is checked above) |
176 | if (h!=2) return 0; // no ISO8601 time, must be 2 digits here |
177 | aISOString+=h; n+=h; |
178 | // optional fractions of seconds |
179 | if (*aISOString=='.') { |
180 | aISOString++; n++; |
181 | h = StrToUShort(aISOString,ms,3); |
182 | if (h==0) return 0; // invalid fraction specified |
183 | aISOString+=h; n+=h; |
184 | while (h<3) { ms *= 10; h++; } // make milliseconds |
185 | } |
186 | timeok: |
187 | // add to timestamp |
188 | aTimestamp += time2lineartime(hr,mi,s,ms); |
189 | // check for zone specification |
190 | h = ISO8601StrToContext(aISOString,aTimeContext); |
191 | aISOString+=h; n+=h; |
Value stored to 'aISOString' is never read | |
192 | } |
193 | // return number of characters converted |
194 | return n; |
195 | } // ISO8601StrToTimestamp |
196 | |
197 | |
198 | |
199 | |
200 | /// @brief convert ISO8601 zone offset to internal time zone |
201 | /// @return number of successfully converted characters or 0 if no valid ISO8601 time zone spec could be decoded |
202 | /// @param[in] aISOString input string in ISO8601 time zone offset format (or just "Z" for UTC) |
203 | /// @param[out] aTimeContext: |
204 | /// TCTX_UNKNOWN if no time zone specification is found |
205 | /// TCTX_UTC if "Z" specifier found |
206 | /// TCTX_OFFSCONTEXT(xx) if explicit UTC offset is found |
207 | sInt16 ISO8601StrToContext(cAppCharP aISOString, timecontext_t &aTimeContext) |
208 | { |
209 | sInt16 n=0,h; |
210 | sInt16 minoffs; |
211 | uInt16 offs; |
212 | |
213 | aTimeContext = TCTX_UNKNOWN((timecontext_t) ((tctx_tz_unknown) | TCTX_SYMBOLIC_TZ)); |
214 | bool western=false; |
215 | // check for UTC special case |
216 | if (*aISOString=='Z') { |
217 | n++; |
218 | aTimeContext = TCTX_UTC((timecontext_t) ((tctx_tz_UTC) | TCTX_SYMBOLIC_TZ)); |
219 | } |
220 | else { |
221 | // check for time zone offset |
222 | if (*aISOString!='+' && *aISOString!='-') |
223 | return 0; // error, nothing converted |
224 | western = *aISOString=='-'; // time is behind of UTC in the west |
225 | aISOString++; |
226 | n++; |
227 | h=StrToUShort(aISOString,offs,2); |
228 | if (h!=2) |
229 | return 0; // not +HH format with 2 digits, nothing converted |
230 | aISOString+=h; n+=h; |
231 | // make minutes |
232 | minoffs = offs*60; |
233 | // hour offset ok, now check minutes |
234 | if (*aISOString==':') { aISOString++; n++; }; // extended format |
235 | // get minutes, if any |
236 | h = StrToUShort(aISOString,offs,2); |
237 | if (h==2) { |
238 | // minute specified |
239 | minoffs += offs; // add minutes |
240 | } |
241 | // adjust sign |
242 | if (western) |
243 | minoffs=-minoffs; // western offset is negative |
244 | // return non-symbolic minute offset |
245 | aTimeContext = TCTX_OFFSCONTEXT(minoffs)((timecontext_t)((sInt16)(minoffs) & TCTX_OFFSETMASK )); |
246 | } |
247 | // return number of characters converted |
248 | return n; |
249 | } // ISO8601StrToContext |
250 | |
251 | |
252 | |
253 | /// @brief convert timestamp to ISO8601 representation |
254 | /// @param[out] aISOString will receive ISO8601 formatted date/time |
255 | /// @param[in] aTimestamp representation of ISO time spec as is (no time zone conversions) |
256 | /// @param[in] aExtFormat if set, extended format is generated |
257 | /// @param[in] aWithFracSecs if set, fractional seconds are displayed (3 digits, milliseconds) |
258 | /// @param[in] aTimeContext: |
259 | /// TCTX_DURATION: timestamp is a duration and is shown in ISO8601 duration format (PnDTnHnMnS.MS) |
260 | /// TCTX_UNKNOWN : time is shown as relative format (no "Z", no explicit offset) |
261 | /// TCTX_TIMEONLY |
262 | /// TCTX_UTC : time is shown with "Z" specifier |
263 | /// TCTX_DATEONLY: only date part is shown (of date or duration) |
264 | /// TCTX_OFFSCONTEXT(xx) : time is shown with explicit UTC offset (but not with "Z", even if offset is 0:00) |
265 | void TimestampToISO8601Str(string &aISOString, lineartime_t aTimestamp, timecontext_t aTimeContext, bool aExtFormat, bool aWithFracSecs) |
266 | { |
267 | // check for duration (should be rendered even if value is 0) |
268 | if (TCTX_IS_DURATION(aTimeContext)) { |
269 | // render as duration |
270 | // - sign |
271 | if (aTimestamp<0) { |
272 | aTimestamp=-aTimestamp; |
273 | aISOString = "-"; |
274 | } |
275 | else { |
276 | aISOString.erase(); |
277 | } |
278 | // - "P" duration designator |
279 | aISOString += "P"; |
280 | // - days |
281 | lineardate_t days = aTimestamp / linearDateToTimeFactor; |
282 | if (days!=0) StringObjAppendPrintf(aISOString,"%ldD",(long)days); |
283 | // - time part if needed |
284 | if (!TCTX_IS_DATEONLY(aTimeContext) && lineartime2timeonly(aTimestamp)>(aWithFracSecs ? 0 : secondToLinearTimeFactor-1)) { |
285 | aISOString += "T"; // we have a time part |
286 | sInt16 h,m,s,ms; |
287 | lineartime2time(aTimestamp,&h,&m,&s,&ms); |
288 | if (h!=0) StringObjAppendPrintf(aISOString,"%hdH",h); |
289 | if (m!=0) StringObjAppendPrintf(aISOString,"%hdM",m); |
290 | if (aWithFracSecs && ms!=0) { |
291 | // with milliseconds, implies seconds as well, even if they are zero |
292 | StringObjAppendPrintf(aISOString,"%hd.%03hdS",s,ms); |
293 | } |
294 | else { |
295 | // no milliseconds, show seconds if not zero |
296 | if (s!=0) StringObjAppendPrintf(aISOString,"%hdS",s); |
297 | } |
298 | } |
299 | else if(days==0) { |
300 | aISOString="PT0S"; // no time part and no days, just display 0 seconds (and not negative, even if fraction might be) |
301 | } |
302 | // done |
303 | return; |
304 | } |
305 | // no timestamp, no string |
306 | if (aTimestamp==0) { |
307 | aISOString.erase(); |
308 | return; |
309 | } |
310 | // Date if we want date |
311 | bool hasDate=false; |
312 | if (!TCTX_IS_TIMEONLY(aTimeContext)) { |
313 | // we want the date part |
314 | sInt16 y,m,d; |
315 | lineartime2date(aTimestamp,&y,&m,&d); |
316 | if (!TCTX_IS_DURATION(aTimeContext) || !(y==0 && m==0 && d==0)) { |
317 | if (aExtFormat) |
318 | StringObjPrintf(aISOString,"%04d-%02d-%02d",y,m,d); // Extended format |
319 | else |
320 | StringObjPrintf(aISOString,"%04d%02d%02d",y,m,d); // Basic format |
321 | hasDate=true; |
322 | } |
323 | } |
324 | else { |
325 | aISOString.erase(); |
326 | } |
327 | // Add time if we want time |
328 | if (!TCTX_IS_DATEONLY(aTimeContext)) { |
329 | // we want the time part |
330 | // - add separator |
331 | if (hasDate) aISOString+='T'; |
332 | // - now add the time |
333 | sInt16 h,m,s,ms; |
334 | lineartime2time(aTimestamp,&h,&m,&s,&ms); |
335 | if (aExtFormat) |
336 | StringObjAppendPrintf(aISOString,"%02hd:%02hd:%02hd",h,m,s); // Extended format |
337 | else |
338 | StringObjAppendPrintf(aISOString,"%02hd%02hd%02hd",h,m,s); // Basic format |
339 | // - add fractions of the second if selected and not 0 |
340 | if (aWithFracSecs && (ms!=0)) { |
341 | StringObjAppendPrintf(aISOString,".%03hd",ms); // 3 decimal fraction digits for milliseconds |
342 | } |
343 | if (!TCTX_IS_DURATION(aTimeContext)) { |
344 | // add explicit time zone specification (or UTC "Z") if aTimecontext is a non-symbolic offset |
345 | ContextToISO8601StrAppend(aISOString, aTimeContext, aExtFormat); |
346 | } |
347 | } |
348 | } // TimestampToISO8601Str |
349 | |
350 | |
351 | |
352 | /// @brief append internal time zone as ISO8601 zone offset to string |
353 | /// @param[out] aISOString ISO8601 time zone spec will be appended to this string |
354 | /// @param[in] aTimeContext |
355 | /// @param[in] aExtFormat if set, extended format is generated |
356 | bool ContextToISO8601StrAppend(string &aISOString, timecontext_t aTimeContext, bool aExtFormat) |
357 | { |
358 | // check for UTC special case |
359 | if (TCTX_IS_UTC(aTimeContext)) { |
360 | aISOString += 'Z'; |
361 | return true; // has time zone |
362 | } |
363 | // check if this is a resolved or a symbolic time zone |
364 | if (TCTX_IS_TZ(aTimeContext)) |
365 | return false; // symbolic (includes unknown) - cannot append minute offset |
366 | // offset specified, show it |
367 | long moffs = TCTX_MINOFFSET(aTimeContext); |
368 | bool minus = moffs<0; |
369 | moffs = labs(moffs); |
370 | long hoffs = moffs / MinsPerHour; |
371 | moffs = moffs % MinsPerHour; |
372 | StringObjAppendPrintf(aISOString, "%c%02ld", minus ? '-' : '+', hoffs); |
373 | if (moffs!=0 || aExtFormat) { |
374 | // minute specification required (always so for extended format) |
375 | if (aExtFormat) |
376 | aISOString+=':'; // add separator for extended format |
377 | StringObjAppendPrintf(aISOString, "%02ld", moffs); |
378 | } |
379 | return true; // has time zone |
380 | } // ContextToISO8601StrAppend |
381 | |
382 | |
383 | } // namespace sysync |
384 | |
385 | /* eof */ |