1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
48
49 public class DefaultDateFunctions
50 {
51
52 private static final double MIN_DATE = -657434.0d;
53
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
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
102 if(year < 100) {
103 year += ((year <= 29) ? 2000 : 1900);
104 }
105
106
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
223
224
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
254 result = getDayDiff(ldt1, ldt2) / 7;
255 } else if(intv.equalsIgnoreCase(INTV_WEEK)) {
256
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
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
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
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
461 }
462 }
463
464
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
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
484
485 return (((day - 1) - (firstDay - 1) + 7) % 7) + 1;
486 }
487
488 private static int weekDayToDayOfWeek(int weekday, int firstDay) {
489
490
491 return (((firstDay - 1) + (weekday - 1)) % 7) + 1;
492 }
493
494 static int getFirstDayParam(
495 LocaleContext ctx, Value[] params, int idx) {
496
497
498 return getOptionalIntParam(ctx, params, idx, 1, 0);
499 }
500
501 static int getFirstWeekTypeParam(
502 LocaleContext ctx, Value[] params, int idx) {
503
504
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
514
515 break;
516 case 2:
517
518 minDays = 4;
519 break;
520 case 3:
521
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 }