10
$\begingroup$

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[1000]] 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.

$\endgroup$
8
  • 1
    $\begingroup$ Why don't you use a Java date difference program and Leonid's Java compilation setup? $\endgroup$ Commented Jun 22, 2012 at 20:54
  • $\begingroup$ Nice idea! I'll look into that. $\endgroup$
    – M.R.
    Commented Jun 22, 2012 at 21:19
  • $\begingroup$ @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. $\endgroup$ Commented Jun 22, 2012 at 21:29
  • $\begingroup$ I'd like to see your java solution. $\endgroup$
    – M.R.
    Commented Jun 22, 2012 at 22:47
  • $\begingroup$ 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. $\endgroup$ Commented Jun 22, 2012 at 23:48

3 Answers 3

9
$\begingroup$

Ok, Java solution, by popular demand.

Solution

  1. Load the Java reloader

  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][0],dates[i][1]-1,dates[i][2],
                      dates[i][3], dates[i][4],dates[i][5]);
                long time = (long) (calendar.getTimeInMillis()/1000);
                calendar.set(dates[i][0]-1,11,31,24,0,0);
                long timepy = (long) (calendar.getTimeInMillis()/1000);
                calendar.set(dates[i][0],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[100000];

SecondOfYearVectorized`secondOfYear[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.

$\endgroup$
5
$\begingroup$

You could use AbsoluteTime to convert to seconds:

In[5]:= 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[6]:= 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[7]:= dates = RandomDates[1000];

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

Out[8]= {6.632562, 1973999/7884000}

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

Out[9]= {0.135024, 1973999/7884000}
$\endgroup$
5
  • $\begingroup$ Still slow for 100,000 elements $\endgroup$
    – M.R.
    Commented Jun 22, 2012 at 21:20
  • $\begingroup$ @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. $\endgroup$ Commented Jun 22, 2012 at 21:32
  • $\begingroup$ @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. $\endgroup$ Commented Jun 22, 2012 at 22:33
  • $\begingroup$ @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...) $\endgroup$ Commented Jun 23, 2012 at 1:27
  • 2
    $\begingroup$ @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. $\endgroup$ Commented Jun 23, 2012 at 12:09
3
$\begingroup$

Another alternative :

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

DaysInMonth[date_] := {{1, 31}, {2, If[LeapYearQ[date[[1]]], 29, 28]}, {3, 31}, {4, 30}, {5, 31}, {6, 30}, {7, 31}, {8, 31}, {9, 30}, {10, 31}, {11, 30}, {12, 31}}[[date[[2]], 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[1000];

{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[[1]]; m = date[[2]]; d = date[[3]]; h = date[[4]]; 
   n = date[[5]]; s = date[[6]];
   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 *)
$\endgroup$
2
  • $\begingroup$ Still slow for 100,000 elements $\endgroup$
    – M.R.
    Commented Jun 22, 2012 at 21:16
  • $\begingroup$ @Mike Added a compiled version but it still takes 3.5 sec. $\endgroup$ Commented Jun 22, 2012 at 21:50

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.