;+
;NAME:
; dsc_time_absolute__define
;
;PURPOSE:
; Defines an object to represent an absolute amount of time, not in reference
; to any date.  e.g., "10 minutes."  Used to enable time amount conversion and 
; string representations.
; 
; Designates all times as a combination of days(d), hours(h), minutes(m),
; seconds(s), and milliseconds(ms) and can be both positive and negative.  
; The time representation is always kept in a reduced form.  So, for example, 
; you can define a time of 1 hour and 65 minutes
;   IDL> t1 = dsc_time_absolute('1h65m')   
; In this case it will be stored as 2 hours and 5 minutes.
;   IDL> print,t1
;   02h 05m
; 
; 
;CALLING SEQUENCE:
; abstime = Obj_New("DSC_TIME_ABSOLUTE")
; abstime = Obj_New("DSC_TIME_ABSOLUTE",h=1,mn=30)
; abstime = Obj_New("DSC_TIME_ABSOLUTE",'1h30m')
; abstime = dsc_time_absolute()
; abstime = dsc_time_absolute(h=1,mn=30)
; abstime = dsc_time_absolute('1h30m')
;
; 
;INPUT:
; TIMESTRING (Optional) If this argument is supplied, all other keywords are ignored.
;             A string representing the desired time amount in the format: '#d#h#m#s#ms'
;             with d,h,m,s,ms signifying respectively days,hours,minutes,seconds, and milliseconds.
;               You may leave out unit id strings, but not repeat them:
;                  OK     '3h2m'
;                  NOT OK '3h45m13m'
;               Numbers must all be positive integers, with the execption of the leading negative.
;                  OK     '3d4h23m16s400ms'
;                  OK '-23h'
;                  NOT OK '15h-4m'
;                  NOT OK '15.4m'
;                  
;KEYWORDS (Optional):
; NEG:    Set this to negate the total time value set by the other keywords
; D=:     Days
; H=:     Hours
; MN=:    Minutes
; S=:     Seconds
; MS=:    Milliseconds
;
;OUTPUT:
; dsc_time_absolute object reference
;
;METHODS:
; Set     - pass a timestring argument or keywords to set the class values
; SetAll	- pass a structure to set class values.
; Negate  - switch the sign of the time value stored
; Copy    - create a copy of this object
; Add     - add an amount of time (as specified in a timestring) to this object
; Sub     - subtranct an amount of time (as specified in a timestring) from this object
; GetAll  - returns a structure of type {dsc_time_absolute} containing this object's values
; GetMilli   - returns this object's millisecond field (long)
; GetSeconds - returns this object's seconds field (long)
; GetMinutes - returns this object's minutes field (long)
; GetHours   - returns this object's hours field (long)
; GetDays    - returns this object's days field (long)
; IsNeg      - returns TRUE if time object is negative, FALSE otherwise (boolean)
; ToString   - returns string representation of this time. Only non-zeroed fields are displayed (string)
; ToSeconds  - returns the total time value of this object, in seconds (double)
; ToMinutes  - returns the total time value of this object, in minutes (double)
; ToHours    - returns the total time value of this object, in hours (double)
; ToDays     - returns the total time value of this object, in days (double)
; StringParse - (Static Method) parse a timestring and return a structure holding the extracted elements
; Convert     - (Static Method) convert between units of time.
;                 usage: dsc_time_absolute.convert(amount, input_unit, output_unit)
;                   where input/output units are one of the following strings:
;                   'd','h','m','s','ms'
;
;NOTES:
; +/- and print/implied print are overloaded for this class.
;   e.g.,
;     t1 = dsc_time_absolute('1h')
;     t2 = dsc_time_absolute('55s')
;     t3 = t1 + t2
;     print,t3
;       ==> 01h 55s
;     t1
;       ==> 01h
; 
;CREATED BY: Ayris Narock (ADNET/GSFC) 2018
;
; $LastChangedBy: nikos $
; $LastChangedDate: 2018-03-12 09:55:28 -0700 (Mon, 12 Mar 2018) $
; $LastChangedRevision: 24869 $
; $URL: svn+ssh://thmsvn@ambrosia.ssl.berkeley.edu/repos/spdsoft/tags/spedas_3_2/projects/dscovr/mission_compare/dsc_time_absolute__define.pro $
;-----------------------------------------------------------------------------------

FUNCTION DSC_TIME_ABSOLUTE::INIT, timestring, $
		neg=neg, $ ; Negative Time Flag
		ms=ms, $ ; Milliseconds
		s=s,   $ ; Seconds
		mn=m,  $ ; Minutes
		h=h,   $ ; Hours
		d=d      ; Days
	
	compile_opt idl2
	
	warnexit = 0
	if isa(timestring,'UNDEFINED') then begin
		; Check for keywords if no string sent
		if isa(neg,'UNDEFINED') then neg = !FALSE
		if isa(ms,'UNDEFINED') then ms = 0
		if isa(s,'UNDEFINED') then s = 0
		if isa(m,'UNDEFINED') then m = 0
		if isa(h,'UNDEFINED') then h =0
		if isa(d,'UNDEFINED') then d = 0

		if ~isa(neg,/BOOLEAN) then begin
			if ~isa(neg,/NUMBER,/SCALAR)||~(neg eq 1 or neg eq 0) then warnexit = 1 $
			else neg = (neg eq 1) ? !TRUE : !FALSE
		endif
		if ~isa(ms,/NUMBER,/SCALAR) then warnexit = 1
		if ~isa(s,/NUMBER,/SCALAR) then warnexit = 1
		if ~isa(m,/NUMBER,/SCALAR) then warnexit = 1
		if ~isa(h,/NUMBER,/SCALAR) then warnexit = 1
		if ~isa(d,/NUMBER,/SCALAR) then warnexit = 1


	endif else begin
		r = dsc_time_absolute.stringParse(timestring)
		if isa(r,'STRUCT') then begin
			neg = !FALSE
			ms = (tag_exist(r,'ms')) ? r.ms : 0
			s  = (tag_exist(r,'s'))  ? r.s  : 0
			m  = (tag_exist(r,'m'))  ? r.m  : 0
			h  = (tag_exist(r,'h'))  ? r.h  : 0
			d  = (tag_exist(r,'d'))  ? r.d  : 0
		endif else warnexit = 1
	endelse
	
	if warnexit eq 1 then begin
		dprint,dlevel=1,'Usage:{"DSC_TIME_ABSOLUTE"} Initialize with properly formatted shortcut string OR time element keywords as scalar numbers'
		return,0
	endif else begin
		self.setAll,{neg:neg, ms:ms, s:s, m:m, h:h, d:d}
		return,1
	endelse
	
END


FUNCTION DSC_TIME_ABSOLUTE::_overloadPrint
	return, self.DSC_TIME_ABSOLUTE::toString()
END


FUNCTION DSC_TIME_ABSOLUTE::_overloadImpliedPrint,varname
	return, self.DSC_TIME_ABSOLUTE::_overloadPrint()
END


FUNCTION DSC_TIME_ABSOLUTE::_overloadPlus,left,right
	compile_opt idl2

	out = left.Copy()
	out.add,(right.toString(/compact))
	return,out
END


FUNCTION DSC_TIME_ABSOLUTE::_overloadMinus,left,right
	compile_opt idl2
	
	out = left.Copy()
	out.sub,(right.toString(/compact))
	return,out
END


;+
;DESCRIPTION:
;  Set the class values by passing a timestring argument or keywords 
;  
;  Timestring argument will supsercede other keywords.
;  
;  Called with no arguments sets the object to 0 time.
;
;SYNTAX:
;  DSC_TIME_ABSOLUTE::Set,timestring,neg=neg,ms=ms,s=s,mn=m,h=h,d=d
;  
;CALLING SEQUENCE:
;  mt = dsc_time_absolute()
;  mt.set,h=3,mn=32,ms=400    --> time is 3 hours 32 minutes and 0.400 seconds
;    
;  mt.set,d=3.5,/neg   --> time is negative 3 days,12 hours
;            
;  mt.set,'3h42s'    --> time is 3 hours and 42 seconds
;             
;  mt.set,'-3m'      --> time is negative 3 minutes
;  
;  mt.set    --> time is 0 seconds
;  
;  mt.set,'15h2m',d=2,h=4  --> sets time to 15 hours and 2 minutes NOT  2 days and 4 hours        
;-
PRO DSC_TIME_ABSOLUTE::Set, timestring, $
	neg=neg, $ ; Negative Time Flag 
	ms=ms, $ ; Milliseconds
	s=s,   $ ; Seconds
	mn=m,  $ ; Minutes
	h=h,   $ ; Hours
	d=d      ; Days

	compile_opt idl2
	
	warnexit = 0
	if isa(timestring,'UNDEFINED') then begin
		; Check for keywords if no string sent
		if isa(neg,'UNDEFINED') then neg = !FALSE
		if isa(ms,'UNDEFINED') then ms = 0
		if isa(s,'UNDEFINED') then s = 0
		if isa(m,'UNDEFINED') then m = 0
		if isa(h,'UNDEFINED') then h =0
		if isa(d,'UNDEFINED') then d = 0
	
		
		if ~isa(neg,/BOOLEAN) then begin
			if ~isa(neg,/NUMBER,/SCALAR)||~(neg eq 1 or neg eq 0) then warnexit = 1 $
			else neg = (neg eq 1) ? !TRUE : !FALSE
		endif
		if ~isa(ms,/NUMBER,/SCALAR) then warnexit = 1
		if ~isa(s,/NUMBER,/SCALAR) then warnexit = 1
		if ~isa(m,/NUMBER,/SCALAR) then warnexit = 1
		if ~isa(h,/NUMBER,/SCALAR) then warnexit = 1
		if ~isa(d,/NUMBER,/SCALAR) then warnexit = 1
		if warnexit eq 1 then dprint,dlevel=1,'Usage: obj_new("DSC_TIME_ABSOLUTE",s=45,h=5,/neg) where time element keywords are scalar numbers.' 

	endif else begin
		r = dsc_time_absolute.stringParse(timestring)
		if isa(r,'STRUCT') then begin
			neg = !FALSE
			ms = (tag_exist(r,'ms')) ? r.ms : 0
			s  = (tag_exist(r,'s'))  ? r.s  : 0
			m  = (tag_exist(r,'m'))  ? r.m  : 0
			h  = (tag_exist(r,'h'))  ? r.h  : 0
			d  = (tag_exist(r,'d'))  ? r.d  : 0
		endif else warnexit = 1
	endelse

	if warnexit eq 0 then	self.setAll,{neg:neg, ms:ms, s:s, m:m, h:h, d:d}
END


;+
;DESCRIPTION:
;  Set the class values by passing a structure.
;  The fields MS, S, M, H, D, and NEG 
;    will be assigned if they exist
;    or set to 0 if they are missing from the passed structure.
;    
;  NEG field must be a boolean.
;   
;CALLING SEQUENCE:
;  mt = dsc_time_absolute()
;  mstruct = {neg:!TRUE, d:4, s:32}
;  mt.SetAll,mstruct
;    --> time is negative 4 days and 32 seconds
;-
PRO DSC_TIME_ABSOLUTE::SetAll, str
	compile_opt idl2
	if isa(str,'STRUCT') then begin
		if tag_exist(str,'neg') && ~isa(str.neg,/BOOLEAN) then begin
			dprint,dlevel=1,'Usage: TIMEABSOLUTE.SETALL,STR where STR.NEG must be BOOLEAN'
			return
		endif
		self.neg = !FALSE
		
		;must maintain this order from smallest increment to largest to work with the
		;downwardly additive set___ private methods.

		if tag_exist(str,'ms') then self.setMilli,str.ms else self.setMilli,0
		if tag_exist(str,'s') then self.setSeconds,str.s else self.setSeconds,0
		if tag_exist(str,'m') then self.setMinutes,str.m else self.setMinutes,0
		if tag_exist(str,'h') then self.setHours,str.h else self.setHours,0
		if tag_exist(str,'d') then self.setDays,str.d else self.setDays,0

		self.reduce	;this may set the neg flag if fields total to negative time
		if tag_exist(str,'neg') && str.neg then self.Negate
	endif else begin
		dprint,dlevel=1,'Usage: DSC_TIME_ABSOLUTE.SETALL,STR where STR is a structure'
		return
	endelse
END


;+
;DESCRIPTION:
;  Switch the sign of the time value stored
;
;CALLING SEQUENCE:
;  mt = dsc_time_absolute('4h15m') --> time is 04h 15m
;  mt.negate                   --> time is -04h 15m
;  mt.negate                   --> time is 04h 15m
;-
PRO	DSC_TIME_ABSOLUTE::Negate
	compile_opt idl2
	
	self.neg = (self.neg) ? !FALSE : !TRUE
END


;+
;DESCRIPTION:
;  Create a copy of this object
;
;CALLING SEQUENCE:
;  mt = dsc_time_absolute(s=40,mn=12,/neg)
;  mtcopy = mt.copy()
;-
FUNCTION DSC_TIME_ABSOLUTE::COPY
	compile_opt idl2

	str = self.getall()
	out = Obj_New("DSC_TIME_ABSOLUTE", $
		neg=str.neg, $
		ms=str.ms, $
		s =str.s,  $
		mn =str.m,  $
		h =str.h,  $
		d =str.d)
	return,out
END


;+
;DESCRIPTION:
;  Add an amount of time to this object by passing a 
;  time string.
;  The overloaded '+' operator uses this method internally.
;  
;  Timestring rules:
;    - A string composed of the concatenation of one or more substrings 
;      of the form substring = '{number}{unit_string}' where:
;	      * {number} is a positive integer
;	      * {unit_string} is one of  'd','h','m',s', or 'ms'  
;	        [Representing days, hours, minutes, seconds, and milliseconds, respectively.]
;	  - The substrings can be combined in any order, with a limit of one 
;	    substring per {unit_string} type.
;	  - A leading '-' is accepted to indicate a negative time amount.
;	  - A leading '+' is accepted (but not required) to indicate a positive time amount.
;
;	  Time is always stored in reduced form in the dsc_time_absolute object.
;	  I.e., 300 seconds will show as 5 minutes; 1 hour 90 minutes as 2 hours 30 minutes, etc.
;	  
;CALLING SEQUENCE:
;  mt = dsc_time_absolute('3h20m')
;  mt.add,'40m'
;  --> time is now 4 hours
;  
;  Alternately, use the '+' operator:
;  mt1 = dsc_time_absolute('3h20m')
;  mt2 = dsc_time_absolute('40m')
;  mt = mt1 + mt2
;
;-
PRO DSC_TIME_ABSOLUTE::Add,addstring
	compile_opt idl2
	
	r = dsc_time_absolute.stringParse(addstring)
	if isa(r,'STRUCT') then begin
		if self.neg then begin
			if tag_exist(r,'ms') then self.setMilli,r.ms-self.ms
			if tag_exist(r,'s') then self.setSeconds,r.s-self.s
			if tag_exist(r,'m') then self.setMinutes,r.m-self.m
			if tag_exist(r,'h') then self.setHours,r.h-self.h
			if tag_exist(r,'d') then self.setDays,r.d-self.d
		endif else begin
			if tag_exist(r,'ms') then self.setMilli,r.ms+self.ms
			if tag_exist(r,'s') then self.setSeconds,r.s+self.s
			if tag_exist(r,'m') then self.setMinutes,r.m+self.m
			if tag_exist(r,'h') then self.setHours,r.h+self.h
			if tag_exist(r,'d') then self.setDays,r.d+self.d
		endelse
		self.neg = !FALSE
		self.reduce	;this will set the neg flag if fields total a negative time
	endif
