-
Notifications
You must be signed in to change notification settings - Fork 93
Replace week year (YYYY) with year (yyyy) in date formats #129
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 12 commits
2b6a115
9cf2f77
355341a
4cbfe7c
974c211
c4bd801
5244382
03f886b
42289b1
71e25a0
47b48b5
9b1f649
384c8e2
7d977be
5211ebe
658a332
4507667
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,120 @@ | ||
| /* | ||
| * Copyright 2023 the original author or authors. | ||
| * <p> | ||
| * Licensed 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 | ||
| * <p> | ||
| * https://www.apache.org/licenses/LICENSE-2.0 | ||
| * <p> | ||
| * 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.openrewrite.staticanalysis; | ||
|
|
||
| import org.openrewrite.*; | ||
| import org.openrewrite.java.JavaIsoVisitor; | ||
| import org.openrewrite.java.MethodMatcher; | ||
| import org.openrewrite.java.search.UsesType; | ||
| import org.openrewrite.java.tree.*; | ||
|
|
||
| import java.util.*; | ||
|
|
||
| public class ReplaceWeekYearWithYear extends Recipe { | ||
| public static final MethodMatcher SIMPLE_DATE_FORMAT_CONSTRUCTOR_MATCHER = new MethodMatcher("java.text.SimpleDateFormat <constructor>(..)"); | ||
| public static final MethodMatcher OF_PATTERN_MATCHER = new MethodMatcher("java.time.format.DateTimeFormatter ofPattern(..)"); | ||
|
|
||
| @Override | ||
| public String getDisplayName() { | ||
| return "Week Year (YYYY) should not be used for date formatting"; | ||
| } | ||
|
|
||
| @Override | ||
| public String getDescription() { | ||
| return "For most dates Week Year (YYYY) and Year (yyyy) yield the same results. However, on the last week of" + | ||
| " December and first week of January Week Year could produce unexpected results."; | ||
| } | ||
|
|
||
| @Override | ||
| public Set<String> getTags() { | ||
| return Collections.singleton("RSPEC-3986"); | ||
| } | ||
|
|
||
| @Override | ||
| public TreeVisitor<?, ExecutionContext> getVisitor() { | ||
| return Preconditions.check( | ||
| Preconditions.or( | ||
| new UsesType<>("java.util.Date", false), | ||
| new UsesType<>("java.time.format.DateTimeFormatter", false), | ||
| new UsesType<>("java.text.SimpleDateFormat", false) | ||
| ), | ||
| new ReplaceWeekYearVisitor() | ||
| ); | ||
| } | ||
|
|
||
| private static class ReplaceWeekYearVisitor extends JavaIsoVisitor<ExecutionContext> { | ||
| @Override | ||
| public J.MethodInvocation visitMethodInvocation(J.MethodInvocation mi, ExecutionContext ctx) { | ||
| if (OF_PATTERN_MATCHER.matches(mi)) { | ||
| getCursor().putMessage("KEY", mi); | ||
| } | ||
|
|
||
| return super.visitMethodInvocation(mi, ctx); | ||
| } | ||
|
|
||
| @Override | ||
| public J.NewClass visitNewClass(J.NewClass nc, ExecutionContext ctx) { | ||
| if (SIMPLE_DATE_FORMAT_CONSTRUCTOR_MATCHER.matches(nc)) { | ||
| getCursor().putMessage("KEY", nc); | ||
| } | ||
|
|
||
| return super.visitNewClass(nc, ctx); | ||
| } | ||
|
|
||
| @Override | ||
| public J.Literal visitLiteral(J.Literal li, ExecutionContext ctx) { | ||
| if (li.getValue() instanceof String) { | ||
| Cursor c = getCursor().dropParentWhile(is -> is instanceof J.Parentheses || !(is instanceof Tree)); | ||
| if (c.getMessage("KEY") != null) { | ||
| String value = li.getValueSource(); | ||
|
|
||
| if (value == null) { | ||
| return li; | ||
| } | ||
|
|
||
| String newValue = replaceY(value); | ||
|
|
||
| return li.withValueSource(newValue).withValue(newValue); | ||
|
||
| } | ||
| } | ||
|
|
||
| return li; | ||
| } | ||
|
|
||
| public static String replaceY(String input) { | ||
| StringBuilder output = new StringBuilder(); | ||
| boolean insideQuotes = false; | ||
|
|
||
| for (int i = 0; i < input.length(); i++) { | ||
| char currentChar = input.charAt(i); | ||
| char nextChar = (i < input.length() - 1) ? input.charAt(i + 1) : '\0'; | ||
|
|
||
| if (currentChar == '\'') { | ||
| insideQuotes = !insideQuotes; | ||
| output.append(currentChar); | ||
| } else if (currentChar == 'Y' && !insideQuotes) { | ||
| output.append('y'); | ||
| } else if (currentChar == 'Y' && nextChar == '\'') { | ||
| output.append(currentChar); | ||
| } else { | ||
| output.append(currentChar); | ||
| } | ||
| } | ||
|
|
||
| return output.toString(); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I suspect that when we do the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah I see, ya I can add a check to see if there are any changes actually being made. |
||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,241 @@ | ||
| /* | ||
| * Copyright 2023 the original author or authors. | ||
| * <p> | ||
| * Licensed 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 | ||
| * <p> | ||
| * https://www.apache.org/licenses/LICENSE-2.0 | ||
| * <p> | ||
| * 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.openrewrite.staticanalysis; | ||
|
|
||
| import org.junit.jupiter.api.Test; | ||
| import org.junitpioneer.jupiter.Issue; | ||
| import org.openrewrite.DocumentExample; | ||
| import org.openrewrite.test.RecipeSpec; | ||
| import org.openrewrite.test.RewriteTest; | ||
|
|
||
| import static org.openrewrite.java.Assertions.java; | ||
|
|
||
| @SuppressWarnings("SuspiciousDateFormat") | ||
| class ReplaceWeekYearWithYearTest implements RewriteTest { | ||
| @Override | ||
| public void defaults(RecipeSpec spec) { | ||
| spec | ||
| .recipe(new ReplaceWeekYearWithYear()); | ||
| } | ||
|
|
||
| @Test | ||
| @DocumentExample | ||
| @Issue("https://github.com/openrewrite/rewrite-static-analysis/issues/58") | ||
| void changeSimpleDateFormat() { | ||
| //language=java | ||
| rewriteRun( | ||
| java( | ||
| """ | ||
| import java.text.SimpleDateFormat; | ||
| import java.util.Date; | ||
|
|
||
| class Test { | ||
| public void formatDate() { | ||
| Date date = new SimpleDateFormat("yyyy/MM/dd").parse("2015/12/31"); | ||
| String result = new SimpleDateFormat("YYYY/MM/dd").format(date); | ||
| } | ||
| } | ||
| """, | ||
| """ | ||
| import java.text.SimpleDateFormat; | ||
| import java.util.Date; | ||
|
|
||
| class Test { | ||
| public void formatDate() { | ||
| Date date = new SimpleDateFormat("yyyy/MM/dd").parse("2015/12/31"); | ||
| String result = new SimpleDateFormat("yyyy/MM/dd").format(date); | ||
| } | ||
| } | ||
| """ | ||
| ) | ||
| ); | ||
| } | ||
|
|
||
| @Test | ||
| void worksWithOfPatternFormatter() { | ||
| //language=java | ||
| rewriteRun( | ||
| java( | ||
| """ | ||
| import java.text.SimpleDateFormat; | ||
| import java.time.format.DateTimeFormatter; | ||
| import java.util.Date; | ||
|
|
||
| class Test { | ||
| public void formatDate() { | ||
| Date date = new SimpleDateFormat("yyyy/MM/dd").parse("2015/12/31"); | ||
| String result = DateTimeFormatter.ofPattern("YYYY/MM/dd").format(date.toInstant()); | ||
| } | ||
| } | ||
| """, | ||
| """ | ||
| import java.text.SimpleDateFormat; | ||
| import java.time.format.DateTimeFormatter; | ||
| import java.util.Date; | ||
|
|
||
| class Test { | ||
| public void formatDate() { | ||
| Date date = new SimpleDateFormat("yyyy/MM/dd").parse("2015/12/31"); | ||
| String result = DateTimeFormatter.ofPattern("yyyy/MM/dd").format(date.toInstant()); | ||
| } | ||
| } | ||
| """ | ||
| ) | ||
| ); | ||
| } | ||
|
|
||
| @Test | ||
| void worksWithYYUses() { | ||
| //language=java | ||
| rewriteRun( | ||
| java( | ||
| """ | ||
| import java.text.SimpleDateFormat; | ||
| import java.time.format.DateTimeFormatter; | ||
| import java.util.Date; | ||
|
|
||
| class Test { | ||
| public void formatDate() { | ||
| Date date = new SimpleDateFormat("yy/MM/dd").parse("2015/12/31"); | ||
| String result = DateTimeFormatter.ofPattern("YY/MM/dd").format(date.toInstant()); | ||
| } | ||
| } | ||
| """, | ||
| """ | ||
| import java.text.SimpleDateFormat; | ||
| import java.time.format.DateTimeFormatter; | ||
| import java.util.Date; | ||
|
|
||
| class Test { | ||
| public void formatDate() { | ||
| Date date = new SimpleDateFormat("yy/MM/dd").parse("2015/12/31"); | ||
| String result = DateTimeFormatter.ofPattern("yy/MM/dd").format(date.toInstant()); | ||
| } | ||
| } | ||
| """ | ||
| ) | ||
| ); | ||
| } | ||
|
|
||
| @Test | ||
| void onlyRunsWhenFormatAndDateTypesAreUsed() { | ||
| //language=java | ||
| rewriteRun( | ||
| java( | ||
| """ | ||
| class Test { | ||
| public static void main(String[] args) { | ||
| String pattern = "YYYY/MM/dd"; | ||
| System.out.println(pattern); | ||
| } | ||
| } | ||
| """ | ||
| ) | ||
| ); | ||
| } | ||
|
|
||
| @Test | ||
| void standaloneNewClassCall() { | ||
| //language=java | ||
| rewriteRun( | ||
| java( | ||
| """ | ||
| import java.text.SimpleDateFormat; | ||
| import java.util.Date; | ||
|
|
||
| class Test { | ||
| public void formatDate() { | ||
| SimpleDateFormat format = new SimpleDateFormat("YYYY-MM-dd"); | ||
| Date date = format.parse("2015/12/31"); | ||
| } | ||
| } | ||
| """, | ||
| """ | ||
| import java.text.SimpleDateFormat; | ||
| import java.util.Date; | ||
|
|
||
| class Test { | ||
| public void formatDate() { | ||
| SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd"); | ||
| Date date = format.parse("2015/12/31"); | ||
| } | ||
| } | ||
| """ | ||
| ) | ||
| ); | ||
| } | ||
| @Test | ||
| void patternUsesSingleQuotes() { | ||
| //language=java | ||
| rewriteRun( | ||
| java( | ||
| """ | ||
| import java.text.SimpleDateFormat; | ||
| import java.util.Date; | ||
|
|
||
| class Test { | ||
| public void formatDate() { | ||
| SimpleDateFormat format = new SimpleDateFormat("'Your date is:' YYYY-MM-dd"); | ||
| Date date = format.parse("2015/12/31"); | ||
| } | ||
| } | ||
| """, | ||
| """ | ||
| import java.text.SimpleDateFormat; | ||
| import java.util.Date; | ||
|
|
||
| class Test { | ||
| public void formatDate() { | ||
| SimpleDateFormat format = new SimpleDateFormat("'Your date is:' yyyy-MM-dd"); | ||
| Date date = format.parse("2015/12/31"); | ||
| } | ||
| } | ||
| """ | ||
| ) | ||
| ); | ||
| } | ||
|
|
||
| @Test | ||
| void patternUsesMultipleSingleQuotes() { | ||
| //language=java | ||
| rewriteRun( | ||
| java( | ||
| """ | ||
| import java.text.SimpleDateFormat; | ||
| import java.util.Date; | ||
|
|
||
| class Test { | ||
| public void formatDate() { | ||
| SimpleDateFormat format = new SimpleDateFormat("'Your date is:' YYYY-MM-dd, 'yy'"); | ||
| Date date = format.parse("2015/12/31"); | ||
| } | ||
| } | ||
| """, | ||
| """ | ||
| import java.text.SimpleDateFormat; | ||
| import java.util.Date; | ||
|
|
||
| class Test { | ||
| public void formatDate() { | ||
| SimpleDateFormat format = new SimpleDateFormat("'Your date is:' yyyy-MM-dd, 'yy'"); | ||
| Date date = format.parse("2015/12/31"); | ||
| } | ||
| } | ||
| """ | ||
| ) | ||
| ); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Detail: I suggest declaring these constants as private. API surface area is always easy to extend, but more difficult to shrink.