from eon_class import *
import re

class eon_date( eon ):

	def __init__ (self,
		ctime	= None,				# ctime needs to stay first
		format	= None,
		regex	= None,
		now		= False,
		julian	= False,

		ticday	= None,
		tictac	= None,
		exponent= g_EXPONENT,

		year	= None,
		doy		= None,
		month	= None,
		day		= None,

		hour	= None,
		minute	= None,
		second	= None,
		chipsec	= None,

		timezone= None,

		jd		= None,
		mjd		= None,
		njd		= None,
		posix	= None,
		jepoch	= None,
		bepoch	= None,

	):
		'''
		#+
		# NAME:
		#	eon_date.__init__
		# PURPOSE:
		#	Creates eon_date object
		# CALLING SEQUENCE:
		#	tt = eon_date()
		# INPUTS:
		# OPTIONAL INPUTS:
		#	exponent    defines units in which time of day is specified as
		#	            10^(-exponent).
		#				Default: g_EXPONENT (defined in eon_helper.py)
		# OUTPUTS:
		#	tt          eon_date object
		#	            tt.exponent     integer
		#								set from optional input 'exponent'
		#	                            (i.e. defaults to g_EXPONENT)
		# PROCEDURE:
		#	The __init__ procedure MUST set tt.ticday and tt.tictac:
		#	  tt.ticday     number of whole days since 2000 January 1, 0 UTC
		#	  tt.tictac		time of days in units of 10^(-tt.exponent)
		#	  tt.exponent   defines units of tt.tictac
		#	As a side effect a number of attributes may get set, but this
		#	is not really necessary.
		#-
		'''
		if g_EON_DEBUG: print self.__class__, 'in', whoami()

		self.exponent = exponent

		tictacs_per_second	= 10**exponent
		tictacs_per_day		= tictacs_per_second*g_SECONDS_PER_DAY

		if ticday != None:			# Input is primary attributes 'ticday' and 'tictac'

			if tictac == None: tictac = 0

			self.ticday	= ticday
			self.tictac	= tictac if is_integer(tictac) else long(round(tictac))

		elif posix != None:			# Input is posix time

			tic = disassemble_number(posix, exponent)
			(ticday,tictac) = divmod( tic[0], g_SECONDS_PER_DAY )
			self.ticday		= -g_JD2000_POSIX	+long(ticday)
			self.tictac		= 					 long(round(tictac*tictacs_per_second+tic[1]))

		elif njd != None:			# Input is 'new' Julian day (days since 2000/01/01 12:00:00)

			(ticday,tictac) = divmod( best_guess_for_number(njd) ,1)
			self.ticday		= -g_NJD_NJD		+long(ticday)
			self.tictac		= tictacs_per_day/2	+long(round(tictac*tictacs_per_day))

		elif mjd != None:			# Modified Julian days

			(ticday,tictac) = divmod( best_guess_for_number(mjd) ,1)
			self.ticday		= -g_NJD_MJD		+long(ticday)
			self.tictac		= tictacs_per_day/2	+long(round(tictac*tictacs_per_day))

		elif jd  != None:			# Julian days

			(ticday,tictac) = divmod( best_guess_for_number(jd) ,1)
			self.ticday		= -g_NJD_JD			+long(ticday)
			self.tictac 	= tictacs_per_day/2	+long(round(tictac*tictacs_per_day))

		elif jepoch != None:

			(ticday,tictac) = divmod(( best_guess_for_number(jepoch)-2000)*g_JULIAN_YEAR,1)
			self.ticday		= 0					+long(ticday)
			self.tictac		= tictacs_per_day/2	+long(round(tictac*tictacs_per_day))

		elif bepoch != None:

			(ticday,tictac) = divmod(-g_JD2000_B1900+(best_guess_for_number(bepoch)-1900)*g_BESSEL_YEAR,1)
			self.ticday		= 0					+long(ticday)
			self.tictac		= tictacs_per_day/2	+long(round(tictac*tictacs_per_day))

		else:

			if is_string(ctime) and ctime != '':	# String input

				# Often a single unnamed argument containing a string with a time is specified.
				# This argument will end up in 'ctime'. This is run through split_date_format
				# or split_time_regex to set the time

				if regex == None:
					(year, doy, month, cmonth, day, hour, minute, second, chipsec) = \
						split_date_format( ctime, format=format, exponent=exponent, julian=julian, now=now)
				else:
					(year, doy, month, cmonth, day, hour, minute, second, chipsec) = \
						split_date_regex ( ctime, regex =regex , exponent=exponent, julian=julian, now=now)

			else:

				# Input is (a subset of) a list of individual time elements (year, doy, etc.)

				(year, doy, month, cmonth, day, hour, minute, second, chipsec) = \
					fill_date_fields( year, doy, month, day, hour, minute, second, chipsec, timezone, exponent=exponent, julian=julian, now=now)

			# At this point a complete set of time elements is specified.
			# Use these to set the primary attributes self.ticday and self.tictac

			if is_string(year):			# 'year' could be returned as a string from split_date_regex
										# Currently only 'posix' has been implemented
				if year == 'posix':
					(ticday,tictac) = divmod(doy,g_SECONDS_PER_DAY)
					self.ticday		= -g_JD2000_POSIX	+long(ticday)
					self.tictac		= 					 long(round(tictac*tictacs_per_second))
				else:
					print "oops: option not implemented: %"%year
					sys.exit()
			else:

				chipsec = chipsec if is_integer(chipsec) else long(round(chipsec))
				self.ticday = days_since_2000(year,doy,julian=julian)+julian*g_GREG2JULIAN
				self.tictac	= ((hour*g_MINUTES_PER_HOUR+minute)*g_SECONDS_PER_MINUTE+second)*10**exponent+chipsec

	 	self.normalize()

		return

	def get (self,
		format		= None	,
		regex		= None	,
		attr		= None	,
		julian		= False	,
		timezone 	= None	,
	):
		'''
		#+
		# NAME:
		#	eon_date.get
		# PURPOSE:
		#	Retrieve attribute from eon_date object
		# CALLING SEQUENCE:
		#	attr = tt.get( format=format, regex=regex, attr=attr, julian=True/False )
		# INPUTS
		#	tt   eon_date object
		# OPTIONAL INPUTS:
		#	Specify one of these to define the attribute required
		#	format      string defining the format for the attribute
		#	            The string can contain a number of identifiers which are
		#	            replaced by the corresponding time properties.
		#	            YYYY    4-digit year
		#	            YY      2-digit year:
		#	                    YYYY-1900 if YYYY less than 2000
		#	                    YYYY-2000 if YYYY if greater/equal 2000
		#	            DOY     day of year as 3-char string between 001 and 366
		#	            MM      month as 2-char string between 01 and 12
		#	            MON     month as 3-char uppercase string: JAN, FEB, .., DEC
		#				Mon		month as 3-char string: Jan, Feb, .., Dec
		#				Month	full name of month: January, February, .., December
		#	            DD      day of month as 2-char string between 01 and 31
		#	            hh      hour of the day as 2-char string between 01 and 23
		#				mm      minute in the the hour as 2-char string between
		#	                    01 and 59
		#	            ss, ss.d#, ss.d, ss.dd, ss.ddd, etc.
		#	                    integer part of second in the minute as 2-char
		#	                    string between 01 and 59, with optional the fraction
		#	                    indicated by .d#, .d, etc. (see below)
		#	            posix, posix.d#, posix.d, posix.dd, posix.ddd, etc.
		#	                    integer part of posix (unix) time with optional the
		#	                    fraction indicated by .d#, .d, etc. (see below)
		#
		#				For ss and posix the fraction of the second is specified as:
		#	                    The fractional second is added with the indicated
		#	                    number digits:
		#	                      .d#   # digits matches the internal time precision
		#	                      .d    one digit
		#	                      .dd   two digits, etc.
		#
		#	            DOW     day of week as 3-char uppercase string: MON, TUE, ..
		#				weekday full day of week: Monday, Tuesday, ..
		#				njd     'new' Julian day: days since 2000 Jan 1, 12:00:00 UTC
		#	            mjd     modified Julian day
		#	            jd      Julian day
		#	            jepoch  Julian epoch
		#	            bepoch	Besselian epoch
		#
		#	regex       not yet implemented
		#
		#	attr		single attribute of time:
		#	            year
		#	            julian_year
		#	            doy
		#	            julian_doy
		#	            month
		#	            julian_month
		#	            day
		#	            julian_day
		#	            dow
		#	            hour
		#	            minute
		#	            second
		#	            chipsec
		#	            njd
		#	            mjd
		#	            jd
		#	            jepoch
		#	            bepoch
		#	            posix
		#
		#	julian		True/False, default: False
		#	              False: return attributes for Gregorian date
		#	              True : return attributes for Julian date
		'''

		if format != None:

			if g_EON_DEBUG: print '%s in "%s" with format "%s"'%(self.__class__, whoami(), format)

			result = format

			if 'njd' in result:
				result = result.replace('njd'		, str(self.njd	 ))

			if 'mjd' in result:
				result = result.replace('mjd'		, str(self.mjd	 ))

			if 'jd' in result:
				result = result.replace('jd'		, str(self.jd	 ))

			if 'jepoch' in result:
				result = result.replace('jepoch'	, str(self.jepoch))

			if 'bepoch' in result:
				result = result.replace('bepoch'	, str(self.bepoch))

			if 'posix' in result:
				posix2 = self.posix2
				result = fill_fraction(result, 'posix', '%d'%posix2[0], posix2[1], exponent=self.exponent)

			if 'ss' in result:
				posix2 = self.posix2
				result = fill_fraction(result, 'ss', '%02d'%divmod(posix2[0],60)[1], posix2[1], exponent=self.exponent)

			if 'TZ' in result:
				tz_hour, tz_minute = disassemble_timezone(timezone)
				tz = ['+','-'][tz_hour < 0]+'%02d%02d'%(abs(tz_hour),abs(tz_minute))
				result = result.replace('TZ'		, tz)

			# The remaining quantities are timezone dependent
			# (part = year, doy, month, day, hour, minute, second, dow)
			# the timezone is set in self.__dict__['timezone'].
			# This trick is used to make the timezone available to __getattr__,
			# which is called every time self.part is referred to.
			# self.__dict__['timezone'] is removed again in __getattr__.
			# So the self.__dict__['timezone'] is set for each self.part reference.

			while 'YYYY' in result:
				self.__dict__['timezone'] = timezone
				result = result.replace('YYYY','%04d'%(self.julian_year if julian else self.year),1)

			if 'YY' in result:
				self.__dict__['timezone'] = timezone
				year	= self.julian_year if julian else self.year
				year	= (year-1900)*(year < 2000)+(year-2000)*(year >= 2000)
				result	= result.replace('YY','%02d'%year,1)

			if 'DOY' in result:
				self.__dict__['timezone'] = timezone
				result = result.replace('DOY','%03d'%(self.julian_doy	if julian else self.doy),1)

			if 'MM' in result:
				self.__dict__['timezone'] = timezone
				result = result.replace('MM','%02d'%(self.julian_month	if julian else self.month)[0],1)

			if 'MON' in result:
				self.__dict__['timezone'] = timezone
				result = result.replace('MON','%3s'%(self.julian_month	if julian else self.month)[1][0:3].upper(),1)

			if 'Month' in result:
				self.__dict__['timezone'] = timezone
				result = result.replace('Month','%s'%(self.julian_month	if julian else self.month)[1],1)

			if 'Mon' in result:
				self.__dict__['timezone'] = timezone
				result = result.replace('Mon','%s'%(self.julian_month	if julian else self.month)[1][0:3],1)

			if 'DD' in result:
				self.__dict__['timezone'] = timezone
				result = result.replace('DD','%02d'%(self.julian_day	if julian else self.day),1)

			if 'hh' in result:
				self.__dict__['timezone'] = timezone
				result = result.replace('hh','%02d'%self.hour,1)

			if 'mm' in result:
				self.__dict__['timezone'] = timezone
				result = result.replace('mm','%02d'%self.minute,1)

			if 'DOW' in result:
				self.__dict__['timezone'] = timezone
				result = result.replace('DOW', self.dow[1][0:3].upper())

			if 'weekday' in result:
				self.__dict__['timezone'] = timezone
				result = result.replace('weekday', self.dow[1])

			if result == format:
				raise EonError( whoami()+", unrecognized format, '"+format+"'")

		elif regex != None:

			if g_EON_DEBUG: print '%s in "%s" with regex "%s"'%(self.__class__, whoami(), regex)

			pass

		else:

			if g_EON_DEBUG: print '%s in "%s" with attr "%s"'%(self.__class__, whoami(), attr)

			if isinstance(attr,list):
				result = []
				for a in attr:
					if a in [	'year'	, 'julian_year'	,
								'doy'	, 'julian_doy'	,
								'month'	, 'julian_month',
								'imonth',
								'cmonth',
								'day'	, 'julian_day'	,
								'dow'	,
								'hour'	, 'minute', 'second', 'chipsec',
								]:
						self.__dict__['timezone'] = timezone

					result.append( self.__getattr__( a ) )
			else:
				if attr in ['year'	, 'julian_year'	,
							'doy'	, 'julian_doy'	,
							'month'	, 'julian_month',
							'imonth',
							'cmonth',
							'day'	, 'julian_day'	,
							'dow'	,
							'hour'	, 'minute', 'second', 'chipsec',
							]:
					self.__dict__['timezone'] = timezone

				result = self.__getattr__( attr )

		return result

	def all_attr(self):
		return {
			'njd'			: 'days since 2000/01/01 12:00 UT'	,
			'mjd'			: 'modified Julian day'				,
			'jd'			: 'Julian day'						,
			'bepoch'		: 'Besselian epoch'					,
			'jepoch'		: 'Julian epoch'					,
			'year'			: 'atom: year, <YYYY>-MM-DD'		,
			'doy'			: 'atom: day of year, <YYYY>-DOY'	,
			'month'			: 'atom: month, YYYY-<MM>-DD'		,
			'day'			: 'atom: day of month, YYYY-MM-<DD>',
			'julian_year'	: 'atom: year (Julian)'				,
			'julian_doy'	: 'atom: day of year (Julian)'		,
			'julian_month'	: 'atom: month (Julian)'			,
			'julian_day'	: 'atom: day of month (Julian)'		,
			'hour'			: 'atom: hour, <hh>:mm:ss.%s'%('d'*self.exponent)					,
			'minute'		: 'atom: minute, hh:<mm>:ss.%s'%('d'*self.exponent)					,
			'second'		: 'atom: second, hh:mm:<ss>.%s'%('d'*self.exponent)					,
			'chipsec'		: 'atom: fraction of second in units of 10^-%d'%self.exponent+', hh:mm:ss.<%s>'%('d'*self.exponent)	,
			'chipsec'		: 'fraction of second'				,
			'dow'			: 'day of week'						,
		}

	def __getattr__(self, name):

		if g_EON_DEBUG: print '%s in %s for "%s"'%(self.__class__, whoami(), name)

		# This intercepts attributes that already have been calculated.
		# This includes the principal attributes self.ticday, self.tictac
		# and self.exponent, and timezone-independent quantities that
		# have been referenced already.

		if name in self.__dict__:
			result = self.__dict__[name]

		# First list all quantities that are independent of timezone.
		# These are stored as attributes of self whenever they are
		# referenced the first time. More subsequence references they
		# are pulled out of self.__dict__ (see above).

		elif name == 'njd':
			tictacs_per_day = g_SECONDS_PER_DAY*10**self.exponent
			chipsec = self.tictac-tictacs_per_day/2
			self.njd = g_NJD_NJD+self.ticday+(0 if chipsec == 0 else float(chipsec)/tictacs_per_day)
			result = self.__dict__[name]

		elif name == 'mjd':
			tictacs_per_day = g_SECONDS_PER_DAY*10**self.exponent
			chipsec = self.tictac-tictacs_per_day/2
			self.mjd = g_NJD_MJD+self.ticday+(0 if chipsec == 0 else float(chipsec)/tictacs_per_day)
			result = self.__dict__[name]

		elif name == 'jd':
			tictacs_per_day = g_SECONDS_PER_DAY*10**self.exponent
			chipsec = self.tictac-tictacs_per_day/2
			self.jd = g_NJD_JD+self.ticday+float(self.tictac-tictacs_per_day/2)/tictacs_per_day
			result = self.__dict__[name]

		elif name == 'jepoch':
			tictacs_per_day = g_SECONDS_PER_DAY*10**self.exponent
			chipsec = self.tictac-tictacs_per_day/2
			self.jepoch = 2000+(self.ticday+(0 if chipsec == 0 else float(chipsec)/tictacs_per_day))/g_JULIAN_YEAR
			result = self.__dict__[name]

		elif name == 'bepoch':
			tictacs_per_day = g_SECONDS_PER_DAY*10**self.exponent
			chipsec = self.tictac-tictacs_per_day/2
			self.bepoch = 1900+(g_JD2000_B1900+(self.ticday+(0 if chipsec == 0 else float(chipsec)/tictacs_per_day)))/g_BESSEL_YEAR
			result = self.__dict__[name]

		elif name == 'posix2':				# Integer number of seconds in posix time
			tictacs_per_second = 10**self.exponent
			iposix, chipsec = divmod(self.tictac, tictacs_per_second)			# Integer seconds and remaining tictacs
			iposix += (g_JD2000_POSIX+self.ticday)*g_SECONDS_PER_DAY
			self.posix2 = (iposix,chipsec)
			result = self.__dict__[name]

		elif name == 'posix':				# NEVER USED? Posix time as float (will loose some precision)
			tictacs_per_second = 10**self.exponent
			iposix, chipsec = divmod(self.tictac, tictacs_per_second)			# Integer seconds and remaining tictacs
			rposix = 0 if chipsec == 0 else float(chipsec)/tictacs_per_second	# Remaining fraction of a second
			iposix += (g_JD2000_POSIX+self.ticday)*g_SECONDS_PER_DAY			# Integer number of seconds in posix time
			self.posix = iposix+rposix
			result = self.__dict__[name]

		elif name in 'second':
			tictacs_per_second = 10**self.exponent
			iposix, chipsec = divmod(self.tictac, tictacs_per_second)			# Integer seconds and remaining tictacs
			self.second = divmod(iposix,60)[1]
			result = self.__dict__[name]

		elif name in 'chipsec':
			tictacs_per_second = 10**self.exponent
			iposix, chipsec = divmod(self.tictac, tictacs_per_second)			# Integer seconds and remaining tictacs
			self.chipsec = chipsec
			result = self.__dict__[name]

		# The remaining attributes are timezone-dependent quantities that
		# need to be calculated from scratch every time they are referenced.
		# The timezone is stored in self.__dict__['timezone'] in __init__.
		# The first call to adjust_for_timezone adjust self.ticday and
		# self.tictac to account for the timezone. The second call resets
		# self.ticday and self.tictac back to their original (UTC) values,
		# and removes self.__dict__['timezone'] from the dictionary.

		elif name == 'year':
			self.adjust_for_timezone()
			(year, doy) = year_doy(self.ticday,julian=False)
			self.adjust_for_timezone()
			result = year

		elif name == 'julian_year':
			self.adjust_for_timezone()
			(year, doy) = year_doy(self.ticday-g_GREG2JULIAN,julian=True)
			self.adjust_for_timezone()
			result = year

		elif name == 'doy':
			self.adjust_for_timezone()
			(year, doy) = year_doy(self.ticday,julian=False)
			self.adjust_for_timezone()
			result = doy

		elif name == 'julian_doy':
			self.adjust_for_timezone()
			(year, doy) = year_doy(self.ticday-g_GREG2JULIAN,julian=True)
			self.adjust_for_timezone()
			result = doy

		elif name in ['month','imonth','cmonth','day']:
			self.adjust_for_timezone()
			(year, doy) = year_doy(self.ticday,julian=False)
			self.adjust_for_timezone()
			(year, month, cmonth, day) = doy2month_day( year, doy, julian=False )
			result = (month,cmonth) if name == 'month' else month if name == 'imonth' else cmonth if name == 'cmonth' else day

		elif name in ['julian_month','julian_day']:
			self.adjust_for_timezone()
			(year, doy) = year_doy(self.ticday-g_GREG2JULIAN,julian=True)
			self.adjust_for_timezone()
			(year, month, cmonth, day) = doy2month_day( year, doy, julian=True )
			result = (month,cmonth) if name == 'julian_month' else day

		elif name == 'dow':
			self.adjust_for_timezone()
			dow = weekday( self.ticday )
			self.adjust_for_timezone()
			result = dow

		elif name in ['hour','minute']:
			self.adjust_for_timezone()
			(day, hour, minute, second, chipsec) = fix_dhms(0, 0, 0, 0, self.tictac, exponent=self.exponent)
			self.adjust_for_timezone()
			result = hour if name == 'hour' else minute

		return result

	def __radd__( self, tt ):
		# date+date:
		#	eon.__add__ 		-> NotImplemented
		#   eon_date.__radd__	-> NotImplemented	-> TypeError raised
		if g_EON_DEBUG: print self.__class__, 'in eon_date', whoami()
		return NotImplemented if isinstance(tt,eon_date) else self.linear_transform(tt,factor=(1,1),exponent=max(self.exponent,tt.exponent))

	def __rsub__( self, tt ):
		if g_EON_DEBUG: print self.__class__, 'in eon_date', whoami()
		return self.linear_transform(tt,factor=(-1,1),exponent=max(self.exponent,tt.exponent)) if isinstance(tt,eon_date) else NotImplemented

	# This method cannot be moved into the superclass 'eon' because it needs
	# access to the eon_date and eon_delta classes

	def make_instance( self, eon_type, ticday, tictac, exponent ):
		if g_EON_DEBUG: print self.__class__, 'in',whoami()

		if eon_type == 'eon_date':
			u = eon_date(
					ticday	= ticday,
					tictac	= tictac,			# Rounding is done in __init__
					exponent= exponent
				)
		else:
			u = eon_delta(
					ticday	= ticday,
					tictac	= tictac,			# Rounding is done in __init__
					exponent= exponent
				)

		u.normalize()

		return u

	def bot(self,dt,t0=None):

		if type(t0) == type(None):
			t0 = eon_date()

		du = self-t0
		if type(du) == type(NotImplemented):
			raise EonError( whoami()+", t.bot(dt), t must be date")

		return t0+du.bot(dt)

	def eot(self,dt,t0=None):
		if type(t0) == type(None):
			t0 = eon_date()

		du = self-t0
		if type(du) == type(NotImplemented):
			raise EonError( whoami()+", t.eot(dt), t must be date")

		return t0+du.eot(dt)

	def round(self,dt,t0=None):
		'''
		#+
		# NAME:
		#	eon_delta.round
		# PURPOSE:
		#	Rounds to nearest multiple of a specified delta time
		# CALLING SEQUENCE:
		#	tt_rounded = tt.round(dt)
		# INPUTS:
		#	dt		date object
		# OPTIONAL INPUT:
		#	t0		date object to be used as time origin
		#			default: 2000/01/01 0 UTC
		# OUTPUTS:
		#	tt_rounded	date object, rounded to nearest dt
		# PROCEDURE:
		#	The time difference tt-t0 is rounded to the nearest
		#	dt. The rounded difference is then added to t0 again.
		#-
		'''
		if type(t0) == type(None):
			t0 = eon_date()

		du = self-t0
		if type(du) == type(NotImplemented):
			raise EonError( whoami()+", t.round(dt), t must be date")

		return t0+du.round(dt)


	def bop(self,deadline):

		cases = ['year','quarter','month','week','day','monday','tuesday','wednesday','thursday','friday','saturday','sunday']
		if deadline in cases:
			deadline += "+0"

		for case in cases:

			m = re.search(case+'([-,+]\d+)',deadline)
			if m:
				uu = self

			#if m:
				n = long(m.group(1))

				if case == 'year':
					tt = eon_date(year=uu.get(attr='year')+n,month=1, day=1)
					break
				elif case == 'quarter':
					tt = eon_date(year=uu.get(attr='year'),month=(uu.get(attr='month')[0]-1)/3*3+1+n*3, day=1)
					break
				elif case == 'month':
					tt = eon_date(year=uu.get(attr='year'),month= uu.get(attr='month')[0]+n, day=1)
					break
				elif case == 'week':
					tt = eon_date(year=uu.get(attr='year'),month= uu.get(attr='month')[0], day=uu.get(attr='day')-(uu.get(attr='dow')[0]-1+n*7))
					break
				elif case == 'day':
					tt = eon_date(year=uu.get(attr='year'),month= uu.get(attr='month')[0], day=uu.get(attr='day')+n)
					break
				elif case == 'monday':
					tt = eon_date(year=uu.get(attr='year'),month= uu.get(attr='month')[0], day=uu.get(attr='day')-(uu.get(attr='dow')[0]+6)%7+n*7)
					break
				elif case == 'tuesday':
					tt = eon_date(year=uu.get(attr='year'),month= uu.get(attr='month')[0], day=uu.get(attr='day')-(uu.get(attr='dow')[0]+5)%7+n*7)
					break
				elif case == 'wednesday':
					tt = eon_date(year=uu.get(attr='year'),month= uu.get(attr='month')[0], day=uu.get(attr='day')-(uu.get(attr='dow')[0]+4)%7+n*7)
					break
				elif case == 'thursday':
					tt = eon_date(year=uu.get(attr='year'),month= uu.get(attr='month')[0], day=uu.get(attr='day')-(uu.get(attr='dow')[0]+3)%7+n*7)
					break
				elif case == 'friday':
					tt = eon_date(year=uu.get(attr='year'),month= uu.get(attr='month')[0], day=uu.get(attr='day')-(uu.get(attr='dow')[0]+2)%7+n*7)
					break
				elif deadline == 'saturday':
					tt = eon_date(year=uu.get(attr='year'),month= uu.get(attr='month')[0], day=uu.get(attr='day')-(uu.get(attr='dow')[0]+1)%7+n*7)
					break
				elif case == 'sunday':
					tt = eon_date(year=uu.get(attr='year'),month= uu.get(attr='month')[0], day=uu.get(attr='day')-(uu.get(attr='dow')[0]+0)%7+n*7)
					break

		else:
			raise EonError( "%s, t.bop(deadline), invalid deadline '%s'"%(whoami(),deadline))

		return tt

	def eop(self,deadline):

		cases = ['year','quarter','month','week','day','monday','tuesday','wednesday','thursday','friday','saturday','sunday']
		if deadline in cases:
			deadline += "+0"

		for case in cases:

			m = re.search(case+'([-,+]\d+)',deadline)
			if m:
				uu = self

				tt = uu-uu.bop(case)
				n = long(m.group(1))
				if uu != uu.bop(case):
					n += 1

				if case == 'year':
					tt = eon_date(year=uu.get(attr='year')+n,month=1, day=1)
					break
				elif case == 'quarter':
					tt = eon_date(year=uu.get(attr='year'),month=(uu.get(attr='month')[0]-1)/3*3+1+n*3, day=1)
					break
				elif case == 'month':
					tt = eon_date(year=uu.get(attr='year'),month=uu.get(attr='month')[0]+n, day=1)
					break
				elif case == 'week':
					tt = eon_date(year=uu.get(attr='year'),month= uu.get(attr='month')[0], day=uu.get(attr='day')-(uu.get(attr='dow')[0]-1+n*7))
					break
				elif case == 'day':
					tt = eon_date(year=uu.get(attr='year'),month= uu.get(attr='month')[0], day=uu.get(attr='day')+n)
					break
				elif case == 'monday':
					tt = eon_date(year=uu.get(attr='year'),month= uu.get(attr='month')[0], day=uu.get(attr='day')-(uu.get(attr='dow')[0]+6)%7+n*7)
					break
				elif case == 'tuesday':
					tt = eon_date(year=uu.get(attr='year'),month= uu.get(attr='month')[0], day=uu.get(attr='day')-(uu.get(attr='dow')[0]+5)%7+n*7)
					break
				elif case == 'wednesday':
					tt = eon_date(year=uu.get(attr='year'),month= uu.get(attr='month')[0], day=uu.get(attr='day')-(uu.get(attr='dow')[0]+4)%7+n*7)
					break
				elif case == 'thursday':
					tt = eon_date(year=uu.get(attr='year'),month= uu.get(attr='month')[0], day=uu.get(attr='day')-(uu.get(attr='dow')[0]+3)%7+n*7)
					break
				elif case == 'friday':
					tt = eon_date(year=uu.get(attr='year'),month= uu.get(attr='month')[0], day=uu.get(attr='day')-(uu.get(attr='dow')[0]+2)%7+n*7)
					break
				elif case == 'saturday':
					tt = eon_date(year=uu.get(attr='year'),month= uu.get(attr='month')[0], day=uu.get(attr='day')-(uu.get(attr='dow')[0]+1)%7+n*7)
					break
				elif case == 'sunday':
					tt = eon_date(year=uu.get(attr='year'),month= uu.get(attr='month')[0], day=uu.get(attr='day')-(uu.get(attr='dow')[0]+0)%7+n*7)
					break

		else:
			raise EonError( "%s, t.bop(deadline), invalid deadline '%s'"%(whoami(),deadline))

		return tt

	def set_deadline(self,deadline):

		tt = self
		if deadline[0] in ['+','-']:
			for fragment in deadline.split(','):
				if fragment[0] == '+':
					tt = tt.eop(fragment[1:])
				elif fragment[0] == '-':
					tt = tt.bop(fragment[1:])
				else:
					raise EonError( "%s, t.set_deadline(deadline), missing sign in '%s'"%(whoami(),deadline))
		else:
			t1 = tt.set_deadline('+'+deadline)
			t2 = tt.set_deadline('-'+deadline)
			tt = t1 if t2 < tt else t2

		return tt

class eon_delta( eon ):

	def __init__ (self,
		ticday	= None,
		tictac	= None,
		exponent= g_EXPONENT,

		year	= None,
		day		= None,

		hour	= None,
		minute	= None,
		second	= None,
		chipsec	= None,

		ctime	= None,
		format	= None,
		regex	= None,
	):
		if g_EON_DEBUG: print self.__class__, 'in',whoami()

		self.exponent = exponent

		tictacs_per_second	= 10**exponent
		tictacs_per_day		= tictacs_per_second*g_SECONDS_PER_DAY

		if ticday != None:

			# Initialize by specifiying primary instance attributes 'ticday' and 'tictac'

			if tictac == None: tictac = 0

			self.ticday	= ticday
			self.tictac	= tictac if is_integer(tictac) else long(round(tictac))

		else:

			if is_string(ctime) and ctime != '':	# String input

				# Often a single unnamed argument containing a string with a time is specified.
				# This argument will end up in 'year'. This is run through split_date_format
				# or split_time_regex to set the time

				if regex == None:
					(day, hour, minute, second, chipsec) = \
						split_delta_format( ctime, format=format, exponent=exponent )
				else:
					(day, hour, minute, second, chipsec) = \
						split_delta_regex  ( ctime, regex  =regex  , exponent=exponent )

			else:

				(day, hour, minute, second, chipsec) = \
					fill_delta_fields( year, day, hour, minute, second, chipsec, exponent=exponent )

			self.ticday	= day

			self.hour	= hour
			self.minute	= minute
			self.second	= second
			self.chipsec= chipsec if is_integer(chipsec) else long(round(chipsec))

			self.tictac	= ((hour*g_MINUTES_PER_HOUR+minute)*g_SECONDS_PER_MINUTE+second)*10**exponent+self.chipsec

	 	self.normalize()

		return

	def get (self,
		format	= None,
		regex	= None,
		attr	= None
	):

		if format != None:

			result = format

			tictacs_per_second = 10**self.exponent

			# D#.ddd		D# same as DDD
			# D#:hh.ddd
			# D#:hh:ss.ddd
			# h#.ddd
			# h#:mm.ddd
			# h#:mm:ss.ddd
			# m#.ddd
			# m#:ss.ddd
			# s#.ddd

			key = None
			if 'D#' in result:			# D#[.ddd]: total number of days with optional fraction
				key = 'D#'
			else:						# DDD#[.ddd]: total number of days (padded with leading zeroes)
				n = 0
				while 'D'*(n+1) in result:
					n += 1
				if n > 0:
					key = 'D'*n

			if not key == None:			# D#[.ddd]: total number of days with optional fraction
				whole, fraction = self.days2
				whole    = ('%s' if key == 'D#' else '%0'+str(n)+'d')%whole
				result   = fill_fraction(result, key,whole,fraction*tictacs_per_second, exponent=self.exponent)

			if 'h#' in result or 'hh' in result:
				key = 'hh' if 'hh' in result else 'h#'
				whole, fraction = self.hours2
				if 'hh' in result: whole = self.hour
				whole    = ('%02d' if key == 'hh' else '%s' )%whole
				result   = fill_fraction(result, key, whole, fraction*tictacs_per_second, exponent=self.exponent)

			if 'm#' in result or 'mm' in result:
				key = 'mm' if 'mm' in result else 'm#'
				whole, fraction = self.minutes2
				if 'mm' in result: whole = self.minute
				whole    = ('%02d' if key == 'mm' else '%s')%whole
				result   = fill_fraction(result, key, whole, fraction*tictacs_per_second, exponent=self.exponent)

			if 's#' in result or 'ss' in result:
				key = 'ss' if 'ss' in result else 's#'
				whole = self.second if 'ss' in result else self.seconds[0]
				fraction = self.chipsec
				whole    = ('%02d' if key == 'ss' else '%s')%whole
				result   = fill_fraction(result, key, whole, fraction, exponent=self.exponent)

			if result == format:
				raise EonError( whoami()+", unrecognized format, '"+format+"'")

		elif regex != None:

			pass

		else:

			result = self.__getattr__( attr )

		return result

	def all_attr(self):

		return {
			'day'		: 'atom: days, <DD>:hh:mm:ss.%s'%('d'*self.exponent)	,
			'hour'		: 'aton: hours, DD:<hh>:mm:ss.%s'%('d'*self.exponent)	,
			'minute'	: 'atom: minutes, DD:hh:<mm>:ss.%s'%('d'*self.exponent)	,
			'second'	: 'atom: seconds, DD:hh:mm:<ss>.%s'%('d'*self.exponent)	,
			'chipsec'	: 'atom: fraction of second in units of 10^-%d'%self.exponent+', DD:hh:mm:ss.<%s>'%('d'*self.exponent)	,
			'years'		: 'duration in Julian years'							,
			'days'		: 'duration in days'									,
			'hours'		: 'duration in hours'									,
			'minutes'	: 'duration in minutes'									,
			'seconds'	: 'duration in seconds'									,
			'chipsecs'	: 'duration in units of 10^-%s seconds'%self.exponent	,
			'days2'		: 'duration in days as (integer,fraction) tuple'		,
			'hours2'	: 'duration in hours as (integer,fraction) tuple'		,
			'minutes2'	: 'duration in minutes as (integer,fraction) tuple'		,
			'seconds2'	: 'duration in seconds as (integer,fraction) tuple'		,
		}

	def __getattr__(self, name):
		if g_EON_DEBUG: print self.__class__, 'in',whoami()

		if name in self.__dict__:
			return self.__dict__[name]

		tictacs_per_second = 10**self.exponent

		if name == 'years':
			tictacs_per_day = g_SECONDS_PER_DAY*tictacs_per_second
			result = (self.ticday+float(self.tictac)/tictacs_per_day)/g_JULIAN_YEAR

		elif name == 'days':
			tictacs_per_day = g_SECONDS_PER_DAY*tictacs_per_second
			result = self.ticday+float(self.tictac)/tictacs_per_day

		elif name == 'hours':
			tictacs_per_hour = g_SECONDS_PER_HOUR*tictacs_per_second
			result = self.ticday*g_HOURS_PER_DAY+float(self.tictac)/tictacs_per_hour

		elif name == 'minutes':
			tictacs_per_minute = g_SECONDS_PER_MINUTE*tictacs_per_second
			result = self.ticday*g_MINUTES_PER_DAY+float(self.tictac)/tictacs_per_minute

		elif name == 'seconds':
			result = self.ticday*g_SECONDS_PER_DAY+float(self.tictac)/tictacs_per_second

		elif name == 'chipsecs':
			tictacs_per_day = g_SECONDS_PER_DAY*tictacs_per_second
			result = self.ticday*tictacs_per_day+self.tictac

		else:

			if 'day' not in self.__dict__:
				(self.day, self.hour, self.minute, self.second, self.chipsec) = fix_dhms(self.ticday, 0, 0, 0, self.tictac, exponent=self.exponent)

			if name in ['day','hour','minute','second','chipsec']:
				pass

			elif name == 'days2':
				tictacs_per_day = g_SECONDS_PER_DAY*tictacs_per_second
				self.days2 = (self.ticday,float(self.tictac)/tictacs_per_day)

			elif name == 'hours2':
				self.hours2 = (
					self.day*g_HOURS_PER_DAY+self.hour,
					(self.minute+(self.second+float(self.chipsec)/tictacs_per_second)/g_SECONDS_PER_MINUTE)/g_MINUTES_PER_HOUR
				)

			elif name == 'minutes2':
				self.minutes2 = (
					(self.day*g_HOURS_PER_DAY+self.hour)*g_MINUTES_PER_HOUR+self.minute,
					(self.second+float(self.chipsec)/tictacs_per_second)/g_SECONDS_PER_MINUTE
				)

			elif name == 'seconds2':
				self.seconds2 = (
					((self.day*g_HOURS_PER_DAY+self.hour)*g_MINUTES_PER_HOUR+self.minute)*g_SECONDS_PER_MINUTE+self.second,
					float(self.chipsec)/tictacs_per_second
				)

			else:
				raise EonError(whoami()+", unknown eon_delta attribute, '%s'"%name)

			result = self.__dict__[name]

		return result

	def __radd__ (self, tt):
		if g_EON_DEBUG: print self.__class__, 'in eon_delta', whoami()
		return self.linear_transform(tt, factor=(1,1), exponent=max(self.exponent,tt.exponent))

	def __rsub__(self, tt):
		if g_EON_DEBUG: print self.__class__, 'in eon_delta', whoami()
		return self.linear_transform(tt, factor=(-1,1), exponent=max(self.exponent,tt.exponent))

	def __neg__(self):
		if g_EON_DEBUG: print self.__class__, 'in eon_delta', whoami()
		return self.linear_transform(factor=-1, exponent=self.exponent)

	def __mul__(self, multiplier):
		if g_EON_DEBUG: print self.__class__, 'in eon_delta', whoami()
		return self.multiply( multiplier )

	def __rmul__(self, multiplier):
		if g_EON_DEBUG: print self.__class__, 'in eon_delta', whoami()
		return self.multiply( multiplier )

	def __div__(self, divider):
		if g_EON_DEBUG: print self.__class__, 'in eon_delta', whoami()
		return self.divide( divider )

	def __floordiv__(self, divider):
		if g_EON_DEBUG: print self.__class__, 'in eon_delta', whoami()
		return self.divmod(divider)[0]

	def __divmod__(self, divider):
		if g_EON_DEBUG: print self.__class__, 'in eon_delta', whoami()
		return self.divmod(divider)

	def __abs__(self):
		if g_EON_DEBUG: print self.__class__, 'in eon_delta', whoami()
		return eon_delta( ticday=abs(self.ticday), tictac=abs(self.tictac) )

	def multiply(self, multiplier):
		if g_EON_DEBUG: print self.__class__, 'in eon_delta', whoami()
		return self.linear_transform(factor=multiplier) if is_number(multiplier) else NotImplemented

	def divide(self, divider):
		if g_EON_DEBUG: print self.__class__, 'in eon_delta', whoami()

		if is_number( divider ):
			u = self.linear_transform(factor=1.0/divider)
		elif isinstance(divider,eon_delta):
			tictacs_per_day = g_SECONDS_PER_DAY*10**self.exponent
			u = float(self.ticday+tictacs_per_day*self.tictac)/float(divider.ticday+tictacs_per_day*divider.tictac)
		else:
			u = NotImplemented

		return u

	def divmod(self, tt):
		if g_EON_DEBUG: print self.__class__, 'in',whoami()

		if isinstance(tt,eon_delta):

			exponent = max(self.exponent,tt.exponent)

			tictacs_per_day = g_SECONDS_PER_DAY*10**exponent

			# Could cause integer overflow??
			du = self.ticday*tictacs_per_day+self.tictac*10**(exponent-self.exponent)
			dt = tt.ticday  *tictacs_per_day+tt.tictac  *10**(exponent-tt.exponent  )

			(nn, du) = divmod(du,dt)
			nn = long(nn)

			(ticday,tictac) = fix_ds(0,0,0,0,du,exponent=exponent)

			u = self.make_instance('eon_delta',ticday,tictac,exponent)

			u = (nn,u)

		else:

			u = NotImplemented

		return u

	# This method cannot be moved into the superclass 'eon' because it needs
	# access to the eon_date and eon_delta classes

	def make_instance( self, eon_type, ticday, tictac, exponent ):
		if g_EON_DEBUG: print self.__class__, 'in',whoami()

		if eon_type == 'eon_date':
			u = eon_date(
					ticday	= ticday,
					tictac	= tictac,
					exponent= exponent
				)
		else:
			u = eon_delta(
					ticday	= ticday,
					tictac	= tictac,
					exponent= exponent
				)

		u.normalize()

		return u

	def bot(self,dt):

		u = self.divmod(abs(dt))
		if type(u) == type(NotImplemented):
			raise EonError( whoami()+", t.bot(dt), dt must be delta time")

		return dt*u[0]

	def eot(self,dt):

		u = self.divmod(abs(dt))
		if type(u) == type(NotImplemented):
			raise EonError( whoami()+", t.eot(dt), dt must be delta time")

		return dt*(u[0]+1)

	def round(self,dt):
		'''
		#+
		# NAME:
		#	eon_delta.round
		# PURPOSE:
		#	Rounds to nearest multiple of a specified delta time
		# CALLING SEQUENCE:
		#	dt_rounded = dt.round(du)
		# INPUTS:
		#	du			delta time object
		# OUTPUTS:
		#	dt_rounded	delta time object, rounded to nearest du
		#-
		'''

		u = self.divmod(abs(dt))
		if type(u) == type(NotImplemented):
			raise EonError( whoami()+", t.round(dt), dt must be delta time")

		return dt*u[0] if u[1]/abs(dt) < 0.5 else dt*(u[0]+1)
