PLearn 0.1
Calendar.cc
Go to the documentation of this file.
00001 // -*- C++ -*-
00002 
00003 // Calendar.cc
00004 //
00005 // Copyright (C) 2004-2005 ApSTAT Technologies Inc.
00006 // 
00007 // Redistribution and use in source and binary forms, with or without
00008 // modification, are permitted provided that the following conditions are met:
00009 // 
00010 //  1. Redistributions of source code must retain the above copyright
00011 //     notice, this list of conditions and the following disclaimer.
00012 // 
00013 //  2. Redistributions in binary form must reproduce the above copyright
00014 //     notice, this list of conditions and the following disclaimer in the
00015 //     documentation and/or other materials provided with the distribution.
00016 // 
00017 //  3. The name of the authors may not be used to endorse or promote
00018 //     products derived from this software without specific prior written
00019 //     permission.
00020 // 
00021 // THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
00022 // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
00023 // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
00024 // NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
00025 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
00026 // TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
00027 // PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
00028 // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
00029 // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
00030 // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
00031 // 
00032 // This file is part of the PLearn library. For more information on the PLearn
00033 // library, go to the PLearn Web site at www.plearn.org
00034 
00035 /* *******************************************************      
00036  * $Id: Calendar.cc 7042 2007-05-09 23:44:20Z saintmlx $ 
00037  ******************************************************* */
00038 
00039 // Authors: Jean-Sébastien Senécal
00040 
00044 #include "Calendar.h"
00045 #include <plearn/base/PDate.h>
00046 #include <plearn/math/TMat_sort.h>
00047 #include <set>
00048 #include <plearn/base/RemoteDeclareMethod.h>
00049 
00050 namespace PLearn {
00051 using namespace std;
00052 
00053 PLEARN_IMPLEMENT_OBJECT(
00054     Calendar,
00055     "Encapsulates the concept of a calendar as an ordered finite list of timestamps.",
00056     "This class encapsulates the concept of a calendar as an ordered "
00057     "finite list of timestamps. The idea is to provide a tool to "
00058     "convert a continuous representation of time (as julian dates) "
00059     "into a discrete one. Not only the calendar time units (CTime) are "
00060     "different, but the time axis may \"leap-over\" time ranges in "
00061     "the continous time (JTime) axis.\n"
00062     "\n"
00063     "For instance, one may want to represent a conception of time "
00064     "where time is sampled daily but only during business week days "
00065     "(i.e. monday to friday) s.t. when time t is a friday, time (t+1) "
00066     "will be the next monday (and not saturday). Such a calendar would "
00067     "contain, in its internal representation, a list of timestamps that "
00068     "correspond to, say, Midnight of every Mon/Tues/Wednes/Thurs/Fri-days "
00069     "from, say, 1900 to 2099.\n"
00070     "\n"
00071     "In addition, the class supports a set of global (static) calendars keyed\n"
00072     "by a string.  Functions are provided to set/get global calendars\n"
00073     "associated with string keys.  A Remote-Method interface is provided as\n"
00074     "well to set global calendars.  Special operators are available in\n"
00075     "VMatLanguage to access those global calendars.\n"
00076     );
00077 
00078 
00079 // Set of global calendars
00080 map<string,PP<Calendar> > Calendar::global_calendars;
00081 
00082   
00083 Calendar::Calendar()
00084     : inherited(),
00085       last_julian_time_(MISSING_VALUE),
00086       last_calendar_time_(-1),
00087       last_use_lower_bound(false),
00088       timestamps_()
00089 { }
00090 
00091 Calendar::Calendar(const JTimeVec& timestamps)
00092     : inherited(),
00093       last_julian_time_(MISSING_VALUE),
00094       last_calendar_time_(-1),
00095       last_use_lower_bound(false),
00096       timestamps_(timestamps)
00097 {
00098     build_();
00099 }
00100 
00101 
00102 PCalendar Calendar::makeCalendar(const Vec& dates)
00103 {
00104     static int Jan1_1900_JDN  = PDate(1900,01,01).toJulianDay();
00105     static int Dec31_2199_JDN = PDate(2199,12,31).toJulianDay();
00106     TVec<JTime> jdates(dates.length());
00107   
00108     // Start by converting the dates to Julian
00109     for (int i=0, n=dates.size() ; i<n ; ++i) {
00110         real date_i = dates[i];
00111 
00112         // YYYYMMDD
00113         if (date_i >= 19000101 && date_i <= 21991231) {
00114             int year  = int(date_i / 10000);
00115             int month = (int(date_i) % 10000) / 100;
00116             int day   = int(date_i) % 100;
00117             jdates[i] = PDate(year,month,day).toJulianDay();
00118         }
00119 
00120         // CYYMMDD (up to 2099/12/31, since 1900/01/01 == JDN-2415021)
00121         else if (date_i >= 000101 && date_i <= 1991231) {
00122             jdates[i] = float_to_date(date_i).toJulianDay();
00123         }
00124 
00125         // Otherwise check it's in the proper range for a JDN
00126         else if (date_i < Jan1_1900_JDN ||
00127                  date_i > Dec31_2199_JDN)
00128             PLERROR("The date value '%g' at index %d could not be interpreted\n"
00129                     "as a date in YYYYMMDD, CYYMMDD or JDN in the 1900/01/01 to\n"
00130                     "2199/12/31 range.", date_i, i);
00131         else
00132             jdates[i] = JTime(date_i);
00133     }
00134 
00135     // Next sort by increasing value...
00136     sortElements(jdates);
00137 
00138     return new Calendar(jdates);
00139 }
00140 
00141 void Calendar::build_()
00142 {
00143     // If we have dwim_timestamps_ and no timestamps_, fill latter from former
00144     if (dwim_timestamps_.size() > 0 && timestamps_.size() == 0) {
00145         PCalendar cal = makeCalendar(dwim_timestamps_);
00146         timestamps_ = cal->timestamps_;
00147     }
00148 
00149     // perform some preventive checks
00150     bool doublons = false;
00151     bool disordered = false;
00152     for (int i=1; i<timestamps_.length(); i++)
00153     {
00154         if (fast_exact_is_equal(timestamps_[i], timestamps_[i-1]))
00155         {
00156             doublons = true;
00157             if (disordered) break;
00158         }
00159         else if (timestamps_[i] < timestamps_[i-1])
00160         {
00161             disordered = true;
00162             if (doublons) break;
00163         }
00164     }
00165     if (doublons)
00166         PLWARNING("In Calendar::build_(), provided timestamps contained repetitions.\n"
00167                   "The calendar's behavior is therefore undefined. Please check it.");
00168     if (disordered)
00169         PLWARNING("In Calendar::build_(), provided timestamps were disordered.\n"
00170                   "The calendar's behavior is therefore undefined. Please check it.");
00171 }
00172 
00173 void Calendar::declareOptions(OptionList& ol)
00174 {
00175     declareOption(
00176         ol, "timestamps", &Calendar::timestamps_,
00177         OptionBase::buildoption,
00178         "List of the time stamps (in julian time) that are the\n"
00179         "skeleton of this calendar.");
00180 
00181     declareOption(
00182         ol, "dwim_timestamps", &Calendar::dwim_timestamps_,
00183         OptionBase::buildoption,
00184         "Alternative list of \"human timestamps\" that may be provided in any of\n"
00185         "the following formats: Julian, CYYMMDD, YYYYMMDD.  The format is\n"
00186         "recognized automatically.  The dates need not be sorted; they will be\n"
00187         "sorted automatically.  After construction of the calendar, the option\n"
00188         "'timestamps' is filled out with the converted Julian timesteps.\n");
00189         
00190     inherited::declareOptions(ol);
00191 }
00192 
00193 void Calendar::declareMethods(RemoteMethodMap& rmm)
00194 {
00195     // Insert a backpointer to remote methods; note that this
00196     // different than for declareOptions()
00197     rmm.inherited(inherited::_getRemoteMethodMap_());
00198 
00199     declareMethod(
00200         rmm, "setGlobalCalendar", &Calendar::remote_setGlobalCalendar,
00201         (BodyDoc("Set a global calendar keyed to the given string.\n"
00202                  "The calendar is created if it does not already exist."),
00203          ArgDoc ("calendar_name",  "Name of the global calendar to be set"),
00204          ArgDoc ("calendar_dates", "List of dates that belong to the calendar,\n"
00205                                    "either in CYYMMDD or YYYYMMDD format")));
00206 
00207     declareMethod(
00208         rmm, "getGlobalCalendar", &Calendar::remote_getGlobalCalendar,
00209         (BodyDoc("Return the dates that belong to the global calendar,\n"
00210                  "given its name"),
00211          ArgDoc ("calendar_name", "Name of the global calendar to be accessed"),
00212          RetDoc ("Set of dates that belong to the calendar, in PLearn\n"
00213                  "Julian Date format")));
00214 }
00215 
00216 void Calendar::build()
00217 {
00218     inherited::build();
00219     build_();
00220 }
00221 
00222 void Calendar::makeDeepCopyFromShallowCopy(CopiesMap& copies)
00223 {
00224     inherited::makeDeepCopyFromShallowCopy(copies);
00225     deepCopyField(timestamps_, copies);
00226 }
00227 
00228 CTime Calendar::getCalendarTime(JTime julian_time, bool use_lower_bound) const
00229 {
00230     if (timestamps_.isEmpty())
00231         PLERROR("In Calendar::getCalendarTime(), this calendar is empty.");
00232     if (last_calendar_time_ == -1 ||
00233         !fast_exact_is_equal(last_julian_time_, julian_time) ||
00234         last_use_lower_bound != use_lower_bound) // *** that one could be made more efficient
00235     {
00236         JTimeVec::iterator result;
00237         if (use_lower_bound)
00238             result = lower_bound(timestamps_.begin(), timestamps_.end(), julian_time);
00239         else
00240             result = upper_bound(timestamps_.begin(), timestamps_.end(), julian_time);
00241         if (result == timestamps_.end()) // don't go after
00242             --result;
00243         last_calendar_time_ = (CTime) (result - timestamps_.begin());
00244         last_julian_time_ = julian_time;
00245         last_use_lower_bound = use_lower_bound;
00246     }
00247   
00248     return last_calendar_time_;
00249 }
00250 
00251 CTime Calendar::getLastDayOfMonth(const PDate& day_of_the_month) const
00252 {
00253     PDate last_day_of_month = day_of_the_month.lastDateOfMonth();
00254     PLASSERT(day_of_the_month.month==last_day_of_month.month);
00255 
00256     JTime jtime_ldom = last_day_of_month.toJulianDay();
00257     CTime ctime_ldom = getCalendarTime(jtime_ldom);
00258 
00259     // Was first day of the following month returned?
00260     if ( ctime_ldom > 0 && timestamps_[ctime_ldom] > jtime_ldom )
00261     {
00262         ctime_ldom--;
00263         PLASSERT( timestamps_[ctime_ldom] <= jtime_ldom );
00264     }
00265 
00266     return ctime_ldom;
00267 }
00268 
00269 
00270 CTime Calendar::convertCalendarTime(const Calendar& source_calendar,
00271                                     const Calendar& dest_calendar,
00272                                     CTime source_time,
00273                                     bool use_lower_bound)
00274 {
00275     if (source_calendar.isEmpty() || dest_calendar.isEmpty())
00276         PLERROR("In convertCalendarTime(), one of the calendars is empty.");
00277     return dest_calendar.getCalendarTime(source_calendar.getTime(source_time), use_lower_bound);
00278 }
00279 
00280 
00281 bool Calendar::containsTime(JTime julian_time, CTime *calendar_time) const
00282 {
00283     CTime found = getCalendarTime(julian_time);
00284     if (fast_exact_is_equal(timestamps_[found], julian_time))
00285     {
00286         if (calendar_time)
00287             *calendar_time = found;
00288         return true;
00289     }
00290     else
00291         return false;
00292 }
00293 
00294 
00295 JTime Calendar::calendarTimeOnOrAfter(JTime julian_time) const
00296 {
00297     CTime start_point = getCalendarTime(julian_time);
00298     if (getTime(start_point) < julian_time)    // if at very end of calendar
00299         return MAX_TIME;
00300     else
00301         return getTime(start_point);
00302 }
00303 
00304 
00305 JTime Calendar::calendarTimeOnOrBefore(JTime julian_time) const
00306 {
00307     CTime start_point = getCalendarTime(julian_time);
00308     while (start_point >= 0 && getTime(start_point) > julian_time)
00309         start_point--;
00310     if (start_point < 0)
00311         return MIN_TIME;
00312     else
00313         return getTime(start_point);
00314 }
00315 
00316 
00317 PCalendar Calendar::unite(const TVec<PCalendar>& raw_calendars)
00318 {
00319     PCalendar united = new Calendar;
00320 
00321     // In the case of shared calendars, the same calendar may be repeated
00322     // multiple times in raw_calendars.  Pick out unique copies
00323     set<PCalendar> calendars(raw_calendars.begin(), raw_calendars.end());
00324   
00325     if (!calendars.empty())
00326     {
00327         set<JTime> timestamps;
00328         for (set<PCalendar>::iterator it = calendars.begin(); it != calendars.end(); ++it)
00329         {
00330             set_union(timestamps.begin(), timestamps.end(),
00331                       (*it)->timestamps_.begin(),  (*it)->timestamps_.end(),
00332                       inserter(timestamps, timestamps.begin()));
00333         }
00334         united->timestamps_.resize(int(timestamps.size()));
00335         copy(timestamps.begin(), timestamps.end(), united->timestamps_.begin());
00336     }
00337 
00338     united->build();
00339   
00340     return united;
00341 }
00342 
00343 
00344 PCalendar Calendar::intersect(const TVec<PCalendar>& raw_calendars)
00345 {
00346     PCalendar intersected = new Calendar;
00347   
00348     // In the case of shared calendars, the same calendar may be repeated
00349     // multiple times in raw_calendars.  Pick out unique copies
00350     set<PCalendar> calendars(raw_calendars.begin(), raw_calendars.end());
00351   
00352     if ( !calendars.empty() )
00353     {
00354         set<PCalendar>::iterator it  = calendars.begin();
00355         set<PCalendar>::iterator end = calendars.end(); 
00356     
00357         if ( it != end ) 
00358         {
00359             JTimeVec intersection = (*it)->timestamps_.copy();
00360             JTimeVec::iterator curend = intersection.end();
00361             ++it;
00362       
00363             for (; it != end; ++it)
00364             {        
00365                 curend = set_intersection(intersection.begin(),
00366                                           curend,
00367                                           (*it)->timestamps_.begin(), (*it)->timestamps_.end(),
00368                                           intersection.begin());
00369             }
00370             intersected->timestamps_ =
00371                 intersection.subVec(0, int(curend-intersection.begin())).copy();
00372         }
00373     }
00374 
00375     intersected->build();  
00376     return intersected;
00377 }
00378 
00379 
00380 PCalendar Calendar::calendarDiff(const Calendar* cal, const JTimeVec& to_remove)
00381 {
00382     set<JTime> cal_times(cal->timestamps_.begin(), cal->timestamps_.end());
00383     for (int i=0, n=to_remove.size() ; i<n ; ++i)
00384         cal_times.erase(to_remove[i]);
00385 
00386     JTimeVec new_timestamps(int(cal_times.size()));
00387     copy(cal_times.begin(), cal_times.end(), new_timestamps.begin());
00388     return new Calendar(new_timestamps);
00389 }
00390 
00391 
00392 PCalendar Calendar::clamp(JTime lower, JTime upper)
00393 {
00394     // Handle degenerate cases
00395     if (size() == 0 || upper < (*this)[0] || lower > (*this)[size()-1])
00396         return new Calendar;
00397     
00398     CTime lower_ctime = getCalendarTime(lower, /* use_lower_bound= */ true);
00399     CTime upper_ctime = getCalendarTime(upper, /* use_lower_bound= */ false);
00400     if ((*this)[upper_ctime] > upper)
00401         --upper_ctime;
00402     PLASSERT( lower_ctime <= upper_ctime );
00403     PLASSERT( (*this)[lower_ctime] >= lower );
00404     PLASSERT( (*this)[upper_ctime] <= upper );
00405 
00406     JTimeVec new_timestamps = timestamps_.subVec(lower_ctime,
00407                                                  upper_ctime - lower_ctime + 1);
00408     return new Calendar(new_timestamps);
00409 }
00410 
00411 
00412 void Calendar::setGlobalCalendar(const string& calendar_name,
00413                                  PCalendar calendar)
00414 {
00415     if (calendar_name != "")
00416         global_calendars[calendar_name] = calendar;
00417     else
00418         PLERROR("Calendar::setGlobalCalendar: the calendar name must be specified");
00419 }
00420 
00421 
00422 const Calendar* Calendar::getGlobalCalendar(const string& calendar_name)
00423 {
00424     map<string,PCalendar>::const_iterator it =
00425         global_calendars.find(calendar_name);
00426     if (it != global_calendars.end())
00427         return (const Calendar*)(it->second);
00428     else
00429         return 0;
00430 }
00431 
00432 
00433 void Calendar::remote_setGlobalCalendar(string calendar_name, Vec calendar_dates)
00434 {
00435     setGlobalCalendar(calendar_name, makeCalendar(calendar_dates));
00436 }
00437 
00438 
00439 JTimeVec Calendar::remote_getGlobalCalendar(string calendar_name)
00440 {
00441     const Calendar* cal = getGlobalCalendar(calendar_name);
00442     if (! cal) {
00443         PLERROR("Global calendar '%s' not found", calendar_name.c_str());
00444         return JTimeVec();                        
00445     }
00446     else
00447         return cal->timestamps_;
00448 }
00449 
00450 } // end of namespace PLearn
00451 
00452 
00453 /*
00454   Local Variables:
00455   mode:c++
00456   c-basic-offset:4
00457   c-file-style:"stroustrup"
00458   c-file-offsets:((innamespace . 0)(inline-open . 0))
00459   indent-tabs-mode:nil
00460   fill-column:79
00461   End:
00462 */
00463 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=79 :
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines