(* :Title: DateFunctions *)

(* :Author: Mark Fisher *)

(* :Date: July 2005 *)

(* :Package Version: 0.1 *)

(* :Summary:
A number of functions to convert dates between list, string, and integer
representations. The options have the same form as ConversionOptions for
Import for "CSV" files.
*)

BeginPackage["DateFunctions`",
	{"Miscellaneous`Calendar`"}]

DateToIndex::usage = "DateToIndex[{year, month, day}] converts the date to
an integer using {1900, 1, 1} as the base day. DateToIndex[{year, month}]
converts the date to an integer using {1900, 1} as the base month. In
either case, a different base can be specified as a second argument."

DayIndexToDate::usage = "DayIndexToDate[index] converts the integer index
to {year, month, day} using {1900, 1, 1} as the base day. A different base
can be specified as a second argument."

MonthIndexToDate::usage = "MonthIndexToDate[index] converts the integer
index to {year, month} using {1900, 1} as the base month. A different base
can be specified as a second argument."

StringDateToListDate::usage = "StringDateToListDate[date, opts] returns a
date of the form {y, m, d} given the string of the form \"yyyy/mm/dd\".
StringDateToListDate takes three options. \"DateStyle\" has settings
\"Scientific\" (the default), \"American\" (\"mm/dd/yyyy\"), or
\"European\" (\"dd/mm/yyyy\"). \"DateSeparator\" has default setting \"/\".
\"TwoDigitYearFunction\" has default None (and has not yet been
implemented). StringDateToListDate also works with dates of the form
\"yyyy/mm\" and \"mm/yyyy\", etc."

StringDateToIntegerDate::usage = "StringDateToIntegerDate[date, opts]
returns an integer of the form yyyymmdd given the string of the form
\"yyyy/mm/dd\". StringDateToListDate takes three options. \"DateStyle\" has
settings \"Scientific\" (the default), \"American\" (\"mm/dd/yyyy\"), or
\"European\" (\"dd/mm/yyyy\"). \"DateSeparator\" has default setting \"/\".
\"TwoDigitYearFunction\" has default None (and has not yet been
implemented)."

ListDateToStringDate::usage = "ListDateToStringDate[{y, m, d}, opts]
returns a date of the form \"yyyy/mm/dd\". StringDateToListDate takes three
options. \"DateStyle\" has settings \"Scientific\" (the default),
\"American\" (\"mm/dd/yyyy\"), or \"European\" (\"dd/mm/yyyy\").
\"DateSeparator\" has default setting \"/\". \"TwoDigitYear\" has
default False."

IntegerDateToStringDate::usage = "IntegerDateToStringDate[int] take an
integer of the form yyyymmdd and returns a string date of the
\"Scientific\" form \"yyyy/mm/dd\". IntegerDateToStringDate takes three
options. \"DateStyle\" has settings \"Scientific\" (the default),
\"American\" (\"mm/dd/yyyy\"), or \"European\" (\"dd/mm/yyyy\").
\"DateSeparator\" has default setting \"/\". \"TwoDigitYear\" has
default False (and has not yet been implemented)."

ListDateToIntegerDate::usage = "ListDateToIntegerDate[{year, month, day}]
returns an integer of the form yyyymmdd. ListDateToIntegerDate[{year, month}]
returns an integer of the form yyyymm."

IntegerDateToListDate::usage "IntegerDateToListDate[int] returns
a list of the form {yyyy, mm, dd} or {yyyy, mm} depending on the
magnitude of the integer."

Begin["Private`"]

(***** code starts here *****)

DateToIndex[date : {y_, m_}, base_:{1900, 1}] := {12, 1}.(date - base)

DateToIndex[date : {y_, m_, d_}, base_:{1900, 1, 1}] := DaysBetween[base, date]

DayIndexToDate[i_Integer, base_:{1900, 1, 1}] := DaysPlus[base, i]

MonthIndexToDate[i_Integer, base_:{1900, 1}] := monthindextodate[i, base]

monthindextodate =
	Compile[{{i, _Integer}, {base, _Integer, 1}},
	With[{q = base[[1]] + Quotient[i, 12], m = base[[2]] + Mod[i, 12]},
    {q + Quotient[m, 12, 1], Mod[m, 12, 1]}
	]]

Options[StringDateToListDate] =
	{"DateStyle" -> "Scientific",
	"DateSeparator" -> "/",
	"TwoDigitYearFunction" -> None}

StringDateToListDate[date_String, opts___?OptionQ] :=
	Module[{separator, style, fun, split, order},
	{separator, style, fun} =
		{"DateSeparator", "DateStyle", "TwoDigitYearFunction"} /.
			{opts} /. Options[StringDateToListDate];
	split = StringSplit[date, separator];
	order = If[Length[split] == 3,
		(* then *)
		Switch[style,
			"Scientific", {1, 2, 3},
			"American", {3, 1, 2},
			"European", {3, 2, 1},
			_, {1, 2, 3}],
		(* else *)
		Switch[style,
			"Scientific", {1, 2},
			"American" | "European", {2, 1},
			_, {1, 2}]
		];
	ToExpression[split][[ order ]]
	]

StringDateToIntegerDate[date_String, opts___?OptionQ] :=
	ListDateToIntegerDate[StringDateToListDate[date, opts]]

Options[ListDateToStringDate] =
	{"DateStyle" -> "Scientific",
	"DateSeparator" -> "/",
	"TwoDigitYear" -> False}

ListDateToStringDate[{y_Integer, m_Integer, d_Integer}, opts___?OptionQ] :=
	Module[{separator, style, y2, order},
	{separator, style, y2} =
		{"DateSeparator", "DateStyle", "TwoDigitYear"} /.
			{opts} /. Options[ListDateToStringDate];
	order = Switch[style,
		"Scientific", {1, 2, 3},
		"American", {2, 3, 1},
		"European", {3, 2, 1},
		_, {1, 2, 3}];
	StringJoin[
		Insert[
			{If[TrueQ[y2], StringDrop[#, 2], #]& @ ToString[y],
			StringJoin @@ (ToString /@ IntegerDigits[m, 10, 2]),
			StringJoin @@ (ToString /@ IntegerDigits[d, 10, 2])}[[order]],
		separator, {{2}, {3}}
			]
		]
	]

ListDateToStringDate[{y_Integer, m_Integer}, opts___?OptionQ] :=
	Module[{separator, style, fun, order},
	{separator, style, y2} =
		{"DateSeparator", "DateStyle", "TwoDigitYear"} /.
			{opts} /. Options[ListDateToStringDate];
	order = Switch[style,
		"Scientific", {1, 2},
		"American" | "European", {2, 1},
		_, {1, 2}];
	StringJoin[
		Insert[
			{If[TrueQ[y2], StringDrop[#, 2], #]& @ ToString[y],
			StringJoin @@ (ToString /@ IntegerDigits[m, 10, 2])}[[order]],
		separator, {{2}}
			]
		]
	]

Options[IntegerDateToStringDate] =
	{"DateStyle" -> "Scientific",
	"DateSeparator" -> "/",
	"TwoDigitYear" -> False}

IntegerDateToStringDate[i_Integer /; i >= 10^6, opts___?OptionQ] :=
	Module[{separator, style, y2, order, list},
	{separator, style, y2} =
		{"DateSeparator", "DateStyle", "TwoDigitYear"} /.
			{opts} /. Options[ListDateToStringDate];
	order = Switch[style,
			"Scientific", {1, 2, 3},
			"American", {3, 1, 2},
			"European", {3, 2, 1},
			_, {1, 2, 3}];
	list = StringSplit[StringInsert[ToString[i], " ", {5, 7}]][[ order ]];
	StringJoin @@ Most[Flatten[Transpose[{list, {separator, separator, ""}}]]]
	]

IntegerDateToStringDate[i_Integer /; i < 10^6, opts___?OptionQ] :=
	Module[{separator, style, y2, order, list},
	{separator, style, y2} =
		{"DateSeparator", "DateStyle", "TwoDigitYear"} /.
			{opts} /. Options[ListDateToStringDate];
	order =	Switch[style,
			"Scientific", {1, 2},
			"American" | "European", {2, 1},
			_, {1, 2}];
	list = StringSplit[StringInsert[ToString[i], " ", {5}]][[ order ]];
	StringJoin @@ Most[Flatten[Transpose[{list, {separator, ""}}]]]
	]

ListDateToIntegerDate[date : {y_Integer, m_Integer, d_Integer}] :=
	date.{10000, 100, 1}

ListDateToIntegerDate[date : {y_Integer, m_Integer}] := date.{100, 1}

IntegerDateToListDate[i_Integer] :=
	ToExpression @ StringSplit[
		StringInsert[ToString[i], " ", If[i < 10^6, {5}, {5, 7}]]
		]

End[]
EndPackage[]
