ewmscp  ..
OptionsChrono.cpp
Go to the documentation of this file.
1 #include "OptionsChrono.h"
2 #include <ctime>
3 #include <algorithm>
4 #include <iostream>
5 #include <sys/stat.h>
6 #include <sys/types.h>
7 #include <string.h>
8 #include <unistd.h>
9 
10 
11 namespace options {
12  namespace internal {
13 
14  std::chrono::duration<double> parseNumberAndUnit(std::stringstream& aStream, int* aMonths, int* aYears) {
15  class unitDescriptor {
16  public:
17  enum unitType {
18  kUnspecific = 0,
19  kWithEnlargingPrefix = 1 << 0,
20  kWithDiminishingPrefix = 1 << 1,
21  kWithPrefix = kWithEnlargingPrefix | kWithDiminishingPrefix,
22  kMonth = 1 << 2,
23  kYear = (1 << 3) | kWithEnlargingPrefix ,
24  kSecond = kWithDiminishingPrefix
25  };
26  const char *lUnitName;
27  double lFactor;
28  unitType lType;
29  unitDescriptor(decltype(lUnitName) aUnitName,
30  decltype(lFactor) aFactor,
31  decltype(lType) aType):
32  lUnitName(aUnitName), lFactor(aFactor), lType(aType) {
33  };
34  };
35 
36  static std::vector<unitDescriptor> unitDescriptors({
37  {"year", 31557500, unitDescriptor::kYear},
38  {"yr", 31557500, unitDescriptor::kYear},
39  {"a", 31557500, unitDescriptor::kYear},
40  {"month", 2629800, unitDescriptor::kMonth},
41  {"week", 3600 * 24 * 7, unitDescriptor::kUnspecific},
42  {"day", 3600 * 24, unitDescriptor::kUnspecific},
43  {"hour", 3600, unitDescriptor::kUnspecific},
44  {"minute", 60, unitDescriptor::kUnspecific},
45  {"min", 60, unitDescriptor::kUnspecific},
46  {"second", 1, unitDescriptor::kSecond},
47  {"sec", 1, unitDescriptor::kSecond},
48  {"s", 1, unitDescriptor::kSecond}
49  }
50  );
51 
52  class prefixDescriptor {
53  public:
54  const char *lName;
55  long long lFactor;
56  std::string::size_type lNameLength;
57  prefixDescriptor(decltype(lName) aName, decltype(lFactor) aFactor) :
58  lName(aName), lFactor(aFactor) {
59  lNameLength = strlen(aName);
60  }
61  };
62  static std::vector<prefixDescriptor> diminishingPrefixDescriptors ({
63  {"milli", 1000},
64  {"m", 1000},
65  {"micro", 1000000},
66  {"u", 1000000},
67  {"nano", 1000000000},
68  {"n", 1000000000}
69  }
70  );
71  static std::vector<prefixDescriptor> enlargingPrefixDescriptors ({
72  {"kilo", 1000},
73  {"k", 1000},
74  {"mega", 1000000},
75  {"m", 1000000},
76  {"giga", 1000000000},
77  {"g", 1000000000}
78  }
79  );
80 
81  double number;
82  aStream >> number;
83  if (aStream.eof()) {
84  return std::chrono::duration<double>::zero();
85  }
86  std::string unit;
87  aStream >> unit;
88  std::transform(unit.begin(), unit.end(), unit.begin(), ::tolower);
89  for (auto& unitDesc : unitDescriptors) {
90  auto location = unit.find(unitDesc.lUnitName);
91  if (location != std::string::npos) {
92  auto unitNameLength = strlen(unitDesc.lUnitName);
93  auto nExtraChars = unit.size() - location - unitNameLength;
94  if (nExtraChars > 0 && // next line is special condition to allow a plural-s
95  !(nExtraChars == 1 && unitNameLength > 2 && unit.back() == 's')) {
96  parser::fGetInstance()->fGetErrorStream() << "Garbage in '" << unit << "' after the unit '" << unitDesc.lUnitName << "'\n";
98  }
99  if (location != 0) {
100  if (!(unitDesc.lType & unitDescriptor::kWithPrefix)) {
101  parser::fGetInstance()->fGetErrorStream() << "Garbage in '" << unit << "' before the unit '" << unitDesc.lUnitName << "'\n";
103  }
104  bool prefixFound = false;
105  if (unitDesc.lType & unitDescriptor::kWithDiminishingPrefix) {
106  for (auto& prefix : diminishingPrefixDescriptors) {
107  if (unit.compare(0, location, prefix.lName) == 0) {
108  number /= prefix.lFactor;
109  prefixFound = true;
110  break;
111  }
112  }
113  }
114  if (unitDesc.lType & unitDescriptor::kWithEnlargingPrefix) {
115  for (auto& prefix : enlargingPrefixDescriptors) {
116  if (unit.compare(0, location, prefix.lName) == 0) {
117  number *= prefix.lFactor;
118  prefixFound = true;
119  break;
120  }
121  }
122  }
123  if (!prefixFound) {
124  parser::fGetInstance()->fGetErrorStream() << "No valid prefix found in '" << unit << "'\n";
126  }
127  }
128  if ((unitDesc.lType == unitDescriptor::kYear && aYears) ||
129  (unitDesc.lType == unitDescriptor::kMonth && aMonths)) {
130  if (unitDesc.lType == unitDescriptor::kYear) {
131  *aYears = number;
132  } else {
133  *aMonths = number;
134  }
135  return std::chrono::duration<double>::zero();
136  } else {
137  return std::chrono::duration<double>(unitDesc.lFactor * number);
138  }
139  }
140  }
141  parser::fGetInstance()->fGetErrorStream() << "Unrecognized time unit in '" << unit << "'\n";
143  return std::chrono::duration<double>::zero();
144  }
145 
146  bool fParseStreamToBrokenDownTime(std::istream &aStream, std::tm* aBrokenDownTime, double& aFractional, std::string& timezone) {
147  aStream >> aBrokenDownTime->tm_year;
148  if (aStream.fail() || aStream.get() != '/') {
149  return false;
150  }
151  aStream >> aBrokenDownTime->tm_mon;
152  if (aStream.fail() || aStream.get() != '/') {
153  return false;
154  }
155  aStream >> aBrokenDownTime->tm_mday;
156  if (aStream.fail()) {
157  return false;
158  }
159  aStream >> aBrokenDownTime->tm_hour;
160  if (!aStream.fail()) {
161  if (aStream.get() != ':') {
162  return false;
163  }
164  aStream >> aBrokenDownTime->tm_min;
165  if (aStream.fail()) {
166  return false;
167  }
168  if (aStream.peek() == ':') {
169  aStream.get();
170  aStream >> aFractional;
171  aBrokenDownTime->tm_sec = aFractional;
172  aFractional -= aBrokenDownTime->tm_sec;
173  if (aStream.fail()) {
174  return false;
175  }
176  }
177  }
178  aStream >> timezone;
179 
180  return true;
181  }
182 
183  std::chrono::system_clock::time_point fParseTimePointString(const std::string& aString) {
184  std::string::size_type pointStringStart = 0;
185  std::string::size_type pointStringLength = std::string::npos;
186  std::string::size_type offsetStringStart = std::string::npos;
187  std::string::size_type offsetStringLength = std::string::npos;
188  bool offsetIsNegative = false;
189  {
190  auto after = aString.find("after");
191  if (after == 0) { // we have no time point
192  offsetStringStart = 6; // the offset starts after after
193  pointStringStart = std::string::npos;
194  } else if (after != std::string::npos) { // "after" after the offset before the point
195  offsetStringStart = 0;
196  offsetStringLength = after - 1;
197  pointStringStart = after + 6;
198  }
199  }
200  {
201  auto before = aString.find("before");
202  if (before == 0) { // we have no time point
203  offsetStringStart = 6; // the offset starts after before
204  pointStringStart = std::string::npos;
205  offsetIsNegative = true;
206  } else if (before != std::string::npos) { // "before" after the offset before the point
207  offsetStringStart = 0;
208  offsetStringLength = before - 1;
209  pointStringStart = before + 7;
210  offsetIsNegative = true;
211  }
212  }
213 
214  std::chrono::system_clock::time_point timePoint;
215  enum dateBitType {
216  kNow = 1 << 0,
217  kToday = 1 << 1,
218  kTomorrow = 1 << 2,
219  kYesterday = 1 << 3,
220  kWeekday = 1 << 4,
221  kDay = kToday | kTomorrow | kYesterday | kWeekday,
222  kLast = 1 << 5,
223  kThis = 1 << 6,
224  kNoon = 1 << 7
225 
226  };
227  if (pointStringStart < aString.size()) {
228  auto pointString = aString.substr(pointStringStart, pointStringLength);
229  std::transform(pointString.begin(), pointString.end(), pointString.begin(), ::tolower);
230 
231  typename std::underlying_type<dateBitType>::type dateBits = 0;
232  int weekDay = 0;
233  if (pointString.find("now") != std::string::npos) {
234  dateBits |= kNow;
235  } else if (pointString.find("today") != std::string::npos) {
236  dateBits |= kToday;
237  } else if (pointString.find("yesterday") != std::string::npos) {
238  dateBits |= kYesterday;
239  } else if (pointString.find("tomorrow") != std::string::npos) {
240  dateBits |= kTomorrow;
241  } else if (pointString.find("sun") != std::string::npos) {
242  dateBits |= kWeekday;
243  weekDay = 0;
244  } else if (pointString.find("mon") != std::string::npos) {
245  dateBits |= kWeekday;
246  weekDay = 1;
247  } else if (pointString.find("tue") != std::string::npos) {
248  dateBits |= kWeekday;
249  weekDay = 2;
250  } else if (pointString.find("wed") != std::string::npos) {
251  dateBits |= kWeekday;
252  weekDay = 3;
253  } else if (pointString.find("thu") != std::string::npos) {
254  dateBits |= kWeekday;
255  weekDay = 4;
256  } else if (pointString.find("fri") != std::string::npos) {
257  dateBits |= kWeekday;
258  weekDay = 5;
259  } else if (pointString.find("sat") != std::string::npos) {
260  dateBits |= kWeekday;
261  weekDay = 6;
262  }
263  if (pointString.find("noon") != std::string::npos) {
264  dateBits |= kNoon;
265  }
266  if (pointString.find("last") != std::string::npos) {
267  dateBits |= kLast;
268  }
269  if (pointString.find("this") != std::string::npos) {
270  dateBits |= kThis;
271  }
272 
273  if (dateBits != 0) {
274  timePoint = std::chrono::system_clock::now();
275  if (dateBits & kDay) {
276  auto coarse_time = std::chrono::system_clock::to_time_t(timePoint);
277  auto broken_down_time = std::localtime(&coarse_time);
278 
279  broken_down_time->tm_sec = 0;
280  broken_down_time->tm_min = 0;
281  broken_down_time->tm_hour = (dateBits & kNoon) ? 12 : 0;
282  if (dateBits & kYesterday) {
283  broken_down_time->tm_mday--;
284  } else if (dateBits & kTomorrow) {
285  broken_down_time->tm_mday++;
286  } else if (dateBits & kWeekday) {
287  auto dayOffset = weekDay - broken_down_time->tm_wday;
288  if (dateBits & kLast) {
289  if (dayOffset >= 0) {
290  dayOffset -= 7;
291  }
292  } else if (dateBits & kThis) {
293  // no change to the Offset
294  } else { // we imply the next day of that name
295  if (dayOffset <= 0) {
296  dayOffset += 7;
297  }
298  }
299  broken_down_time->tm_mday += dayOffset;
300  }
301 
302  timePoint = std::chrono::system_clock::from_time_t(std::mktime(broken_down_time));
303  }
304  } else { // no date bits found, we have a direct specification
305  if (pointString[0] == '@') { // as for date(1) this is seconds since 1970
306  auto seconds = std::stod(pointString.substr(1));
307  timePoint = std::chrono::system_clock::from_time_t(0) +
308  std::chrono::duration_cast<std::chrono::system_clock::duration>(std::chrono::duration<double>(seconds));
309  } else { // try direct spec like yyy/mm/dd [HH:MM:SS]
310  std::tm broken_down_time;
311  memset(&broken_down_time, 0, sizeof(broken_down_time));
312  double fractionalPart;
313  std::stringstream buf(aString.substr(pointStringStart, pointStringLength));
314  std::string timezone;
315  if (internal::fParseStreamToBrokenDownTime(buf, &broken_down_time, fractionalPart, timezone)) {
316  broken_down_time.tm_year -= 1900;
317  broken_down_time.tm_mon--;
318  broken_down_time.tm_isdst = -1;
319  #ifdef TZFILE_PATH
320  auto oldTZ = getenv("TZ");
321  std::string oldTZstring;
322  if (oldTZ) {
323  oldTZstring = oldTZ;
324  }
325  if (timezone.length() > 2) {
326  std::string tzfilename(TZFILE_PATH);
327  tzfilename += timezone;
328  struct stat s;
329  if (stat(tzfilename.c_str(), &s) < 0) {
330  parser::fGetInstance()->fGetErrorStream() << "can't find timezone file '" << tzfilename << "'\n";
332  }
333  timezone = ":" + timezone;
334  setenv("TZ", timezone.c_str(), true);
335  tzset();
336  }
337  #endif
338  timePoint = std::chrono::system_clock::from_time_t(std::mktime(&broken_down_time));
339  timePoint += std::chrono::duration_cast<std::chrono::system_clock::duration>(std::chrono::duration<double>(fractionalPart));
340  #ifdef TZFILE_PATH
341  if (timezone.length() > 2) {
342  if (oldTZ) {
343  setenv("TZ", oldTZstring.c_str(), true);
344  } else {
345  unsetenv("TZ");
346  }
347  }
348  #endif
349  } else {
350  parser::fGetInstance()->fGetErrorStream() << "Unrecognized time in '" << pointString << "'\n";
352  }
353  }
354  }
355  } else {
356  timePoint = std::chrono::system_clock::now();
357  }
358 
359  if (offsetStringStart < aString.size()) {
360  std::chrono::system_clock::duration offset;
361  auto offsetString = aString.substr(offsetStringStart, offsetStringLength);
362  std::transform(offsetString.begin(), offsetString.end(), offsetString.begin(), ::tolower);
363 
364  int months = 0;
365  int years = 0;
366  internal::parseDurationString(offset, offsetString, &months, &years);
367 
368  if (offsetIsNegative) {
369  timePoint -= offset;
370  } else {
371  timePoint += offset;
372  }
373  if (months != 0 || years != 0) {
374  auto coarse_time = std::chrono::system_clock::to_time_t(timePoint);
375  auto fractionalPart = timePoint - std::chrono::system_clock::from_time_t(coarse_time);
376  auto broken_down_time = std::localtime(&coarse_time);
377  if (offsetIsNegative) {
378  broken_down_time->tm_mon -= months;
379  broken_down_time->tm_year -= years;
380  } else {
381  broken_down_time->tm_mon += months;
382  broken_down_time->tm_year += years;
383  }
384  timePoint = std::chrono::system_clock::from_time_t(std::mktime(broken_down_time));
385  timePoint += fractionalPart;
386  }
387  }
388  return timePoint;
389  }
390  } // end of namespace internal
391 
392 
394  auto flags(aStream.flags());
395  aStream << std::fixed;
396  aStream << std::chrono::duration<double>(aValue.time_since_epoch()).count();
397  aStream.flags(flags);
398  }
399  single<std::chrono::system_clock::time_point>::single(char aShortName, const std::string& aLongName, const std::string& aExplanation, valueType aDefault, const std::vector<valueType>& aRange, valuePrinterType aValuePrinter):
400  internal::typed_base<std::chrono::system_clock::time_point>(aShortName, aLongName, aExplanation, 1),
401  valuePrinter(aValuePrinter) {
402  *static_cast<valueType*>(this) = aDefault;
403  if (!aRange.empty()) {
404  fAddToRange(aRange);
405  }
406  }
407  single<std::chrono::system_clock::time_point>::single(char aShortName, const std::string& aLongName, const std::string& aExplanation, const std::string& aDefault, const std::vector<std::string>& aRange, valuePrinterType aValuePrinter):
408  internal::typed_base<std::chrono::system_clock::time_point>(aShortName, aLongName, aExplanation, 1),
409  valuePrinter(aValuePrinter) {
410  try {
411  *static_cast<valueType*>(this) = internal::fParseTimePointString(aDefault);
412  } catch (const std::runtime_error& e) {
413  throw internal::optionError(this, e.what());
414  }
415  lOriginalString = aDefault;
416  if (!aRange.empty()) {
417  fAddToRange(aRange);
418  }
419  }
420 
422  std::getline(aStream, lOriginalString);
423  try {
424  *static_cast<valueType*>(this) = internal::fParseTimePointString(lOriginalString);
425  } catch (const std::runtime_error& e) {
426  throw internal::optionError(this, e.what());
427  }
428  }
429 
431  if (! lRange.empty()) {
432  aStream << "# allowed range is";
433  if (lRange.size() == 2) {
434  aStream << " [";
435  lValuePrinter(aStream, lRange[0]);
436  aStream << ", ";
437  lValuePrinter(aStream, lRange[1]);
438  aStream << "]\n";
439  } else {
440  for (auto& rangeElement : lRange) {
441  lValuePrinter(aStream, rangeElement);
442  aStream << "\n";
443  }
444  aStream << "\n";
445  }
446  }
447  }
448 
450  lValuePrinter(aStream, *this);
451  }
453  using escapedIO::operator>>;
454  aStream >> lOriginalString;
455  try {
456  *static_cast<valueType*>(this) = internal::fParseTimePointString(lOriginalString);
457  } catch (const std::runtime_error& e) {
458  throw internal::optionError(this, e.what());
459  }
460  fSetSource(aSource);
461  }
462 
463 
464  namespace escapedIO {
465  std::ostream& operator<<(std::ostream& aStream, const std::chrono::system_clock::time_point& aTime) {
467  return aStream;
468  }
469  std::istream& operator>>(std::istream& aStream, std::chrono::system_clock::time_point& aTime) {
470  std::string buf;
471  aStream >> buf;
472  if (!aStream.fail()) {
473  aTime = internal::fParseTimePointString(buf);
474  }
475  return aStream;
476  }
477 
478  } // end of namespace escapedIO
479 
480 } // end of namespace options
options::internal::parseNumberAndUnit
std::chrono::duration< double > parseNumberAndUnit(std::stringstream &aStream, int *aMonths=nullptr, int *aYears=nullptr)
Definition: OptionsChrono.cpp:14
options::single
generic option class with any type that can be used with std::istream and std::ostream
Definition: Options.h:533
options::internal::typed_base< T >::fAddToRange
virtual void fAddToRange(rangeValueType aValue)
add a value to the range of allowed values
Definition: Options.h:458
options
Definition: Options.h:33
options::internal::fParseTimePointString
std::chrono::system_clock::time_point fParseTimePointString(const std::string &aString)
Definition: OptionsChrono.cpp:183
options::single::fSetMe
void fSetMe(std::istream &aStream, const internal::sourceItem &aSource) override
function to set the value from a string, remembering the source
Definition: Options.h:578
options::internal::fParseStreamToBrokenDownTime
bool fParseStreamToBrokenDownTime(std::istream &aStream, std::tm *aBrokenDownTime, double &aFractional, std::string &timezone)
Definition: OptionsChrono.cpp:146
options::escapedIO::operator>>
std::istream & operator>>(std::istream &aStream, const char *&aCstring)
Definition: Options.cpp:435
OptionsChrono.h
options::valuePrinter
template interface class for options that provide a value printer
Definition: Options.h:174
options::escapedIO::operator<<
std::ostream & operator<<(std::ostream &aStream, const std::string &aString)
Definition: Options.cpp:447
options::single::fWriteValue
void fWriteValue(std::ostream &aStream) const override
write textual representation of value to a std::ostream
Definition: Options.h:569
options::single::single
single(char aShortName, const std::string &aLongName, const std::string &aExplanation, T aDefault, const std::vector< T > &aRange={})
construct an object of single<T>
Definition: Options.h:546
statCollector::timePoint
std::chrono::system_clock::time_point timePoint(const struct timespec &spec)
Definition: statCollector.h:115
options::internal::optionError
Definition: Options.h:279
options::internal::parseDurationString
void parseDurationString(std::chrono::duration< Rep, Period > &aDuration, const std::string &aString, int *aMonths=nullptr, int *aYears=nullptr)
parse a string into a std::chrono::duration, if given set the years and months separately
Definition: OptionsChrono.h:22
options::parser::fGetErrorStream
std::ostream & fGetErrorStream() const
Definition: Options.cpp:121
options::internal::typed_base< T >::fWriteRange
void fWriteRange(std::ostream &aStream) const override
Definition: Options.h:491
options::internal::sourceItem
class to remember from which line (or item) of a file/line an option was set from
Definition: Options.h:53
options::parser::fGetInstance
static parser * fGetInstance()
get the only allwed instance of the option parser.
Definition: Options.cpp:144
options::parser::fComplainAndLeave
virtual void fComplainAndLeave(bool aWithHelp=true)
print help (if required) and exit.
Definition: Options.cpp:365
options::single< std::chrono::system_clock::time_point >::valueType
rangeValueType valueType
Definition: OptionsChrono.h:74
options::single::fAddDefaultFromStream
void fAddDefaultFromStream(std::istream &aStream) override
special for use in the shellScriptOptionParser
Definition: Options.h:564
options::base::fSetSource
virtual void fSetSource(const internal::sourceItem &aSource)
remember the source that provided the value, e.g. commandline or a config file
Definition: Options.cpp:561
options::internal::typed_base< T >::lRange
std::multiset< rangeValueType > lRange
Definition: Options.h:451