Skip to content

Commit

Permalink
feat(query): add custom date pattern support for time range queries (#…
Browse files Browse the repository at this point in the history
…1151)

* feat(query): add custom date pattern support for time range queries

- Add datePattern() function to Condition class
- Update timeRange() function in AbstractConditionConverter to support custom date patterns
- Add DATE_PATTERN_OPTION_KEY constant to Condition class

* refactor(wow-query): support custom date pattern in condition

- Add datePattern function to Condition class
- Implement datePattern handling in various time-related functions
- Add toDate function to handle different date patterns
- Update timeRange function to support custom date patterns

* feat(query): add date pattern support for temporal conditions

- Add datePatternOptions utility function to handle date pattern options
- Update today, beforeToday, tomorrow, thisWeek, nextWeek, lastWeek, thisMonth, lastMonth, and recentDays functions to support date patterns
- Modify ConditionDsl to include datePattern parameter in relevant temporal conditions
- Remove unnecessary trailing minus sign in AbstractConditionConverter

* fix(wow-query): update today condition to support custom date time formatter

- Modify ConditionDslTest to use custom date time formatter in today condition
- Update MongoConverterTest to handle new date time formatter parameter
- Add test cases for beforeToday and tomorrow with custom date time formatter
  • Loading branch information
Ahoo-Wang authored Feb 8, 2025
1 parent 3be21dd commit 0e5d570
Show file tree
Hide file tree
Showing 5 changed files with 173 additions and 66 deletions.
68 changes: 56 additions & 12 deletions wow-api/src/main/kotlin/me/ahoo/wow/api/query/Condition.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
package me.ahoo.wow.api.query

import java.time.ZoneId
import java.time.format.DateTimeFormatter

data class Condition(
val field: String = "",
Expand Down Expand Up @@ -54,15 +55,34 @@ data class Condition(
}
}

fun datePattern(): DateTimeFormatter? {
val datePatternOptionValue = options[DATE_PATTERN_OPTION_KEY] ?: return null
return when (datePatternOptionValue) {
is String -> DateTimeFormatter.ofPattern(datePatternOptionValue)
is DateTimeFormatter -> datePatternOptionValue
else -> null
}
}

