/* datefrac 1.0.1 -- 11 October 2023 Tommy Morgan - labhours@tmorg.org a Stata command that takes any variable that represents an exact date and generates a numeric variable representing the fraction of that year that has passed at the beginning of that date by taking the number of days since 1 January of a given year, dividing that number by 365, and adding the resulting fraction to the given year. For example, datefrac assigns the value 2020.000 (2020 + 0/365) to the date 1 January 2020 and the value 1999.17260 (1999 + 63/365) to the date 4 March 1999. It also accounts for leap years if the year is a multiple of 4, assigning the value 2000.17486 (2000 + 64/366) to the date 4 March 2000. */ program define datefrac version 10 syntax varname, GENerate(string) [ORDER(string)] *make sure they're using generate if "`generate'" == "" { di as err "must specify option generate()" exit 198 } *make sure their newvar is valid if "`generate'" != "" { capture confirm new variable `generate' if _rc { di as err "generate() contains existing variable(s) and/or illegal variable name(s)" exit _rc } } *check if the variable is a string to do date stuff: capture confirm string variable `varlist' if _rc == 0 { rename `varlist' superyeehaw`varlist' gen `varlist' = date(superyeehaw`varlist',"`order'") } *do the actual command! quietly { *get the year, month, and day from the existing date variable gen supermegayeehawyear = year(`varlist') gen supermegayeehawmonth = month(`varlist') gen supermegayeehawday = day(`varlist') *build the lookup matrix mat supermegadatefracker=J(366,4,.) loc dy 31 29 31 30 31 30 31 31 30 31 30 31 loc i 0 loc m 0 foreach d of loc dy { loc m=`m'+1 forv j=1/`d' { loc i=`i'+1 mat supermegadatefracker[`i',3] = `m' mat supermegadatefracker[`i',4] = `j' } } mat supermegadatefracker[`i',1]=0 mat supermegadatefracker[`i',2]=0 forv i=2/366 { mat supermegadatefracker[`i',2] = `=`i'-1'/366 } forv i=2/59 { mat supermegadatefracker[`i',1] = `=`i'-1'/365 } forv i=61/366 { mat supermegadatefracker [`i',1] = `=`i'-2'/365 } mat supermegadatefracker[1,1]=0 mat supermegadatefracker[1,2]=0 *bring in the fractional values from the lookup table gen double supermegayeehawfraction = . gen double supermegayeehawleapyearfraction = . forval i = 1/366 { quietly replace supermegayeehawfraction = supermegadatefracker[`i', 1] if supermegayeehawmonth == supermegadatefracker[`i', 3] & supermegayeehawday == supermegadatefracker[`i', 4] } forval i = 1/366 { quietly replace supermegayeehawleapyearfraction = supermegadatefracker[`i', 2] if supermegayeehawmonth == supermegadatefracker[`i', 3] & supermegayeehawday == supermegadatefracker[`i', 4] } *build the actual variable gen double `generate' = supermegayeehawyear + supermegayeehawfraction replace `generate' = supermegayeehawyear + supermegayeehawleapyearfraction if mod(supermegayeehawyear,4)==0 format `generate' %12.7f capture confirm string variable superyeehaw`varlist' if _rc == 0 { drop `varlist' rename superyeehaw`varlist' `varlist' } drop supermegayeehaw* mat drop supermegadatefracker } // end of quietly *check if the new variable is all missing values and warn if so: cap assert `generate'==. if _rc==0 { cap drop `generate' di as error "`generate' would contain all missing values, so it was not generated." di in smcl "{bf:order()} of `varlist' may not be specified correctly;" di in smcl "see {it:s2} section of {help f_date} for help, treating `varlist' as {it:s1}." exit 198 } end