1/*
2 * Legal Notice
3 *
4 * This document and associated source code (the "Work") is a part of a
5 * benchmark specification maintained by the TPC.
6 *
7 * The TPC reserves all right, title, and interest to the Work as provided
8 * under U.S. and international laws, including without limitation all patent
9 * and trademark rights therein.
10 *
11 * No Warranty
12 *
13 * 1.1 TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, THE INFORMATION
14 * CONTAINED HEREIN IS PROVIDED "AS IS" AND WITH ALL FAULTS, AND THE
15 * AUTHORS AND DEVELOPERS OF THE WORK HEREBY DISCLAIM ALL OTHER
16 * WARRANTIES AND CONDITIONS, EITHER EXPRESS, IMPLIED OR STATUTORY,
17 * INCLUDING, BUT NOT LIMITED TO, ANY (IF ANY) IMPLIED WARRANTIES,
18 * DUTIES OR CONDITIONS OF MERCHANTABILITY, OF FITNESS FOR A PARTICULAR
19 * PURPOSE, OF ACCURACY OR COMPLETENESS OF RESPONSES, OF RESULTS, OF
20 * WORKMANLIKE EFFORT, OF LACK OF VIRUSES, AND OF LACK OF NEGLIGENCE.
21 * ALSO, THERE IS NO WARRANTY OR CONDITION OF TITLE, QUIET ENJOYMENT,
22 * QUIET POSSESSION, CORRESPONDENCE TO DESCRIPTION OR NON-INFRINGEMENT
23 * WITH REGARD TO THE WORK.
24 * 1.2 IN NO EVENT WILL ANY AUTHOR OR DEVELOPER OF THE WORK BE LIABLE TO
25 * ANY OTHER PARTY FOR ANY DAMAGES, INCLUDING BUT NOT LIMITED TO THE
26 * COST OF PROCURING SUBSTITUTE GOODS OR SERVICES, LOST PROFITS, LOSS
27 * OF USE, LOSS OF DATA, OR ANY INCIDENTAL, CONSEQUENTIAL, DIRECT,
28 * INDIRECT, OR SPECIAL DAMAGES WHETHER UNDER CONTRACT, TORT, WARRANTY,
29 * OR OTHERWISE, ARISING IN ANY WAY OUT OF THIS OR ANY OTHER AGREEMENT
30 * RELATING TO THE WORK, WHETHER OR NOT SUCH AUTHOR OR DEVELOPER HAD
31 * ADVANCE NOTICE OF THE POSSIBILITY OF SUCH DAMAGES.
32 *
33 * Contributors
34 * - Charles Levine
35 */
36
37#include "utilities/DateTime.h"
38
39#include <stdio.h>
40#include <stdexcept>
41#include <chrono>
42
43// DJ: perhaps all unixes need this so maybe we want #ifndef WIN32 or something
44// like that?
45#if (__unix) || (_AIX)
46#include <sys/time.h> // for gettimeofday
47#endif
48
49using namespace TPCE;
50
51// Initialize static const member arrays.
52const INT32 CDateTime::monthArray[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
53const INT32 CDateTime::monthArrayLY[12] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
54const INT32 CDateTime::cumulativeMonthArray[] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334};
55
56bool CDateTime::IsLeapYear(INT32 year) {
57 if ((year % 4) != 0) // only years which are multiples of 4 can be leap years
58 {
59 return false;
60 }
61
62 if ((year % 400) == 0) // years divisible by 400 are leap years
63 {
64 return true;
65 }
66
67 if ((year % 100) == 0) // years divisible by 100 but not 400 are not leap years
68 {
69 return false;
70 }
71
72 // must be a leap year if you get here
73 return true;
74}
75
76// Checks whether the date/time is valid.
77bool CDateTime::IsValid(INT32 year, INT32 month, INT32 day, INT32 hour, INT32 minute, INT32 second, INT32 msec) {
78 // check all values that have static, absolute bounds
79 if (hour < minValidHour || maxValidHour < hour || minute < minValidMinute || maxValidMinute < minute ||
80 second < minValidSecond || maxValidSecond < second || msec < minValidMilliSecond ||
81 maxValidMilliSecond < msec || year < minValidYear || maxValidYear < year || month < minValidMonth ||
82 maxValidMonth < month || day < minValidDay) {
83 return false;
84 }
85
86 // check the day of the month
87 // optimize for common case. if check passes, we're done
88 if (day <= monthArray[month - 1]) {
89 return true;
90 }
91
92 // Only one possibility left -- February 29th
93 // Feb 29th valid only if a leap year; invalid otherwise
94 if ((month == 2) && (day == 29)) {
95 return IsLeapYear(year);
96 }
97
98 // exhausted all possibilities; can't be valid
99 return false;
100}
101
102// Validate the specified date/time and throw an out_of_range exception if it
103// isn't valid.
104void CDateTime::Validate(INT32 year, INT32 month, INT32 day, INT32 hour, INT32 minute, INT32 second, INT32 msec) {
105 if (!IsValid(year, month, day, hour, minute, second, msec)) {
106 std::ostringstream msg;
107 msg << "The specified Date/Time is not valid.";
108 throw std::out_of_range(msg.str());
109 }
110}
111
112// Validate the specified day number and throw an out_of_range exception if it
113// isn't valid.
114void CDateTime::Validate(INT32 dayNumber) {
115 if ((dayNumber < minValidDayNumber) || (CalculateDayNumber(maxValidYear, maxValidMonth, maxValidDay) < dayNumber)) {
116 std::ostringstream msg;
117 msg << "The specified day-number is not valid.";
118 throw std::out_of_range(msg.str());
119 }
120}
121
122// Validate a day-number/msec pair.
123void CDateTime::Validate(INT32 dayNumber, INT32 msecSoFarToday) {
124 if ((msecSoFarToday < minValidMilliSecond) || (maxValidMilliSecond < msecSoFarToday) ||
125 (dayNumber < minValidDayNumber) || (CalculateDayNumber(maxValidYear, maxValidMonth, maxValidDay) < dayNumber)) {
126 std::ostringstream msg;
127 msg << "The specified (day-number, msec) pair is not valid.";
128 throw std::out_of_range(msg.str());
129 }
130}
131
132// Computes the number of days since Jan 1, 0001. (Year 1 AD)
133// 1-Jan-0001 = 0
134INT32 CDateTime::CalculateDayNumber(INT32 yr, INT32 mm, INT32 dd) {
135 // compute day of year
136 INT32 jd = cumulativeMonthArray[mm - 1] + dd - 1;
137
138 // adjust day of year if this is a leap year and it is after February
139 if ((mm > 2) && IsLeapYear(yr)) {
140 jd++;
141 }
142
143 // compute number of days from 1/1/0001 to beginning of present year
144 yr--; // start counting from year 1 AD (1-based instead of 0-based)
145 jd += yr / 400 * dy400;
146 yr %= 400;
147 jd += yr / 100 * dy100;
148 yr %= 100;
149 jd += yr / 4 * dy4;
150 yr %= 4;
151 jd += yr * dy1;
152
153 return jd;
154}
155
156INT32 CDateTime::YMDtoDayno(INT32 yr, INT32 mm, INT32 dd) {
157 // Validate year, month and day (use known safe values for hours -
158 // milliseconds).
159 Validate(yr, mm, dd, minValidHour, minValidMinute, minValidSecond, minValidMilliSecond);
160
161 return CalculateDayNumber(yr, mm, dd);
162}
163
164// returns text representation of DateTime
165// the style argument is interpreted as a two digit field, where the first digit
166// (in the tens place) is the date format and the second digit (in the ones
167// place) is the time format.
168//
169// The following formats are provided:
170// STYLE DATE TIME
171// ----- ---- ----
172// 0 <omit> <omit>
173// 1 YYYY-MM-DD HH:MM:SS (24hr)
174// 2 MM/DD/YY HH:MM:SS.mmm (24hr)
175// 3 MM/DD/YYYY HH:MM (24hr)
176// 4 DD-MON-YYYY HH:MM:SS [AM|PM]
177// 5 DD-MON-YY HH:MM:SS.mmm [AM|PM]
178// 6 MM-DD-YY HH:MM [AM|PM]
179// 7 MON DD YYYY
180// 8 Month DD, YYYY
181//
182char *CDateTime::ToStr(INT32 style = 11) {
183 static const char *szMonthsShort[] = {"JAN", "FEB", "MAR", "APR", "MAY", "JUN",
184 "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"};
185 static const char *szMonthsFull[] = {"January", "February", "March", "April", "May", "June",
186 "July", "August", "September", "October", "November", "December"};
187 static const char *szAmPm[] = {"AM", "PM"};
188 // the following array is used to map from 24-hour to 12-hour time
189 static const INT32 iHr12[] = {12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
190
191 INT32 year, month, day, hour, minute, second, msec;
192 INT32 p = 0;
193 static const INT32 iMaxStrLen = 40;
194
195 if (m_szText == NULL)
196 m_szText = new char[iMaxStrLen];
197 m_szText[0] = '\0';
198
199 GetYMDHMS(&year, &month, &day, &hour, &minute, &second, &msec);
200
201 size_t lengthTotal = iMaxStrLen;
202 char *pszText = m_szText;
203
204 // DATE portion
205 switch (style / 10) {
206 case 1:
207 // YYYY-MM-DD
208 p = snprintf(pszText, lengthTotal, "%04d-%02d-%02d ", year, month, day);
209 break;
210 case 2:
211 // MM/DD/YY
212 p = snprintf(pszText, lengthTotal, "%02d/%02d/%02d ", month, day, year % 100);
213 break;
214 case 3:
215 // MM/DD/YYYY
216 p = snprintf(pszText, lengthTotal, "%02d/%02d/%04d ", month, day, year);
217 break;
218 case 4:
219 // DD-MON-YYYY
220 p = snprintf(pszText, lengthTotal, "%02d-%s-%04d ", day, szMonthsShort[month - 1], year);
221 break;
222 case 5:
223 // DD-MON-YY
224 p = snprintf(pszText, lengthTotal, "%02d-%s-%02d ", day, szMonthsShort[month - 1], year % 100);
225 break;
226 case 6:
227 // MM-DD-YY
228 p = snprintf(pszText, lengthTotal, "%02d-%02d-%02d ", month, day, year % 100);
229 break;
230 case 7:
231 // MON DD YYYY
232 p = snprintf(pszText, lengthTotal, "%s %02d %04d ", szMonthsShort[month - 1], day, year);
233 break;
234 case 8:
235 // Month DD, YYYY
236 p = snprintf(pszText, lengthTotal, "%s %02d, %04d ", szMonthsFull[month - 1], day, year);
237 break;
238 }
239
240 size_t lengthRemaining = lengthTotal - p;
241 pszText = m_szText + (char)p;
242
243 // TIME portion
244 switch (style % 10) {
245 case 1:
246 // HH:MM:SS (24hr)
247 p += snprintf(pszText, lengthRemaining, "%02d:%02d:%02d", hour, minute, second);
248 break;
249 case 2:
250 // HH:MM:SS.mmm (24hr)
251 p += snprintf(pszText, lengthRemaining, "%02d:%02d:%02d.%03d", hour, minute, second, msec);
252 break;
253 case 3:
254 // HH:MM (24hr)
255 p += snprintf(pszText, lengthRemaining, "%02d:%02d", hour, minute);
256 break;
257 case 4:
258 // HH:MM:SS [AM|PM]
259 p += snprintf(pszText, lengthRemaining, "%02d:%02d:%02d %s", iHr12[hour], minute, second, szAmPm[hour / 12]);
260 break;
261 case 5:
262 // HHH:MM:SS.mmm [AM|PM]
263 p += snprintf(pszText, lengthRemaining, "%02d:%02d:%02d.%03d %s", iHr12[hour], minute, second, msec,
264 szAmPm[hour / 12]);
265 break;
266 case 6:
267 // HH:MM [AM|PM]
268 p += snprintf(pszText, lengthRemaining, "%02d:%02d %s", iHr12[hour], minute, szAmPm[hour / 12]);
269 break;
270 }
271
272 // trim trailing blank, if there is one.
273 if (p > 0 && m_szText[p - 1] == ' ')
274 m_szText[p - 1] = '\0';
275
276 return m_szText;
277}
278
279// set to current local time
280CDateTime::CDateTime(void) {
281 m_szText = NULL;
282 Set();
283}
284
285CDateTime::CDateTime(INT32 dayno) {
286 Validate(dayno);
287 m_szText = NULL;
288 m_dayno = dayno;
289 m_msec = 0;
290}
291
292CDateTime::CDateTime(INT32 year, INT32 month, INT32 day) {
293 Validate(year, month, day, minValidHour, minValidMinute, minValidSecond, minValidMilliSecond);
294 m_dayno = CalculateDayNumber(year, month, day);
295 m_szText = NULL;
296 m_msec = 0;
297}
298
299// Copy constructor
300CDateTime::CDateTime(const CDateTime &dt) {
301 // Assume source is valid.
302 // Validate( dt.m_dayno, dt.m_msec );
303 m_dayno = dt.m_dayno;
304 m_msec = dt.m_msec;
305 m_szText = NULL;
306}
307
308CDateTime::~CDateTime(void) {
309 if (m_szText)
310 delete[] m_szText;
311}
312
313CDateTime::CDateTime(INT32 year, INT32 month, INT32 day, INT32 hour, INT32 minute, INT32 second, INT32 msec) {
314 // Validate specified date/time
315 Validate(year, month, day, hour, minute, second, msec);
316
317 m_szText = NULL;
318 m_dayno = CalculateDayNumber(year, month, day);
319 m_msec = ((hour * MinutesPerHour + minute) * SecondsPerMinute + second) * MsPerSecond + msec;
320}
321
322CDateTime::CDateTime(TPCE::TIMESTAMP_STRUCT *ts) {
323 Validate(ts->year, ts->month, ts->day, ts->hour, ts->minute, ts->second, ts->fraction / 1000000);
324
325 m_szText = NULL;
326 m_dayno = CalculateDayNumber(ts->year, ts->month, ts->day);
327 m_msec = ((ts->hour * MinutesPerHour + ts->minute) * SecondsPerMinute + ts->second) * MsPerSecond +
328 ts->fraction / 1000000;
329}
330
331// set to current local time
332void CDateTime::Set(void) {
333 // assert(0); // why is this necessary?
334 // //UNIX-specific code to get the current time with 1ms resolution
335 // struct timeval tv;
336 // struct tm ltr;
337 // int secs;
338 // gettimeofday(&tv, NULL);
339 // struct tm* lt = localtime_r(&tv.tv_sec, &ltr); //expand into
340 // year/month/day/...
341 // // NOTE: 1 is added to tm_mon because it is 0 based, but
342 // CalculateDayNumber expects it to
343 // // be 1 based.
344 // m_dayno = CalculateDayNumber(lt->tm_year+1900, lt->tm_mon+1,
345 // lt->tm_mday); // tm_year is based on 1900, not 0.
346
347 // secs = (lt->tm_hour * MinutesPerHour + lt->tm_min)*SecondsPerMinute +
348 // lt->tm_sec;
349 // m_msec = static_cast<INT32>((long)secs * MsPerSecond + tv.tv_usec /
350 // 1000);
351}
352
353void CDateTime::Set(INT32 dayno) {
354 Validate(dayno);
355 m_dayno = dayno;
356 m_msec = 0;
357}
358
359void CDateTime::Set(INT32 year, INT32 month, INT32 day) {
360 Validate(year, month, day, minValidHour, minValidMinute, minValidSecond, minValidMilliSecond);
361
362 m_dayno = CalculateDayNumber(year, month, day);
363 m_msec = 0;
364}
365
366void CDateTime::Set(INT32 hour, INT32 minute, INT32 second, INT32 msec) {
367 Validate(minValidYear, minValidMonth, minValidDay, hour, minute, second, msec);
368
369 m_msec = ((hour * MinutesPerHour + minute) * SecondsPerMinute + second) * MsPerSecond + msec;
370}
371
372void CDateTime::Set(INT32 year, INT32 month, INT32 day, INT32 hour, INT32 minute, INT32 second, INT32 msec) {
373 Validate(year, month, day, hour, minute, second, msec);
374
375 m_dayno = CalculateDayNumber(year, month, day);
376 m_msec = ((hour * MinutesPerHour + minute) * SecondsPerMinute + second) * MsPerSecond + msec;
377}
378
379// DaynoToYMD converts a day index to
380// its corresponding calendar value (mm/dd/yr). The valid range for days
381// is { 0 .. ~3.65M } for dates from 1-Jan-0001 to 31-Dec-9999.
382void CDateTime::GetYMD(INT32 *year, INT32 *month, INT32 *day) {
383 INT32 dayno = m_dayno;
384
385 // local variables
386 INT32 y, m;
387
388 y = 1; // based on year 1 AD
389 y += (dayno / dy400) * 400;
390 dayno %= dy400;
391 if (dayno == dy400 - 1) // special case for last day of 400-year leap-year
392 {
393 y += 399;
394 dayno -= 3 * dy100 + 24 * dy4 + 3 * dy1;
395 } else {
396 y += (dayno / dy100) * 100;
397 dayno %= dy100;
398 y += (dayno / dy4) * 4;
399 dayno %= dy4;
400 if (dayno == dy4 - 1) // special case for last day of 4-year leap-year
401 {
402 y += 3;
403 dayno -= 3 * dy1;
404 } else {
405 y += dayno / dy1;
406 dayno %= dy1;
407 }
408 }
409
410 m = 1;
411 dayno++;
412 if (IsLeapYear(y)) {
413 while (dayno > monthArrayLY[m - 1]) {
414 dayno -= monthArrayLY[m - 1];
415 m++;
416 }
417 } else {
418 while (dayno > monthArray[m - 1]) {
419 dayno -= monthArray[m - 1];
420 m++;
421 }
422 }
423
424 *year = y;
425 *month = m;
426 *day = dayno;
427}
428
429void CDateTime::GetYMDHMS(INT32 *year, INT32 *month, INT32 *day, INT32 *hour, INT32 *minute, INT32 *second,
430 INT32 *msec) {
431 GetYMD(year, month, day);
432 GetHMS(hour, minute, second, msec);
433}
434
435void CDateTime::GetHMS(INT32 *hour, INT32 *minute, INT32 *second, INT32 *msec) {
436 INT32 ms = m_msec;
437
438 *msec = ms % MsPerSecond;
439 ms /= MsPerSecond;
440 *second = ms % SecondsPerMinute;
441 ms /= SecondsPerMinute;
442 *minute = ms % MinutesPerHour;
443 *hour = ms / MinutesPerHour;
444}
445
446void CDateTime::GetTimeStamp(TPCE::TIMESTAMP_STRUCT *ts) {
447 INT32 year, month, day, hour, minute, second, msec;
448
449 GetYMDHMS(&year, &month, &day, &hour, &minute, &second, &msec);
450 ts->year = (INT16)year;
451 ts->month = (UINT16)month;
452 ts->day = (UINT16)day;
453 ts->hour = (UINT16)hour;
454 ts->minute = (UINT16)minute;
455 ts->second = (UINT16)second;
456 ts->fraction = (UINT32)msec * 1000000; // because "fraction" is 1/billion'th of a second
457}
458
459#ifdef COMPILE_ODBC_LOAD
460static const INT32 dayno_1Jan1900 = CDateTime::YMDtoDayno(1900, 1, 1);
461
462void CDateTime::GetDBDATETIME(DBDATETIME *dt) {
463 dt->dtdays = m_dayno - dayno_1Jan1900;
464 dt->dttime = m_msec * 3 / 10;
465}
466#endif // COMPILE_ODBC_LOAD
467
468void CDateTime::Add(INT32 days, INT32 msec, bool adjust_weekend /* =false */) {
469 if (adjust_weekend) {
470 days = ((days / DaysPerWorkWeek) * DaysPerWeek) + (days % DaysPerWorkWeek);
471 }
472
473 m_dayno += days;
474
475 m_msec += msec;
476 m_dayno += m_msec / MsPerDay;
477 m_msec %= MsPerDay;
478 if (m_msec < 0) {
479 m_dayno--;
480 m_msec += MsPerDay;
481 }
482}
483void CDateTime::AddMinutes(INT32 Minutes) {
484 Add(0, Minutes * SecondsPerMinute * MsPerSecond);
485}
486void CDateTime::AddWorkMs(INT64 WorkMs) {
487 INT32 WorkDays = (INT32)(WorkMs / (INT64)MsPerWorkDay);
488 Add(WorkDays, (INT32)(WorkMs % MsPerWorkDay), true);
489}
490bool CDateTime::operator<(const CDateTime &dt) {
491 return (m_dayno == dt.m_dayno) ? (m_msec < dt.m_msec) : (m_dayno < dt.m_dayno);
492}
493
494bool CDateTime::operator<=(const CDateTime &dt) {
495 return (m_dayno == dt.m_dayno) ? (m_msec <= dt.m_msec) : (m_dayno <= dt.m_dayno);
496}
497
498namespace TPCE {
499
500// Need const reference left argument for greater<CDateTime> comparison function
501bool operator>(const CDateTime &l_dt, const CDateTime &r_dt) {
502 return (l_dt.m_dayno == r_dt.m_dayno) ? (l_dt.m_msec > r_dt.m_msec) : (l_dt.m_dayno > r_dt.m_dayno);
503}
504
505} // namespace TPCE
506
507bool CDateTime::operator>=(const CDateTime &dt) {
508 return (m_dayno == dt.m_dayno) ? (m_msec >= dt.m_msec) : (m_dayno >= dt.m_dayno);
509}
510
511bool CDateTime::operator==(const CDateTime &dt) {
512 return m_dayno == dt.m_dayno ? m_msec == dt.m_msec : false;
513}
514
515// compute the difference between two DateTimes;
516// result in seconds
517double CDateTime::operator-(const CDateTime &dt) {
518 double dSecs;
519 dSecs = (double)((m_dayno - dt.m_dayno) * SecondsPerMinute * MinutesPerHour * HoursPerDay);
520 dSecs += (double)(m_msec - dt.m_msec) / MsPerSecondDivisor;
521 return dSecs;
522}
523
524INT32 CDateTime::DiffInMilliSeconds(const CDateTime &BaseTime) {
525 INT32 mSecs;
526 mSecs = (m_dayno - BaseTime.m_dayno) * MsPerSecond * SecondsPerMinute * MinutesPerHour * HoursPerDay;
527 mSecs += (m_msec - BaseTime.m_msec);
528 return mSecs;
529}
530
531INT32 CDateTime::DiffInMilliSeconds(CDateTime *pBaseTime) {
532 INT32 mSecs;
533 mSecs = (m_dayno - pBaseTime->m_dayno) * MsPerSecond * SecondsPerMinute * MinutesPerHour * HoursPerDay;
534 mSecs += (m_msec - pBaseTime->m_msec);
535 return mSecs;
536}
537
538CDateTime &CDateTime::operator=(const CDateTime &dt) {
539 // Assume source is valid.
540 // Validate( dt.m_dayno, dt.m_msec );
541 m_dayno = dt.m_dayno;
542 m_msec = dt.m_msec;
543
544 return *this;
545}
546
547CDateTime &CDateTime::operator+=(const CDateTime &dt) {
548 Add(dt.m_dayno, dt.m_msec);
549
550 return *this;
551}
552