source: XIOS/trunk/src/node/calendar_wrapper.cpp @ 550

Last change on this file since 550 was 550, checked in by rlacroix, 6 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: 6.8 KB
Line 
1#include "calendar_wrapper.hpp"
2#include "type.hpp"
3#include "calendar_type.hpp"
4#include "duration.hpp"
5#include "context.hpp"
6
7namespace xios {
8
9  /// ////////////////////// Définitions ////////////////////// ///
10
11  CCalendarWrapper::CCalendarWrapper(void)
12    : CObjectTemplate<CCalendarWrapper>(), CCalendarWrapperAttributes()
13  { /* Ne rien faire de plus */ }
14
15  CCalendarWrapper::CCalendarWrapper(const StdString & id)
16    : CObjectTemplate<CCalendarWrapper>(id), CCalendarWrapperAttributes()
17  { /* Ne rien faire de plus */ }
18
19  CCalendarWrapper::~CCalendarWrapper(void)
20  {}
21
22  //----------------------------------------------------------------
23
24  StdString CCalendarWrapper::GetName(void)    { return StdString("calendar_wrapper"); }
25  StdString CCalendarWrapper::GetDefName(void) { return StdString("calendar"); }
26  ENodeType CCalendarWrapper::GetType(void)    { return eCalendarWrapper; }
27
28  //----------------------------------------------------------------
29
30  /*!
31  \brief Extract the calendar from its wrapper
32  \return the calendar
33  */
34  boost::shared_ptr<CCalendar> CCalendarWrapper::getCalendar(bool checkValid /*= false*/) const
35  {
36    if (checkValid && !this->calendar)
37      ERROR("CCalendarWrapper::getCalendar(bool checkValid = true)", << "The calendar was accessed before being created!");
38    return this->calendar;
39  }
40
41  const CDate& CCalendarWrapper::getInitDate() const
42  {
43    return getCalendar(true)->getInitDate();
44  }
45
46  const CDate& CCalendarWrapper::getTimeOrigin() const
47  {
48    return getCalendar(true)->getTimeOrigin();
49  }
50
51  //----------------------------------------------------------------
52
53  void CCalendarWrapper::setInitDate(const CDate& initDate)
54  {
55    getCalendar(true)->setInitDate(initDate);
56    start_date = initDate.toString();
57  }
58
59  void CCalendarWrapper::setTimeOrigin(const CDate& timeOrigin)
60  {
61    getCalendar(true)->setTimeOrigin(timeOrigin);
62    time_origin = timeOrigin.toString();
63  }
64
65  //----------------------------------------------------------------
66
67  /*!
68  \brief Parse the calendar node
69  \param [in] node xmld node corresponding to the calendar definition
70  */
71  void CCalendarWrapper::parse(xml::CXMLNode& node)
72  {
73    SuperClass::parse(node);
74
75    // Try to create the calendar
76    createCalendar();
77  }
78
79  /*!
80  \brief Try to create the calendar from the parsed attributes
81  */
82  void CCalendarWrapper::createCalendar(void)
83  {
84    // Create the calendar if possible
85    if (calendar)
86    {
87      ERROR("CCalendarWrapper::createCalendar(void)",
88            << "Error: the calendar can only be defined once!");
89    }
90    else if (!type.isEmpty())
91    {
92#define DECLARE_CALENDAR(MType, eType)                                     \
93      if (type.getValue() == type_attr::eType)                             \
94        calendar = boost::shared_ptr<CCalendar>(new C##MType##Calendar());
95#include "calendar_type.conf"
96#undef DECLARE_CALENDAR
97      // Special case for the user defined calendar
98      if (type.getValue() == type_attr::user_defined)
99      {
100        if (day_length.isEmpty())
101          ERROR("CCalendarWrapper::createCalendar(void)",
102                << "The day length must be configured when using an user defined calendar.");
103        if (month_lengths.isEmpty() == year_length.isEmpty())
104          ERROR("CCalendarWrapper::createCalendar(void)",
105                << "Either the month lengths or the year length must be configured when using an user defined calendar.");
106        if (leap_year_drift.isEmpty() != leap_year_month.isEmpty())
107          ERROR("CCalendarWrapper::createCalendar(void)",
108                << "Both leap_year_drift and leap_year_month attributes must be set if you wish to configure leap years.");
109        if (leap_year_drift.isEmpty() && !leap_year_drift_offset.isEmpty())
110          ERROR("CCalendarWrapper::createCalendar(void)",
111                << "Both leap_year_drift and leap_year_month attributes are mandatory if you wish to use leap_year_drift_offset attribute.");
112
113        boost::shared_ptr<CUserDefinedCalendar> userDefinedCalendar;
114        if (year_length.isEmpty())
115          userDefinedCalendar.reset(new CUserDefinedCalendar(day_length.getValue(), month_lengths.getValue()));
116        else
117          userDefinedCalendar.reset(new CUserDefinedCalendar(day_length.getValue(), year_length.getValue()));
118
119        if (!leap_year_month.isEmpty())
120          userDefinedCalendar->configureLeapYear(leap_year_month.getValue(),
121                                                 leap_year_drift.getValue(),
122                                                 leap_year_drift_offset.isEmpty() ? 0.0 : leap_year_drift_offset.getValue());
123
124        calendar = userDefinedCalendar;
125      }
126      else
127      {
128#define CHECK_EMPTY(attr)                                                                       \
129        if (!attr.isEmpty())                                                                    \
130          ERROR("CCalendarWrapper::createCalendar(void)",                                       \
131                << "The attribute \"" #attr "\" can only be used with user defined calendar.");
132        CHECK_EMPTY(day_length)
133        CHECK_EMPTY(month_lengths)
134        CHECK_EMPTY(year_length)
135        CHECK_EMPTY(leap_year_month)
136        CHECK_EMPTY(leap_year_drift)
137        CHECK_EMPTY(leap_year_drift_offset)
138#undef CHECK_EMPTY
139      }
140
141      if (!calendar)
142        ERROR("CCalendarWrapper::createCalendar(void)",
143              << "[ type = " << type.getStringValue() << " ] "
144              << "The calendar is not properly handled!");
145
146      // Set the timestep is available
147      if (!timestep.isEmpty())
148        calendar->setTimeStep(timestep.getValue());
149
150      // Parse and set the start date if available
151      if (!start_date.isEmpty())
152        calendar->setInitDate(CDate::FromString(start_date.getValue(), *calendar));
153
154      // Parse and set the time origin if available
155      if (!time_origin.isEmpty())
156        calendar->setTimeOrigin(CDate::FromString(time_origin.getValue(), *calendar));
157
158      // Notify the context about the calendar
159      CContext* context = CContext::getCurrent();
160      if (!context)
161        ERROR("CCalendarWrapper::createCalendar(void)", << "Impossible to set the calendar: no current context available.");
162      context->setCalendar(calendar);
163    }
164    else if (!start_date.isEmpty() || !time_origin.isEmpty())
165    {
166      ERROR("CCalendarWrapper::createCalendar(void)",
167            << "The calendar type must be set before defining the start date or the time origin!");
168    }
169  }
170
171  /*!
172  \brief Try to update the timestep of the calendar with the corresponding attribute
173  */
174  void CCalendarWrapper::updateTimestep(void)
175  {
176    if (timestep.isEmpty())
177    {
178      ERROR("CCalendarWrapper::updateTimestep(void)",
179            << "Error: the timestep needs to be defined!");
180    }
181    else if (calendar)
182      calendar->setTimeStep(timestep.getValue());
183  }
184}
Note: See TracBrowser for help on using the repository browser.