View Javadoc
1   /*
2   Copyright (c) 2017 James Ahlborn
3   
4   Licensed under the Apache License, Version 2.0 (the "License");
5   you may not use this file except in compliance with the License.
6   You may obtain a copy of the License at
7   
8       http://www.apache.org/licenses/LICENSE-2.0
9   
10  Unless required by applicable law or agreed to in writing, software
11  distributed under the License is distributed on an "AS IS" BASIS,
12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  See the License for the specific language governing permissions and
14  limitations under the License.
15  */
16  
17  package com.healthmarketscience.jackcess.impl.expr;
18  
19  
20  import java.time.DateTimeException;
21  import java.time.DayOfWeek;
22  import java.time.LocalDate;
23  import java.time.LocalDateTime;
24  import java.time.LocalTime;
25  import java.time.Month;
26  import java.time.MonthDay;
27  import java.time.Year;
28  import java.time.format.DateTimeFormatter;
29  import java.time.format.TextStyle;
30  import java.time.temporal.ChronoField;
31  import java.time.temporal.ChronoUnit;
32  import java.time.temporal.TemporalAccessor;
33  import java.time.temporal.WeekFields;
34  
35  import com.healthmarketscience.jackcess.expr.EvalContext;
36  import com.healthmarketscience.jackcess.expr.EvalException;
37  import com.healthmarketscience.jackcess.expr.Function;
38  import com.healthmarketscience.jackcess.expr.LocaleContext;
39  import com.healthmarketscience.jackcess.expr.TemporalConfig;
40  import com.healthmarketscience.jackcess.expr.Value;
41  import com.healthmarketscience.jackcess.impl.ColumnImpl;
42  import static com.healthmarketscience.jackcess.impl.expr.DefaultFunctions.*;
43  import static com.healthmarketscience.jackcess.impl.expr.FunctionSupport.*;
44  
45  /**
46   *
47   * @author James Ahlborn
48   */
49  public class DefaultDateFunctions
50  {
51    // min, valid, recognizable date: January 1, 100 A.D. 00:00:00
52    private static final double MIN_DATE = -657434.0d;
53    // max, valid, recognizable date: December 31, 9999 A.D. 23:59:59
54    private static final double MAX_DATE = 2958465.999988426d;
55  
56    private static final String INTV_YEAR = "yyyy";
57    private static final String INTV_QUARTER = "q";
58    private static final String INTV_MONTH = "m";
59    private static final String INTV_DAY_OF_YEAR = "y";
60    private static final String INTV_DAY = "d";
61    private static final String INTV_WEEKDAY = "w";
62    private static final String INTV_WEEK = "ww";
63    private static final String INTV_HOUR = "h";
64    private static final String INTV_MINUTE = "n";
65    private static final String INTV_SECOND = "s";
66  
67    private static final WeekFields SUNDAY_FIRST =
68      WeekFields.of(DayOfWeek.SUNDAY, 1);
69  
70    private DefaultDateFunctions() {}
71  
72    static void init() {
73      // dummy method to ensure this class is loaded
74    }
75  
76    public static final Function DATE = registerFunc(new Func0("Date") {
77      @Override
78      protected Value eval0(EvalContext ctx) {
79        return ValueSupport.toValue(LocalDate.now());
80      }
81    });
82  
83    public static final Function DATEVALUE = registerFunc(new Func1NullIsNull("DateValue") {
84      @Override
85      protected Value../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval1(EvalContext ctx, Value param1) {
86        Value dv = param1.getAsDateTimeValue(ctx);
87        if(dv.getType() == Value.Type.DATE) {
88          return dv;
89        }
90        return ValueSupport.toValue(dv.getAsLocalDateTime(ctx).toLocalDate());
91      }
92    });
93  
94    public static final Function DATESERIAL = registerFunc(new Func3("DateSerial") {
95      @Override
96      protected Valuef="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Valuef="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Value../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval3(EvalContext ctx, Valuef="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Valuef="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Value param1, Valuef="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Value param2, Value param3) {
97        int year = param1.getAsLongInt(ctx);
98        int month = param2.getAsLongInt(ctx);
99        int day = param3.getAsLongInt(ctx);
100 
101       // "default" two digit year handling
102       if(year < 100) {
103         year += ((year <= 29) ? 2000 : 1900);
104       }
105 
106       // we have to construct incrementatlly to handle out of range values
107       LocalDate ld = LocalDate.of(year,1,1).plusMonths(month - 1)
108         .plusDays(day - 1);
109 
110       return ValueSupport.toValue(ld);
111     }
112   });
113 
114   public static final Function DATEPART = registerFunc(new FuncVar("DatePart", 2, 4) {
115     @Override
116     protected Value/com/healthmarketscience/jackcess/expr/Value.html#Value">Value evalVar(EvalContext ctx, Value[] params) {
117       Value param2 = params[1];
118       if(param2.isNull()) {
119         return ValueSupport.NULL_VAL;
120       }
121 
122       int firstDay = getFirstDayParam(ctx, params, 2);
123       int firstWeekType = getFirstWeekTypeParam(ctx, params, 3);
124 
125       String intv = params[0].getAsString(ctx).trim();
126       int result = -1;
127       if(intv.equalsIgnoreCase(INTV_YEAR)) {
128         result = param2.getAsLocalDateTime(ctx).getYear();
129       } else if(intv.equalsIgnoreCase(INTV_QUARTER)) {
130         result = getQuarter(param2.getAsLocalDateTime(ctx));
131       } else if(intv.equalsIgnoreCase(INTV_MONTH)) {
132         result = param2.getAsLocalDateTime(ctx).getMonthValue();
133       } else if(intv.equalsIgnoreCase(INTV_DAY_OF_YEAR)) {
134         result = param2.getAsLocalDateTime(ctx).getDayOfYear();
135       } else if(intv.equalsIgnoreCase(INTV_DAY)) {
136         result = param2.getAsLocalDateTime(ctx).getDayOfMonth();
137       } else if(intv.equalsIgnoreCase(INTV_WEEKDAY)) {
138         int dayOfWeek = param2.getAsLocalDateTime(ctx)
139           .get(SUNDAY_FIRST.dayOfWeek());
140         result = dayOfWeekToWeekDay(dayOfWeek, firstDay);
141       } else if(intv.equalsIgnoreCase(INTV_WEEK)) {
142         result = weekOfYear(ctx, param2, firstDay, firstWeekType);
143       } else if(intv.equalsIgnoreCase(INTV_HOUR)) {
144         result = param2.getAsLocalDateTime(ctx).getHour();
145       } else if(intv.equalsIgnoreCase(INTV_MINUTE)) {
146         result = param2.getAsLocalDateTime(ctx).getMinute();
147       } else if(intv.equalsIgnoreCase(INTV_SECOND)) {
148         result = param2.getAsLocalDateTime(ctx).getSecond();
149       } else {
150         throw new EvalException("Invalid interval " + intv);
151       }
152 
153       return ValueSupport.toValue(result);
154     }
155   });
156 
157   public static final Function DATEADD = registerFunc(new Func3("DateAdd") {
158     @Override
159     protected Value eval3(EvalContext ctx,
160                           Valuef="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Valuef="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Value param1, Valuef="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Value param2, Value param3) {
161       if(param3.isNull()) {
162         return ValueSupport.NULL_VAL;
163       }
164 
165       String intv = param1.getAsString(ctx).trim();
166       int val = param2.getAsLongInt(ctx);
167 
168       LocalDateTime ldt = param3.getAsLocalDateTime(ctx);
169 
170       if(intv.equalsIgnoreCase(INTV_YEAR)) {
171         ldt = ldt.plus(val, ChronoUnit.YEARS);
172       } else if(intv.equalsIgnoreCase(INTV_QUARTER)) {
173         ldt = ldt.plus(val * 3, ChronoUnit.MONTHS);
174       } else if(intv.equalsIgnoreCase(INTV_MONTH)) {
175         ldt = ldt.plus(val, ChronoUnit.MONTHS);
176       } else if(intv.equalsIgnoreCase(INTV_DAY_OF_YEAR) ||
177                 intv.equalsIgnoreCase(INTV_DAY) ||
178                 intv.equalsIgnoreCase(INTV_WEEKDAY)) {
179         ldt = ldt.plus(val, ChronoUnit.DAYS);
180       } else if(intv.equalsIgnoreCase(INTV_WEEK)) {
181         ldt = ldt.plus(val, ChronoUnit.WEEKS);
182       } else if(intv.equalsIgnoreCase(INTV_HOUR)) {
183         ldt = ldt.plus(val, ChronoUnit.HOURS);
184       } else if(intv.equalsIgnoreCase(INTV_MINUTE)) {
185         ldt = ldt.plus(val, ChronoUnit.MINUTES);
186       } else if(intv.equalsIgnoreCase(INTV_SECOND)) {
187         ldt = ldt.plus(val, ChronoUnit.SECONDS);
188       } else {
189         throw new EvalException("Invalid interval " + intv);
190       }
191 
192       return ValueSupport.toValue(ldt);
193     }
194   });
195 
196   public static final Function DATEDIFF = registerFunc(new FuncVar("DateDiff", 3, 5) {
197     @Override
198     protected Value/com/healthmarketscience/jackcess/expr/Value.html#Value">Value evalVar(EvalContext ctx, Value[] params) {
199 
200       Value param2 = params[1];
201       Value param3 = params[2];
202       if(param2.isNull() || param3.isNull()) {
203         return ValueSupport.NULL_VAL;
204       }
205 
206       int firstDay = getFirstDayParam(ctx, params, 3);
207       int firstWeekType = getFirstWeekTypeParam(ctx, params, 4);
208 
209       String intv = params[0].getAsString(ctx).trim();
210 
211       LocalDateTime ldt1 = param2.getAsLocalDateTime(ctx);
212       LocalDateTime ldt2 = param3.getAsLocalDateTime(ctx);
213 
214       int sign = 1;
215       if(ldt1.isAfter(ldt2)) {
216         LocalDateTime tmp = ldt1;
217         ldt1 = ldt2;
218         ldt2 = tmp;
219         sign = -1;
220       }
221 
222       // NOTE: DateDiff understands leap years, but not daylight savings time
223       // (i.e. it doesn't really take into account timezones).  so all time
224       // based calculations assume 24 hour days.
225 
226       int result = -1;
227       if(intv.equalsIgnoreCase(INTV_YEAR)) {
228         result = ldt2.getYear() - ldt1.getYear();
229       } else if(intv.equalsIgnoreCase(INTV_QUARTER)) {
230         int y1 = ldt1.getYear();
231         int q1 = getQuarter(ldt1);
232         int y2 = ldt2.getYear();
233         int q2 = getQuarter(ldt2);
234         while(y2 > y1) {
235           q2 += 4;
236           --y2;
237         }
238         result = q2 - q1;
239       } else if(intv.equalsIgnoreCase(INTV_MONTH)) {
240         int y1 = ldt1.getYear();
241         int m1 = ldt1.getMonthValue();
242         int y2 = ldt2.getYear();
243         int m2 = ldt2.getMonthValue();
244         while(y2 > y1) {
245           m2 += 12;
246           --y2;
247         }
248         result = m2 - m1;
249       } else if(intv.equalsIgnoreCase(INTV_DAY_OF_YEAR) ||
250                 intv.equalsIgnoreCase(INTV_DAY)) {
251         result = getDayDiff(ldt1, ldt2);
252       } else if(intv.equalsIgnoreCase(INTV_WEEKDAY)) {
253         // this calulates number of 7 day periods between two dates
254         result = getDayDiff(ldt1, ldt2) / 7;
255       } else if(intv.equalsIgnoreCase(INTV_WEEK)) {
256         // this counts number of "week of year" intervals between two dates
257         WeekFields weekFields = weekFields(firstDay, firstWeekType);
258         int w1 = ldt1.get(weekFields.weekOfWeekBasedYear());
259         int y1 = ldt1.get(weekFields.weekBasedYear());
260         int w2 = ldt2.get(weekFields.weekOfWeekBasedYear());
261         int y2 = ldt2.get(weekFields.weekBasedYear());
262         while(y2 > y1) {
263           --y2;
264           w2 += weeksInYear(y2, weekFields);
265         }
266         result = w2 - w1;
267       } else if(intv.equalsIgnoreCase(INTV_HOUR)) {
268         result = getHourDiff(ldt1, ldt2);
269       } else if(intv.equalsIgnoreCase(INTV_MINUTE)) {
270         result = getMinuteDiff(ldt1, ldt2);
271       } else if(intv.equalsIgnoreCase(INTV_SECOND)) {
272         int s1 = ldt1.getSecond();
273         int s2 = ldt2.getSecond();
274         int minuteDiff = getMinuteDiff(ldt1, ldt2);
275         result = (s2 + (60 * minuteDiff)) - s1;
276       } else {
277         throw new EvalException("Invalid interval " + intv);
278       }
279 
280       return ValueSupport.toValue(result * sign);
281     }
282   });
283 
284   public static final Function NOW = registerFunc(new Func0("Now") {
285     @Override
286     protected Value eval0(EvalContext ctx) {
287       return ValueSupport.toValue(Value.Type.DATE_TIME,
288                                   LocalDateTime.now(ctx.getZoneId()));
289     }
290   });
291 
292   public static final Function TIME = registerFunc(new Func0("Time") {
293     @Override
294     protected Value eval0(EvalContext ctx) {
295       return ValueSupport.toValue(LocalTime.now(ctx.getZoneId()));
296     }
297   });
298 
299   public static final Function TIMEVALUE = registerFunc(new Func1NullIsNull("TimeValue") {
300     @Override
301     protected Value../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval1(EvalContext ctx, Value param1) {
302       Value dv = param1.getAsDateTimeValue(ctx);
303       if(dv.getType() == Value.Type.TIME) {
304         return dv;
305       }
306       return ValueSupport.toValue(dv.getAsLocalDateTime(ctx).toLocalTime());
307     }
308   });
309 
310   public static final Function TIMER = registerFunc(new Func0("Timer") {
311     @Override
312     protected Value eval0(EvalContext ctx) {
313       double dd = LocalTime.now(ctx.getZoneId())
314         .get(ChronoField.MILLI_OF_DAY) / 1000d;
315       return ValueSupport.toValue(dd);
316     }
317   });
318 
319   public static final Function TIMESERIAL = registerFunc(new Func3("TimeSerial") {
320     @Override
321     protected Valuef="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Valuef="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Value../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval3(EvalContext ctx, Valuef="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Valuef="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Value param1, Valuef="../../../../../com/healthmarketscience/jackcess/expr/Value.html#Value">Value param2, Value param3) {
322       int hours = param1.getAsLongInt(ctx);
323       int minutes = param2.getAsLongInt(ctx);
324       int seconds = param3.getAsLongInt(ctx);
325 
326       // we have to construct incrementatlly to handle out of range values
327       LocalTime lt = ColumnImpl.BASE_LT.plusHours(hours).plusMinutes(minutes)
328         .plusSeconds(seconds);
329 
330       return ValueSupport.toValue(lt);
331     }
332   });
333 
334   public static final Function HOUR = registerFunc(new Func1NullIsNull("Hour") {
335     @Override
336     protected Value../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval1(EvalContext ctx, Value param1) {
337       return ValueSupport.toValue(param1.getAsLocalDateTime(ctx).getHour());
338     }
339   });
340 
341   public static final Function MINUTE = registerFunc(new Func1NullIsNull("Minute") {
342     @Override
343     protected Value../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval1(EvalContext ctx, Value param1) {
344       return ValueSupport.toValue(param1.getAsLocalDateTime(ctx).getMinute());
345     }
346   });
347 
348   public static final Function SECOND = registerFunc(new Func1NullIsNull("Second") {
349     @Override
350     protected Value../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval1(EvalContext ctx, Value param1) {
351       return ValueSupport.toValue(param1.getAsLocalDateTime(ctx).getSecond());
352     }
353   });
354 
355   public static final Function YEAR = registerFunc(new Func1NullIsNull("Year") {
356     @Override
357     protected Value../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval1(EvalContext ctx, Value param1) {
358       return ValueSupport.toValue(param1.getAsLocalDateTime(ctx).getYear());
359     }
360   });
361 
362   public static final Function MONTH = registerFunc(new Func1NullIsNull("Month") {
363     @Override
364     protected Value../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval1(EvalContext ctx, Value param1) {
365       return ValueSupport.toValue(param1.getAsLocalDateTime(ctx).getMonthValue());
366     }
367   });
368 
369   public static final Function MONTHNAME = registerFunc(new FuncVar("MonthName", 1, 2) {
370     @Override
371     protected Value/com/healthmarketscience/jackcess/expr/Value.html#Value">Value evalVar(EvalContext ctx, Value[] params) {
372       Value param1 = params[0];
373       if(param1.isNull()) {
374         return ValueSupport.NULL_VAL;
375       }
376       Month month = Month.of(param1.getAsLongInt(ctx));
377 
378       TextStyle textStyle = getTextStyle(ctx, params, 1);
379       String monthName = month.getDisplayName(
380           textStyle, ctx.getTemporalConfig().getLocale());
381       return ValueSupport.toValue(monthName);
382     }
383   });
384 
385   public static final Function DAY = registerFunc(new Func1NullIsNull("Day") {
386     @Override
387     protected Value../com/healthmarketscience/jackcess/expr/Value.html#Value">Value eval1(EvalContext ctx, Value param1) {
388       return ValueSupport.toValue(
389           param1.getAsLocalDateTime(ctx).getDayOfMonth());
390     }
391   });
392 
393   public static final Function WEEKDAY = registerFunc(new FuncVar("Weekday", 1, 2) {
394     @Override
395     protected Value/com/healthmarketscience/jackcess/expr/Value.html#Value">Value evalVar(EvalContext ctx, Value[] params) {
396       Value param1 = params[0];
397       if(param1.isNull()) {
398         return ValueSupport.NULL_VAL;
399       }
400       int dayOfWeek = param1.getAsLocalDateTime(ctx)
401         .get(SUNDAY_FIRST.dayOfWeek());
402 
403       int firstDay = getFirstDayParam(ctx, params, 1);
404 
405       return ValueSupport.toValue(dayOfWeekToWeekDay(dayOfWeek, firstDay));
406     }
407   });
408 
409   public static final Function WEEKDAYNAME = registerFunc(new FuncVar("WeekdayName", 1, 3) {
410     @Override
411     protected Value/com/healthmarketscience/jackcess/expr/Value.html#Value">Value evalVar(EvalContext ctx, Value[] params) {
412       Value param1 = params[0];
413       if(param1.isNull()) {
414         return ValueSupport.NULL_VAL;
415       }
416       int weekday = param1.getAsLongInt(ctx);
417 
418       TextStyle textStyle = getTextStyle(ctx, params, 1);
419 
420       int firstDay = getFirstDayParam(ctx, params, 2);
421 
422       int dayOfWeek = weekDayToDayOfWeek(weekday, firstDay);
423       String weekdayName = dayOfWeek(dayOfWeek).getDisplayName(
424           textStyle, ctx.getTemporalConfig().getLocale());
425       return ValueSupport.toValue(weekdayName);
426     }
427   });
428 
429   static Value stringToDateValue(LocaleContext ctx, String valStr) {
430     // see if we can coerce to date/time
431     TemporalConfig.Type valTempType = ExpressionTokenizer.determineDateType(
432         valStr, ctx);
433 
434     if(valTempType != null) {
435 
436       DateTimeFormatter parseDf = ctx.createDateFormatter(
437           ctx.getTemporalConfig().getDateTimeFormat(valTempType));
438 
439       try {
440         TemporalAccessor parsedInfo = parseDf.parse(valStr);
441 
442         LocalDate ld = ColumnImpl.BASE_LD;
443         if(valTempType.includesDate()) {
444           // the year may not be explicitly specified
445           if(parsedInfo.isSupported(ChronoField.YEAR)) {
446             ld = LocalDate.from(parsedInfo);
447           } else {
448             ld = MonthDay.from(parsedInfo).atYear(
449                 Year.now(ctx.getZoneId()).getValue());
450           }
451         }
452 
453         LocalTime lt = ColumnImpl.BASE_LT;
454         if(valTempType.includesTime()) {
455           lt = LocalTime.from(parsedInfo);
456         }
457 
458         return ValueSupport.toValue(LocalDateTime.of(ld, lt));
459       } catch(DateTimeException de) {
460         // note a valid date/time
461       }
462     }
463 
464     // not a valid date string, not a date/time
465     return null;
466   }
467 
468   static boolean isValidDateDouble(double dd) {
469     return ((dd >= MIN_DATE) && (dd <= MAX_DATE));
470   }
471 
472   static Value numberToDateValue(double dd) {
473     if(!isValidDateDouble(dd)) {
474       // outside valid date range
475       return null;
476     }
477 
478     LocalDateTime ldt = ColumnImpl.ldtFromLocalDateDouble(dd);
479     return ValueSupport.toValue(ldt);
480   }
481 
482   private static int dayOfWeekToWeekDay(int day, int firstDay) {
483     // shift all the values to 0 based to calculate the correct value, then
484     // back to 1 based to return the result
485     return (((day - 1) - (firstDay - 1) + 7) % 7) + 1;
486   }
487 
488   private static int weekDayToDayOfWeek(int weekday, int firstDay) {
489     // shift all the values to 0 based to calculate the correct value, then
490     // back to 1 based to return the result
491     return (((firstDay - 1) + (weekday - 1)) % 7) + 1;
492   }
493 
494   static int getFirstDayParam(
495       LocaleContext ctx, Value[] params, int idx) {
496     // vbSunday (default) 1
497     // vbUseSystem 0
498     return getOptionalIntParam(ctx, params, idx, 1, 0);
499   }
500 
501   static int getFirstWeekTypeParam(
502       LocaleContext ctx, Value[] params, int idx) {
503     // vbFirstJan1 (default) 1
504     // vbUseSystem 0
505     return getOptionalIntParam(ctx, params, idx, 1, 0);
506   }
507 
508   static WeekFields weekFields(int firstDay, int firstWeekType) {
509 
510     int minDays = 1;
511     switch(firstWeekType) {
512     case 1:
513       // vbUseSystem 0
514       // vbFirstJan1 1 (default)
515       break;
516     case 2:
517       // vbFirstFourDays 2
518       minDays = 4;
519       break;
520     case 3:
521       // vbFirstFullWeek 3
522       minDays = 7;
523       break;
524     default:
525       throw new EvalException("Invalid first week of year type " +
526                               firstWeekType);
527     }
528 
529     return WeekFields.of(dayOfWeek(firstDay), minDays);
530   }
531 
532   private static DayOfWeek dayOfWeek(int dayOfWeek) {
533     return DayOfWeek.SUNDAY.plus(dayOfWeek - 1);
534   }
535 
536   private static TextStyle getTextStyle(EvalContext ctx, Value[] params,
537                                         int idx) {
538     boolean abbreviate = getOptionalBooleanParam(ctx, params, idx);
539     return (abbreviate ? TextStyle.SHORT : TextStyle.FULL);
540   }
541 
542   private static int weekOfYear(EvalContext ctx, Value param, int firstDay,
543                                 int firstWeekType) {
544     return weekOfYear(param.getAsLocalDateTime(ctx), firstDay, firstWeekType);
545   }
546 
547   private static int weekOfYear(LocalDateTime ldt, int firstDay,
548                                 int firstWeekType) {
549     WeekFields weekFields = weekFields(firstDay, firstWeekType);
550     return ldt.get(weekFields.weekOfWeekBasedYear());
551   }
552 
553   private static int weeksInYear(int year, WeekFields weekFields) {
554     return (int)LocalDate.of(year,2,1).range(weekFields.weekOfWeekBasedYear())
555       .getMaximum();
556   }
557 
558   private static int getQuarter(LocalDateTime ldt) {
559     int month = ldt.getMonthValue() - 1;
560     return (month / 3) + 1;
561   }
562 
563   private static int getDayDiff(LocalDateTime ldt1, LocalDateTime ldt2) {
564     int y1 = ldt1.getYear();
565     int d1 = ldt1.getDayOfYear();
566     int y2 = ldt2.getYear();
567     int d2 = ldt2.getDayOfYear();
568     while(y2  > y1) {
569       ldt2 = ldt2.minusYears(1);
570       d2 += ldt2.range(ChronoField.DAY_OF_YEAR).getMaximum();
571       y2 = ldt2.getYear();
572     }
573     return d2 - d1;
574   }
575 
576   private static int getHourDiff(LocalDateTime ldt1, LocalDateTime ldt2) {
577     int h1 = ldt1.getHour();
578     int h2 = ldt2.getHour();
579     int dayDiff = getDayDiff(ldt1, ldt2);
580     return (h2 + (24 * dayDiff)) - h1;
581   }
582 
583   private static int getMinuteDiff(LocalDateTime ldt1, LocalDateTime ldt2) {
584     int m1 = ldt1.getMinute();
585     int m2 = ldt2.getMinute();
586     int hourDiff = getHourDiff(ldt1, ldt2);
587     return (m2 + (60 * hourDiff)) - m1;
588   }
589 }