Take the 2-minute tour ×
Mathematica Stack Exchange is a question and answer site for users of Mathematica. It's 100% free, no registration required.

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.

share|improve this question
1  
Why don't you use a Java date difference program and Leonid's Java compilation setup? –  Sjoerd C. de Vries 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. –  Leonid Shifrin 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. –  Leonid Shifrin Jun 22 '12 at 23:48
show 3 more comments

3 Answers 3

up vote 8 down vote accepted

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.

share|improve this answer
add comment

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}
share|improve this answer
    
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. –  Leonid Shifrin 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. –  Leonid Shifrin 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...) –  Brett Champion Jun 23 '12 at 1:27
1  
@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. –  Leonid Shifrin Jun 23 '12 at 12:09
add comment

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 *)
share|improve this answer
    
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. –  b.gatessucks Jun 22 '12 at 21:50
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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