WSL/SLF GitLab Repository

Date.cc 45.3 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
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
52
const float Date::RFC868_offset = 2415020.5; ///< offset between julian date and RFC868 time (ref is 1900-01-01T00:00 GMT)
53
const float Date::Excel_offset = 2415018.5;  ///<offset between julian date and Excel dates (note that excel invented some days...)
54
const float Date::Matlab_offset = 1721058.5; ///<offset between julian date and Matlab dates
55

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

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

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

/**
* @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)
*/
107
108
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.),
109
           gmt_year(0), gmt_month(0), gmt_day(0), gmt_hour(0), gmt_minute(0), gmt_second(0),
110
           dst(false), undef(true)
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

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

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

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

165
166
167
/**
* @brief Set date by elements.
* All values are checked for plausibility.
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
173
* @param i_second
174
175
* @param i_timezone timezone as an offset to GMT (in hours, optional)
* @param i_dst is it DST? (default: no)
176
*/
177
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_second, const double& i_timezone, const bool& i_dst)
178
{
179
	plausibilityCheck(i_year, i_month, i_day, i_hour, i_minute, i_second); //also checks leap years
180
	setTimeZone(i_timezone, i_dst);
181
182
	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
183
		gmt_julian = rnd( calculateJulianDate(i_year, i_month, i_day, i_hour, i_minute, i_second), (unsigned)1);
184
185
	} else {
		//computing local julian date
186
		const double local_julian = calculateJulianDate(i_year, i_month, i_day, i_hour, i_minute, i_second);
187
188
		//converting local julian date to GMT julian date, rounded to internal precision of 1 second
		gmt_julian = rnd( localToGMT(local_julian), (unsigned)1);
189
	}
190
	//updating values to GMT, fixing potential 24:00 hour (ie: replaced by next day, 00:00)
191
	calculateValues(gmt_julian, gmt_year, gmt_month, gmt_day, gmt_hour, gmt_minute, gmt_second);
192
	undef = false;
193
194
}

195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
void Date::setDate(const int& year, const unsigned int& month, const unsigned int& day, const unsigned int& hour, const unsigned int& minute, const double& second, const double& in_timezone, const bool& in_dst)
{
	setDate(year, (signed)month, (signed)day, (signed)hour, (signed)minute, second, in_timezone, in_dst);
}

/**
* @brief Set date by elements.
* All values are checked for plausibility.
* @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_second
* @param i_timezone timezone as an offset to GMT (in hours, optional)
* @param i_dst is it DST? (default: no)
*/
void Date::setDate(const int& i_year, const int& i_month, const int& i_day, const int& i_hour, const int& i_minute, const int& i_second, const double& i_timezone, const bool& i_dst)
{
	setDate(i_year, i_month, i_day, i_hour, i_minute, static_cast<double>(i_second), i_timezone, i_dst);
}

void Date::setDate(const int& year, const unsigned int& month, const unsigned int& day, const unsigned int& hour, const unsigned int& minute, const unsigned int& second, const double& in_timezone, const bool& in_dst)
{
	setDate(year, (signed)month, (signed)day, (signed)hour, (signed)minute, static_cast<double>(second), in_timezone, in_dst);
}

/**
* @brief Set date by elements.
* All values are checked for plausibility.
* @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)
*/
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)
{
	setDate(i_year, i_month, i_day, i_hour, i_minute, 0., i_timezone, i_dst);
}

238
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)
239
{
240
	setDate(year, (signed)month, (signed)day, (signed)hour, (signed)minute, 0., in_timezone, in_dst);
241
242
}

243
244
245
246
247
248
249
250
/**
* @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);
251
	gmt_julian = rnd( localToGMT(julian_in), (unsigned)1); //round to internal precision of 1 second
252
	calculateValues(gmt_julian, gmt_year, gmt_month, gmt_day, gmt_hour, gmt_minute, gmt_second);
253
	undef = false;
254
255
256
257
}

/**
* @brief Set date from a Unix date.
258
259
* @param i_time unix time (ie: as number of seconds since Unix Epoch, always UTC)
* @param i_dst is it DST? (default: no)
260
*/
261
262
void Date::setDate(const time_t& i_time, const bool& i_dst) {
	setUnixDate(i_time, i_dst);
263
264
265
266
267
}