END


;+
;DESCRIPTION:
;  Subtract an amount of time from this object by passing a 
;  time string.
;  The overloaded '-' operator uses this method internally.
;  
;  Timestring rules:
;    - A string composed of the concatenation of one or more substrings 
;      of the form substring = '{number}{unit_string}' where:
;	      * {number} is a positive integer
;	      * {unit_string} is one of  'd','h','m',s', or 'ms'  
;	        [Representing days, hours, minutes, seconds, and milliseconds, respectively.]
;	  - The substrings can be combined in any order, with a limit of one 
;	    substring per {unit_string} type.
;	  - A leading '-' is accepted to indicate a negative time amount.
;	  - A leading '+' is accepted (but not required) to indicate a positive time amount.
;
;	  Time is always stored in reduced form in the dsc_time_absolute object.
;	  I.e., 300 seconds will show as 5 minutes; 1 hour 90 minutes as 2 hours 30 minutes, etc.
;	  
;CALLING SEQUENCE:
;  mt = dsc_time_absolute('3h20m')
;  mt.sub,'40m'
;  --> time is now 2 hours 40 minutes
;  
;  Alternately, use the '-' operator:
;  mt1 = dsc_time_absolute('3h20m')
;  mt2 = dsc_time_absolute('40m')
;  mt = mt1 - mt2
;-
PRO DSC_TIME_ABSOLUTE::Sub,substring
	compile_opt idl2

	r = dsc_time_absolute.stringParse(substring)
	if isa(r,'STRUCT') then begin
		if self.neg then begin
			if tag_exist(r,'ms') then self.setMilli,-self.ms-r.ms
			if tag_exist(r,'s') then self.setSeconds,-self.s-r.s
			if tag_exist(r,'m') then self.setMinutes,-self.m-r.m
			if tag_exist(r,'h') then self.setHours,-self.h-r.h
			if tag_exist(r,'d') then self.setDays,-self.d-r.d
		endif else begin
			if tag_exist(r,'ms') then self.setMilli,self.ms-r.ms
			if tag_exist(r,'s') then self.setSeconds,self.s-r.s
			if tag_exist(r,'m') then self.setMinutes,self.m-r.m
			if tag_exist(r,'h') then self.setHours,self.h-r.h
			if tag_exist(r,'d') then self.setDays,self.d-r.d
		endelse
		self.neg = !FALSE
		self.reduce	;this will set the neg flag if fields total a negative time
	endif
END


