auger-convert-time.cc
Go to the documentation of this file.
1 
6 // C++
7 #include <cstring> // strrchr()
8 #include <iostream>
9 #include <iomanip>
10 #include <sstream>
11 
12 // C
13 #include <unistd.h> // getopt
14 
15 // Auger Offline
16 #include <utl/AugerUnits.h>
17 #include <utl/TimeStamp.h>
18 #include <utl/TimeInterval.h>
19 #include <utl/UTCDateTime.h>
20 #include <utl/MoonCycle.h>
21 #include <utl/LeapSeconds.h>
22 #include <utl/ModifiedJulianDate.h>
23 #include <utl/AugerException.h>
24 #include <utl/ErrorLogger.h>
25 
26 // Terminal escape to underline text
27 #define ul(text) << "\033[4m" << #text << "\033[0m" <<
28 
29 class Parser {
30  public:
31  // Possible input and output formats
32  enum Format {
39  };
40 
41  Parser() :
42  fInputFormat(eNone),
44  fLeapSeconds( utl::LeapSeconds::GetInstance() )
45  {}
46 
47  void
48  Dump()
49  const
50  {
52  }
53 
54  // Clear error states and seek back to start of stream so we can try reading it again
55  void
56  ResetStream(std::istringstream& stream)
57  const
58  {
59  stream.clear();
60  stream.seekg(0);
61  }
62 
63  // Perform typical validation of the input stream
64  bool
65  ValidateStream(std::istringstream& stream, const std::string& description)
66  const
67  {
68  // Check if the read failed
69  if ( stream.fail() ) {
70  ResetStream(stream);
71  std::string what;
72  stream >> what;
73  ERROR("Could not parse " + description + " \"" + what + "\"");
74  return false;
75  }
76 
77  // Check for additional characters
78  std::string what;
79  stream >> what;
80  if ( !what.empty() ) {
81  ERROR("Unexpected characters in " + description + " \"" + what + "\"");
82  return false;
83  }
84 
85  return true;
86  }
87 
88  /* Offline provides no mechanism to convert lunation to GPS second, however by studying the
89  source code in MoonCycle.cc, we can infer the method to achieve this reverse
90  conversion. However, Offline uses Unix time (via MJD) to calculate the moon cycle, so
91  leap seconds are lost. This means that converting UTC to moon cycle and *back* to UTC will
92  give a different answer by the number of leap seconds since 2004 */
94  MoonCycleToGPS(const double& lunation)
95  const
96  {
97  /* Assume input is a full moon lunation in the Jan 2004 epoch and convert to a new moon
98  lunation in the Jan 2000 epoch */
99  const double altLunation(lunation + 0.5 + 49.0);
100 
101  // Calculate the interval of time between this lunation and the start of the Jan 2000 epoch
102  // From MoonCycle.cc:15, which isn't in the .h so it isn't in the accessible utl namespace!
103  const double kMeanSynodicMonth(29.530589);
104  const utl::TimeInterval interval(altLunation*kMeanSynodicMonth*utl::day);
105 
106  /* The mean start of the Jan 2000 epoch, i.e., new moon lunation 0.0 in the Jan 2000 epoch.
107  This date is given in the source code in MJD */
108  const utl::UTCDateTime epoch(2000, 1, 6, 14, 20, 30);
109 
110  // Important: round off the nanosecond because we don't purport that much precision
112  result.SetGPSTime(result.GetGPSSecond() + (result.GetGPSNanoSecond() > 500e6 ? 1 : 0), 0);
113  return result;
114  }
115 
116  void
118  {
119  fInputFormat = expected;
120  }
121 
122  void
124  {
125  fOutputFormat = expected;
126  }
127 
128  bool
129  ReadStrToTime(std::string& str)
130  {
131  std::istringstream inputStrm(str);
132 
133  if (fInputFormat == eMoon) {
134  // Extract the input
135  double moonCycle(-1.0);
136  inputStrm >> moonCycle;
137 
138  if ( !ValidateStream(inputStrm, "moon cycle") )
139  return false;
140 
141  fTime = MoonCycleToGPS(moonCycle);
142  }
143  else if (fInputFormat == eUnix) {
144  time_t unixSec(0);
145  inputStrm >> unixSec;
146 
147  if ( !ValidateStream(inputStrm, "Unix timestamp") )
148  return false;
149 
150  // Let Offline handle the error checking
151  try {
152  unsigned long GPSsec(0);
153  fLeapSeconds.ConvertUnixToGPS(unixSec, GPSsec);
154  fTime.SetGPSTime(GPSsec);
155  }
156  catch (utl::OutOfBoundException&) {
157  // Offline already prints the exception
158  return false;
159  }
160  }
161 
162  // Try to parse as UTC
163  if (fInputFormat == eNone || fInputFormat == eUTC) {
164  // This parsing logic is adapted from UTCDate::Parse() in utl/UTCDate.cc
165  int year(0);
166  char dash1('?');
167  int month(1);
168  char dash2('?');
169  int day(1);
170  char tee('?');
171  int hour(0);
172  char colon1('?');
173  int minute(0);
174  char colon2('?');
175  int second(0);
176  std::string what;
177  inputStrm >> year >> dash1 >> month >> dash2 >> day >> tee >> hour >> colon1 >> minute
178  >> colon2 >> second >> what;
179 
180  if (!what.empty() && what != "Z") {
181  // We let a 'Z' through because that's the UTC time zone specifier
182  ERROR("Unexpected characters in UTC timestamp \"" + what + "\"");
183  return false;
184  }
185 
186  // If we don't know what we're expecting yet, decide
187  if (fInputFormat == eNone) {
188  if (dash1 != '-') {
189  // Didn't start as UTC, so try the input as GPS second instead
190  fInputFormat = eGPS;
191  ResetStream(inputStrm);
192  }
193  else {
194  // Try and proceed with interpreting as UTC
195  fInputFormat = eUTC;
196  }
197  }
198 
199  // By this step, we could be checking any of the timestamps on stdin
200  if (fInputFormat == eUTC) {
201  if (dash1 == '?' || (dash1 == '-' && dash2 == '?')) {
202  ResetStream(inputStrm);
203  std::string what;
204  inputStrm >> what;
205  ERROR("UTC timestamp \"" + what + "\" not given to sufficient precision (at least"
206  " to the day)");
207  return false;
208  }
209  if (dash1 != '-' || dash2 != '-' || (tee != '?' && tee != 'T') || (colon1 != '?'
210  && colon1 != ':') || (colon2 != '?' && colon2 != ':')) {
211  ResetStream(inputStrm);
212  std::string what;
213  inputStrm >> what;
214  ERROR("Could not parse UTC timestamp \"" + what + "\"");
215  return false;
216  }
217 
218  // Let Offline handle the error checking
219  try {
220  fTime = utl::UTCDateTime(year, month, day, hour, minute, second).GetTimeStamp();
221  }
222  catch (utl::OutOfBoundException& ex) {
223  ERROR( ex.GetMessage() );
224  return false;
225  }
226  }
227  }
228 
229  // We've worked out the format by now
230  if (fInputFormat == eGPS) {
231  // Use a signed long because that's what utl::TimeStamp::SetNormalized() ultimately gets
232  long GPSsec(0);
233  inputStrm >> GPSsec;
234 
235  if ( !ValidateStream(inputStrm, "GPS second timestamp") )
236  return false;
237 
238  // Let Offline handle the error checking
239  try {
240  fTime.SetGPSTime(GPSsec);
241  }
242  catch (utl::OutOfBoundException&) {
243  // Offline already prints the exception
244  return false;
245  }
246  }
247 
248  // At this stage, fTime is guaranteed to be set
249 
250  // Decide the output if needed
251  if (fOutputFormat == eNone) {
252  if (fInputFormat == eUTC)
254  else
256  }
257 
258  return true;
259  }
260 
261  void
262  PrintTime()
263  const
264  {
265  // Produce the desired output
266  switch (fOutputFormat) {
267  case eGPS: {
268  std::cout << fTime.GetGPSSecond() << std::endl;
269  break;
270  }
271  case eUTC: {
272  // The XML format is ISO 8601 UTC by Auger convention
273  std::cout << utl::UTCDateTime(fTime).GetInXMLFormat() << std::endl;
274  break;
275  }
276  case eMoon: {
277  /* 7 decimal places gives us precision to the second, which is all we guarantee */
278  std::cout << std::fixed << std::setprecision(7)
279  << utl::MoonCycle(fTime).GetLunation() << std::endl;
280  break;
281  }
282  case eUnix: {
283  time_t unixSec;
285  std::cout << unixSec << std::endl;
286  break;
287  }
288  case eMJD: {
289  // The standard for MJD seems to be 5 decimal places
290  std::cout << std::fixed << std::setprecision(5)
291  << utl::ModifiedJulianDate(fTime) << std::endl;
292  break;
293  }
294  default:
295  break;
296  }
297  }
298 
299  private:
300  Format fInputFormat;
304 };
305 
306 int
307 help(char*& execPath, const int& exitCode)
308 {
309  // Find the basename of the exec path
310  char* execBase( strrchr(execPath, '/') );
311  if (execBase == 0)
312  execBase = execPath;
313  else
314  execBase += 1;
315 
316  // Keep printed lines to 80 chars or fewer
317  std::cout << "Usage: " << execBase << " [" ul(OPTION) "]... [" ul(TIMESTAMP) "]\n"
318  << "Unless the interpretation of " ul(TIMESTAMP) " is made explicit with an option, it is\n"
319  << "assumed to be either a GPS second timestamp or a UTC timestamp in ISO 8601\n"
320  << "format (eg, YYYY-MM-DDTHH:MM:SSZ) given to at least the day.\n"
321  << "Unless the desired output format is chosen with an option, it will be GPS\n"
322  << "second if the input was UTC, and UTC in all other cases.\n"
323  << "\n"
324  << "Options\n"
325  << "-------\n"
326  << " -d Dump the Offline leap second table and exit, ignoring " ul(TIMESTAMP) ".\n"
327  << " -M Interpret " ul(TIMESTAMP) " as a full moon cycle in the January 2004 epoch (ie,\n"
328  << " full moon at 0.0, new moon at 0.5, and so on). Note that moon cycle skips\n"
329  << " leap seconds.\n"
330  << " -m Print output as full moon cycle in the January 2004 epoch.\n"
331  << " -j Print output as Modified Julian Date (days since 1858-11-17 UTC).\n"
332  << " -U Interpret " ul(TIMESTAMP) " as a Unix timestamp (seconds since 1970-01-01 UTC).\n"
333  << " Note that Unix time skips leap seconds.\n"
334  << " -u Print output as Unix timestamp.\n"
335  << "\n"
336  << "With no " ul(TIMESTAMP) " parameter, timestamps will be read from stdin. If multiple\n"
337  << "times are provided via stdin, then they must all be of the same format (eg,\n"
338  << "they must all be GPS second timestamps, or all UTC timestamps, etc).\n";
339  return exitCode;
340 }
341 
342 int
343 main(int argc, char* argv[])
344 {
345  Parser parse;
346 
347  // Read options - documentation at `man 3 getopt`
348  char opt;
349  while ( (opt = getopt(argc, argv, ":dhMmjUu")) != -1 ) {
350  switch (opt) {
351  case 'd': parse.Dump(); return 0;
352  case 'h': return help(argv[0], 0);
353  case 'M': parse.SetInputFormat(Parser::eMoon); break;
354  case 'm': parse.SetOutputFormat(Parser::eMoon); break;
355  case 'j': parse.SetOutputFormat(Parser::eMJD); break;
356  case 'U': parse.SetInputFormat(Parser::eUnix); break;
357  case 'u': parse.SetOutputFormat(Parser::eUnix); break;
358  case '?': ERROR(std::string("Unrecognised option -") + char(optopt)); return 2;
359  case ':': ERROR(std::string("Expected an argument for option -") + char(optopt)); return 2;
360  }
361  }
362 
363  // Check if a parameter was given after the options
364  bool readParam;
365  if (optind == argc)
366  readParam = false; // No parameters left, so read stdin
367  else if (optind == argc - 1)
368  readParam = true; // Exactly one parameter to use
369  else {
370  ERROR("Too many input parameters. To convert multiple timestamps you must "
371  "pass them over stdin (ie, pipe them into this program)");
372  return 2;
373  }
374 
375  // Start the run loop
376  bool hadErrors(false);
377  for (std::string token; readParam || std::cin >> token; ) {
378  // Not reading from stdin, so use remaining parameter as our token
379  if (readParam)
380  token = argv[argc - 1];
381 
382  if ( parse.ReadStrToTime(token) )
383  parse.PrintTime();
384  else {
385  hadErrors = true;
386  break;
387  }
388 
389  // Stop if we are only reading the one parameter
390  if (readParam)
391  break;
392  }
393 
394  if (hadErrors)
395  return 1;
396  else
397  return 0;
398 }
double ModifiedJulianDate(const time_t unixSecond)
void SetInputFormat(Format expected)
#define ul(text)
A TimeStamp holds GPS second and nanosecond for some event.
Definition: TimeStamp.h:110
Exception for reporting variable out of valid range.
void SetGPSTime(const unsigned long sec, const double nsec=0)
Set GPS second and (optionally) nanosecond.
Definition: TimeStamp.h:120
int main(int argc, char *argv[])
Definition: DBSync.cc:58
const double second
Definition: GalacticUnits.h:32
void SetOutputFormat(Format expected)
bool ReadStrToTime(std::string &str)
bool ConvertGPSToUnix(const unsigned long gpsSecond, time_t &unixSecond) const
returns true if the GPS second is an UTC leap second
Definition: LeapSeconds.cc:11
utl::TimeStamp fTime
double GetLunation(const LunationType type=eFullMoon, const LunationEpoch epoch=eJan2004) const
Definition: MoonCycle.cc:15
constexpr double minute
Definition: AugerUnits.h:149
constexpr double hour
Definition: AugerUnits.h:150
Format fOutputFormat
A TimeInterval is used to represent time elapsed between two events.
Definition: TimeInterval.h:43
utl::LeapSeconds & fLeapSeconds
unsigned long GetGPSSecond() const
GPS second.
Definition: TimeStamp.h:124
void Dump() const
Definition: LeapSeconds.cc:90
const utl::UTCDateTime epoch(2000, 1, 6, 14, 20, 30)
void help(char *argv[])
Definition: split_ad.cc:12
void Dump(const FEvent &fevent)
const double kMeanSynodicMonth(29.530589)
std::string GetInXMLFormat() const
Definition: UTCDateTime.cc:94
#define ERROR(message)
Macro for logging error messages.
Definition: ErrorLogger.h:165
TimeStamp GetTimeStamp() const
Definition: UTCDateTime.cc:115
constexpr double day
Definition: AugerUnits.h:151
const std::string & GetMessage() const
Retrieve the message from the exception.
void ConvertUnixToGPS(const time_t unixSecond, unsigned long &gpsSecond) const
Definition: LeapSeconds.cc:25
const double year
Definition: GalacticUnits.h:22
const utl::TimeInterval interval(altLunation *kMeanSynodicMonth *utl::day)

, generated on Tue Sep 26 2023.