PLearn 0.1
|
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 :