/**
* @brief Set date from a modified julian date (MJD).
* @param julian_in julian date to set
268
269
* @param i_timezone timezone as an offset to GMT (in hours, optional)
* @param i_dst is it DST? (default: no)
270
*/
271
272
273
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);
274
275
}

276
277
278
279
280
281
282
283
284
285
286
/**
* @brief Set date from an RFC868 date (time since 1900-01-01T00:00 GMT).
* @param julian_in julian date to set
* @param i_timezone timezone as an offset to GMT (in hours, optional)
* @param i_dst is it DST? (default: no)
*/
void Date::setRFC868Date(const double& julian_in, const double& i_timezone, const bool& i_dst) {
	const double tmp_julian = julian_in + RFC868_offset;
	setDate(tmp_julian, i_timezone, i_dst);
}

287
288
289
290
291
292
293
294
295
296
297
298
299
/**
* @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
300
301
* @param i_timezone timezone as an offset to GMT (in hours, optional)
* @param i_dst is it DST? (default: no)
302
*/
303
void Date::setExcelDate(const double excel_in, const double& i_timezone, const bool& i_dst) {
304
	//TODO: handle date < 1900-01-00 and date before 1900-03-01
305
	//see http://www.mathworks.com/help/toolbox/finance/x2mdate.html
306
307
	const double tmp_julian = excel_in + Excel_offset;
	setDate(tmp_julian, i_timezone, i_dst);
308
309
}

310
311
312
/**
* @brief Set date from an Matlab date.
* @param matlab_in Matlab date to set
313
314
* @param i_timezone timezone as an offset to GMT (in hours, optional)
* @param i_dst is it DST? (default: no)
315
*/
316
317
318
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);
319
320
321
}


322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
// 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
*/
355
double Date::getJulian(const bool& gmt) const {
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
	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);
	}
}

386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
/**
* @brief Return RFC868 date.
* The RFC868 date is defined as the fractional number of days since 1900-01-01T00:00 GMT
* @param gmt convert returned value to GMT? (default: false)
* @return RFC868 julian date in the current timezone / in GMT depending on the gmt parameter
*/
double Date::getRFC868Date(const bool& gmt) const {
	if(undef==true)
		throw UnknownValueException("Date object is undefined!", AT);

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

404
405
406
/**
* @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.
407
* The last origin (ie: 0) was 1995-10-10T00:00
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
* (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).
427
* It is therefore ALWAYS in GMT.
428
429
430
431
* (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
*/
432
time_t Date::getUnixDate() const {
433
434
435
436
	if(undef==true)
		throw UnknownValueException("Date object is undefined!", AT);

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

439
	return ( (time_t)floor( (gmt_julian - Unix_offset) * (24*60*60) ));
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
}

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

465
/**
466
* @brief Return Matlab date.
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
* 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);
	}
}


484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
/**
* @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);

511
512
513
514
	if(gmt) {
		return gmt_year;
	} else {
		const double local_julian = GMTToLocal(gmt_julian);
515
516
		int local_year, local_month, local_day, local_hour, local_minute, local_second;
		calculateValues(local_julian, local_year, local_month, local_day, local_hour, local_minute, local_second);
517
518
		return local_year;
	}
519
520
}

521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536

/**
* @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);
537
538
		int local_year, local_month, local_day, local_second;
		calculateValues(local_julian, local_year, local_month, local_day, hour_out, minute_out, local_second);
539
540
541
	}
}

542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
/**
* @brief Return time of the day.
* @param hour_out
* @param minute_out
* @param second_out
* @param gmt convert returned value to GMT? (default: false)
*/
void Date::getTime(int& hour_out, int& minute_out, int& second_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;
		second_out = gmt_second;
	} 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, second_out);
	}
}

564
565
566
567
568
569
570
571
572
573
574
/**
* @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);

575
576
577
578
579
580
	if(gmt) {
		year_out = gmt_year;
		month_out = gmt_month;
		day_out = gmt_day;
	} else {
		const double local_julian = GMTToLocal(gmt_julian);
581
582
		int local_hour, local_minute, local_second;
		calculateValues(local_julian, year_out, month_out, day_out, local_hour, local_minute, local_second);
583
	}
584
585
586
587
588
589
590
591
592
593
594
595
596
597
}

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

598
599
600
601
602
603
604
	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);
605
606
		int local_minute, local_second;
		calculateValues(local_julian, year_out, month_out, day_out, hour_out, local_minute, local_second);
607
	}
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
}

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

623
624
625
626
627
628
629
630
	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);
631
632
		int local_second;
		calculateValues(local_julian, year_out, month_out, day_out, hour_out, minute_out, local_second);
633
	}
634
635
}

636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
/**
* @brief Return year, month, day.
* @param year_out
* @param month_out
* @param day_out
* @param hour_out
* @param minute_out
* @param second_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, int& second_out, const bool& gmt) const {
	if(undef==true)
		throw UnknownValueException("Date object is undefined!", AT);

	if(gmt) {
		year_out = gmt_year;
		month_out = gmt_month;
		day_out = gmt_day;
		hour_out = gmt_hour;
		minute_out = gmt_minute;
		second_out = gmt_second;
	} else {
		const double local_julian = GMTToLocal(gmt_julian);
		calculateValues(local_julian, year_out, month_out, day_out, hour_out, minute_out, second_out);
	}
}

663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
/**
* @brief Return the day of the week for the current date.
* The day of the week is between 1 (Monday) and 7 (Sunday).
* @param gmt convert returned value to GMT? (default: false)
*/
unsigned short Date::getDayOfWeek(const bool& gmt) const {
//principle: start from day julian=0 that is a Monday
	if (gmt) {
		const unsigned int dow = static_cast<unsigned int>(gmt_julian+.5) % 7 + 1;
		return static_cast<unsigned short>(dow);
	} else {
		const double local_julian = GMTToLocal(gmt_julian);
		const unsigned int dow = static_cast<unsigned int>(local_julian+.5) % 7 + 1;
		return static_cast<unsigned short>(dow);
	}
}

/**
* @brief Return the ISO 8601 week number
* The week number range from 1 to 53 for a leap year. The first week is the week that contains
* the first Thursday of the year. Previous days are attributed to the last week of the previous
* year (See https://en.wikipedia.org/wiki/ISO_week_date).
* @param gmt convert returned value to GMT? (default: false)
*/
687
unsigned short Date::getISOWeekNr(int &ISO_year, const bool& gmt) const
688
{
689
	ISO_year = getYear(gmt);
690
	const double jdn = getJulianDayNumber(gmt);
691
	Date newYear(*this - jdn + 1);
692
693
694
	const unsigned short newYear_dow = newYear.getDayOfWeek(gmt);
	const int firstThursday = (7 - newYear_dow + 4) % 7 + 1; //first Thursday of the year belongs to week 1
	const int firstWeekMonday = firstThursday - 3; //this could be <0, for example if Jan 01 is a Thursday
695
696
697
698
699
700
701
	
	if (jdn>=359) { //handle the last few days before the new year that might belong to week 1
		const bool is_leapYear = isLeapYear();
		const int jdn_last = (is_leapYear)? 366 : 365;
		const unsigned char week_offset = (is_leapYear)? 1 : 0; //for leap years, dec. 31 is one dow later as jan. 1st 
		const double lastDay_dow = (newYear_dow + week_offset - 1) % 7 + 1;
		const double lastMonday = jdn_last - lastDay_dow + 1; //dow starts at 1
702
703
704
705
		if (jdn>=lastMonday && lastDay_dow<4) {
			ISO_year++;
			return 1;
		}
706
707
708
	}
	
	//these are certainly normal days, ie no special case
709
	if (jdn>=firstWeekMonday) { //at worst, we are in week 01, otherwise after...
710
		return static_cast<unsigned short>( Optim::intPart( (jdn+3-(double)firstThursday) / 7 ) + 1);
711
712
713
714
715
716
717
718
719
720
	} else {
		//handle the first few days of the new year that are before week 1
		//we are *before* the Monday of the first week. This implies that dow>4 (otherwise, the current week would be week 01)
		//so these few days belong to the last week of the previous year
		ISO_year--;
		if (newYear_dow==5) return 53; // Friday indicates a leap year
		if (newYear_dow==7) return 52; // Sunday is no leap year
		
		//Saturday depends on the year before...
		if (isLeapYear(ISO_year)) return 53;
721
722
723
724
		else return 52;
	}
}

