;+ ; NAME: ; TimeYDate ; PURPOSE: ; Gets day of year from year, month day, or gets month and ; day from year and day of year ; CATEGORY: ; gen/idl/toolbox/time ; CALLING SEQUENCE: PRO TimeYDate, yr, month, day, doy, get_date=get_date, julian=julian, jul2greg=jul2greg ; INPUTS: ; yr scalar or array; type: integer ; year ; ; /get_date NOT set: ; ; month scalar or array; type: integer or string ; month as integer in 1-12 or strings of type ; 'JAN','FEB', etc. (case-insensitive) ; day scalar or array; type: any ; day of month, possibly including a fraction for ; the time of day. ; ; /get_date SET: ; ; doy scalar or array; type: any ; day of year, possibly including a fraction for ; the time of day. ; OPTIONAL INPUT PARAMETERS: ; /get_date by default, yr, month and day are assumed to be input ; and the doy is returned. If /get_date is set then ; yr and doy are input and month and day are output. ; /julian interpret yr,doy and yr,mon,day as dates in Julian calendar ; /jul2greg ; jul2greg=jul2greg ; array[1]; type: time structure ; time at which the transition from Julian to Gregorian ; calendar is made. ; /jul2greg is the same as jul2greg=TimeSet(yr=1582, mon='oct', day=15) ; OUTPUTS: ; /get_date set: ; ; month scalar or array; type: integer or string ; month as integer in 1-12 ; (use TimeMonth to get 3-char strings) ; day scalar or array; type: same as input doy ; day of month, with the fraction from the input ; doy added (if present) ; ; /get_date NOT set: ; ; doy scalar or array; type: same as input day ; day of year, with the fraction from the input ; day added (if present). ; INCLUDE: @compile_opt.pro ; On error, return to caller ; SIDE EFFECTS: ; yr is converted to integer if it isn't already. None of the ; other input variables are modified. ; CALLS: ; SyncArgs, InitVar, IsType, IsTime, TimeYDate, TimeYDoy, TimeGet ; TimeMonth, TimeSet ; PROCEDURE: ; For the input arguments combinations of scalars and arrays are permitted. ; ; If integer day and doy are used the conversion is exact. For floating input ; the fraction is moved from day to doy or v.v., i.e. it is not exact. ; ; The relation between yr,month,day and yr,doy depends only on ; whether yr is a leap year or not. ; Two calendar schemes are implemented. In the Julian calendar ; (/julian SET) every multiple of 4 is a leap year. In the Gregrorian ; calendar (/julian NOT set) every multiple of 4 is a leap year, except ; when the year is a centennial year that is not a multiple of 400 ; (i.e. 1700, 1800, 1900 are not leap years, but 1600 and 2000 are),. ; ; See also documentation of TimeYDoy ; MODIFICATION HISTORY: ; JAN-2004, Paul Hick (UCSD/CASS) ; JUL-2008, Paul Hick (UCSD/CASS; pphick@ucsd.edu) ; If /get_date NOT set (and yr, month, day are input), and ; month is integer, outside the range 1-12, then month is now ; mapped back into the range 1-12 and yr is increased/decreased ; accordingly. This is used in TimeGet when the /eomonth ; (end of month) keyword is set to bridge into the next year. ;- InitVar, get_date, /key InitVar, julian , /key ; Calculate in Julian calendar IF IsType(jul2greg) THEN BEGIN IF NOT IsTime(jul2greg) THEN jul2greg = TimeSet(yr=1582,month='oct',day=15) ; Assume that all dates are Gregorian IF NOT get_date THEN TimeYDate, yr, month, day, doy TimeYDoy , yr, doy, jd ; jd are days since 2000 1.0 Jan ; If the Gregorian interpretation leads to a Julian date ; preceeding the swith than those dates need to be interpreted as ; Julian dates. TimeYDate, yr, month, day, doy, get_date=get_date, julian=jd LT TimeGet(/d2000, jul2greg, /scalar) RETURN ENDIF DAYS = [0,31,30,31,30,31,30,31,31,30,31,30,31] yr = long(yr) CASE get_date OF 0: BEGIN ; yr, month, day --> doy CASE IsType(month,/generic_int) OF 0: mon = TimeMonth(month) ; Convert string to integer 1: BEGIN ; Check integer ; /check' forces the integers into the range 1-12, ; and updates 'yr' where outside this range mon = TimeMonth(month, /check, year=yr) month = mon END ENDCASE ; Correction for # days in february (1 for leap year; otherwise 2) L = 2-(yr/4*4 EQ yr AND (julian OR yr/100*100 NE yr OR yr/400*400 EQ yr)) d = floor(day) IF (where(d LT 1 OR d GT DAYS[mon]-(mon EQ 2)*L))[0] NE -1 THEN message, 'invalid input for day of month' f = day-d ; The fraction is transferred to doy later ; Day of year is # days in months past (mon-1), plus days in month (d) ; Note the correction if February is already past. doy = mon-1 ; Months past doy = doy*31-((doy/2)<3)-(((doy-7)/2)>0)-(doy ge 2)*L+d IF (where(f NE 0))[0] NE -1 then doy += double(f) END 1: BEGIN ; Yr, Doy --> Month, Day ; 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. A = size(yr) B = size(doy) bSameType = (where(A[0:A[0]] NE B[0:B[0]]))[0] EQ -1 y = yr d = doy ; yr/doy could be scalar/array, array/scalar or array/array ; Use y+d to set up arrays with the proper array structure for y and d ; This will also become the array structure for month and day SyncArgs, y+d, y, d A = floor(d) f = d-A ; Fraction of day d = A ; Integer day of year ; Correct # days in february (1 for leap year) L = 2-(y/4*4 EQ y AND (julian OR y/100*100 NE y OR y/400*400 EQ y)) i = where(d LT 1, n) ; Doy too small; year needs updating IF n NE 0 THEN BEGIN CASE bSameType OF 0: message, 'invalid negative day of year' 1: BEGIN j = lindgen(n) REPEAT BEGIN i = i[j] y[i] -= 1 ; Decrease year by one where Doy < 1 A = y[i] ; Update leap year where Doy < 1 L[i] = 2-(A/4*4 EQ A AND (julian OR A/100*100 NE A OR A/400*400 EQ A)) d[i] += 367-L[i] j = where(d[i] LT 1) ENDREP UNTIL j[0] EQ -1 END ENDCASE ENDIF B = 367-L ; # days in year i = where(d GT B, n) ; Doy outside allowed range; year needs updating IF n NE 0 THEN BEGIN CASE bSameType OF 0: message, 'invalid day of year (too large)' 1: BEGIN j = lindgen(n) REPEAT BEGIN B = B[j] i = i[j] ; Index into y, d arrays y[i] += 1 ; Add year by one where Doy > # days in year d[i] -= B ; Update # days A = y[i] ; Update leap year where Doy < # days in year L[i] = 2-(A/4*4 EQ A AND (julian OR A/100*100 NE A OR A/400*400 EQ A)) B = 367-L[i] j = where(d[i] GT B) ENDREP UNTIL j[0] EQ -1 END ENDCASE ENDIF month = 0*d day = d i = lindgen(n_elements(day)) REPEAT BEGIN month[i] += (day[i]-1)/31 ; Underestimate # full months in Day A = month[i] B = A*31-((A/2)<3)-(((A-7)/2)>0)-(A GE 2)*L[i] ; # days in months past day[i] = d[i]-B ; Days left over ; Where more than one month left B = where(day[i] GT 31) if B[0] ne -1 then i = i[B] ; Indices into full y,d arrays ENDREP UNTIL B[0] EQ -1 ; There may be one more month left in Day month += 1 ; Month in which Day is located A = DAYS[month]-(month EQ 2)*L ; # days in month B = day GT A ; One additional whole month in Day day -= B*A ; Subtract # days in month month += B ; Increase month by one IF (where(f NE 0))[0] NE -1 then day += double(f) END ENDCASE RETURN & END