companion object {
const val EMPTY_VALUE = ""
val ALL = Condition(field = EMPTY_VALUE, operator = Operator.ALL, value = EMPTY_VALUE)

const val IGNORE_CASE_OPTION_KEY = "ignoreCase"
const val ZONE_ID_OPTION_KEY = "zoneId"
const val DATE_PATTERN_OPTION_KEY = "datePattern"
val IGNORE_CASE_OPTIONS = mapOf(IGNORE_CASE_OPTION_KEY to true)
val IGNORE_CASE_FALSE_OPTIONS = mapOf(IGNORE_CASE_OPTION_KEY to false)
fun ignoreCaseOptions(value: Boolean) = if (value) IGNORE_CASE_OPTIONS else IGNORE_CASE_FALSE_OPTIONS
fun datePatternOptions(value: Any?): Map<String, Any> {
if (value == null) {
return emptyMap()
}
require(value is String || value is DateTimeFormatter) {
"datePatternOptions value must be String or DateTimeFormatter"
}
return mapOf(DATE_PATTERN_OPTION_KEY to value)
}

fun and(vararg conditions: Condition) = Condition(EMPTY_VALUE, Operator.AND, children = conditions.toList())
fun and(conditions: List<Condition>) = Condition(EMPTY_VALUE, Operator.AND, children = conditions)
Expand Down Expand Up @@ -105,18 +125,42 @@ data class Condition(
fun aggregateIds(vararg value: String) = aggregateIds(value.asList())
fun tenantId(value: String) = Condition(field = EMPTY_VALUE, operator = Operator.TENANT_ID, value = value)
fun deleted(value: Boolean) = Condition(field = EMPTY_VALUE, operator = Operator.DELETED, value = value)
fun today(field: String) = Condition(field = field, operator = Operator.TODAY)
fun beforeToday(field: String, time: Any) =
Condition(field = field, operator = Operator.BEFORE_TODAY, value = time)

fun tomorrow(field: String) = Condition(field = field, operator = Operator.TOMORROW)
fun thisWeek(field: String) = Condition(field = field, operator = Operator.THIS_WEEK)
fun nextWeek(field: String) = Condition(field = field, operator = Operator.NEXT_WEEK)
fun lastWeek(field: String) = Condition(field = field, operator = Operator.LAST_WEEK)
fun thisMonth(field: String) = Condition(field = field, operator = Operator.THIS_MONTH)
fun lastMonth(field: String) = Condition(field = field, operator = Operator.LAST_MONTH)
fun recentDays(field: String, days: Int) =
Condition(field = field, operator = Operator.RECENT_DAYS, value = days)
fun today(field: String, datePattern: Any? = null) =
Condition(field = field, operator = Operator.TODAY, options = datePatternOptions(datePattern))

fun beforeToday(field: String, time: Any, datePattern: Any? = null) =
Condition(
field = field,
operator = Operator.BEFORE_TODAY,
value = time,
options = datePatternOptions(datePattern)
)

fun tomorrow(field: String, datePattern: Any? = null) =
Condition(field = field, operator = Operator.TOMORROW, options = datePatternOptions(datePattern))

fun thisWeek(field: String, datePattern: Any? = null) =
Condition(field = field, operator = Operator.THIS_WEEK, options = datePatternOptions(datePattern))

fun nextWeek(field: String, datePattern: Any? = null) =
Condition(field = field, operator = Operator.NEXT_WEEK, options = datePatternOptions(datePattern))

fun lastWeek(field: String, datePattern: Any? = null) =
Condition(field = field, operator = Operator.LAST_WEEK, options = datePatternOptions(datePattern))

fun thisMonth(field: String, datePattern: Any? = null) =
Condition(field = field, operator = Operator.THIS_MONTH, options = datePatternOptions(datePattern))

fun lastMonth(field: String, datePattern: Any? = null) =
Condition(field = field, operator = Operator.LAST_MONTH, options = datePatternOptions(datePattern))

fun recentDays(field: String, days: Int, datePattern: Any? = null) =
Condition(
field = field,
operator = Operator.RECENT_DAYS,
value = days,
options = datePatternOptions(datePattern)
)

fun raw(value: Any) = Condition(field = EMPTY_VALUE, operator = Operator.RAW, value = value)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import java.time.DayOfWeek
import java.time.LocalTime
import java.time.OffsetDateTime
import java.time.ZoneOffset
import java.time.format.DateTimeFormatter
import java.time.temporal.TemporalAdjusters
import java.util.stream.Stream

Expand Down Expand Up @@ -147,6 +148,15 @@ class MongoConverterTest {
}
}

@Test
fun beforeTodayDateTimeFormatter() {
Assertions.assertThrows(IllegalArgumentException::class.java) {
Condition.beforeToday("field", 0, Any()).let {
SnapshotConditionConverter.convert(it)
}
}
}

@Test
fun tomorrow() {
val actual = Condition.tomorrow("field").let {
Expand All @@ -165,6 +175,25 @@ class MongoConverterTest {
assertThat(actual, equalTo(expected))
}

@Test
fun tomorrowDateTimeFormatter() {
val dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
val actual = Condition.tomorrow("field", dateTimeFormatter).let {
SnapshotConditionConverter.convert(it)
}
val expected = Filters.and(
Filters.gte(
"field",
dateTimeFormatter.format(OffsetDateTime.now().plusDays(1).with(LocalTime.MIN))
),
Filters.lte(
"field",
dateTimeFormatter.format(OffsetDateTime.now().plusDays(1).with(LocalTime.MAX))
)
)
assertThat(actual, equalTo(expected))
}

@Test
fun thisWeek() {
val actual = Condition.thisWeek("field").let {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import java.time.DayOfWeek
import java.time.LocalTime
import java.time.OffsetDateTime
import java.time.ZoneId
import java.time.format.DateTimeFormatter
import java.time.temporal.TemporalAdjusters

abstract class AbstractConditionConverter<T> : ConditionConverter<T> {
Expand All @@ -27,11 +28,24 @@ abstract class AbstractConditionConverter<T> : ConditionConverter<T> {
return OffsetDateTime.now(zoneId)
}

private fun toDate(time: OffsetDateTime, datePattern: DateTimeFormatter?): Any {
return if (datePattern != null) {
time.format(datePattern)
} else {
time.toInstant().toEpochMilli()
}
}

override fun today(condition: Condition): T {
val now = now(condition)
val startOfDay = now.with(LocalTime.MIN)
val endOfDay = now.with(LocalTime.MAX)
return timeRange(condition.field, startOfDay, endOfDay)
return timeRange(
field = condition.field,
from = startOfDay,
to = endOfDay,
datePattern = condition.datePattern()
)
}

override fun beforeToday(condition: Condition): T {
Expand All @@ -44,8 +58,9 @@ abstract class AbstractConditionConverter<T> : ConditionConverter<T> {
throw IllegalArgumentException("Unsupported condition value type:${conditionValue::class.java}")
}
}
val ltDateTime = now(condition).with(time).toInstant().toEpochMilli()
val ltCondition = Condition.lt(condition.field, ltDateTime)
val now = now(condition).with(time)
val ltDate = toDate(now, condition.datePattern())
val ltCondition = Condition.lt(condition.field, ltDate)
return lt(ltCondition)
}

Expand All @@ -54,9 +69,10 @@ abstract class AbstractConditionConverter<T> : ConditionConverter<T> {
val startOfTomorrow = now.plusDays(1).with(LocalTime.MIN)
val endOfTomorrow = now.plusDays(1).with(LocalTime.MAX)
return timeRange(
condition.field,
startOfTomorrow,
endOfTomorrow
field = condition.field,
from = startOfTomorrow,
to = endOfTomorrow,
datePattern = condition.datePattern()
)
}

Expand All @@ -65,7 +81,12 @@ abstract class AbstractConditionConverter<T> : ConditionConverter<T> {
val startOfWeek =
now.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY)).with(LocalTime.MIN)
val endOfWeek = now.with(TemporalAdjusters.nextOrSame(DayOfWeek.SUNDAY)).with(LocalTime.MAX)
return timeRange(condition.field, startOfWeek, endOfWeek)
return timeRange(
field = condition.field,
from = startOfWeek,
to = endOfWeek,
datePattern = condition.datePattern()
)
}

override fun nextWeek(condition: Condition): T {
Expand All @@ -74,9 +95,10 @@ abstract class AbstractConditionConverter<T> : ConditionConverter<T> {
.with(LocalTime.MIN)
val endOfNextWeek = now.plusWeeks(1).with(TemporalAdjusters.nextOrSame(DayOfWeek.SUNDAY)).with(LocalTime.MAX)
return timeRange(
condition.field,
startOfNextWeek,
endOfNextWeek
field = condition.field,
from = startOfNextWeek,
to = endOfNextWeek,
datePattern = condition.datePattern()
)
}

Expand All @@ -86,17 +108,23 @@ abstract class AbstractConditionConverter<T> : ConditionConverter<T> {
.with(LocalTime.MIN)
val endOfLastWeek = now.minusWeeks(1).with(TemporalAdjusters.nextOrSame(DayOfWeek.SUNDAY)).with(LocalTime.MAX)
return timeRange(
condition.field,
startOfLastWeek,
endOfLastWeek
field = condition.field,
from = startOfLastWeek,
to = endOfLastWeek,
datePattern = condition.datePattern()
)
}

override fun thisMonth(condition: Condition): T {
val now = now(condition)
val startOfMonth = now.withDayOfMonth(1).with(LocalTime.MIN)
val endOfMonth = now.with(TemporalAdjusters.lastDayOfMonth()).with(LocalTime.MAX)
return timeRange(condition.field, startOfMonth, endOfMonth)
return timeRange(
field = condition.field,
from = startOfMonth,
to = endOfMonth,
datePattern = condition.datePattern()
)
}

override fun lastMonth(condition: Condition): T {
Expand All @@ -106,7 +134,8 @@ abstract class AbstractConditionConverter<T> : ConditionConverter<T> {
return timeRange(
field = condition.field,
from = startOfLastMonth,
to = endOfLastMonth
to = endOfLastMonth,
datePattern = condition.datePattern()
)
}

Expand All @@ -116,16 +145,17 @@ abstract class AbstractConditionConverter<T> : ConditionConverter<T> {
val startOfRecentDays = now.minusDays(days.toLong() - 1).with(LocalTime.MIN)
val endOfRecentDays = now.with(LocalTime.MAX)
return timeRange(
condition.field,
startOfRecentDays,
endOfRecentDays
field = condition.field,
from = startOfRecentDays,
to = endOfRecentDays,
datePattern = condition.datePattern()
)
}

fun timeRange(field: String, from: OffsetDateTime, to: OffsetDateTime): T {
val fromEpoch = from.toInstant().toEpochMilli()
val toEpoch = to.toInstant().toEpochMilli()
val betweenCondition = Condition.between(field, fromEpoch, toEpoch)
fun timeRange(field: String, from: OffsetDateTime, to: OffsetDateTime, datePattern: DateTimeFormatter? = null): T {
val fromDate: Any = toDate(from, datePattern)
val toDate: Any = toDate(to, datePattern)
val betweenCondition = Condition.between(field, fromDate, toDate)
return between(betweenCondition)
}
}
Loading

0 comments on commit 0e5d570

Please sign in to comment.