commit 780ae60cb42633eb4f412db874cea0e9f5cfb1d9 Author: fabianArbeit Date: Mon Aug 5 14:33:49 2024 +0200 initial commit from local files diff --git a/Darlehenberechner.java b/Darlehenberechner.java new file mode 100644 index 0000000..37df4d6 --- /dev/null +++ b/Darlehenberechner.java @@ -0,0 +1,149 @@ +import java.text.DecimalFormat; +import java.math.BigDecimal; +import java.math.MathContext; +import java.time.Month; +import java.time.YearMonth; + +class Darlehenberechner { + + private static final class Konfiguration { + + private BigDecimal darlehenswert; + private BigDecimal zinssatzProzent; + private BigDecimal monatlicheRate; + private Integer laufzeitJahre; + private Integer tilgungsfreieZeit; + private YearMonth anfangsmonat; + + + public BigDecimal getDarlehenswert() { + return darlehenswert; + } + + + public Konfiguration setDarlehenswert(BigDecimal darlehenswert) { + this.darlehenswert = darlehenswert; + return this; + } + + + public BigDecimal getZinssatzProzent() { + return zinssatzProzent; + } + + + public Konfiguration setZinssatzProzent(BigDecimal zinssatzProzent) { + this.zinssatzProzent = zinssatzProzent; + return this; + } + + + public BigDecimal getMonatlicheRate() { + return monatlicheRate; + } + + + public Konfiguration setMonatlicheRate(BigDecimal monatlicheRate) { + this.monatlicheRate = monatlicheRate; + return this; + } + + + public Integer getLaufzeitJahre() { + return laufzeitJahre; + } + + + public Konfiguration setLaufzeitJahre(Integer laufzeitJahre) { + this.laufzeitJahre = laufzeitJahre; + return this; + } + + + public Integer getTilgungsfreieZeit() { + return tilgungsfreieZeit; + } + + + public Konfiguration setTilgungsfreieZeit(Integer tilgungsfreieZeit) { + this.tilgungsfreieZeit = tilgungsfreieZeit; + return this; + } + + + public YearMonth getAnfangsmonat() { + return anfangsmonat; + } + + + public Konfiguration setAnfangsmonat(YearMonth anfangsmonat) { + this.anfangsmonat = anfangsmonat; + return this; + } + } + + private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("#,##0.00"); + private static final BigDecimal ZWOELF = BigDecimal.valueOf(12); + + public static void main(String[] args) { + var konfig = new Konfiguration() + .setDarlehenswert(BigDecimal.valueOf(168_000)) + .setZinssatzProzent(BigDecimal.valueOf(3.73)) + .setMonatlicheRate(BigDecimal.valueOf(1_500)) + .setTilgungsfreieZeit(0) + .setAnfangsmonat(YearMonth.of(2024, Month.SEPTEMBER)); + berechneWerte(konfig); + + /*var konfig = new Konfiguration() + .setDarlehenswert(BigDecimal.valueOf(168_000)) + .setZinssatzProzent(BigDecimal.valueOf(3.73)) + .setMonatlicheRate(BigDecimal.valueOf(1_500)) + .setTilgungsfreieZeit(0) + .setLaufzeitJahre(11) + .setAnfangsmonat(YearMonth.of(2024, Month.SEPTEMBER)); + berechneWerte(konfig);*/ + } + + + private static void berechneWerte(Konfiguration konfig) { + BigDecimal zinssatzReal = konfig.getZinssatzProzent().divide(BigDecimal.valueOf(100), MathContext.DECIMAL128); + BigDecimal monatlicheRate = konfig.getMonatlicheRate(); + Integer laufzeitJahre = konfig.getLaufzeitJahre(); + Integer tilgungsfreieZeit = konfig.getTilgungsfreieZeit(); + BigDecimal restschuld = konfig.getDarlehenswert(); + YearMonth aktuellerMonat = konfig.getAnfangsmonat(); + BigDecimal summeZinsen = BigDecimal.ZERO; + BigDecimal summeTilgung = BigDecimal.ZERO; + int laufzeitMonate = 0; + while ((laufzeitJahre == null || laufzeitMonate < (laufzeitJahre * 12)) && restschuld.signum() > 0) { + // berechne Beträge/ aktualisiere Restschuld + BigDecimal zinsbetrag = restschuld.multiply(zinssatzReal).divide(ZWOELF, MathContext.DECIMAL128); + if (monatlicheRate.compareTo(restschuld) > 0) { + monatlicheRate = restschuld.add(zinsbetrag); // die letzte Rate ist gleich der Restschuld + Zinsen + } + BigDecimal tilgungsbetrag = tilgungsfreieZeit != null && tilgungsfreieZeit > 0 ? BigDecimal.ZERO : monatlicheRate.subtract(zinsbetrag); + restschuld = restschuld.subtract(tilgungsbetrag); + System.out.println(aktuellerMonat + ": " + DECIMAL_FORMAT.format(monatlicheRate) + " = " + DECIMAL_FORMAT.format(zinsbetrag) + + " + " + DECIMAL_FORMAT.format(tilgungsbetrag) + " | " + DECIMAL_FORMAT.format(restschuld)); + // berechne Summen für Zusammenfassung + summeZinsen = summeZinsen.add(zinsbetrag); + summeTilgung = summeTilgung.add(tilgungsbetrag); + if ((laufzeitMonate > 0 && laufzeitMonate % 11 == 0) || aktuellerMonat.getMonthValue() == 12) { + System.out.println(" " + DECIMAL_FORMAT.format(summeZinsen) + " + " + DECIMAL_FORMAT.format(summeTilgung)); + } + // aktualisiere Werte für den nächsten Lauf + aktuellerMonat = aktuellerMonat.plusMonths(1); + laufzeitMonate++; + if (tilgungsfreieZeit != null) { + tilgungsfreieZeit--; + } + } + // letzte Zusammenfassung + System.out.println(" " + DECIMAL_FORMAT.format(summeZinsen) + " + " + DECIMAL_FORMAT.format(summeTilgung)); + // Ausgabe Laufzeit + Restschuld + laufzeitJahre = laufzeitJahre == null ? laufzeitMonate / 12 : laufzeitJahre; + int laufzeitMonateTeil = laufzeitMonate - (laufzeitJahre * 12); + System.out.println("Laufzeit: " + laufzeitJahre + " Jahre " + laufzeitMonateTeil + " Monate"); + System.out.println("Restschuld: " + DECIMAL_FORMAT.format(restschuld)); + } +} diff --git a/Java11.java b/Java11.java new file mode 100644 index 0000000..11012cd --- /dev/null +++ b/Java11.java @@ -0,0 +1,162 @@ +import java.awt.Point; +import java.io.IOException; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +class Java11 { + public static void main(String[] args) { + //////// + // 1. // + //////// + // if the resource is referenced by a final or effectively final variable, + // a try-with-resources statement can manage a resource without a new variable being declared: + MyAutoCloseable mac = new MyAutoCloseable(); + try (mac) { + // do some stuff with mac + } + + //////// + // 2. // + //////// + // use diamond operator in conjunction with anonymous inner classes + List fc = new ArrayList<>(1) {}; // anonymous inner class + + List fc0 = new ArrayList<>(1) { + // anonymous inner class + }; + + List fc1 = new ArrayList<>(1) {}; // anonymous inner class + + //////// + // 4. // + //////// + // Local Variable Type Inference + var message = "Hello, Java 10"; + var point = new Point(1, 2); + + //////// + // 5. // + //////// + // immutable Collections/ Maps + Map meineMap = Map.of(); + Set meinSet = Set.of("key1", "key2", "key3"); + List meineListe = List.copyOf(meinSet); + + //////// + // 6. // + //////// + // Optional to Stream Optional.stream() gives us an easy way to you use the power of Streams on Optional elements + List filteredList = new ArrayList>().stream() + .flatMap(Optional::stream) + .collect(Collectors.toList()); + + //////// + // 7. // + //////// + // Collectors get additional methods to collect a Stream into unmodifiable List, Map or Set: + List evenList = new ArrayList().stream() + .filter(i -> i % 2 == 0) + .collect(Collectors.toUnmodifiableList()); + + //////// + // 8. // + //////// + // Optional got a new method orElseThrow() which doesn’t take any argument and throws NoSuchElementException if no value is present: + Integer firstEven = List.of(1, 2, 3).stream() + .filter(i -> i % 2 == 0) + .findFirst() + .orElseThrow(); + + //////// + // 9. // + //////// + // Java 11 adds a few new methods to the String class + var multilineString = "Baeldung helps \n \n developers \n explore Java."; + List lines = multilineString.lines().collect(Collectors.toList()); + assert " ".isBlank(); + assert " x ".strip().equals("x"); + assert " x ".stripLeading().equals("x "); + assert " x ".stripTrailing().equals(" x"); + assert "x".repeat(5).equals("xxxxx"); + + ///////// + // 10. // + ///////// + // We can use the new readString and writeString static methods from the Files class: + String fileContent = null; + try { + Path filePath = Files.writeString(Files.createTempFile("demo", ".txt"), "Sample text"); + fileContent = Files.readString(filePath); + } catch (IOException ioe) {} + assert fileContent.equals("Sample text"); + + ///////// + // 11. // + ///////// + // The java.util.Collection interface contains a new default toArray method which takes an IntFunction argument. + List sampleList = List.of("Java", "Kotlin"); + String[] sampleArray = sampleList.toArray(String[]::new); + + ///////// + // 12. // + ///////// + // A static not method has been added to the Predicate interface. + sampleList = List.of("Java", "\n \n", "Kotlin", " "); + List withoutBlanks = sampleList.stream() + .filter(Predicate.not(String::isBlank)) + .collect(Collectors.toList()); + // While not(isBlank) reads more naturally than isBlank.negate(), the big advantage is that we can also use not with method references, + // like not(String::isBlank). + + ///////// + // 13. // + ///////// + // Support for using the local variable syntax (var keyword) in lambda parameters was added in Java 11. + // We can make use of this feature to apply modifiers to our local variables, like defining a type annotation: + sampleList = List.of("Java", "Kotlin"); + String resultString = sampleList.stream() + .map((@Nonnull var x) -> x.toUpperCase()) + .collect(Collectors.joining(", ")); + assert resultString.equals("JAVA, KOTLIN"); + } +} + +class MyAutoCloseable implements AutoCloseable { + public void close() {} +} + +//////// +// 3. // +//////// +// Interfaces in the upcoming JVM version can have private methods, which can be used to split lengthy default methods +interface InterfaceWithPrivateMethods { + private static String staticPrivate() { + return "static private"; + } + + private String instancePrivate() { + return "instance private"; + } + + default void check() { + String result = staticPrivate(); + InterfaceWithPrivateMethods pvt = new InterfaceWithPrivateMethods() { + // anonymous class + }; + result = pvt.instancePrivate(); + } +} + + +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.PARAMETER) +public @interface Nonnull {} diff --git a/LoadingBar.java b/LoadingBar.java new file mode 100644 index 0000000..3e225a6 --- /dev/null +++ b/LoadingBar.java @@ -0,0 +1,338 @@ +import java.text.DecimalFormat; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.TimeUnit; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +class LoadingBar { + + private static final String TIME_FORMAT = "HH:mm"; + private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern(TIME_FORMAT); + private static final Pattern TIME_PATTERN = Pattern.compile("(?>[01][0-9]|2[0-4]):[0-5][0-9]"); + private static final Pattern LUNCH_DURATION_PATTERN = Pattern.compile("\\d+"); + private static final Pattern OFFSET_PATTERN = Pattern.compile("[\\+\\-]\\d+"); + private static final DecimalFormat PERCENTAGE_FORMAT = new DecimalFormat("00.00"); + private static final int MIN_LUNCH_DURATION = 30; + private static final LocalTime LATEST_LUNCH_TIME = LocalTime.of(13, 30); + private static final int DEFAULT_NUMBER_WORK_MINS_BEFORE_LUNCH = 5 * 60; + private static final int MAX_NUMBER_WORK_MINS_WITHOUT_LUNCH = 6 * 60; + private static final int MAX_NUMBER_WORK_MINS = 8 * 60; + + private static enum DaySection { + MITTAG("-m", "Mittag"), + ZAPFENSTREICH("-z", "Zapfenstreich"); + + private final String param; + private final String description; + + + private DaySection(String param, String description) { + this.param = param; + this.description = description; + } + + + public static DaySection byParam(String param) { + return Arrays.stream(DaySection.values()).filter((DaySection section) -> section.getParam().equals(param)).findFirst().orElse(null); + } + + + public String getParam() { + return param; + } + + + public String getDescription() { + return description; + } + } + + + public static void main(String[] args) { + if (args.length > 0 && Objects.equals(args[0], "--help")) { + printHelp(); + return; + } + verifyMinimumNumberOfArgs(args); + String nextArg = args[0]; + verifyTimeFormat(nextArg, "Erstes Argument"); + var startTime = LocalTime.parse(nextArg, TIME_FORMATTER); + nextArg = args[1]; + var section = DaySection.byParam(nextArg); + verifyDaySection(section, nextArg); + if (section == DaySection.MITTAG) { + handleMittagspause(args, startTime); + } else { + handleZapfenstreich(args, startTime); + } + } + + + private static void handleMittagspause(String[] args, LocalTime startTime) { + if (args.length == 2) { + showLoadingBarMittagspause(startTime); + return; + } + String nextArg = args[2]; + verifyTimeFormat(nextArg, "Argument nach " + DaySection.MITTAG.getParam()); + var maxMittagspause = LocalTime.parse(nextArg, TIME_FORMATTER); + showLoadingBarMittagspause(startTime, maxMittagspause); + } + + + private static void handleZapfenstreich(String[] args, LocalTime startTime) { + Integer lunchDuration = null; + if (args.length == 2) { + showLoadingBarZapfenstreich(startTime, lunchDuration); + return; + } + String nextArg = args[2]; + LocalTime maxZapfenstreich = null; + int endTimeOffset = 0; + if (TIME_PATTERN.matcher(nextArg).matches()) { + maxZapfenstreich = LocalTime.parse(nextArg, TIME_FORMATTER); + } else if (OFFSET_PATTERN.matcher(nextArg).matches()) { + endTimeOffset = Integer.parseInt(nextArg); + } else { + verifyDurationFormat(nextArg, "Argument nach " + DaySection.ZAPFENSTREICH.getParam(), true); // FSFIXME erweitere Fehlermeldung + lunchDuration = Integer.parseInt(nextArg); + } + if (args.length == 3) { + if (maxZapfenstreich == null && endTimeOffset == 0) { + showLoadingBarZapfenstreich(startTime, lunchDuration); + } else if (maxZapfenstreich == null) { + showLoadingBarZapfenstreich(startTime, lunchDuration, endTimeOffset); + } else { + showLoadingBarZapfenstreich(startTime, lunchDuration, maxZapfenstreich); + } + return; + } + nextArg = args[3]; + if (lunchDuration == null) { + System.out.println("Letztes Argument darf nur auf Mittagspausendauer folgen."); + System.exit(1); + } + if (maxZapfenstreich == null && !OFFSET_PATTERN.matcher(nextArg).matches()) { + verifyTimeFormat(nextArg, "Letztes Argument nach " + DaySection.ZAPFENSTREICH.getParam() + " und Mittagspausendauer"); + maxZapfenstreich = LocalTime.parse(nextArg, TIME_FORMATTER); + showLoadingBarZapfenstreich(startTime, lunchDuration, maxZapfenstreich); + return; + } + verifyOffsetFormat(nextArg, "Letztes Argument nach " + DaySection.ZAPFENSTREICH.getParam() + " und Enduhrzeit"); + endTimeOffset = Integer.parseInt(nextArg); + showLoadingBarZapfenstreich(startTime, lunchDuration, endTimeOffset); + } + + + private static void verifyMinimumNumberOfArgs(String[] args) { + if (args.length >= 2) { + return; + } + System.out.println("Mindestens 2 Argumente müssen gegeben sein."); + printHelp(); + System.exit(1); + } + + + private static void verifyTimeFormat(String param, String errMsgPrefix) { + verifyInputFormat(TIME_PATTERN, param, errMsgPrefix, true, false); + } + + + private static void verifyDurationFormat(String param, String errMsgPrefix, boolean timeInputPossible) { + verifyInputFormat(LUNCH_DURATION_PATTERN, param, errMsgPrefix, false, timeInputPossible); + } + + + private static void verifyOffsetFormat(String param, String errMsgPrefix) { + verifyInputFormat(OFFSET_PATTERN, param, errMsgPrefix, false, false); + } + + + private static void verifyInputFormat(Pattern pattern, String param, String errMsgPrefix, boolean timeInput, boolean timeInputPossible) { + if (pattern.matcher(param).matches()) { + return; + } // FSFIXME fine tune message -> HH:mm, mm, -+mm + var firstInputPart = timeInput ? "Uhrzeitformat ("+ TIME_FORMAT + ")" : "Minutenanzahl (" + LUNCH_DURATION_PATTERN + ")"; + var possibleTimeInputPart = !timeInput && timeInputPossible ? " oder Uhrzeitformat ("+ TIME_FORMAT + ")" : ""; + System.out.println(errMsgPrefix + " \"" + param + "\" muss " + firstInputPart + possibleTimeInputPart + " entsprechen."); + System.exit(1); + } + + + private static void verifyDaySection(DaySection section, String param) { + if (section != null) { + return; + } + List sectionDescs = Arrays.stream(DaySection.values()).map((DaySection ds) -> ds.getDescription() + " (" + ds.getParam() + ")") + .collect(Collectors.toList()); + String sectionDescsJoined = String.join(" oder ", sectionDescs); + System.out.println("Argument nach Startzeit \"" + param + "\" muss Angabe für " + sectionDescsJoined + " sein."); + System.exit(1); + } + + + private static void printHelp() { + System.out.println(new StringBuilder().append("Mögliche Argumente für LoadingBar:\n") + .append("Normalfall Vormittag (Mittagspause <= ").append(LATEST_LUNCH_TIME).append(")\n") + .append(TIME_FORMAT).append(" ").append(DaySection.MITTAG.getParam()).append("\n") + .append("Vormittag mit expliziter Mittagspause (<= ").append(LATEST_LUNCH_TIME).append(")\n") + .append(TIME_FORMAT).append(" ").append(DaySection.MITTAG.getParam()).append(" ").append(TIME_FORMAT).append("\n") + .append("Normalfall Nachmittag (Mittagspause ").append(MIN_LUNCH_DURATION).append(" min)\n") + .append(TIME_FORMAT).append(" ").append(DaySection.ZAPFENSTREICH.getParam()).append("\n") + .append("Nachmittag mit expliziter Länge Mittagspause (Mittagspause unter ").append(MIN_LUNCH_DURATION).append(" min wird auf ").append(MIN_LUNCH_DURATION).append(" min korrigiert)\n") + .append(TIME_FORMAT).append(" ").append(DaySection.ZAPFENSTREICH.getParam()).append(" mm\n") + .append("Nachmittag mit explizitem Feierabend (Mittagspause je nach Minimum (s.u.))\n") + .append(TIME_FORMAT).append(" ").append(DaySection.ZAPFENSTREICH.getParam()).append(" ").append(TIME_FORMAT).append("\n") + .append("Nachmittag mit abweichender Minutenanzahl Arbeitszeit\n") + .append(TIME_FORMAT).append(" ").append(DaySection.ZAPFENSTREICH.getParam()).append(" -+mm\n") + .append("Nachmittag mit explizitem Feierabend u. expliziter Länge Mittagspause (Mittagspause unter Minimum (s.u.) wird auf Minimum korrigiert)\n") + .append(TIME_FORMAT).append(" ").append(DaySection.ZAPFENSTREICH.getParam()).append(" mm ").append(TIME_FORMAT).append("\n") + .append("Nachmittag mit explizitem Feierabend u. abweichender Minutenanzahl Arbeitszeit\n") + .append(TIME_FORMAT).append(" ").append(DaySection.ZAPFENSTREICH.getParam()).append(" ").append(TIME_FORMAT).append(" -+mm\n\n") + .append("Mittagspause minimum in Minuten:\n") + .append(" - bis ").append(MAX_NUMBER_WORK_MINS_WITHOUT_LUNCH).append(" min (") + .append(MAX_NUMBER_WORK_MINS_WITHOUT_LUNCH / 60).append(" std): 0\n") + .append(" - bis ").append(MAX_NUMBER_WORK_MINS_WITHOUT_LUNCH).append(" min + ") + .append(MIN_LUNCH_DURATION).append(" min: Arbeitszeit - ").append(MAX_NUMBER_WORK_MINS_WITHOUT_LUNCH).append(" min\n") + .append(" - ab ").append(MAX_NUMBER_WORK_MINS_WITHOUT_LUNCH).append(" min + ").append(MIN_LUNCH_DURATION).append(" min: ") + .append(MIN_LUNCH_DURATION).append(" min\n") + .toString()); + } + + + private static void showLoadingBarMittagspause(LocalTime startTime) { + showLoadingBarMittagspause(startTime, null); + } + + + private static void showLoadingBarMittagspause(LocalTime startTime, LocalTime manualEndTime) { + LocalTime endTime = manualEndTime != null ? manualEndTime : startTime.plusMinutes(DEFAULT_NUMBER_WORK_MINS_BEFORE_LUNCH); + LocalTime trueEndTime = endTime.isAfter(LATEST_LUNCH_TIME) ? LATEST_LUNCH_TIME : endTime; + showLoadingBar(startTime, trueEndTime); + } + + + private static void showLoadingBarZapfenstreich(LocalTime startTime, Integer manualLunchDuration) { + showLoadingBarZapfenstreich(startTime, manualLunchDuration, 0); + } + + + private static void showLoadingBarZapfenstreich(LocalTime startTime, Integer manualLunchDuration, int endTimeOffset) { + int minLunchDuration = getMinLunchDuration(startTime, endTimeOffset); + int realLunchDuration = getRealLunchDuration(manualLunchDuration, minLunchDuration); + LocalTime trueEndTime = startTime.plusMinutes(MAX_NUMBER_WORK_MINS + realLunchDuration + endTimeOffset); + realShowLoadingBarZapfenstreich(startTime, realLunchDuration, trueEndTime); + } + + + private static void showLoadingBarZapfenstreich(LocalTime startTime, Integer manualLunchDuration, LocalTime manualEndTime) { + LocalTime trueEndTime = manualEndTime; + int minLunchDuration = getMinLunchDuration(startTime, trueEndTime); + int realLunchDuration = getRealLunchDuration(manualLunchDuration, minLunchDuration); + if (trueEndTime == null) { + trueEndTime = startTime.plusMinutes(MAX_NUMBER_WORK_MINS + realLunchDuration); + } + realShowLoadingBarZapfenstreich(startTime, realLunchDuration, trueEndTime); + } + + + private static int getMinLunchDuration(LocalTime startTime, int endTimeOffset) { + if (endTimeOffset == 0) { + return MIN_LUNCH_DURATION; + } + int totalDuration = MAX_NUMBER_WORK_MINS + endTimeOffset; + int effectiveLunchDuration = totalDuration - MAX_NUMBER_WORK_MINS_WITHOUT_LUNCH; + return getMinLunchDuration(effectiveLunchDuration); + } + + + private static int getMinLunchDuration(LocalTime startTime, LocalTime endTime) { + if (endTime == null) { + return MIN_LUNCH_DURATION; + } + long totalDuration = startTime.until(endTime, ChronoUnit.MINUTES); + int effectiveLunchDuration = ((int) totalDuration) - MAX_NUMBER_WORK_MINS_WITHOUT_LUNCH; + return getMinLunchDuration(effectiveLunchDuration); + } + + + private static int getMinLunchDuration(int effectiveLunchDuration) { + if (effectiveLunchDuration < 0) { + effectiveLunchDuration = 0; + } + return effectiveLunchDuration < MIN_LUNCH_DURATION ? effectiveLunchDuration : MIN_LUNCH_DURATION; + } + + + private static int getRealLunchDuration(Integer manualLunchDuration, int minLunchDuration) { + return manualLunchDuration != null && manualLunchDuration >= minLunchDuration ? manualLunchDuration : minLunchDuration; + } + + + private static void realShowLoadingBarZapfenstreich(LocalTime startTime, int manualLunchDuration, LocalTime endTime) { + if (manualLunchDuration > 0) { + var totalWorkTime = LocalTime.MIDNIGHT.plusMinutes(startTime.until(endTime, ChronoUnit.MINUTES) - manualLunchDuration); + System.out.print("Arbeitszeit: " + TIME_FORMATTER.format(totalWorkTime) + "; "); + } + showLoadingBar(startTime, endTime); + } + + + private static void showLoadingBar(LocalTime startTime, LocalTime endTime) { + long initialMinutes = startTime.until(endTime, ChronoUnit.MINUTES); + System.out.print(minutesToTimeString(initialMinutes) + " gesamt; Endzeit: " + TIME_FORMATTER.format(endTime) + "\n"); + long passedMinutes = startTime.until(LocalTime.now(), ChronoUnit.MINUTES); + if (passedMinutes > initialMinutes) { + passedMinutes = initialMinutes; + } else if (passedMinutes < 0) { + System.out.println(fillLoadingBar(initialMinutes, 0, false)); + return; + } + while (passedMinutes < initialMinutes) { + System.out.print(fillLoadingBar(initialMinutes, passedMinutes, true)); + try { + var now = LocalTime.now(); + var oneMinuteLater = now.plusMinutes(1).truncatedTo(ChronoUnit.MINUTES); + // +1 second to adjust for ignored milliseconds as it is better to switch between 00 and 01 as between 59 and 00 + TimeUnit.SECONDS.sleep(now.until(oneMinuteLater, ChronoUnit.SECONDS) + 1); + // TimeUnit.SECONDS.sleep(1L); // DEBUG + } catch (InterruptedException ie) { + throw new RuntimeException(ie); + } + passedMinutes++; + } + System.out.println(fillLoadingBar(initialMinutes, passedMinutes, false)); + } + + + private static String fillLoadingBar(long initialMinutes, long passedMinutes, boolean progressive) { + double wholePercentage = ((double) passedMinutes / initialMinutes) * 100; + long remainingMinutes = initialMinutes - passedMinutes; + int numberOfEquals = (int) wholePercentage; + var sb = new StringBuilder("["); + for (int i = 0; i < 100; i++) { + if (i < numberOfEquals) { + sb.append("="); + } else { + sb.append("-"); + } + } + sb.append("] ").append(PERCENTAGE_FORMAT.format(wholePercentage)).append("% ") + .append(minutesToTimeString(passedMinutes)).append(" - ").append(minutesToTimeString(remainingMinutes)); + if (progressive) { + sb.append("\r"); + } + return sb.toString(); + } + + + private static String minutesToTimeString(long minutes) { + return LocalTime.of((int) minutes / 60, (int) minutes % 60).format(TIME_FORMATTER); + } +} diff --git a/SimpleLoadingBar.java b/SimpleLoadingBar.java new file mode 100644 index 0000000..266d01b --- /dev/null +++ b/SimpleLoadingBar.java @@ -0,0 +1,147 @@ +import java.text.DecimalFormat; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; +import java.util.Objects; +import java.util.concurrent.TimeUnit; +import java.util.regex.Pattern; + +class SimpleLoadingBar { + + private static final String TIME_FORMAT = "HH:mm"; + private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern(TIME_FORMAT); + private static final Pattern TIME_PATTERN = Pattern.compile("(?>[01][0-9]|2[0-4]):[0-5][0-9]"); + private static final DecimalFormat PERCENTAGE_FORMAT = new DecimalFormat("00.00"); + + + public static void main(String[] args) { + if (args.length > 0 && Objects.equals(args[0], "--help")) { + printHelp(); + return; + } + verifyMinimumNumberOfArgs(args); + String nextArg = args[0]; + verifyTimeFormat(nextArg, "Erstes Argument"); // FSFIXME package + var firstTime = LocalTime.parse(nextArg, TIME_FORMATTER); // FSFIXME package + LocalTime startTime = null; + String title = ""; + LocalTime endTime = null; + if (args.length > 1) { + startTime = firstTime; + nextArg = args[1]; + if (nextArg.equals("-msg")) { + title = args.length > 2 ? args[2] : title; + } else { + verifyTimeFormat(nextArg, "Zweites Argument"); + endTime = LocalTime.parse(nextArg, TIME_FORMATTER); + } + } else { + startTime = LocalTime.now(); + endTime = firstTime; + } + String fallbackTitle = "Ende! Endzeit " + TIME_FORMATTER.format(endTime) + " erreicht."; + if (args.length == 2 || !title.isBlank()) { + title = title.isBlank() ? fallbackTitle : title; + showLoadingBar(startTime, endTime, title); + return; + } + // if there are 3 arguments, the third will be discarded. + boolean hasTitleArg = args.length > 3 && args[2].equals("-msg"); + title = hasTitleArg ? args[3] : title; + title = title.isBlank() ? fallbackTitle : title; + showLoadingBar(startTime, endTime, title); + } + + + private static void printHelp() { + System.out.println(new StringBuilder().append("Mögliche Argumente für LoadingBar:\n") + .append("Startzeit, Endzeit, Endnachricht (Optional)\n") + .append(TIME_FORMAT).append(" ").append(TIME_FORMAT).append("-msg \n") + .append("Endzeit (Startzeit = jetzt), Endnachricht (Optional)\n") + .append(TIME_FORMAT).append("-msg \n") + ); + } + + + private static void verifyMinimumNumberOfArgs(String[] args) { + if (args.length >= 1) { + return; + } + System.out.println("Mindestens 1 Argument muss gegeben sein."); + printHelp(); + System.exit(1); + } + + + private static void verifyTimeFormat(String param, String errMsgPrefix) { + if (TIME_PATTERN.matcher(param).matches()) { + return; + } + System.out.println(errMsgPrefix + " \"" + param + "\" muss Uhrzeitformat ("+ TIME_FORMAT + ") entsprechen."); + System.exit(1); + } + + + + public static void showLoadingBar(LocalTime startTime, LocalTime endTime, String title) { + long initialMinutes = startTime.until(endTime, ChronoUnit.MINUTES); + System.out.print(minutesToTimeString(initialMinutes) + " gesamt; Endzeit: " + TIME_FORMATTER.format(endTime) + "\n"); + long passedMinutes = startTime.until(LocalTime.now(), ChronoUnit.MINUTES); + if (passedMinutes > initialMinutes) { + passedMinutes = initialMinutes; + } else if (passedMinutes < 0) { + System.out.println(fillLoadingBar(initialMinutes, 0, false)); + return; + } + while (passedMinutes < initialMinutes) { + System.out.print(fillLoadingBar(initialMinutes, passedMinutes, true)); + try { + var now = LocalTime.now(); + var oneMinuteLater = now.plusMinutes(1).truncatedTo(ChronoUnit.MINUTES); + // +1 second to adjust for ignored milliseconds as it is better to switch between 00 and 01 as between 59 and 00 + TimeUnit.SECONDS.sleep(now.until(oneMinuteLater, ChronoUnit.SECONDS) + 1); + // TimeUnit.SECONDS.sleep(1L); // DEBUG + } catch (InterruptedException ie) { + throw new RuntimeException(ie); + } + passedMinutes++; + } + System.out.println(fillLoadingBar(initialMinutes, passedMinutes, false)); + System.out.println(formatTitle(title)); + } + + + private static String fillLoadingBar(long initialMinutes, long passedMinutes, boolean progressive) { + double wholePercentage = ((double) passedMinutes / initialMinutes) * 100; + long remainingMinutes = initialMinutes - passedMinutes; + int numberOfEquals = (int) wholePercentage; + var sb = new StringBuilder("["); + for (int i = 0; i < 100; i++) { + if (i < numberOfEquals) { + sb.append("="); + } else { + sb.append("-"); + } + } + sb.append("] ").append(PERCENTAGE_FORMAT.format(wholePercentage)).append("% ") + .append(minutesToTimeString(passedMinutes)).append(" - ").append(minutesToTimeString(remainingMinutes)); + if (progressive) { + sb.append("\r"); + } + return sb.toString(); + } + + + private static String minutesToTimeString(long minutes) { + return LocalTime.of((int) minutes / 60, (int) minutes % 60).format(TIME_FORMATTER); + } + + + private static String formatTitle(String title) { + var sb = new StringBuilder(); + for (int i = 0; i < title.length(); i++) { + sb.append("*"); + } + return sb.toString() + "\n" + title + "\n" + sb.toString(); + } +}