Skip to content

Commit cf4caaf

Browse files
authored
Merge pull request #104 from Schlaumeier5/10-improve-schoolclass-weeks-functionality
Added Holiday api usage
2 parents 4bba2f3 + 3a53afd commit cf4caaf

File tree

3 files changed

+253
-0
lines changed

3 files changed

+253
-0
lines changed

src/main/java/de/igslandstuhl/database/Application.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import de.igslandstuhl.database.api.SerializationException;
88
import de.igslandstuhl.database.api.Subject;
99
import de.igslandstuhl.database.api.Topic;
10+
import de.igslandstuhl.database.holidays.Holiday;
1011
import de.igslandstuhl.database.server.Server;
1112
import de.igslandstuhl.database.server.commands.Command;
1213
import de.igslandstuhl.database.utils.CommandLineUtils;
@@ -91,6 +92,9 @@ public Topic[] readFile(String file) throws SerializationException, SQLException
9192
public static void main(String[] args) throws Exception {
9293
instance = new Application(args);
9394
Server.getInstance().getConnection().createTables();
95+
96+
Holiday.setupCurrentSchoolYear();
97+
9498
if (getInstance().runsWebServer()) {
9599
Server.getInstance().getWebServer().start();
96100
}
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
package de.igslandstuhl.database.holidays;
2+
3+
import java.io.IOException;
4+
import java.net.URISyntaxException;
5+
import java.net.URL;
6+
import java.net.http.HttpClient;
7+
import java.net.http.HttpRequest;
8+
import java.net.http.HttpResponse;
9+
import java.sql.SQLException;
10+
import java.time.Instant;
11+
import java.time.LocalDate;
12+
import java.time.ZoneId;
13+
import java.time.ZoneOffset;
14+
import java.time.format.DateTimeFormatter;
15+
import java.util.Arrays;
16+
import java.util.List;
17+
import java.util.Map;
18+
19+
import com.google.gson.Gson;
20+
import com.google.gson.reflect.TypeToken;
21+
22+
import de.igslandstuhl.database.api.SchoolYear;
23+
24+
public final class Holiday {
25+
private static final HttpClient HTTP_CLIENT = HttpClient.newHttpClient();
26+
private static final String API_URL = "https://www.mehr-schulferien.de/api/v2.1/schools/66849-integrierte-gesamtschule-am-na/periods";
27+
private static final String SUMMER_HOLIDAY_ID = "Sommer";
28+
29+
private final int id;
30+
private final String name;
31+
private final Instant startsOn, endsOn;
32+
private final int locationId;
33+
private final boolean publicHoliday, schoolVacation;
34+
35+
public Holiday(int id, String name, Instant startsOn, Instant endsOn, int locationId, boolean publicHoliday,
36+
boolean schoolVacation) {
37+
this.id = id;
38+
this.name = name;
39+
this.startsOn = startsOn;
40+
this.endsOn = endsOn;
41+
this.locationId = locationId;
42+
this.publicHoliday = publicHoliday;
43+
this.schoolVacation = schoolVacation;
44+
}
45+
46+
public int getId() {
47+
return id;
48+
}
49+
public Instant getStart() {
50+
return startsOn;
51+
}
52+
public Instant getEnd() {
53+
return endsOn;
54+
}
55+
public int getLocationId() {
56+
return locationId;
57+
}
58+
public boolean isPublicHoliday() {
59+
return publicHoliday;
60+
}
61+
public boolean isSchoolVacation() {
62+
return schoolVacation;
63+
}
64+
public String getName() {
65+
return name;
66+
}
67+
68+
@Override
69+
public String toString() {
70+
return "Holiday [id=" + id + ", name=" + name + ", startsOn=" + startsOn + ", endsOn=" + endsOn
71+
+ ", locationId=" + locationId + ", publicHoliday=" + publicHoliday + ", schoolVacation="
72+
+ schoolVacation + "]";
73+
}
74+
75+
private static int readInt(Map<String, Object> map, String key) {
76+
return ((Number)map.get(key)).intValue();
77+
}
78+
private static boolean readBool(Map<String, Object> map, String key) {
79+
return (Boolean) map.get(key);
80+
}
81+
private static Instant readInstantStart(Map<String, Object> map, String key) {
82+
String date = (String) map.get(key);
83+
return LocalDate.parse(date).atStartOfDay(ZoneOffset.UTC).toInstant();
84+
}
85+
private static Instant readInstantEnd(Map<String, Object> map, String key) {
86+
String date = (String) map.get(key);
87+
return LocalDate.parse(date).atTime(23, 59, 59).toInstant(ZoneOffset.UTC);
88+
}
89+
private static String readString(Map<String, Object> map, String key) {
90+
return (String) map.get(key);
91+
}
92+
93+
public static Holiday fromMap(Map<String, Object> jsonMap) {
94+
int id = readInt(jsonMap, "id");
95+
String name = readString(jsonMap, "name");
96+
Instant startsOn = readInstantStart(jsonMap, "starts_on");
97+
Instant endsOn = readInstantEnd(jsonMap, "ends_on");
98+
int locationId = readInt(jsonMap, "location_id");
99+
boolean isPublicHoliday = readBool(jsonMap, "is_public_holiday");
100+
boolean isSchoolVacation = readBool(jsonMap, "is_school_vacation");
101+
102+
return new Holiday(id, name, startsOn, endsOn, locationId, isPublicHoliday, isSchoolVacation);
103+
104+
}
105+
public static Holiday fromJson(String json) {
106+
Gson gson = new Gson();
107+
java.lang.reflect.Type mapType = new TypeToken<Map<String, Object>>(){}.getType();
108+
Map<String, Object> jsonMap = gson.fromJson(json, mapType);
109+
return fromMap(jsonMap);
110+
}
111+
112+
public static List<Holiday> readList(String json) {
113+
Gson gson = new Gson();
114+
java.lang.reflect.Type listType = new TypeToken<List<Map<String,Object>>>(){}.getType();
115+
List<Map<String,Object>> list = gson.fromJson(json, listType);
116+
return list.stream().map(Holiday::fromMap).toList();
117+
}
118+
119+
public static Holiday[] holidaysInterval(Instant start, Instant end) {
120+
String startIso = start.atZone(ZoneId.of("UTC")).toLocalDate().format(DateTimeFormatter.ISO_DATE);
121+
String endIso = end.atZone(ZoneId.of("UTC")).toLocalDate().format(DateTimeFormatter.ISO_DATE);
122+
try {
123+
URL url = new URL(API_URL + "?start_date=" + startIso + "&end_date=" + endIso);
124+
HttpRequest request = HttpRequest.newBuilder()
125+
.uri(url.toURI())
126+
.GET()
127+
.build();
128+
129+
HttpResponse<String> response = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString());
130+
131+
if (response.statusCode() != 200) throw new IllegalStateException("Server responded with Status code " + response.statusCode());
132+
133+
String body = response.body();
134+
String list = "[" + body.split("\\[")[1].split("\\]")[0] + "]";
135+
List<Holiday> holidays = readList(list);
136+
Holiday[] holidayArr = new Holiday[holidays.size()];
137+
return holidays.toArray(holidayArr);
138+
} catch (URISyntaxException | IOException e) {
139+
throw new IllegalStateException(e);
140+
} catch (InterruptedException e) {
141+
e.printStackTrace();
142+
throw new IllegalStateException(e);
143+
}
144+
}
145+
146+
public static Holiday getSummerHoliday(Instant timeIntervalStart, Instant timeIntervalEnd) {
147+
Holiday[] lastYearHolidays = holidaysInterval(timeIntervalStart, timeIntervalEnd);
148+
return Arrays.stream(lastYearHolidays)
149+
.filter((holiday) -> (holiday.getName().equals(SUMMER_HOLIDAY_ID)))
150+
.sorted((h1, h2) -> h1.getEnd().compareTo(h2.getEnd()))
151+
.findFirst().get();
152+
}
153+
public static Holiday getLastSummerHoliday() {
154+
Instant now = Instant.now();
155+
Instant yearAgo = now.minusSeconds(31536000);
156+
return getSummerHoliday(yearAgo, now);
157+
}
158+
public static Holiday getNextSummerHoliday() {
159+
Instant now = Instant.now();
160+
Instant inAYear = now.plusSeconds(31536000);
161+
return getSummerHoliday(now, inAYear);
162+
}
163+
public static int getTotalWeeks() {
164+
return (int) SchoolWeek.getAll(getLastSummerHoliday().getEnd(), getNextSummerHoliday().getStart(), ZoneId.of("UTC")).stream().filter(SchoolWeek::noSchoolUTC).count();
165+
}
166+
public static int getActualWeek() {
167+
return (int) SchoolWeek.getAll(getLastSummerHoliday().getEnd(), Instant.now(), ZoneId.of("UTC")).stream().filter(SchoolWeek::noSchoolUTC).count();
168+
}
169+
public static void setupCurrentSchoolYear() throws SQLException {
170+
String name = getLastSummerHoliday().getEnd().toString().substring(0, 4) + "/" + getNextSummerHoliday().getStart().toString().substring(0, 4);
171+
int totalWeeks = getTotalWeeks();
172+
int actualWeek = getActualWeek();
173+
SchoolYear.addSchoolYear(name, totalWeeks, actualWeek);
174+
}
175+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package de.igslandstuhl.database.holidays;
2+
3+
import java.time.DayOfWeek;
4+
import java.time.Instant;
5+
import java.time.LocalDate;
6+
import java.time.LocalTime;
7+
import java.time.ZoneId;
8+
import java.util.ArrayList;
9+
import java.util.Arrays;
10+
import java.util.List;
11+
12+
public record SchoolWeek(Instant start, Instant end) {
13+
public static SchoolWeek of(Instant instant, ZoneId zone) {
14+
LocalDate date = instant.atZone(zone).toLocalDate();
15+
16+
// Move to Monday of the same week
17+
LocalDate monday = date.with(java.time.DayOfWeek.MONDAY);
18+
LocalDate friday = monday.plusDays(4);
19+
20+
Instant startInstant = monday.atStartOfDay(zone).toInstant();
21+
Instant endInstant = friday.atTime(LocalTime.MAX).atZone(zone).toInstant();
22+
23+
return new SchoolWeek(startInstant, endInstant);
24+
}
25+
public static List<SchoolWeek> getAll(Instant start, Instant end, ZoneId zone) {
26+
List<SchoolWeek> weeks = new ArrayList<>();
27+
SchoolWeek current = of(start, zone);
28+
29+
while (!current.start().isAfter(end)) {
30+
weeks.add(current);
31+
current = current.nextWeek(zone);
32+
}
33+
34+
return weeks;
35+
}
36+
public SchoolWeek nextWeek(ZoneId zone) {
37+
// Find Monday of the next week
38+
LocalDate nextMonday = this.start.atZone(zone).toLocalDate().plusWeeks(1);
39+
LocalDate nextFriday = nextMonday.plusDays(4);
40+
41+
Instant startInstant = nextMonday.atStartOfDay(zone).toInstant();
42+
Instant endInstant = nextFriday.atTime(LocalTime.MAX).atZone(zone).toInstant();
43+
44+
return new SchoolWeek(startInstant, endInstant);
45+
}
46+
public SchoolWeek lastWeek(ZoneId zone) {
47+
// Find Monday of the next week
48+
LocalDate nextMonday = this.start.atZone(zone).toLocalDate().minusWeeks(1);
49+
LocalDate nextFriday = nextMonday.plusDays(4);
50+
51+
Instant startInstant = nextMonday.atStartOfDay(zone).toInstant();
52+
Instant endInstant = nextFriday.atTime(LocalTime.MAX).atZone(zone).toInstant();
53+
54+
return new SchoolWeek(startInstant, endInstant);
55+
}
56+
public Holiday[] getHolidays() {
57+
return Holiday.holidaysInterval(start, end);
58+
}
59+
public boolean currentWeek(ZoneId zone) {
60+
return start.isBefore(Instant.now()) && nextWeek(zone).start.isAfter(Instant.now());
61+
}
62+
private boolean schoolOnDay(Holiday[] holidays, DayOfWeek day, ZoneId zone) {
63+
Instant weekday = start.atZone(zone).toLocalDate().with(day).atTime(12, 0).atZone(zone).toInstant();
64+
return Arrays.stream(holidays).anyMatch((h) -> h.getStart().isBefore(weekday) && h.getEnd().isAfter(weekday));
65+
}
66+
public boolean hasSchool(ZoneId zone) {
67+
Holiday[] holidays = getHolidays();
68+
if (holidays.length == 0) return false;
69+
return Arrays.stream(DayOfWeek.values()).filter((d) -> d != DayOfWeek.SATURDAY && d != DayOfWeek.SUNDAY).allMatch((d) -> schoolOnDay(holidays, d, zone));
70+
}
71+
public boolean noSchoolUTC() {
72+
return !hasSchool(ZoneId.of("UTC"));
73+
}
74+
}

0 commit comments

Comments
 (0)