Skip to content

Commit 6656fb7

Browse files
committed
version 0.15
added new IntervalUnits Day and Hours
1 parent 8e1ebca commit 6656fb7

File tree

8 files changed

+113
-29
lines changed

8 files changed

+113
-29
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
.gradle
2+
.kotlin
23
out
34
build
45
workspace

README.md

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,15 @@ The Label class is for making labels, String for use in different contexts.
2525
```java
2626
Label id = Label.of("project").with("version");
2727

28-
id.camelCase(); // projectVersion
29-
id.lowerHyphen(); // project-version
30-
id.lowerColonPath(); // project:version
28+
id.
29+
30+
camelCase(); // projectVersion
31+
id.
32+
33+
lowerHyphen(); // project-version
34+
id.
35+
36+
lowerColonPath(); // project:version
3137
```
3238

3339
There are a few flavors of Label:
@@ -65,15 +71,20 @@ class provides a way to create Refs for child resources of the construct.
6571
Provides [IntervalUnit](clusterless-commons-core/src/main/java/clusterless/commons/temporal/IntervalUnit.java)
6672
implementations of:
6773

68-
- Fourths - 15 minute intervals
69-
- Sixths - 10 minute intervals
70-
- Twelfths - 5 minute intervals
71-
72-
These intervals are used by Clusterless to label lots.
74+
- Day - the day interval - `20230206PT24H`
75+
- Hours - 60 minute intervals within a day (24 intervals) - `20230206PT1H23`
76+
- Fourths - 15 minute intervals within a day (96 intervals) - `20230206PT15M92`
77+
- Sixths - 10 minute intervals within a day (144 intervals) - `20230206PT10M138`
78+
- Twelfths - 5 minute intervals within a day (288 intervals) - `20230206PT5M276`
7379

7480
There is also a
7581
[IntervalDateTimeFormatter](clusterless-commons-core/src/main/java/clusterless/commons/temporal/IntervalDateTimeFormatter.java)
76-
for formatting dates and times of these intervals.
82+
for formatting and parsing of the dates and times these intervals represent. See above for examples.
83+
84+
These intervals are used by Clusterless to label the execution of a batch job (or lot).
85+
86+
For continuously arriving data, the lexical order of the jobs by the formatted interval string makes it trivial to
87+
identify gaps and reason about how long the batch accumulated data before execution.
7788

7889
### Collection
7990

build-logic/src/main/kotlin/clusterless.commons.java-common-properties.gradle.kts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
*/
88

99
extra["group"] = "io.clusterless"
10-
extra["version"] = "0.14"
10+
//extra["version"] = "0.15-wip-"+System.getProperty("build.number","dev")
11+
extra["version"] = "0.15"
1112

1213
extra["repoUserName"] = System.getProperty("publish.repo.userName")
1314
extra["repoPassword"] = System.getProperty("publish.repo.password")

clusterless-commons-core/src/main/java/clusterless/commons/temporal/IntervalDateTimeFormatter.java

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,26 +14,56 @@
1414
import java.time.temporal.TemporalUnit;
1515

1616
import static clusterless.commons.temporal.IntervalField.*;
17+
import static clusterless.commons.temporal.IntervalField.HOUR_OF_DAY;
1718
import static java.time.temporal.ChronoField.*;
1819

