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.

permalink | | 2007.11.15-11:31.00

hum