;+
;DESCRIPTION:
;  (Static Method) 
;  Parse a timestring and return a structure holding the extracted elements
;
;CALLING SEQUENCE:
;  IDL> elements = dsc_time_absolute.StringParse('-1d12h15m')
;  IDL> print,elements,/implied
;  {
;      "D": -1,
;      "H": -12,
;      "M": -15,
;      "S": 0,
;      "MS": 0
;  }
;-
FUNCTION DSC_TIME_ABSOLUTE::StringParse,input
	compile_opt idl2, static
	
	out = {d:0,h:0,m:0,s:0,ms:0}
	warnexit = 0
	if isa(input,/STRING,/SCALAR) then begin
		if input.Matches('^\-') then begin
			mysign = -1
			input = input.Substring(1,-1)
		endif else begin
			mysign = 1
			if input.Matches('^\+') then input = input.Substring(1,-1)
		endelse
		if input.Matches('([^dhms0-9]|^[dhms]|[0-9]$)') then warnexit = 1 $
		else begin
			dsplit = (input.Split('d',/fold_case)).length
			hsplit = (input.Split('h',/fold_case)).length
			msplit = (input.Split('m([^s]|$)',/fold_case)).length
			ssplit = (input.Split('[^m]s',/fold_case)).length
			mssplit = (input.Split('ms',/fold_case)).length
			if dsplit gt 2 || hsplit gt 2 || msplit gt 2 || ssplit gt 2 || mssplit gt 2 then warnexit = 1
		endelse
		
		if warnexit ne 1 then begin
			out.d = mysign*((input.Extract('[0-9]+d',/fold_case)).Substring(0,-2)).toInteger()
			out.h = mysign*((input.Extract('[0-9]+h',/fold_case)).Substring(0,-2)).toInteger()
			mstring = input.Extract('[0-9]+m([0-9]|$)',/fold_case)
			out.m = ((mstring.Substring(-1)).Matches('[0-9]')) ? mysign*(mstring.Substring(0,-3)).toInteger() : mysign*(mstring.Substring(0,-2)).toInteger()
			out.s = mysign*((input.Extract('[0-9]+s',/fold_case)).Substring(0,-2)).toInteger()
			out.ms = mysign*((input.Extract('[0-9]+ms',/fold_case)).Substring(0,-2)).toInteger()
		endif
	endif else warnexit = 1
	
	if warnexit eq 1 then begin
		dprint,dlevel=1,'Usage: DSC_TIME_ABSOLUTE shortcut string error.
		dprint,dlevel=1,'   Format: ''#d#h#m#s#ms'''
		dprint,dlevel=1,'You may leave out unit id strings, but not repeat them:'
		dprint,dlevel=1,'   OK     ''3h2m'' '
		dprint,dlevel=1,'   NOT OK ''3h45m13m'' '
		dprint,dlevel=1,'Numbers must all be positive integers, with the execption of the leading negative.'
		dprint,dlevel=1,'   OK     ''3d4h23m16s400ms'' '
		dprint,dlevel=1,'   OK ''-23h'' '
		dprint,dlevel=1,'   NOT OK ''15h-4m'' '
		dprint,dlevel=1,'   NOT OK ''15.4m'' '
		out = -1
	endif
	return,out
END


;+
;DESCRIPTION:
;  (Private Method)
;-
PRO DSC_TIME_ABSOLUTE::SetMilli,ms
	compile_opt idl2,hidden

	; Work around to act as a private method
	catch, stranger
	if stranger ne 0 then begin
		catch, /cancel
		dprint,dlevel=1,'Call failed; Attempt to call private method; Use .Set or .SetAll'
		return
	endif
	
	if obj_isa((scope_varfetch('self', level=-1)), 'DSC_TIME_ABSOLUTE') then begin
		if isa(ms,/number,/scalar) then self.ms = round(ms) $
		else begin
			dprint,dlevel=1,'MILLISECONDS must be scalar number'
			return
		endelse
	endif else dprint,dlevel=1,'Call failed; Attempt to call private method; Use .Set or .SetAll'
END


;+
;DESCRIPTION:
;  (Private Method)
;-
PRO DSC_TIME_ABSOLUTE::SetSeconds,s
	compile_opt idl2,hidden

	; Work around to act as a private method
	catch, stranger
	if stranger ne 0 then begin
		catch, /cancel
		dprint,dlevel=1,'Call failed; Attempt to call private method; Use .Set or .SetAll'
		return
	endif

	if obj_isa((scope_varfetch('self', level=-1)), 'DSC_TIME_ABSOLUTE') then begin
		if isa(s,/number,/scalar) then begin
			self.s = floor(s)
			if (s-self.s gt 0) then self.SetMilli,self.ms+((s-self.s)*1000.)
		endif	else begin
			dprint,dlevel=1,'SECONDS must be scalar number'
			return
		endelse
	endif
END


;+
;DESCRIPTION:
;  (Private Method)
;-
PRO DSC_TIME_ABSOLUTE::SetMinutes,m
	compile_opt idl2,hidden

	; Work around to act as a private method
	catch, stranger
	if stranger ne 0 then begin
		catch, /cancel
		dprint,dlevel=1,'Call failed; Attempt to call private method; Use .Set or .SetAll'
		return
	endif

	if obj_isa((scope_varfetch('self', level=-1)), 'DSC_TIME_ABSOLUTE') then begin
		if isa(m,/number,/scalar) then begin
			self.m = floor(m)
			if (m-self.m gt 0) then self.setSeconds,self.s+((m-self.m)*60.)
		endif	else begin
			dprint,dlevel=1,'MINUTES must be scalar number'
			return
		endelse
	endif
END


;+
;DESCRIPTION:
;  (Private Method)
;-
PRO DSC_TIME_ABSOLUTE::SetHours,h
	compile_opt idl2,hidden

	; Work around to act as a private method
	catch, stranger
	if stranger ne 0 then begin
		catch, /cancel
		dprint,dlevel=1,'Call failed; Attempt to call private method; Use .Set or .SetAll'
		return
	endif

	if obj_isa((scope_varfetch('self', level=-1)), 'DSC_TIME_ABSOLUTE') then begin
		if isa(h,/number,/scalar) then begin
			self.h = floor(h)
			if (h-self.h gt 0) then self.setMinutes,self.m+((h-self.h)*60.)
		endif	else begin
			dprint,dlevel=1,'HOURS must be scalar number'
			return
		endelse
	endif
