source: trunk/MagicSoft/Mars/mbase/MTime.cc@ 7366

Last change on this file since 7366 was 7366, checked in by tbretz, 19 years ago
*** empty log message ***
File size: 20.6 KB
Line 
1/* ======================================================================== *\
2!
3! *
4! * This file is part of MARS, the MAGIC Analysis and Reconstruction
5! * Software. It is distributed to you in the hope that it can be a useful
6! * and timesaving tool in analysing Data of imaging Cerenkov telescopes.
7! * It is distributed WITHOUT ANY WARRANTY.
8! *
9! * Permission to use, copy, modify and distribute this software and its
10! * documentation for any purpose is hereby granted without fee,
11! * provided that the above copyright notice appear in all copies and
12! * that both that copyright notice and this permission notice appear
13! * in supporting documentation. It is provided "as is" without express
14! * or implied warranty.
15! *
16!
17!
18! Author(s): Thomas Bretz 12/2000 <mailto:tbretz@astro.uni-wuerzburg.de>
19!
20! Copyright: MAGIC Software Development, 2000-2003
21!
22!
23\* ======================================================================== */
24
25/////////////////////////////////////////////////////////////////////////////
26//
27// MTime
28//
29// A generalized MARS time stamp.
30//
31//
32// We do not use floating point values here, because of several reasons:
33// - having the times stored in integers only is more accurate and
34// more reliable in comparison conditions
35// - storing only integers gives similar bit-pattern for similar times
36// which makes compression (eg gzip algorithm in TFile) more
37// successfull
38//
39// Note, that there are many conversion function converting the day time
40// into a readable string. Also a direct interface to SQL time strings
41// is available.
42//
43// If you are using MTime containers as axis lables in root histograms
44// use GetAxisTime(). Make sure that you use the correct TimeFormat
45// on your TAxis (see GetAxisTime())
46//
47//
48// WARNING: Be carefull changing this class. It is also used in the
49// MAGIC drive software cosy as VERY IMPORTANT stuff!
50//
51// Remarke: If you encounter strange behaviour, check the casting.
52// Note, that on Linux machines ULong_t and UInt_t is the same.
53//
54//
55// Version 1:
56// ----------
57// - first version
58//
59// Version 2:
60// ----------
61// - removed fTimeStamp[2]
62//
63// Version 3:
64// ----------
65// - removed fDurtaion - we may put it back when it is needed
66// - complete rewrite of the data members (old ones completely replaced)
67//
68/////////////////////////////////////////////////////////////////////////////
69#include "MTime.h"
70
71#include <iomanip>
72
73#ifndef __USE_XOPEN
74#define __USE_XOPEN // on some systems needed for strptime
75#endif
76
77#include <time.h> // struct tm
78#include <sys/time.h> // struct timeval
79
80#include <TTime.h>
81
82#include "MLog.h"
83#include "MLogManip.h"
84
85#include "MAstro.h"
86
87ClassImp(MTime);
88
89using namespace std;
90
91const UInt_t MTime::kHour = 3600000; // [ms] one hour
92const UInt_t MTime::kDay = MTime::kHour*24; // [ms] one day
93
94// --------------------------------------------------------------------------
95//
96// Constructor. Calls SetMjd(d) for d>0 in all other cases the time
97// is set to the current UTC time.
98//
99MTime::MTime(Double_t d)
100{
101 Init(0, 0);
102 if (d<=0)
103 Now();
104 else
105 SetMjd(d);
106}
107
108// --------------------------------------------------------------------------
109//
110// Return date as year(y), month(m), day(d)
111//
112void MTime::GetDate(UShort_t &y, Byte_t &m, Byte_t &d) const
113{
114 MAstro::Mjd2Ymd((Long_t)fTime<0?fMjd-1:fMjd, y, m, d);
115}
116
117// --------------------------------------------------------------------------
118//
119// Return date as year(y), month(m), day(d). If the time is afternoon
120// (>=13:00:00) the date of the next day is returned.
121//
122void MTime::GetDateOfSunrise(UShort_t &y, Byte_t &m, Byte_t &d) const
123{
124 MAstro::Mjd2Ymd(fMjd, y, m, d);
125}
126
127// --------------------------------------------------------------------------
128//
129// GetMoonPhase - calculate phase of moon as a fraction:
130// Returns -1 if calculation failed
131//
132// see MAstro::GetMoonPhase
133//
134Double_t MTime::GetMoonPhase() const
135{
136 return MAstro::GetMoonPhase(GetMjd());
137}
138
139// --------------------------------------------------------------------------
140//
141// Calculate the Period to which the time belongs to. The Period is defined
142// as the number of synodic months ellapsed since the first full moon
143// after Jan 1st 1980 (which was @ MJD=44240.37917)
144//
145// see MAstro::GetMoonPeriod
146//
147Double_t MTime::GetMoonPeriod() const
148{
149 return MAstro::GetMoonPeriod(GetMjd());
150}
151
152// --------------------------------------------------------------------------
153//
154// To get the moon period as defined for MAGIC observation we take the
155// nearest integer mjd, eg:
156// 53257.8 --> 53258
157// 53258.3 --> 53258
158// Which is the time between 13h and 12:59h of the following day. To
159// this day-period we assign the moon-period at midnight. To get
160// the MAGIC definition we now substract 284.
161//
162// For MAGIC observation period do eg:
163// GetMagicPeriod(53257.91042)
164// or
165// MTime t;
166// t.SetMjd(53257.91042);
167// GetMagicPeriod(t.GetMjd());
168// or
169// MTime t;
170// t.Set(2004, 1, 1, 12, 32, 11);
171// GetMagicPeriod(t.GetMjd());
172//
173//
174// see MAstro::GetMagicPeriod
175//
176Int_t MTime::GetMagicPeriod() const
177{
178 return MAstro::GetMagicPeriod(GetMjd());
179}
180
181
182// --------------------------------------------------------------------------
183//
184// Return the time in the range [0h, 24h) = [0h0m0.000s - 23h59m59.999s]
185//
186void MTime::GetTime(Byte_t &h, Byte_t &m, Byte_t &s, UShort_t &ms) const
187{
188 Long_t tm = GetTime24();
189 ms = tm%1000; // [ms]
190 tm /= 1000; // [s]
191 s = tm%60; // [s]
192 tm /= 60; // [m]
193 m = tm%60; // [m]
194 tm /= 60; // [h]
195 h = tm; // [h]
196}
197
198// --------------------------------------------------------------------------
199//
200// Return time as MJD (=JD-24000000.5)
201//
202Double_t MTime::GetMjd() const
203{
204 return fMjd+(Double_t)(fNanoSec/1e6+(Long_t)fTime)/kDay;
205}
206
207// --------------------------------------------------------------------------
208//
209// Return a time which is expressed in milliseconds since 01/01/1995 0:00h
210// This is compatible with root's definition used in gSystem->Now()
211// and TTime.
212// Note, gSystem->Now() returns local time, such that it may differ
213// from GetRootTime() (if you previously called MTime::Now())
214//
215TTime MTime::GetRootTime() const
216{
217 return (ULong_t)((GetMjd()-49718)*kDay);
218}
219
220// --------------------------------------------------------------------------
221//
222// Return a time which is expressed in seconds since 01/01/1995 0:00h
223// This is compatible with root's definition used in TAxis.
224// Note, a TAxis always displayes (automatically) given times in
225// local time (while here we return UTC) such, that you may encounter
226// strange offsets. You can get rid of this by calling:
227// TAxis::SetTimeFormat("[your-format] %F1995-01-01 00:00:00 GMT");
228//
229Double_t MTime::GetAxisTime() const
230{
231 return (GetMjd()-49718)*kDay/1000;
232}
233
234// --------------------------------------------------------------------------
235//
236// Set a time expressed in MJD, Time of Day (eg. 23:12.779h expressed
237// in milliseconds) and a nanosecond part.
238//
239Bool_t MTime::SetMjd(UInt_t mjd, ULong_t ms, UInt_t ns)
240{
241 // [d] mjd (eg. 52320)
242 // [ms] time (eg. 17h expressed in ms)
243 // [ns] time (ns part of time)
244
245 if (ms>kDay-1 || ns>999999)
246 return kFALSE;
247
248 const Bool_t am = ms<kHour*13; // day of sunrise?
249
250 fMjd = am ? mjd : mjd + 1;
251 fTime = (Long_t)(am ? ms : ms-kDay);
252 fNanoSec = ns;
253
254 return kTRUE;
255}
256
257// --------------------------------------------------------------------------
258//
259// Set MTime to given MJD (eg. 52080.0915449892)
260//
261void MTime::SetMjd(Double_t m)
262{
263 const UInt_t mjd = (UInt_t)TMath::Floor(m);
264 const Double_t frac = fmod(m, 1)*kDay; // [ms] Fraction of day
265 const UInt_t ns = (UInt_t)fmod(frac*1e6, 1000000);
266
267 SetMjd(mjd, (ULong_t)TMath::Floor(frac), ns);
268}
269
270// --------------------------------------------------------------------------
271//
272// Set MTime to given time and date
273//
274Bool_t MTime::Set(UShort_t y, Byte_t m, Byte_t d, Byte_t h, Byte_t min, Byte_t s, UShort_t ms, UInt_t ns)
275{
276 if (h>23 || min>59 || s>59 || ms>999 || ns>999999)
277 return kFALSE;
278
279 const Int_t mjd = MAstro::Ymd2Mjd(y, m, d);
280 if (mjd<0)
281 return kFALSE;
282
283 const ULong_t tm = ((((h*60+min)*60)+s)*1000)+ms;
284
285 return SetMjd(mjd, tm, ns);
286}
287
288// --------------------------------------------------------------------------
289//
290// Set MTime to time expressed in a 'struct timeval'
291//
292void MTime::Set(const struct timeval &tv)
293{
294 const UInt_t mjd = (UInt_t)TMath::Floor(1000.*tv.tv_sec/kDay) + 40587;
295
296 const Long_t tm = tv.tv_sec%(24*3600)*1000 + tv.tv_usec/1000;
297 const UInt_t ms = tv.tv_usec%1000;
298
299 SetMjd(mjd, tm, ms*1000);
300}
301
302// --------------------------------------------------------------------------
303//
304// Return contents as a TString of the form:
305// "dd.mm.yyyy hh:mm:ss.fff"
306//
307Bool_t MTime::SetString(const char *str)
308{
309 if (!str)
310 return kFALSE;
311
312 UInt_t y, mon, d, h, m, s, ms;
313 const Int_t n = sscanf(str, "%02u.%02u.%04u %02u:%02u:%02u.%03u",
314 &d, &mon, &y, &h, &m, &s, &ms);
315
316 return n==7 ? Set(y, mon, d, h, m, s, ms) : kFALSE;
317}
318
319// --------------------------------------------------------------------------
320//
321// Return contents as a TString of the form:
322// "yyyy-mm-dd hh:mm:ss"
323//
324Bool_t MTime::SetSqlDateTime(const char *str)
325{
326 if (!str)
327 return kFALSE;
328
329 UInt_t y, mon, d, h, m, s;
330 const Int_t n = sscanf(str, "%04u-%02u-%02u %02u:%02u:%02u",
331 &y, &mon, &d, &h, &m, &s);
332
333 return n==6 ? Set(y, mon, d, h, m, s) : kFALSE;
334}
335
336// --------------------------------------------------------------------------
337//
338// Return contents as a TString of the form:
339// "yyyymmddhhmmss"
340//
341Bool_t MTime::SetSqlTimeStamp(const char *str)
342{
343 if (!str)
344 return kFALSE;
345
346 UInt_t y, mon, d, h, m, s;
347 const Int_t n = sscanf(str, "%04u%02u%02u%02u%02u%02u",
348 &y, &mon, &d, &h, &m, &s);
349
350 return n==6 ? Set(y, mon, d, h, m, s) : kFALSE;
351}
352
353// --------------------------------------------------------------------------
354//
355// Set MTime to time expressed as in CT1 PreProc files
356//
357void MTime::SetCT1Time(UInt_t mjd, UInt_t t1, UInt_t t0)
358{
359 // int isecs_since_midday; // seconds passed since midday before sunset (JD of run start)
360 // int isecfrac_200ns; // fractional part of isecs_since_midday
361 // fTime->SetTime(isecfrac_200ns, isecs_since_midday);
362 fNanoSec = (200*t1)%1000000;
363 const ULong_t ms = (200*t1)/1000000 + t0+12*kHour;
364
365 fTime = (Long_t)(ms<13*kHour ? ms : ms-kDay);
366
367 fMjd = mjd+1;
368}
369
370// --------------------------------------------------------------------------
371//
372// Update the magic time. Make sure, that the MJD is set correctly.
373// It must be the MJD of the corresponding night. You can set it
374// by Set(2003, 12, 24);
375//
376// It is highly important, that the time correspoding to the night is
377// between 13:00:00.0 (day of dawning) and 12:59:59.999 (day of sunrise)
378//
379Bool_t MTime::UpdMagicTime(Byte_t h, Byte_t m, Byte_t s, UInt_t ns)
380{
381 if (h>23 || m>59 || s>59 || ns>999999999)
382 return kFALSE;
383
384 const ULong_t tm = ((((h*60+m)*60)+s)*1000)+ns/1000000;
385
386 fTime = (Long_t)(tm<kHour*13 ? tm : tm-kDay); // day of sunrise?
387 fNanoSec = ns%1000000;
388
389 return kTRUE;
390}
391
392// --------------------------------------------------------------------------
393//
394// Conversion from Universal Time to Greenwich mean sidereal time,
395// with rounding errors minimized.
396//
397// The result is the Greenwich Mean Sidereal Time (radians)
398//
399// There is no restriction on how the UT is apportioned between the
400// date and ut1 arguments. Either of the two arguments could, for
401// example, be zero and the entire date+time supplied in the other.
402// However, the routine is designed to deliver maximum accuracy when
403// the date argument is a whole number and the ut argument lies in
404// the range 0 to 1, or vice versa.
405//
406// The algorithm is based on the IAU 1982 expression (see page S15 of
407// the 1984 Astronomical Almanac). This is always described as giving
408// the GMST at 0 hours UT1. In fact, it gives the difference between
409// the GMST and the UT, the steady 4-minutes-per-day drawing-ahead of
410// ST with respect to UT. When whole days are ignored, the expression
411// happens to equal the GMST at 0 hours UT1 each day.
412//
413// In this routine, the entire UT1 (the sum of the two arguments date
414// and ut) is used directly as the argument for the standard formula.
415// The UT1 is then added, but omitting whole days to conserve accuracy.
416//
417// The extra numerical precision delivered by the present routine is
418// unlikely to be important in an absolute sense, but may be useful
419// when critically comparing algorithms and in applications where two
420// sidereal times close together are differenced.
421//
422Double_t MTime::GetGmst() const
423{
424 const Double_t ut = (Double_t)(fNanoSec/1e6+(Long_t)fTime)/kDay;
425
426 // Julian centuries since J2000.
427 const Double_t t = (ut -(51544.5-fMjd)) / 36525.0;
428
429 // GMST at this UT1
430 const Double_t r1 = 24110.54841+(8640184.812866+(0.093104-6.2e-6*t)*t)*t;
431 const Double_t r2 = 86400.0*ut;
432
433 const Double_t sum = (r1+r2)/(24*3600);
434
435 return fmod(sum, 1)*TMath::TwoPi();//+TMath::TwoPi();
436}
437
438// --------------------------------------------------------------------------
439//
440// Set the time to the current system time. The timezone is ignored.
441// If everything is set correctly you'll get UTC.
442//
443void MTime::Now()
444{
445#ifdef __LINUX__
446 struct timeval tv;
447 if (gettimeofday(&tv, NULL)<0)
448 Clear();
449 else
450 Set(tv);
451#else
452 Clear();
453#endif
454}
455
456// --------------------------------------------------------------------------
457//
458// Return contents as a TString of the form:
459// "dd.mm.yyyy hh:mm:ss.fff"
460//
461TString MTime::GetString() const
462{
463 UShort_t y, ms;
464 Byte_t mon, d, h, m, s;
465
466 GetDate(y, mon, d);
467 GetTime(h, m, s, ms);
468
469 return TString(Form("%02d.%02d.%04d %02d:%02d:%02d.%03d", d, mon, y, h, m, s, ms));
470}
471
472// --------------------------------------------------------------------------
473//
474// Return contents as a string format'd with strftime:
475// Here is a short summary of the most important formats. For more
476// information see the man page (or any other description) of
477// strftime...
478//
479// %a The abbreviated weekday name according to the current locale.
480// %A The full weekday name according to the current locale.
481// %b The abbreviated month name according to the current locale.
482// %B The full month name according to the current locale.
483// %c The preferred date and time representation for the current locale.
484// %d The day of the month as a decimal number (range 01 to 31).
485// %e Like %d, the day of the month as a decimal number,
486// but a leading zero is replaced by a space.
487// %H The hour as a decimal number using a 24-hour clock (range 00 to 23)
488// %k The hour (24-hour clock) as a decimal number (range 0 to 23);
489// single digits are preceded by a blank.
490// %m The month as a decimal number (range 01 to 12).
491// %M The minute as a decimal number (range 00 to 59).
492// %R The time in 24-hour notation (%H:%M). For a
493// version including the seconds, see %T below.
494// %S The second as a decimal number (range 00 to 61).
495// %T The time in 24-hour notation (%H:%M:%S).
496// %x The preferred date representation for the current
497// locale without the time.
498// %X The preferred time representation for the current
499// locale without the date.
500// %y The year as a decimal number without a century (range 00 to 99).
501// %Y The year as a decimal number including the century.
502// %+ The date and time in date(1) format.
503//
504// The default is: Tuesday 16.February 2004 12:17:22
505//
506// The maximum size of the return string is 128 (incl. NULL)
507//
508TString MTime::GetStringFmt(const char *fmt) const
509{
510 if (!fmt)
511 fmt = "%A %e.%B %Y %H:%M:%S";
512
513 UShort_t y, ms;
514 Byte_t mon, d, h, m, s;
515
516 GetDate(y, mon, d);
517 GetTime(h, m, s, ms);
518
519 struct tm time;
520 time.tm_sec = s;
521 time.tm_min = m;
522 time.tm_hour = h;
523 time.tm_mday = d;
524 time.tm_mon = mon-1;
525 time.tm_year = y-1900;
526 time.tm_isdst = 0;
527
528 // recalculate tm_yday and tm_wday
529 mktime(&time);
530
531 char ret[128];
532 return TString(strftime(ret, 127, fmt, &time) ? ret : "");
533}
534
535// --------------------------------------------------------------------------
536//
537// Set the time according to the format fmt.
538// Default is "%A %e.%B %Y %H:%M:%S"
539//
540// For more information see GetStringFmt
541//
542Bool_t MTime::SetStringFmt(const char *time, const char *fmt)
543{
544 if (!fmt)
545 fmt = "%A %e.%B %Y %H:%M:%S";
546
547 struct tm t;
548 memset(&t, 0, sizeof(struct tm));
549 strptime(time, fmt, &t);
550
551 return Set(t.tm_year+1900, t.tm_mon+1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec);
552}
553
554// --------------------------------------------------------------------------
555//
556// Return contents as a TString of the form:
557// "yyyy-mm-dd hh:mm:ss"
558//
559TString MTime::GetSqlDateTime() const
560{
561 return GetStringFmt("%Y-%m-%d %H:%M:%S");
562}
563
564// --------------------------------------------------------------------------
565//
566// Return contents as a TString of the form:
567// "yyyymmddhhmmss"
568//
569TString MTime::GetSqlTimeStamp() const
570{
571 return GetStringFmt("%Y%m%d%H%M%S");
572}
573
574// --------------------------------------------------------------------------
575//
576// Return contents as a TString of the form:
577// "yyyymmdd_hhmmss"
578//
579TString MTime::GetFileName() const
580{
581 return GetStringFmt("%Y%m%d_%H%M%S");
582}
583
584// --------------------------------------------------------------------------
585//
586// Print MTime as string
587//
588void MTime::Print(Option_t *) const
589{
590 UShort_t yea, ms;
591 Byte_t mon, day, h, m, s;
592
593 GetDate(yea, mon, day);
594 GetTime(h, m, s, ms);
595
596 *fLog << all << GetDescriptor() << ": ";
597 *fLog << GetString() << Form(" (+%dns)", fNanoSec) << endl;
598}
599
600istream &MTime::ReadBinary(istream &fin)
601{
602 UShort_t y;
603 Byte_t mon, d, h, m, s;
604
605 fin.read((char*)&y, 2);
606 fin.read((char*)&mon, 1);
607 fin.read((char*)&d, 1);
608 fin.read((char*)&h, 1);
609 fin.read((char*)&m, 1);
610 fin.read((char*)&s, 1); // Total=7
611
612 Set(y, mon, d, h, m, s, 0);
613
614 return fin;
615}
616
617void MTime::AddMilliSeconds(UInt_t ms)
618{
619 fTime += ms;
620
621 fTime += 11*kHour;
622 fMjd += (Long_t)fTime/kDay;
623 fTime = (Long_t)fTime%kDay;
624 fTime -= 11*kHour;
625}
626
627void MTime::Plus1ns()
628{
629 fNanoSec++;
630
631 if (fNanoSec<1000000)
632 return;
633
634 fNanoSec = 0;
635 fTime += 1;
636
637 if ((Long_t)fTime<(Long_t)kDay*13)
638 return;
639
640 fTime = 11*kDay;
641 fMjd++;
642}
643
644void MTime::Minus1ns()
645{
646 if (fNanoSec>0)
647 {
648 fNanoSec--;
649 return;
650 }
651
652 fTime -= 1;
653 fNanoSec = 999999;
654
655 if ((Long_t)fTime>=-(Long_t)kDay*11)
656 return;
657
658 fTime = 13*kDay-1;
659 fMjd--;
660}
661
662/*
663MTime MTime::operator-(const MTime &tm1)
664{
665 const MTime &tm0 = *this;
666
667 MTime t0 = tm0>tm1 ? tm0 : tm1;
668 const MTime &t1 = tm0>tm1 ? tm1 : tm0;
669
670 if (t0.fNanoSec<t1.fNanoSec)
671 {
672 t0.fNanoSec += 1000000;
673 t0.fTime -= 1;
674 }
675
676 t0.fNanoSec -= t1.fNanoSec;
677 t0.fTime -= t1.fTime;
678
679 if ((Long_t)t0.fTime<-(Long_t)kHour*11)
680 {
681 t0.fTime += kDay;
682 t0.fMjd--;
683 }
684
685 t0.fMjd -= t1.fMjd;
686
687 return t0;
688}
689
690void MTime::operator-=(const MTime &t)
691{
692 *this = *this-t;
693}
694
695MTime MTime::operator+(const MTime &t1)
696{
697 MTime t0 = *this;
698
699 t0.fNanoSec += t1.fNanoSec;
700
701 if (t0.fNanoSec>999999)
702 {
703 t0.fNanoSec -= 1000000;
704 t0.fTime += kDay;
705 }
706
707 t0.fTime += t1.fTime;
708
709 if ((Long_t)t0.fTime>=(Long_t)kHour*13)
710 {
711 t0.fTime -= kDay;
712 t0.fMjd++;
713 }
714
715 t0.fMjd += t1.fMjd;
716
717 return t0;
718}
719
720void MTime::operator+=(const MTime &t)
721{
722 *this = *this+t;
723}
724*/
725
726void MTime::SetMean(const MTime &t0, const MTime &t1)
727{
728 // This could be an operator+
729 *this = t0;
730
731 fNanoSec += t1.fNanoSec;
732
733 if (fNanoSec>999999)
734 {
735 fNanoSec -= 1000000;
736 fTime += kDay;
737 }
738
739 fTime += t1.fTime;
740
741 if ((Long_t)fTime>=(Long_t)kHour*13)
742 {
743 fTime -= kDay;
744 fMjd++;
745 }
746
747 fMjd += t1.fMjd;
748
749 // This could be an operator/
750 if ((Long_t)fTime<0)
751 {
752 fTime += kDay;
753 fMjd--;
754 }
755
756 Int_t reminder = fMjd%2;
757 fMjd /= 2;
758
759 fTime += reminder*kDay;
760 reminder = (Long_t)fTime%2;
761 fTime /= 2;
762
763 fNanoSec += reminder*1000000;
764 fNanoSec /= 2;
765
766 fTime += 11*kHour;
767 fMjd += (Long_t)fTime/kDay;
768 fTime = (Long_t)fTime%kDay;
769 fTime -= 11*kHour;
770}
771
772void MTime::SetMean(Double_t t0, Double_t t1)
773{
774 const Double_t mean = (t0+t1)*(500./kDay);
775 SetMjd(mean);
776}
777
778void MTime::AsciiRead(istream &fin)
779{
780 fin >> *this;
781}
782
783Bool_t MTime::AsciiWrite(ostream &out) const
784{
785 out << *this;
786 return out;
787}
Note: See TracBrowser for help on using the repository browser.