WSL/SLF GitLab Repository

Date.cc 36.7 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/***********************************************************************************/
/*  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/>.
*/
18

19
20
#include <cmath>
#include <cstdio>
21
22
23
#include <iomanip>
#include <iostream>
#include <ctime>
24

25
#include <meteoio/dataClasses/Date.h>
26
#include <meteoio/IOUtils.h>
27
#include <meteoio/MathOptim.h>
28
29
30
31
32

using namespace std;

namespace mio {

33
#ifdef __MINGW32__
34
35
36
37
38
39
40
41
42
43
44
45
46
	//some version of MINGW have a buggy 64 bits implementation of difftime
	//this is Mingw bug 2152
	static __inline__
	double difftime( time_t __t1, time_t __t0 ) {
		if (sizeof(time_t)==8) { //time_t is 64 bits
			return (double)((long double)(__t1) - (long double)(__t0));
		} else {
			//return (double)((__int64)(__t1) - (__int64)(__t0));
			return (double)__t1 - (double)__t0;
		}
	}
#endif

47
48
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};
49
50
51
52
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...)
53
const float Date::Matlab_offset = 1721058.5; ///<offset between julian date and Matlab dates
54

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

58
59
60
61
// CONSTUCTORS
/**
* @brief Default constructor: timezone is set to GMT without DST, julian date is set to 0 (meaning -4713-01-01T12:00)
*/
62
Date::Date() : timezone(0.), gmt_julian(0.),
63
               gmt_year(0), gmt_month(0), gmt_day(0), gmt_hour(0), gmt_minute(0),
64
65
               dst(false), undef(true)
{
66
67
68
69
70
71
72
73
}

/**
* @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)
*/
74
75
Date::Date(const double& julian_in, const double& in_timezone, const bool& in_dst)
         : timezone(0.), gmt_julian(0.),
76
           gmt_year(0), gmt_month(0), gmt_day(0), gmt_hour(0), gmt_minute(0),
77
           dst(false), undef(true)
78
{
79
80
81
82
83
84
85
86
	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)
*/
87
88
Date::Date(const time_t& in_time, const bool& in_dst)
         : timezone(in_dst), gmt_julian(0.),
89
           gmt_year(0), gmt_month(0), gmt_day(0), gmt_hour(0), gmt_minute(0),
90
91
           dst(false), undef(true)
{
92
	setDate(in_time, in_dst);
93
94
95
96
97
98
99
100
101
102
103
104
105
}

/**
* @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)
*/
106
107
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.),
108
           gmt_year(0), gmt_month(0), gmt_day(0), gmt_hour(0), gmt_minute(0),
109
           dst(false), undef(true)
110
111
112
113
114
115
116
117
118
{
	setDate(in_year, in_month, in_day, in_hour, in_minute, in_timezone, in_dst);
}

// SETTERS
void Date::setUndef(const bool& flag) {
	undef = flag;
}
/**
119
* @brief Set internal gmt time from system time as well as system time zone.
120
121
*/
void Date::setFromSys() {
122
123
	const time_t curr = time(NULL);// current time in UTC
	tm local = *gmtime(&curr);// current time in UTC, stored as tm
124
	const time_t utc = mktime(&local);// convert GMT tm to GMT time_t
125
#ifndef __MINGW32__
126
	double tz = - difftime(utc,curr)/3600.; //time zone shift (sign so that if curr>utc, tz>0)
127
128
129
#else //workaround for Mingw bug 2152
	double tz = - mio::difftime(utc,curr)/3600.; //time zone shift (sign so that if curr>utc, tz>0)
#endif
130
131
	setDate( curr ); //Unix time_t setter, always in gmt
	setTimeZone( tz );
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
}

/**
* @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;
}

149
150
151
152
153
154
155
156
157
158
/**
* @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 {
159
		setDate(in_date.getJulian(), in_date.getTimeZone(), in_date.getDST());
160
161
162
	}
}

163
164
165
/**
* @brief Set date by elements.
* All values are checked for plausibility.
166
167
168
169
170
171
172
* @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)
173
*/
174
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)
175
{
176
	plausibilityCheck(i_year, i_month, i_day, i_hour, i_minute); //also checks leap years
177
	setTimeZone(i_timezone, i_dst);
178
179
180
	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);