END


;+
;DESCRIPTION:
;  (Private Method)
;-
PRO DSC_TIME_ABSOLUTE::SetDays,d
	compile_opt idl2,hidden

	; Work around to act as a private method
	catch, stranger
	if stranger ne 0 then begin
		catch, /cancel
		dprint,dlevel=1,'Call failed; Attempt to call private method; Use .Set or .SetAll'
		return
	endif

	if obj_isa((scope_varfetch('self', level=-1)), 'DSC_TIME_ABSOLUTE') then begin
		if isa(d,/number,/scalar) then begin
			self.d = floor(d)
			if (d-self.d gt 0) then self.setHours,self.h+((d-self.d)*24.)
		endif	else begin
			dprint,dlevel=1,'DAYS must be scalar number'
			return
		endelse
	endif
END


;+
;DESCRIPTION:
;  Return a structure of type {DSC_TIME_ABSOLUTE} that containins
;  this object's values
;
;CALLING SEQUENCE:
;  IDL> mt = dsc_time_absolute('-15m36s300ms')
;  IDL> mtall = mt.GetAll()
;  IDL> print,mtall,/implied
;  {
;			"IDL_OBJECT_TOP": 0,
;			"__OBJ__": <NullObject>,
;			"IDL_OBJECT_BOTTOM": 0,
;			"NEG": true,
;			"MS": 300,
;			"S": 36,
;			"M": 15,
;			"H": 0,
;			"D": 0
;		}
;
;-
FUNCTION DSC_TIME_ABSOLUTE::GetAll
	compile_opt idl2
	
	str = create_struct(name=obj_class(self))
	struct_assign,self,str
	RETURN,str
END


;+
;DESCRIPTION:
;  Return this object's Milliseconds field (long)
;  Note: Time is always stored in reduced form
;	   I.e., 5000 milliseconds is stored as 5 seconds
;	  
;	 Unit fields are always positive.  The sign of the DSC_TIME_ABSOLUTE object
;	 is stored in the NEG field.
;
;CALLING SEQUENCE:
;  IDL> mt = dsc_time_absolute('12h2m35ms')
;  IDL> mt.getMilli()
;  				35
;  
;  IDL> mt.set,'3m2s5050ms'
;  IDL> mt.getMilli()
;					50
;-
FUNCTION DSC_TIME_ABSOLUTE::GetMilli
	compile_opt idl2
	return,self.ms
END


;+
;DESCRIPTION:
;  Return this object's Seconds field (long)
;  Note: Time is always stored in reduced form
;	   I.e., 75 seconds is stored at 1 minute, 15 seconds
;	   
;	 Unit fields are always positive.  The sign of the DSC_TIME_ABSOLUTE object
;	 is stored in the NEG field.
;
;CALLING SEQUENCE:
;  IDL> mt = dsc_time_absolute('12h2s35ms')
;  IDL> mt.getSeconds()
;  				2
;  
;  IDL> mt.set,'3m2s5050ms'
;  IDL> mt.getMilli()
;					7
;-
FUNCTION DSC_TIME_ABSOLUTE::GetSeconds
	compile_opt idl2
	return,self.s
END


;+
;DESCRIPTION:
;  Return this object's Minutes field (long)
;  Note: Time is always stored in reduced form
;	   I.e., 90 minutes is stored as 1 hour, 30 minutes
;	   
;	 Unit fields are always positive.  The sign of the DSC_TIME_ABSOLUTE object
;	 is stored in the NEG field.
;
;CALLING SEQUENCE:
;  IDL> mt = dsc_time_absolute('3h90m')
;  IDL> mt.getMinutes()
;  				30
;  
;  IDL> mt.set,'30m200s'
;  IDL> mt.getMinutes()
;					33
;-
FUNCTION DSC_TIME_ABSOLUTE::GetMinutes
	compile_opt idl2
	return,self.m
END


;+
;DESCRIPTION:
;  Return this object's Hours field (long)
;  Note: Time is always stored in reduced form
;	   I.e., 90 minutes is stored as 1 hour, 30 minutes
;	   
;	 Unit fields are always positive.  The sign of the DSC_TIME_ABSOLUTE object
;	 is stored in the NEG field.
;
;CALLING SEQUENCE:
;  IDL> mt = dsc_time_absolute('-3h90m')
;  IDL> mt.getHours()
;  				4
;  
;  IDL> mt.set,'66m'
;  IDL> mt.getHours()
;					1
;-
FUNCTION DSC_TIME_ABSOLUTE::GetHours
	compile_opt idl2
	return,self.h
END


