*! 1.0.0 NJC 2 February 2006
program ntimeofday, rclass
	version 8
	syntax varlist(str) [if] [in], ///
	Generate(string) ///
	Numeric(str)     ///
	String(string)   ///
	[                ///
	Type(str)        ///
	Parse(str asis) destring * ///
	Extremes ///
	Cend(numlist max=1 >100 <10000) ///
	am(str asis) pm(str asis) ///
        ]

	// the wild-card is to pass extra options to -split-, but
	// undocumented at present

	// check numeric() and string()
	// either could say nothing: unlikely, but check

	if trim("`numeric'") == "" {
		di as err "invalid numeric() option"
		exit 198
	}

	if trim("`string'") == "" {
		di as err "invalid string() option"
		exit 198
	}

	// numeric() has to signal one of days hours hrs minutes seconds

	local numeric = lower(trim("`numeric'"))
	local len = length("`numeric'")

	foreach ok in days hours hrs minutes seconds {
		if "`numeric'" == substr("`ok'",1,`len') {
			local unit = cond("`ok'" == "hrs", "hours", "`ok'")
		}
	}

	if "`unit'" == "" {
		di as err ///
	"numeric() option should specify one of days, hours, minutes, seconds"
		exit 198
	}

	// string() may signal
	//
	// days dmy dym
	// hours hrs
	// mdy minutes months myd
	// seconds
	// ydm years ymd yrs

	local OK days dmy dym hours hrs mdy minutes months myd seconds
	local OK `OK' ydm years ymd yrs
	local OK2 dmy dym mdy myd ydm ymd

	foreach s of local string {
		local s = lower("`s'")
		local len = length("`s'")
		foreach ok of local OK {
			local min = ///
			2 - inlist("`ok'", "seconds", "hours", "hrs")
			local min = max(`min', `len')
			if "`s'" == substr("`ok'",1,`min') {
				local String `String' `ok'
			}
		}
	}

	// just "h" could mean "hrs" or "hours", and just "y" could mean "yrs"
	// or "years": not ambiguous, but don't double count

	local String : subinstr local String "hrs" "hours"
	local String : subinstr local String "yrs" "years"

	// no duplicates allowed, and every component must be valid

	local String : list uniq String

	if `: word count `string'' != `: word count `String'' {
		di as err "invalid string() option"
		exit 198
	}

	// check for information specifying days

	local String : subinstr local String "years" "years",   count(local y)
	local string : subinstr local String "months" "months", count(local m)
	local string : subinstr local string "days" "days",     count(local d)

	/*
	inputs:

	y          invalid
	y m        invalid
	y m d      valid     (days should be 1 ... 28/31)   y + m + d = 3
	  m d      invalid
	  m        invalid
	    d      valid                                    y + m + d = 1
	y   d      valid     (days should be 1 ... 365/366) y + m + d = 2
	(none)     valid

	*/

	// exit if invalid

	if inlist("`string'", "dmy", "dym", "mdy", "myd", "ydm", "ymd") ///
	& inlist("`string'", "days", "months", "years") {
		di as err "string() invalid"
		exit 498
	}

	local both : list OK2 & string
	if `: list sizeof both' > 1 {
		di as err "choose at most one from `OK2'"
		exit 498
	}

	if `y' == 1 & `d' == 0 {
		di as err "string() should also specify days?"
		exit 498
	}

	if `m' == 1 & `d' == 0  & `y' == 0 {
		di as err "string() should also specify days and years?"
		exit 498
	}

	if `m' == 1 & `d' == 1  & `y' == 0 {
		di as err "string() should also specify years?"
		exit 498
	}

	// need for later processing
	local listtype = `y' + `m' + `d'

	// process varlist
	tokenize `varlist'
	local nvars : word count `varlist'
	local oldvarlist "`varlist'"

	// do before second -syntax-
	marksample touse, novarlist
	qui count if `touse'
	if r(N) == 0 error 2000

	// new varlist valid?
	local 0 "`generate'"
	syntax newvarlist

	// do existing and new varlists match one-to-one?
	if `nvars' != `: word count `varlist'' {
		di as err "number of new variables not equal to" _c
		di as err " number of existing variables"
		exit 198
	}

	// default parse strings
	if `"`parse'"' == "" local parse `" : " " "'

	// initial blank line if check for extremes
	if "`extremes'" != "" di

	// for each variable in original varlist
	quietly forval i = 1 / `nvars' {
		tempvar work touse2 ispm
		tempname stub

		// markout separately for each variable
		gen byte `touse2' = `touse'
		markout `touse2' ``i'', strok

		// am or pm: first strip text indicators
		// keep a note of which times are pm

		gen byte `ispm' = 0
		local I "``i''"

		if `"`am'`pm'"' != "" {
			tempvar copyi
			gen `copyi' = ``i''
			local `i' "`copyi'"
		}

		if `"`am'"' != "" {
			foreach s of local am {
				replace `copyi' = subinstr(`copyi', "`s'","",.)
			}
		}

		if `"`pm'"' != "" {
			foreach s of local pm {
				replace `ispm' = max(`ispm', index(`copyi', "`s'") > 0)
				replace `copyi' = subinstr(`copyi', "`s'","",.)
			}
		}

		// split is main engine here
		split ``i'', gen(`stub') parse(`parse') destring `options'
		if `r(nvars)' != `: word count `string'' {
			di as err "`I' does not appear to be `string'"
			exit 498
		}
		local split "`r(varlist)'"

		// initialise
		if "`type'" == "" local type "double"
		gen `type' `work' = 0
		local nd = 0 // for y + m + d
		local quiet

		// for each component

		forval j = 1/`: word count `string'' {
			local comp : word `j' of `string'
			local cont : word `j' of `split'

			if "`comp'" == "years" {
				capture confirm numeric variable `cont'
				if _rc {
					di as err "years component of `I' is string"
					exit _rc
				}

				local Y "`cont'"
				local ++nd
			}
			else if "`comp'" == "months" {
				capture confirm numeric variable `cont'
				if _rc {
					tempvar tostring
					gen `tostring' = ///
			month(date(`cont' + " 1 1960", "mdy")) if `touse2'
					local cont "`tostring'"
				}

				local M "`cont'"
				local ++nd
			}
			else if "`comp'" == "days" {
				capture confirm numeric variable `cont'
				if _rc {
					di as err "days component of `I' is string"
					exit _rc
				}

				local D "`cont'"
				local ++nd
			}
			else if "`comp'" == "hours" {
				capture confirm numeric variable `cont'
				if _rc {
					di as err "hours component of `I' is string"
					exit _rc
				}

				// pm adjustment
				replace `cont' = `cont' + 12 * (`cont' < 12) * `ispm'

				if "`unit'" == "days" {
					replace `work' = `work' + `cont' / 24
				}
				else if "`unit'" == "hours" {
					replace `work' = `work' + `cont'
			}
				else if "`unit'" == "minutes" {
					replace `work' = `work' + 60 * `cont'
				}
				else if "`unit'" == "seconds" {
					replace `work' = `work' + 3600 * `cont'
				}
			}
			else if "`comp'" == "minutes" {
				capture confirm numeric variable `cont'
				if _rc {
					di as err "minutes component of `I' is string"
					exit _rc
				}

				if "`unit'" == "days" {
					replace `work' = `work' + `cont' / 1440
				}
				else if "`unit'" == "hours" {
					replace `work' = `work' + `cont' / 60
				}
				else if "`unit'" == "minutes" {
					replace `work' = `work' + `cont'
				}
				else if "`unit'" == "seconds" {
					replace `work' = `work' + 60 * `cont'
				}
			}
			else if "`comp'" == "seconds" {
				capture confirm numeric variable `cont'
				if _rc {
					di as err "seconds component of `I' is string"
					exit _rc
				}

				if "`unit'" == "days" {
					replace `work' = `work' + `cont' / 86400
				}
				else if "`unit'" == "hours" {
					replace `work' = `work' + `cont' / 3600
				}
				else if "`unit'" == "minutes" {
					replace `work' = `work' + `cont' / 60
				}
				else if "`unit'" == "seconds" {
					replace `work' = `work' + `cont'
				}
			}
			else if inlist("`comp'", "dmy", "dym", "mdy", "myd", "ydm", "ymd") {
				tempvar todate
				capture confirm string var `cont'
				if _rc {
					di as err "`comp' element of `I' is numeric"
					exit 498
				}

				if "`cend'" != "" local cend ", `cend'"
				capture gen double `todate' = date(`cont', "`comp'" `cend') if `touse2'
				if _rc {
					di as err "`comp' element of `I' is problematic"
					exit _rc
				}

				local cont "`todate'"

				if "`unit'" == "days" {
					replace `work' = `work' + `cont'
				}
				else if "`unit'" == "hours" {
					replace `work' = `work' + 24 * `cont'
				}
				else if "`unit'" == "minutes" {
					replace `work' = `work' + 1440 * `cont'
				}
				else if "`unit'" == "seconds" {
					replace `work' = `work' + 86400 * `cont'
				}
			}

			if "`extremes'" != "" {
				`quiet' noi di as res abbrev("`I'", 21) ///
					as txt "{col 23}min    max"
				`quiet' noi di as txt "{hline 32}"
				local quiet "*"
				su `cont' if `touse2', meanonly
				noi di as txt "{col 5}`comp'{col 19}" ///
					       as res %7.0g r(min) %7.0g r(max)

			}

			if `nd' > 0 & `nd' == `listtype' {
				if `nd' == 2 { // years and days both to hand
					capture assert ///
					inrange(`D',1,doy(mdy(12,31,`Y'))) if `touse2'
					if _rc {
						di as err ///
					"day element of `I' outside (1, 365 or 366)"
						exit 498
					}

					local cont "mdy(1,1,`Y') + `D' - 1"
				}
				else if `nd' == 3 { // years, months, days to hand
					local cont "mdy(`M',`D',`Y')"
				}
				else if `nd' == 1 { // days to hand
					// OK
				}

				if "`unit'" == "days" {
					replace `work' = `work' + `cont'
				}
				else if "`unit'" == "hours" {
					replace `work' = `work' + 24 * `cont'
				}
				else if "`unit'" == "minutes" {
					replace `work' = `work' + 1440 * `cont'
				}
				else if "`unit'" == "seconds" {
					replace `work' = `work' + 86400 * `cont'
				}

				// don't do this more than once
				local nd = 0
			}
		}

		if "`extremes'" != "" noi di

		local togenerate "`togenerate' `work'"

		drop `touse2'
		drop `ispm'
		capture drop `tostring'
		capture drop `todate'
		drop `stub'*
	}

	// generate new variables iff all are OK
	tokenize "`togenerate'"
	local i = 1

	quietly {
		foreach v of local varlist {
			gen `type' `v' = ``i''
			local V : word `i' of `oldvarlist'
			label var `v' "`V' (`unit')"
			local ++i
		}
	}

	d `varlist'
	return local varlist "`varlist'"
end