725
726
727
728
729
730
unsigned short Date::getISOWeekNr(const bool& gmt) const
{
	int ISO_year;
	return getISOWeekNr(ISO_year, gmt);
}

731
/**
732
* @brief Return the julian day for the current date.
733
* Return the day of the year index for the current Date object
734
* @param gmt convert returned value to GMT? (default: false)
735
* @return julian day number, starting from 1
736
*/
737
int Date::getJulianDayNumber(const bool& gmt) const {
738
739
740
	if(undef==true)
		throw UnknownValueException("Date object is undefined!", AT);

741
	if(gmt) {
Mathias Bavay's avatar
Mathias Bavay committed
742
		const double first_day_of_year = static_cast<double>(getJulianDayNumber(gmt_year, 1, 1));
743
		return static_cast<int>(gmt_julian - first_day_of_year + 1.5);
744
745
	} else {
		const double local_julian = GMTToLocal(gmt_julian);
746
747
		int local_year, local_month, local_day, local_hour, local_minute, local_second;
		calculateValues(local_julian, local_year, local_month, local_day, local_hour, local_minute, local_second);
748
		return static_cast<int>(Optim::intPart(local_julian+0.5)) - static_cast<int>(getJulianDayNumber(local_year, 1, 1)) + 1;
749
	}
750
751
752
753
754
755
756
757
758
759
}

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

760
	return (isLeapYear( getYear() ));
761
}
762
763

/**
764
765
766
 * @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.
767
768
769
770
771
772
773
774
775
776
 * @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;
777
	const double fractional = modf(julian-.5, &integral);
778
779
780
	const double rnd_factor = (3600*24)/(double)precision;

	if(type==CLOSEST)
781
		return integral + (double)Optim::round( fractional*rnd_factor ) / rnd_factor + .5;
782
	if(type==UP)
783
		return integral + (double)Optim::ceil( fractional*rnd_factor ) / rnd_factor + .5;
784
	if(type==DOWN)
785
		return integral + (double)Optim::floor( fractional*rnd_factor ) / rnd_factor + .5;
786
787

	throw UnknownValueException("Unknown rounding strategy!", AT);
788
789
790
791
792
793
794
}

/**
 * @brief Round date to a given precision
 * @param precision round date to the given precision, in seconds
 * @param type rounding strategy (default: CLOSEST)
 */
795
void Date::rnd(const unsigned int& precision, const RND& type) {
796
797
798
799
	if(!undef) {
		const double rnd_julian = rnd( getJulian(false), precision, type ); //round local time
		setDate(rnd_julian, timezone, dst);
	}
800
801
}

802
const Date Date::rnd(const Date& indate, const unsigned int& precision, const RND& type) {
803
	Date tmp(indate);
804
805
806
807
	if(!tmp.undef) {
		const double rnd_julian = rnd( tmp.getJulian(false), precision, type ); //round local time
		tmp.setDate(rnd_julian, tmp.getTimeZone(), tmp.getDST());
	}
808

809
	return tmp;
810
811
812
813
814
815
816
817
818
}

// 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;
819
	rnd(1); //round to internal precision of 1 second
820
	calculateValues(gmt_julian, gmt_year, gmt_month, gmt_day, gmt_hour, gmt_minute, gmt_second);
821
822
823
824
825
826
827
828
829
	return *this;
}

Date& Date::operator-=(const Date& indate) {
	if(undef==true || indate.isUndef()) {
		undef=true;
		return *this;
	}
	gmt_julian -= indate.gmt_julian;
830
	rnd(1); //round to internal precision of 1 second
831
	calculateValues(gmt_julian, gmt_year, gmt_month, gmt_day, gmt_hour, gmt_minute, gmt_second);
832
833
834
835
836
837
	return *this;
}

Date& Date::operator+=(const double& indate) {
	if(undef==false) {
		gmt_julian += indate;
838
		rnd(1); //round to internal precision of 1 second
839
		calculateValues(gmt_julian, gmt_year, gmt_month, gmt_day, gmt_hour, gmt_minute, gmt_second);
840
841
842
843
844
845
846
	}
	return *this;
}

