Nov 15, 2007
Handling dates
Mathematica has three date formats. The first represents the date and time as a single integer, the total number of seconds since the beginning of January 1, 1900. The second represents the same information as a list of integers, {year, month, day, hour, minute, second}. The third is a string, such as "12/01/2006". As of version 6, Mathematica can convert freely between these formats and can automatically create graphs of dated data. Previously, this could be done only with custom Tick functions (not terribly difficult, but the solution built into Mathematica 6 is cleaner).
I have created functions that allow the easy conversion of NF1 lists using any of these date formats to any of the others. I call the first format "Mathematica Integer," the second is just "Mathematica," and the third is called "Conventional." In the conversion functions, these are flagged as "mi," "m," and "c," respectively.
Note the availability of the wtime flag in most of these functions. If this is set to False, these functions return date only, omitting the time.
First, a function that takes an nf1 object and identifies what datetype it uses. This presumes the same date format is used consistently through the nf1 object.
SternDateTypeNF1[nf1obj_] :=
Module[{cf, testingpiece}, testingpiece = nf1obj[[2]][[1]][[1]];
cf = "sorry, no match."; If[VectorQ[testingpiece], cf = "M"];
If[IntegerQ[testingpiece], cf = "MI"];
If[Head[testingpiece] === String, cf = "C"]; cf]
SternDateTypeNF1::usage =
"SternDateTypeNF1[nf1obj_]. This function takes an nf1 object with
dates in any format, and reports M, MI, or C, for Mathematica
{y,m,d,h,m,s}, Mathematica Integer (expressing seconds since January
1,1900), or Conventional (" <> FromCharacterCode[34] <> "1/31/2001" <>
FromCharacterCode[34] <> ").";
or like this, for non-NF1 dates
SternDateType[date_] :=
Module[{cf}, cf = "X"; If[VectorQ[date] , cf = "M"];
If[IntegerQ[date], cf = "MI"]; If[Head[date] === String, cf = "C"];
If[Not[cf == "M" || cf == "MI" || cf == "C"],
Message[SternDateType::"noformat"], cf]]
SternDateType::usage =
"SternDateType[date_]. This function takes a date in any format,
and reports what format it's in (C, M, or MI for conventional,
Mathematica, or Mathematica Integer).";
SternDateType::noformat =
"Can't figure out what format this date is in.";
Then a utility function that converts a date in any format to any specified other format.
Options[SternDateConversion] = {wtime -> True,
cFormat -> {"Month", "Day", "Year"}};
SternDateConversion[date_, targetformat_, OptionsPattern[]] :=
Module[{cf, conclusion, w}, cf = SternDateType[date];
w = OptionValue[wtime];
If[(targetformat == "M" || targetformat == "MI" ||
targetformat == "C"),
If[cf == "M" && targetformat == "MI",
conclusion = AbsoluteTime[date]];
If[cf == "C" && targetformat == "MI",
conclusion = AbsoluteTime[{date, OptionValue[cFormat]}]];
If[(cf == "M" || cf == "MI") && targetformat == "C",
conclusion =
If[w == False,
DateString[date, {"MonthShort", "/", "DayShort", "/", "Year"}],
DateString[
date, {"MonthShort", "/", "DayShort", "/", "Year", " - ",
"Hour12Short", ":", "Minute", " ", "AMPMLowerCase"}]]];
If[cf == "MI" && targetformat == "M",
conclusion = If[w, DateList[date], Take[DateList[date], 3]]];
If[cf == "C" && targetformat == "M",
conclusion =
If[w, DateList[{date, OptionValue[cFormat]}],
Take[DateList[{date, {"Month", "Day", "Year"}}], 3]]];
If[(cf == "M" && targetformat == "M") || (cf == "C" &&
targetformat == "C") || (cf == "MI" && targetformat == "MI"),
conclusion = date], Message[SternDateConversion::"badformat"]];
conclusion]
SternDateConversion::usage =
"SternDateConversion[date_,targetformat_,opts___]. This function
takes a date in any format, and an argument telling it what date
format to convert to, and converts the list of input dates to a list
of dates in the desired format. The formatting string must be M, MI,
or C, for Mathematica {y,m,d,h,m,s}, Mathematica Integer (expressing
seconds since January 1,1900), or Conventional (" <>
FromCharacterCode[34] <> "1/31/2001" <> FromCharacterCode[34] <>
"). Can set wtime flag to False to shut off time in conventional
dates. Use DateListConversion or DateDataPairConversionNF1 for
lists.";
SternDateConversion::badformat = "Target format must be M, MI, or C.";
And this does the same for NF1 objects.
Options[DateDataPairConversionNF1] = {wtime -> True,
cFormat -> {"Month", "Day", "Year"}};
DateDataPairConversionNF1[nf1obj_, targetformat_, OptionsPattern[]] :=
Module[{meat, head}, head = nf1obj[[1]];
meat = nf1obj[[2]]; {head,
Transpose[{Map[
SternDateConversion[#, targetformat,
wtime -> OptionValue[wtime],
cFormat -> OptionValue[cFormat]] &, Transpose[meat][[1]]],
Transpose[meat][[2]]}]}]
SternDateConversion::usage =
"SternDateConversion[date_,targetformat_,opts___]. This function
takes a date in any format, and an argument telling it what date
format to convert to, and converts the list of input dates to a list
of dates in the desired format. The formatting string must be M, MI,
or C, for Mathematica {y,m,d,h,m,s}, Mathematica Integer (expressing
seconds since January 1,1900), or Conventional (" <>
FromCharacterCode[34] <> "1/31/2001" <> FromCharacterCode[34] <>
"). Can set wtime flag to False to shut off time in conventional
dates. Use DateListConversion or DateDataPairConversionNF1 for
lists.";
Finally, a little function for selecting date ranges out of an NF1 object.
SternDateRangeNF1[nf1object_, earliestdate_,
lastdate_: "12/31/2099"] :=
If[(SternDateConversion[lastdate, "MI"]) > (SternDateConversion[
earliestdate, "MI"]), {nf1object[[1]],
Select[DateDataPairConversionNF1[nf1object, "MI"][[2]],
SternDateConversion[earliestdate, "MI"] <= #[[1]] <=
SternDateConversion[lastdate, "MI"] &]},(*
else give an error message *)
Message[SternDateRangeNF1::"badorder"]]
SternDateRangeNF1::usage =
"SternDateRangeNF1[nf1object_,earliestdate_,lastdate_]. This
function trims an NF1 object and returns the same object for a
specified date range. You provide the name of the object, the
earliest date to allow and, optionally, the last date to allow. The
latter will be set to 12/31/2099 if you don't provide anything
sooner. If dates are out of order when submitted, they will be out of
order when returned. There is no guarantee as to the date format the
object will be left in. Use DateDataPairConversionNF1[] if that
matters to you.";
SternDateRangeNF1::badorder = "The lastdate entered must come after the
earliestdate entered.";
For example, take the following data object
sampleNF1 = {{"nf1", "sample", "day", "value"}, {{"1/1/2007", 3},
{"1/2/2007", 3.1}, {"1/3/2007", 3.2}, {"1/4/2007", 3.3},
{"1/5/2007", 3.4}}};
We can convert it to other date formats now quite easily.
In[]:= DateDataPairConversionNF1[sampleNF1, "MI"]
Out[]= {{"nf1", "sample", "day", "value"}, {{3376598400, 3},
{3376684800, 3.1}, {3376771200, 3.2}, {3376857600, 3.3},
{3376944000, 3.4}}}
In[]:= DateDataPairConversionNF1[sampleNF1, "M", wtime -> False]
Out[]= {{"nf1", "sample", "day",
"value"}, {{{2007, 1, 1}, 3}, {{2007, 1, 2}, 3.1}, {{2007, 1, 3},
3.2}, {{2007, 1, 4}, 3.3}, {{2007, 1, 5}, 3.4}}}
A few more building blocks in coming days, then we will be able to begin useful work.