1920
/**
2021
* IntervalDateTimeFormatter provided formatters for the {@link IntervalUnit} units.
21-
*
22+
* <p>
2223
* See {@link IntervalUnits#formatter(TemporalUnit)} for looking up an appropriate formatter.
2324
*/
2425
public class IntervalDateTimeFormatter {
26+
public static final DateTimeFormatter DAY_FORMATTER;
27+
public static final DateTimeFormatter HOUR_FORMATTER;
2528
public static final DateTimeFormatter FOURTH_FORMATTER;
2629
public static final DateTimeFormatter SIXTH_FORMATTER;
2730
public static final DateTimeFormatter TWELFTH_FORMATTER;
2831

32+
static {
33+
DAY_FORMATTER = new DateTimeFormatterBuilder()
34+
.parseStrict()
35+
.appendValue(YEAR, 4)
36+
.appendValue(MONTH_OF_YEAR, 2)
37+
.appendValue(DAY_OF_MONTH, 2)
38+
.appendLiteral(DAY.getBaseUnit().getDuration().toString())
39+
// ensure that the TemporalAccessor is set to midnight
40+
.parseDefaulting(HOUR_OF_DAY, 0)
41+
.parseDefaulting(MINUTE_OF_HOUR, 0)
42+
.parseDefaulting(SECOND_OF_MINUTE, 0)
43+
.toFormatter()
44+
.withZone(ZoneOffset.UTC);
45+
}
46+
47+
static {
48+
HOUR_FORMATTER = new DateTimeFormatterBuilder()
49+
.parseStrict()
50+
.appendValue(YEAR, 4)
51+
.appendValue(MONTH_OF_YEAR, 2)
52+
.appendValue(DAY_OF_MONTH, 2)
53+
.appendLiteral(HOUR_OF_DAY.getBaseUnit().getDuration().toString())
54+
.appendValue(HOUR_OF_DAY, 2)
55+
.toFormatter()
56+
.withZone(ZoneOffset.UTC);
57+
}
58+
2959
static {
3060
FOURTH_FORMATTER = new DateTimeFormatterBuilder()
3161
.parseStrict()
3262
.appendValue(YEAR, 4)
3363
.appendValue(MONTH_OF_YEAR, 2)
3464
.appendValue(DAY_OF_MONTH, 2)
3565
.appendLiteral(FOURTH_OF_DAY.getBaseUnit().getDuration().toString())
36-
.appendValue(FOURTH_OF_DAY, 3)
66+
.appendValue(FOURTH_OF_DAY, 2) // max 96
3767
.toFormatter()
3868
.withZone(ZoneOffset.UTC);
3969
}

clusterless-commons-core/src/main/java/clusterless/commons/temporal/IntervalField.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,26 @@
1919
import java.util.Objects;
2020

2121
import static java.time.temporal.ChronoUnit.DAYS;
22+
import static java.time.temporal.ChronoUnit.YEARS;
2223

2324
/**
2425
* Mostly copied from {@link ChronoField} adding support for new IntervalUnits.
2526
*/
2627
public enum IntervalField implements TemporalField {
28+
/**
29+
* The day.
30+
* <p>
31+
* This represents the actual day only.
32+
*/
33+
DAY("Day", IntervalUnit.DAY, YEARS, ValueRange.of(0, 365)),
34+
35+
/**
36+
* The hour-of-day.
37+
* <p>
38+
* This counts the full hour within the day, from 0 to 24 * 60.
39+
*/
40+
HOUR_OF_DAY("HourOfDay", IntervalUnit.HOURS, DAYS, ValueRange.of(0, 24 * 60)),
41+
2742
/**
2843
* The fourth-of-day.
2944
* <p>

clusterless-commons-core/src/main/java/clusterless/commons/temporal/IntervalUnit.java

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,16 @@
99
package clusterless.commons.temporal;
1010

1111
import java.time.Duration;
12+
import java.time.temporal.ChronoUnit;
1213
import java.time.temporal.Temporal;
1314
import java.time.temporal.TemporalUnit;
1415

1516
import static java.time.temporal.ChronoField.MINUTE_OF_DAY;
16-
import static java.time.temporal.ChronoUnit.MINUTES;
1717

1818
/**
1919
* Breaks a day into the number of intervals requested.
20+
* <p>
21+
* Day is the actual day as an interval where the day component is of the month.
2022
* <p/>
2123
* Fourths is a 15-minute duration, there are 4 Fourths in an hour, and 96 Fourths in a day.
2224
* <p/>
@@ -25,6 +27,8 @@
2527
* Twelfths is a 5-minute duration, there are 12 Twelfths in an hour, and 288 Twelfth in a day.
2628
*/
2729
public enum IntervalUnit implements TemporalUnit {
30+
DAY("Day", Duration.ofDays(1)),
31+
HOURS("Hours", Duration.ofHours(1)),
2832
FOURTHS("Fourths", Duration.ofMinutes(15)),
2933
SIXTHS("Sixths", Duration.ofMinutes(10)),
3034
TWELFTHS("Twelfths", Duration.ofMinutes(5));
@@ -66,12 +70,16 @@ public boolean isSupportedBy(Temporal temporal) {
6670
@Override
6771
public <R extends Temporal> R addTo(R temporal, long amount) {
6872
switch (this) {
73+
case DAY:
74+
return (R) temporal.plus(amount, ChronoUnit.DAYS);
75+
case HOURS:
76+
return (R) temporal.plus(amount, ChronoUnit.HOURS);
6977
case FOURTHS:
70-
return (R) temporal.plus(15 * amount, MINUTES);
78+
return (R) temporal.plus(15 * amount, ChronoUnit.MINUTES);
7179
case SIXTHS:
72-
return (R) temporal.plus(10 * amount, MINUTES);
80+
return (R) temporal.plus(10 * amount, ChronoUnit.MINUTES);
7381
case TWELFTHS:
74-
return (R) temporal.plus(5 * amount, MINUTES);
82+
return (R) temporal.plus(5 * amount, ChronoUnit.MINUTES);
7583
default:
7684
throw new IllegalArgumentException();
7785
}
@@ -83,12 +91,16 @@ public long between(Temporal temporal1Inclusive, Temporal temporal2Exclusive) {
8391
return temporal1Inclusive.until(temporal2Exclusive, this);
8492
}
8593
switch (this) {
94+
case DAY:
95+
return temporal1Inclusive.until(temporal2Exclusive, ChronoUnit.DAYS);
96+
case HOURS:
97+
return temporal1Inclusive.until(temporal2Exclusive, ChronoUnit.HOURS);
8698
case FOURTHS:
87-
return temporal1Inclusive.until(temporal2Exclusive, MINUTES) / 15;
99+
return temporal1Inclusive.until(temporal2Exclusive, ChronoUnit.MINUTES) / 15;
88100
case SIXTHS:
89-
return temporal1Inclusive.until(temporal2Exclusive, MINUTES) / 10;
101+
return temporal1Inclusive.until(temporal2Exclusive, ChronoUnit.MINUTES) / 10;
90102
case TWELFTHS:
91-
return temporal1Inclusive.until(temporal2Exclusive, MINUTES) / 5;
103+
return temporal1Inclusive.until(temporal2Exclusive, ChronoUnit.MINUTES) / 5;
92104
default:
93105
throw new IllegalArgumentException();
94106
}

clusterless-commons-core/src/main/java/clusterless/commons/temporal/IntervalUnits.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,12 @@ public static void verifyHasFormatter(TemporalUnit unit) {
3939
* @throws IllegalArgumentException if the given unit does not have an associated formatter
4040
*/
4141
public static DateTimeFormatter formatter(TemporalUnit unit) {
42+
if (unit == IntervalUnit.DAY) {
43+
return IntervalDateTimeFormatter.DAY_FORMATTER;
44+
}
45+
if (unit == IntervalUnit.HOURS) {
46+
return IntervalDateTimeFormatter.HOUR_FORMATTER;
47+
}
4248
if (unit == IntervalUnit.FOURTHS) {
4349
return IntervalDateTimeFormatter.FOURTH_FORMATTER;
4450
}
@@ -63,9 +69,9 @@ public static TemporalUnit find(String name) {
6369
Objects.requireNonNull(name, "name");
6470

6571
try {
66-
return ChronoUnit.valueOf(name.toUpperCase(Locale.ROOT));
67-
} catch (IllegalArgumentException e) {
6872
return IntervalUnit.valueOf(name.toUpperCase(Locale.ROOT));
73+
} catch (IllegalArgumentException e) {
74+
return ChronoUnit.valueOf(name.toUpperCase(Locale.ROOT));
6975
}
7076
}
7177

clusterless-commons-core/src/test/java/clusterless/commons/temporal/TemporalTest.java

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import java.time.ZoneId;
1818
import java.time.format.DateTimeFormatter;
1919
import java.time.temporal.TemporalAccessor;
20+
import java.time.temporal.TemporalField;
2021
import java.time.temporal.TemporalUnit;
2122
import java.util.stream.Stream;
2223

@@ -35,19 +36,26 @@ public class TemporalTest {
3536
private static Stream<Arguments> arguments() {
3637
return Stream.of(
3738
// Mon Feb 06 2023 23:52:06 GMT+0000
38-
Arguments.of(1675727526500L, 1675727100000L, 95, IntervalField.FOURTH_OF_DAY, "PT15M"),
39-
Arguments.of(1675727526500L, 1675727400000L, 143, IntervalField.SIXTH_OF_DAY, "PT10M"),
40-
Arguments.of(1675727526500L, 1675727400000L, 286, IntervalField.TWELFTH_OF_DAY, "PT5M"),
39+
Arguments.of(1675727526500L, 1675727100000L, 95, IntervalField.FOURTH_OF_DAY, "PT15M", "20230206%s%02d"),
40+
Arguments.of(1675727526500L, 1675727400000L, 143, IntervalField.SIXTH_OF_DAY, "PT10M", "20230206%s%03d"),
41+
Arguments.of(1675727526500L, 1675727400000L, 286, IntervalField.TWELFTH_OF_DAY, "PT5M", "20230206%s%03d"),
4142
// Mon Feb 06 2023 23:00:21 GMT+0000
42-
Arguments.of(1675724421500L, 1675724400000L, 96 - 4, IntervalField.FOURTH_OF_DAY, "PT15M"),
43-
Arguments.of(1675724421500L, 1675724400000L, 144 - 6, IntervalField.SIXTH_OF_DAY, "PT10M"),
44-
Arguments.of(1675724421500L, 1675724400000L, 288 - 12, IntervalField.TWELFTH_OF_DAY, "PT5M")
43+
Arguments.of(1675724421500L, 1675724400000L, 96 - 4, IntervalField.FOURTH_OF_DAY, "PT15M", "20230206%s%02d"),
44+
Arguments.of(1675724421500L, 1675724400000L, 144 - 6, IntervalField.SIXTH_OF_DAY, "PT10M", "20230206%s%03d"),
45+
Arguments.of(1675724421500L, 1675724400000L, 288 - 12, IntervalField.TWELFTH_OF_DAY, "PT5M", "20230206%s%03d"),
46+
47+
Arguments.of(1675724421500L, 1675724400000L, 23, IntervalField.HOUR_OF_DAY, "PT1H", "20230206%s%02d"),
48+
Arguments.of(1675724421500L, 1675641600000L, 0, IntervalField.DAY, "PT24H", "20230206%s")
49+
50+
// unsupported by java.time so we made our own above
51+
// Arguments.of(1675724421500L, 1675724400000L, 23, ChronoField.HOUR_OF_DAY, "PT1H", "20230206%s%02d"),
52+
// Arguments.of(1675724421500L, 1675641600000L, 0, ChronoField.DAY_OF_MONTH, "PT24H", "20230206%s")
4553
);
4654
}
4755

4856
@ParameterizedTest
4957
@MethodSource("arguments")
50-
public void test(long epochMilli, long truncatedMilli, int expected, IntervalField intervalField, String durationFragment) {
58+
public void test(long epochMilli, long truncatedMilli, int expected, TemporalField intervalField, String durationFragment, String stringFormat) {
5159
TemporalUnit durationUnit = intervalField.getBaseUnit();
5260
DateTimeFormatter formatter = IntervalUnits.formatter(durationUnit);
5361
Instant instant = Instant.ofEpochMilli(epochMilli);
@@ -60,7 +68,7 @@ public void test(long epochMilli, long truncatedMilli, int expected, IntervalFie
6068

6169
String format = formatter.format(instant);
6270

63-
assertEquals(String.format("20230206%s%03d", durationFragment, i), format);
71+
assertEquals(String.format(stringFormat, durationFragment, i), format);
6472

6573
TemporalAccessor temporalAccessor = formatter.parse(format);
6674

0 commit comments

Comments
 (0)