/***********************************************************************************/
/* 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 .
*/
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
namespace mio {
#ifdef __MINGW32__
//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
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};
const double Date::DST_shift = 1.0; //in hours
const float Date::MJD_offset = 2400000.5; ///utc, tz>0)
#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
setDate( curr ); //Unix time_t setter, always in gmt
setTimeZone( tz );
}
/**
* @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;
}
/**
* @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 {
setDate(in_date.getJulian(), in_date.getTimeZone(), in_date.getDST());
}
}
/**
* @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 double& i_second, const double& i_timezone, const bool& i_dst)
{
plausibilityCheck(i_year, i_month, i_day, i_hour, i_minute, i_second); //also checks leap years
setTimeZone(i_timezone, i_dst);
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, i_second), (unsigned)1);
} else {
//computing local julian date
const double local_julian = calculateJulianDate(i_year, i_month, i_day, i_hour, i_minute, i_second);
//converting local julian date to GMT julian date, rounded to internal precision of 1 second
gmt_julian = rnd( localToGMT(local_julian), (unsigned)1);
}
//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, gmt_second);
undef = false;
}
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(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(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);
}
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)
{
setDate(year, (signed)month, (signed)day, (signed)hour, (signed)minute, 0., in_timezone, in_dst);
}
/**
* @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);
gmt_julian = rnd( localToGMT(julian_in), (unsigned)1); //round to internal precision of 1 second
calculateValues(gmt_julian, gmt_year, gmt_month, gmt_day, gmt_hour, gmt_minute, gmt_second);
undef = false;
}
/**
* @brief Set date from a Unix date.
* @param i_time unix time (ie: as number of seconds since Unix Epoch, always UTC)
* @param i_dst is it DST? (default: no)
*/
void Date::setDate(const time_t& i_time, const bool& i_dst) {
setUnixDate(i_time, i_dst);
}
/**
* @brief Set date from a modified julian date (MJD).
* @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::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);
}
/**
* @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);
}
/**
* @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
* @param i_timezone timezone as an offset to GMT (in hours, optional)
* @param i_dst is it DST? (default: no)
*/
void Date::setExcelDate(const double excel_in, const double& i_timezone, const bool& i_dst) {
//TODO: handle date < 1900-01-00 and date before 1900-03-01
//see http://www.mathworks.com/help/toolbox/finance/x2mdate.html
const double tmp_julian = excel_in + Excel_offset;
setDate(tmp_julian, i_timezone, i_dst);
}
/**
* @brief Set date from an Matlab date.
* @param matlab_in Matlab 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::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);
}
// 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
*/
double Date::getJulian(const bool& gmt) const {
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 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);
}
}
/**
* @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.
* The last origin (ie: 0) was 1995-10-10T00:00
* (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).
* It is therefore ALWAYS in GMT.
* (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
*/
time_t Date::getUnixDate() const {
if(undef==true)
throw UnknownValueException("Date object is undefined!", AT);
if (gmt_julian < Unix_offset)
throw IOException("Dates before 1970 cannot be displayed in Unix epoch time", AT);
return ( (time_t)floor( (gmt_julian - Unix_offset) * (24*60*60) ));
}
/**
* @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);
}
}
/**
* @brief Return Matlab date.
* 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);
}
}
/**
* @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);
if(gmt) {
return gmt_year;
} else {
const double local_julian = GMTToLocal(gmt_julian);
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);
return local_year;
}
}
/**
* @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, local_second;
calculateValues(local_julian, local_year, local_month, local_day, hour_out, minute_out, local_second);
}
}
/**
* @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);
}
}
/**
* @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);
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, local_second;
calculateValues(local_julian, year_out, month_out, day_out, local_hour, local_minute, local_second);
}
}
/**
* @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);
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, local_second;
calculateValues(local_julian, year_out, month_out, day_out, hour_out, local_minute, local_second);
}
}
/**
* @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);
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);
int local_second;
calculateValues(local_julian, year_out, month_out, day_out, hour_out, minute_out, local_second);
}
}
/**
* @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);
}
}
/**
* @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(gmt_julian+.5) % 7 + 1;
return static_cast(dow);
} else {
const double local_julian = GMTToLocal(gmt_julian);
const unsigned int dow = static_cast(local_julian+.5) % 7 + 1;
return static_cast(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)
*/
unsigned short Date::getISOWeekNr(int &ISO_year, const bool& gmt) const
{
ISO_year = getYear(gmt);
const double jdn = getJulianDayNumber(gmt);
Date newYear(*this - jdn + 1);
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
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
if (jdn>=lastMonday && lastDay_dow<4) {
ISO_year++;
return 1;
}
}
//these are certainly normal days, ie no special case
if (jdn>=firstWeekMonday) { //at worst, we are in week 01, otherwise after...
return static_cast( Optim::intPart( (jdn+3-(double)firstThursday) / 7 ) + 1);
} 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;
else return 52;
}
}
unsigned short Date::getISOWeekNr(const bool& gmt) const
{
int ISO_year;
return getISOWeekNr(ISO_year, gmt);
}
/**
* @brief Return the julian day for the current date.
* Return the day of the year index for the current Date object
* @param gmt convert returned value to GMT? (default: false)
* @return julian day number, starting from 1
*/
int Date::getJulianDayNumber(const bool& gmt) const {
if(undef==true)
throw UnknownValueException("Date object is undefined!", AT);
if(gmt) {
const double first_day_of_year = static_cast(getJulianDayNumber(gmt_year, 1, 1));
return static_cast(gmt_julian - first_day_of_year + 1.5);
} else {
const double local_julian = GMTToLocal(gmt_julian);
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);
return static_cast(Optim::intPart(local_julian+0.5)) - static_cast(getJulianDayNumber(local_year, 1, 1)) + 1;
}
}
/**
* @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);
return (isLeapYear( getYear() ));
}
/**
* @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.
* @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-.5, &integral);
const double rnd_factor = (3600*24)/(double)precision;
if(type==CLOSEST)
return integral + (double)Optim::round( fractional*rnd_factor ) / rnd_factor + .5;
if(type==UP)
return integral + (double)Optim::ceil( fractional*rnd_factor ) / rnd_factor + .5;
if(type==DOWN)
return integral + (double)Optim::floor( fractional*rnd_factor ) / rnd_factor + .5;
throw UnknownValueException("Unknown rounding strategy!", AT);
}
/**
* @brief Round date to a given precision
* @param precision round date to the given precision, in seconds
* @param type rounding strategy (default: CLOSEST)
*/
void Date::rnd(const unsigned int& precision, const RND& type) {
if(!undef) {
const double rnd_julian = rnd( getJulian(false), precision, type ); //round local time
setDate(rnd_julian, timezone, dst);
}
}
const Date Date::rnd(const Date& indate, const unsigned int& precision, const RND& type) {
Date tmp(indate);
if(!tmp.undef) {
const double rnd_julian = rnd( tmp.getJulian(false), precision, type ); //round local time
tmp.setDate(rnd_julian, tmp.getTimeZone(), tmp.getDST());
}
return tmp;
}
// 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;
rnd(1); //round to internal precision of 1 second
calculateValues(gmt_julian, gmt_year, gmt_month, gmt_day, gmt_hour, gmt_minute, gmt_second);
return *this;
}
Date& Date::operator-=(const Date& indate) {
if(undef==true || indate.isUndef()) {
undef=true;
return *this;
}
gmt_julian -= indate.gmt_julian;
rnd(1); //round to internal precision of 1 second
calculateValues(gmt_julian, gmt_year, gmt_month, gmt_day, gmt_hour, gmt_minute, gmt_second);
return *this;
}
Date& Date::operator+=(const double& indate) {
if(undef==false) {
gmt_julian += indate;
rnd(1); //round to internal precision of 1 second
calculateValues(gmt_julian, gmt_year, gmt_month, gmt_day, gmt_hour, gmt_minute, gmt_second);
}
return *this;
}
Date& Date::operator-=(const double& indate) {
if(undef==false) {
gmt_julian -= indate;
rnd(1); //round to internal precision of 1 second
calculateValues(gmt_julian, gmt_year, gmt_month, gmt_day, gmt_hour, gmt_minute, gmt_second);
}
return *this;
}
Date& Date::operator*=(const double& value) {
if(undef==false) {
gmt_julian *= value;
rnd(1); //round to internal precision of 1 second
calculateValues(gmt_julian, gmt_year, gmt_month, gmt_day, gmt_hour, gmt_minute, gmt_second);
}
return *this;
}
Date& Date::operator/=(const double& value) {
if(undef==false) {
gmt_julian /= value;
rnd(1); //round to internal precision of 1 second
calculateValues(gmt_julian, gmt_year, gmt_month, gmt_day, gmt_hour, gmt_minute, gmt_second);
}
return *this;
}
bool Date::operator==(const Date& indate) const {
if(undef==true || indate.isUndef()) {
return( undef==true && indate.isUndef() );
}
return IOUtils::checkEpsilonEquality(gmt_julian, indate.gmt_julian, epsilon);
}
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);
}
#ifdef NEGATIVE_JULIAN
if(*this==indate) return false;
return (gmt_julian < indate.gmt_julian);
#else
return (gmt_julian < (indate.gmt_julian-epsilon));
#endif
}
bool Date::operator<=(const Date& indate) const {
if(undef==true || indate.isUndef()) {
throw UnknownValueException("Date object is undefined!", AT);
}
#ifdef NEGATIVE_JULIAN
if(*this==indate) return true;
return (gmt_julian <= indate.gmt_julian);
#else
return (gmt_julian <= (indate.gmt_julian+epsilon));
#endif
}
bool Date::operator>(const Date& indate) const {
if(undef==true || indate.isUndef()) {
throw UnknownValueException("Date object is undefined!", AT);
}
#ifdef NEGATIVE_JULIAN
if(*this==indate) return false;
return (gmt_julian > indate.gmt_julian);
#else
return (gmt_julian > (indate.gmt_julian+epsilon));
#endif
}
bool Date::operator>=(const Date& indate) const {
if(undef==true || indate.isUndef()) {
throw UnknownValueException("Date object is undefined!", AT);
}
#ifdef NEGATIVE_JULIAN
if(*this==indate) return true;
return (gmt_julian >= indate.gmt_julian);
#else
return (gmt_julian >= (indate.gmt_julian-epsilon));
#endif
}
const Date Date::operator+(const Date& indate) const {
if(undef==true || indate.isUndef()) {
Date tmp; //create an Undef date
return tmp;
}
Date tmp(gmt_julian + indate.gmt_julian, 0.);
tmp.setTimeZone(timezone);
return tmp;
}
const Date Date::operator-(const Date& indate) const {
if(undef==true || indate.isUndef()) {
Date tmp; //create an Undef date
return tmp;
}
Date tmp(gmt_julian - indate.gmt_julian, 0.);
tmp.setTimeZone(timezone);
return tmp;
}
const Date Date::operator+(const double& indate) const {
//remains undef if undef
Date tmp(gmt_julian + indate, 0.);
tmp.setTimeZone(timezone);
return tmp;
}
const Date Date::operator-(const double& indate) const {
//remains undef if undef
Date tmp(gmt_julian - indate, 0.);
tmp.setTimeZone(timezone);
return tmp;
}
const Date Date::operator*(const double& value) const {
//remains undef if undef
Date tmp(gmt_julian * value, 0.);
tmp.setTimeZone(timezone);
return tmp;
}
const Date Date::operator/(const double& value) const {
//remains undef if undef
Date tmp(gmt_julian / value, 0.);
tmp.setTimeZone(timezone);
return tmp;
}
/**
* @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
* - '+01:30' like in 2013-02-13T21:13+01:30 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)
{
if(timezone_iso.empty()) //just in case, to avoid a segfault
return 0.;
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;
}
}
/**
* @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
*/
std::string Date::printFractionalDay(const double& fractional) {
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();
}
/**
* @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)
int year_out, month_out, day_out, hour_out, minute_out, second_out;
double julian_out;
if(undef==true)
throw UnknownValueException("Date object is undefined!", AT);
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, second_out);
}
ostringstream tmpstr;
switch(type) {
case(ISO_TZ):
case(ISO):
tmpstr
<< 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 << ":"
<< setw(2) << setfill('0') << second_out;
if(type==ISO_TZ) {
int tz_h, tz_min;
if(timezone>=0.) {
tz_h = static_cast(timezone);
tz_min = static_cast( (timezone - (double)tz_h)*60. + .5 ); //round to closest
tmpstr << "+";
} else {
tz_h = -static_cast(timezone);
tz_min = -static_cast( (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(ISO_DATE):
tmpstr
<< setw(4) << setfill('0') << year_out << "-"
<< setw(2) << setfill('0') << month_out << "-"
<< setw(2) << setfill('0') << day_out;
break;
case(NUM):
tmpstr
<< 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
<< setw(2) << setfill('0') << second_out;
break;
case(FULL):
tmpstr
<< 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;
break;
case(DIN):
tmpstr
<< 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 << ":"
<< setw(2) << setfill('0') << second_out;
break;
case(ISO_WEEK):
{
int ISO_year;
const int ISO_week = getISOWeekNr(ISO_year, gmt);
tmpstr
<< setw(4) << setfill('0') << ISO_year << "-W"
<< setw(2) << setfill('0') << ISO_week << "-"
<< setw(2) << setfill('0') << getDayOfWeek(gmt);
break;
}
default:
throw InvalidArgumentException("Wrong date conversion format requested", AT);
}
return tmpstr.str();
}
const std::string Date::toString() const {
std::ostringstream os;
os << "\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 << "\n";
return os.str();
}
std::iostream& operator<<(std::iostream& os, const Date& date) {
os.write(reinterpret_cast(&date.timezone), sizeof(date.timezone));
os.write(reinterpret_cast(&date.gmt_julian), sizeof(date.gmt_julian));
os.write(reinterpret_cast(&date.gmt_year), sizeof(date.gmt_year));
os.write(reinterpret_cast(&date.gmt_month), sizeof(date.gmt_month));
os.write(reinterpret_cast(&date.gmt_day), sizeof(date.gmt_day));
os.write(reinterpret_cast(&date.gmt_hour), sizeof(date.gmt_hour));
os.write(reinterpret_cast(&date.gmt_minute), sizeof(date.gmt_minute));
os.write(reinterpret_cast(&date.gmt_second), sizeof(date.gmt_second));
os.write(reinterpret_cast(&date.dst), sizeof(date.dst));
os.write(reinterpret_cast(&date.undef), sizeof(date.undef));
return os;
}
std::iostream& operator>>(std::iostream& is, Date& date) {
is.read(reinterpret_cast(&date.timezone), sizeof(date.timezone));
is.read(reinterpret_cast(&date.gmt_julian), sizeof(date.gmt_julian));
is.read(reinterpret_cast(&date.gmt_year), sizeof(date.gmt_year));
is.read(reinterpret_cast(&date.gmt_month), sizeof(date.gmt_month));
is.read(reinterpret_cast(&date.gmt_day), sizeof(date.gmt_day));
is.read(reinterpret_cast(&date.gmt_hour), sizeof(date.gmt_hour));
is.read(reinterpret_cast(&date.gmt_minute), sizeof(date.gmt_minute));
is.read(reinterpret_cast(&date.gmt_second), sizeof(date.gmt_second));
is.read(reinterpret_cast(&date.dst), sizeof(date.dst));
is.read(reinterpret_cast(&date.undef), sizeof(date.undef));
return is;
}
// PRIVATE METHODS
double Date::calculateJulianDate(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
{
const long julday = getJulianDayNumber(i_year, i_month, i_day);
const double frac = (i_hour-12.)/24. + i_minute/(24.*60.) + i_second/(24.*3600.); //the julian date reference is at 12:00
return (((double)julday) + frac);
}
void Date::calculateValues(const double& i_julian, int& o_year, int& o_month, int& o_day, int& o_hour, int& o_minute, int& o_second) const
{ //given a julian day, calculate the year, month, day, hours and minutes
//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 second since this is our current resolution
const double tmp_julian = rnd(i_julian, 1);
const long julday = Optim::floor(tmp_julian+0.5);
long t1 = julday + 68569L;
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;
o_day = (int) ( t1 - 2447L * mo / 80L );
t1 = mo / 11L;
o_month = (int) ( mo + 2L - 12L * t1 );
o_year = (int) ( 100L * ( t2 - 49L ) + yr + t1 );
// Correct for BC years -> astronomical year, that is from year -1 to year 0
if ( o_year <= 0 ) o_year--;
double integral;
const double frac = modf(tmp_julian+.5, &integral); //the julian date reference is at 12:00
const int sec = static_cast(Optim::round(frac*(24.*3600.)));
o_hour = static_cast(double(sec)/3600.);
o_minute = static_cast(double(sec - 3600*o_hour)/60.);
o_second = sec - 3600*o_hour - 60*o_minute;
}
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;
}
long Date::getJulianDayNumber(const int& i_year, const int& i_month, const int& i_day) const
{ //given year, month, day, calculate the matching julian day
//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
const long lmonth = (long) i_month, lday = (long) i_day;
long lyear = (long) i_year;
// Correct for BC years -> astronomical year, that is from year -1 to year 0
if ( lyear < 0 ) lyear++;
const long jdn = lday - 32075L +
1461L * ( lyear + 4800L + ( lmonth - 14L ) / 12L ) / 4L +
367L * ( lmonth - 2L - ( lmonth - 14L ) / 12L * 12L ) / 12L -
3L * ( ( lyear + 4900L + ( lmonth - 14L ) / 12L ) / 100L ) / 4L;
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 double& in_second) const {
if ((in_year < -4713) || (in_year >3000)
|| (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)
|| (in_minute < 0) || (in_minute > 59)
|| (in_second < 0.) || (in_second >= 60.)) {
ostringstream ss;
ss << std::fixed << std::setfill('0') << std::setprecision(0);
ss << "Invalid Date requested: " << std::setw(4) << in_year << "-";
ss << std::setw(2) << in_month << "-";
ss << std::setw(2) << in_day << "T";
ss << std::setw(2) << in_hour << ":";
ss << std::setw(2) << in_minute << ":";
ss << std::setw(2) << in_second;
throw IOException(ss.str(), AT);
}
if ((in_hour == 24) && (in_minute != 0) && (in_second != 0.)) {
ostringstream ss;
ss << std::fixed << std::setfill('0') << std::setprecision(0);
ss << "Invalid Date requested: " << std::setw(4) << in_year << "-";
ss << std::setw(2) << in_month << "-";
ss << std::setw(2) << in_day << "T";
ss << std::setw(2) << in_hour << ":";
ss << std::setw(2) << in_minute << ":";
ss << std::setw(2) << in_second;
throw IOException(ss.str(), AT);
}
}
double Date::localToGMT(const double& i_julian) const {
if(dst) {
return (i_julian - timezone/24. - DST_shift/24.);
} else {
return (i_julian - timezone/24.);
}
}
double Date::GMTToLocal(const double& i_gmt_julian) const {
if(dst) {
return (i_gmt_julian + timezone/24. + DST_shift/24.);
} else {
return (i_gmt_julian + timezone/24.);
}
}
} //namespace