;+
;DESCRIPTION:
;  Return this object's Days field (long)
;  Note: Time is always stored in reduced form
;	   I.e., 25 hours is stored as 1 day, 1 hour
;	   
;	 Unit fields are always positive.  The sign of the DSC_TIME_ABSOLUTE object
;	 is stored in the NEG field.
;
;CALLING SEQUENCE:
;  IDL> mt = dsc_time_absolute('2d15h')
;  IDL> mt.getDays()
;  				2
;  
;  IDL> mt.set,'-30h'
;  IDL> mt.getDays()
;					1
;-
FUNCTION DSC_TIME_ABSOLUTE::GetDays
	compile_opt idl2
	return,self.d
END


;+
;DESCRIPTION:
;  Returns TRUE if time object is negative, FALSE otherwise (boolean)
;
;CALLING SEQUENCE:
;  IDL> mt = dsc_time_absolute('-30h')
;  IDL> mt.IsNeg()
;  true
;  
;  IDL> mt.set,'15s2h'
;  IDL> mt.IsNeg()
;  false
;
;-
FUNCTION DSC_TIME_ABSOLUTE::IsNeg
	compile_opt idl2
	return,self.neg
END


;+
;DESCRIPTION:
;  Returns the string representation of this time. 
;  Only non-zeroed fields are displayed (string)
;
;  Use keyword /COMPACT to remove whitespace
;  
;CALLING SEQUENCE:
;  IDL> mt = dsc_time_absolute(d=3,h=42.5,s=10)
;  IDL> mtstring = mt.ToString()
;  IDL> print,mtstring
;  04d 18h 30m 10s
;
;-
FUNCTION DSC_TIME_ABSOLUTE::ToString,compact=compact
	compile_opt idl2
	;TODO - add option flags - like, /minutes, etc.
	out = ''
	compact = keyword_set(compact) ? 1 : 0
	prefix = (self.neg) ? '-' : ''
	if self.d gt 0 then out = out+String(self.d,format='(I02)')+'d '
	if self.h gt 0 then out = out+String(self.h,format='(I02)')+'h '
	if self.m gt 0 then out = out+String(self.m,format='(I02)')+'m '
	if self.ms gt 0 then out = out+String(self.s,format='(I02)')+'.'+String(self.ms,format='(I03)')+'s' $
	else if self.s gt 0 then begin
		out = out+String(self.s,format='(I02)')+'s'
	endif
	if compact then begin
		a = out.Split(' ')
		out = a.Join()
	endif
	if out eq '' then return,'0s'
	return,(prefix+out).Trim()
END


;+
;DESCRIPTION:
;  Returns the total time value of this object, in seconds (double)
;
;CALLING SEQUENCE:
;  IDL> mt.set,'1d1h1m1s1ms'
;  IDL> mt.toSeconds()
;        90061.001000000004
;  IDL> mt.negate()
;  IDL> mt.toSeconds()
;       -90061.001000000004
;-
FUNCTION DSC_TIME_ABSOLUTE::ToSeconds
	compile_opt idl2
	result = $
		self.convert(self.ms,'ms','s') + $
		self.s +  $
		self.convert(self.m,'m','s') + $
		self.convert(self.h,'h','s') + $
		self.convert(self.d,'d','s')
	
	if self.neg then result = -1*result
	return,result
END


;+
;DESCRIPTION:
;  Returns the total time value of this object, in minutes (double)
;
;CALLING SEQUENCE:
;  IDL> mt.set,'1d1h1m1s1ms'
;  IDL> mt.toMinutes()
;        1501.0166833333333
;  IDL> mt.negate()
;  IDL> mt.toMinutes()
;       -1501.0166833333333
;-
FUNCTION DSC_TIME_ABSOLUTE::ToMinutes
	compile_opt idl2
	result = $
		self.convert(self.ms,'ms','m') + $
		self.convert(self.s,'s','m') +  $
		self.m + $
		self.convert(self.h,'h','m') + $
		self.convert(self.d,'d','m')

	if self.neg then result = -1*result		
	return,result
END


;+
;DESCRIPTION:
;  Returns the total time value of this object, in hours (double)
;
;CALLING SEQUENCE:
;  IDL> mt.set,'1d1h1m1s1ms'
;  IDL> mt.toHours()
;        25.016944722222224
;  IDL> mt.negate()
;  IDL> mt.toHours()
;       -25.016944722222224
;-
FUNCTION DSC_TIME_ABSOLUTE::ToHours
	compile_opt idl2
	result = $
		self.convert(self.ms,'ms','h') + $
		self.convert(self.s,'s','h') +  $
		self.convert(self.m,'m','h') + $
		self.h + $
		self.convert(self.d,'d','h')

	if self.neg then result = -1*result
	return,result
END


;+
;DESCRIPTION:
;  Returns the total time value of this object, in days (double)
;
;CALLING SEQUENCE:
;  IDL> mt.set,'1d1h1m1s1ms'
;  IDL> mt.toDays()
;        1.0423726967592593
;  IDL> mt.negate()
;  IDL> mt.toDays()
;       -1.0423726967592593
;-
FUNCTION DSC_TIME_ABSOLUTE::ToDays
	compile_opt idl2
	result = $
		self.convert(self.ms,'ms','d') + $
		self.convert(self.s,'s','d') + $
		self.convert(self.m,'m','d') + $
		self.convert(self.h,'h','d') + $
		self.d

	if self.neg then result = -1*result
	return,result
