WSL/SLF GitLab Repository

Date.cc 33.5 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/***********************************************************************************/
/*  Copyright 2009 WSL Institute for Snow and Avalanche Research    SLF-DAVOS      */
/***********************************************************************************/
/* This file is part of MeteoIO.
    MeteoIO is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    MeteoIO is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with MeteoIO.  If not, see <http://www.gnu.org/licenses/>.
*/
#include <meteoio/Date.h>
19
#include <meteoio/IOUtils.h>
20
#include <meteoio/MathOptim.h>
21
#include <cmath>
22
23
24
25
26

using namespace std;

namespace mio {

27
28
const int Date::daysLeapYear[12] = {31,29,31,30,31,30,31,31,30,31,30,31};
const int Date::daysNonLeapYear[12] = {31,28,31,30,31,30,31,31,30,31,30,31};
29
30
31
32
const double Date::DST_shift = 1.0; //in hours
const float Date::MJD_offset = 2400000.5; ///<offset between julian date and modified julian date
const float Date::Unix_offset = 2440587.5; ///<offset between julian date and Unix Epoch time
const float Date::Excel_offset = 2415018.5;  ///<offset between julian date and Excel dates (note that excel invented some days...)
33
const float Date::Matlab_offset = 1721058.5; ///<offset between julian date and Matlab dates
34

35
36
37
const double Date::epsilon=1./(24.*3600.); ///< minimum difference between two dates. 1 second in units of days
//NOTE: For the comparison operators, we assume that dates are positive so we can bypass a call to abs()

38
39
40
41
// CONSTUCTORS
/**
* @brief Default constructor: timezone is set to GMT without DST, julian date is set to 0 (meaning -4713-01-01T12:00)
*/
42
Date::Date() : timezone(0.), gmt_julian(0.),
43
               gmt_year(0), gmt_month(0), gmt_day(0), gmt_hour(0), gmt_minute(0),
44
45
               dst(false), undef(true)
{
46
47
48
49
50
51
52
53
}

/**
* @brief Julian date constructor.
* @param julian_in julian date to set
* @param in_timezone timezone as an offset to GMT (in hours, optional)
* @param in_dst is it DST? (default: no)
*/
54
55
Date::Date(const double& julian_in, const double& in_timezone, const bool& in_dst)
         : timezone(0.), gmt_julian(0.),
56
           gmt_year(0), gmt_month(0), gmt_day(0), gmt_hour(0), gmt_minute(0),
57
           dst(false), undef(true)
58
{
59
60
61
62
63
64
65
66
	setDate(julian_in, in_timezone, in_dst);
}

/**
* @brief Unix date constructor.
* @param in_time unix time (ie: as number of seconds since Unix Epoch, always UTC)
* @param in_dst is it DST? (default: no)
*/
67
68
Date::Date(const time_t& in_time, const bool& in_dst)
         : timezone(in_dst), gmt_julian(0.),
69
           gmt_year(0), gmt_month(0), gmt_day(0), gmt_hour(0), gmt_minute(0),
70
71
           dst(false), undef(true)
{
72
	setDate(in_time, in_dst);
73
74
75
76
77
78
79
80
81
82
83
84
85
}

/**
* @brief Date constructor by elements.
* All values are checked for plausibility.
* @param in_year in 4 digits
* @param in_month please keep in mind that first month of the year is 1 (ie: not 0!)
* @param in_day please keep in mind that first day of the month is 1 (ie: not 0!)
* @param in_hour
* @param in_minute
* @param in_timezone timezone as an offset to GMT (in hours, optional)
* @param in_dst is it DST? (default: no)
*/
86
87
Date::Date(const int& in_year, const int& in_month, const int& in_day, const int& in_hour, const int& in_minute, const double& in_timezone, const bool& in_dst)
         : timezone(in_timezone), gmt_julian(0.),
88
           gmt_year(0), gmt_month(0), gmt_day(0), gmt_hour(0), gmt_minute(0),
89
           dst(false), undef(true)
90
91
92
93
94
95
96
97
98
{
	setDate(in_year, in_month, in_day, in_hour, in_minute, in_timezone, in_dst);
}

// SETTERS
void Date::setUndef(const bool& flag) {
	undef = flag;
}
/**
99
* @brief Set internal gmt time from system time as well as system time zone.
100
101
*/
void Date::setFromSys() {
102
	/*const time_t curr = time(NULL); //current time in UTC
103
	tm local = *localtime(&curr); //convert to local time
104
105
106
107
108
	const double tz = (double)local.tm_gmtoff/3600.; //time zone shift*/

	const time_t curr = time(NULL);// current time in UTC
	tm local = *gmtime(&curr);// current time in UTC, stored as tm
	const time_t utc = (mktime(&local));// convert GMT tm to GMT time_t
109
	double tz = - difftime(utc,curr)/3600.; //time zone shift (sign so that if curr>utc, tz>0)
110
111
112

	setDate( curr ); //Unix time_t setter, always in gmt
	setTimeZone( tz );
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
}

/**
* @brief Set timezone and Daylight Saving Time flag.
* @param in_timezone timezone as an offset to GMT (in hours)
* @param in_dst is it DST?
*/
void Date::setTimeZone(const double& in_timezone, const bool& in_dst) {
//please keep in mind that timezone might be fractional (ie: 15 minutes, etc)
	if(abs(in_timezone) > 12) {
		throw InvalidArgumentException("[E] Time zone can NOT be greater than +/-12!!", AT);
	}

	timezone = in_timezone;
	dst = in_dst;
}

130
131
132
133
134
135
136
137
138
139
/**
* @brief Copy setter.
* @param in_date Date object to copy
*/
void Date::setDate(const Date& in_date)
{
	if(in_date.isUndef()) {
		dst = false;
		undef = true;
	} else {
140
		setDate(in_date.getJulian(), in_date.getTimeZone(), in_date.getDST());
141
142
143
	}
}

144
145
146
/**
* @brief Set date by elements.
* All values are checked for plausibility.
147
148
149
150
151
152
153
* @param i_year in 4 digits
* @param i_month please keep in mind that first month of the year is 1 (ie: not 0!)
* @param i_day please keep in mind that first day of the month is 1 (ie: not 0!)
* @param i_hour
* @param i_minute
* @param i_timezone timezone as an offset to GMT (in hours, optional)
* @param i_dst is it DST? (default: no)
154
*/
155
void Date::setDate(const int& i_year, const int& i_month, const int& i_day, const int& i_hour, const int& i_minute, const double& i_timezone, const bool& i_dst)
156
{
157
	plausibilityCheck(i_year, i_month, i_day, i_hour, i_minute); //also checks leap years
158
	setTimeZone(i_timezone, i_dst);
159
160
161
	if(timezone==0 && dst==false) { //data is GMT and no DST
		//setting values and computing GMT julian date, rounded to internal precision of 1 second
		gmt_julian = rnd( calculateJulianDate(i_year, i_month, i_day, i_hour, i_minute), (unsigned)1);
162
163
	} else {
		//computing local julian date
164
		const double local_julian = calculateJulianDate(i_year, i_month, i_day, i_hour, i_minute);
165
166
		//converting local julian date to GMT julian date, rounded to internal precision of 1 second
		gmt_julian = rnd( localToGMT(local_julian), (unsigned)1);
167
	}
168
169
	//updating values to GMT, fixing potential 24:00 hour (ie: replaced by next day, 00:00)
	calculateValues(gmt_julian, gmt_year, gmt_month, gmt_day, gmt_hour, gmt_minute);
170
	undef = false;
171
172
}

173
void Date::setDate(const int& year, const unsigned int& month, const unsigned int& day, const unsigned int& hour, const unsigned int& minute, const double& in_timezone, const bool& in_dst)
174
175
176
177
{
	setDate(year, (signed)month, (signed)day, (signed)hour, (signed)minute, in_timezone, in_dst);
}

178
179
180
181
182
183
184
185
/**
* @brief Set date from a julian date (JD).
* @param julian_in julian date to set
* @param in_timezone timezone as an offset to GMT (in hours, optional)
* @param in_dst is it DST? (default: no)
*/
void Date::setDate(const double& julian_in, const double& in_timezone, const bool& in_dst) {
	setTimeZone(in_timezone, in_dst);
186
	gmt_julian = rnd( localToGMT(julian_in), (unsigned)1); //round to internal precision of 1 second
187
	calculateValues(gmt_julian, gmt_year, gmt_month, gmt_day, gmt_hour, gmt_minute);
188
	undef = false;
189
190
191
192
}

/**
* @brief Set date from a Unix date.
193
194
* @param i_time unix time (ie: as number of seconds since Unix Epoch, always UTC)
* @param i_dst is it DST? (default: no)
195
*/
196
197
void Date::setDate(const time_t& i_time, const bool& i_dst) {
	setUnixDate(i_time, i_dst);
198
199
200
201
202
}

/**
* @brief Set date from a modified julian date (MJD).
* @param julian_in julian date to set
203
204
* @param i_timezone timezone as an offset to GMT (in hours, optional)
* @param i_dst is it DST? (default: no)
205
*/
206
207
208
void Date::setModifiedJulianDate(const double& julian_in, const double& i_timezone, const bool& i_dst) {
	const double tmp_julian = julian_in + MJD_offset;
	setDate(tmp_julian, i_timezone, i_dst);
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
}

/**
* @brief Set date from a Unix date.
* @param in_time unix time (ie: as number of seconds since Unix Epoch, always UTC)
* @param in_dst is it DST? (default: no)
*/
void Date::setUnixDate(const time_t& in_time, const bool& in_dst) {
	const double in_julian = (double)(in_time)/(24.*60.*60.) + Unix_offset;
	setDate(in_julian, 0., in_dst);
}

/**
* @brief Set date from an Excel date.
* @param excel_in Excel date to set
224
225
* @param i_timezone timezone as an offset to GMT (in hours, optional)
* @param i_dst is it DST? (default: no)
226
*/
227
void Date::setExcelDate(const double excel_in, const double& i_timezone, const bool& i_dst) {
228
	//TODO: handle date < 1900-01-00 and date before 1900-03-01
229
	//see http://www.mathworks.com/help/toolbox/finance/x2mdate.html
230
231
	const double tmp_julian = excel_in + Excel_offset;
	setDate(tmp_julian, i_timezone, i_dst);
232
233
}

234
235
236
/**
* @brief Set date from an Matlab date.
* @param matlab_in Matlab date to set
237
238
* @param i_timezone timezone as an offset to GMT (in hours, optional)
* @param i_dst is it DST? (default: no)
239
*/
240
241
242
void Date::setMatlabDate(const double matlab_in, const double& i_timezone, const bool& i_dst) {
	const double tmp_julian = matlab_in + Matlab_offset;
	setDate(tmp_julian, i_timezone, i_dst);
243
244
245
}


246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
// GETTERS
bool Date::isUndef() const {
	return undef;
}

/**
* @brief Returns timezone.
* @return timezone as an offset to GMT
*/
double Date::getTimeZone() const {
	if(undef==true)
		throw UnknownValueException("Date object is undefined!", AT);

	return timezone;
}

/**
* @brief Returns Daylight Saving Time flag.
* @return dst enabled?
*/
bool Date::getDST() const {
	if(undef==true)
		throw UnknownValueException("Date object is undefined!", AT);

	return dst;
}

/**
* @brief Return julian date (JD).
* The julian date is defined as the fractional number of days since -4713-01-01T12:00 UTC.
* @param gmt convert returned value to GMT? (default: false)
* @return julian date in the current timezone / in GMT depending on the gmt parameter
*/
279
double Date::getJulian(const bool& gmt) const {
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
	if(undef==true)
		throw UnknownValueException("Date object is undefined!", AT);

	if(gmt) {
		return gmt_julian;
	} else {
		const double local_julian = GMTToLocal(gmt_julian);
		return local_julian;
	}
}

/**
* @brief Return modified julian date (MJD).
* The modified julian date is defined as the fractional number of days since 1858-11-17T00:00 UTC
* (definition by the Smithsonian Astrophysical Observatory, MA).
* @param gmt convert returned value to GMT? (default: false)
* @return modified julian date in the current timezone / in GMT depending on the gmt parameter
*/
double Date::getModifiedJulianDate(const bool& gmt) const {
	if(undef==true)
		throw UnknownValueException("Date object is undefined!", AT);

	if(gmt) {
		return (gmt_julian - MJD_offset);
	} else {
		const double local_julian = GMTToLocal(gmt_julian);
		return (local_julian - MJD_offset);
	}
}

/**
* @brief Return truncated julian date (TJD).
* The truncated julian date is defined as the julian day shifted to start at 00:00 and modulo 10000 days.
313
* The last origin (ie: 0) was 1995-10-10T00:00
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
* (definition by National Institute of Standards and Technology).
* @param gmt convert returned value to GMT? (default: false)
* @return truncated julian date in the current timezone / in GMT depending on the gmt parameter
*/
double Date::getTruncatedJulianDate(const bool& gmt) const {
	if(undef==true)
		throw UnknownValueException("Date object is undefined!", AT);

	if(gmt) {
		return (fmod( (gmt_julian - 0.5), 10000. ));
	} else {
		const double local_julian = GMTToLocal(gmt_julian);
		return (fmod( (local_julian - 0.5), 10000. ));
	}
}

/**
* @brief Return Unix time (or POSIX time).
* The Unix time is defined as the number of seconds since 1970-01-01T00:00 UTC (Unix Epoch).
333
* It is therefore ALWAYS in GMT.
334
335
336
337
* (defined as IEEE P1003.1 POSIX. See http://www.mail-archive.com/leapsecs@rom.usno.navy.mil/msg00109.html
* for some technical, historical and funny insight into the standardization process)
* @return Unix time in the current timezone / in GMT depending on the gmt parameter
*/
338
time_t Date::getUnixDate() const {
339
340
341
342
	if(undef==true)
		throw UnknownValueException("Date object is undefined!", AT);

	if (gmt_julian < Unix_offset)
343
		throw IOException("Dates before 1970 cannot be displayed in Unix epoch time", AT);
344

345
	return ( (time_t)floor( (gmt_julian - Unix_offset) * (24*60*60) ));
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
}

/**
* @brief Return Excel date.
* The (sick) Excel date is defined as the number of days since 1900-01-00T00:00 (no, this is NOT a typo).
* Moreover, it (wrongly) considers that 1900 was a leap year (in order to remain compatible with an old Lotus123 bug).
* This practically means that for dates after 1900-03-01, an Excel date really represents the number of days since 1900-01-01T00:00 PLUS 2.
* @param gmt convert returned value to GMT? (default: false)
* @return Excel date in the current timezone / in GMT depending on the gmt parameter
*/
double Date::getExcelDate(const bool& gmt) const {
	if(undef==true)
		throw UnknownValueException("Date object is undefined!", AT);

	if (gmt_julian < Excel_offset)
		throw IOException("Dates before 1900 cannot be converted to Excel date", AT);

	if(gmt) {
		return ( gmt_julian - Excel_offset);
	} else {
		const double local_julian = GMTToLocal(gmt_julian);
		return ( local_julian - Excel_offset);
	}
}

371
/**
372
* @brief Return Matlab date.
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
* This is the number of days since 0000-01-01T00:00:00. See http://www.mathworks.com/help/techdoc/ref/datenum.html
* @param gmt convert returned value to GMT? (default: false)
* @return Matlab date in the current timezone / in GMT depending on the gmt parameter
*/
double Date::getMatlabDate(const bool& gmt) const {
	if(undef==true)
		throw UnknownValueException("Date object is undefined!", AT);

	if(gmt) {
		return ( gmt_julian - Matlab_offset);
	} else {
		const double local_julian = GMTToLocal(gmt_julian);
		return ( local_julian - Matlab_offset);
	}
}


390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
/**
* @brief Retrieve julian date.
* This method is a candidate for deletion: it should now be obsolete.
* @param julian_out julian date (in local time zone or GMT depending on the gmt flag)
* @param gmt convert returned value to GMT? (default: false)
*/
void Date::getDate(double& julian_out, const bool& gmt) const {
	if(undef==true)
		throw UnknownValueException("Date object is undefined!", AT);

	if(gmt) {
		julian_out = gmt_julian;
	} else {
		const double local_julian = GMTToLocal(gmt_julian);
		julian_out = local_julian;
	}
}

/**
* @brief Return year.
* @param gmt convert returned value to GMT? (default: false)
* @return year
*/
int Date::getYear(const bool& gmt) const {
	if(undef==true)
		throw UnknownValueException("Date object is undefined!", AT);

417
418
419
420
421
422
423
424
	if(gmt) {
		return gmt_year;
	} else {
		const double local_julian = GMTToLocal(gmt_julian);
		int local_year, local_month, local_day, local_hour, local_minute;
		calculateValues(local_julian, local_year, local_month, local_day, local_hour, local_minute);
		return local_year;
	}
425
426
427
428
429
430
431
432
433
434
435
436
437
}

/**
* @brief Return year, month, day.
* @param year_out
* @param month_out
* @param day_out
* @param gmt convert returned value to GMT? (default: false)
*/
void Date::getDate(int& year_out, int& month_out, int& day_out, const bool& gmt) const {
	if(undef==true)
		throw UnknownValueException("Date object is undefined!", AT);

438
439
440
441
442
443
444
445
446
	if(gmt) {
		year_out = gmt_year;
		month_out = gmt_month;
		day_out = gmt_day;
	} else {
		const double local_julian = GMTToLocal(gmt_julian);
		int local_hour, local_minute;
		calculateValues(local_julian, year_out, month_out, day_out, local_hour, local_minute);
	}
447
448
449
450
451
452
453
454
455
456
457
458
459
460
}

/**
* @brief Return year, month, day.
* @param year_out
* @param month_out
* @param day_out
* @param hour_out
* @param gmt convert returned value to GMT? (default: false)
*/
void Date::getDate(int& year_out, int& month_out, int& day_out, int& hour_out, const bool& gmt) const {
	if(undef==true)
		throw UnknownValueException("Date object is undefined!", AT);

461
462
463
464
465
466
467
468
469
470
	if(gmt) {
		year_out = gmt_year;
		month_out = gmt_month;
		day_out = gmt_day;
		hour_out = gmt_hour;
	} else {
		const double local_julian = GMTToLocal(gmt_julian);
		int local_minute;
		calculateValues(local_julian, year_out, month_out, day_out, hour_out, local_minute);
	}
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
}

/**
* @brief Return year, month, day.
* @param year_out
* @param month_out
* @param day_out
* @param hour_out
* @param minute_out
* @param gmt convert returned value to GMT? (default: false)
*/
void Date::getDate(int& year_out, int& month_out, int& day_out, int& hour_out, int& minute_out, const bool& gmt) const {
	if(undef==true)
		throw UnknownValueException("Date object is undefined!", AT);

486
487
488
489
490
491
492
493
494
495
	if(gmt) {
		year_out = gmt_year;
		month_out = gmt_month;
		day_out = gmt_day;
		hour_out = gmt_hour;
		minute_out = gmt_minute;
	} else {
		const double local_julian = GMTToLocal(gmt_julian);
		calculateValues(local_julian, year_out, month_out, day_out, hour_out, minute_out);
	}
496
497
498
}

/**
499
* @brief Return the julian day for the current date.
500
* Return the day of the year index for the current Date object
501
* @param gmt convert returned value to GMT? (default: false)
502
503
* @return julian day number
*/
504
int Date::getJulianDayNumber(const bool& gmt) const {
505
506
507
	if(undef==true)
		throw UnknownValueException("Date object is undefined!", AT);

508
509
510
511
512
513
514
515
516
517
518
	if(gmt) {
		const double first_day_of_year = getJulianDayNumber(gmt_year, 1, 1);
		return (int)(gmt_julian - first_day_of_year + 1);
	} else {
		const double local_julian = GMTToLocal(gmt_julian);
		int local_year, local_month, local_day, local_hour, local_minute;
		calculateValues(local_julian, local_year, local_month, local_day, local_hour, local_minute);
		const double in_day_offset = 1./24.*((double)local_hour+1./60.*(double)local_minute) - 0.5;
		const double first_day_of_year = static_cast<double>(getJulianDayNumber(local_year, 1, 1)) + in_day_offset;
		return (int)(local_julian - first_day_of_year + 1);
	}
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
}

/**
* @brief Return true if the current year is a leap year
* @return true if the current year is a leap year
*/
bool Date::isLeapYear() const {
	if(undef==true)
		throw UnknownValueException("Date object is undefined!", AT);

	int local_year, local_month, local_day, local_hour, local_minute;
	getDate(local_year, local_month, local_day, local_hour, local_minute);

	return (isLeapYear(local_year));
}
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559

/**
 * @brief Round a julian date to a given precision
 * @param julian date to round
 * @param precision round date to the given precision, in seconds
 * @param type rounding strategy (default: CLOSEST)
 */
double Date::rnd(const double& julian, const unsigned int& precision, const RND& type)
{
	if(precision == 0)
		throw InvalidArgumentException("Can not round dates to 0 seconds precision!", AT);

	double integral;
	const double fractional = modf(julian, &integral);
	const double rnd_factor = (3600*24)/(double)precision;

	if(type==CLOSEST)
		return integral + (double)Optim::round( fractional*rnd_factor ) / rnd_factor;
	if(type==UP)
		return integral + (double)Optim::ceil( fractional*rnd_factor ) / rnd_factor;
	if(type==DOWN)
		return integral + (double)Optim::floor( fractional*rnd_factor ) / rnd_factor;

	throw UnknownValueException("Unknown rounding strategy!", AT);
	return julian;

560
561
562
563
564
565
566
}

/**
 * @brief Round date to a given precision
 * @param precision round date to the given precision, in seconds
 * @param type rounding strategy (default: CLOSEST)
 */
567
568
569
void Date::rnd(const unsigned int& precision, const RND& type) {
	if(!undef)
		gmt_julian = rnd(gmt_julian, precision, type);
570
571
}

572
const Date Date::rnd(const Date& indate, const unsigned int& precision, const RND& type) {
573
	Date tmp(indate);
574
575
576
	if(!tmp.undef)
		tmp.gmt_julian = rnd(tmp.gmt_julian, precision, type);

577
	return tmp;
578
579
580
581
582
583
584
585
586
}

// OPERATORS //HACK this will have to handle Durations
Date& Date::operator+=(const Date& indate) {
	if(undef==true || indate.isUndef()) {
		undef=true;
		return *this;
	}
	gmt_julian += indate.gmt_julian;
587
	rnd(1); //round to internal precision of 1 second
588
	calculateValues(gmt_julian, gmt_year, gmt_month, gmt_day, gmt_hour, gmt_minute);
589
590
591
592
593
594
595
596
597
	return *this;
}

Date& Date::operator-=(const Date& indate) {
	if(undef==true || indate.isUndef()) {
		undef=true;
		return *this;
	}
	gmt_julian -= indate.gmt_julian;
598
	rnd(1); //round to internal precision of 1 second
599
	calculateValues(gmt_julian, gmt_year, gmt_month, gmt_day, gmt_hour, gmt_minute);
600
601
602
603
604
605
	return *this;
}

Date& Date::operator+=(const double& indate) {
	if(undef==false) {
		gmt_julian += indate;
606
		rnd(1); //round to internal precision of 1 second
607
		calculateValues(gmt_julian, gmt_year, gmt_month, gmt_day, gmt_hour, gmt_minute);
608
609
610
611
612
613
614
	}
	return *this;
}

Date& Date::operator-=(const double& indate) {
	if(undef==false) {
		gmt_julian -= indate;
615
		rnd(1); //round to internal precision of 1 second
616
		calculateValues(gmt_julian, gmt_year, gmt_month, gmt_day, gmt_hour, gmt_minute);
617
618
619
620
621
622
623
	}
	return *this;
}

Date& Date::operator*=(const double& value) {
	if(undef==false) {
		gmt_julian *= value;
624
		rnd(1); //round to internal precision of 1 second
625
		calculateValues(gmt_julian, gmt_year, gmt_month, gmt_day, gmt_hour, gmt_minute);
626
627
628
629
630
631
632
	}
	return *this;
}

Date& Date::operator/=(const double& value) {
	if(undef==false) {
		gmt_julian /= value;
633
		rnd(1); //round to internal precision of 1 second
634
		calculateValues(gmt_julian, gmt_year, gmt_month, gmt_day, gmt_hour, gmt_minute);
635
636
637
638
639
640
641
642
	}
	return *this;
}

bool Date::operator==(const Date& indate) const {
	if(undef==true || indate.isUndef()) {
		return( undef==true && indate.isUndef() );
	}
643

644
	return IOUtils::checkEpsilonEquality(gmt_julian, indate.gmt_julian, epsilon);
645
646
647
648
649
650
651
652
653
654
655
}

bool Date::operator!=(const Date& indate) const {
	return !(*this==indate);
}

bool Date::operator<(const Date& indate) const {
	if(undef==true || indate.isUndef()) {
		throw UnknownValueException("Date object is undefined!", AT);
	}

656
657
658
659
#ifdef NEGATIVE_JULIAN
	if(*this==indate) return false;
	return (gmt_julian < indate.gmt_julian);
#else
660
	return (gmt_julian < (indate.gmt_julian-epsilon));
661
#endif
662
663
664
665
666
667
668
}

bool Date::operator<=(const Date& indate) const {
	if(undef==true || indate.isUndef()) {
		throw UnknownValueException("Date object is undefined!", AT);
	}

669
670
671
672
#ifdef NEGATIVE_JULIAN
	if(*this==indate) return true;
	return (gmt_julian <= indate.gmt_julian);
#else
673
	return (gmt_julian <= (indate.gmt_julian+epsilon));
674
#endif
675
676
677
678
679
680
681
}

bool Date::operator>(const Date& indate) const {
	if(undef==true || indate.isUndef()) {
		throw UnknownValueException("Date object is undefined!", AT);
	}

682
683
684
685
#ifdef NEGATIVE_JULIAN
	if(*this==indate) return false;
	return (gmt_julian > indate.gmt_julian);
#else
686
	return (gmt_julian > (indate.gmt_julian+epsilon));
687
#endif
688
689
690
691
692
693
694
}

bool Date::operator>=(const Date& indate) const {
	if(undef==true || indate.isUndef()) {
		throw UnknownValueException("Date object is undefined!", AT);
	}

695
696
697
698
#ifdef NEGATIVE_JULIAN
	if(*this==indate) return true;
	return (gmt_julian >= indate.gmt_julian);
#else
699
	return (gmt_julian >= (indate.gmt_julian-epsilon));
700
#endif
701
702
703
704
705
706
707
708
}

const Date Date::operator+(const Date& indate) const {
	if(undef==true || indate.isUndef()) {
		Date tmp; //create an Undef date
		return tmp;
	}

709
710
	Date tmp(gmt_julian + indate.gmt_julian, 0.);
	tmp.setTimeZone(timezone);
711
712
713
714
715
716
717
718
719
	return tmp;
}

const Date Date::operator-(const Date& indate) const {
	if(undef==true || indate.isUndef()) {
		Date tmp; //create an Undef date
		return tmp;
	}

720
721
	Date tmp(gmt_julian - indate.gmt_julian, 0.);
	tmp.setTimeZone(timezone);
722
723
724
725
726
	return tmp;
}

const Date Date::operator+(const double& indate) const {
	//remains undef if undef
727
728
	Date tmp(gmt_julian + indate, 0.);
	tmp.setTimeZone(timezone);
729
730
731
732
733
	return tmp;
}

const Date Date::operator-(const double& indate) const {
	//remains undef if undef
734
735
	Date tmp(gmt_julian - indate, 0.);
	tmp.setTimeZone(timezone);
736
737
738
739
740
	return tmp;
}

const Date Date::operator*(const double& value) const {
	//remains undef if undef
741
742
	Date tmp(gmt_julian * value, 0.);
	tmp.setTimeZone(timezone);
743
744
745
746
747
	return tmp;
}

const Date Date::operator/(const double& value) const {
	//remains undef if undef
748
749
	Date tmp(gmt_julian / value, 0.);
	tmp.setTimeZone(timezone);
750
751
752
753
754
755
756
757
758
	return tmp;
}

std::ostream& operator<<(std::ostream &os, const Date &date) {
	os << "<date>\n";
	if(date.undef==true)
		os << "Date is undefined\n";
	else {
		os << date.toString(Date::ISO) << "\n";
759
		os << "TZ=GMT" << showpos << date.timezone << noshowpos << "\t\t" << "DST=" << date.dst << "\n";
760
		os << "julian:\t\t\t" << setprecision(10) << date.getJulian() << "\t(GMT=" << date.getJulian(true) << ")\n";
761
762
		os << "ModifiedJulian:\t\t" << date.getModifiedJulianDate() << "\n";
		os << "TruncatedJulian:\t" << date.getTruncatedJulianDate() << "\n";
763
		os << "MatlabJulian:\t\t" << date.getMatlabDate() << "\n";
764
765
766
767
768
769
770
771
772
773
774
		try {
			os << "Unix:\t\t\t" << date.getUnixDate() << "\n";
		} catch (...) {}
		try {
			os << "Excel:\t\t\t" << date.getExcelDate() << "\n";
		} catch (...) {}
	}
	os << "</date>\n";
	return os;
}

775
776
777
778
779
780
781
782
783
784
785
786
787
788
/**
* @brief Parse an ISO 8601 formatted time zone specification.
* Time zones MUST be specified right after a date/time/combined representation
* according to the following formats:
*   - 'Z' like in 2013-02-13T19:43Z, meaning GMT
*   - '+01' like in 2013-02-13T20:43+01 meaning GMT+1
*   - '+0130' like in 2013-02-13T21:13+0130 meaning GMT+1.5
*   - '-0515' like in 2013-02-13T15:28-0515 meaning GMT-5.25
* See https://en.wikipedia.org/wiki/ISO_8601 for more information
* @param timezone_iso time zone string
* @return time zone/shift in hours
*/
double Date::parseTimeZone(const std::string& timezone_iso)
{
789
790
791
	if(timezone_iso.empty()) //just in case, to avoid a segfault
		return 0.;

792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
	if(timezone_iso=="Z") { //time as Z
		return 0.;
	} else if(timezone_iso[0]=='+' || timezone_iso[0]=='-') {
		const char *c_str = timezone_iso.c_str();
		const size_t str_size = timezone_iso.size();
		switch(str_size) {
			case 6: { //timezone as +01:00
				int tz_h, tz_min;
				if( sscanf(c_str, "%d:%d", &tz_h, &tz_min) == 2 ) {
					//if there is a "-", apply it to the minutes
					if(tz_h>=0.)
						return (double)tz_h + (double)tz_min/60.;
					else
						return (double)tz_h - (double)tz_min/60.;
				} else {
					return IOUtils::nodata;
				}
			}
			case 5: { //timezone as +0100
				int tz_tmp;
				if( sscanf(c_str, "%d", &tz_tmp) == 1 ) {
					const int tz_h = tz_tmp/100;
					const int tz_min = tz_tmp-100*tz_h;
					return (double)tz_h + (double)tz_min/60.;
				} else {
					return IOUtils::nodata;
				}
			}
			case 3: { //timezone as -01
				int tz_h;
				if( sscanf(c_str, "%d", &tz_h) == 1 )
					return (double)tz_h;
				else
					return IOUtils::nodata;
			}
		}
		return IOUtils::nodata;
	} else {
		return IOUtils::nodata;
	}
}

834
835
836
837
838
839
840
841
/**
* @brief Return a nicely formated string.
* @param type select the formating to apply (see the definition of Date::FORMATS)
* @param gmt convert returned value to GMT? (default: false)
* @return formatted time in a string
*/
const string Date::toString(FORMATS type, const bool& gmt) const
{//the date are displayed in LOCAL timezone (more user friendly)
842
843
	int year_out, month_out, day_out, hour_out, minute_out;
	double julian_out;
844
845
846
847

	if(undef==true)
		throw UnknownValueException("Date object is undefined!", AT);

848
849
850
851
852
853
854
855
856
857
858
	if(gmt) {
		julian_out = gmt_julian;
		year_out = gmt_year;
		month_out = gmt_month;
		day_out = gmt_day;
		hour_out = gmt_hour;
		minute_out = gmt_minute;
	} else {
		julian_out = GMTToLocal(gmt_julian);
		calculateValues(julian_out, year_out, month_out, day_out, hour_out, minute_out);
	}
859
860

	stringstream tmpstr;
861
862
863
	switch(type) {
		case(ISO_TZ):
		case(ISO):
864
			tmpstr
865
866
867
868
869
			<< setw(4) << setfill('0') << year_out << "-"
			<< setw(2) << setfill('0') << month_out << "-"
			<< setw(2) << setfill('0') << day_out << "T"
			<< setw(2) << setfill('0') << hour_out << ":"
			<< setw(2) << setfill('0') << minute_out;
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
			if(type==ISO_TZ) {
				int tz_h, tz_min;
				if(timezone>=0.) {
					tz_h = static_cast<int>(timezone);
					tz_min = static_cast<int>( (timezone - (double)tz_h)*60. + .5 ); //round to closest
					tmpstr << "+";
				} else {
					tz_h = -static_cast<int>(timezone);
					tz_min = -static_cast<int>( (timezone + (double)tz_h)*60. + .5 ); //round to closest
					tmpstr << "-";
				}
				tmpstr << setw(2) << setfill('0') << tz_h << ":"
				<< setw(2) << setfill('0') << tz_min;
			}
			break;
		case(NUM):
886
			tmpstr
887
888
889
890
891
			<< setw(4) << setfill('0') << year_out
			<< setw(2) << setfill('0') << month_out
			<< setw(2) << setfill('0') << day_out
			<< setw(2) << setfill('0') << hour_out
			<< setw(2) << setfill('0') << minute_out ;
892
893
			break;
		case(FULL):
894
			tmpstr
895
896
897
898
899
900
901
			<< setw(4) << setfill('0') << year_out << "-"
			<< setw(2) << setfill('0') << month_out << "-"
			<< setw(2) << setfill('0') << day_out << "T"
			<< setw(2) << setfill('0') << hour_out << ":"
			<< setw(2) << setfill('0') << minute_out << " ("
			<< setprecision(10) << julian_out << ") GMT"
			<< setw(2) << setfill('0') << showpos << timezone << noshowpos;
902
903
			break;
		case(DIN):
904
			tmpstr
905
906
907
908
909
			<< setw(2) << setfill('0') << day_out << "."
			<< setw(2) << setfill('0') << month_out << "."
			<< setw(4) << setfill('0') << year_out << " "
			<< setw(2) << setfill('0') << hour_out << ":"
			<< setw(2) << setfill('0') << minute_out;
910
911
912
			break;
		default:
			throw InvalidArgumentException("Wrong date conversion format requested", AT);
913
914
915
916
917
918
	}

	return tmpstr.str();
}

// PRIVATE METHODS
919
double Date::calculateJulianDate(const int& i_year, const int& i_month, const int& i_day, const int& i_hour, const int& i_minute) const
920
{
921
922
	const long julday = getJulianDayNumber(i_year, i_month, i_day);
	const double frac = (i_hour-12.)/24. + i_minute/(24.*60.); //the julian date reference is at 12:00
923
924
925
926

	return (((double)julday) + frac);
}

927
void Date::calculateValues(const double& i_julian, int& o_year, int& o_month, int& o_day, int& o_hour, int& o_minute) const
928
{ //given a julian day, calculate the year, month, day, hours and minutes
929
930
 //see Fliegel, H. F. and van Flandern, T. C. 1968. Letters to the editor: a machine algorithm for processing calendar dates. Commun. ACM 11, 10 (Oct. 1968), 657. DOI= http://doi.acm.org/10.1145/364096.364097
	//we round the given julian date to the closest minute since this is our current resolution
931
	const double tmp_julian = rnd(i_julian, 60);
932

933
934
	const long julday = Optim::floor(tmp_julian+0.5);
	long t1 = julday + 68569L;
935
936
937
938
939
940
	const long t2 = 4L * t1 / 146097L;
	t1 = t1 - ( 146097L * t2 + 3L ) / 4L;
	const long yr = 4000L * ( t1 + 1L ) / 1461001L;
	t1 = t1 - 1461L * yr / 4L + 31L;
	const long mo = 80L * t1 / 2447L;

941
	o_day = (int) ( t1 - 2447L * mo / 80L );
942
	t1 = mo / 11L;
943
944
	o_month = (int) ( mo + 2L - 12L * t1 );
	o_year = (int) ( 100L * ( t2 - 49L ) + yr + t1 );
945
946

	// Correct for BC years -> astronomical year, that is from year -1 to year 0
947
	if ( o_year <= 0 ) o_year--;
948

949
950
951
952
	double integral;
	const double frac = modf(tmp_julian+.5, &integral); //the julian date reference is at 12:00
	o_minute = Optim::round(frac*24.0*60.0) % 60;
	o_hour = Optim::round( (24.*60.*frac-(double)o_minute) / 60.0 );
953
954
}

955
956
957
958
959
bool Date::isLeapYear(const int& i_year) const {
	//Using the leap year rule: years that can be divided by 4 if they are not centuries
	//For centuries, they are leap years only if they can be divided by 400
	const bool is_leapYear = (i_year%4 == 0 && (i_year %100 != 0 || i_year%400 == 0));
	return is_leapYear;
960
961
}

962
long Date::getJulianDayNumber(const int& i_year, const int& i_month, const int& i_day) const
963
{ //given year, month, day, calculate the matching julian day
964
 //see Fliegel, H. F. and van Flandern, T. C. 1968. Letters to the editor: a machine algorithm for processing calendar dates. Commun. ACM 11, 10 (Oct. 1968), 657. DOI= http://doi.acm.org/10.1145/364096.364097
965
966
	const long lmonth = (long) i_month, lday = (long) i_day;
	long lyear = (long) i_year;
967
968

	// Correct for BC years -> astronomical year, that is from year -1 to year 0
969
	if ( lyear < 0 ) lyear++;
970
971

	const long jdn = lday - 32075L +
972
973
974
	                  1461L * ( lyear + 4800L + ( lmonth - 14L ) / 12L ) / 4L +
	                  367L * ( lmonth - 2L - ( lmonth - 14L ) / 12L * 12L ) / 12L -
	                  3L * ( ( lyear + 4900L + ( lmonth - 14L ) / 12L ) / 100L ) / 4L;
975
976
977
978
979
980

	return jdn;
}

void Date::plausibilityCheck(const int& in_year, const int& in_month, const int& in_day, const int& in_hour, const int& in_minute) const {
	if ((in_year < -4713) || (in_year >3000)
981
982
983
984
	    || (in_month < 1) || (in_month > 12)
	    || (in_day < 1) || ((in_day > daysNonLeapYear[in_month-1]) && !isLeapYear(in_year))
	    || ((in_day > daysLeapYear[in_month-1]) && isLeapYear(in_year))
	    || (in_hour < 0) || (in_hour > 24)
985
	    || (in_minute < 0) || (in_minute > 59)) {
986
		stringstream ss;
987
988
		ss << "Invalid Date requested: " << in_year << "-" << in_month;
		ss << "-" << in_day << "T" << in_hour << ":" << in_minute;
989
		throw IOException(ss.str(), AT);
990
991
992
	}

	if ((in_hour == 24) && (in_minute != 0)) {
993
		stringstream ss;
994
995
		ss << "Invalid Date requested: " << in_year << "-" << in_month;
		ss << "-" << in_day << "T" << in_hour << ":" << in_minute;
996
		throw IOException(ss.str(), AT);
997
998
999
	}
}

1000
double Date::localToGMT(const double& i_julian) const {
For faster browsing, not all history is shown. View entire blame