From a877fcc381b45f003efb4b2e9147e00ce26fcd01 Mon Sep 17 00:00:00 2001 From: Xiaobing Zhou Date: Thu, 29 Jan 2015 16:51:20 -0800 Subject: [PATCH] HIVE-9518: Implement MONTHS_BETWEEN aligned with Oracle one --- .../hadoop/hive/ql/exec/FunctionRegistry.java | 1 + .../hive/ql/udf/generic/GenericUDFDateDiff.java | 137 +---------- .../ql/udf/generic/GenericUDFDateDiffBase.java | 160 ++++++++++++ .../ql/udf/generic/GenericUDFMonthsBetween.java | 125 ++++++++++ ql/src/test/TestGenericUDFMonthsBetween.java | 272 +++++++++++++++++++++ 5 files changed, 565 insertions(+), 130 deletions(-) create mode 100644 ql/src/java/org/apache/hadoop/hive/ql/udf/generic/GenericUDFDateDiffBase.java create mode 100644 ql/src/java/org/apache/hadoop/hive/ql/udf/generic/GenericUDFMonthsBetween.java create mode 100644 ql/src/test/TestGenericUDFMonthsBetween.java diff --git ql/src/java/org/apache/hadoop/hive/ql/exec/FunctionRegistry.java ql/src/java/org/apache/hadoop/hive/ql/exec/FunctionRegistry.java index 2e722da..a32fb22 100644 --- ql/src/java/org/apache/hadoop/hive/ql/exec/FunctionRegistry.java +++ ql/src/java/org/apache/hadoop/hive/ql/exec/FunctionRegistry.java @@ -278,6 +278,7 @@ registerGenericUDF("date_add", GenericUDFDateAdd.class); registerGenericUDF("date_sub", GenericUDFDateSub.class); registerGenericUDF("datediff", GenericUDFDateDiff.class); + registerGenericUDF("months_between", GenericUDFMonthsBetween.class); registerGenericUDF("add_months", GenericUDFAddMonths.class); registerUDF("get_json_object", UDFJson.class, false); diff --git ql/src/java/org/apache/hadoop/hive/ql/udf/generic/GenericUDFDateDiff.java ql/src/java/org/apache/hadoop/hive/ql/udf/generic/GenericUDFDateDiff.java index 1ecd835..e4b4525 100644 --- ql/src/java/org/apache/hadoop/hive/ql/udf/generic/GenericUDFDateDiff.java +++ ql/src/java/org/apache/hadoop/hive/ql/udf/generic/GenericUDFDateDiff.java @@ -17,31 +17,15 @@ */ package org.apache.hadoop.hive.ql.udf.generic; -import java.sql.Timestamp; -import java.text.ParseException; -import java.text.SimpleDateFormat; import java.util.Date; -import java.util.TimeZone; import org.apache.hadoop.hive.ql.exec.Description; -import org.apache.hadoop.hive.ql.exec.UDFArgumentException; -import org.apache.hadoop.hive.ql.exec.UDFArgumentLengthException; -import org.apache.hadoop.hive.ql.exec.UDFArgumentTypeException; import org.apache.hadoop.hive.ql.exec.vector.VectorizedExpressions; import org.apache.hadoop.hive.ql.exec.vector.expressions.VectorUDFDateDiffColCol; import org.apache.hadoop.hive.ql.exec.vector.expressions.VectorUDFDateDiffColScalar; import org.apache.hadoop.hive.ql.exec.vector.expressions.VectorUDFDateDiffScalarCol; -import org.apache.hadoop.hive.ql.metadata.HiveException; -import org.apache.hadoop.hive.serde2.io.DateWritable; -import org.apache.hadoop.hive.serde2.io.TimestampWritable; -import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector; -import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspectorConverters; -import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspectorConverters.Converter; -import org.apache.hadoop.hive.serde2.objectinspector.PrimitiveObjectInspector; -import org.apache.hadoop.hive.serde2.objectinspector.PrimitiveObjectInspector.PrimitiveCategory; -import org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory; -import org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorConverter.TimestampConverter; import org.apache.hadoop.io.IntWritable; +import org.apache.hadoop.io.WritableComparable; /** * UDFDateDiff. @@ -60,131 +44,24 @@ + " > SELECT _FUNC_('2009-07-30', '2009-07-31') FROM src LIMIT 1;\n" + " 1") @VectorizedExpressions({VectorUDFDateDiffColScalar.class, VectorUDFDateDiffColCol.class, VectorUDFDateDiffScalarCol.class}) -public class GenericUDFDateDiff extends GenericUDF { - private transient SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd"); - private transient Converter inputConverter1; - private transient Converter inputConverter2; - private IntWritable output = new IntWritable(); - private transient PrimitiveCategory inputType1; - private transient PrimitiveCategory inputType2; - private IntWritable result = new IntWritable(); +public class GenericUDFDateDiff extends GenericUDFDateDiffBase { public GenericUDFDateDiff() { - formatter.setTimeZone(TimeZone.getTimeZone("UTC")); + udfName = "datediff"; } @Override - public ObjectInspector initialize(ObjectInspector[] arguments) throws UDFArgumentException { - if (arguments.length != 2) { - throw new UDFArgumentLengthException( - "datediff() requires 2 argument, got " + arguments.length); - } - inputConverter1 = checkArguments(arguments, 0); - inputConverter2 = checkArguments(arguments, 1); - inputType1 = ((PrimitiveObjectInspector) arguments[0]).getPrimitiveCategory(); - inputType2 = ((PrimitiveObjectInspector) arguments[1]).getPrimitiveCategory(); - ObjectInspector outputOI = PrimitiveObjectInspectorFactory.writableIntObjectInspector; - return outputOI; - } - - @Override - public IntWritable evaluate(DeferredObject[] arguments) throws HiveException { - output = evaluate(convertToDate(inputType1, inputConverter1, arguments[0]), - convertToDate(inputType2, inputConverter2, arguments[1])); - return output; - } - - @Override - public String getDisplayString(String[] children) { - StringBuilder sb = new StringBuilder(); - sb.append("datediff("); - if (children.length > 0) { - sb.append(children[0]); - for (int i = 1; i < children.length; i++) { - sb.append(", "); - sb.append(children[i]); - } - } - sb.append(")"); - return sb.toString(); - } + protected WritableComparable evaluate(Date date1, Date date2) { - private Date convertToDate(PrimitiveCategory inputType, Converter converter, DeferredObject argument) - throws HiveException { - assert(converter != null); - assert(argument != null); - if (argument.get() == null) { + if (date1 == null || date2 == null) { return null; } - Date date = new Date(); - switch (inputType) { - case STRING: - case VARCHAR: - case CHAR: - String dateString = converter.convert(argument.get()).toString(); - try { - date = formatter.parse(dateString); - } catch (ParseException e) { - return null; - } - break; - case TIMESTAMP: - Timestamp ts = ((TimestampWritable) converter.convert(argument.get())) - .getTimestamp(); - date.setTime(ts.getTime()); - break; - case DATE: - DateWritable dw = (DateWritable) converter.convert(argument.get()); - date = dw.get(); - break; - default: - throw new UDFArgumentException( - "TO_DATE() only takes STRING/TIMESTAMP/DATEWRITABLE types, got " + inputType); - } - return date; - } - private Converter checkArguments(ObjectInspector[] arguments, int i) throws UDFArgumentException { - if (arguments[i].getCategory() != ObjectInspector.Category.PRIMITIVE) { - throw new UDFArgumentTypeException(0, - "Only primitive type arguments are accepted but " - + arguments[i].getTypeName() + " is passed. as first arguments"); - } - PrimitiveCategory inputType = ((PrimitiveObjectInspector) arguments[i]).getPrimitiveCategory(); - Converter converter; - switch (inputType) { - case STRING: - case VARCHAR: - case CHAR: - converter = ObjectInspectorConverters.getConverter( - (PrimitiveObjectInspector) arguments[i], - PrimitiveObjectInspectorFactory.writableStringObjectInspector); - break; - case TIMESTAMP: - converter = new TimestampConverter((PrimitiveObjectInspector) arguments[i], - PrimitiveObjectInspectorFactory.writableTimestampObjectInspector); - break; - case DATE: - converter = ObjectInspectorConverters.getConverter((PrimitiveObjectInspector)arguments[i], - PrimitiveObjectInspectorFactory.writableDateObjectInspector); - break; - default: - throw new UDFArgumentException( - " DATEDIFF() only takes STRING/TIMESTAMP/DATEWRITABLE types as " + (i + 1) - + "-th argument, got " + inputType); - } - return converter; - } - - private IntWritable evaluate(Date date, Date date2) { - - if (date == null || date2 == null) { - return null; - } // NOTE: This implementation avoids the extra-second problem // by comparing with UTC epoch and integer division. // 86400 is the number of seconds in a day - long diffInMilliSeconds = date.getTime() - date2.getTime(); + long diffInMilliSeconds = date1.getTime() - date2.getTime(); + IntWritable result = new IntWritable(); result.set((int) (diffInMilliSeconds / (86400 * 1000))); return result; } diff --git ql/src/java/org/apache/hadoop/hive/ql/udf/generic/GenericUDFDateDiffBase.java ql/src/java/org/apache/hadoop/hive/ql/udf/generic/GenericUDFDateDiffBase.java new file mode 100644 index 0000000..43b4df5 --- /dev/null +++ ql/src/java/org/apache/hadoop/hive/ql/udf/generic/GenericUDFDateDiffBase.java @@ -0,0 +1,160 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.hadoop.hive.ql.udf.generic; + +import java.sql.Timestamp; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.TimeZone; + +import org.apache.hadoop.hive.ql.exec.UDFArgumentException; +import org.apache.hadoop.hive.ql.exec.UDFArgumentLengthException; +import org.apache.hadoop.hive.ql.exec.UDFArgumentTypeException; +import org.apache.hadoop.hive.ql.metadata.HiveException; +import org.apache.hadoop.hive.serde2.io.DateWritable; +import org.apache.hadoop.hive.serde2.io.TimestampWritable; +import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector; +import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspectorConverters; +import org.apache.hadoop.hive.serde2.objectinspector.PrimitiveObjectInspector; +import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspectorConverters.Converter; +import org.apache.hadoop.hive.serde2.objectinspector.PrimitiveObjectInspector.PrimitiveCategory; +import org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory; +import org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorConverter.TimestampConverter; +import org.apache.hadoop.io.WritableComparable; + +public abstract class GenericUDFDateDiffBase extends GenericUDF { + + private transient SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd"); + private transient Converter inputConverter1; + private transient Converter inputConverter2; + private transient PrimitiveCategory inputType1; + private transient PrimitiveCategory inputType2; + protected String udfName = ""; + + public GenericUDFDateDiffBase() { + formatter.setTimeZone(TimeZone.getTimeZone("UTC")); + } + + @Override + public ObjectInspector initialize(ObjectInspector[] arguments) throws UDFArgumentException { + if (arguments.length != 2) { + throw new UDFArgumentLengthException( + udfName + "() requires 2 argument, got " + arguments.length); + } + inputConverter1 = checkArguments(arguments, 0); + inputConverter2 = checkArguments(arguments, 1); + inputType1 = ((PrimitiveObjectInspector) arguments[0]).getPrimitiveCategory(); + inputType2 = ((PrimitiveObjectInspector) arguments[1]).getPrimitiveCategory(); + ObjectInspector outputOI = PrimitiveObjectInspectorFactory.writableIntObjectInspector; + return outputOI; + } + + @Override + public WritableComparable evaluate(DeferredObject[] arguments) throws HiveException { + WritableComparable output = evaluate(convertToDate(inputType1, inputConverter1, arguments[0]), + convertToDate(inputType2, inputConverter2, arguments[1])); + return output; + } + + protected abstract WritableComparable evaluate(Date date1, Date date2); + + @Override + public String getDisplayString(String[] children) { + StringBuilder sb = new StringBuilder(); + sb.append(udfName + "("); + if (children.length > 0) { + sb.append(children[0]); + for (int i = 1; i < children.length; i++) { + sb.append(", "); + sb.append(children[i]); + } + } + sb.append(")"); + return sb.toString(); + } + + private Date convertToDate(PrimitiveCategory inputType, Converter converter, DeferredObject argument) + throws HiveException { + assert(converter != null); + assert(argument != null); + if (argument.get() == null) { + return null; + } + Date date = new Date(); + switch (inputType) { + case STRING: + case VARCHAR: + case CHAR: + String dateString = converter.convert(argument.get()).toString(); + try { + date = formatter.parse(dateString); + } catch (ParseException e) { + return null; + } + break; + case TIMESTAMP: + Timestamp ts = ((TimestampWritable) converter.convert(argument.get())) + .getTimestamp(); + date.setTime(ts.getTime()); + break; + case DATE: + DateWritable dw = (DateWritable) converter.convert(argument.get()); + date = dw.get(); + break; + default: + throw new UDFArgumentException( + "TO_DATE() only takes STRING/TIMESTAMP/DATEWRITABLE types, got " + inputType); + } + return date; + } + + private Converter checkArguments(ObjectInspector[] arguments, int i) throws UDFArgumentException { + if (arguments[i].getCategory() != ObjectInspector.Category.PRIMITIVE) { + throw new UDFArgumentTypeException(0, + "Only primitive type arguments are accepted but " + + arguments[i].getTypeName() + " is passed. as first arguments"); + } + PrimitiveCategory inputType = ((PrimitiveObjectInspector) arguments[i]).getPrimitiveCategory(); + Converter converter; + switch (inputType) { + case STRING: + case VARCHAR: + case CHAR: + converter = ObjectInspectorConverters.getConverter( + (PrimitiveObjectInspector) arguments[i], + PrimitiveObjectInspectorFactory.writableStringObjectInspector); + break; + case TIMESTAMP: + converter = new TimestampConverter((PrimitiveObjectInspector) arguments[i], + PrimitiveObjectInspectorFactory.writableTimestampObjectInspector); + break; + case DATE: + converter = ObjectInspectorConverters.getConverter((PrimitiveObjectInspector)arguments[i], + PrimitiveObjectInspectorFactory.writableDateObjectInspector); + break; + default: + throw new UDFArgumentException( + " " + udfName.toUpperCase() + "() only takes STRING/TIMESTAMP/DATEWRITABLE types as " + (i + 1) + + "-th argument, got " + inputType); + } + return converter; + } + +} diff --git ql/src/java/org/apache/hadoop/hive/ql/udf/generic/GenericUDFMonthsBetween.java ql/src/java/org/apache/hadoop/hive/ql/udf/generic/GenericUDFMonthsBetween.java new file mode 100644 index 0000000..8e7ccbf --- /dev/null +++ ql/src/java/org/apache/hadoop/hive/ql/udf/generic/GenericUDFMonthsBetween.java @@ -0,0 +1,125 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.hadoop.hive.ql.udf.generic; + +import java.math.BigDecimal; +import java.util.Calendar; +import java.util.Date; +import java.util.TimeZone; + +import org.apache.hadoop.hive.ql.exec.Description; +import org.apache.hadoop.io.DoubleWritable; +import org.apache.hadoop.io.WritableComparable; + +/** + * UDFMonthsBetween. + * + * Calculate number of months between dates date1 and date2. The time part of + * the string will be ignored. If dateString1 is earlier than dateString2, then + * the result can be negative. + * + */ +@Description(name = "months_between", +value = "_FUNC_(date1, date2) - returns number of months between dates date1 and date2", +extended = "If date1 is later than date2, then the result is positive. " + + "If date1 is earlier than date2, then the result is negative." + + "If date1 and date2 are either the same days of the month or both last days of months, " + + "then the result is always an integer." + + "otherwise, it will calculate the fractional portion of the result based on a 31-day " + + "month and considers the difference in time components date1 and date2.\n" + + "date1 and date2 are strings in the format " + + "'yyyy-MM-dd HH:mm:ss' or 'yyyy-MM-dd'. The time parts are ignored.\n" + + "Example:\n " + + " > SELECT _FUNC_('02-02-1995', '01-01-1995') FROM src LIMIT 1;\n" + " 1.03225806") +public class GenericUDFMonthsBetween extends GenericUDFDateDiffBase { + + public GenericUDFMonthsBetween() { + udfName = "months_between"; + } + + @Override + protected WritableComparable evaluate(Date date1, Date date2) { + + if (date1 == null || date2 == null) { + return null; + } + + Calendar cal1 = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + cal1.setTime(date1); + + Calendar cal2 = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + cal2.setTime(date2); + + double monthsBetween = 0; + // difference in month for years + monthsBetween = (cal1.get(Calendar.YEAR) - cal2.get(Calendar.YEAR)) * 12; + // difference in month for months + monthsBetween += cal1.get(Calendar.MONTH) - cal2.get(Calendar.MONTH); + + // difference in month for days + //if both date1 and date2 are the same days or the last days of the month + if (cal1.get(Calendar.DAY_OF_MONTH) == cal2.get(Calendar.DAY_OF_MONTH) || + (cal1.get(Calendar.DAY_OF_MONTH) == cal1.getActualMaximum(Calendar.DAY_OF_MONTH) + && cal2.get(Calendar.DAY_OF_MONTH) == cal2.getActualMaximum(Calendar.DAY_OF_MONTH))) { + monthsBetween += 0; + } else { + int dayDiff = cal1.get(Calendar.DAY_OF_MONTH) - cal2.get(Calendar.DAY_OF_MONTH); + monthsBetween += (dayDiff / 31d); + } + + Double result = new BigDecimal(monthsBetween).setScale(8, BigDecimal.ROUND_HALF_UP) + .doubleValue(); + DoubleWritable output = new DoubleWritable(result); + return output; + } + + protected WritableComparable evaluate2(Date date1, Date date2) { + + if (date1 == null || date2 == null) { + return null; + } + + Calendar cal1 = Calendar.getInstance(); + cal1.setTime(date1); + + Calendar cal2 = Calendar.getInstance(); + cal2.setTime(date2); + + double monthsBetween = 0; + // difference in month for years + monthsBetween = (cal1.get(Calendar.YEAR) - cal2.get(Calendar.YEAR)) * 12; + // difference in month for months + monthsBetween += cal1.get(Calendar.MONTH) - cal2.get(Calendar.MONTH); + + // difference in month for days + if (cal1.get(Calendar.DAY_OF_MONTH) == cal1.getActualMaximum(Calendar.DAY_OF_MONTH) + && cal2.get(Calendar.DAY_OF_MONTH) == cal2.getActualMaximum(Calendar.DAY_OF_MONTH)) { + int dayDiff = cal1.get(Calendar.DAY_OF_MONTH) - cal2.get(Calendar.DAY_OF_MONTH); + monthsBetween += (dayDiff / 31d); + } + + // if both date1 and date2 are either the same days of the month or the last + // days of months, no fraction. + Double result = new BigDecimal(monthsBetween).setScale(8, BigDecimal.ROUND_HALF_UP) + .doubleValue(); + DoubleWritable output = new DoubleWritable(result); + return output; + } + +} diff --git ql/src/test/TestGenericUDFMonthsBetween.java ql/src/test/TestGenericUDFMonthsBetween.java new file mode 100644 index 0000000..9b8bb66 --- /dev/null +++ ql/src/test/TestGenericUDFMonthsBetween.java @@ -0,0 +1,272 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.sql.Date; +import java.sql.Timestamp; + +import org.apache.hadoop.hive.ql.metadata.HiveException; +import org.apache.hadoop.hive.ql.udf.generic.GenericUDF.DeferredJavaObject; +import org.apache.hadoop.hive.ql.udf.generic.GenericUDF.DeferredObject; +import org.apache.hadoop.hive.ql.udf.generic.GenericUDFMonthsBetween; +import org.apache.hadoop.hive.serde2.io.DateWritable; +import org.apache.hadoop.hive.serde2.io.TimestampWritable; +import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector; +import org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory; +import org.apache.hadoop.io.DoubleWritable; +import org.apache.hadoop.io.Text; + +import junit.framework.TestCase; + +public class TestGenericUDFMonthsBetween extends TestCase { + + public void testStringToDate() throws HiveException { + GenericUDFMonthsBetween udf = new GenericUDFMonthsBetween(); + ObjectInspector valueOI1 = PrimitiveObjectInspectorFactory.javaStringObjectInspector; + ObjectInspector valueOI2 = PrimitiveObjectInspectorFactory.javaStringObjectInspector; + ObjectInspector[] arguments = {valueOI1, valueOI2}; + + //test month diff with fraction considering time components + udf.initialize(arguments); + DeferredObject valueObj1 = new DeferredJavaObject(new Text("1995-02-02")); + DeferredObject valueObj2 = new DeferredJavaObject(new Text("1995-01-01")); + DeferredObject[] args = {valueObj1, valueObj2}; + DoubleWritable output = (DoubleWritable) udf.evaluate(args); + assertEquals("months_between() test for STRING failed ", "1.03225806", output.toString()); + + valueObj1 = new DeferredJavaObject(new Text("2003-07-17")); + valueObj2 = new DeferredJavaObject(new Text("2005-07-06")); + args = new DeferredObject[] {valueObj1, valueObj2}; + output = (DoubleWritable) udf.evaluate(args); + assertEquals("months_between() test for STRING failed ", "-23.64516129", output.toString()); + + //test the last day of month + valueObj1 = new DeferredJavaObject(new Text("2001-06-30")); + valueObj2 = new DeferredJavaObject(new Text("2000-05-31")); + args = new DeferredObject[] {valueObj1, valueObj2}; + output = (DoubleWritable) udf.evaluate(args); + assertEquals("months_between() test for STRING failed ", "13.0", output.toString()); + + //test the same day of month + valueObj1 = new DeferredJavaObject(new Text("2000-06-01")); + valueObj2 = new DeferredJavaObject(new Text("2004-07-01")); + args = new DeferredObject[] {valueObj1, valueObj2}; + output = (DoubleWritable) udf.evaluate(args); + assertEquals("months_between() test for STRING failed ", "-49.0", output.toString()); + + //test february of non-leap year, 2/28 + valueObj1 = new DeferredJavaObject(new Text("2002-02-28")); + valueObj2 = new DeferredJavaObject(new Text("2002-03-01")); + args = new DeferredObject[] {valueObj1, valueObj2}; + output = (DoubleWritable) udf.evaluate(args); + assertEquals("months_between() test for STRING failed ", "-0.12903226", output.toString()); + + //test february of non-leap year, 2/31 is viewd as 3/3 due to 3 days diff from 2/31 to 2/28 + valueObj1 = new DeferredJavaObject(new Text("2002-02-31")); + valueObj2 = new DeferredJavaObject(new Text("2002-03-01")); + args = new DeferredObject[] {valueObj1, valueObj2}; + output = (DoubleWritable) udf.evaluate(args); + assertEquals("months_between() test for STRING failed ", "0.06451613", output.toString()); + + //test Feb of leap year, 2/29 + valueObj1 = new DeferredJavaObject(new Text("2012-02-29")); + valueObj2 = new DeferredJavaObject(new Text("2012-03-01")); + args = new DeferredObject[] {valueObj1, valueObj2}; + output = (DoubleWritable) udf.evaluate(args); + assertEquals("months_between() test for STRING failed ", "-0.09677419", output.toString()); + + //test february of leap year, 2/31 is viewed as 3/2 due to 2 days diff from 2/31 to 2/29 + valueObj1 = new DeferredJavaObject(new Text("2012-02-31")); + valueObj2 = new DeferredJavaObject(new Text("2012-03-01")); + args = new DeferredObject[] {valueObj1, valueObj2}; + output = (DoubleWritable) udf.evaluate(args); + assertEquals("months_between() test for STRING failed ", "0.03225806", output.toString()); + + // Test with null args + args = new DeferredObject[] { new DeferredJavaObject(null), valueObj2 }; + assertNull("months_between() 1st arg null", udf.evaluate(args)); + + args = new DeferredObject[] { valueObj1, new DeferredJavaObject(null) }; + assertNull("months_between() 2nd arg null", udf.evaluate(args)); + + args = new DeferredObject[] { new DeferredJavaObject(null), new DeferredJavaObject(null) }; + assertNull("months_between() both args null", udf.evaluate(args)); + } + + public void testTimestampToDate() throws HiveException { + GenericUDFMonthsBetween udf = new GenericUDFMonthsBetween(); + ObjectInspector valueOI1 = PrimitiveObjectInspectorFactory.writableTimestampObjectInspector; + ObjectInspector valueOI2 = PrimitiveObjectInspectorFactory.writableTimestampObjectInspector; + ObjectInspector[] arguments = { valueOI1, valueOI2 }; + + //test month diff with fraction considering time components + udf.initialize(arguments); + DeferredObject valueObj1 = new DeferredJavaObject(new TimestampWritable(new Timestamp(95, 1, + 2, 0, 0, 0, 0))); + DeferredObject valueObj2 = new DeferredJavaObject(new TimestampWritable(new Timestamp(95, 0, + 1, 0, 0, 0, 0))); + DeferredObject[] args = { valueObj1, valueObj2 }; + DoubleWritable output = (DoubleWritable) udf.evaluate(args); + assertEquals("months_between() test for TIMESTAMP failed ", "1.03225806", output.toString()); + + valueObj1 = new DeferredJavaObject(new TimestampWritable(new Timestamp(103, 6, + 17, 0, 0, 0, 0))); + valueObj2 = new DeferredJavaObject(new TimestampWritable(new Timestamp(105, 6, + 6, 0, 0, 0, 0))); + args = new DeferredObject[]{ valueObj1, valueObj2 }; + output = (DoubleWritable) udf.evaluate(args); + assertEquals("months_between() test for TIMESTAMP failed ", "-23.64516129", output.toString()); + + //test the last day of month + valueObj1 = new DeferredJavaObject(new TimestampWritable(new Timestamp(101, 5, + 30, 0, 0, 0, 0))); + valueObj2 = new DeferredJavaObject(new TimestampWritable(new Timestamp(100, 4, + 31, 0, 0, 0, 0))); + args = new DeferredObject[]{ valueObj1, valueObj2 }; + output = (DoubleWritable) udf.evaluate(args); + assertEquals("months_between() test for TIMESTAMP failed ", "13.0", output.toString()); + + //test the same day of month + valueObj1 = new DeferredJavaObject(new TimestampWritable(new Timestamp(100, 5, + 1, 0, 0, 0, 0))); + valueObj2 = new DeferredJavaObject(new TimestampWritable(new Timestamp(104, 6, + 1, 0, 0, 0, 0))); + args = new DeferredObject[]{ valueObj1, valueObj2 }; + output = (DoubleWritable) udf.evaluate(args); + assertEquals("months_between() test for TIMESTAMP failed ", "-49.0", output.toString()); + + //test february of non-leap year, 2/28 + valueObj1 = new DeferredJavaObject(new TimestampWritable(new Timestamp(102, 1, + 28, 0, 0, 0, 0))); + valueObj2 = new DeferredJavaObject(new TimestampWritable(new Timestamp(102, 2, + 1, 0, 0, 0, 0))); + args = new DeferredObject[]{ valueObj1, valueObj2 }; + output = (DoubleWritable) udf.evaluate(args); + assertEquals("months_between() test for TIMESTAMP failed ", "-0.12903226", output.toString()); + + //test february of non-leap year, 2/31 is viewd as 3/3 due to 3 days diff from 2/31 to 2/28 + valueObj1 = new DeferredJavaObject(new TimestampWritable(new Timestamp(102, 1, + 31, 0, 0, 0, 0))); + valueObj2 = new DeferredJavaObject(new TimestampWritable(new Timestamp(102, 2, + 1, 0, 0, 0, 0))); + args = new DeferredObject[]{ valueObj1, valueObj2 }; + output = (DoubleWritable) udf.evaluate(args); + assertEquals("months_between() test for TIMESTAMP failed ", "0.06451613", output.toString()); + + //test Feb of leap year, 2/29 + valueObj1 = new DeferredJavaObject(new TimestampWritable(new Timestamp(112, 1, + 29, 0, 0, 0, 0))); + valueObj2 = new DeferredJavaObject(new TimestampWritable(new Timestamp(112, 2, + 1, 0, 0, 0, 0))); + args = new DeferredObject[]{ valueObj1, valueObj2 }; + output = (DoubleWritable) udf.evaluate(args); + assertEquals("months_between() test for TIMESTAMP failed ", "-0.09677419", output.toString()); + + //test february of leap year, 2/31 is viewed as 3/2 due to 2 days diff from 2/31 to 2/29 + valueObj1 = new DeferredJavaObject(new TimestampWritable(new Timestamp(112, 1, + 31, 0, 0, 0, 0))); + valueObj2 = new DeferredJavaObject(new TimestampWritable(new Timestamp(112, 2, + 1, 0, 0, 0, 0))); + args = new DeferredObject[]{ valueObj1, valueObj2 }; + output = (DoubleWritable) udf.evaluate(args); + assertEquals("months_between() test for TIMESTAMP failed ", "0.03225806", output.toString()); + + // Test with null args + args = new DeferredObject[] { new DeferredJavaObject(null), valueObj2 }; + assertNull("months_between() 1st arg null", udf.evaluate(args)); + + args = new DeferredObject[] { valueObj1, new DeferredJavaObject(null) }; + assertNull("months_between() 2nd arg null", udf.evaluate(args)); + + args = new DeferredObject[] { new DeferredJavaObject(null), new DeferredJavaObject(null) }; + assertNull("months_between() both args null", udf.evaluate(args)); + } + + public void testDateWritablepToDate() throws HiveException { + GenericUDFMonthsBetween udf = new GenericUDFMonthsBetween(); + ObjectInspector valueOI1 = PrimitiveObjectInspectorFactory.writableDateObjectInspector; + ObjectInspector valueOI2 = PrimitiveObjectInspectorFactory.writableDateObjectInspector; + ObjectInspector[] arguments = {valueOI1, valueOI2}; + + //test month diff with fraction considering time components + udf.initialize(arguments); + DeferredObject valueObj1 = new DeferredJavaObject(new DateWritable(new Date(95, 1, 2))); + DeferredObject valueObj2 = new DeferredJavaObject(new DateWritable(new Date(95, 0, 1))); + DeferredObject[] args = {valueObj1, valueObj2}; + DoubleWritable output = (DoubleWritable) udf.evaluate(args); + assertEquals("months_between() test for DATEWRITABLE failed ", "1.03225806", output.toString()); + + udf.initialize(arguments); + valueObj1 = new DeferredJavaObject(new DateWritable(new Date(103, 6, 17))); + valueObj2 = new DeferredJavaObject(new DateWritable(new Date(105, 6, 6))); + args = new DeferredObject[] {valueObj1, valueObj2}; + output = (DoubleWritable) udf.evaluate(args); + assertEquals("months_between() test for DATEWRITABLE failed ", "-23.64516129", output.toString()); + + //test the last day of month + valueObj1 = new DeferredJavaObject(new DateWritable(new Date(101, 5, 30))); + valueObj2 = new DeferredJavaObject(new DateWritable(new Date(100, 4, 31))); + args = new DeferredObject[] {valueObj1, valueObj2}; + output = (DoubleWritable) udf.evaluate(args); + assertEquals("months_between() test for DATEWRITABLE failed ", "13.0", output.toString()); + + //test the same day of month + valueObj1 = new DeferredJavaObject(new DateWritable(new Date(100, 5, 1))); + valueObj2 = new DeferredJavaObject(new DateWritable(new Date(104, 6, 1))); + args = new DeferredObject[] {valueObj1, valueObj2}; + output = (DoubleWritable) udf.evaluate(args); + assertEquals("months_between() test for DATEWRITABLE failed ", "-49.0", output.toString()); + + //test february of non-leap year, 2/28 + valueObj1 = new DeferredJavaObject(new DateWritable(new Date(102, 1, 28))); + valueObj2 = new DeferredJavaObject(new DateWritable(new Date(102, 2, 1))); + args = new DeferredObject[] {valueObj1, valueObj2}; + output = (DoubleWritable) udf.evaluate(args); + assertEquals("months_between() test for DATEWRITABLE failed ", "-0.12903226", output.toString()); + + //test february of non-leap year, 2/31 is viewd as 3/3 due to 3 days diff from 2/31 to 2/28 + valueObj1 = new DeferredJavaObject(new DateWritable(new Date(102, 1, 31))); + valueObj2 = new DeferredJavaObject(new DateWritable(new Date(102, 2, 1))); + args = new DeferredObject[] {valueObj1, valueObj2}; + output = (DoubleWritable) udf.evaluate(args); + assertEquals("months_between() test for DATEWRITABLE failed ", "0.06451613", output.toString()); + + //test Feb of leap year, 2/29 + valueObj1 = new DeferredJavaObject(new DateWritable(new Date(112, 1, 29))); + valueObj2 = new DeferredJavaObject(new DateWritable(new Date(112, 2, 1))); + args = new DeferredObject[] {valueObj1, valueObj2}; + output = (DoubleWritable) udf.evaluate(args); + assertEquals("months_between() test for DATEWRITABLE failed ", "-0.09677419", output.toString()); + + //test february of leap year, 2/31 is viewed as 3/2 due to 2 days diff from 2/31 to 2/29 + valueObj1 = new DeferredJavaObject(new DateWritable(new Date(112, 1, 31))); + valueObj2 = new DeferredJavaObject(new DateWritable(new Date(112, 2, 1))); + args = new DeferredObject[] {valueObj1, valueObj2}; + output = (DoubleWritable) udf.evaluate(args); + assertEquals("months_between() test for DATEWRITABLE failed ", "0.03225806", output.toString()); + + // Test with null args + args = new DeferredObject[] { new DeferredJavaObject(null), valueObj2 }; + assertNull("date_add() 1st arg null", udf.evaluate(args)); + + args = new DeferredObject[] { valueObj1, new DeferredJavaObject(null) }; + assertNull("date_add() 2nd arg null", udf.evaluate(args)); + + args = new DeferredObject[] { new DeferredJavaObject(null), new DeferredJavaObject(null) }; + assertNull("date_add() both args null", udf.evaluate(args)); + } +} -- 1.9.3 (Apple Git-50)