from math import floor
import re

g_DAYS_PER_NORMAL_YEAR	= 365
g_DAYS_PER_LEAP_YEAR 	= 366
g_JULIAN_YEAR			= 365.25
g_BESSEL_YEAR			= 365.242198781
g_JD2000_B1900			= 36524.68648	# Nr of days from 2000/01/01 00 UT to B1900
g_JD2000_POSIX			= 10957			# Nr of days from 1970/01/01 00 UTC to 2000/01/01 00 UTC

g_SECONDS_PER_MINUTE	= 60
g_MINUTES_PER_HOUR		= 60
g_HOURS_PER_DAY			= 24
g_SECONDS_PER_HOUR		= g_SECONDS_PER_MINUTE*g_MINUTES_PER_HOUR
g_SECONDS_PER_DAY		= g_SECONDS_PER_HOUR*g_HOURS_PER_DAY
g_MINUTES_PER_DAY		= g_SECONDS_PER_DAY/g_SECONDS_PER_MINUTE
g_EXPONENT				=  9			# Default precision for time of day is nanosecond
g_GREG2JULIAN 			= 13			# Offset in days between Gregorian and Julian calender in 2000

g_NJD_NJD				=       0		# Nr of days from  2000/01/01 00 UTC (njd is counted from noon)
g_NJD_MJD				=   51545		# Nr of days from  1858/11/16 00 UTC (mjd is counted from noon)
g_NJD_JD				= 2451545		# Nr of days from -4713/11/24 00 UTC (jd is counted from noon)

g_EON_DEBUG				= False

# Class for raising execptions

class EonError(Exception):
	def __init__(self, message):
		self.message = message
	def __str__(self):
		return self.message

# Gives name of calling function
# used to set up message when raising exception

def whoami():
	import sys
	return sys._getframe(1).f_code.co_name

def is_integer ( var ):
	return isinstance(var,int) or isinstance(var,long)

def is_float( var ):
	return isinstance(var,float)

def is_number( var ):
	return is_integer(var) or is_float(var)

def is_string ( var ):
	return isinstance(var,str)

def best_guess_for_number( var, n=10 ):
	"""
	# NAME:
	#	best_guess_for_number
	# PURPOSE:
	#	Tries to extract a valid float or integer number from
	#	a string. If a number (float or integer) is specified
	#	then the input is returned unmodified.
	# CALLING SEQUENCE:
	#	value = best_guess_for_number( var, n=10 )
	# INPUTS:
	#	var         string containing single number
	#				representing time.
	# OPTIONAL INPUTS:
	#	n=10        maximum number of digits before the decimal
	#	            point
	# OUTPUTS:
	#	value       number extracted from 'var' as float or
	#	            integer number.
	# PROCEDURE:
	#	- If the input is already a float or integer than the
	#	  input value is returned.
	#	- For a string the following steps are done:
	#	  1. Try to convert to number using 'float' (if the input
	#	     contains a dot, or 'long' if not.
	#	  2. If this fails, then extract the number with the longest
	#	     number of leading digits (but no more then 'n')
	#	  3. If no number is found, raise error
	#	  4. If number is found convert to float number.
	# MODIFICATION HISTORY:
	#	JUL-2009, Paul Hick (UCSD/CAIDA/CASS; pphick@ucsd.edu)
	"""

	if is_string( var ):

		try:
			value = float(var) if '.' in var else long(var)

		except:
			m = re.search('\d{%s,}(\.\d+)?'%n,var)
			while not m and n > 1:
				n -= 1
				m = re.search('\d{%s,}(\.\d+)?'%n,var)

			if not m:
				raise EonError(whoami()+", no good number found in string, '%s'"%var)

			var = m.group(0)
			value = float(var) if '.' in var else long(var)

	else:

		value = var

	return value

def disassemble_number( var, exponent=g_EXPONENT, n=10 ):

	if is_string( var ):

		try:
			value = float(var) if '.' in var else long(var)

		except:
			m = re.search('\d{%s,}(\.\d+)?'%n,var)
			while not m and n > 1:
				n -= 1
				m = re.search('\d{%s,}(\.\d+)?'%n,var)

			if not m:
				raise EonError(whoami()+", no good number found in string, '%s'"%var)

			var = m.group(0)
			value = float(var) if '.' in var else long(var)

		if var[-1] == '.':
			var = var[0:-1]

		if '.' in var:
			p = var.index('.')
			value = ( long(var[0:p]), long(var[p+1:])*10**(exponent-(len(var)-p-1)) )
		else:
			value = ( long(var), 0 )

	else:

		value = ( var, 0  )

	return value

def weekday( days ):
	"""
	# NAME:
	#	weekday
	# PURPOSE:
	#	Gives the weekday for the time specified as the number
	#	of days since 2000/01/01 00:00:00 (Gregorian)
	# CALLING SEQUENCE:
	#	day = weekday( days )
	# INPUTS:
	#	days        integer scalar
	#	                days since 2000/01/01 00:00:00
	# OUTPUTS:
	#	(day,name)  tuple of integer and char
	#	                day is day of week (Monday=1, Sunday=7)
	#	                name is full name of weekday
	# PROCEDURE:
	#	2000/01/01 in the Gregorian calender was a Saturday
	#	day of week 6).
	#	Note that 2000/01/01 in the Julian calendar corresponds
	#	to 2000/01/14 in the Gregorian calendar, i.e. a Friday
	# MODIFICATION HISTORY:
	#	JUL-2009, Paul Hick (UCSD/CAIDA/CASS; pphick@ucsd.edu)
	"""
	w = (5+days)%7
	return (w+1,['Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday'][w])

def convert_month( yr, month ):
	"""
	# NAME:
	#	convert_month
	# PURPOSE:
	#	Converts integer month to character month and v.v.
	# CATEGORY:
	#	general/python
	# CALLING SEQUENCE:
	#	(yr, imonth, cmonth) = convert_month( yr, month )
	# INPUTS:
	#	yr      scalar; integer
	#	            specifies the year
	#	month   scalar; integer or string
	#	            if string then the first 3 characters must
	#	            match one of JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,
	#	            SEP,OCT,NOV,DE (case insensitive).
	# OUTPUTS:
	#	(yr, imonth, cmonth)
	#	        tuple with:
	#	        yr      integer year
	#	        imonth  integer month in [1..12]
	# 	        cmonth  string with full name of month
	# SIDE EFFECT:
	#	If an invalid string is specified for the month,
	#	then an error exception is raised.
	# PROCEDURE:
	#	For numeric input of the month values outside the range 1-12
	#	are mapped back into this range, and 'yr' is updated
	#	accordingly. e.g. if month=14 and yr=2003 on input, then
	#	imonth=2 and yr=2004 is returned.
	#
	#	For character input of the month the first three characters
	#	are used to identify the integer month (case insensitive).
	#	If no match is found an exception is raised
	#	(see SIDE EFFECTS)
	#
	#	The character representation is the full name of the month
	#	with the first char in uppercase (e.g. January)
	# MODIFICATION HISTORY:
	#	JUL-2009, Paul Hick (UCSD/CAIDA/CASS; pphick@ucsd.edu)
	"""
	cmonths = ['January','February','March','April','May','June','July','August','September','October','November','December']

	# Check for integers (pos or neg)
	if is_string(month) and	(month.isdigit() or (len(month) > 1 and month[0] in ['-','+'] and month[1:].isdigit())):
		month = int(month)

	if is_integer(month):				# Integer month -> char month

		(iy,im) = divmod(month-1,12)
		iy += yr

	else:								# Char month -> integer month

		months = [cm[0:3].upper() for cm in cmonths]
		cm = month[0:3].upper()		# Switch to uppercase

		if cm not in months:
			raise EonError(whoami()+", invalid character input for month, '%s'"%month)

		iy = yr
		im = months.index(cm)

	return (iy, im+1, cmonths[im])

def is_leap_year( yr, julian=False):

	"""
	# NAME:
	#	isleapyear
	# PURPOSE:
	#	Check whether specified year is a leap year
	# CALLING SEQUENCE:
	#	leap = isleapyear(yr [, [julian=]True/False])
	# INPUTS:
	#	yr		scalar, integer
	#				year
	# OPTIONAL INPUT PARAMETERS:
	#	julian	boolean, default: False
	#				by default the Gregorian calendar is assumed.
	#				if julian=True the Julian calendar is assumed
	# PROCEDURE:
	#	In the Julian calendar every year that is a multiple of four is a
	#	leap year. In the Gregorian calendar centurion years (divisible
	#	by 100) are not leap years, unless they are also divisible by 400.
	#	So 1700, 1800, 1900 were not leap yeass, but 1600 and 2000 were.
	# MODIFICATION HISTORY:
	#	JUL-2009, Paul Hick (UCSD/CAIDA/CASS; pphick@ucsd.edu
	"""

	return yr%4 == 0 and (julian or yr%100 != 0 or yr%400 == 0)

