; $Id: label_date.pro,v 1.13 2004/01/21 15:54:55 scottm Exp $ ; ; Copyright (c) 1993-2004, Research Systems, Inc. All rights reserved. ; Unauthorized reproduction prohibited. ;+ ; NAME: ; LABEL_DATE ; ; PURPOSE: ; This function labels axes with dates and times. ; ; CATEGORY: ; Plotting. ; ; CALLING SEQUENCE: ; To set up: ; dummy = LABEL_DATE(DATE_FORMAT='string') ; To use: ; PLOT, x, y, XTICKFORMAT='LABEL_DATE' ; ; INPUTS: ; No explicit user defined inputs. When called from the plotting ; routines, the input parameters are (Axis, Index, Value [, Level]) ; ; KEYWORD PARAMETERS: ; DATE_FORMAT: a format string which may contain the following: ; %M for month ; %N for month (2 digit abbr) ; %D for day of month, ; %Y for 4 digit year. ; %Z for last two digits of year. ; %W for day of week ; For time: ; %A for AM or PM ; %H for Hours, 2 digits. ; %I for mInutes, 2 digits. ; %S for Seconds, 2 digits. ; %0--%9 following %S, indicates digits after decimal point. ; %% is %. ; ; If a time format string is specified, the time of day ; will be rounded to the nearest least significant time format ; specified. E.g. if the format '%H:%I' is specified ; (hours:minutes) the time is rounded to the nearest minute. ; ; Other characters are passed directly thru. ; For example, '%M %D, %Y' prints DEC 11, 1993 ; '%M %2Y' yields DEC 93 ; '%D-%M' yields 11-DEC ; '%D/%N/%Y' yields 11/12/1993 ; '%M!C%Y' yields DEC on the top line, 1993 on ; the bottom (!C is the new line graphic command). ; ; AM_PM: The names for AM or PM. The default is ["am","pm"] ; ; DAYS_OF_WEEK: The names of the days, a seven-element string array. ; The default is [Sun, Mon, Tue, Wed, Thu, Fri, Sat]. ; ; MONTHS: The names of the months, a twelve element string array. ; The default is [Jan, Feb,..., Dec]. ; ; OFFSET: Set this keyword to a value representing the offset to be added ; to each tick value before conversion to a label. This keyword ; is usually used when your axis values are measured relative to ; a certain starting time. In this case, OFFSET should be set to ; the Julian date of the starting time. ; ; ROUND_UP: Set this keyword to force times to be rounded up to the ; smallest time unit that is present in the DATE_FORMAT string. ; The default is for times to be truncated to the ; smallest time unit. ; ; OUTPUTS: ; The date string to be plotted. ; ; COMMON BLOCKS: ; LABEL_DATE_COM. ; ; RESTRICTIONS: ; Uses a common block, so only one date axis may be simultaneously active. ; ; PROCEDURE: ; Straightforward. ; ; For an alternative way to label a plot axis with dates, refer to ; the C() format code accepted within format strings (applicable via ; the [XYZ]TICKFORMAT keywords). This format code was ; introduced in IDL 5.2. ; ; EXAMPLE: ; For example, to plot from Jan 1, 1993, to July 12, 1994: ; Start_date = julday(1, 1, 1993) ; End_date = julday(7, 12, 1994) ; Dummy = LABEL_DATE(DATE_FORMAT='%N/%D') ;Simple mm/dd ; x = findgen(end_date+1 - start_date) + start_date ;Time axis ; PLOT, x, sqrt(x), XTICKFORMAT = 'LABEL_DATE', XSTYLE=1 ; (Plot with X axis style set to exact.) ; ; Example with times: ; For example, to plot from 3PM, Jan 1, 1993, to 5AM, Jan 3, ; 1993: ; Start_date = Julday(1,1,1993) ;Also starting offset ; Start_time = (3+12)/24. ;Starting_time less offset ; End_time = (Julday(1,3,1993) - Start_date) + 5./24. ;Ending ; ;date/time - offset, note that the order of operations is ; ; important to avoid loss of precision. ; Dummy = LABEL_DATE(DATE_FORMAT='%D %M!C%H:%I', $ ; offset=Start_date) ;MMM NN HH:MM format ; x = findgen(20) * (End_time - Start_time) / 19 + start_time ;Time axis ; PLOT, x, sqrt(x), XTICKFORMAT = 'LABEL_DATE', XSTYLE=1 ; ; MODIFICATION HISTORY: ; DMS, RSI. April, 1993. Written. ; DMS, RSI. March, 1997. Added Time format. ; DMS, RSI. Jan, 1999. Rounded least significant time unit ; CT, RSI. May 2000. Completely rewrote to use calendar format codes. ; Added Level argument for new date/time TICKUNITS. ; Added AM_PM and DAYS_OF_WEEK keywords, '%A' and '%W' codes. ;- ;-------------------------------------------------- LABEL_DATE_CONVERT_FORMAT ; Given a LABEL_DATE input string, convert codes to Calendar format codes. FUNCTION label_date_convert_format, format, cMonths, cAmpm, cDaysWeek COMPILE_OPT idl2, hidden ON_ERROR, 2 n = strlen(format) newFormat = '' for i=0L, n-1 do begin ;Each format character... add = strmid(format, i, 1) ;The character. if add eq '%' then begin i = i + 1 ; skip to next character c = STRUPCASE(strmid(format, i, 1)) ; if format character, then convert to calendar format code case c of ; if user specified months, then width=zero to use "natural" ; month length, otherwise use the default month length (3) 'M': add = (N_ELEMENTS(cMonths) EQ 12) ? 'CMoA0' : 'CMoA' 'N': add = 'CMOI2.2' 'D': add = 'CDI2.2' 'Y': add = 'CYI4' 'Z': add = 'CYI2.2' 'H': add = 'CHI2.2' 'I': add = 'CMI2.2' 'S': BEGIN add = 'CSI2.2' ; default format for seconds ; check for additional %n code IF (STRMID(format,i+1,1) EQ '%') THEN BEGIN numberChar = STRMID(format,i+2,1) number = STRPOS('0123456789',numberChar) IF (number GE 0) THEN BEGIN i = i + 2 ; skip format characters width = STRTRIM(3+number,2) ;tens+ones+decimal add = 'CSF' + width + '.' + numberChar ; add TLn,CSI2.2 to include leading zero add = add + ',TL' + width + ',CSI2.2' ENDIF ENDIF END 'W' : add = (N_ELEMENTS(cDaysWeek) EQ 7) ? 'CDwA0' : 'CDwA' 'A' : add = (N_ELEMENTS(cAmpm) EQ 2) ? 'CapA0' : 'CapA' '%' : add = '"%"' ELSE: add = '""' ; if unknown, add null string endcase endif else add = '"' + add + '"' ; just output the character newFormat = (newFormat EQ '') ? add : newFormat + ',' + add endfor IF (STRPOS(newFormat,'CapA') GE 0) THEN BEGIN hourPos = STRPOS(newFormat,'CHI') IF (hourPos GE 0) THEN newFormat = STRMID(newFormat,0,hourPos+1) + $ 'hI' + STRMID(newFormat,hourPos+6) ENDIF RETURN, '(C(' + newFormat + '))' END ;----------------------------------------------------------------- LABEL_DATE FUNCTION LABEL_DATE, axisIn, indexIn, valueIn, levelIn, $ AM_PM = am_pm, $ DATE_FORMAT = dateFormat, $ DAYS_OF_WEEK = days_of_week, $ MONTHS = months, $ OFFSET = offs, $ ROUND_UP = round_up COMPILE_OPT idl2 COMMON label_date_com, cFormatArray, cMonths, cOffset, $ cRoundup, cAmpm, cDaysWeek ON_ERROR, 2 IF (N_PARAMS() LT 3) THEN $ ; use default for no inputs IF NOT KEYWORD_SET(dateFormat) THEN dateFormat='' ; process a new months vector? ; if months is undefined, then make cMonths undefined IF ARG_PRESENT(months) OR (N_ELEMENTS(months) GT 0) THEN BEGIN cMonths = -1 dummy = TEMPORARY(cMonths) ENDIF ; if months has 12 elements, then copy it to cMonths if (N_ELEMENTS(months) EQ 12) then cMonths = months ; process a new days_of_week vector? ; if days_of_week is undefined, then make cDaysWeek undefined IF ARG_PRESENT(days_of_week) OR (N_ELEMENTS(days_of_week) GT 0) THEN BEGIN cDaysWeek = -1 dummy = TEMPORARY(cDaysWeek) ENDIF ; if days_of_week has 2 elements, then copy it to cDaysWeek if (N_ELEMENTS(days_of_week) EQ 7) then cDaysWeek = days_of_week ; process a new AM_PM vector? ; if AM_PM is undefined, then make cAmpm undefined IF ARG_PRESENT(am_pm) OR (N_ELEMENTS(am_pm) GT 0) THEN BEGIN cAmpm = -1 dummy = TEMPORARY(cAmpm) ENDIF ; if AM_PM has 2 elements, then copy it to cAmpm if (N_ELEMENTS(am_pm) EQ 2) then cAmpm = am_pm ; process a new cOffset? IF ARG_PRESENT(offs) THEN cOffset = 0 IF (N_ELEMENTS(offs) GT 0) THEN cOffset = offs[0] ; make sure we have an cOffset IF (N_ELEMENTS(cOffset) EQ 0) THEN cOffset = 0d ; process a new cRoundup? IF (N_ELEMENTS(round_up) GT 0) THEN cRoundup = KEYWORD_SET(round_up) ; process a new date_format string? nNewFormat = N_ELEMENTS(dateFormat) IF (nNewFormat GT 0) THEN BEGIN cFormatArray = STRARR(nNewFormat) FOR a=0L,nNewFormat-1 DO cFormatArray[a] = $ LABEL_DATE_CONVERT_FORMAT(dateFormat[a],cMonths,cAmpm,cDaysWeek) ENDIF IF (N_ELEMENTS(cFormatArray) EQ 0) THEN cFormatArray = '(C())' IF (N_PARAMS() LT 3) THEN RETURN, 0 ;------------------------------------------------------ Process an axis value value1 = valueIn + cOffset ; convert to Julian date = LONG(value1) ; Julian date time = value1 - date ; Julian time IF (N_ELEMENTS(levelIn) LT 1) THEN levelIn = 0 nFormat = N_ELEMENTS(cFormatArray) formatLevel = cFormatArray[levelIn MOD nFormat] ; repeat cyclically ; Round subseconds to the desired precision secPos = STRPOS(formatLevel, 'CSF') ; for fractional seconds, need to manually round because the "float" ; format codes do rounding rather than truncation. IF (secPos GE 0) THEN BEGIN decimalPoint = STRPOS(formatLevel,'.',secPos) sigdigits = 10d^STRMID(formatLevel, decimalPoint + 1, 1) time = (ROUND(time*86400d*sigdigits,/L64)+0.1d)/(86400d*sigdigits) value1 = date + time ENDIF ; Round fractional time to the least significant format specified. IF KEYWORD_SET(cRoundup) THEN BEGIN ; Because the "integer" format codes automatically truncate, ; just add 1/2 of smallest desired time increment to do rounding. ; The CASE selections must be in order from smallest units to largest, ; so that rounding occurs to the least-significant unit. CASE 1 OF (secPos GE 0): ; should have already done rounding (STRPOS(formatLevel, 'CSI') GE 0): time = time + 0.5d/86400d (STRPOS(formatLevel, 'CMI') GE 0): time = time + 0.5d/1440d (STRPOS(formatLevel, 'CHI') GE 0): time = time + 0.5/24d ELSE: ENDCASE value1 = date + time ENDIF ; check for negative (B.C.E.) years jan1_1ad = 1721424L ; Julian date for 1 Jan, 1 C.E. IF (date LT jan1_1ad) THEN BEGIN yearPos = STRPOS(formatLevel,'CYI') ; if there is a year code, then increase width by one for minus sign IF (yearPos GE 0) THEN BEGIN yearPos = yearPos + 3 width = STRTRIM(FIX(STRMID(formatLevel,yearPos,1))+1,2) formatLevel = STRMID(formatLevel, 0, yearPos) + $ width + STRMID(formatLevel, yearPos+1) ENDIF ENDIF RETURN, STRING(value1,FORMAT=formatLevel, $ MONTHS=cMonths,AM_PM=cAmpm,DAYS_OF_WEEK=cDaysWeek) END