bes  Updated for version 3.20.6
DODS_Date.cc
1 // -*- mode: c++; c-basic-offset:4 -*-
2 
3 // This file is part of ff_handler a FreeForm API handler for the OPeNDAP
4 // DAP2 data server.
5 
6 // Copyright (c) 2005 OPeNDAP, Inc.
7 // Author: James Gallagher <jgallagher@opendap.org>
8 //
9 // This is free software; you can redistribute it and/or modify it under the
10 // terms of the GNU Lesser General Public License as published by the Free
11 // Software Foundation; either version 2.1 of the License, or (at your
12 // option) any later version.
13 //
14 // This software is distributed in the hope that it will be useful, but
15 // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16 // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
17 // License for more details.
18 //
19 // You should have received a copy of the GNU Lesser General Public
20 // License along with this library; if not, write to the Free Software
21 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
22 //
23 // You can contact OPeNDAP, Inc. at PO Box 112, Saunderstown, RI. 02874-0112.
24 
25 // (c) COPYRIGHT URI/MIT 1998
26 // Please read the full copyright statement in the file COPYRIGHT.
27 //
28 // Authors:
29 // jhrg,jimg James Gallagher (jgallagher@gso.uri.edu)
30 
31 //
32 // Implementation of the DODS Date class
33 
34 #include "config_ff.h"
35 
36 static char rcsid[] not_used ="$Id$";
37 
38 #include <cassert>
39 #include <cstdlib>
40 #include <sstream>
41 #include <string>
42 #include <iomanip>
43 
44 #include "DODS_Date.h"
45 #include "date_proc.h"
46 
47 #include <BaseType.h>
48 #include <Str.h>
49 #include <debug.h>
50 
51 using namespace std;
52 
53 // The Error class is defined in the core software. For testing we don't need
54 // this function and can supply a dummy version. That simplifies building the
55 // test code. 11/12/98 jhrg
56 
57 // This function is repeated in DODS_Time, something that should be changed,
58 // at the least. However, the real problem is that it is pretty hard to read
59 // values from DODS types. 1/8/99 jhrg
60 
61 #include "Error.h"
62 
63 using namespace std;
64 
65 static string extract_argument(BaseType *arg)
66 {
67 #ifndef TEST
68  if (arg->type() != dods_str_c)
69  throw Error(malformed_expr, "The Projection function requires a DODS string-type argument.");
70 #if 0
71  // Use String until conversion of String to string is complete. 9/3/98
72  // jhrg
73  string *sp = NULL;
74  arg->buf2val((void **) &sp);
75  string s = sp->c_str();
76  delete sp;
77 
78  DBG(cerr << "s: " << s << endl);
79 
80  return s;
81 #endif
82  return static_cast<Str*>(arg)->value();
83 #else
84  return "";
85 #endif
86 }
87 
88 bool DODS_Date::OK() const
89 {
90  return _year > 0 && _month > 0 && _day > 0 && _julian_day > 0 && _day_number > 0 && _format != unknown_format;
91 }
92 
93 // Public member functions.
94 
96  _julian_day(0), _year(0), _month(0), _day(0), _day_number(0), _format(unknown_format)
97 {
98 }
99 
100 DODS_Date::DODS_Date(BaseType *arg)
101 {
102  string s = extract_argument(arg);
103  set(s);
104 }
105 
106 DODS_Date::DODS_Date(string date_str)
107 {
108  set(date_str);
109 }
110 
111 DODS_Date::DODS_Date(int year, int day_num)
112 {
113  set(year, day_num);
114 }
115 
116 DODS_Date::DODS_Date(int year, int month, int day)
117 {
118  set(year, month, day);
119 }
120 
121 DODS_Date::DODS_Date(int year, int month, int day, date_format format)
122 {
123  set(year, month, day, format);
124 }
125 
126 void DODS_Date::set(BaseType *arg)
127 {
128  string s = extract_argument(arg);
129  set(s);
130 }
131 
132 // The software that parses data strings is pretty weak on error checking.
133 // This should be bolstered. For example, a real parser which flags invalid
134 // separators, etc. would improve error detection.
135 
136 void DODS_Date::parse_integer_time(string date)
137 {
138  // Parse the date_str.
139  istringstream iss(date.c_str());
140  char c;
141  size_t pos1, pos2;
142  iss >> _year;
143  iss >> c;
144  iss >> _month;
145 
146  // If there are two slashes, assume a yyyy/mm/dd date.
147  pos1 = date.find("/");
148  pos2 = date.rfind("/");
149  if ((pos1 == date.npos) && (pos2 == date.npos)) {
150  string msg = "I cannot understand the date string: ";
151  msg += date + ". I expected a date formatted like yyyy/mm/dd or yyyy/ddd.";
152  throw Error(malformed_expr, msg);
153  }
154  else if ((pos1 != pos2)) {
155  iss >> c;
156  iss >> _day;
157  // Convert to julian day number and record year, month, ...
158  _julian_day = ::julian_day(_year, _month, _day);
159  _day_number = month_day_to_days(_year, _month, _day);
160  _format = ymd; // jhrg 10/1/13
161  }
162  else {
163  // Note that when a `yyyy/ddd' date is read in, the day-number winds
164  // up in the `_month' member.
165  _day_number = _month;
166  days_to_month_day(_year, _day_number, &_month, &_day);
167  _julian_day = ::julian_day(_year, _month, _day);
168  _format = yd; // jhrg 10/1/13
169  }
170 }
171 
172 void DODS_Date::parse_iso8601_time(string date)
173 {
174  // Parse the date_str.
175  istringstream iss(date.c_str());
176  char c;
177  size_t pos1, pos2;
178  iss >> _year;
179  iss >> c;
180  iss >> _month;
181 
182  // If there are two dashes, assume a ccyy-mm-dd date.
183  pos1 = date.find("-");
184  pos2 = date.rfind("-");
185  if ((pos1 != date.npos) && (pos2 != date.npos) && (pos1 != pos2)) {
186  iss >> c;
187  iss >> _day;
188  // Convert to julian day number and record year, month, ...
189  _julian_day = ::julian_day(_year, _month, _day);
190  _day_number = month_day_to_days(_year, _month, _day);
191  _format = ymd;
192  }
193  // Added parens around the AND below. jhrg 9/26/13
194  else if (((pos1 != date.npos) && (pos2 == date.npos)) || (pos1 == pos2)) {
195  // There is one dash, assume a ccyy-mm date.
196  _day = 1;
197  _julian_day = ::julian_day(_year, _month, _day);
198  _day_number = month_day_to_days(_year, _month, _day);
199  _format = ym;
200  }
201 
202  else if ((pos1 == date.npos) && (date.length() == 4)) {
203  // There are no dashes, assume a ccyy date.
204  _day = 1;
205  _month = 1;
206  _julian_day = ::julian_day(_year, _month, _day);
207  _day_number = month_day_to_days(_year, _month, _day);
208  _format = ym;
209  }
210  else {
211  string msg = "I cannot understand the date string: ";
212  msg += date + ". I expected an iso8601 date (ccyy-mm-dd, ccyy-mm or ccyy).";
213  throw Error(malformed_expr, msg);
214  }
215 }
216 
217 // This parser was originally used to build both DODS_Date and DODS_Time
218 // objects in Dan's DODS_Decimal_Year class. I've left in the code that does
219 // stuff for time because the fractional part of the seconds might bump the
220 // day count (and because it was easy to use the code without changing it
221 // :-). 5/29/99 jhrg
222 
223 void DODS_Date::parse_fractional_time(string dec_year)
224 {
225  double secs_in_year;
226  double d_year_day, d_hr_day, d_min_day, d_sec_day;
227  int i_year, i_year_day, i_hr_day, i_min_day, i_sec_day;
228 
229  // The format for the decimal-year string is <year part>.<fraction part>.
230 
231  double d_year = strtod(dec_year.c_str(), 0);
232 
233  i_year = (int) d_year;
234  double year_fraction = d_year - i_year;
235 
236  secs_in_year = days_in_year(_year) * seconds_per_day;
237 
238  //
239  // Recreate the 'day' in the year.
240  //
241  d_year_day = (secs_in_year * year_fraction) / seconds_per_day + 1;
242  i_year_day = (int) d_year_day;
243 
244  //
245  // Recreate the 'hour' in the day.
246  //
247  d_hr_day = ((d_year_day - i_year_day) * seconds_per_day) / seconds_per_hour;
248  i_hr_day = (int) d_hr_day;
249 
250  //
251  // Recreate the 'minute' in the hour.
252  //
253  d_min_day = ((d_hr_day - i_hr_day) * seconds_per_hour) / seconds_per_minute;
254  i_min_day = (int) d_min_day;
255 
256  //
257  // Recreate the 'second' in the minute.
258  //
259  d_sec_day = (d_min_day - i_min_day) * seconds_per_minute;
260  i_sec_day = (int) d_sec_day;
261 
262  //
263  // Round-off second to nearest value, handle condition
264  // where seconds/minutes roll over modulo values.
265  //
266  if ((d_sec_day - i_sec_day) >= .5) i_sec_day++;
267 
268  if (i_sec_day == 60) {
269  // i_sec_day = 0;
270  i_min_day++;
271  if (i_min_day == 60) {
272  // i_min_day = 0;
273  i_hr_day++;
274  if (i_hr_day == 24) {
275  // i_hr_day = 0;
276  i_year_day++;
277  if (i_year_day == (days_in_year(_year) + 1)) {
278  i_year_day = 1;
279  i_year++;
280  }
281  }
282  }
283  }
284 
285  set(i_year, i_year_day);
286 
287  assert(OK());
288 }
289 
290 void DODS_Date::set(string date)
291 {
292  // Check for fractional date/time strings.
293  if (date.find(".") != string::npos) {
294  parse_fractional_time(date);
295  }
296  else if (date.find("/") != string::npos) {
297  parse_integer_time(date);
298  }
299  else if (date.find("-") != string::npos) {
300  parse_iso8601_time(date);
301  }
302  else if (date.length() == 4) {
303  date += "-1-1";
304  parse_iso8601_time(date);
305  }
306  else
307  throw Error(malformed_expr, "Could not recognize date format");
308 
309  assert(OK());
310 }
311 
312 void DODS_Date::set(int year, int day_num)
313 {
314  _year = year;
315  _day_number = day_num;
316  days_to_month_day(_year, _day_number, &_month, &_day);
317  _julian_day = ::julian_day(_year, _month, _day);
318 
319  _format = yd; // jhrg 10/1/13
320 
321  assert(OK());
322 }
323 
324 void DODS_Date::set(int year, int month, int day)
325 {
326  _year = year;
327  _month = month;
328  _day = day;
329  _day_number = month_day_to_days(_year, _month, _day);
330  _julian_day = ::julian_day(_year, _month, _day);
331 
332  _format = ymd; // jhrg 10/1/13
333 
334  assert(OK());
335 }
336 
337 void DODS_Date::set(int year, int month, int day, date_format format)
338 {
339  _year = year;
340  _month = month;
341  _day = day;
342  _day_number = month_day_to_days(_year, _month, _day);
343  _julian_day = ::julian_day(_year, _month, _day);
344  _format = format;
345 
346  assert(OK());
347 }
348 
349 int operator==(DODS_Date &d1, DODS_Date &d2)
350 {
351  if (d2.format() == ym) {
352  return ((d2._julian_day >= ::julian_day(d1.year(), d1.month(), 1))
353  && (d2._julian_day <= ::julian_day(d1.year(), d1.month(), days_in_month(d1.year(), d1.month())))) ?
354  1 : 0;
355  }
356  else
357  return d1._julian_day == d2._julian_day ? 1 : 0;
358 }
359 
360 int operator!=(DODS_Date &d1, DODS_Date &d2)
361 {
362  return d1._julian_day != d2._julian_day ? 1 : 0;
363 }
364 
365 int operator<(DODS_Date &d1, DODS_Date &d2)
366 {
367  return d1._julian_day < d2._julian_day ? 1 : 0;
368 }
369 
370 int operator>(DODS_Date &d1, DODS_Date &d2)
371 {
372  return d1._julian_day > d2._julian_day ? 1 : 0;
373 }
374 
375 int operator<=(DODS_Date &d1, DODS_Date &d2)
376 {
377  if (d2.format() == ym)
378  return ((d2._julian_day >= ::julian_day(d1.year(), d1.month(), 1)) && true) ? 1 : 0;
379  else
380  return d1._julian_day <= d2._julian_day ? 1 : 0;
381 }
382 
383 int operator>=(DODS_Date &d1, DODS_Date &d2)
384 {
385  if (d2.format() == ym)
386  return ((d2._julian_day <= ::julian_day(d1.year(), d1.month(), days_in_month(d1.year(), d1.month()))) && true) ?
387  1 : 0;
388  else
389  return d1._julian_day >= d2._julian_day ? 1 : 0;
390 }
391 
392 int DODS_Date::year() const
393 {
394  return _year;
395 }
396 
397 int DODS_Date::month() const
398 {
399  return _month;
400 }
401 
402 int DODS_Date::day() const
403 {
404  return _day;
405 }
406 
408 {
409  return _day_number;
410 }
411 
413 {
414  return _julian_day;
415 }
416 
417 // Return the fractional part of the date. A private function.
418 
419 date_format DODS_Date::format() const
420 {
421  return _format;
422 }
423 
424 double DODS_Date::fraction() const
425 {
426  return _year + (_day_number - 1) / days_in_year(_year);
427 }
428 
429 string DODS_Date::get(date_format format) const
430 {
431  ostringstream oss;
432 
433  switch (format) {
434  case yd:
435  oss << _year << "/" << _day_number;
436  break;
437  case ymd:
438  oss << _year << "/" << _month << "/" << _day;
439  break;
440  case iso8601:
441  if (_format == ym) {
442  oss << _year << "-" << setfill('0') << setw(2) << _month;
443  }
444  else {
445  oss << _year << "-" << setfill('0') << setw(2) << _month << "-" << setfill('0') << setw(2) << _day;
446  }
447  break;
448  case decimal:
449  oss.precision(14);
450  oss << fraction();
451  break;
452  default:
453 #ifndef TEST
454  throw Error(unknown_error, "Invalid date format");
455 #else
456  assert("Invalid date format" && false);
457 #endif
458  }
459 
460  return oss.str();
461 }
462 
463 time_t DODS_Date::unix_time() const
464 {
465  struct tm tm_rec;
466  tm_rec.tm_mday = _day;
467  tm_rec.tm_mon = _month - 1; // zero-based
468  tm_rec.tm_year = _year - 1900; // years since 1900
469  tm_rec.tm_hour = 0;
470  tm_rec.tm_min = 0;
471  tm_rec.tm_sec = 1; // smallest time into the day
472  tm_rec.tm_isdst = -1;
473 
474  return mktime(&tm_rec);
475 }
476 
477 #ifdef TEST_DATE
478 
479 // Call this with one, two or three args. If one arg, call the string ctor.
480 // If two or three args, use the yd or ymd ctor. Once built, compare to 1 Jan
481 // 1970 and then call the yd_date() and ymd_date() mfuncs. 11/4/98 jhrg
482 
483 // Build with: `g++ -g -I../../include -DHAVE_CONFIG_H -DTEST_DATE -TEST
484 // DODS_Date.cc date_proc.o -lg++'. Add: `-ftest-coverage -fprofile-arcs' for
485 // test coverage.
486 
487 int main(int argc, char *argv[])
488 {
489  DODS_Date epoc((string)"1970/1/1");
490  DODS_Date d1;
491 
492  switch (--argc) {
493  case 1:
494  d1.set((string)argv[1]);
495  break;
496  case 2:
497  d1.set(atoi(argv[1]), atoi(argv[2]));
498  break;
499  case 3:
500  d1.set(atoi(argv[1]), atoi(argv[2]), atoi(argv[3]));
501  break;
502  case 4:
503  d1.set(atoi(argv[1]), atoi(argv[2]), atoi(argv[3]), (date_format)atoi(argv[4]));
504  break;
505  default:
506  cerr << "Wrong number of args!" << endl;
507  abort();
508  }
509 
510  if (d1 < epoc)
511  cout << "True: d1 < epoc" << endl;
512  else
513  cout << "False: d1 < epoc" << endl;
514 
515  if (d1 > epoc)
516  cout << "True: d1 > epoc" << endl;
517  else
518  cout << "False: d1 > epoc" << endl;
519 
520  if (d1 <= epoc)
521  cout << "True: d1 <= epoc" << endl;
522  else
523  cout << "False: d1 <= epoc" << endl;
524 
525  if (d1 >= epoc)
526  cout << "True: d1 >= epoc" << endl;
527  else
528  cout << "False: d1 >= epoc" << endl;
529 
530  if (d1 == epoc)
531  cout << "True: d1 == epoc" << endl;
532  else
533  cout << "False: d1 == epoc" << endl;
534 
535  if (d1 != epoc)
536  cout << "True: d1 != epoc" << endl;
537  else
538  cout << "False: d1 != epoc" << endl;
539 
540  cout << "YMD: " << d1.get() << endl;
541  cout << "ISO8601: " << d1.get(iso8601) << endl;
542  cout << "YD: " << d1.get(yd) << endl;
543  cout << "Julian day: " << d1.julian_day() << endl;
544  cout << "Seconds: " << d1.unix_time() << endl;
545 }
546 #endif // TEST_DATE
547 // $Log: DODS_Date.cc,v $
548 // Revision 1.16 2004/07/09 17:54:24 jimg
549 // Merged with release-3-4-3FCS.
550 //
551 // Revision 1.12.4.2 2004/03/07 22:05:51 rmorris
552 // Final code changes to port the freeform server to win32.
553 //
554 // Revision 1.15 2004/02/04 20:50:08 jimg
555 // Build fixes. No longer uses Pix.
556 //
557 // Revision 1.14 2003/12/08 22:01:12 edavis
558 // Merge release-3-4 into trunk
559 //
560 // Revision 1.12.4.1 2003/06/29 05:35:10 rmorris
561 // Use standard template library headers correctly and add missing usage
562 // statements.
563 //
564 // Revision 1.13 2003/05/14 19:23:13 jimg
565 // Changed from strstream to sstream.
566 //
567 // Revision 1.12 2003/02/10 23:01:52 jimg
568 // Merged with 3.2.5
569 //
570 // Revision 1.11 2001/09/28 23:19:43 jimg
571 // Merged with 3.2.3.
572 //
573 // Revision 1.10.2.3 2002/11/13 05:51:57 dan
574 // Modified get(date_format format) method, renaming
575 // return variable from 'yd' to 'dateString'. 'yd' is
576 // a value of the enumeration date_format and in multi-threaded
577 // code this was causing a seg-fault in mutex-lock.
578 //
579 // Revision 1.10.2.2 2001/09/19 22:40:06 jimg
580 // Added simple error checking for malformed dates. Works sometimes... To do
581 // a thorough job will take at least a day.
582 //
583 // Revision 1.10.2.1 2001/05/23 18:25:29 dan
584 // Modified to support year/month date representations,
585 // and to support ISO8601 output formats.
586 //
587 // Revision 1.10 2000/10/11 19:37:55 jimg
588 // Moved the CVS log entries to the end of files.
589 // Changed the definition of the read method to match the dap library.
590 // Added exception handling.
591 // Added exceptions to the read methods.
592 //
593 // Revision 1.9 2000/08/31 22:16:53 jimg
594 // Merged with 3.1.7
595 //
596 // Revision 1.8.2.1 2000/08/03 20:18:57 jimg
597 // Removed config_dap.h and replaced it with config_ff.h (in *.cc files;
598 // neither should be included in a header file).
599 // Changed code that calculated leap year information so that it uses the
600 // functions in date_proc.c/h.
601 //
602 // Revision 1.8 1999/07/22 21:28:08 jimg
603 // Merged changes from the release-3-0-2 branch
604 //
605 // Revision 1.7.2.1 1999/06/01 15:38:05 jimg
606 // Added code to parse and return floating point dates.
607 //
608 // Revision 1.7 1999/05/27 17:02:21 jimg
609 // Merge with alpha-3-0-0
610 //
611 // Revision 1.6.2.1 1999/05/20 21:37:26 edavis
612 // Fix spelling of COPYRIGHT and remove some #if 0 stuff.
613 //
614 // Revision 1.6 1999/05/04 02:55:35 jimg
615 // Merge with no-gnu
616 //
617 // Revision 1.5.6.1 1999/05/01 04:40:28 brent
618 // converted old String.h to the new std C++ <string> code
619 //
620 // Revision 1.5 1999/01/08 22:09:01 jimg
621 // Added some comments about errors.
622 //
623 // Revision 1.4 1999/01/05 00:34:04 jimg
624 // Removed string class; replaced with the GNU String class. It seems those
625 // don't mix well.
626 // Switched to simpler method names.
627 //
628 // Revision 1.3 1998/12/30 06:39:18 jimg
629 // Define TEST when building the test version (also define DATE_TEST).
630 //
631 // Revision 1.2 1998/12/30 02:00:58 jimg
632 // Added class invariant.
633 //
634 // Revision 1.1 1998/12/28 19:08:25 jimg
635 // Initial version of the DODS_Date object
636 //
DODS_Date::DODS_Date
DODS_Date()
Definition: DODS_Date.cc:95
DODS_Date::julian_day
long julian_day() const
Definition: DODS_Date.cc:412
DODS_Date
Definition: DODS_Date.h:108
DODS_Date::OK
bool OK() const
Definition: DODS_Date.cc:88
DODS_Date::format
date_format format() const
Definition: DODS_Date.cc:419
DODS_Date::month
int month() const
Definition: DODS_Date.cc:397
DODS_Date::day
int day() const
Definition: DODS_Date.cc:402
DODS_Date::day_number
int day_number() const
Definition: DODS_Date.cc:407
DODS_Date::year
int year() const
Definition: DODS_Date.cc:392
Error
DODS_Date::set
void set(string date)
Definition: DODS_Date.cc:290
DODS_Date::get
string get(date_format format=ymd) const
Definition: DODS_Date.cc:429
DODS_Date::unix_time
time_t unix_time() const
Definition: DODS_Date.cc:463