Skip to content

Add DateTimeMetric, Analyzer and Example#568

Open
zeotuan wants to merge 7 commits into
awslabs:masterfrom
zeotuan:DateTimeMetric
Open

Add DateTimeMetric, Analyzer and Example#568
zeotuan wants to merge 7 commits into
awslabs:masterfrom
zeotuan:DateTimeMetric

Conversation

@zeotuan

@zeotuan zeotuan commented May 16, 2024

Copy link
Copy Markdown
Contributor

Description of changes:
Add DateTimeMetric support. This chunk part of #299 to only DateTimeMetric support:

  • Remove usage of deprecated UDAF from original PR
  • use java.time.Instant isntead of java.sql.timestamp
  • Fix bug + Adding Test

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

@zeotuan zeotuan marked this pull request as ready for review September 15, 2024 06:22
@zeotuan

zeotuan commented Sep 16, 2024

Copy link
Copy Markdown
Contributor Author

Hi @rdsharma26, @mentekid please help review this one

@eycho-am eycho-am left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general the changes look good and thank you for adding the example.

Could you add some more testing please?
Would like to see more tests for the analyzer behavior (in https://github.com/awslabs/deequ/tree/master/src/test/scala/com/amazon/deequ/analyzers) and tests to see how it works within the VerificationSuite (in https://github.com/awslabs/deequ/blob/master/src/test/scala/com/amazon/deequ/VerificationSuiteTest.scala)

Comment thread pom.xml Outdated
)
}

"datetimeDistribution analyzer with VerificationSuite" in withSparkSessionJava8APIEnabled { sparkSession =>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@eycho-am Hi just to confirm this is the kind of test you was looking for right?
Currently It is only implemented as an Analyzer to run with AnalysisRunner or as RequiredAnalyzer

For more advance use case with VerificationSuite, I can open a PR to implement some check based on it

@zeotuan zeotuan requested a review from eycho-am October 23, 2024 21:27

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


Generated by AI (model: us.anthropic.claude-opus-4-6-v1, prompt: 926c07a3) — may not be fully accurate. Reply if this doesn't help.


private[this] val dateTypes = Set(TimestampType, DateType)

private[this] val caseSensitive = {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Set(StructType, MapType, ArrayType) compares companion objects, not types. But dateTypes uses the same pattern with Set(TimestampType, DateType). This set is never actually used for matching (the isDateType method uses pattern matching instead), so it's dead code that only appears in the error message. However, the error message dateTypes.mkString(", ") will print the companion object toString representations, which may not be what you want. Consider using Set("TimestampType", "DateType") or just inline the string.

@@ -405,6 +435,20 @@ object Preconditions {
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Misleading scaladoc: says /** Specified column has string type */ but the method checks for date/timestamp type.

override def zero: Map[Long, Long] = Map.empty[Long, Long]

override def reduce(agg: Map[Long, Long], input: Instant): Map[Long, Long] = {
val dateTime = input.toEpochMilli

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Null safety: if the column contains null values, input will be null and input.toEpochMilli will throw a NullPointerException. You should add a null check and skip null inputs (return agg unchanged).


// Define encoder for output
def outputEncoder: Encoder[Map[Long, Long]] = ExpressionEncoder()
} No newline at end of file

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing newline at end of file.

case (x, y) => (Instant.ofEpochMilli(x), Instant.ofEpochMilli(x + frequency - 1L)) -> y
})
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The computeStateFromResult computes the end of the interval as x + frequency - 1L (in milliseconds). This means the last millisecond of each bucket overlaps conceptually with the next bucket's start. For example, with DAILY interval, the end is ...T23:59:59.999Z but the next bucket starts at ...T00:00:00.000Z of the next day. This works but is fragile — consider documenting that the range is inclusive on both ends, or use x + frequency as an exclusive upper bound.

instance: String,
value: Try[Instant]
) extends Metric[Instant] {
override def flatten(): Seq[DoubleMetric] = value match {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DateTimeMetric.flatten() converts to epoch millis as a DoubleMetric. Double has only 53 bits of mantissa, and epoch millis can exceed 2^53 for dates far in the future, causing precision loss. For current dates this is fine, but it's worth documenting this limitation or considering epoch seconds instead.

* @return
*/
def hasPastDates(
column: String,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hasPastDates uses now() which is evaluated at Spark query planning time. If the check is reused or the DataFrame is lazily evaluated, the now() value may differ from expectations. Also, now() is not a standard Spark SQL function — you likely mean current_timestamp().

* @return
*/
def hasFutureDates(
column: String,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue as hasPastDates: now() is not a standard Spark SQL function. Use current_timestamp() instead.

*/

package org.apache.spark.sql

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Package is org.apache.spark.sql but the file lives under com/amazon/deequ/analyzers/catalyst/. While this is intentional to access Spark internals (like other files in this package), the class DateTimeAggregation doesn't seem to need internal Spark access — it only uses public Aggregator API. Consider moving it to the deequ package.

.master("local")
.appName("test")
.config("spark.ui.enabled", "false")
.config("spark.sql.datetime.java8API.enabled", "true")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting spark.sql.datetime.java8API.enabled=true globally in ExampleUtils affects all examples, not just the DateTime ones. This could change behavior of existing examples that rely on java.sql.Timestamp/java.sql.Date types.

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

This PR has been inactive for 60 days. It will be closed in 14 days if there is no further activity. If you are still working on this, please push an update or comment to keep it open.

@github-actions github-actions Bot added the stale label Jul 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants