bes Updated for version 3.21.1
The Backend Server (BES) is the lower two tiers of the Hyrax data server
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
36static 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 <libdap/BaseType.h>
48#include <libdap/Str.h>
49#include <libdap/debug.h>
50
51using 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 <libdap/Error.h>
62
63using namespace std;
64
65static 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
88bool 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
101{
102 string s = extract_argument(arg);
103 set(s);
104}
105
106DODS_Date::DODS_Date(string date_str)
107{
108 set(date_str);
109}
110
111DODS_Date::DODS_Date(int year, int day_num)
112{
113 set(year, day_num);
114}
115
117{
118 set(year, month, day);
119}
120
121DODS_Date::DODS_Date(int year, int month, int day, date_format format)
122{
123 set(year, month, day, format);
124}
125
126void 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
136void 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
172void 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.size() == 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
223void 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
290void 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.size() == 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
312void 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
324void 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
337void 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
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
361{
362 return d1._julian_day != d2._julian_day ? 1 : 0;
363}
364
366{
367 return d1._julian_day < d2._julian_day ? 1 : 0;
368}
369
371{
372 return d1._julian_day > d2._julian_day ? 1 : 0;
373}
374
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
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
393{
394 return _year;
395}
396
398{
399 return _month;
400}
401
402int 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
419date_format DODS_Date::format() const
420{
421 return _format;
422}
423
424double DODS_Date::fraction() const
425{
426 return _year + (_day_number - 1) / days_in_year(_year);
427}
428
429string 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
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
487int 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//
int day_number() const
Definition DODS_Date.cc:407
friend int operator==(DODS_Date &d1, DODS_Date &d2)
Equality.
Definition DODS_Date.cc:349
friend int operator<(DODS_Date &d1, DODS_Date &d2)
Less than.
Definition DODS_Date.cc:365
int day() const
Definition DODS_Date.cc:402
void set(string date)
Definition DODS_Date.cc:290
friend int operator>=(DODS_Date &d1, DODS_Date &d2)
Greater than or equal.
Definition DODS_Date.cc:383
friend int operator!=(DODS_Date &d1, DODS_Date &d2)
Inequality.
Definition DODS_Date.cc:360
int year() const
Definition DODS_Date.cc:392
bool OK() const
Definition DODS_Date.cc:88
friend int operator>(DODS_Date &d1, DODS_Date &d2)
Greater than.
Definition DODS_Date.cc:370
string get(date_format format=ymd) const
Definition DODS_Date.cc:429
date_format format() const
Definition DODS_Date.cc:419
friend int operator<=(DODS_Date &d1, DODS_Date &d2)
Less than or equal.
Definition DODS_Date.cc:375
long julian_day() const
Definition DODS_Date.cc:412
time_t unix_time() const
Definition DODS_Date.cc:463
int month() const
Definition DODS_Date.cc:397