181
182
	} else {
		//computing local julian date
183
		const double local_julian = calculateJulianDate(i_year, i_month, i_day, i_hour, i_minute);
184
185
		//converting local julian date to GMT julian date, rounded to internal precision of 1 second
		gmt_julian = rnd( localToGMT(local_julian), (unsigned)1);
186
	}
187
188
	//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);
189
	undef = false;
190
191
}

192
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)
193
194
195
196
{
	setDate(year, (signed)month, (signed)day, (signed)hour, (signed)minute, in_timezone, in_dst);
}

197
198
199
200
201
202
203
204
/**
* @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);
205
	gmt_julian = rnd( localToGMT(julian_in), (unsigned)1); //round to internal precision of 1 second
206
	calculateValues(gmt_julian, gmt_year, gmt_month, gmt_day, gmt_hour, gmt_minute);
207
	undef = false;
208
209
210
211
}

/**
* @brief Set date from a Unix date.
212
213
* @param i_time unix time (ie: as number of seconds since Unix Epoch, always UTC)
* @param i_dst is it DST? (default: no)
214
*/
215
216
void Date::setDate(const time_t& i_time, const bool& i_dst) {
	setUnixDate(i_time, i_dst);
217
218
219
220
221
}

/**
* @brief Set date from a modified julian date (MJD).
* @param julian_in julian date to set
222
223
* @param i_timezone timezone as an offset to GMT (in hours, optional)
* @param i_dst is it DST? (default: no)
224
*/
225
226
227
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);
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
}

/**
* @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
243
244
* @param i_timezone timezone as an offset to GMT (in hours, optional)
* @param i_dst is it DST? (default: no)
245
*/
246
void Date::setExcelDate(const double excel_in, const double& i_timezone, const bool& i_dst) {
247
	//TODO: handle date < 1900-01-00 and date before 1900-03-01
248
	//see http://www.mathworks.com/help/toolbox/finance/x2mdate.html
249
250
	const double tmp_julian = excel_in + Excel_offset;
	setDate(tmp_julian, i_timezone, i_dst);
251
252
}

253
254
255
/**
* @brief Set date from an Matlab date.
* @param matlab_in Matlab date to set
256
257
* @param i_timezone timezone as an offset to GMT (in hours, optional)
* @param i_dst is it DST? (default: no)
258
*/
259
260
261
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);
262
263
264
}


265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
// 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
*/
298
double Date::getJulian(const bool& gmt) const {
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
	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.
332
* The last origin (ie: 0) was 1995-10-10T00:00
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
* (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).
352
* It is therefore ALWAYS in GMT.
353
354
355
356
* (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
*/
357
time_t Date::getUnixDate() const {
358
359
360
361
	if(undef==true)
		throw UnknownValueException("Date object is undefined!", AT);

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

364
	return ( (time_t)floor( (gmt_julian - Unix_offset) * (24*60*60) ));
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
}

/**
* @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);
	}
}

390
/**
391
* @brief Return Matlab date.
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
* 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);
	}
}


409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
/**
* @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);

436
437
438
439
440
441
442
443
	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;
	}
444
445
}

446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466

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

	if(gmt) {
		hour_out = gmt_hour;
		minute_out = gmt_minute;
	} else {
		const double local_julian = GMTToLocal(gmt_julian);
		int local_year, local_month, local_day;
		calculateValues(local_julian, local_year, local_month, local_day, hour_out, minute_out);
	}
}

467
468
469
470
471
472
473
474
475
476
477
/**
* @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);

478
479
480
481
482
483
484
485
486
	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);
	}
487
488
489
490
491
492
493
494
495
496
497
498
499
500
}

/**
* @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);

501
502
503
504
505
506
507
508
509
510
	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);
	}
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
}

/**
* @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);

526
527
528
529
530
531
532
533
534
535
	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);
	}
536
537
538
}

/**
539
* @brief Return the julian day for the current date.
540
* Return the day of the year index for the current Date object
541
* @param gmt convert returned value to GMT? (default: false)
542
543
* @return julian day number
*/
544
int Date::getJulianDayNumber(const bool& gmt) const {
545
546
547
	if(undef==true)
		throw UnknownValueException("Date object is undefined!", AT);

548
	if(gmt) {
Mathias Bavay's avatar
Mathias Bavay committed
549
550
		const double first_day_of_year = static_cast<double>(getJulianDayNumber(gmt_year, 1, 1));
		return static_cast<int>(gmt_julian - first_day_of_year + 1);
551
552
553
554
	} 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);
555
		return static_cast<int>(Optim::intPart(local_julian)) - static_cast<int>(getJulianDayNumber(local_year, 1, 1)) + 1;
556
	}
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
}