END


;+
;DESCRIPTION:
;  (Private Method)
;-
PRO DSC_TIME_ABSOLUTE::Reduce
	compile_opt idl2,hidden

	; Work around to act as a private method
	catch, stranger
	if stranger ne 0 then begin
		catch, /cancel
		dprint,dlevel=1,'Call failed; Attempt to call private method.
		return
	endif
		
	if obj_isa((scope_varfetch('self', level=-1)), 'DSC_TIME_ABSOLUTE') then begin		
		DHMfactor = [86400.,3600.,60.]
		out = intarr(3)
		sec = self.toSeconds()
		self.neg = (sec lt 0) ? !TRUE : !FALSE
		sec = abs(sec)
		
		for i = 0,2 do begin
			out[i] = floor(sec/DHMFactor[i])
			sec = sec mod DHMfactor[i]
		endfor
	
		self.d = out[0]
		self.h = out[1]
		self.m = out[2]
		self.s = floor(sec)
		self.ms = round((sec mod 1)*1000.)
	endif
END


;+
;DESCRIPTION:
;  (Static Method)
;  Convert between units of time.
;	 Usage: 
;    dsc_time_absolute.convert(amount, input_unit, output_unit)
;      where input/output units are one of the following strings:
;      'd','h','m','s','ms'
;
;CALLING SEQUENCE:
;  IDL> dsc_time_absolute.convert(15, 'm', 's')
;       900.00000000000000
;  IDL> dsc_time_absolute.convert(368, 'm', 'h')
;				6.1333333333333329      
;-
FUNCTION DSC_TIME_ABSOLUTE::Convert,value,in_unit,out_unit
	compile_opt idl2, static
	
	out_value = -1
	if (~isa(value,/scalar,/number) || $
		 ~isa(in_unit,/scalar,/string) || $
		 ~isa(out_unit,/scalar,/string)) then begin 
		dprint,dlevel=1,'Usage Error: DSC_TIME_ABSOLUTE.convert,VALUE,IN_UNIT,OUT_UNIT'
		dprint,dlevel=1,"and   IN_UNIT,OUT_UNIT in the set {'ms','s','m','h','d'}"
		return,-1
	endif

	in_unit = in_unit.ToLower()
	out_unit = out_unit.ToLower()
	strerr = 0
	case (in_unit) of
		'ms': begin
						case (out_unit) of
							'ms': multiplier = 1d
							's' : multiplier = .001d
							'm' : multiplier = .001d/60.
							'h' : multiplier = .001d/3600.
							'd' : multiplier = .001d/(3600.*24.)
							else: strerr = 1
						endcase
			end
		's':  begin
						case (out_unit) of
							'ms': multiplier = 1000d
							's' : multiplier = 1d
							'm' : multiplier = 1d/60.
							'h' : multiplier = 1d/3600.
							'd' : multiplier = 1d/(3600.*24.)
							else: strerr = 1
						endcase
			end
		'm':  begin
						case (out_unit) of
							'ms': multiplier = 60000d
							's' : multiplier = 60d
							'm' : multiplier = 1d
							'h' : multiplier = 1d/60.
							'd' : multiplier = 1d/(60.*24.)
							else: strerr = 1
						endcase
			end
		'h':  begin
						case (out_unit) of
							'ms': multiplier = 3.6d6
							's' : multiplier = 3600d
							'm' : multiplier = 60d
							'h' : multiplier = 1d
							'd' : multiplier = 1d/24.
							else: strerr = 1
						endcase
			end
		'd':  begin
						case (out_unit) of
							'ms': multiplier = 24d*3.6e6
							's' : multiplier = 24d*3600.
							'm' : multiplier = 24d*60.
							'h' : multiplier = 24d
							'd' : multiplier = 1d
							else: strerr = 1
						endcase
			end
		else: strerr = 1
	endcase

	if strerr then begin
		dprint,dlevel=1,'Usage Error: DSC_TIME_ABSOLUTE.convert,VALUE,IN_UNIT,OUT_UNIT'
		dprint,dlevel=1,"and   IN_UNIT,OUT_UNIT in the set {'ms','s','m','h','d'}"
		return,-1
	endif

	return, value*multiplier
END


PRO DSC_TIME_ABSOLUTE__DEFINE
	compile_opt idl2
	
	STRUCT = { DSC_TIME_ABSOLUTE, INHERITS IDL_Object, $
		neg: !FALSE,  $ ; Negative Time Flag
		ms: 0,   $ ; Milliseconds
		s : 0,   $ ; Seconds
		m : 0,   $ ; Minutes
		h : 0,   $ ; Hours
		d : 0    $ ; Days
	}

END