Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/maven-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ jobs:
- name: Publish to GitHub Packages Apache Maven
run: |
mvn deploy -B \
-Pgithub-repo \
-DskipTests \
-Pgithub-packages \
-Pgpg-sign \
-Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>io.github.emays</groupId>
<artifactId>parent</artifactId>
<version>0.0.8</version>
<version>0.0.9-SNAPSHOT</version>
<relativePath />
</parent>

Expand Down
103 changes: 103 additions & 0 deletions src/main/java/com/mays/util/TimeUtil.java
Original file line number Diff line number Diff line change
@@ -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);
}

}
105 changes: 105 additions & 0 deletions src/test/java/com/mays/util/TimeUtilTest.java
Original file line number Diff line number Diff line change
@@ -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<LocalDate> 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);

}
}

}