# Faster Alternatives to DateDifference

I need a faster implementation of FractionOfYear and FractionOfMonth, which do the following:

Input: A time/date specified by {y_, m_, d_, h_, n_, s_}

Output: A real number from 0 to 1 representing the fraction of the year or month that the given time/date spec occurs in.

Leap days and leap seconds complicate things, so I thought I could just rely on DateDifference, but it is too slow:

RandomDateList[] := {RandomInteger[{1800, 2100}], RandomInteger[{1, 12}], RandomInteger[{1, 28}], RandomInteger[{0, 23}], RandomInteger[{0, 59}], RandomInteger[{0, 59}]};
RandomDates[n_] := Table[RandomDateList[],{n}]

secondOfYear[{y_, m_, d_, h_, n_, s_}] :=
First[DateDifference[{y - 1, 12, 31, 24, 0, 0}, {y, m, d, h, n, s},
"Second"]] / First[DateDifference[{y - 1, 12, 31, 24, 0, 0}, {y, 12, 31, 24, 0, 0}, "Second"]]

secondOfMonth[{y_, m_, d_, h_, n_, s_}] := First[DateDifference[{y, m, 1, 0, 0, 0},
{y, m, d, h, n, s}, "Second"]]/First[DateDifference[{y, m, 1, 0, 0, 0}, If[m==12, {y+1, 1, 1, 0, 0, 0}, {y, m+1, 1, 0, 0, 0}], "Second"]]


AbsoluteTiming [secondOfYear /@ RandomDates] takes 6 seconds.

There must be a faster easier way of doing this! I'll accept the first answer that takes under a second for 100,000 elements.

• Why don't you use a Java date difference program and Leonid's Java compilation setup? Jun 22 '12 at 20:54
• Nice idea! I'll look into that.
– M.R.
Jun 22 '12 at 21:19
• @SjoerdC.deVries Apparently, AbsoluteTime is compilable, and when you Map it onto a large list of dates, it is still very fast (Map auto-compiles). Java solution is only about 1.5 times faster, and first few times perhaps even slower (due the Java HotSpot JIT warm-up most likely). I can post it for didactic purposes, but I'd go with AbsoluteTime. Jun 22 '12 at 21:29
• I'd like to see your java solution.
– M.R.
Jun 22 '12 at 22:47
• Ok, you got it. Note that you have to ping the person you address the comment to, like e.g. @Leonid, or that person does not get notified - I discovered your comment by chance. Jun 22 '12 at 23:48

Ok, Java solution, by popular demand.

### Solution

2. Compile this class:

JCompileLoad@
"import java.util.Calendar;

public class SecondOfYearVectorized{
public static double[] secondOfYear(int[][] dates){
Calendar calendar = Calendar.getInstance();
double[] result = new double[dates.length];
for(int i=0;i<dates.length;i++){
calendar.set(dates[i],dates[i]-1,dates[i],
dates[i], dates[i],dates[i]);
long time = (long) (calendar.getTimeInMillis()/1000);
calendar.set(dates[i]-1,11,31,24,0,0);
long timepy = (long) (calendar.getTimeInMillis()/1000);
calendar.set(dates[i],11,31,24,0,0);
long timey = (long) (calendar.getTimeInMillis()/1000);
result[i]= ((double)(timepy-time))/(timepy-timey);
}
return result;
}
}"


### Usage and comparisons

dates = RandomDates;

SecondOfYearVectorizedsecondOfYear[dates]//Short//AbsoluteTiming

  {0.2460938,{0.310529,0.0296395,<<99996>>,0.0393697,0.0470913}}

N[secondOfYear2 /@ dates]//Short//AbsoluteTiming

  {1.5937500,{0.310643,0.0296395,<<99996>>,0.0393697,0.0470913}}


### Remarks

The Java solution seems about 6 times faster than Brett's one, and about 4 times faster than the fastest vectorized solution I could cook up with AbsoluteTime. The AbsoluteTime itself is pretty fast though, so I don't know to what should I attribute the speed-up (my guess is that I save on data transfer, which causes a performance hit, since I only transfer data once. And probably Java Calendar-based functionality is still faster by itself). Note that results obtained with Java and Mathematica solution are only approximately the same, but the differences are quite small, when any. Note also that Java solution reaches full speed after several runs, likely due to a warm-up of JVM HotSpot JIT compiler.

You could use AbsoluteTime to convert to seconds:

In:= secondOfYear2[{y_, m_, d_, h_, n_, s_}] :=
(AbsoluteTime[{y - 1, 12, 31, 24, 0, 0}] - AbsoluteTime[{y, m, d, h, n, s}])/
(AbsoluteTime[{y - 1, 12, 31, 24, 0, 0}] - AbsoluteTime[{y, 12, 31, 24, 0, 0}])

In:= secondOfMonth2[{y_, m_, d_, h_, n_, s_}] :=
(AbsoluteTime[{y, m, 1, 0, 0, 0}] - AbsoluteTime[{y, m, d, h, n, s}])/
(AbsoluteTime[{y, m, 1, 0, 0, 0}] - AbsoluteTime[
If[m == 12, {y + 1, 1, 1, 0, 0, 0}, {y, m + 1, 1, 0, 0, 0}]])

In:= dates = RandomDates;

In:= AbsoluteTiming[First[secondOfYear /@ dates]]

Out= {6.632562, 1973999/7884000}

In:= AbsoluteTiming[First[secondOfYear2 /@ dates]]

Out= {0.135024, 1973999/7884000}

• Still slow for 100,000 elements
– M.R.
Jun 22 '12 at 21:20
• @Mike Of course, but not because of AbsoluteTime - because you set up your function so that Map, for example, can not auto-compile. Vectorize your problem and you will win big. Jun 22 '12 at 21:32
• @Mike Actually, I take my words back: I tested and vectorization only speeds things up about 1.5 times, with respect to Brett's function secondOfYear2. So, it looks like this is more or less as fast as it gets, unless you implement some specialized version of AbsoluteTime, which may be faster. Jun 22 '12 at 22:33
• @LeonidShifrin I'm down to ~2s on my machine for secondOfMonth, but it's boring code you'd write in CS101 without any date library functions (does look-ups for how many seconds in each month, accounting for leap years, etc...) Jun 23 '12 at 1:27
• @BrettChampion Yeah, right ... I guess that's what libraries are for :). There are lots of more interesting problems to solve, and this stuff was probably beaten to death in any language we can name. Jun 23 '12 at 12:09

Another alternative :

LeapYearQ[year_] := (Mod[year, 4] == 0 && Mod[year, 100] != 0) || Mod[year,400] == 0

DaysInMonth[date_] := {{1, 31}, {2, If[LeapYearQ[date[]], 29, 28]}, {3, 31}, {4, 30}, {5, 31}, {6, 30}, {7, 31}, {8, 31}, {9, 30}, {10, 31}, {11, 30}, {12, 31}}[[date[], 2]];

mySOfY[{y_, m_, d_, h_, n_, s_}] := Module[{num, den},
num = (Total[DaysInMonth[{y, #, d, h, n, s}] &  /@ Range[1, m - 1]] +
(d - 1)) 86400 + h 3600 + n 60 + s;
den = (Total[DaysInMonth[{y, #, d, h, n, s}] &  /@ Range[1, 12]]) 86400;
num/den]

alist = RandomDates;

{elapsedMike, mike} = secondOfYear /@ alist // AbsoluteTiming;
{elapsedFr, Fr} = mySOfY /@ alist // AbsoluteTiming;

elapsedMike
elapsedFr

(* 4.085852 *)
(* 0.136444 *)

mike === Fr

(* True *)


Compiled version :

cLeapYear = Compile[{{year, _Integer}}, (Mod[year, 4] == 0 &&
Mod[year, 100] != 0) || Mod[year, 400] == 0, CompilationTarget -> "C"]

denF[year_] := If[cLeapYear[year], 366 86400, 365 86400]

cmySOfY = Compile[{{date, _Integer, 1}},
Module[{num, den, y, m, d, h, n, s},
y = date[]; m = date[]; d = date[]; h = date[];
n = date[]; s = date[];
num = (Total[
DaysInMonth[{y, #, d, h, n, s}] &  /@
Range[1, m - 1]] + (d - 1)) 86400 + h 3600 + n 60 + s;
den = denF[y];
num/den],
{{cLeapYear[_], True | False}, {DaysInMonth[_], _Integer},
{denF[_], _Integer}},
CompilationTarget -> "C", RuntimeAttributes -> {Listable},
RuntimeOptions -> "Speed"]


Still not fast enough :

{elapsedcFr, cFr} = cmySOfY[RandomDates[10^5]] // AbsoluteTiming;
elapsedcFr
(* 3.565060 *)
`
• Still slow for 100,000 elements
– M.R.
Jun 22 '12 at 21:16
• @Mike Added a compiled version but it still takes 3.5 sec. Jun 22 '12 at 21:50