/**
* @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));
}
572
573

/**
574
575
576
 * @brief Round a julian date to a given precision.
 * If you want to round a local date, do NOT provide it as gmt julian but as local julian,
 * otherwise things like rounding to the next day would be shifted by TimeZone.
577
578
579
580
581
582
583
584
585
586
 * @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;
587
	const double fractional = modf(julian-.5, &integral);
588
589
590
	const double rnd_factor = (3600*24)/(double)precision;

	if(type==CLOSEST)
591
		return integral + (double)Optim::round( fractional*rnd_factor ) / rnd_factor + .5;
592
	if(type==UP)
593
		return integral + (double)Optim::ceil( fractional*rnd_factor ) / rnd_factor + .5;
594
	if(type==DOWN)
595
		return integral + (double)Optim::floor( fractional*rnd_factor ) / rnd_factor + .5;
596
597

	throw UnknownValueException("Unknown rounding strategy!", AT);
598
599
600
601
602
603
604
}

/**
 * @brief Round date to a given precision
 * @param precision round date to the given precision, in seconds
 * @param type rounding strategy (default: CLOSEST)
 */
605
void Date::rnd(const unsigned int& precision, const RND& type) {
606
607
608
609
	if(!undef) {
		const double rnd_julian = rnd( getJulian(false), precision, type ); //round local time
		setDate(rnd_julian, timezone, dst);
	}
610
611
}

612
const Date Date::rnd(const Date& indate, const unsigned int& precision, const RND& type) {
613
	Date tmp(indate);
614
615
616
617
	if(!tmp.undef) {
		const double rnd_julian = rnd( tmp.getJulian(false), precision, type ); //round local time
		tmp.setDate(rnd_julian, tmp.getTimeZone(), tmp.getDST());
	}
618

619
	return tmp;
620
621
622
623
624
625
626
627
628
}

// 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;
629
	rnd(1); //round to internal precision of 1 second
630
	calculateValues(gmt_julian, gmt_year, gmt_month, gmt_day, gmt_hour, gmt_minute);
631
632
633
634
635
636
637
638
639
	return *this;
}

Date& Date::operator-=(const Date& indate) {
	if(undef==true || indate.isUndef()) {
		undef=true;
		return *this;
	}
	gmt_julian -= indate.gmt_julian;
640
	rnd(1); //round to internal precision of 1 second
641
	calculateValues(gmt_julian, gmt_year, gmt_month, gmt_day, gmt_hour, gmt_minute);
642
643
644
645
646
647
	return *this;
}

Date& Date::operator+=(const double& indate) {
	if(undef==false) {
		gmt_julian += indate;
648
		rnd(1); //round to internal precision of 1 second
649
		calculateValues(gmt_julian, gmt_year, gmt_month, gmt_day, gmt_hour, gmt_minute);
650
651
652
653
654
655
656
	}
	return *this;
}

Date& Date::operator-=(const double& indate) {
	if(undef==false) {
		gmt_julian -= indate;
657
		rnd(1); //round to internal precision of 1 second
658
		calculateValues(gmt_julian, gmt_year, gmt_month, gmt_day, gmt_hour, gmt_minute);
659
660
661
662
663
664
665
	}
	return *this;
}

