From ceb53b9e412f67a917e29f288cf5098d1d8a0a02 Mon Sep 17 00:00:00 2001 From: Eric Mays Date: Sun, 1 Feb 2026 15:50:59 -0500 Subject: [PATCH 1/4] Skip tests on publish to github --- .github/workflows/maven-publish.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/maven-publish.yml b/.github/workflows/maven-publish.yml index 3857d1d..2cd17e5 100644 --- a/.github/workflows/maven-publish.yml +++ b/.github/workflows/maven-publish.yml @@ -28,6 +28,7 @@ jobs: - name: Publish to GitHub Packages Apache Maven run: | mvn deploy -B \ + -DskipTests \ -Pgithub-packages \ -Pgpg-sign \ -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn From dc499d1de98b980df0d963ab092b7c72f684c745 Mon Sep 17 00:00:00 2001 From: Eric Mays Date: Sun, 8 Feb 2026 11:11:03 -0500 Subject: [PATCH 2/4] Update parent to snapshot --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index daa4631..2726280 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ io.github.emays parent - 0.0.8 + 0.0.9-SNAPSHOT From e3da02c0a38b358592eab9c8f560f85b64979b3a Mon Sep 17 00:00:00 2001 From: Eric Mays Date: Sat, 14 Feb 2026 15:15:26 -0500 Subject: [PATCH 3/4] Add (moved) TimeUtil --- src/main/java/com/mays/util/TimeUtil.java | 103 +++++++++++++++++ src/test/java/com/mays/util/TimeUtilTest.java | 105 ++++++++++++++++++ 2 files changed, 208 insertions(+) create mode 100644 src/main/java/com/mays/util/TimeUtil.java create mode 100644 src/test/java/com/mays/util/TimeUtilTest.java diff --git a/src/main/java/com/mays/util/TimeUtil.java b/src/main/java/com/mays/util/TimeUtil.java new file mode 100644 index 0000000..3088f19 --- /dev/null +++ b/src/main/java/com/mays/util/TimeUtil.java @@ -0,0 +1,103 @@ +package com.mays.util; + +import java.time.DayOfWeek; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalAdjusters; + +public class TimeUtil { + + public static boolean equalYearMonth(LocalDate d1, LocalDate d2) { + return d1.getYear() == d2.getYear() && d1.getMonthValue() == d2.getMonthValue(); + } + + public static String format(TemporalAccessor temporal, String pattern) { + return DateTimeFormatter.ofPattern(pattern).format(temporal); + } + + // TODO Should use + // https://stackoverflow.com/questions/1060479/determine-whether-daylight-savings-time-dst-is-active-in-java-for-a-specified + + // https://www.timeanddate.com/time/change/usa + + // Daylight Saving Time (DST) in most of the United States + + // starts on the 2nd Sunday in March + + public static LocalDate getPossibleDstStart(int year) { + return LocalDate.of(year, 3, 1).with(TemporalAdjusters.firstInMonth(DayOfWeek.SUNDAY)).plusWeeks(1); + } + + public static boolean isDstStart(LocalDate date) { + return date.equals(getPossibleDstStart(date.getYear())); + } + + public static boolean isDstStart(LocalDate date, ZoneId tz) { + return isDstStart(date) + && tz.getRules().isDaylightSavings(ZonedDateTime.of(date, LocalTime.NOON, tz).toInstant()); + } + + public static LocalDate getDstStart(int year, ZoneId tz) { + LocalDate d = getPossibleDstStart(year); + if (isDstStart(d, tz)) + return d; + return null; + } + + // ends on the 1st Sunday in November + + public static LocalDate getPossibleDstEnd(int year) { + return LocalDate.of(year, 11, 1).with(TemporalAdjusters.firstInMonth(DayOfWeek.SUNDAY)); + } + + public static boolean isDstEnd(LocalDate date) { + return date.equals(getPossibleDstEnd(date.getYear())); + } + + public static boolean isDstEnd(LocalDate date, ZoneId tz) { + return isDstEnd(date) + && tz.getRules().isDaylightSavings(ZonedDateTime.of(date.minusDays(1), LocalTime.NOON, tz).toInstant()); + } + + public static LocalDate getDstEnd(int year, ZoneId tz) { + LocalDate d = getPossibleDstEnd(year); + if (isDstEnd(d, tz)) + return d; + return null; + } + + private static String getDstText(String tag, LocalDate dst, ZonedDateTime time, int lower, int upper) { + if (dst == null) + return null; + long days = time.toLocalDate().until(dst, ChronoUnit.DAYS); + if (days < lower || days > upper) + return null; + if (days < -1) + return tag + "ed " + -days + " days ago"; + if (days > 1) + return tag + "s in " + days + " days"; + if (days == -1) + return tag + "ed yesterday"; + if (days == 0) + return tag + "s today"; + if (days == 1) + return tag + "s tomorrow"; + throw new IllegalStateException(time + " " + lower + " " + upper); + } + + public static String getDstStartText(ZonedDateTime time, int lower, int upper) { + LocalDate dst = TimeUtil.getDstStart(time.getYear(), time.getZone()); + return getDstText("Start", dst, time, lower, upper); + } + + public static String getDstEndText(ZonedDateTime time, int lower, int upper) { + LocalDate dst = TimeUtil.getDstEnd(time.getYear(), time.getZone()); + return getDstText("End", dst, time, lower, upper); + } + +} diff --git a/src/test/java/com/mays/util/TimeUtilTest.java b/src/test/java/com/mays/util/TimeUtilTest.java new file mode 100644 index 0000000..290e742 --- /dev/null +++ b/src/test/java/com/mays/util/TimeUtilTest.java @@ -0,0 +1,105 @@ +package com.mays.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.time.LocalDate; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class TimeUtilTest { + + @SuppressWarnings("unused") + private static final Logger logger = LoggerFactory.getLogger(TimeUtilTest.class); + + private static final ZoneId TZ = ZoneId.of("America/New_York"); + + private Stream genDays() { + LocalDate j1 = LocalDate.of(2024, 1, 1); + return IntStream.range(0, j1.lengthOfYear()).mapToObj(d -> j1.plusDays(d)); + } + + private final LocalDate start = LocalDate.of(2024, 3, 10); + private final LocalDate end = LocalDate.of(2024, 11, 3); + + @Test + public void isDst() { +// TimeZoneUtil.getInstance().getZoneIds().stream().map(ZoneId::getId).sorted().forEach(logger::info); +// ZoneId.getAvailableZoneIds().stream().filter(x -> x.contains("America")).sorted().forEach(logger::info); +// genDays().map(LocalDate::toString).forEach(logger::info); + assertEquals(366, genDays().count()); + assertEquals(1, genDays().filter(d -> TimeUtil.isDstStart(d)).count()); + assertEquals(1, genDays().filter(d -> TimeUtil.isDstEnd(d)).count()); + assertEquals(1, genDays().filter(d -> TimeUtil.isDstStart(d, TZ)).count()); + assertEquals(1, genDays().filter(d -> TimeUtil.isDstEnd(d, TZ)).count()); + assertTrue(TimeUtil.isDstStart(start)); + assertTrue(TimeUtil.isDstStart(start, TZ)); + assertTrue(TimeUtil.isDstEnd(end)); + assertTrue(TimeUtil.isDstEnd(end, TZ)); + assertEquals(start, TimeUtil.getDstStart(start.getYear(), TZ)); + assertEquals(end, TimeUtil.getDstEnd(end.getYear(), TZ)); + } + + @Test + public void noDst() { + ZoneId tz = ZoneId.of("America/Phoenix"); + assertEquals(0, genDays().filter(d -> TimeUtil.isDstStart(d, tz)).count()); + assertEquals(0, genDays().filter(d -> TimeUtil.isDstEnd(d, tz)).count()); + assertNull(TimeUtil.getDstStart(start.getYear(), tz)); + assertNull(TimeUtil.getDstEnd(end.getYear(), tz)); + } + + @Test + public void dstStartText() { + for (int i = -50; i < 10; i++) { + ZonedDateTime time = ZonedDateTime.of(start.plusDays(i).atStartOfDay(), TZ); + String text = TimeUtil.getDstStartText(time, -7, 45); + if (i < -45 || i > 7) { + assertEquals(null, text, i + " " + text); + continue; + } + if (i < -1) + assertEquals("Starts in " + -i + " days", text, "" + i); + if (i == -1) + assertEquals("Starts tomorrow", text, "" + i); + if (i == 0) + assertEquals("Starts today", text, "" + i); + if (i == 1) + assertEquals("Started yesterday", text, "" + i); + if (i > 1) + assertEquals("Started " + i + " days ago", text, "" + i); + + } + } + + @Test + public void dstEndText() { + for (int i = -50; i < 10; i++) { + ZonedDateTime time = ZonedDateTime.of(end.plusDays(i).atStartOfDay(), TZ); + String text = TimeUtil.getDstEndText(time, -7, 45); + if (i < -45 || i > 7) { + assertEquals(null, text, i + " " + text); + continue; + } + if (i < -1) + assertEquals("Ends in " + -i + " days", text, "" + i); + if (i == -1) + assertEquals("Ends tomorrow", text, "" + i); + if (i == 0) + assertEquals("Ends today", text, "" + i); + if (i == 1) + assertEquals("Ended yesterday", text, "" + i); + if (i > 1) + assertEquals("Ended " + i + " days ago", text, "" + i); + + } + } + +} From b75817b6aff1b50c92938dc5fd0f11a42d80d1ee Mon Sep 17 00:00:00 2001 From: Eric Mays Date: Sat, 14 Feb 2026 15:54:38 -0500 Subject: [PATCH 4/4] Activate github-repo profile in publish to Github --- .github/workflows/maven-publish.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/maven-publish.yml b/.github/workflows/maven-publish.yml index 2cd17e5..c191f67 100644 --- a/.github/workflows/maven-publish.yml +++ b/.github/workflows/maven-publish.yml @@ -28,6 +28,7 @@ jobs: - name: Publish to GitHub Packages Apache Maven run: | mvn deploy -B \ + -Pgithub-repo \ -DskipTests \ -Pgithub-packages \ -Pgpg-sign \