def days_in_month( yr, month, julian=False ):
	"""
	# NAME:
	#	days_in_month
	# PURPOSE:
	#	Gives the number of days in a given month
	# CALLING SEQUENCE:
	#	days = days_in_month(yr, month [, [julian=]Trus/False])
	# INPUTS:
	#	yr          integer scalar
	#	                year
	#	month       integer or character scalar
	#	                month
	# OPTIONAL INPUT PARAMETERS:
	#	julian      boolean, default: False
	#	                by default the Gregorian calendar is assumed.
	#	                if julian=True the Julian calendar is assumed
	# OUTPUTS:
	#	days        integer scalar
	#	                number of days in month
	# CALLS:
	#	convert_month, is_leap_year
	# PROCEDURE:
	#	Trivial
	# MODIFICATION HISTORY:
	#	JUL-2009, Paul Hick (UCSD/CAIDA/CASS; pphick@ucsd.edu)
	"""
	(yr, im, cm) = convert_month(yr, month)
	return [31,28+is_leap_year(yr,julian),31,30,31,30,31,31,30,31,30,31][im-1]

def days_in_past_months( yr, month, julian=False ):
	"""
	# NAME:
	#	days_in_past_months
	# PURPOSE:
	#	Gives the number of days from the start of the year
	#	upto, but excluding, the specified month
	# CALLING SEQUENCE:
	#	days = days_in_past_months(yr, month [, [julian=]Trus/False])
	# INPUTS:
	#	yr          integer scalar
	#	                year
	#	month       integer or character scalar
	#	                month
	# OPTIONAL INPUT PARAMETERS:
	#	julian      boolean, default: False
	#	                by default the Gregorian calendar is assumed.
	#	                if julian=True the Julian calendar is assumed
	# OUTPUTS:
	#	days        integer scalar
	#	                number of days upto and excluding (yr, month)
	# CALLS:
	#	convert_month, is_leap_year
	# MODIFICATION HISTORY:
	#	JUL-2009, Paul Hick (UCSD/CAIDA/CASS; pphick@ucsd.edu)
	"""
	(yr, im, cm) = convert_month(yr, month)
	im -= 1

	return im*31-min([im//2,3])-max([(im-7)//2, 0])-(im >= 2)*(2-is_leap_year(yr, julian))

def days_in_year( yr, julian=False ):
	"""
	# NAME:
	#	days_in_year
	# PURPOSE:
	#	Gives the number of days in the specified year
	# CALLING SEQUENCE:
	#	days = days_in_year(yr [, [julian=]Trus/False])
	# INPUTS:
	#	yr          integer scalar
	#	                year
	# OPTIONAL INPUT PARAMETERS:
	#	julian      boolean, default: False
	#	                by default the Gregorian calendar is assumed.
	#	                if julian=True the Julian calendar is assumed
	# OUTPUTS:
	#	days        integer scalar
	#	                number of days in year
	# CALLS:
	#	is_leap_year
	# MODIFICATION HISTORY:
	#	JUL-2009, Paul Hick (UCSD/CAIDA/CASS; pphick@ucsd.edu)
	"""
	return g_DAYS_PER_LEAP_YEAR if is_leap_year(yr,julian) else g_DAYS_PER_NORMAL_YEAR

def days_in_past_years( yr, julian=False):
	"""
	# NAME:
	#	days_in_past_years
	# PURPOSE:
	#	Number of days from 2000/01/01 to 2000+yr/01/01
	# INPUTS:
	#	yr          integer scalar
	#	                years since 2000/01/01
	#	                (i.e. the full year, minus 2000)
	# OPTIONAL INPUT PARAMETERS:
	#	julian      boolean, default: False
	#	                by default the Gregorian calendar is assumed.
	#	                if julian=True the Julian calendar is assumed
	# OUTPUTS:
	#	days        integer scalar
	#	                number of days since 2000/01/01
	# MODIFICATION HISTORY:
	#	JUL-2009, Paul Hick (UCSD/CAIDA/CASS; pphick@ucsd.edu)
	"""
	A  = yr			# Whole years from 2000/01/01 to same of yr
	C  = A > 0
	D  = A < 0		# Needed for floor division (not present in original IDL code)
	A -= C
	B  = (1-julian)*A

	# The trailing part of the expression below is the number of leap years
	# between 2000/01/01 and "yr" (2000 was a leap year).

	return g_DAYS_PER_NORMAL_YEAR*yr+A//4+D+C-B//100+B//400

def fix_yr_month_day( yr, month, day, julian=False ):
	"""
	# NAME:
	#	fix_yr_month_day
	# PURPOSE:
	#	Checks for out of range values for month and/or day,
	#	and maps them back to valid values
	# CaLLING SEQUENCE:
	#	(yr,month,day) = fix_yr_month_day(yr,month,day)
	# INPUTS:
	#	yr          integer scalar
	#	month       integer or char month
	#	day         day of month
	# OPTIONAL INPUT PARAMETERS:
	#	julian      boolean, default: False
	#	                by default the Gregorian calendar is assumed.
	#	                if julian=True the Julian calendar is assumed
	# OUTPUTS:
	#	(yr, month, cmonth, day)
	#	            tuple
	#	                year, month and day of month, mapped back to
	#	                valid values.
	#	                Output month is of same type as input month
	#	                (integer or char)
	# CALLS:
	#	convert_month, days_in_month, is_integer
	# PROCEDURE:
	#	Adjustments are made, subtracting/adding one month
	#	at a time until values for month and day of month
	#	are valid
	# MODIFICATION PROCEDURE:
	#	JUL-2009, Paul Hick (UCSD/CAIDA/CASS; pphick@ucsd.edu)
	"""

	# Sets im to [1..12]
	(yr, im, cm) = convert_month( yr, month )

	while day < 1:
		(yr, im, cm) = convert_month( yr, im-1 )
		day += days_in_month( yr, im, julian )

	days = days_in_month( yr, im, julian )
	while day > days:
		day -= days
		(yr, im, cm) = convert_month( yr, im+1 )
		days = days_in_month( yr, im, julian )

	return (yr, im, cm, day)

def fix_yr_doy( yr, doy, julian=False ):
	"""
	# NAME:
	#	fix_yr_doy
	# PURPOSE:
	#	Checks for out of range values for day of year,
	#	and maps it back to valid values
	# INPUTS:
	#	yr          integer scalar; year
	#	doy         integer scalar; day of year
	# OPTIONAL INPUT PARAMETERS:
	#	julian      boolean, default: False
	#	                by default the Gregorian calendar is assumed.
	#	                if julian=True the Julian calendar is assumed
	# OUTPUTS:
	#	(yr, doy)   tuple
	#	                year, doy, mapped back to valid values.
	# CALLS:
	#	convert_month, days_in_year
	# PROCEDURE:
	#	Adjustments are made, subtracting/adding one year
	#	at a time until value for day of year is valid
	# MODIFICATION PROCEDURE:
	#	JUL-2009, Paul Hick (UCSD/CAIDA/CASS; pphick@ucsd.edu)
	"""
	while doy < 1:
		yr  -= 1
		doy += days_in_year(yr,julian)

	days = days_in_year(yr,julian)
	while doy > days:
		doy -= days
		yr  += 1
		days = days_in_year(yr,julian)

	return ( yr, doy )

def fix_ds(day, hour, minute, second, chipsec, exponent=g_EXPONENT):
	"""
	# NAME:
	#	fix_ds
	# PURPOSE:
	#	Reduces time-of-day quantifiers day, hour, minute second
	#	and chipsec to day and chipsec
	# INPUTS:
	#	day, hour, minute, second, chipsec
	#	            scalar; integer or float
	# OPTIONAL INPUT PARAMETERS:
	#	exponent    scalar; integer; default: g_EXPONENT
	# OUTPUTS:
	#	day, chipsec
	#	            scalars; day will be integer, chipsec is integer or
	#	            float in range [0..10^exponent-1]
	# PROCEDURE:
	#	Input hour, minute, second and chipsec can be "out of range",
	#	i.e. hour can be negative or >= 24, minute and second can
	#	be negative or >= 60, chipsec can be negative or
	#	>= 10^exponent.
	#	Adjustments are made, bringing chipsec into its
	#	customary range.
	# MODIFICATION PROCEDURE:
	#	JUL-2009, Paul Hick (UCSD/CAIDA/CASS; pphick@ucsd.edu)
	"""
	tictacs_per_second = 10**exponent
	tictacs_per_day	   = tictacs_per_second*g_SECONDS_PER_DAY

	(i,f) = divmod(day,1)
	day   = long(i)

	hour += f*g_HOURS_PER_DAY

	chipsec += ((hour*g_MINUTES_PER_HOUR+minute)*g_SECONDS_PER_MINUTE+second)*tictacs_per_second

	(i,f) = divmod(chipsec,tictacs_per_day)
	day += long(i)
	chipsec = f if is_integer(f) else long(f) if f == long(f) else f

	return (day, chipsec)

def fix_dhms(day, hour, minute, second, chipsec, exponent=g_EXPONENT):
	"""
	# NAME:
	#	fix_dhms
	# PURPOSE:
	#	Normalizes time-of-day quantifiers day, hour, minute
	#	second and chipsec
	# INPUTS:
	#	day, hour, minute, second, chipsec
	#	            scalars; integer or float
	# OPTIONAL INPUT PARAMETERS:
	#	exponent    scalar; integer; default: g_EXPONENT
	# OUTPUTS:
	#	day, hour, minute, second, chipsec
	#	            scalar; day, hour, minute, second are integer
	#	            in the customary range for each unit, i.e.
	#	            hour in [0:23], minute and second in [0..59].
	#	            chipsec will be integer or float in the range
	#	            [0..10^exponent-1]
	# PROCEDURE:
	#	Input hour, minute, second and chipsec can be "out of range",
	#	i.e. hour can be negative or >= 24, minute and second can
	#	be negative or >= 60, chipsec can be negative or
	#	>= 10^exponent.
	#	Adjustments are made, subtracting/adding as appropriate
	#	to bring each unit in is customary range.
	# MODIFICATION PROCEDURE:
	#	JUL-2009, Paul Hick (UCSD/CAIDA/CASS; pphick@ucsd.edu)
	"""
	tictacs_per_second = 10**exponent

	(i,f) = divmod(day,1)
	day   = long(i)
	hour += f*g_HOURS_PER_DAY

	(i,f) = divmod(hour,1)
	hour  = long(i)
	minute += f*g_MINUTES_PER_HOUR

	(i,f) = divmod(minute,1)
	minute = long(i)
	second += f*g_SECONDS_PER_MINUTE

	(i,f) = divmod(second,1)
	second = long(i)
	chipsec += f*tictacs_per_second

	(i,f) = divmod(chipsec,tictacs_per_second)
	second += long(i)
	chipsec = f if is_integer(f) else long(f) if f == long(f) else f

	(i,second) = divmod(second,g_SECONDS_PER_MINUTE)
	minute += i

	(i,minute) = divmod(minute,g_MINUTES_PER_HOUR)
	hour += i

	(i,hour) = divmod(hour,g_HOURS_PER_DAY)
	day += i

	return (day, hour, minute, second, chipsec)

def underestimate_years( yr, doy, days, julian=False ):
	"""
	# NAME:
	#	underestimate_years
	# PURPOSE:
	#	Underestimate the number of whole years in time
	#	specified as days since 2000/01/01 00:00:00
	# CALLING SEQUENCE:
	#	(yr,doy) = underestimate_years( yr, doy, days [, julian=True/False )
	# INPUTS:
	#	yr      integer scalar;
	#	            years since 2000/01/01
	#	doy	integer scalar;
	#	            remaining part of days; may still contain whole year
	#	days    integer scalar
	#	            days since 2000/01/01
	# OPTIONAL INPUT PARAMETERS:
	#	julian  boolean, default: False
	#	            by default the Gregorian calendar is assumed.
	#	            if julian=True the Julian calendar is assumed
	# OUTPUTS:
	#	(yr,doy)
	#	        tuple of two integers
	#	            improved estimates for yr and doy
	#	            yr: years since 2000/01/01
	#	            doy: remaining part of days
	# MODIFICATION HISTORY:
	#	JUL-2009, Paul Hick (UCSD/CAIDA/CASS; pphick@ucsd.edu)
	"""
	# Underestimate # full years in "doy" and update yr
	# This is the new best estimate for "yr"

	yr += doy//g_DAYS_PER_LEAP_YEAR

	# Calculate number of days in "doy" not accounted for by
	# subtracting the number of days in the new "yr"

	doy = days-days_in_past_years(yr,julian)

	return (yr, doy)

def underestimate_months( yr, month, day, doy, julian=False ):

	"""
	# NAME:
	#	underestimate_months
	# PURPOSE:
	#	Underestimate number of whole months in specified
	#	day of year
	# CALLING SEQUENCE:
	#	(month,day) = underestimate_months( yr, month, day, doy, julian )
	# INPUTS:
	#	yr      integer scalar;
	#	            year
	#	month   integer scalar
	#	            month
	#	day     integer scalar
	#	            day of month; may still contain whole month
	#	doy     integer doy
	#	            day of year
	# OPTIONAL INPUT PARAMETERS:
	#	julian  boolean, default: False
	#	            by default the Gregorian calendar is assumed.
	#	            if julian=True the Julian calendar is assumed
	# OUTPUTS:
	#	(month, day)
	#	        tuple of two integers
	#	            improved estimates for month and day of month
	# CALLS:
	#	days_in_past_months
	# MODIFICATION HISTORY:
	#	JUL-2009, Paul Hick (UCSD/CAIDA/CASS; pphick@ucsd.edu)
	"""

	month += (day-1)//31	# Underestimate # full months in "day" and update month
	day = doy-days_in_past_months( yr, month+1, julian )

	return (month, day)

def year_doy( days, julian=False ):
	"""
	# NAME:
	#	year_doy
	# PURPOSE:
	#	Get year and day of year from days elapsed since
	#	2000/01/01 00:00:00 (Greg)
	# CALLING SEQUENCE:
	#	(yr,doy) = year_doy( days [, julian=True/False] )
	# INPUTS:
	#	days    scalar; any numerical type
	#	            days since 2000/01/01 00:00:00 (Greg)
	#	            (can include time of day as fraction)
	# OPTIONAL INPUT PARAMETERS:
	#	julian  boolean, default: False
	#	            by default the Gregorian calendar is assumed.
	#	            if julian=True the Julian calendar is assumed
	# CALLS:
	#	underestimate_years, fix_yr_doy
	# MODIFICATION HISTORY:
	#	JUL-2009, Paul Hick (UCSD/CAIDA/CASS; pphick@ucsd.edu)
	"""
	if is_integer(days):			# Check for integer ("floor" makes float value)
		fraction = 0
	else:
		(days, fraction) = divmod(days,1)
		days = long(days)

	# Initialize output variables
	# Number of integer years and integer days since 2000/01/01

	yr  = 0
	doy = days

	# The value of "days" never changes
	# Keep modifying "doy" until -g_DAYS_PER_NORMAL_YEAR <= "doy" <= g_DAYS_PER_NORMAL_YEAR

	(yr,doy) = underestimate_years( yr, doy, days, julian )
	while abs(doy) > g_DAYS_PER_NORMAL_YEAR:
		(yr,doy) = underestimate_years( yr, doy, days, julian )

	# Now -365 <= "doy" <= 365. "doy" is positive/negative for dates
	# after/before 2000/01/01. The input "days" corresponds to the day
	# "doy" after the start of year "yr"

	# Add 2000 to "yr" and 1 to "doy" to start counting at doy=1 instead of doy=0

	yr  += 2000
	doy += 1

	# Note that for "yr" = -418 (= 1582 AD), "doy" >= 0 already, so the
	# correction for the number of days (only 355 in 1582) is not done.

	(yr, doy) = fix_yr_doy( yr, doy, julian )

	# Restore fraction for time of day

	if fraction != 0:
		doy += fraction

	return (yr, doy)

def days_since_2000( yr, doy, julian=False ):
	"""
	# NAME:
	#	days_since_2000
	# PURPOSE:
	#	Get days since 2000/01/01 00:00:00 from year and day of year
	# CALLING SEQUENCE:
	#	days = days_since_2000( yr, doy [, julian=True/False] )
	# INPUTS:
	#	yr      integer scalar
	#	            year
	#	doy     scalar of any numerical type
	#	            day of year (incl. fraction for time of day)
	# OPTIONAL INPUT PARAMETERS:
	#	julian  boolean, default: False
	#	            by default the Gregorian calendar is assumed.
	#	            if julian=True the Julian calendar is assumed
	# OUTPUTS:
	#	days    scalar; same numerical type as doy
	#	            days since 2000/01/01 00:00:00
	#	            (incl. fraction for time of day)
	# CALLS:
	#	underestimate_years, fix_yr_doy
	# MODIFICATION HISTORY:
	#	JUL-2009, Paul Hick (UCSD/CAIDA/CASS; pphick@ucsd.edu)
	"""

	days = days_in_past_years( yr-2000, julian)

	if doy != 1:
		days += doy-1

	return days

def doy2month_day( yr, doy, julian=False ):

	"""
	# NAME:
	#	doy2month_day
	# PURPOSE:
	#	Get yr, month and day of month from yr and day of year
	# CALLING SEQUENCE:
	#	(yr,month,day) = doy2month_day( yr, doy [, julian=True/False] )
	# INPUTS:
	#	yr      integer scalar
	#	            year
	#	doy     scalar of any numerical type
	#	            day of year (incl. fraction for time of day)
	# OPTIONAL INPUT PARAMETERS:
	#	julian  boolean, default: False
	#	            by default the Gregorian calendar is assumed.
	#	            if julian=True the Julian calendar is assumed
	# OUTPUTS:
	#	(yr,month,cmonth,day)
	#	        tuple with year, month and day of month
	#	            yr and month are integers, day is of same
	#	            numerical type as input doy
	# CALLS:
	#	fix_yr_doy, under_estimate_months, days_in_month
	# MODIFICATION HISTORY:
	#	JUL-2009, Paul Hick (UCSD/CAIDA/CASS; pphick@ucsd.edu)
	"""

	# If the input yr and doy are the same type of variable (as opposed to a scalar-array
	# combination, we can correct for negative doy or doy larger than the number of days
	# in the year by adjusting both the yr and doy arrays.

	if is_integer(doy):
		fraction = 0
	else:
		(doy,fraction) = divmod(doy,1)
		doy = long(doy)

	# Fix out-of-bounds "doy" values
	# This sets yr to its final value

	(yr, doy) = fix_yr_doy( yr, doy, julian )

	im = 0				# Months past
	day = doy

	(im, day) = underestimate_months( yr, im, day, doy, julian )
	while day > 31:
		(im, day) = underestimate_months( yr, im, day, doy, julian )

	im += 1				# Month in which 'day' is located

	(yr, im, cm, day) = fix_yr_month_day( yr, im, day, julian)

	if fraction != 0:
		day += fraction

	return ( yr, im, cm, day )

def month_day2doy( yr, month, day, julian=False ):
	"""
	# NAME:
	#	month_day2doy
	# PURPOSE:
	#	Get yr and day of year from yr, month and day of month
	# CALLING SEQUENCE:
	#	(yr,doy) = month_day2doy( yr, month, day [, julian=True/False] )
	# INPUTS:
	#	yr          integer scalar
	#	                year
	#	month       scalar of type integer or char
	#	day         scalar of any numerical type
	#	                day of month (incl. fraction for time of day)
	# OPTIONAL INPUT PARAMETERS:
	#	julian      boolean, default: False
	#	                by default the Gregorian calendar is assumed.
	#	                if julian=True the Julian calendar is assumed
	# OUTPUTS:
	#	(yr,doy)    tuple with year and day of year
	#	                yr is integers, doy is of same
	#	                numerical type as input day
	# CALLS:
	#	convert_month, days_in_past_months, fix_yr_doy
	# MODIFICATION HISTORY:
	#	JUL-2009, Paul Hick (UCSD/CAIDA/CASS; pphick@ucsd.edu)
	"""

	(yr, im, cm, day) = fix_yr_month_day(yr, month, day, julian=julian)

	if is_integer(day):
		fraction = 0
	else:
		(day, fraction) = divmod(day,1)
		day = long(day)

	doy = days_in_past_months(yr, im, julian)+day

	(yr, doy) = fix_yr_doy(yr, doy, julian)

	if fraction != 0:
		doy += fraction

	return (yr, doy)

def gregorian_to_julian( yr, doy ):
	"""
	# NAME:
	#	gregorian_to_julian
	# PURPOSE:
	#	Converts (yr,doy) in Gregorian calendary to (yr,doy) in
	#	Julian calendar
	# CALLING SEQUENCE:
	#	(yr,doy) = gregorian_to_julian( yr, doy )
	# INPUTS:
	#	yr          integer scalar
	#	                year
	#	doy         scalar of any numerical type
	#	                day of year (incl. fraction for time of day)
	# OUTPUTS:
	#	(yr,doy)    tuple with year and day of year in Julian calendar
	#	                yr is integer, doy is of same
	#	                numerical type as input day
	# CALLS:
	#	year_doy, days_since_2000
	# PROCEDURE:
	#	2000/01/01 (Julian) = 2000/01/14 (Gregorian) (13 day difference)
	#	1582/10/05 (Julian) = 1582/10/15 (Gregorian) (10 day difference)
	#	3-day difference due to 1700, 1800, 1900 which are Julian leap years
	#	but non-leap Gregrorian years
	# MODIFICATION HISTORY:
	#	JUL-2009, Paul Hick (UCSD/CAIDA/CASS; pphick@ucsd.edu)
	"""
	return year_doy( days_since_2000(yr, doy, julian=False)-g_GREG2JULIAN, julian=True )

def julian_to_gregorian( yr, doy ):
	"""
	# NAME:
	#	julian_to_gregorian
	# PURPOSE:
	#	Converts (yr,doy) in Julian calendary to (yr,doy) in
	#	Gregorian calendar
	# CALLING SEQUENCE:
	#	(yr,doy) = julian_to_gregorian( yr, doy )
	# INPUTS:
	#	yr          integer scalar
	#	                year
	#	doy         scalar of any numerical type
	#	                day of year (incl. fraction for time of day)
	# OUTPUTS:
	#	(yr,doy)    tuple with year and day of year in Gregorian calendar
	#	                yr is integer, doy is of same
	#	                numerical type as input day
	# CALLS:
	#	year_doy, days_since_2000
	# PROCEDURE:
	#	2000/01/01 (Julian) = 2000/01/14 (Gregorian) (13 day difference)
	#	1582/10/05 (Julian) = 1582/10/15 (Gregorian) (10 day difference)
	#	3-day difference due to 1700, 1800, 1900 which are Julian leap years
	#	but non-leap Gregrorian years
	# MODIFICATION HISTORY:
	#	JUL-2009, Paul Hick (UCSD/CAIDA/CASS; pphick@ucsd.edu)
	"""
	return year_doy( days_since_2000(yr, doy, julian=True)+g_GREG2JULIAN, julian=False )

def disassemble_timezone(tz_field):

#ALMT	Alma-Ata Time						Asia			UTC + 6 hours
#AMST	Armenia Summer Time					Asia			UTC + 5 hours
#AMST	Amazon Summer Time					South America	UTC - 3 hours
#AMT	Armenia Time						Asia			UTC + 4 hours
#AMT	Amazon Time							South America	UTC - 4 hours
#ANAST	Anadyr Summer Time					Asia			UTC + 12 hours
#ANAT	Anadyr Time							Asia			UTC + 12 hours
#AQTT	Aqtobe Time							Asia			UTC + 5 hours
#ART	Argentina Time						South America	UTC - 3 hours
#AST	Arabia Standard Time				Asia			UTC + 3 hours
#AZST	Azerbaijan Summer Time				Asia			UTC + 5 hours
#AZT	Azerbaijan Time						Asia			UTC + 4 hours
#BNT	Brunei Darussalam Time				Asia			UTC + 8 hours
#BOT	Bolivia Time						South America	UTC - 4 hours
#BRST	Brasilia Summer Time				South America	UTC - 2 hours
#BRT	Brasilia time						South America	UTC - 3 hours
#BST	Bangladesh Standard Time			Asia			UTC + 6 hours
#BST	British Summer Time					Europe			UTC + 1 hour
#BTT	Bhutan Time							Asia			UTC + 6 hours
#CAST	Casey Time							Antarctica		UTC + 8 hours
#CAT	Central Africa Time					Africa			UTC + 2 hours
#CCT	Cocos Islands Time					Indian Ocean	UTC + 6:30 hours
#CDT	Central Daylight Time				Australia		UTC + 10:30 hours
#CDT	Cuba Daylight Time					Caribbean		UTC - 4 hours
#CHADT	Chatham Island Daylight Time		Pacific			UTC + 13:45 hours
#CHAST	Chatham Island Standard Time		Pacific			UTC + 12:45 hours
#CKT	Cook Island Time					Pacific			UTC - 10 hours
#CLST	Chile Summer Time					South America	UTC - 3 hours
#CLT	Chile Standard Time					South America	UTC - 4 hours
#COT	Colombia Time						South America	UTC - 5 hours
#CST	China Standard Time					Asia			UTC + 8 hours
#CST	Central Standard Time				Australia		UTC + 9:30 hours
#CST	Cuba Standard Time					Caribbean		UTC - 5 hours
#CST	Central Standard Time				North America	UTC - 6 hours
#CVT	Cape Verde Time						Africa			UTC - 1 hour
#CXT	Christmas Island Time				Australia		UTC + 7 hours
#ChST	Chamorro Standard Time				Pacific			UTC + 10 hours
#DAVT	Davis Time							Antarctica		UTC + 7 hours
#EASST	Easter Island Summer Time			Pacific			UTC - 5 hours
#EAST	Easter Island Standard Time			Pacific			UTC - 6 hours
#ECT	Ecuador Time						South America	UTC - 5 hours
#EDT	Eastern Daylight Time				Australia		UTC + 11 hours
#EDT	Eastern Daylight Time				Caribbean		UTC - 4 hours
#EDT	Eastern Daylight Time				Pacific			UTC + 11 hours
#EST	Eastern Standard Time				Australia		UTC + 10 hours
#ET		Tiempo del Este						Central America	UTC - 5 hours
#ET		Tiempo del Este						Caribbean		UTC - 5 hours
#ET		Tiempo Del Este 					North America	UTC - 5 hours
#FJST	Fiji Summer Time					Pacific			UTC + 13 hours
#FJT	Fiji Time							Pacific			UTC + 12 hours
#FKST	Falkland Islands Summer Time		South America	UTC - 3 hours
#FKT	Falkland Island Time				South America	UTC - 4 hours
#FNT	Fernando de Noronha Time			South America	UTC - 2 hours
#GALT	Galapagos Time						Pacific			UTC - 6 hours
#GAMT	Gambier Time						Pacific			UTC - 9 hours
#GET	Georgia Standard Time				Asia			UTC + 4 hours
#GFT	French Guiana Time					South America	UTC - 3 hours
#GILT	Gilbert Island Time					Pacific			UTC + 12 hours
#GST	Gulf Standard Time					Asia			UTC + 4 hours
#GYT	Guyana Time							South America	UTC - 4 hours
#HAA	Heure Avancee de l'Atlantique		Atlantic		UTC - 3 hours
#HAA	Heure Avancee de l'Atlantique		North America	UTC - 3 hours
#HAC	Heure Avancee du Centre				North America	UTC - 5 hours
#HADT	Hawaii-Aleutian Daylight Time		North America	UTC - 9 hours
#HAE	Heure Avancee de l'Est 				Caribbean		UTC - 4 hours
#HAE	Heure Avancee de l'Est 				North America	UTC - 4 hours
#HAP	Heure Avancee du Pacifique			North America	UTC - 7 hours
#HAR	Heure Avancee des Rocheuses			North America	UTC - 6 hours
#HAST	Hawaii-Aleutian Standard Time		North America	UTC - 10 hours
#HAT	Heure Avancee de Terre-Neuve		North America	UTC - 2:30 hours
#HAY	Heure Avancee du Yukon				North America	UTC - 8 hours
#HKT	Hong Kong Time						Asia			UTC + 8 hours
#HLV	Hora Legal de Venezuela				South America	UTC - 4:30 hours
#HNA	Heure Normale de l'Atlantique		Atlantic		UTC - 4 hours
#HNA	Heure Normale de l'Atlantique		Caribbean		UTC - 4 hours
#HNA	Heure Normale de l'Atlantique		North America	UTC - 4 hours
#HNC	Heure Normale du Centre				Central America	UTC - 6 hours
#HNC	Heure Normale du Centre				North America	UTC - 6 hours
#HNE	Heure Normale de l'Est				Central America	UTC - 5 hours
#HNE	Heure Normale de l'Est				Caribbean		UTC - 5 hours
#HNE	Heure Normale de l'Est				North America	UTC - 5 hours
#HNP	Heure Normale du Pacifique			North America	UTC - 8 hours
#HNR	Heure Normale des Rocheuses			North America	UTC - 7 hours
#HNT	Heure Normale de Terre-Neuve		North America	UTC - 3:30 hours
#HNY	Heure Normale du Yukon				North America	UTC - 9 hours
#HOVT	Hovd Time							Asia			UTC + 7 hours
#ICT	Indochina Time						Asia			UTC + 7 hours
#IDT	Israel Daylight Time				Asia			UTC + 3 hours
#IOT	Indian Chagos Time					Indian Ocean	UTC + 6 hours
#IRDT	Iran Daylight Time					Asia			UTC + 4:30 hours
#IRKST	Irkutsk Summer Time					Asia			UTC + 9 hours
#IRKT	Irkutsk Time						Asia			UTC + 8 hours
#IRST	Iran Standard Time					Asia			UTC + 3:30 hours
#IST	Israel Standard Time				Asia			UTC + 2 hours
#IST	India Standard Time					Asia			UTC + 5:30 hours
#IST	Irish Standard Time					Europe			UTC + 1 hour
#KGT	Kyrgyzstan Time						Asia			UTC + 6 hours
#KRAST	Krasnoyarsk Summer Time				Asia			UTC + 8 hours
#KRAT	Krasnoyarsk Time					Asia			UTC + 7 hours
#KST	Korea Standard Time					Asia			UTC + 9 hours
#KUYT	Kuybyshev Time						Europe			UTC + 4 hours
#LHDT	Lord Howe Daylight Time				Australia		UTC + 11 hours
#LHST	Lord Howe Standard Time				Australia		UTC + 10:30 hours
#LINT	Line Islands Time					Pacific			UTC + 14 hours
#MAGST	Magadan Summer Time					Asia			UTC + 12 hours
#MAGT	Magadan Time						Asia			UTC + 11 hours
#MART	Marquesas Time						Pacific			UTC - 9:30 hours
#MAWT	Mawson Time							Antarctica		UTC + 5 hours
#MHT	Marshall Islands Time				Pacific			UTC + 12 hours
#MMT	Myanmar Time						Asia			UTC + 6:30 hours
#MSD	Moscow Daylight Time				Europe			UTC + 4 hours
#MSK	Moscow Standard Time				Europe			UTC + 3 hours
#MUT	Mauritius Time						Africa			UTC + 4 hours
#MVT	Maldives Time						Asia			UTC + 5 hours
#MYT	Malaysia Time						Asia			UTC + 8 hours
#NCT	New Caledonia Time					Pacific			UTC + 11 hours
#NDT	Newfoundland Daylight Time			North America	UTC - 2:30 hours
#NFT	Norfolk Time						Australia		UTC + 11:30 hours
#NOVST	Novosibirsk Summer Time				Asia			UTC + 7 hours
#NOVT	Novosibirsk Time					Asia			UTC + 6 hours
#NPT	Nepal Time 							Asia			UTC + 5:45 hours
#NST	Newfoundland Standard Time			North America	UTC - 3:30 hours
#NUT	Niue Time							Pacific			UTC - 11 hours
#OMSST	Omsk Summer Time					Asia			UTC + 7 hours
#OMST	Omsk Standard Time					Asia			UTC + 6 hours
#PET	Peru Time							South America	UTC - 5 hours
#PETST	Kamchatka Summer Time				Asia			UTC + 12 hours
#PETT	Kamchatka Time						Asia			UTC + 12 hours
#PGT	Papua New Guinea Time				Pacific			UTC + 10 hours
#PHOT	Phoenix Island Time					Pacific			UTC + 13 hours
#PHT	Philippine Time						Asia			UTC + 8 hours
#PKT	Pakistan Standard Time				Asia			UTC + 5 hours
#PONT	Pohnpei Standard Time				Pacific			UTC + 11 hours
#PT		Tiempo del Pacifico					North America	UTC - 8 hours
#PWT	Palau Time							Pacific			UTC + 9 hours
#PYST	Paraguay Summer Time				South America	UTC - 3 hours
#PYT	Paraguay Time						South America	UTC - 4 hours
#RET	Reunion Time						Africa			UTC + 4 hours
#SAMT	Samara Time							Europe			UTC + 4 hours
#SAST	South Africa Standard Time			Africa			UTC + 2 hours
#SBT	Solomon Islands Time				Pacific			UTC + 11 hours
#SCT	Seychelles Time						Africa			UTC + 4 hours
#SGT	Singapore Time						Asia			UTC + 8 hours
#SRT	Suriname Time						South America	UTC - 3 hours
#SST	Samoa Standard Time					Pacific			UTC - 11 hours
#TAHT	Tahiti Time							Pacific			UTC - 10 hours
#TFT	French Southern and Antarctic Time	Indian Ocean	UTC + 5 hours
#TJT	Tajikistan Time						Asia			UTC + 5 hours
#TKT	Tokelau Time						Pacific			UTC - 10 hours
#TLT	East Timor Time						Asia			UTC + 9 hours
#TMT	Turkmenistan Time					Asia			UTC + 5 hours
#TVT	Tuvalu Time							Pacific			UTC + 12 hours
#ULAT	Ulaanbaatar Time					Asia			UTC + 8 hours
#UYST	Uruguay Summer Time					South America	UTC - 2 hours
#UYT	Uruguay Time						South America	UTC - 3 hours
#UZT	Uzbekistan Time						Asia			UTC + 5 hours
#VET	Venezuelan Standard Time			South America	UTC - 4:30 hours
#VLAST	Vladivostok Summer Time				Asia			UTC + 11 hours
#VLAT	Vladivostok Time					Asia			UTC + 10 hours
#VUT	Vanuatu Time						Pacific			UTC + 11 hours
#WAST	West Africa Summer Time				Africa			UTC + 2 hours
#WAT	West Africa Time					Africa			UTC + 1 hour
#WDT	Western Daylight Time				Australia		UTC + 9 hours
#WEST	Western European Summer Time		Africa			UTC + 1 hour
#WEST	Western European Summer Time		Europe			UTC + 1 hour
#WFT	Wallis and Futuna Time				Pacific			UTC + 12 hours
#WGST	Western Greenland Summer Time		North America	UTC - 2 hours
#WGT	West Greenland Time					North America	UTC - 3 hours
#WIB	Western Indonesian Time				Asia			UTC + 7 hours
#WIT	Eastern Indonesian Time				Asia			UTC + 9 hours
#WITA	Central Indonesian Time				Asia			UTC + 8 hours
#WST	Western Sahara Summer Time			Africa			UTC + 1 hour
#WST	Western Standard Time				Australia		UTC + 8 hours
#WST	West Samoa Time						Pacific			UTC - 11 hours
#YAKST	Yakutsk Summer Time					Asia			UTC + 10 hours
#YAKT	Yakutsk Time						Asia			UTC + 9 hours
#YAPT	Yap Time							Pacific			UTC + 10 hours
#YEKST	Yekaterinburg Summer Time			Asia			UTC + 6 hours
#YEKT	Yekaterinburg Time					Asia			UTC + 5 hours

	tz = {	\
		'Z'		:	(  0, 0)	,		# Zulu Time Zone	Military
		'A'		:	(  1, 0)	,		# Alpha Time Zone	Military
		'B' 	:	(  2, 0)	,		# Bravo Time Zone	Military
		'C'		:	(  3, 0)	,		# Charlie Time Zone	Military
		'D'		:	(  4, 0)	,		# Delta Time Zone	Military
		'E'		:	(  5, 0)	,		# Echo Time Zone	Military
		'F'		:	(  6, 0)	,		# Foxtrot Time Zone	Military
		'G'		:	(  7, 0)	,		# Golf Time Zone	Military
		'H'		:	(  8, 0)	,		# Hotel Time Zone	Military
		'I'		:	(  9, 0)	,		# India Time Zone	Military
		'K'		:	( 10, 0)	,		# Kilo Time Zone	Military
		'L'		:	( 11, 0)	,		# Lima Time Zone	Military
		'M'		:	( 12, 0)	,		# Mike Time Zone	Military
		'N'		:	( -1, 0)	,		# November Time ZoneMilitary
		'O'		:	( -2, 0)	,		# Oscar Time Zone	Military
		'P'		:	( -3, 0)	,		# Papa Time Zone	Military
		'Q'		:	( -4, 0)	,		# Quebec Time Zone	Military
		'R'		:	( -5, 0)	,		# Romeo Time Zone	Military
		'S'		:	( -6, 0)	,		# Sierra Time Zone	Military
		'T'		:	( -7, 0)	,		# Tango Time Zone	Military
		'U'		:	( -8, 0)	,		# Uniform Time Zone	Military
		'V'		:	( -9, 0)	,		# Victor Time Zone	Military
		'W'		:	(-10, 0)	,		# Whiskey Time Zone	Military
		'X'		:	(-11, 0)	,		# X-ray Time Zone	Military
		'Y'		:	(-12, 0)	,		# Yankee Time Zone	Military

		'UT'	:	(  0, 0)	,		# Universal Time
		'UTC'	:	(  0, 0)	,		# Universal Time
		'GMT'	:	(  0, 0)	,		# Greenwich Mean Time				Europe
		'WET'	:	(  0, 0)	,		# Western European Time				Europe
		'WT'	:	(  0, 0)	,		# Western Sahara Standard Time		Africa

		'CET'	:	(  1, 0)	,		# Central European Time				Europe
		'CEST'	:	(  2, 0)	,		# Central European Summer Time		Europe

		'EET'	:	(  2, 0)	,		# Eastern European Time				Europe
		'EEST'	:	(  3, 0)	,		# Eastern European Summer Time		Europe

		'EAT'	:	(  3, 0)	,		# East Africa Time					Africa

		'AFT'	:	(  4,30)	, 		# Afghanistan Time					Asia

		'JST'	:	(  9, 0)	,		# Japan Standard Time				Asia

		'NZST'	:	( 12, 0)	,		# New Zealand Standard Time			Pacific
		'NZDT'	:	( 13, 0)	,		# New Zealand Daylight Time			Pacific

		'AKST'	:	( -9, 0)	,		# Alaska Standard Time				North America
		'AKDT'	:	( -8, 0)	,		# Alaska Daylight Time				North America

		'PST'	:	( -8, 0)	,		# Pacific Standard Time				North America = Pitcairn Standard Time	Pacific
		'PDT'	:	( -7, 0)	,		# Pacific Daylight Time				North America

		'MST'	:	( -7, 0)	,		# Mountain Standard Time			North America
		'MDT'	:	( -6, 0)	,		# Mountain Daylight Time			North America

		'CST'	:	( -6, 0)	,		# Central Standard Time				North America
		'CDT'	:	( -5, 0)	,		# Central Daylight Time				North America

		'EST'	:	( -5, 0)	,		# Eastern Standard Time				North America
		'EDT'	:	( -4, 0)	,		# Eastern Daylight Time				North America

		'AST'	:	( -4, 0)	,		# Atlantic Standard Time			Atlantic
		'ADT'	:	( -3, 0)	,		# Atlantic Daylight Time			Atlantic

		'PMST'	:	( -3, 0)	,		# Pierre & Miquelon Standard Time	North America
		'PMDT'	:	( -2, 0)	,		# Pierre & Miquelon Daylight Time	North America

		'AZOT'	:	( -1, 0)	,		# Azores Time						Atlantic
		'AZOST'	:	(  0, 0)	,		# Azores Summer Time				Atlantic

		'EGT'	:	( -1, 0)	,		# East Greenland Time				North America
		'EGST'	:	(  0, 0)	, 		# Eastern Greenland Summer Time		North America
	}

	if tz_field == None or len(tz_field) == 0:
		return ( 0, 0)

	if tz_field in tz:
		return tz[tz_field]

	p = 3 if tz_field[0:3] == 'UTC' else 0

	if tz_field[p] == '+':
		p += 1
		sign = 1
	elif tz_field[p] == '-':
		p += 1
		sign = -1
	else:
		sign = 1

	if len(tz_field[p:]) < 4:
		raise EonError( whoami()+", incomplete timezone (expected [+/-]hhmm), '"+tz_field+"'")

	if not tz_field[p:].isdigit():
		raise EonError( whoami()+", unrecognized timezone, '"+tz_field+"'")

	tz_hour   = long(tz_field[p  :p+2])
	tz_minute = long(tz_field[p+2:p+4])

	if tz_hour < 0 or tz_hour > 13:
		raise EonError( whoami()+", invalid hours in timezone, '"+tz_field+"'")

	if tz_minute < 0 or tz_minute > 59:
		raise EonError( whoami()+", invalid minutes in timezone, '"+tz_field+"'")

	return sign*tz_hour, sign*tz_minute

def split_date_format( ctime, format=None, exponent=g_EXPONENT, julian=False, now=False):

	from time import gmtime
	tt = gmtime() if now else (2000,1,1,0,0,0,5,1,0)

	if format != None:

		original_format = format

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

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

		if 'YYYY' in format:			# Check for 4-digit year

			# This allows formats for year with more than 4 digits,
			# i.e. very large positive and negative years.

			p = format.find('YYYY')
			n = 4
			while format[p:p+n] == 'Y'*n:
				n += 1
			n -= 1

			year	= int( ctime[p:p+n] )
			format	= format[0:p]+' '*n+format[p+n:]

		if 'YY' in format:				# check for 2-digit year
			p		= format.find('YY')
			year	= int( ctime[p:p+2] )
			year	= (1900+year)*(year > 50)+(2000+year)*(year <= 50)
			format = format[0:p]+'  '+format[p+2:]

		if year == None:
			year = tt[0]
			fill_now = True
		else:
			fill_now = False

		have_ydoy = False
		have_ymd  = False

		if 'DOY' in format:				# 3-digit integer day of year
			p		= format.find('DOY')
			doy		= int( ctime[p:p+3] )
			format	= format[0:p]+'   '+format[p+3:]

			( year, month, cmonth, day ) = doy2month_day( year, doy, julian )

			fill_now	= False
			have_ydoy	= True

		elif 'MON' in format.upper():			# 3-char string month
			p		= format.upper().find('MON')
			month	= ctime[p:p+3]
			format	= format[0:p]+'   '+format[p+3:]

			fill_now = False
			have_ymd = True

			(year, month, cmonth) = convert_month( year, month )

		elif 'MM' in format:			# 2-digit month
			p		= format.find('MM')
			month	= int( ctime[p:p+2] )
			format	= format[0:p]+'  '+format[p+2:]

			fill_now = False
			have_ymd = True

			(year, month, cmonth) = convert_month( year, month)

		if not have_ydoy:

			# Year has been set a this point.
			# If have_ymd = True than also the month has been set
			# The day of the month has not been set.

			if 'DD' in format:
				if fill_now:
					month = tt[1]
				have_ymd = True

				p		= format.find('DD')
				day		= int( ctime[p:p+2] )
				format	= format[0:p]+'  '+format[p+2:]

			elif have_ymd:
				day		= 1

			fill_now = False

			doy	= days_in_past_months( year, month, julian )+day

		if fill_now:
			doy = tt[7]
			( year, month, cmonth, day ) = doy2month_day( year, doy, julian )

		if 'hh' in format:
			p		= format.find('hh')
			hour	= int( ctime[p:p+2] )
			format	= format[0:p]+'  '+format[p+2:]
			fill_now = False

		elif fill_now:
			hour = tt[3]

		if 'mm' in format:
			p		= format.find('mm')
			minute	= int( ctime[p:p+2] )
			format	= format[0:p]+'  '+format[p+2:]
			fill_now = False

		elif fill_now:
			minute = tt[4]

		if 'ss' in format:
			p		= format.find('ss')
			second	= int( ctime[p:p+2] )
			l = len(ctime)
			i = 1
			while p+2+i < l and '0' <= ctime[p+2+i] and ctime[p+2+i] <= '9' and 'ss.'+'d'*i in format:
				i += 1
			i -= 1

			if i > 0:
				chipsec = long(round(long(ctime[p+3:p+3+i])*10**(exponent-i)))

			format	= format[0:p]+' '*(3+i)+format[p+3+i:]
			fill_now = False

		elif fill_now:
			second = tt[5]
			chipsec = 0

		if not fill_now:
			if doy		== None: doy     = 1
			if hour		== None: hour    = 0
			if minute	== None: minute  = 0
			if second	== None: second  = 0
			if chipsec	== None: chipsec = 0

		if 'TZ' in format:			# Determine timezone
			p = format.find('TZ')
			tz_field = ctime[p:].split(' ')[0]
			format = format[0:p]+' '*len(tz_field)+format[p+2:]
			tz_hour, tz_minute = disassemble_timezone(tz_field)
			hour   -= tz_hour		# Internally UTC is used
			minute -= tz_minute

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


	# No format specified: check for posix time before trying date format

	elif re.match('^\d{9,10}.*?$',ctime):
		posix = float(ctime) if '.' in ctime else int( ctime )
		return ('posix',posix,None,None,None,None,None,None,None)

	else:

		# TODO: need a function that can do some intelligent guessing:
		# - find timezone
		# - recognize posix time

		months = ['JAN','FEB','MAR','APR','MAY','JUN','JUL','AUG','SEP','OCT','NOV','DEC']
		for i in range(len(months)):
			if months[i] in ctime.upper():
				n = ctime.upper().find(months[i])
				ctime = ctime[0:n]+'%02d'%(i+1)+ctime[n+3:]

		# Need to detect the timezone here, but don't know how


		# Replace non-digits by spaces; then split into components

		ctime = ''.join([[' ',x][x >= '0' and x <= '9'] for x in list(ctime)]).strip().split()

		n = len(ctime)
		i = 0
		if n == i:
			year	= tt[0]
			doy		= tt[7]

			hour	= tt[3]
			minute	= tt[4]
			second	= tt[5]
			chipsec	= 0

		else:

			year = int(ctime[i])
			if len(ctime[i]) == 2:
				year = (1900+year)*(year > 50)+(2000+year)*(year <=50)

			doy		= 1

			hour	= 0
			minute	= 0
			second	= 0
			chipsec	= 0

		i += 1
		if n > i:
			if len(ctime[i]) == 3:
				doy	= int(ctime[i])
				( year, month, cmonth, day ) = doy2month_day( year, doy, julian )
			else:
				month = int(ctime[i])
				i += 1
				day = int(ctime[i]) if n > i else 1

				(year, month, cmonth) = convert_month( year, month)
				doy = days_in_past_months( year, month, julian )+day

		i += 1
		if n > i:
			hour = int(ctime[i])

		i += 1
		if n > i:
			minute = int(ctime[i])

		i += 1
		if n > i:
			second = int(ctime[i])

		i += 1
		if n > i:
			ctime[i] = ctime[i][::-1]
			while len(ctime[i]) > 0 and ctime[i][0] == '0':
				ctime[i] = ctime[i][1:]
			if ctime[i] == '':
				chipsec = 0
			else:
				ctime[i] = ctime[i][::-1]
				chipsec = long(round(long(ctime[i])*10**(exponent-len(ctime[i]))))

	( doy, hour, minute, second, chipsec ) = fix_dhms( doy, hour, minute, second, chipsec, exponent=exponent )
	( year, doy ) = fix_yr_doy( year, doy, julian )
	( year, month, cmonth, day ) = doy2month_day( year, doy, julian )

	return (year,doy,month,cmonth,day,hour,minute,second,chipsec)

def split_date_regex( ctime, regex=None, exponent=g_EXPONENT, julian=False, now=False ):

	import re

	r = re.compile('\((.*?)\)')
	i = 0
	to_be_removed = []
	matches = dict()

	for m in r.finditer(regex):
		i += 1
		g = m.group(1)
		if '=' in g:
			to_be_removed.append( (m.start(1), g.find('=')) )
			(key, value) = g.split('=')
			matches[key] = i

	to_be_removed.reverse()
	for i in to_be_removed:
		regex = regex[0:i[0]]+regex[i[0]+i[1]+1:]

	m = re.search(regex,ctime)
	if m == None:
		raise EonError(whoami()+", '"+ctime+"' does not match '"+regex+"'")

	year	= None
	doy		= None
	month	= None
	cmonth	= None
	day		= None

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

	posix	= None
	timezone= None

	for key in matches.keys():
		value = m.group(matches[key])

		if key == 'YYYY':
			year = float(value) if '.' in value else int( value )

		elif key == 'YY':
			year = float(value) if '.' in value else int( value )
			year = (1900+year)*(year > 50)+(2000+year)*(year <= 50)

		elif key == 'DOY':
			doy = float(value) if '.' in value else int( value )

		elif key == 'MON':
			month = value

		elif key == 'MM':
			month = int(value)

		elif key == 'DD':
			day = float(value) if '.' in value else int( value )

		elif key == 'hh':
			hour = float(value) if '.' in value else int( value )

		elif key == 'mm':
			minute = float(value) if '.' in value else int( value )

		elif key == 'ss':
			second = value

		elif key == 'TZ':
			timezone = value

		elif key == 'posix':
			posix = float(value) if '.' in value else int( value )
			return ('posix',posix,None,None,None,None,None,None,None)

		else:
			raise EonError(whoami()+", '"+regex+"' contains unrecognized time specifier '"+key+"'")

	if posix != None:
		pass

	from time import gmtime
	tt = gmtime() if now else (2000,1,1,0,0,0,5,1,0)

	if year == None:
		year = tt[0]
		fill_now = True
	else:
		fill_now = False

	have_ydoy = False
	have_ymd  = False

	if doy != None:

		( year, month, cmonth, day ) = doy2month_day( year, doy, julian )

		fill_now	= False
		have_ydoy	= True

	elif month != None:			# 3-char string month

		(year, month, cmonth) = convert_month( year, month)

		fill_now = False
		have_ymd = True

	if not have_ydoy and day != None:
		if fill_now:
			month = tt[1]
		doy	= days_in_past_months( year, month, julian )+day

		have_ymd = True
		fill_now = False

	if fill_now:
		doy = tt[7]
		#( year, month, cmonth, day ) = doy2month_day( year, doy, julian )

	if hour != None:
		fill_now = False

	elif fill_now:
		hour = tt[3]

	if minute != None:
		fill_now = False

	elif fill_now:
		minute = tt[4]

	if second != None:
		if '.' in second:
			csec = second
			p = csec.find('.')
			second = int( csec[0:p] ) if p > 0 else 0
			csec = csec[p+1:]
			l = len(csec)
			chipsec = long(round( long(csec)*10**(exponent-l))) if l > 0 else 0
		else:
			second = int( second )
			chipsec = 0

		fill_now = False

	elif fill_now:
		second = tt[5]
		chipsec = 0

	if not fill_now:
		if doy		== None: doy     = 1
		if hour		== None: hour    = 0
		if minute	== None: minute  = 0
		if second	== None: second  = 0
		if chipsec	== None: chipsec = 0

	if timezone != None:
		tz_hour, tz_minute = disassemble_timezone(timezone)
		hour   -= tz_hour		# Internally UTC is used
		minute -= tz_minute

	( doy, hour, minute, second, chipsec ) = fix_dhms( doy, hour, minute, second, chipsec, exponent=exponent )
	( year, doy ) = fix_yr_doy( year, doy, julian )
	( year, month, cmonth, day ) = doy2month_day( year, doy, julian )

	return (year,doy,month,cmonth,day,hour,minute,second,chipsec)

def fill_date_fields( year, doy, month, day, hour, minute, second, chipsec, timezone, exponent=g_EXPONENT, julian=False, now=False):

	from time import gmtime

	# (year,month,day,hour,minute,second,weekday,doy,isdst)
	#     y,    y,  n,   y,     y,     y,      n,  y,    n) Used?

	tt = gmtime() if now else (2000,1,1,0,0,0,5,1,0)
	tt = list(tt)

	# If timezone is not UTC then tt should be converted to the
	# specified timezone? Would give more consistent replacement values?
	# This needs testing!
	#tz_hour, tz_minute = disassemble_timezone( timezone )
	#if tz_hour != 0 or tz_minute != 0:
	#	( tt[7], tt[3], tt[4], tt[5], dummy ) = fix_dhms( tt[7], tt[3]+tz_hour, tt[4]+tz_minute, tt[5], 0, exponent=exponent )
	#	( tt[0], tt[7] ) = fix_yr_doy( tt[0], tt[7], julian )
	#	( tt[0], tt[1], dummy, tt[2] ) = doy2month_day( tt[0], tt[7], julian )

	if year == None:
		year = tt[0]
		fill_now = True
	else:
		fill_now = False
		if year%1 != 0:
			raise EonError(whoami()+", 'year' must be an integer; year=%s"%year)

	have_ydoy = False
	have_ymd  = False

	if doy != None:
		( year, month, cmonth, day ) = doy2month_day( year, doy, julian )
		fill_now	= False
		have_ydoy	= True

	elif month != None:
		fill_now = False
		have_ymd = True
		(year, month, cmonth) = convert_month( year, month)

	if not have_ydoy and day != None:
		if fill_now:
			month = tt[1]
		elif month == None:
			raise EonError(whoami()+", must specify 'month' with 'year' and 'day'")
		if not is_string(month) and month%1 != 0:
			raise EonError(whoami()+", 'month' must be an integer; month=%s"%month)

		(year, month, cmonth) = convert_month( year, month )
		have_ymd = True
		fill_now = False
		doy	= days_in_past_months( year, month, julian )+day

	if fill_now:
		doy = tt[7]
		#( year, month, cmonth, day ) = doy2month_day( year, doy, julian )

	if hour != None:
		fill_now = False

	elif fill_now:
		hour = tt[3]

	if minute != None:
		fill_now = False

	elif fill_now:
		minute = tt[4]

	if second != None:
		fill_now = False

	elif fill_now:
		second = tt[5]
		chipsec = 0

	if not fill_now:
		if doy		== None: doy     = 1
		if hour		== None: hour    = 0
		if minute	== None: minute  = 0
		if second	== None: second  = 0
		if chipsec	== None: chipsec = 0

	if timezone != None:
		tz_hour, tz_minute = disassemble_timezone(timezone)
		hour   -= tz_hour		# Internally UTC is used
		minute -= tz_minute

	( doy, hour, minute, second, chipsec ) = fix_dhms( doy, hour, minute, second, chipsec, exponent=exponent )
	( year, doy ) = fix_yr_doy( year, doy, julian )
	( year, month, cmonth, day ) = doy2month_day( year, doy, julian )

	return (year,doy,month,cmonth,day,hour,minute,second,chipsec)

def split_delta_format( ctime, format=None, exponent=g_EXPONENT):

	day		= 0
	hour	= 0
	minute	= 0
	second	= 0
	chipsec	= 0

	if format != None:

		original_format = format

		n = 0
		while 'Y'*(n+1) in format:
			n += 1

		if n > 0:
			p		= format.find('Y'*n)
			day	   += float( ctime[p:p+n] )*g_JULIAN_YEAR
			format	= format[0:p]+' '*n+format[p+n:]

		n = 0
		while 'D'*(n+1) in format:
			n += 1

		if n > 0:
			p		= format.find('D'*n)
			day	   += float( ctime[p:p+n] ) if '.' in ctime[p:p+n] else long(ctime[p:p+n])
			format	= format[0:p]+' '*n+format[p+n:]

		n = 0
		while 'h'*(n+1) in format:
			n += 1

		if n > 0:
			p		= format.find('h'*n)
			hour	= float( ctime[p:p+n] ) if '.' in ctime[p:p+n] else long(ctime[p:p+n])
			format	= format[0:p]+' '*n+format[p+n:]

		n = 0
		while 'm'*(n+1) in format:
			n += 1

		if n > 0:
			p		= format.find('m'*n)
			minute	= float( ctime[p:p+n] ) if '.' in ctime[p:p+n] else long(ctime[p:p+n])
			format	= format[0:p]+' '*n+format[p+n:]

		n = 0
		while 's'*(n+1) in format:
			n += 1

		if n > 0:
			p		= format.find('s'*n)
			second	= long( ctime[p:p+n] )
			l = len(ctime)
			i = 1
			while p+n+i < l and '0' <= ctime[p+n+i] and ctime[p+n+i] <= '9' and 's'*n+'.'+'d'*i in format:
				i += 1
			i -= 1

			if i > 0:
				chipsec = long(round(long(ctime[p+n+1:p+n+1+i])*10**(exponent-i)))

			format	= format[0:p]+' '*(n+1+i)+format[p+n+1+i:]

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

	else:

		# Replace non-digits by spaces; then split into components

		ctime = ''.join([[' ',x][x >= '0' and x <= '9'] for x in list(ctime)]).strip().split()

		n = len(ctime)
		i = 0

		if n > i:
			day = long(ctime[i])

		i += 1
		if n > i:
			hour = long(ctime[i])

		i += 1
		if n > i:
			minute = long(ctime[i])

		i += 1
		if n > i:
			second = long(ctime[i])

		i += 1
		if n > i:
			ctime[i] = ctime[i][::-1]
			while len(ctime[i]) > 0 and ctime[i][0] == '0':
				ctime[i] = ctime[i][1:]
			if ctime[i] == '':
				chipsec = 0
			else:
				ctime[i] = ctime[i][::-1]
				chipsec = long(round(long(ctime[i])*10**(exponent-len(ctime[i]))))

	( day, hour, minute, second, chipsec ) = fix_dhms( day, hour, minute, second, chipsec, exponent=exponent )

	return (day,hour,minute,second,chipsec)

def split_delta_regex( ctime, regex=None, exponent=g_EXPONENT ):

	import re

	r = re.compile('\((.*?)\)')
	i = 0
	to_be_removed = []
	matches = dict()

	for m in r.finditer(regex):
		i += 1
		g = m.group(1)
		if '=' in g:
			to_be_removed.append( (m.start(1), g.find('=')))
			(key, value) = g.split('=')
			matches[key] = i

	to_be_removed.reverse()
	for i in to_be_removed:
		regex = regex[0:i[0]]+regex[i[0]+i[1]+1:]

	m = re.search(regex,ctime)

	day		= 0
	hour	= 0
	minute	= 0
	second	= 0
	chipsec	= 0

	for key in matches.keys():
		value = m.group(matches[key])

		if 'Y' in key:
			day += float(value)*g_JULIAN_YEAR

		if 'D' in key:
			day += float(value) if '.' in value else int( value )

		if 'h' in key:
			hour = float(value) if '.' in value else int( value )

		if 'm' in key:
			minute = float(value) if '.' in value else int( value )

		if 's' in key:
			second = value
			if '.' in second:
				csec = second
				p = csec.find('.')
				second = int( csec[0:p] ) if p > 0 else 0
				csec = csec[p+1:]
				l = len(csec)
				chipsec = long(round( long(csec)*10**(exponent-l))) if l > 0 else 0
			else:
				second = int( second )
				chipsec = 0

	( day, hour, minute, second, chipsec ) = fix_dhms( day, hour, minute, second, chipsec, exponent=exponent )

	return (day,hour,minute,second,chipsec)

def fill_delta_fields( year, day, hour, minute, second, chipsec, exponent=g_EXPONENT):

	year	= 0 if year		== None else best_guess_for_number(year     )
	day		= 0 if day		== None else best_guess_for_number(day		)
	hour	= 0 if hour		== None else best_guess_for_number(hour		)
	minute	= 0 if minute	== None else best_guess_for_number(minute	)
	second  = 0 if second	== None else best_guess_for_number(second	)
	chipsec = 0 if chipsec	== None else best_guess_for_number(chipsec	)

	if year != None:
		day += year*g_JULIAN_YEAR
		year = 0

	( day, hour, minute, second, chipsec ) = fix_dhms( day, hour, minute, second, chipsec, exponent=exponent )

	return (day,hour,minute,second,chipsec)

def fill_fraction(format, spec, integer_spec, fractional_spec, exponent=g_EXPONENT) :
	'''
	#+
	# NAME:
	#	fill_fraction
	# PURPOSE:
	#	eon helper function
	# CALLING SEQUENCE:
	#	str = fill_fraction(format, spec, integer_spec, fractional_spec , exponent])
	# INPUTS:
	#	format             string containing time format
	#	spec
	#	integer_spec
	#	fractional_spec
	#	exponent
	# OUTPUTS:
	#	str
	# PROCEDURE:
	# MODIFICATION HISTORY:
	#-
	'''
	slen = len(spec)

	result = format
	p = result.find(spec)

	if spec+'.d#' in result:
		result = result.replace(spec+'.d#', spec+'.'+'d'*exponent)

	i = 1
	while spec+'.'+'d'*i in result:
		i += 1
	i -= 1				# Number of decimal digits in output format

	if i == 0:			# No digits, just integer part
		result = result[0:p]+integer_spec+result[p+slen:]
	else:
		chipsec = fractional_spec*10**(i-exponent)

		# Note that we truncate if i < self.exponent.
		# We do NOT want to round here. This might lead to chipsec
		# representing a full second, which would need to be
		# added to the 'ss' field, which then might become a full
		# minute, which would need to added to 'mm', etc.

		if i < exponent:
			chipsec = long(chipsec)

		result	= '%s%s%s%s%s'%(		\
					result[0:p]				,
					integer_spec			,
					result[p+slen]			 ,
					('%0'+str(i)+'d')%chipsec,
					result[p+slen+1+i:]		 ,
				)

	return result
