{"id":22941,"date":"2025-10-26T11:28:42","date_gmt":"2025-10-26T10:28:42","guid":{"rendered":"http:\/\/blog.wenzlaff.de\/?p=22941"},"modified":"2025-11-01T19:49:55","modified_gmt":"2025-11-01T18:49:55","slug":"1-stunde-gewonnen-beim-zeitumstellung-dann-mal-ein-rest-service-mit-quarkus-zu-ermittelung-der-tage-bis-zur-rente","status":"publish","type":"post","link":"http:\/\/blog.wenzlaff.de\/?p=22941","title":{"rendered":"1 Stunde gewonnen beim Zeitumstellung: dann mal ein REST-Service mit Quarkus zu Ermittelung der Tage bis zur Rente"},"content":{"rendered":"<p>Ein fokussiertes Mini\u2011Projekt f\u00fcr die umgestellte Stunde: ein Quarkus\u2011REST\u2011Service, der die verbleibenden Tage bis zur Rente liefert, lokal entwickeln und anschlie\u00dfend auf einem Raspberry Pi deployen.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/blog.wenzlaff.de\/wp-content\/uploads\/2017\/10\/Sommerzeit.png\" alt=\"\" width=\"2732\" height=\"2048\" \/><\/p>\n<p>Idee und Ziel<\/p>\n<p>-Ein kleiner REST\u2011Endpunkt liefert JSON mit: verbleibenden Tagen<\/p>\n<p>&#8211; Projekt: Quarkus, Java 21, RESTEasy Reactive, Jackson. <!--more--><\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/blog.wenzlaff.de\/wp-content\/uploads\/2019\/10\/Quarks-rest-Java.png\" alt=\"\" width=\"5500\" height=\"4000\" \/><\/p>\n<p>-Deployment: fast-jar mit quarkus-run.jar auf dem Raspberry Pi als systemd\u2011Service.<\/p>\n<p>Ok, start, zuerst den Java Endpunkt:<\/p>\n<pre class=\"lang:java decode:true \" >package de.wenzlaff.twcount;\r\n\r\nimport java.time.LocalDate;\r\nimport java.time.temporal.ChronoUnit;\r\n\r\nimport jakarta.ws.rs.GET;\r\nimport jakarta.ws.rs.Path;\r\nimport jakarta.ws.rs.Produces;\r\nimport jakarta.ws.rs.core.MediaType;\r\n\r\n\/**\r\n * REST Endpunkt.\r\n * \r\n * @author Thomas Wenzlaff\r\n *\r\n *\/\r\n@Path(\"\/countdown\")\r\npublic class CountdownResource {\r\n\r\n\tprivate static final LocalDate TARGET_DATE = LocalDate.of(2027, 6, 1);\r\n\r\n\t@GET\r\n\t@Produces(MediaType.APPLICATION_JSON)\r\n\tpublic CountdownResponse getDaysCountdown() {\r\n\t\tLocalDate now = LocalDate.now();\r\n\t\tlong daysRemaining = ChronoUnit.DAYS.between(now, TARGET_DATE);\r\n\r\n\t\t\/\/ Falls das Ziel-Datum bereits vergangen ist, korrigiere auf 0\r\n\t\tif (daysRemaining &lt; 0) {\r\n\t\t\tdaysRemaining = 0;\r\n\t\t}\r\n\r\n\t\treturn new CountdownResponse(daysRemaining);\r\n\t}\r\n}<\/pre>\n<p>Noch ein kleines BE:<\/p>\n<pre class=\"lang:java decode:true \" >package de.wenzlaff.twcount;\r\n\r\n\/**\r\n * BE.\r\n * \r\n * @author Thomas Wenzlaff\r\n *\r\n *\/\r\npublic class CountdownResponse {\r\n\r\n\tprivate long daysRemaining;\r\n\r\n\tpublic CountdownResponse(long daysRemaining) {\r\n\t\tthis.daysRemaining = daysRemaining;\r\n\t}\r\n\r\n\tpublic long getDaysRemaining() {\r\n\t\treturn daysRemaining;\r\n\t}\r\n\r\n\tpublic void setDaysRemaining(long daysRemaining) {\r\n\t\tthis.daysRemaining = daysRemaining;\r\n\t}\r\n}\r\n<\/pre>\n<p>Die pom.xml ist auch nicht gro\u00df:<\/p>\n<pre class=\"nums:false nums-toggle:false minimize:true lang:xhtml decode:true \" >&lt;?xml version=\"1.0\" encoding=\"UTF-8\"?&gt;\r\n&lt;project xmlns=\"http:\/\/maven.apache.org\/POM\/4.0.0\" xmlns:xsi=\"http:\/\/www.w3.org\/2001\/XMLSchema-instance\" xsi:schemaLocation=\"http:\/\/maven.apache.org\/POM\/4.0.0 https:\/\/maven.apache.org\/xsd\/maven-4.0.0.xsd\"&gt;\r\n    &lt;modelVersion&gt;4.0.0&lt;\/modelVersion&gt;\r\n    &lt;groupId&gt;de.wenzlaff.twcount&lt;\/groupId&gt;\r\n    &lt;artifactId&gt;de.wenzlaff.twcount&lt;\/artifactId&gt;\r\n    &lt;version&gt;1.0.0&lt;\/version&gt;\r\n\r\n    &lt;properties&gt;\r\n        &lt;compiler-plugin.version&gt;3.14.0&lt;\/compiler-plugin.version&gt;\r\n        &lt;maven.compiler.release&gt;21&lt;\/maven.compiler.release&gt;\r\n        &lt;project.build.sourceEncoding&gt;UTF-8&lt;\/project.build.sourceEncoding&gt;\r\n        &lt;project.reporting.outputEncoding&gt;UTF-8&lt;\/project.reporting.outputEncoding&gt;\r\n        &lt;quarkus.platform.artifact-id&gt;quarkus-bom&lt;\/quarkus.platform.artifact-id&gt;\r\n        &lt;quarkus.platform.group-id&gt;io.quarkus.platform&lt;\/quarkus.platform.group-id&gt;\r\n        &lt;quarkus.platform.version&gt;3.28.5&lt;\/quarkus.platform.version&gt;\r\n        &lt;skipITs&gt;true&lt;\/skipITs&gt;\r\n        &lt;surefire-plugin.version&gt;3.5.4&lt;\/surefire-plugin.version&gt;\r\n    &lt;\/properties&gt;\r\n\r\n    &lt;dependencyManagement&gt;\r\n        &lt;dependencies&gt;\r\n            &lt;dependency&gt;\r\n                &lt;groupId&gt;${quarkus.platform.group-id}&lt;\/groupId&gt;\r\n                &lt;artifactId&gt;${quarkus.platform.artifact-id}&lt;\/artifactId&gt;\r\n                &lt;version&gt;${quarkus.platform.version}&lt;\/version&gt;\r\n                &lt;type&gt;pom&lt;\/type&gt;\r\n                &lt;scope&gt;import&lt;\/scope&gt;\r\n            &lt;\/dependency&gt;\r\n        &lt;\/dependencies&gt;\r\n    &lt;\/dependencyManagement&gt;\r\n    &lt;dependencies&gt;\r\n        &lt;dependency&gt;\r\n            &lt;groupId&gt;io.quarkus&lt;\/groupId&gt;\r\n            &lt;artifactId&gt;quarkus-rest&lt;\/artifactId&gt;\r\n        &lt;\/dependency&gt;    \r\n\t\t&lt;dependency&gt;\r\n\t\t    &lt;groupId&gt;io.quarkus&lt;\/groupId&gt;\r\n\t\t    &lt;artifactId&gt;quarkus-rest-jackson&lt;\/artifactId&gt;\t   \r\n\t\t&lt;\/dependency&gt;\r\n\t\t&lt;dependency&gt;\r\n            &lt;groupId&gt;io.quarkus&lt;\/groupId&gt;\r\n            &lt;artifactId&gt;quarkus-arc&lt;\/artifactId&gt;\r\n        &lt;\/dependency&gt;\r\n        \r\n        &lt;dependency&gt;  \r\n\t\t    &lt;groupId&gt;io.quarkus&lt;\/groupId&gt;  \r\n\t\t    &lt;artifactId&gt;quarkus-smallrye-openapi&lt;\/artifactId&gt;  \r\n\t\t&lt;\/dependency&gt;  \r\n\r\n        &lt;dependency&gt;\r\n            &lt;groupId&gt;io.quarkus&lt;\/groupId&gt;\r\n            &lt;artifactId&gt;quarkus-junit5&lt;\/artifactId&gt;\r\n            &lt;scope&gt;test&lt;\/scope&gt;\r\n        &lt;\/dependency&gt;\r\n        &lt;dependency&gt;\r\n            &lt;groupId&gt;io.rest-assured&lt;\/groupId&gt;\r\n            &lt;artifactId&gt;rest-assured&lt;\/artifactId&gt;\r\n            &lt;scope&gt;test&lt;\/scope&gt;\r\n        &lt;\/dependency&gt;\r\n        \r\n    &lt;\/dependencies&gt;\r\n\r\n    &lt;build&gt;\r\n        &lt;plugins&gt;\r\n            &lt;plugin&gt;\r\n                &lt;groupId&gt;${quarkus.platform.group-id}&lt;\/groupId&gt;\r\n                &lt;artifactId&gt;quarkus-maven-plugin&lt;\/artifactId&gt;\r\n                &lt;version&gt;${quarkus.platform.version}&lt;\/version&gt;\r\n                &lt;extensions&gt;true&lt;\/extensions&gt;\r\n                &lt;executions&gt;\r\n                    &lt;execution&gt;\r\n                        &lt;goals&gt;\r\n                            &lt;goal&gt;build&lt;\/goal&gt;\r\n                            &lt;goal&gt;generate-code&lt;\/goal&gt;\r\n                            &lt;goal&gt;generate-code-tests&lt;\/goal&gt;\r\n                            &lt;goal&gt;native-image-agent&lt;\/goal&gt;\r\n                        &lt;\/goals&gt;\r\n                    &lt;\/execution&gt;\r\n                &lt;\/executions&gt;\r\n            &lt;\/plugin&gt;\r\n            &lt;plugin&gt;\r\n                &lt;artifactId&gt;maven-compiler-plugin&lt;\/artifactId&gt;\r\n                &lt;version&gt;${compiler-plugin.version}&lt;\/version&gt;\r\n                &lt;configuration&gt;\r\n                    &lt;parameters&gt;true&lt;\/parameters&gt;\r\n                &lt;\/configuration&gt;\r\n            &lt;\/plugin&gt;\r\n            &lt;plugin&gt;\r\n                &lt;artifactId&gt;maven-surefire-plugin&lt;\/artifactId&gt;\r\n                &lt;version&gt;${surefire-plugin.version}&lt;\/version&gt;\r\n                &lt;configuration&gt;\r\n                    &lt;argLine&gt;--add-opens java.base\/java.lang=ALL-UNNAMED&lt;\/argLine&gt;\r\n                    &lt;systemPropertyVariables&gt;\r\n                        &lt;java.util.logging.manager&gt;org.jboss.logmanager.LogManager&lt;\/java.util.logging.manager&gt;\r\n                        &lt;maven.home&gt;${maven.home}&lt;\/maven.home&gt;\r\n                    &lt;\/systemPropertyVariables&gt;\r\n                &lt;\/configuration&gt;\r\n            &lt;\/plugin&gt;\r\n            &lt;plugin&gt;\r\n                &lt;artifactId&gt;maven-failsafe-plugin&lt;\/artifactId&gt;\r\n                &lt;version&gt;${surefire-plugin.version}&lt;\/version&gt;\r\n                &lt;executions&gt;\r\n                    &lt;execution&gt;\r\n                        &lt;goals&gt;\r\n                            &lt;goal&gt;integration-test&lt;\/goal&gt;\r\n                            &lt;goal&gt;verify&lt;\/goal&gt;\r\n                        &lt;\/goals&gt;\r\n                    &lt;\/execution&gt;\r\n                &lt;\/executions&gt;\r\n                &lt;configuration&gt;\r\n                    &lt;argLine&gt;--add-opens java.base\/java.lang=ALL-UNNAMED&lt;\/argLine&gt;\r\n                    &lt;systemPropertyVariables&gt;\r\n                        &lt;native.image.path&gt;${project.build.directory}\/${project.build.finalName}-runner&lt;\/native.image.path&gt;\r\n                        &lt;java.util.logging.manager&gt;org.jboss.logmanager.LogManager&lt;\/java.util.logging.manager&gt;\r\n                        &lt;maven.home&gt;${maven.home}&lt;\/maven.home&gt;\r\n                    &lt;\/systemPropertyVariables&gt;\r\n                &lt;\/configuration&gt;\r\n            &lt;\/plugin&gt;\r\n        &lt;\/plugins&gt;\r\n    &lt;\/build&gt;\r\n\r\n    &lt;profiles&gt;\r\n        &lt;profile&gt;\r\n            &lt;id&gt;native&lt;\/id&gt;\r\n            &lt;activation&gt;\r\n                &lt;property&gt;\r\n                    &lt;name&gt;native&lt;\/name&gt;\r\n                &lt;\/property&gt;\r\n            &lt;\/activation&gt;\r\n            &lt;properties&gt;\r\n                &lt;quarkus.package.jar.enabled&gt;false&lt;\/quarkus.package.jar.enabled&gt;\r\n                &lt;skipITs&gt;false&lt;\/skipITs&gt;\r\n                &lt;quarkus.native.enabled&gt;true&lt;\/quarkus.native.enabled&gt;\r\n            &lt;\/properties&gt;\r\n        &lt;\/profile&gt;\r\n    &lt;\/profiles&gt;\r\n&lt;\/project&gt;\r\n<\/pre>\n<p>So, nun noch ein JUnit Test, das muss sein:<\/p>\n<pre class=\"lang:java decode:true \" >package de.wenzlaff.twcount;\r\n\r\nimport static org.junit.jupiter.api.Assertions.assertEquals;\r\n\r\nimport java.time.LocalDate;\r\nimport java.time.temporal.ChronoUnit;\r\n\r\nimport org.junit.jupiter.api.Test;\r\n\r\nimport io.quarkus.test.common.http.TestHTTPEndpoint;\r\nimport io.quarkus.test.junit.QuarkusTest;\r\nimport io.restassured.RestAssured;\r\nimport io.restassured.http.ContentType;\r\n\r\n\/**\r\n * Test mit gleichen Erwartungswerten wie Ressource.\r\n * \r\n * @author Thomas Wenzlaff\r\n *\r\n *\/\r\n@QuarkusTest\r\n@TestHTTPEndpoint(CountdownResource.class)\r\nclass CountdownResourceTest {\r\n\r\n\t@Test\r\n\tvoid testCountdownEndpoint() {\r\n\r\n\t\tLocalDate now = LocalDate.now();\r\n\t\tlong targetDays = ChronoUnit.DAYS.between(now, LocalDate.of(2027, 6, 1));\r\n\t\tInteger expected = (int) Math.max(targetDays, 0);\r\n\r\n\t\tInteger actual = RestAssured.given().accept(ContentType.JSON).when().get() \/\/ Pfad kommt aus @TestHTTPEndpoint\r\n\t\t\t\t.then().statusCode(200).extract().path(\"daysRemaining\");\r\n\r\n\t\tassertEquals(expected, actual);\r\n\t}\r\n\r\n}<\/pre>\n<p>So es l\u00e4uft. Dann build und deploy:<\/p>\n<p><strong>mvn clean package -Dquarkus.package.jar.type=fast-jar<\/strong><\/p>\n<p># in das target\/quarkus-app Verzeichnis und alles auf den Pi kopieren<br \/>\n<strong><br \/>\nscp -r quarkus-app pi@vier:\/home\/pi<\/strong><\/p>\n<p>Swagger UI l\u00e4uft auch mit: <strong>http:\/\/localhost:9009\/q\/swagger-ui\/<\/strong><\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/blog.wenzlaff.de\/wp-content\/uploads\/2025\/10\/swagger-ui-count-scaled.jpeg\" alt=\"\" width=\"2560\" height=\"2058\" class=\"aligncenter size-full wp-image-22944\" srcset=\"http:\/\/blog.wenzlaff.de\/wp-content\/uploads\/2025\/10\/swagger-ui-count-scaled.jpeg 2560w, http:\/\/blog.wenzlaff.de\/wp-content\/uploads\/2025\/10\/swagger-ui-count-300x241.jpeg 300w, http:\/\/blog.wenzlaff.de\/wp-content\/uploads\/2025\/10\/swagger-ui-count-1024x823.jpeg 1024w, http:\/\/blog.wenzlaff.de\/wp-content\/uploads\/2025\/10\/swagger-ui-count-768x618.jpeg 768w, http:\/\/blog.wenzlaff.de\/wp-content\/uploads\/2025\/10\/swagger-ui-count-1536x1235.jpeg 1536w, http:\/\/blog.wenzlaff.de\/wp-content\/uploads\/2025\/10\/swagger-ui-count-2048x1647.jpeg 2048w\" sizes=\"auto, (max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px\" \/><\/p>\n<p>Dann noch ein system Service f\u00fcr den PI einfach diese Datei anlegen \/etc\/systemd\/system\/quarkus.service<\/p>\n<pre class=\"lang:default decode:true \" ># \/etc\/systemd\/system\/quarkus.service\r\n[Unit]\r\nDescription=Quarkus Fast-JAR Service\r\nAfter=network-online.target\r\n\r\n[Service]\r\nUser=pi\r\nWorkingDirectory=\/home\/pi\/quarkus-app\r\nExecStart=\/usr\/bin\/java -Dquarkus.http.host=IP-ADRESSE -jar \/home\/pi\/quarkus-app\/quarkus-run.jar\r\nRestart=on-failure\r\nRestartSec=5\r\n\r\n[Install]\r\nWantedBy=multi-user.target<\/pre>\n<p>Und aktivieren:<\/p>\n<p><strong>sudo systemctl enable  quarkus.service<br \/>\nsudo systemctl daemon-reload<br \/>\nsudo systemctl start quarkus<\/strong><\/p>\n<p>So, dann noch den Service in <strong>n8n<\/strong> aufrufen und an <strong>ntfy<\/strong> versenden, das ist aber in der einen Stunde nicht mehr m\u00f6glich. Aber nach <a href=\"https:\/\/gitlab.com\/IT-Berater\/twcount\" target=\"_blank\">GitLab<\/a> habe ich es dann doch noch mit Pipeline gebracht.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Ein fokussiertes Mini\u2011Projekt f\u00fcr die umgestellte Stunde: ein Quarkus\u2011REST\u2011Service, der die verbleibenden Tage bis zur Rente liefert, lokal entwickeln und anschlie\u00dfend auf einem Raspberry Pi deployen. Idee und Ziel -Ein kleiner REST\u2011Endpunkt liefert JSON mit: verbleibenden Tagen &#8211; Projekt: Quarkus, Java 21, RESTEasy Reactive, Jackson.<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[220,6202,3163,1023],"tags":[4053,2178,6255,6256,6176,2487,760,6257],"class_list":["post-22941","post","type-post","status-publish","format-standard","hentry","category-anleitung","category-java-21-challenge","category-maven","category-raspberry-pi","tag-countdown","tag-java","tag-n8n","tag-ntfy","tag-rente","tag-rest","tag-scp","tag-zeitumstellung"],"_links":{"self":[{"href":"http:\/\/blog.wenzlaff.de\/index.php?rest_route=\/wp\/v2\/posts\/22941","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/blog.wenzlaff.de\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/blog.wenzlaff.de\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/blog.wenzlaff.de\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"http:\/\/blog.wenzlaff.de\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=22941"}],"version-history":[{"count":0,"href":"http:\/\/blog.wenzlaff.de\/index.php?rest_route=\/wp\/v2\/posts\/22941\/revisions"}],"wp:attachment":[{"href":"http:\/\/blog.wenzlaff.de\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=22941"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/blog.wenzlaff.de\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=22941"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/blog.wenzlaff.de\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=22941"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}