Date& Date::operator-=(const double& indate) {
	if(undef==false) {
		gmt_julian -= indate;
847
		rnd(1); //round to internal precision of 1 second
848
		calculateValues(gmt_julian, gmt_year, gmt_month, gmt_day, gmt_hour, gmt_minute, gmt_second);
849
850
851
852
853
854
855
	}
	return *this;
}

Date& Date::operator*=(const double& value) {
	if(undef==false) {
		gmt_julian *= value;
856
		rnd(1); //round to internal precision of 1 second
857
		calculateValues(gmt_julian, gmt_year, gmt_month, gmt_day, gmt_hour, gmt_minute, gmt_second);
858
859
860
861
862
863
864
	}
	return *this;
}

Date& Date::operator/=(const double& value) {
	if(undef==false) {
		gmt_julian /= value;
865
		rnd(1); //round to internal precision of 1 second
866
		calculateValues(gmt_julian, gmt_year, gmt_month, gmt_day, gmt_hour, gmt_minute, gmt_second);
867
868
869
870
871
872
873
874
	}
	return *this;
}

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

876
	return IOUtils::checkEpsilonEquality(gmt_julian, indate.gmt_julian, epsilon);
877
878
879
880
881
882
883
884
885
886
887
}

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

888
889
890
891
#ifdef NEGATIVE_JULIAN
	if(*this==indate) return false;
	return (gmt_julian < indate.gmt_julian);
#else
892
	return (gmt_julian < (indate.gmt_julian-epsilon));
893
#endif
894
895
896
897
898
899
900
}

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

901
902
903
904
#ifdef NEGATIVE_JULIAN
	if(*this==indate) return true;
	return (gmt_julian <= indate.gmt_julian);
#else
905
	return (gmt_julian <= (indate.gmt_julian+epsilon));
906
#endif
907
908
909
910
911
912
913
}

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

914
915
916
917
#ifdef NEGATIVE_JULIAN
	if(*this==indate) return false;
	return (gmt_julian > indate.gmt_julian);
#else
918
	return (gmt_julian > (indate.gmt_julian+epsilon));
919
#endif
920
921
922
923
924
925
926
}

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

927
928
929
930
#ifdef NEGATIVE_JULIAN
	if(*this==indate) return true;
	return (gmt_julian >= indate.gmt_julian);
#else
931
	return (gmt_julian >= (indate.gmt_julian-epsilon));
932
#endif
933
934
935
936
937
938
939
940
}

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

941
942
	Date tmp(gmt_julian + indate.gmt_julian, 0.);
	tmp.setTimeZone(timezone);
943
944
945
946
947
948
949
950
951
	return tmp;
}

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

952
953
	Date tmp(gmt_julian - indate.gmt_julian, 0.);
	tmp.setTimeZone(timezone);
954
955
956
957
958
	return tmp;
}

const Date Date::operator+(const double& indate) const {
	//remains undef if undef
959
960
	Date tmp(gmt_julian + indate, 0.);
	tmp.setTimeZone(timezone);
961
962
963
964
965
	return tmp;
}

const Date Date::operator-(const double& indate) const {
	//remains undef if undef
966
967
	Date tmp(gmt_julian - indate, 0.);
	tmp.setTimeZone(timezone);
968
969
970
971
972
	return tmp;
}

const Date Date::operator*(const double& value) const {
	//remains undef if undef
973
974
	Date tmp(gmt_julian * value, 0.);
	tmp.setTimeZone(timezone);
975
976
977
978
979
	return tmp;
}

const Date Date::operator/(const double& value) const {
	//remains undef if undef
980
981
	Date tmp(gmt_julian / value, 0.);
	tmp.setTimeZone(timezone);
982
983
984
	return tmp;
}

985
986
987
988
989
990
991
/**
* @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
992
*   - '+01:30' like in 2013-02-13T21:13+01:30 meaning GMT+1.5
993
994
995
996
997
998
999
*   - '-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)
{
1000
	if(timezone_iso.empty()) //just in case, to avoid a segfault
For faster browsing, not all history is shown. View entire blame