Many years ago, I needed a way to carry out date and time calculations that I could be certain were exact to the second. That is, given a date and time, I needed to be able to add or subtract n seconds to that date and time and get an exact answer. In order to do this, I wrote a library that uses the built-in IDL CALDAT and JULDAY routines to do date calculations, while I handled the hour, minute, and second calculations myself using integer arithmetic.
This library has found its way into much of my other code. For example, to read the 18Z NCEP Reanalysis temperature data for January 1, 2006, I do the following:
IDL> T = NCEP_P_READ_VAR('T', MAKE_DATE(2006, 1, 1, 18))
The NCEP_P_READ_VAR function returns a structure containing the temperature data and ancillary information, such as longitudes, latitudes, altitudes, and units.
The library is based around two named structures that store equivalent information in different forms: {CDATE} and {JTIME}. A {CDATE} structure contains the year, month, day, hour, minute, and second as integers. A {JTIME} structure contains the Julian day number and time of day in seconds, also as integers. The library includes the DATE_TO_TIME and TIME_TO_DATE functions to convert between the two formats. In the discussion below, I will refer to these structures and functions as "time" calculations. Generally you should not manipulate the {CDATE} or {JTIME} structures directly.
A few of the utility routines make use of the system variable !TauDSolar, which is the length of the mean solar day in seconds (24 x 60 x 60 = 86400). This line should be added to your startup.pro file if you plan to use the library:
DEFSYSV, '!TauDSolar', 86400L, 1
Generally I use the {CDATE}-structure version of the routines. A sample calculation would look something like this:
IDL> t1 = MAKE_DATE(2006, 1, 1) ;Create date and time
IDL> help, t1, /structure
** Structure CDATE, 6 tags, length=24, data length=24:
YEAR LONG 2006
MONTH LONG 1
DAY LONG 1
HOUR LONG 0
MINUTE LONG 0
SECOND LONG 0
IDL> print, MAKE_ISO_DATE_STRING(t1) ;Print using ISO 8601 format
2006-01-01 00:00:00
IDL> t2 = TIME_INC(t1, 1000) ;Add 1000 s to t1
IDL> print, t2
{ 2006 1 1 0 16 40}
IDL> print, MAKE_ISO_DATE_STRING(t2)
2006-01-01 00:16:40
The default values for year, month, day, hour, minute, and second are 1, 1, 1, 0, 0, and 0, respectively.
Given a date and time t, the library includes utilities for finding the next day, the last day of the month, the first day of the next month, and the first day of the next year.
To step through one month, starting at 00:00:00 on the first day of the month, with a time step of 6 hours, use the following code snippet:
t1 = MAKE_DATE(year, month)
t2 = NEXT_MONTH(t1)
t = t1
WHILE (TIME_DIFF(t, t2) LT 0) DO BEGIN
...
t = TIME_INC(t, !TauDSolar/4)
ENDWHILE
To write times into files, including netCDF files, I generally write them as strings using my MAKE_ISO_DATE_STRING function. This function provides control over the precision with which the time is written and whether to use "human" or "compact" format.
IDL> PRINT, MAKE_ISO_DATE_STRING(date, /COMPACT)
20050901T181503
IDL> PRINT, MAKE_ISO_DATE_STRING(date, PRECISION = 'hour', /UTC)
2005-09-01 18Z
IDL> PRINT, MAKE_ISO_DATE_STRING(date, PRECISION = 'hour', /COMPACT, /UTC)
20050901T18Z
One advantage to this library is that it was relatively easy to modify it to use an artificial calendar that has no leap days (for the NCAR climate model, for example). All of the details are hidden from the user in the library. Just create your date using the NO_LEAP keyword. The library does all calculations with "no leap" times using a 365 day year.
The library could also be extended (though I have not needed to do so) to handle greater precision by adding, for example, a microseconds field to the {CDATE} and {JTIME} structures.
As with any IDL structure, you can make an array of structures to handle a list of times. To create a list of times separated by 1 day, use the following code snippet:
t = REPLICATE(MAKE_DATE(2006, 1, 1), 10)
FOR i = 1, 9 DO t[i] = TIME_INC(t[i-1], !TauDSolar)
Note, however, that the library functions only work with scalars, not with arrays.