You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

DefaultDateFunctions.java 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. /*
  2. Copyright (c) 2017 James Ahlborn
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package com.healthmarketscience.jackcess.impl.expr;
  14. import java.math.BigDecimal;
  15. import java.text.DateFormat;
  16. import java.util.Calendar;
  17. import java.util.Date;
  18. import com.healthmarketscience.jackcess.expr.EvalContext;
  19. import com.healthmarketscience.jackcess.expr.EvalException;
  20. import com.healthmarketscience.jackcess.expr.Function;
  21. import com.healthmarketscience.jackcess.expr.Value;
  22. import com.healthmarketscience.jackcess.impl.ColumnImpl;
  23. import static com.healthmarketscience.jackcess.impl.expr.DefaultFunctions.*;
  24. /**
  25. *
  26. * @author James Ahlborn
  27. */
  28. public class DefaultDateFunctions
  29. {
  30. // min, valid, recognizable date: January 1, 100 A.D. 00:00:00
  31. private static final double MIN_DATE = -657434.0d;
  32. // max, valid, recognizable date: December 31, 9999 A.D. 23:59:59
  33. private static final double MAX_DATE = 2958465.999988426d;
  34. private static final long SECONDS_PER_DAY = 24L * 60L * 60L;
  35. private static final double DSECONDS_PER_DAY = SECONDS_PER_DAY;
  36. private static final long SECONDS_PER_HOUR = 60L * 60L;
  37. private static final long SECONDS_PER_MINUTE = 60L;
  38. private DefaultDateFunctions() {}
  39. static void init() {
  40. // dummy method to ensure this class is loaded
  41. }
  42. public static final Function DATE = registerFunc(new Func0("Date") {
  43. @Override
  44. protected Value eval0(EvalContext ctx) {
  45. DateFormat fmt = BuiltinOperators.getDateFormatForType(ctx, Value.Type.DATE);
  46. double dd = dateOnly(currentTimeDouble(fmt));
  47. return BuiltinOperators.toValue(Value.Type.DATE, dd, fmt);
  48. }
  49. });
  50. public static final Function DATEVALUE = registerFunc(new Func1NullIsNull("DateValue") {
  51. @Override
  52. protected Value eval1(EvalContext ctx, Value param1) {
  53. Value dv = nonNullToDateValue(ctx, param1);
  54. if(dv.getType() == Value.Type.DATE) {
  55. return dv;
  56. }
  57. double dd = dateOnly(dv.getAsDouble());
  58. DateFormat fmt = BuiltinOperators.getDateFormatForType(ctx, Value.Type.DATE);
  59. return BuiltinOperators.toValue(Value.Type.DATE, dd, fmt);
  60. }
  61. });
  62. public static final Function DATESERIAL = registerFunc(new Func3("DateSerial") {
  63. @Override
  64. protected Value eval3(EvalContext ctx, Value param1, Value param2, Value param3) {
  65. int year = param1.getAsLongInt();
  66. int month = param2.getAsLongInt();
  67. int day = param3.getAsLongInt();
  68. // "default" two digit year handling
  69. if(year < 100) {
  70. year += ((year <= 29) ? 2000 : 1900);
  71. }
  72. DateFormat fmt = BuiltinOperators.getDateFormatForType(ctx, Value.Type.DATE);
  73. Calendar cal = fmt.getCalendar();
  74. cal.clear();
  75. cal.set(Calendar.YEAR, year);
  76. // convert to 0 based value
  77. cal.set(Calendar.MONTH, month - 1);
  78. cal.set(Calendar.DAY_OF_MONTH, day);
  79. return BuiltinOperators.toValue(Value.Type.DATE, cal.getTime(), fmt);
  80. }
  81. });
  82. public static final Function NOW = registerFunc(new Func0("Now") {
  83. @Override
  84. protected Value eval0(EvalContext ctx) {
  85. DateFormat fmt = BuiltinOperators.getDateFormatForType(ctx, Value.Type.DATE_TIME);
  86. return BuiltinOperators.toValue(Value.Type.DATE_TIME, new Date(), fmt);
  87. }
  88. });
  89. public static final Function TIME = registerFunc(new Func0("Time") {
  90. @Override
  91. protected Value eval0(EvalContext ctx) {
  92. DateFormat fmt = BuiltinOperators.getDateFormatForType(ctx, Value.Type.TIME);
  93. double dd = timeOnly(currentTimeDouble(fmt));
  94. return BuiltinOperators.toValue(Value.Type.TIME, dd, fmt);
  95. }
  96. });
  97. public static final Function TIMEVALUE = registerFunc(new Func1NullIsNull("TimeValue") {
  98. @Override
  99. protected Value eval1(EvalContext ctx, Value param1) {
  100. Value dv = nonNullToDateValue(ctx, param1);
  101. if(dv.getType() == Value.Type.TIME) {
  102. return dv;
  103. }
  104. double dd = timeOnly(dv.getAsDouble());
  105. DateFormat fmt = BuiltinOperators.getDateFormatForType(ctx, Value.Type.TIME);
  106. return BuiltinOperators.toValue(Value.Type.TIME, dd, fmt);
  107. }
  108. });
  109. public static final Function TIMER = registerFunc(new Func0("Timer") {
  110. @Override
  111. protected Value eval0(EvalContext ctx) {
  112. DateFormat fmt = BuiltinOperators.getDateFormatForType(ctx, Value.Type.TIME);
  113. double dd = timeOnly(currentTimeDouble(fmt)) * DSECONDS_PER_DAY;
  114. return BuiltinOperators.toValue(dd);
  115. }
  116. });
  117. public static final Function TIMESERIAL = registerFunc(new Func3("TimeSerial") {
  118. @Override
  119. protected Value eval3(EvalContext ctx, Value param1, Value param2, Value param3) {
  120. int hours = param1.getAsLongInt();
  121. int minutes = param2.getAsLongInt();
  122. int seconds = param3.getAsLongInt();
  123. long totalSeconds = (hours * SECONDS_PER_HOUR) +
  124. (minutes * SECONDS_PER_MINUTE) + seconds;
  125. if(totalSeconds < 0L) {
  126. do {
  127. totalSeconds += SECONDS_PER_DAY;
  128. } while(totalSeconds < 0L);
  129. } else if(totalSeconds > SECONDS_PER_DAY) {
  130. totalSeconds %= SECONDS_PER_DAY;
  131. }
  132. DateFormat fmt = BuiltinOperators.getDateFormatForType(ctx, Value.Type.TIME);
  133. double dd = totalSeconds / DSECONDS_PER_DAY;
  134. return BuiltinOperators.toValue(Value.Type.TIME, dd, fmt);
  135. }
  136. });
  137. public static final Function HOUR = registerFunc(new Func1NullIsNull("Hour") {
  138. @Override
  139. protected Value eval1(EvalContext ctx, Value param1) {
  140. return BuiltinOperators.toValue(
  141. nonNullToCalendarField(ctx, param1, Calendar.HOUR_OF_DAY));
  142. }
  143. });
  144. public static final Function MINUTE = registerFunc(new Func1NullIsNull("Minute") {
  145. @Override
  146. protected Value eval1(EvalContext ctx, Value param1) {
  147. return BuiltinOperators.toValue(
  148. nonNullToCalendarField(ctx, param1, Calendar.MINUTE));
  149. }
  150. });
  151. public static final Function SECOND = registerFunc(new Func1NullIsNull("Second") {
  152. @Override
  153. protected Value eval1(EvalContext ctx, Value param1) {
  154. return BuiltinOperators.toValue(
  155. nonNullToCalendarField(ctx, param1, Calendar.SECOND));
  156. }
  157. });
  158. public static final Function YEAR = registerFunc(new Func1NullIsNull("Year") {
  159. @Override
  160. protected Value eval1(EvalContext ctx, Value param1) {
  161. return BuiltinOperators.toValue(
  162. nonNullToCalendarField(ctx, param1, Calendar.YEAR));
  163. }
  164. });
  165. public static final Function MONTH = registerFunc(new Func1NullIsNull("Month") {
  166. @Override
  167. protected Value eval1(EvalContext ctx, Value param1) {
  168. // convert from 0 based to 1 based value
  169. return BuiltinOperators.toValue(
  170. nonNullToCalendarField(ctx, param1, Calendar.MONTH) + 1);
  171. }
  172. });
  173. public static final Function DAY = registerFunc(new Func1NullIsNull("Day") {
  174. @Override
  175. protected Value eval1(EvalContext ctx, Value param1) {
  176. return BuiltinOperators.toValue(
  177. nonNullToCalendarField(ctx, param1, Calendar.DAY_OF_MONTH));
  178. }
  179. });
  180. public static final Function WEEKDAY = registerFunc(new FuncVar("Weekday", 1, 2) {
  181. @Override
  182. protected Value evalVar(EvalContext ctx, Value[] params) {
  183. Value param1 = params[0];
  184. if(param1 == null) {
  185. return null;
  186. }
  187. int day = nonNullToCalendarField(ctx, param1, Calendar.DAY_OF_WEEK);
  188. // vbSunday (default)
  189. int firstDay = 1;
  190. if(params.length > 1) {
  191. firstDay = params[1].getAsLongInt();
  192. if(firstDay == 0) {
  193. // 0 == vbUseSystem, so we will use the default "sunday"
  194. firstDay = 1;
  195. }
  196. }
  197. // shift all the values to 0 based to calculate the correct value, then
  198. // back to 1 based to return the result
  199. day = (((day - 1) - (firstDay - 1) + 7) % 7) + 1;
  200. return BuiltinOperators.toValue(day);
  201. }
  202. });
  203. private static int nonNullToCalendarField(EvalContext ctx, Value param,
  204. int field) {
  205. return nonNullToCalendar(ctx, param).get(field);
  206. }
  207. private static Calendar nonNullToCalendar(EvalContext ctx, Value param) {
  208. Value origParam = param;
  209. param = nonNullToDateValue(ctx, param);
  210. if(param == null) {
  211. // not a date/time
  212. throw new EvalException("Invalid date/time expression '" +
  213. origParam + "'");
  214. }
  215. Calendar cal = getDateValueFormat(ctx, param).getCalendar();
  216. cal.setTime(param.getAsDateTime(ctx));
  217. return cal;
  218. }
  219. static Value nonNullToDateValue(EvalContext ctx, Value param) {
  220. Value.Type type = param.getType();
  221. if(type.isTemporal()) {
  222. return param;
  223. }
  224. if(type == Value.Type.STRING) {
  225. // see if we can coerce to date/time
  226. // FIXME use ExpressionatorTokenizer to detect explicit date/time format
  227. try {
  228. return numberToDateValue(ctx, param.getAsDouble());
  229. } catch(NumberFormatException ignored) {
  230. // not a number
  231. return null;
  232. }
  233. }
  234. // must be a number
  235. return numberToDateValue(ctx, param.getAsDouble());
  236. }
  237. private static Value numberToDateValue(EvalContext ctx, double dd) {
  238. if((dd < MIN_DATE) || (dd > MAX_DATE)) {
  239. // outside valid date range
  240. return null;
  241. }
  242. boolean hasDate = (dateOnly(dd) != 0.0d);
  243. boolean hasTime = (timeOnly(dd) != 0.0d);
  244. Value.Type type = (hasDate ? (hasTime ? Value.Type.DATE_TIME : Value.Type.DATE) :
  245. Value.Type.TIME);
  246. DateFormat fmt = BuiltinOperators.getDateFormatForType(ctx, type);
  247. return BuiltinOperators.toValue(type, dd, fmt);
  248. }
  249. private static DateFormat getDateValueFormat(EvalContext ctx, Value param) {
  250. return ((param instanceof BaseDateValue) ?
  251. ((BaseDateValue)param).getFormat() :
  252. BuiltinOperators.getDateFormatForType(ctx, param.getType()));
  253. }
  254. private static double dateOnly(double dd) {
  255. // the integral part of the date/time double is the date value. discard
  256. // the fractional portion
  257. return (long)dd;
  258. }
  259. private static double timeOnly(double dd) {
  260. // the fractional part of the date/time double is the time value. discard
  261. // the integral portion and convert to seconds
  262. return new BigDecimal(dd).remainder(BigDecimal.ONE).doubleValue();
  263. }
  264. private static double currentTimeDouble(DateFormat fmt) {
  265. return ColumnImpl.toDateDouble(System.currentTimeMillis(), fmt.getCalendar());
  266. }
  267. }