source: XIOS/trunk/src/date/user_defined.cpp @ 568

Last change on this file since 568 was 550, checked in by rlacroix, 9 years ago

Add a new user defined calendar type.

A new calendar type "user_defined" is now available. This allows the users to create a custom calendar that we can configured to be suitable for planets other than the Earth.

An user defined calendar is always defined by two mandatory attributes:

  • day_length: the duration of a day, in seconds
  • and either:
    • month_length: an array containing the duration of each month, in days (the number of elements in the array is the number of months in a year)
    • or year_length: the duration of a year, in seconds (in that case, the calendar does not have months).

If the calendar has months (i.e. month_length attribute is set) and only in that case, it is possible to define leap years in order to compensate for the duration of an astronomical year not being a multiple of the day length. The leap years are defined by two mandatory attributes:

  • leap_year_month: the month to which the extra day will be added in case of leap year, expressed as an integer number in the range [1, numberOfMonths]
  • and leap_year_drift: the fraction of a day representing the yearly drift between the calendar year and the astronomical year, expressed as a real number in the range [0, 1).

Optionally, one can define the leap_year_drift_offset attribute to set the original drift at the beginning of the time origin's year, again expressed as a real number in the range [0, 1). If leap_year_drift_offset + leap_year_drift is greater or equal to 1, then the first year will be a leap year.

For example, the following configuration creates a Gregorian-like calendar:

<calendar type="user_defined" start_date="2012-03-01 15:00:00" time_origin="2012-02-28 15:00:00 + 1d" day_length="86400" month_lengths="(1, 12) [31 28 31 30 31 30 31 31 30 31 30 31]" leap_year_month="2" leap_year_drift="0.25" leap_year_drift_offset="0.75" />

Note that dates attributes must be written differently in the configuration file when using an user defined calendar without months:

  • if the year length is greater than the day length, the input format is year-day hh:min:sec instead of year-month-day hh:min:sec
  • if the day length is greater or equal to the year length, the input format is year hh:min:sec.

In all cases, it is still possible to use the date + duration notation to build a date (with both the date and duration parts being optional).

The Fortran interface has been updated accordingly so that xios_define_calendar can accept the new attributes necessary to define custom calendars.