Date& Date::operator*=(const double& value) {
	if(undef==false) {
		gmt_julian *= value;
666
		rnd(1); //round to internal precision of 1 second
667
		calculateValues(gmt_julian, gmt_year, gmt_month, gmt_day, gmt_hour, gmt_minute);
668
669
670
671
672
673
674
	}
	return *this;
}

Date& Date::operator/=(const double& value) {
	if(undef==false) {
		gmt_julian /= value;
675
		rnd(1); //round to internal precision of 1 second
676
		calculateValues(gmt_julian, gmt_year, gmt_month, gmt_day, gmt_hour, gmt_minute);
677
678
679
680
681
682
683
684
	}
	return *this;
}

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

686
	return IOUtils::checkEpsilonEquality(gmt_julian, indate.gmt_julian, epsilon);
687
688
689
690
691
692
693
694
695
696
697
}

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);
	}

698
699
700
701
#ifdef NEGATIVE_JULIAN
	if(*this==indate) return false;
	return (gmt_julian < indate.gmt_julian);
#else
702
	return (gmt_julian < (indate.gmt_julian-epsilon));
703
#endif
704
705
706
707
708
709
710
}

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

711
712
713
714
#ifdef NEGATIVE_JULIAN
	if(*this==indate) return true;
	return (gmt_julian <= indate.gmt_julian);
#else
715
	return (gmt_julian <= (indate.gmt_julian+epsilon));
716
#endif
717
718
719
720
721
722
723
}

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

724
725
726
727
#ifdef NEGATIVE_JULIAN
	if(*this==indate) return false;
	return (gmt_julian > indate.gmt_julian);
#else
728
	return (gmt_julian > (indate.gmt_julian+epsilon));
729
#endif
730
731
732
733
734
735
736
}

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

737
738
739
740
#ifdef NEGATIVE_JULIAN
	if(*this==indate) return true;
	return (gmt_julian >= indate.gmt_julian);
#else
741
	return (gmt_julian >= (indate.gmt_julian-epsilon));
742
#endif
743
744
745
746
747
748
749
750
}

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

751
752
	Date tmp(gmt_julian + indate.gmt_julian, 0.);
	tmp.setTimeZone(timezone);
753
754
755
756
757
758
759
760
761
	return tmp;
}

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

762
763
	Date tmp(gmt_julian - indate.gmt_julian, 0.);
	tmp.setTimeZone(timezone);
764
765
766
767
768
	return tmp;
}

const Date Date::operator+(const double& indate) const {
	//remains undef if undef
769
770
	Date tmp(gmt_julian + indate, 0.);
	tmp.setTimeZone(timezone);
771
772
773
774
775
	return tmp;
}

const Date Date::operator-(const double& indate) const {
	//remains undef if undef
776
777
	Date tmp(gmt_julian - indate, 0.);
	tmp.setTimeZone(timezone);
778
779
780
781
782
	return tmp;
}

const Date Date::operator*(const double& value) const {
	//remains undef if undef
783
784
	Date tmp(gmt_julian * value, 0.);
	tmp.setTimeZone(timezone);
785
786
787
788
789
	return tmp;
}

const Date Date::operator/(const double& value) const {
	//remains undef if undef
790
791
	Date tmp(gmt_julian / value, 0.);
	tmp.setTimeZone(timezone);
792
793
794
	return tmp;
}

