cftime.cpp 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  1. #ifdef HAVE_NETCDF
  2. #include "cftime.h"
  3. #include "cf.h"
  4. #include <string>
  5. #include <assert.h>
  6. #include <string.h>
  7. #include <math.h>
  8. #include <stdio.h>
  9. namespace GuessNC {
  10. namespace CF {
  11. const int HOURS_PER_DAY = 24;
  12. const int SECONDS_PER_MINUTE = 60;
  13. const int MINUTES_PER_HOUR = 60;
  14. const int SECONDS_PER_HOUR = SECONDS_PER_MINUTE*MINUTES_PER_HOUR;
  15. const int SECONDS_PER_DAY = SECONDS_PER_HOUR*HOURS_PER_DAY;
  16. const int MINUTES_PER_DAY = MINUTES_PER_HOUR*HOURS_PER_DAY;
  17. const int FIRST_GREGORIAN_YEAR = 1583;
  18. const char* PRE_GREGORIAN_ERROR_MESSAGE = "standard/gregorian calendar not supported for dates before 1583";
  19. void check_gregorian_year(int year) {
  20. if (year < FIRST_GREGORIAN_YEAR) {
  21. throw CFError(PRE_GREGORIAN_ERROR_MESSAGE);
  22. }
  23. }
  24. CalendarType parse_calendar(const char* calendar) {
  25. std::string str(calendar);
  26. if (str == "standard" || str == "gregorian") {
  27. return STANDARD;
  28. }
  29. else if (str == "proleptic_gregorian") {
  30. return PROLEPTIC_GREGORIAN;
  31. }
  32. else if (str == "noleap" || str == "no_leap" || str == "365_day") {
  33. return NO_LEAP;
  34. }
  35. else {
  36. throw CFError(std::string("Unsupported calendar type: ") + calendar);
  37. }
  38. }
  39. TimeUnit parse_time_unit(const char* time_unit) {
  40. std::string str(time_unit);
  41. // zero-terminated arrays of synonyms for each time unit
  42. static const char* s_strings[] =
  43. { "seconds", "second", "secs", "sec", "s", 0 };
  44. static const char* m_strings[] =
  45. { "minutes", "minute", "mins", "min", 0};
  46. static const char* h_strings[] =
  47. { "hours", "hour", "hrs", "hr", "h", 0};
  48. static const char* d_strings[] =
  49. { "days", "day", "d", 0 };
  50. // array of the above arrays, ordered as the TimeUnit enum
  51. static const char** strings[] = {
  52. s_strings, m_strings, h_strings, d_strings
  53. };
  54. // search among the synonyms
  55. for (int i = 0; i < NBR_TIMEUNITS; ++i) {
  56. for (int j = 0; strings[i][j] != 0; ++j) {
  57. if (str == strings[i][j]) {
  58. return static_cast<TimeUnit>(i);
  59. }
  60. }
  61. }
  62. // nothing found...
  63. throw CFError(std::string("Unsupported time unit: ") + time_unit);
  64. }
  65. bool is_leap(int year) {
  66. return (!(year % 4) && (year % 100 | !(year % 400)));
  67. }
  68. int days_in_month(int year, int month, CalendarType calendar_type) {
  69. static const int days_of_month[] =
  70. {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
  71. if (calendar_type == NO_LEAP) {
  72. return days_of_month[month-1];
  73. }
  74. else if (calendar_type == STANDARD ||
  75. calendar_type == PROLEPTIC_GREGORIAN) {
  76. if (month != 2 || !is_leap(year)) {
  77. return days_of_month[month-1];
  78. }
  79. else {
  80. return 29;
  81. }
  82. }
  83. else {
  84. // Shouldn't happen...
  85. throw CFError("Unsupported calendar type");
  86. }
  87. }
  88. int days_in_year(int year, CalendarType calendar_type) {
  89. if (calendar_type == NO_LEAP) {
  90. return 365;
  91. }
  92. else if (calendar_type == STANDARD || calendar_type == PROLEPTIC_GREGORIAN) {
  93. if (is_leap(year)) {
  94. return 366;
  95. }
  96. else {
  97. return 365;
  98. }
  99. }
  100. else {
  101. // Shouldn't happen...
  102. throw CFError("Unsupported calendar type");
  103. }
  104. }
  105. bool valid_date(int year, int month, int day, CalendarType calendar_type) {
  106. if (month < 1 || month > 12) {
  107. return false;
  108. }
  109. if (day < 1 || day > days_in_month(year, month, calendar_type)) {
  110. return false;
  111. }
  112. if (calendar_type == STANDARD && year < FIRST_GREGORIAN_YEAR) {
  113. return false;
  114. }
  115. return true;
  116. }
  117. DateTime::DateTime()
  118. :year(0), month(1), day(1), seconds(0) {
  119. }
  120. DateTime::DateTime(int y, int mon, int d,
  121. int h, int min, double s)
  122. : year(y), month(mon), day(d),
  123. seconds(h*SECONDS_PER_HOUR+min*SECONDS_PER_MINUTE+s) {
  124. }
  125. DateTime::DateTime(const char* specification) {
  126. int hour, minute;
  127. float second;
  128. int count = sscanf(specification, "%d-%d-%d %d:%d:%f",
  129. &year, &month, &day, &hour, &minute, &second);
  130. if (count == 3) { // date only
  131. hour = 0;
  132. minute = 0;
  133. seconds = 0;
  134. }
  135. else if (count == 6) { // date and time
  136. seconds = hour*3600 + minute*60 + second;
  137. }
  138. else { // unknown format
  139. throw CFError(std::string("Unknown date time format: ") + specification);
  140. }
  141. }
  142. bool DateTime::operator==(const DateTime& other) const {
  143. return year == other.year &&
  144. month == other.month &&
  145. day == other.day &&
  146. seconds == other.seconds;
  147. }
  148. int DateTime::get_hour() const {
  149. return static_cast<int>(seconds)/SECONDS_PER_HOUR;
  150. }
  151. int DateTime::get_minute() const {
  152. return (static_cast<int>(seconds)%SECONDS_PER_HOUR)/SECONDS_PER_MINUTE;
  153. }
  154. double DateTime::get_second() const {
  155. return seconds - (get_hour()*SECONDS_PER_HOUR+get_minute()*SECONDS_PER_MINUTE);
  156. }
  157. double DateTime::get_seconds_after_midnight() const {
  158. return seconds;
  159. }
  160. void DateTime::add_time(double time, TimeUnit time_unit, CalendarType calendar_type,
  161. double resolution /* = 1.0 */) {
  162. if (time < 0) {
  163. throw CFError("Negative time offsets not supported");
  164. }
  165. if (resolution < 0) {
  166. throw CFError("Negative resolution sent to add_time");
  167. }
  168. if (calendar_type == STANDARD) {
  169. check_gregorian_year(year);
  170. }
  171. // convert time, which is in the unit time_unit, into two
  172. // parts: days_increment (unit days) and seconds_increment (unit seconds)
  173. // (so time is equal to days_increment days plus seconds_increment seconds)
  174. double days_increment;
  175. double seconds_increment;
  176. switch (time_unit) {
  177. case SECONDS:
  178. days_increment = time/SECONDS_PER_DAY;
  179. break;
  180. case MINUTES:
  181. days_increment = time/MINUTES_PER_DAY;
  182. break;
  183. case HOURS:
  184. days_increment = time/HOURS_PER_DAY;
  185. break;
  186. case DAYS:
  187. days_increment = time;
  188. break;
  189. default:
  190. // should never happen
  191. throw CFError("Unsupported time unit in DateTime::add_time");
  192. }
  193. // keep only integer part in days_increment, and multiply fractional
  194. // part by SECONDS_PER_DAY to get seconds_increment
  195. seconds_increment = modf(days_increment, &days_increment) * SECONDS_PER_DAY;
  196. add_days(static_cast<int>(days_increment), calendar_type);
  197. seconds += seconds_increment;
  198. // overflow?
  199. if (seconds >= SECONDS_PER_DAY) {
  200. seconds -= SECONDS_PER_DAY;
  201. add_days(1, calendar_type);
  202. }
  203. // rounding
  204. if (resolution != 0) {
  205. seconds = floor(seconds/resolution+0.5)*resolution;
  206. if (seconds >= SECONDS_PER_DAY) {
  207. seconds -= SECONDS_PER_DAY;
  208. add_days(1, calendar_type);
  209. }
  210. }
  211. }
  212. int DateTime::get_day_index(CalendarType calendar_type) const {
  213. // todo: optimize
  214. int result = 0;
  215. for (int i = 1; i < month; ++i) {
  216. result += days_in_month(year, i, calendar_type);
  217. }
  218. result += day - 1;
  219. return result;
  220. }
  221. void DateTime::skip_ahead_whole_years(int& day_increment, CalendarType calendar_type) {
  222. // todo: optimize
  223. int day_index = get_day_index(calendar_type);
  224. while (day_index + day_increment >= days_in_year(year, calendar_type)) {
  225. int days_added = days_in_year(year, calendar_type)-day_index;
  226. day_increment -= days_added;
  227. day = 1;
  228. month = 1;
  229. ++year;
  230. day_index = 0;
  231. }
  232. }
  233. void DateTime::skip_ahead_whole_months(int& day_increment, CalendarType calendar_type) {
  234. // assumes we're called just after skip_ahead_whole_years, so
  235. // day_increments isn't large enough to cause year to increase
  236. while (day + day_increment > days_in_month(year, month, calendar_type)) {
  237. int days_added = days_in_month(year, month, calendar_type) - day + 1;
  238. day_increment -= days_added;
  239. day = 1;
  240. ++month;
  241. }
  242. }
  243. void DateTime::add_days(int day_increment, CalendarType calendar_type) {
  244. // make sure we start with a valid date
  245. assert(valid_date(year, month, day, calendar_type));
  246. skip_ahead_whole_years(day_increment, calendar_type);
  247. skip_ahead_whole_months(day_increment, calendar_type);
  248. // day_increment should now be small enough not to cause month increase
  249. day += day_increment;
  250. // make sure we end with a valid date
  251. assert(valid_date(year, month, day, calendar_type));
  252. }
  253. std::string DateTime::to_string() const {
  254. char buff[256];
  255. sprintf(buff, "%d-%02d-%02d %02d:%02d:%05.2f",
  256. year, month, day, get_hour(), get_minute(), get_second());
  257. return std::string(buff);
  258. }
  259. TimeUnitSpecification::TimeUnitSpecification()
  260. : time_unit(SECONDS),
  261. reference_time(1970, 01, 01, 0, 0, 0) {
  262. }
  263. TimeUnitSpecification::TimeUnitSpecification(const char* specification) {
  264. char buff[256];
  265. sscanf(specification, "%s", buff);
  266. time_unit = parse_time_unit(buff);
  267. const char* psince = strstr(specification, "since");
  268. if (!psince) {
  269. throw CFError(std::string("Bad time unit string: ") + specification);
  270. }
  271. reference_time = DateTime(psince+strlen("since"));
  272. }
  273. DateTime TimeUnitSpecification::get_date_time(double time, CalendarType calendar,
  274. double resolution /* = 1.0 */) const {
  275. DateTime result = reference_time;
  276. result.add_time(time, time_unit, calendar, resolution);
  277. return result;
  278. }
  279. } // namespace CF
  280. } // namespace GuessNC
  281. #endif // HAVE_NETCDF