classes/hirondelle/web4j/model/DateTimeInterval.java
changeset 0 3060119b1292
equal deleted inserted replaced
-1:000000000000 0:3060119b1292
       
     1 package hirondelle.web4j.model;
       
     2 
       
     3 import static hirondelle.web4j.model.DateTime.DayOverflow;
       
     4 import static hirondelle.web4j.model.DateTime.Unit;
       
     5 
       
     6 /**
       
     7  Helper class for adding intervals of time. 
       
     8  The mental model of this class is similar to that of a car's odometer, except
       
     9  in reverse. 
       
    10  */
       
    11 final class DateTimeInterval {
       
    12 
       
    13   /**  Constructor.  */
       
    14   DateTimeInterval(DateTime aFrom, DayOverflow aMonthOverflow){
       
    15     fFrom = aFrom;
       
    16     checkUnits();
       
    17     fYear = fFrom.getYear() == null ? 1 : fFrom.getYear();
       
    18     fMonth = fFrom.getMonth() == null ? 1 : fFrom.getMonth();
       
    19     fDay = fFrom.getDay() == null ? 1 : fFrom.getDay();
       
    20     fHour = fFrom.getHour() == null ? 0 : fFrom.getHour();
       
    21     fMinute = fFrom.getMinute() == null ? 0 : fFrom.getMinute();
       
    22     fSecond = fFrom.getSecond() == null ? 0 : fFrom.getSecond();
       
    23     fNanosecond = fFrom.getNanoseconds() == null ? 0 : fFrom.getNanoseconds();
       
    24     fDayOverflow = aMonthOverflow;
       
    25   }
       
    26   
       
    27   DateTime plus(int aYear, int aMonth, int aDay, int aHour, int aMinute, int aSecond, int aNanosecond){
       
    28     return plusOrMinus(PLUS, aYear, aMonth, aDay, aHour, aMinute, aSecond, aNanosecond);
       
    29   }
       
    30   
       
    31   DateTime minus(int aYear, int aMonth, int aDay, int aHour, int aMinute, int aSecond, int aNanosecond){
       
    32     return plusOrMinus(MINUS, aYear, aMonth, aDay, aHour, aMinute, aSecond, aNanosecond);
       
    33   }
       
    34   
       
    35   // PRIVATE 
       
    36   
       
    37   //the base date to which the interval is calculated
       
    38   private final DateTime fFrom;
       
    39   
       
    40   private boolean fIsPlus;
       
    41   private DateTime.DayOverflow fDayOverflow;
       
    42   
       
    43   //the various increments
       
    44   private int fYearIncr;
       
    45   private int fMonthIncr;
       
    46   private int  fDayIncr;
       
    47   private int fHourIncr;
       
    48   private int fMinuteIncr;
       
    49   private int fSecondIncr;
       
    50   private int fNanosecondIncr;
       
    51 
       
    52   //work area for the final result - starts off with values from base date fFrom
       
    53   private Integer fYear;
       
    54   private Integer fMonth;
       
    55   private Integer fDay;
       
    56   private Integer fHour;
       
    57   private Integer fMinute;
       
    58   private Integer fSecond;
       
    59   private Integer fNanosecond;
       
    60 
       
    61   private static final int MIN = 0;
       
    62   private static final int MAX = 9999;
       
    63   private static final int MIN_NANOS = 0;
       
    64   private static final int MAX_NANOS = 999999999;
       
    65   private static final boolean PLUS = true;
       
    66   private static final boolean MINUS = false;
       
    67 
       
    68   private void checkUnits(){
       
    69     boolean success = false;
       
    70     if(fFrom.unitsAllPresent(Unit.YEAR, Unit.MONTH, Unit.DAY, Unit.HOUR, Unit.MINUTE, Unit.SECOND) ){
       
    71       success = true;
       
    72     }
       
    73     else if( fFrom.unitsAllPresent(Unit.YEAR, Unit.MONTH, Unit.DAY) &&  fFrom.unitsAllAbsent(Unit.HOUR, Unit.MINUTE, Unit.SECOND) ){
       
    74       success = true;
       
    75     }
       
    76     else if ( fFrom.unitsAllAbsent(Unit.YEAR, Unit.MONTH, Unit.DAY) && fFrom.unitsAllPresent(Unit.HOUR, Unit.MINUTE, Unit.SECOND) ){
       
    77       success = true;
       
    78     }
       
    79     else {
       
    80       success = false;
       
    81     }
       
    82     if(! success ){
       
    83       throw new IllegalArgumentException("For interval calculations, DateTime must have year-month-day, or hour-minute-second, or both.");
       
    84     }
       
    85   }
       
    86   
       
    87   private DateTime plusOrMinus(boolean aIsPlus, Integer aYear, Integer aMonth, Integer aDay, Integer aHour, Integer aMinute, Integer aSecond, Integer aNanosecond){
       
    88     fIsPlus = aIsPlus;
       
    89     fYearIncr = aYear;
       
    90     fMonthIncr = aMonth;
       
    91     fDayIncr = aDay;
       
    92     fHourIncr = aHour;
       
    93     fMinuteIncr = aMinute;
       
    94     fSecondIncr = aSecond;
       
    95     fNanosecondIncr = aNanosecond;
       
    96     
       
    97     checkRange(fYearIncr, "Year");
       
    98     checkRange(fMonthIncr, "Month");
       
    99     checkRange(fDayIncr, "Day");
       
   100     checkRange(fHourIncr, "Hour");
       
   101     checkRange(fMinuteIncr, "Minute");
       
   102     checkRange(fSecondIncr, "Second");
       
   103     checkRangeNanos(fNanosecondIncr);
       
   104     
       
   105     changeYear();
       
   106     changeMonth();
       
   107     handleMonthOverflow();
       
   108     changeDay();
       
   109     changeHour();
       
   110     changeMinute();
       
   111     changeSecond();
       
   112     changeNanosecond();
       
   113     
       
   114     return new DateTime(fYear, fMonth, fDay, fHour, fMinute, fSecond, fNanosecond);
       
   115   }
       
   116 
       
   117   private void checkRange(Integer aValue, String aName) {
       
   118     if ( aValue <  MIN || aValue > MAX ) { 
       
   119       throw new IllegalArgumentException(aName + " is not in the range " + MIN + ".." + MAX); 
       
   120     }
       
   121   }
       
   122   
       
   123   private void checkRangeNanos(Integer aValue) {
       
   124     if ( aValue <  MIN_NANOS || aValue > MAX_NANOS ) { 
       
   125       throw new IllegalArgumentException("Nanosecond interval is not in the range " + MIN_NANOS + ".." + MAX_NANOS); 
       
   126     }
       
   127   }
       
   128   
       
   129   private void changeYear(){
       
   130     if(fIsPlus){
       
   131       fYear = fYear + fYearIncr;
       
   132     }
       
   133     else {
       
   134       fYear = fFrom.getYear() - fYearIncr;
       
   135     }
       
   136     //the DateTime ctor will check the range of the year 
       
   137   }
       
   138   
       
   139   private void changeMonth(){
       
   140     int count = 0;
       
   141     while (count < fMonthIncr){
       
   142       stepMonth();
       
   143       count++;
       
   144     }
       
   145   }
       
   146 
       
   147   private void  changeDay(){
       
   148     int count = 0;
       
   149     while (count < fDayIncr){
       
   150       stepDay();
       
   151       count++;
       
   152     }
       
   153   }
       
   154 
       
   155   private  void changeHour(){
       
   156     int count = 0;
       
   157     while (count < fHourIncr){
       
   158       stepHour();
       
   159       count++;
       
   160     }
       
   161   }
       
   162 
       
   163   private void changeMinute(){
       
   164     int count = 0;
       
   165     while (count < fMinuteIncr){
       
   166       stepMinute();
       
   167       count++;
       
   168     }
       
   169   }
       
   170   
       
   171   private void changeSecond(){
       
   172     int count = 0;
       
   173     while (count < fSecondIncr){
       
   174       stepSecond();
       
   175       count++;
       
   176     }
       
   177   }
       
   178 
       
   179   /** 
       
   180    Nanos are different from other items. They don't cycle one step at a time.
       
   181    They are just added. If they under/over flow, then extra math is performed.
       
   182    They don't over/under by more than 1 second, since the size of the increment is limited.
       
   183   */ 
       
   184   private void changeNanosecond(){
       
   185     if (fIsPlus){
       
   186       fNanosecond = fNanosecond + fNanosecondIncr;      
       
   187     }
       
   188     else {
       
   189       fNanosecond = fNanosecond - fNanosecondIncr;      
       
   190     }
       
   191     if(fNanosecond > MAX_NANOS){
       
   192       stepSecond();
       
   193       fNanosecond = fNanosecond - MAX_NANOS - 1;
       
   194     }
       
   195     else if (fNanosecond < MIN_NANOS){
       
   196       stepSecond();
       
   197       fNanosecond =  MAX_NANOS + fNanosecond + 1;
       
   198     }
       
   199   }
       
   200   
       
   201   private void stepYear() {
       
   202     if(fIsPlus) {
       
   203       fYear = fYear + 1;
       
   204     }
       
   205     else {
       
   206       fYear = fYear - 1;
       
   207     }
       
   208   }
       
   209   
       
   210   private void stepMonth() {
       
   211     if(fIsPlus){
       
   212       fMonth = fMonth + 1;
       
   213     }
       
   214     else {
       
   215       fMonth = fMonth - 1;
       
   216     }
       
   217     if(fMonth > 12) { 
       
   218       fMonth = 1;
       
   219       stepYear();
       
   220     }
       
   221     else if(fMonth < 1){
       
   222       fMonth = 12;
       
   223       stepYear();
       
   224     }
       
   225   }
       
   226 
       
   227   private void stepDay() {
       
   228     if(fIsPlus){
       
   229       fDay = fDay + 1;
       
   230     }
       
   231     else {
       
   232       fDay = fDay - 1;
       
   233     }
       
   234     if(fDay > numDaysInMonth()){
       
   235       fDay = 1;
       
   236       stepMonth();
       
   237     }
       
   238     else if (fDay < 1){
       
   239       fDay = numDaysInPreviousMonth();
       
   240       stepMonth();
       
   241     }
       
   242   }
       
   243   
       
   244   private int numDaysInMonth(){
       
   245     return DateTime.getNumDaysInMonth(fYear, fMonth);
       
   246   }
       
   247   
       
   248   private int numDaysInPreviousMonth(){
       
   249     int result = 0;
       
   250     if(fMonth > 1) {
       
   251       result = DateTime.getNumDaysInMonth(fYear, fMonth - 1);
       
   252     }
       
   253     else {
       
   254       result = DateTime.getNumDaysInMonth(fYear - 1 , 12);
       
   255     }
       
   256     return result;
       
   257   }
       
   258   
       
   259   private void stepHour() {
       
   260     if(fIsPlus){
       
   261       fHour = fHour + 1;      
       
   262     }
       
   263     else {
       
   264       fHour = fHour - 1;      
       
   265     }
       
   266     if(fHour > 23){
       
   267       fHour = 0;
       
   268       stepDay();
       
   269     }
       
   270     else if (fHour < 0){
       
   271       fHour = 23;
       
   272       stepDay();
       
   273     }
       
   274   }
       
   275   
       
   276   private void stepMinute() {
       
   277     if(fIsPlus){
       
   278       fMinute = fMinute + 1;      
       
   279     }
       
   280     else {
       
   281       fMinute = fMinute - 1;      
       
   282     }
       
   283     if(fMinute > 59){
       
   284       fMinute = 0;
       
   285       stepHour();
       
   286     }
       
   287     else if (fMinute < 0){
       
   288       fMinute = 59;
       
   289       stepHour();
       
   290     }
       
   291   }
       
   292   
       
   293   private void stepSecond() {
       
   294     if(fIsPlus){
       
   295       fSecond = fSecond + 1;        
       
   296     }
       
   297     else {
       
   298       fSecond = fSecond - 1;        
       
   299     }
       
   300     if (fSecond > 59){
       
   301       fSecond = 0;
       
   302       stepMinute();
       
   303     }
       
   304     else if (fSecond < 0){
       
   305       fSecond = 59;
       
   306       stepMinute();
       
   307     }
       
   308   }
       
   309   
       
   310   private void handleMonthOverflow(){
       
   311     int daysInMonth = numDaysInMonth();
       
   312     if( fDay > daysInMonth ){
       
   313       if(DayOverflow.Abort == fDayOverflow) {
       
   314         throw new RuntimeException(
       
   315           "Day Overflow: Year:" + fYear + " Month:" + fMonth + " has " + daysInMonth + " days, but day has value:" + fDay + 
       
   316           " To avoid these exceptions, please specify a different DayOverflow policy."
       
   317         );
       
   318       }
       
   319       else if (DayOverflow.FirstDay == fDayOverflow) {
       
   320         fDay = 1;
       
   321         stepMonth();
       
   322       }
       
   323       else if (DayOverflow.LastDay == fDayOverflow) {
       
   324         fDay = daysInMonth;
       
   325       }
       
   326       else if (DayOverflow.Spillover == fDayOverflow) {
       
   327         int overflowAmount = fDay - daysInMonth;
       
   328         fDay = overflowAmount;
       
   329         stepMonth();
       
   330       }
       
   331     }
       
   332   }
       
   333 
       
   334 
       
   335 }