795
796
797
798
799
800
801
/**
* @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
802
*   - '+01:30' like in 2013-02-13T21:13+01:30 meaning GMT+1.5
803
804
805
806
807
808
809
*   - '-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)
{
810
811
812
	if(timezone_iso.empty()) //just in case, to avoid a segfault
		return 0.;

813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
	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;
	}
}

855
856
857
858
859
/**
* @brief Nicely format an hour given as fractional day into a human readable hour.
* @param fractional fractional day (ie: fractional part of a julian date)
* @return string containing a human readable time
*/
860
std::string Date::printFractionalDay(const double& fractional) {
861
862
863
864
865
866
867
868
869
870
871
872
873
	const double hours=floor(fractional*24.);
	const double minutes=floor((fractional*24.-hours)*60.);
	const double seconds=fractional*24.*3600.-hours*3600.-minutes*60.;

	std::ostringstream tmp;
	tmp << std::fixed << std::setfill('0') << std::setprecision(0);
	tmp << std::setw(2) << hours << ":";
	tmp << std::setw(2) << minutes << ":";
	tmp << std::setw(2) << seconds;

	return tmp.str();
}

874
875
876
877
878
879
880
881
/**
* @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)
882
883
	int year_out, month_out, day_out, hour_out, minute_out;
	double julian_out;
884
885
886
887

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

888
889
890
891
892
893
894
895
896
897
898
	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);
	}
899

900
	ostringstream tmpstr;
901
902
903
	switch(type) {
		case(ISO_TZ):
		case(ISO):
904
			tmpstr
905
906
907
908
909
			<< 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;
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
			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):
926
			tmpstr
927
928
929
930
931
			<< 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 ;
932
933
			break;
		case(FULL):
934
			tmpstr
935
936
937
938
939
940
941
			<< 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;
942
943
			break;
		case(DIN):
944
			tmpstr
945
946
947
948
949
			<< 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;
950
951
952
			break;
		default:
			throw InvalidArgumentException("Wrong date conversion format requested", AT);
953
954
955
956
957
	}

	return tmpstr.str();
}

958
const std::string Date::toString() const {
959
	std::ostringstream os;
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
	os << "<date>\n";
	if(undef==true)
		os << "Date is undefined\n";
	else {
		os << toString(Date::ISO) << "\n";
		os << "TZ=GMT" << showpos << timezone << noshowpos << "\t\t" << "DST=" << dst << "\n";
		os << "julian:\t\t\t" << setprecision(10) << getJulian() << "\t(GMT=" << getJulian(true) << ")\n";
		os << "ModifiedJulian:\t\t" << getModifiedJulianDate() << "\n";
		os << "TruncatedJulian:\t" << getTruncatedJulianDate() << "\n";
		os << "MatlabJulian:\t\t" << getMatlabDate() << "\n";
		try {
			os << "Unix:\t\t\t" << getUnixDate() << "\n";
		} catch (...) {}
		try {
			os << "Excel:\t\t\t" << getExcelDate() << "\n";
		} catch (...) {}
	}
	os << "</date>\n";
	return os.str();
}

std::iostream& operator<<(std::iostream& os, const Date& date) {
	os.write(reinterpret_cast<const char*>(&date.timezone), sizeof(date.timezone));
	os.write(reinterpret_cast<const char*>(&date.gmt_julian), sizeof(date.gmt_julian));

	os.write(reinterpret_cast<const char*>(&date.gmt_year), sizeof(date.gmt_year));
	os.write(reinterpret_cast<const char*>(&date.gmt_month), sizeof(date.gmt_month));
	os.write(reinterpret_cast<const char*>(&date.gmt_day), sizeof(date.gmt_day));
	os.write(reinterpret_cast<const char*>(&date.gmt_hour), sizeof(date.gmt_hour));
	os.write(reinterpret_cast<const char*>(&date.gmt_minute), sizeof(date.gmt_minute));

	os.write(reinterpret_cast<const char*>(&date.dst), sizeof(date.dst));
	os.write(reinterpret_cast<const char*>(&date.undef), sizeof(date.undef));
	return os;
}

std::iostream& operator>>(std::iostream& is, Date& date) {
	is.read(reinterpret_cast<char*>(&date.timezone), sizeof(date.timezone));
	is.read(reinterpret_cast<char*>(&date.gmt_julian), sizeof(date.gmt_julian));

	is.read(reinterpret_cast<char*>(&date.gmt_year), sizeof(date.gmt_year));
For faster browsing, not all history is shown. View entire blame