File size: 9.7 KB
Line 
1#include "user_defined.hpp"
2#include "calendar_util.hpp"
3
4namespace xios
5{
6  /// ////////////////////// Définitions ////////////////////// ///
7
8  CUserDefinedCalendar::CUserDefinedCalendar(int dayLength, const CArray<int,1>& monthLengths)
9    : CCalendar("user_defined")
10    , dayLength(dayLength)
11    , monthLengths(monthLengths)
12    , yearLength(sum(monthLengths) * dayLength)
13    , leapYearMonth(0)
14    , leapYearDrift(0.0)
15    , leapYearDriftOffset(0.0)
16  {
17    if (dayLength <= 0)
18      ERROR("CUserDefinedCalendar::CUserDefinedCalendar(int dayLength, const CArray<int,1>& monthLengths)",
19            << "The day length must be strictly positive.");
20    if (monthLengths.numElements() == 0)
21      ERROR("CUserDefinedCalendar::CUserDefinedCalendar(int dayLength, const CArray<int,1>& monthLengths)",
22            << "The month lengths must be specified.");
23    if (min(monthLengths) <= 0)
24      ERROR("CUserDefinedCalendar::CUserDefinedCalendar(int dayLength, const CArray<int,1>& monthLengths)",
25            << "All month lengths must be strictly positive.");
26    // Ensure that the month lengths array is always 0-indexed
27    this->monthLengths.reindexSelf(TinyVector<int, 1>(0));
28  }
29
30  CUserDefinedCalendar::CUserDefinedCalendar(int dayLength, int yearLength)
31    : CCalendar("user_defined")
32    , dayLength(dayLength)
33    , yearLength(yearLength)
34    , leapYearMonth(0)
35    , leapYearDrift(0.0)
36    , leapYearDriftOffset(0.0)
37  {
38    if (dayLength <= 0)
39      ERROR("CUserDefinedCalendar::CUserDefinedCalendar(int dayLength, int yearLength)",
40            << "The day length must be strictly positive.");
41    if (yearLength <= 0)
42      ERROR("CUserDefinedCalendar::CUserDefinedCalendar(int dayLength, int yearLength)",
43            << "The year length must be strictly positive.");
44  }
45
46  CUserDefinedCalendar::~CUserDefinedCalendar(void)
47  { /* Nothing to do here */ }
48
49  ///--------------------------------------------------------------
50
51  void CUserDefinedCalendar::configureLeapYear(int leapYearMonth, double leapYearDrift, double leapYearDriftOffset /*= 0.0*/)
52  {
53    if (monthLengths.numElements() == 0)
54      ERROR("void CUserDefinedCalendar::configureLeapYear(int leapYearMonth, double leapYearDrift, double leapYearDriftOffset /*= 0.0*/)",
55            << "Impossible to define leap years on a calendar without months.");
56    if (leapYearMonth < 1 || leapYearMonth > monthLengths.numElements())
57      ERROR("void CUserDefinedCalendar::configureLeapYear(int leapYearMonth, double leapYearDrift, double leapYearDriftOffset /*= 0.0*/)",
58            << "The month chosen for the additional day must be in the range [1, " << monthLengths.numElements() << "].");
59    if (leapYearDrift < 0.0 || leapYearDrift >= 1.0)
60      ERROR("void CUserDefinedCalendar::configureLeapYear(int leapYearMonth, double leapYearDrift, double leapYearDriftOffset /*= 0.0*/)",
61            << "The year drift must be in the range [0.0, 1.0).");
62    if (leapYearDriftOffset < 0.0 || leapYearDriftOffset >= 1.0)
63      ERROR("void CUserDefinedCalendar::configureLeapYear(int leapYearMonth, double leapYearDrift, double leapYearDriftOffset /*= 0.0*/)",
64            << "The year drift offset must be in the range [0.0, 1.0).");
65
66    this->leapYearMonth = leapYearMonth;
67    this->leapYearDrift = leapYearDrift;
68    this->leapYearDriftOffset = leapYearDriftOffset;
69  }
70
71  ///--------------------------------------------------------------
72
73  StdString CUserDefinedCalendar::getType(void) const { return StdString("user_defined"); }
74
75  /*! Returns the duration of the date's month in days. */
76  int CUserDefinedCalendar::getMonthLength(const CDate& date) const
77  {
78    int monthLength = 0;
79    if (monthLengths.numElements() > 0)
80    {
81      monthLength = monthLengths(date.getMonth() - 1);
82      if (date.getMonth() == leapYearMonth && isLeapYear(date.getYear()))
83        monthLength++;
84    }
85    return monthLength;
86  }
87
88  /*! Returns the duration of the date's year in seconds. */
89  int CUserDefinedCalendar::getYearTotalLength(const CDate& date) const
90  {
91    return isLeapYear(date.getYear()) ? yearLength + dayLength : yearLength;
92  }
93
94  /*! Returns the duration of a day in hours. Note that this value is
95      not necessarily exact since it can be rounded but it is always
96      the smallest integer number of hours that can hold a day. */
97  int CUserDefinedCalendar::getDayLength(void) const { return int(ceil(double(dayLength) / (getHourLength() * getMinuteLength()))); }
98
99  /*! Returns the duration of a year in months. */
100  int CUserDefinedCalendar::getYearLength(void) const { return monthLengths.numElements(); }
101
102  /*! Returns the day length expressed in seconds. */
103  int CUserDefinedCalendar::getDayLengthInSeconds(void) const { return dayLength; }
104
105  /*! Test if the calendar can have leap year. */
106  bool CUserDefinedCalendar::hasLeapYear() const{ return (leapYearDrift != 0.0); }
107
108  /*!
109    Test if the specified year is a leap year.
110    \param year the year to be tested
111    \return true if and only the specified year is a leap year
112  */
113  bool CUserDefinedCalendar::isLeapYear(int year) const
114  {
115    double intPart; // dummy variable
116    return (hasLeapYear() &&
117            (abs(1.0 - (modf(leapYearDriftOffset + (year - getTimeOrigin().getYear()) * leapYearDrift, &intPart) + leapYearDrift)) < 1e-14));
118  }
119
120  CDuration& CUserDefinedCalendar::resolve(CDuration& dur, bool noNegativeTime /*= false*/) const
121  {
122    if (monthLengths.numElements() > 0) // normal case, we can rely on the generic function
123      return CCalendar::resolve(dur, noNegativeTime);
124
125    if (dur.month)
126      ERROR("CDuration& CUserDefinedCalendar::resolve(CDuration& dur, bool noNegativeTime /*= false*/) const",
127            << "month = " << dur.month << " but the user defined calendar has no month.");
128
129    const int hourLengthInSeconds = getHourLength() * getMinuteLength();
130
131    // Convert everything in seconds
132    Time t = Time(dur.year * yearLength + dur.day * dayLength
133                    + (dur.hour * getHourLength() + dur.minute) * getMinuteLength() + dur.second);
134
135    // Then convert back to years
136    dur.year = int(t / yearLength);
137    t %= yearLength;
138
139    // days
140    dur.day = int(t / dayLength);
141    t %= dayLength;
142
143    // Do we allow hour, minute, second to be negative?
144    if (noNegativeTime)
145    {
146      // If we don't, we remove some days or years until the time is positive
147      double& val = (dayLength < yearLength) ? dur.day : dur.year;
148      int length  = (dayLength < yearLength) ? dayLength : yearLength;
149      while (t < 0)
150      {
151        t += length;
152        val -= 1.0;
153      }
154    }
155
156    // hours
157    dur.hour = int(t / hourLengthInSeconds);
158    t %= hourLengthInSeconds;
159    // minutes
160    dur.minute = int(t / getMinuteLength());
161    // secondes
162    dur.second = int(t % getMinuteLength());
163
164    return dur;
165  }
166
167  /*! Parse a date using the calendar's parser. */
168  void CUserDefinedCalendar::parseDate(StdIStream& in, CDate& date) const
169  {
170    if (monthLengths.numElements() > 0) // normal case, we can rely on the generic function
171    {
172      CCalendar::parseDate(in, date);
173      return;
174    }
175
176    char sep = '-'; // Le caractÚre c est utilisé pour "recueillir" les séparateurs "/" et ":".
177    char c;
178
179    // Default initialize the date
180    int year = 00, month  = 01, day    = 01;
181    int hour = 00, minute = 00, second = 00;
182
183    // Read the year
184    in >> year;
185    // Read the day only if the day length is smaller than the year length
186    c = in.get();
187    if (c == sep && dayLength < yearLength)
188    {
189      in >> day;
190      c = in.get();
191    }
192    // Read the time
193    sep = ' ';
194    if (c == sep)
195    {
196      in >> hour >> c;
197      sep = ':';
198      if (c == sep)
199      {
200        in >> minute >> c;
201        if (c == sep)
202        {
203          in >> second;
204          in >> c;
205        }
206      }
207    }
208
209    date.setDate(year, month, day, hour, minute, second);
210
211    // Delay the verification until we get a calendar we can compare the date to
212    if (!checkDate(date))
213      ERROR("void CUserDefinedCalendar::parseDate(StdIStream& in, CDate& date) const",
214            << "Bad date format or not conform to calendar");
215
216    if (c == '+') // We will be adding a duration to the date
217    {
218      CDuration dur;
219      in >> dur;
220      date = date + dur;
221    }
222    else if (!in.eof())
223      ERROR("void CUserDefinedCalendar::parseDate(StdIStream& in, CDate& date) const",
224            << "Invalid date format: unexpected trailing character(s)");
225  }
226
227  /*! Test if a date is valid with regard to the current calendar. */
228  bool CUserDefinedCalendar::checkDate(CDate& date) const
229  {
230    if (monthLengths.numElements() > 0) // normal case, we can rely on the generic function
231      return CCalendar::checkDate(date);
232
233    const int maxDay = (yearLength + dayLength - 1) / dayLength;
234
235    bool isValid = true;
236
237    // Vérification de la valeur du mois.
238    if (date.getMonth() != 1)
239    { isValid = false; date.setMonth(1); }
240
241    // Vérification de la valeur du jour.
242    if (date.getDay() < 1)
243    { isValid = false; date.setDay(1); }
244    else if (date.getDay() > maxDay)
245    { isValid = false; date.setDay(maxDay); }
246
247    // Vérification de la valeur de l'heure.
248    if (date.getHour() < 0)
249    { isValid = false; date.setHour(0); }
250    else if (date.getHour() >= getDayLength())
251    { isValid = false; date.setHour(getDayLength() - 1); }
252
253    // Vérification de la valeur des minutes.
254    if (date.getMinute() < 0)
255    { isValid = false; date.setMinute(0); }
256    else if (date.getMinute() >= getHourLength())
257    { isValid = false; date.setMinute(getHourLength() - 1); }
258
259    // Vérification de la valeur des secondes.
260    if (date.getSecond() < 0)
261    { isValid = false; date.setSecond(0); }
262    else if (date.getSecond() >= getMinuteLength())
263    { isValid = false; date.setSecond(getMinuteLength() - 1); }
264
265    return isValid;
266  }
267} // namespace xmlioserver
268
Note: See TracBrowser for help on using the repository browser.