Skip to content

Commit 3b3f34f

Browse files
amishra-utimtebeek
andauthored
Joda-Time to Java time: Add templates for Joda Interval to Threeten-extra Interval (#617)
* Joda-Time to Java time: Add templates for migrating Joda Interval to Threeten-Extra Interval * Syling: remove indent from empty lines * Add threeten-extra dependency, if needed * checkstyle * Minor polish --------- Co-authored-by: Tim te Beek <[email protected]>
1 parent bd2cc9c commit 3b3f34f

File tree

11 files changed

+378
-10
lines changed

11 files changed

+378
-10
lines changed

build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ dependencies {
5050

5151
testImplementation("com.google.guava:guava:33.0.0-jre")
5252
testImplementation("joda-time:joda-time:2.12.3")
53+
testImplementation("org.threeten:threeten-extra:1.8.0")
5354

5455
testRuntimeOnly("com.fasterxml.jackson.datatype:jackson-datatype-jsr353")
5556
testRuntimeOnly("com.fasterxml.jackson.core:jackson-core")

src/main/java/org/openrewrite/java/migrate/joda/JodaTimeVisitor.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ public Javadoc visitReference(Javadoc.Reference reference, ExecutionContext ctx)
6565
maybeRemoveImport(JODA_DURATION);
6666
maybeRemoveImport(JODA_ABSTRACT_INSTANT);
6767
maybeRemoveImport(JODA_INSTANT);
68+
maybeRemoveImport(JODA_INTERVAL);
6869
maybeRemoveImport("java.util.Locale");
6970

7071
maybeAddImport(JAVA_DATE_TIME);
@@ -79,6 +80,7 @@ public Javadoc visitReference(Javadoc.Reference reference, ExecutionContext ctx)
7980
maybeAddImport(JAVA_TEMPORAL_ISO_FIELDS);
8081
maybeAddImport(JAVA_CHRONO_FIELD);
8182
maybeAddImport(JAVA_UTIL_DATE);
83+
maybeAddImport(THREE_TEN_EXTRA_INTERVAL);
8284
return super.visitCompilationUnit(cu, ctx);
8385
}
8486

@@ -168,9 +170,11 @@ public Javadoc visitReference(Javadoc.Reference reference, ExecutionContext ctx)
168170
if (!isJodaVarRef(ident)) {
169171
return super.visitIdentifier(ident, ctx);
170172
}
171-
Optional<NamedVariable> mayBeVar = findVarInScope(ident.getSimpleName());
172-
if (!mayBeVar.isPresent() || acc.getUnsafeVars().contains(mayBeVar.get())) {
173-
return ident;
173+
if (this.safeMigration) {
174+
Optional<NamedVariable> mayBeVar = findVarInScope(ident.getSimpleName());
175+
if (!mayBeVar.isPresent() || acc.getUnsafeVars().contains(mayBeVar.get())) {
176+
return ident;
177+
}
174178
}
175179

176180
JavaType.FullyQualified jodaType = ((JavaType.Class) ident.getType());
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright 2024 the original author or authors.
3+
* <p>
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* <p>
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
* <p>
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.openrewrite.java.migrate.joda.templates;
17+
18+
import lombok.Getter;
19+
import org.openrewrite.java.JavaParser;
20+
import org.openrewrite.java.JavaTemplate;
21+
import org.openrewrite.java.MethodMatcher;
22+
23+
import java.util.ArrayList;
24+
import java.util.List;
25+
26+
import static org.openrewrite.java.migrate.joda.templates.TimeClassNames.*;
27+
28+
public class AbstractIntervalTemplates implements Templates {
29+
private final MethodMatcher getStart = new MethodMatcher(JODA_ABSTRACT_INTERVAL + " getStart()");
30+
private final MethodMatcher getEnd = new MethodMatcher(JODA_ABSTRACT_INTERVAL + " getEnd()");
31+
private final MethodMatcher toDuration = new MethodMatcher(JODA_ABSTRACT_INTERVAL + " toDuration()");
32+
private final MethodMatcher toDurationMillis = new MethodMatcher(JODA_ABSTRACT_INTERVAL + " toDurationMillis()");
33+
private final MethodMatcher contains = new MethodMatcher(JODA_ABSTRACT_INTERVAL + " contains(long)");
34+
35+
private final JavaTemplate getStartTemplate = JavaTemplate.builder("#{any(" + THREE_TEN_EXTRA_INTERVAL + ")}.getStart().atZone(ZoneId.systemDefault())")
36+
.javaParser(JavaParser.fromJavaVersion().classpath("threeten-extra"))
37+
.imports(JAVA_ZONE_ID)
38+
.build();
39+
private final JavaTemplate getEndTemplate = JavaTemplate.builder("#{any(" + THREE_TEN_EXTRA_INTERVAL + ")}.getEnd().atZone(ZoneId.systemDefault())")
40+
.javaParser(JavaParser.fromJavaVersion().classpath("threeten-extra"))
41+
.imports(JAVA_ZONE_ID)
42+
.build();
43+
private final JavaTemplate toDurationTemplate = JavaTemplate.builder("#{any(" + THREE_TEN_EXTRA_INTERVAL + ")}.toDuration()")
44+
.javaParser(JavaParser.fromJavaVersion().classpath("threeten-extra"))
45+
.build();
46+
private final JavaTemplate toDurationMillisTemplate = JavaTemplate.builder("#{any(" + THREE_TEN_EXTRA_INTERVAL + ")}.toDuration().toMillis()")
47+
.javaParser(JavaParser.fromJavaVersion().classpath("threeten-extra"))
48+
.build();
49+
private final JavaTemplate containsTemplate = JavaTemplate.builder("#{any(" + THREE_TEN_EXTRA_INTERVAL + ")}.contains(Instant.ofEpochMilli(#{any(long)}))")
50+
.javaParser(JavaParser.fromJavaVersion().classpath("threeten-extra"))
51+
.imports(JAVA_INSTANT)
52+
.build();
53+
54+
@Getter
55+
private final List<MethodTemplate> templates = new ArrayList<MethodTemplate>() {
56+
{
57+
add(new MethodTemplate(getStart, getStartTemplate));
58+
add(new MethodTemplate(getEnd, getEndTemplate));
59+
add(new MethodTemplate(toDuration, toDurationTemplate));
60+
add(new MethodTemplate(toDurationMillis, toDurationMillisTemplate));
61+
add(new MethodTemplate(contains, containsTemplate));
62+
}
63+
};
64+
}

src/main/java/org/openrewrite/java/migrate/joda/templates/AllTemplates.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ public class AllTemplates {
3939
private static final MethodMatcher ANY_ABSTRACT_DURATION = new MethodMatcher(JODA_ABSTRACT_DURATION + " *(..)");
4040
private static final MethodMatcher ANY_INSTANT = new MethodMatcher(JODA_INSTANT + " *(..)");
4141
private static final MethodMatcher ANY_NEW_INSTANT = new MethodMatcher(JODA_INSTANT + "<constructor>(..)");
42+
private static final MethodMatcher ANY_NEW_INTERVAL = new MethodMatcher(JODA_INTERVAL + "<constructor>(..)");
43+
private static final MethodMatcher ANY_ABSTRACT_INTERVAL = new MethodMatcher(JODA_ABSTRACT_INTERVAL + " *(..)");
4244

4345
private static List<MatcherAndTemplates> templates = new ArrayList<MatcherAndTemplates>() {
4446
{
@@ -55,6 +57,8 @@ public class AllTemplates {
5557
add(new MatcherAndTemplates(ANY_DATE_TIMEZONE, new TimeZoneTemplates()));
5658
add(new MatcherAndTemplates(ANY_INSTANT, new InstantTemplates()));
5759
add(new MatcherAndTemplates(ANY_NEW_INSTANT, new InstantTemplates()));
60+
add(new MatcherAndTemplates(ANY_NEW_INTERVAL, new IntervalTemplates()));
61+
add(new MatcherAndTemplates(ANY_ABSTRACT_INTERVAL, new AbstractIntervalTemplates()));
5862
}
5963
};
6064

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright 2024 the original author or authors.
3+
* <p>
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* <p>
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
* <p>
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.openrewrite.java.migrate.joda.templates;
17+
18+
import lombok.Getter;
19+
import org.openrewrite.java.JavaParser;
20+
import org.openrewrite.java.JavaTemplate;
21+
import org.openrewrite.java.MethodMatcher;
22+
import org.openrewrite.java.tree.Expression;
23+
24+
import java.util.ArrayList;
25+
import java.util.List;
26+
27+
import static org.openrewrite.java.migrate.joda.templates.TimeClassNames.*;
28+
29+
public class IntervalTemplates implements Templates {
30+
private final MethodMatcher interval = new MethodMatcher(JODA_INTERVAL + " <constructor>(long, long)");
31+
private final MethodMatcher intervalWithTimeZone = new MethodMatcher(JODA_INTERVAL + " <constructor>(long, long, " + JODA_DATE_TIME_ZONE + ")");
32+
private final MethodMatcher intervalWithDateTime = new MethodMatcher(JODA_INTERVAL + " <constructor>(" + JODA_READABLE_INSTANT + ", " + JODA_READABLE_INSTANT + ")");
33+
private final MethodMatcher intervalWithDateTimeAndDuration = new MethodMatcher(JODA_INTERVAL + " <constructor>(" + JODA_READABLE_INSTANT + ", " + JODA_READABLE_DURATION + ")");
34+
35+
private final JavaTemplate intervalTemplate = JavaTemplate.builder("Interval.of(Instant.ofEpochMilli(#{any(long)}), Instant.ofEpochMilli(#{any(long)}))")
36+
.javaParser(JavaParser.fromJavaVersion().classpath("threeten"))
37+
.imports(JAVA_INSTANT, THREE_TEN_EXTRA_INTERVAL)
38+
.build();
39+
private final JavaTemplate intervalWithDateTimeTemplate = JavaTemplate.builder("Interval.of(#{any(" + JAVA_DATE_TIME + ")}.toInstant(), #{any(" + JAVA_DATE_TIME + ")}.toInstant())")
40+
.javaParser(JavaParser.fromJavaVersion().classpath("threeten"))
41+
.imports(THREE_TEN_EXTRA_INTERVAL)
42+
.build();
43+
private final JavaTemplate intervalWithDateTimeAndDurationTemplate = JavaTemplate.builder("Interval.of(#{any(" + JAVA_DATE_TIME + ")}.toInstant(), #{any(" + JAVA_DURATION + ")})")
44+
.javaParser(JavaParser.fromJavaVersion().classpath("threeten"))
45+
.imports(THREE_TEN_EXTRA_INTERVAL)
46+
.build();
47+
48+
@Getter
49+
private final List<MethodTemplate> templates = new ArrayList<MethodTemplate>() {
50+
{
51+
add(new MethodTemplate(interval, intervalTemplate));
52+
add(new MethodTemplate(intervalWithTimeZone, intervalTemplate,
53+
m -> new Expression[]{m.getArguments().get(0), m.getArguments().get(1)}));
54+
add(new MethodTemplate(intervalWithDateTime, intervalWithDateTimeTemplate));
55+
add(new MethodTemplate(intervalWithDateTimeAndDuration, intervalWithDateTimeAndDurationTemplate));
56+
}
57+
};
58+
}

src/main/java/org/openrewrite/java/migrate/joda/templates/TimeClassMap.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ public class TimeClassMap {
3535
put(JODA_TIME_FORMATTER, javaTypeClass(JAVA_TIME_FORMATTER, object));
3636
put(JODA_DURATION, javaTypeClass(JAVA_DURATION, object));
3737
put(JODA_READABLE_DURATION, javaTypeClass(JAVA_DURATION, object));
38+
put(JODA_INTERVAL, javaTypeClass(THREE_TEN_EXTRA_INTERVAL, object));
3839
}
3940
};
4041

src/main/java/org/openrewrite/java/migrate/joda/templates/TimeClassNames.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ public class TimeClassNames {
2727
public static final String JODA_TIME_PKG = "org.joda.time";
2828
public static final String JODA_ABSTRACT_DATE_TIME = JODA_TIME_PKG + ".base.AbstractDateTime";
2929
public static final String JODA_ABSTRACT_DURATION = JODA_TIME_PKG + ".base.AbstractDuration";
30+
public static final String JODA_ABSTRACT_INTERVAL = JODA_TIME_PKG + ".base.AbstractInterval";
3031
public static final String JODA_BASE_DATE_TIME = JODA_TIME_PKG + ".base.BaseDateTime";
3132
public static final String JODA_DATE_TIME = JODA_TIME_PKG + ".DateTime";
3233
public static final String JODA_DATE_TIME_ZONE = JODA_TIME_PKG + ".DateTimeZone";
@@ -39,7 +40,9 @@ public class TimeClassNames {
3940
public static final String JODA_DURATION = JODA_TIME_PKG + ".Duration";
4041
public static final String JODA_READABLE_DURATION = JODA_TIME_PKG + ".ReadableDuration";
4142
public static final String JODA_ABSTRACT_INSTANT = JODA_TIME_PKG + ".base.AbstractInstant";
43+
public static final String JODA_READABLE_INSTANT = JODA_TIME_PKG + ".ReadableInstant";
4244
public static final String JODA_INSTANT = JODA_TIME_PKG + ".Instant";
45+
public static final String JODA_INTERVAL = JODA_TIME_PKG + ".Interval";
4346

4447
// Java Time classes
4548
public static final String JAVA_TIME_PKG = "java.time";
@@ -55,4 +58,8 @@ public class TimeClassNames {
5558
public static final String JAVA_LOCAL_TIME = JAVA_TIME_PKG + ".LocalTime";
5659
public static final String JAVA_TEMPORAL_ISO_FIELDS = JAVA_TIME_PKG + ".temporal.IsoFields";
5760
public static final String JAVA_CHRONO_FIELD = JAVA_TIME_PKG + ".temporal.ChronoField";
61+
62+
// ThreeTen-Extra classes
63+
public static final String THREE_TEN_EXTRA_PKG = "org.threeten.extra";
64+
public static final String THREE_TEN_EXTRA_INTERVAL = THREE_TEN_EXTRA_PKG + ".Interval";
5865
}

src/main/java/org/openrewrite/java/migrate/joda/templates/VarTemplates.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package org.openrewrite.java.migrate.joda.templates;
1717

18+
import org.openrewrite.java.JavaParser;
1819
import org.openrewrite.java.JavaTemplate;
1920
import org.openrewrite.java.tree.J;
2021
import org.openrewrite.java.tree.JavaType;
@@ -35,6 +36,7 @@ public class VarTemplates {
3536
put(JODA_LOCAL_TIME, JAVA_LOCAL_TIME);
3637
put(JODA_DATE_TIME_ZONE, JAVA_ZONE_ID);
3738
put(JODA_DURATION, JAVA_DURATION);
39+
put(JODA_INTERVAL, THREE_TEN_EXTRA_INTERVAL);
3840
}
3941
};
4042

@@ -65,8 +67,9 @@ public static Optional<JavaTemplate> getTemplate(J.VariableDeclarations variable
6567
}
6668
}
6769
return Optional.of(JavaTemplate.builder(template.toString())
68-
.imports(typeName)
69-
.build());
70+
.imports(typeName)
71+
.javaParser(JavaParser.fromJavaVersion().classpath("threeten"))
72+
.build());
7073
}
7174

7275
public static Optional<JavaTemplate> getTemplate(J.Assignment assignment) {
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#
2+
# Copyright 2021 the original author or authors.
3+
# <p>
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
# <p>
8+
# https://www.apache.org/licenses/LICENSE-2.0
9+
# <p>
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
#
16+
---
17+
type: specs.openrewrite.org/v1beta/recipe
18+
name: org.openrewrite.java.migrate.joda.NoJodaTime
19+
displayName: Prefer the Java standard library instead of Joda-Time
20+
description: >-
21+
Before Java 8, Java lacked a robust date and time library, leading to the widespread use of Joda-Time to fill this
22+
gap. With the release of Java 8, the `java.time` package was introduced, incorporating most of Joda-Time's concepts.
23+
Features deemed too specialized or bulky for `java.time` were included in the ThreeTen-Extra library. This recipe
24+
migrates Joda-Time types to `java.time` and `threeten-extra` types.
25+
tags:
26+
- joda-time
27+
recipeList:
28+
- org.openrewrite.java.dependencies.AddDependency:
29+
groupId: org.threeten
30+
artifactId: threeten-extra
31+
version: 1.8.0
32+
onlyIfUsing: org.joda.time.*Interval*
33+
- org.openrewrite.java.migrate.joda.JodaTimeRecipe

src/test/java/org/openrewrite/java/migrate/joda/JodaTimeVisitorTest.java

Lines changed: 84 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ class JodaTimeVisitorTest implements RewriteTest {
3232
public void defaults(RecipeSpec spec) {
3333
spec
3434
.recipe(toRecipe(() -> new JodaTimeVisitor(new JodaTimeRecipe.Accumulator(), true, new LinkedList<>())))
35-
.parser(JavaParser.fromJavaVersion().classpath("joda-time"));
35+
.parser(JavaParser.fromJavaVersion().classpath("joda-time", "threeten-extra"));
3636
}
3737

3838
@DocumentExample
@@ -695,11 +695,11 @@ void unhandledCases() {
695695
rewriteRun(
696696
java(
697697
"""
698-
import org.joda.time.Interval;
698+
import org.joda.time.PeriodType;
699699
700700
class A {
701701
public void foo() {
702-
new Interval(100, 50);
702+
PeriodType.standard();
703703
}
704704
}
705705
"""
@@ -782,11 +782,90 @@ void unhandledVarDeclaration() {
782782
rewriteRun(
783783
java(
784784
"""
785+
import org.joda.time.PeriodType;
786+
787+
class A {
788+
public void foo(PeriodType periodType) {
789+
periodType = PeriodType.days();
790+
}
791+
}
792+
"""
793+
)
794+
);
795+
}
796+
797+
@Test
798+
void migrateInterval() {
799+
// language=java
800+
rewriteRun(
801+
java(
802+
"""
803+
import org.joda.time.DateTime;
804+
import org.joda.time.Duration;
785805
import org.joda.time.Interval;
806+
import org.joda.time.DateTimeZone;
807+
808+
class A {
809+
public void foo() {
810+
System.out.println(new Interval(50, 100));
811+
System.out.println(new Interval(50, 100, DateTimeZone.UTC));
812+
System.out.println(new Interval(DateTime.now(), DateTime.now().plusDays(1)));
813+
System.out.println(new Interval(DateTime.now(), Duration.standardDays(1)));
814+
}
815+
}
816+
""",
817+
"""
818+
import org.threeten.extra.Interval;
786819
820+
import java.time.Duration;
821+
import java.time.Instant;
822+
import java.time.ZonedDateTime;
823+
787824
class A {
788-
public void foo(Interval interval) {
789-
interval = new Interval(100, 50);
825+
public void foo() {
826+
System.out.println(Interval.of(Instant.ofEpochMilli(50), Instant.ofEpochMilli(100)));
827+
System.out.println(Interval.of(Instant.ofEpochMilli(50), Instant.ofEpochMilli(100)));
828+
System.out.println(Interval.of(ZonedDateTime.now().toInstant(), ZonedDateTime.now().plusDays(1).toInstant()));
829+
System.out.println(Interval.of(ZonedDateTime.now().toInstant(), Duration.ofDays(1)));
830+
}
831+
}
832+
"""
833+
)
834+
);
835+
}
836+
837+
@Test
838+
void migrateAbstractInterval() {
839+
// language=java
840+
rewriteRun(
841+
java(
842+
"""
843+
import org.joda.time.DateTime;
844+
import org.joda.time.Interval;
845+
846+
class A {
847+
public void foo() {
848+
new Interval(50, 100).getStart();
849+
new Interval(50, 100).getEnd();
850+
new Interval(50, 100).toDuration();
851+
new Interval(50, 100).toDurationMillis();
852+
new Interval(50, 100).contains(75);
853+
}
854+
}
855+
""",
856+
"""
857+
import org.threeten.extra.Interval;
858+
859+
import java.time.Instant;
860+
import java.time.ZoneId;
861+
862+
class A {
863+
public void foo() {
864+
Interval.of(Instant.ofEpochMilli(50), Instant.ofEpochMilli(100)).getStart().atZone(ZoneId.systemDefault());
865+
Interval.of(Instant.ofEpochMilli(50), Instant.ofEpochMilli(100)).getEnd().atZone(ZoneId.systemDefault());
866+
Interval.of(Instant.ofEpochMilli(50), Instant.ofEpochMilli(100)).toDuration();
867+
Interval.of(Instant.ofEpochMilli(50), Instant.ofEpochMilli(100)).toDuration().toMillis();
868+
Interval.of(Instant.ofEpochMilli(50), Instant.ofEpochMilli(100)).contains(Instant.ofEpochMilli(75));
790869
}
791870
}
792871
"""

0 commit comments

Comments
 (0)