diff --git a/DrinkingBar.java b/DrinkingBar.java index c3d42db..a7ccc28 100644 --- a/DrinkingBar.java +++ b/DrinkingBar.java @@ -18,59 +18,115 @@ public class DrinkingBar { private static final int MINS_PER_HALF_HOUR = MINS_PER_HOUR / 2; private static final int MINUTES_BEFORE_PAUSE = 4 * MINS_PER_HOUR + MINS_PER_HALF_HOUR; private static final int MINUTES_WITH_PAUSE = 6 * MINS_PER_HOUR; + private static final int DEFAULT_TOTAL_TIME = 8 * MINS_PER_HOUR + MINS_PER_HALF_HOUR; private static final DecimalFormat LITER_FORMAT = new DecimalFormat("0.00"); private static final DecimalFormat PERCENTAGE_FORMAT = new DecimalFormat("00.00"); private static final BigDecimal MINS_PER_HOUR_BD = BigDecimal.valueOf(MINS_PER_HOUR); private static final MathContext MC_INTEGER = new MathContext(1, RoundingMode.HALF_EVEN); + private final LocalTime startTime; + private LocalTime endTime; + private long totalMinutes; + private BigDecimal totalMinutesBD; - public static void main(String[] args) throws IOException { - var br = new BufferedReader(new InputStreamReader(System.in, StandardCharsets.UTF_8)); - System.out.print("Ankunftszeit: "); - var startTime = LocalTime.parse(br.readLine(), TIME_FORMATTER).truncatedTo(ChronoUnit.MINUTES); - long totalMinutes = 8 * MINS_PER_HOUR + MINS_PER_HALF_HOUR; - long passedMinutes = startTime.until(LocalTime.now().truncatedTo(ChronoUnit.MINUTES), ChronoUnit.MINUTES); - // long passedMinutes = 0; // DEBUG - double prevPrintedLitres = 0.0; - while (passedMinutes < totalMinutes) { - if (passedMinutes <= MINUTES_BEFORE_PAUSE || passedMinutes > MINUTES_WITH_PAUSE) { - double currentLitres = 2.0 / totalMinutes * passedMinutes + 0.25; - double printedLitres = currentLitres - (currentLitres % 0.25); - double currentProgressToNextStep = 100 / 0.25 * (currentLitres - printedLitres); - long minutesToNextStep = getMinutesToNextStep(currentLitres, totalMinutes); - System.out.print("\rAktuelles Volumen: " + LITER_FORMAT.format(printedLitres) + "L - " - + PERCENTAGE_FORMAT.format(currentProgressToNextStep) + "% - " + minutesToTimeString(minutesToNextStep)); - prevPrintedLitres = printedLitres; - } - 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.MILLISECONDS.sleep(100L); // DEBUG - } catch (InterruptedException ie) { - throw new RuntimeException(ie); - } - passedMinutes++; - } - System.out.println(""); + private DrinkingBar(LocalTime startTime) { + this.startTime = startTime; + this.totalMinutes = DEFAULT_TOTAL_TIME; + this.totalMinutesBD = BigDecimal.valueOf(totalMinutes); + this.endTime = startTime.plusMinutes(totalMinutes); } - private static long getMinutesToNextStep(double currentLitres, long totalMinutes) { + public static void main(String[] args) throws IOException { + var br = new BufferedReader(new InputStreamReader(System.in, StandardCharsets.UTF_8)); + print("Ankunftszeit: "); + var startTime = LocalTime.parse(br.readLine(), TIME_FORMATTER).truncatedTo(ChronoUnit.MINUTES); + var db = new DrinkingBar(startTime); + db.showLoadingBar(); + } + + + private static void println(Object o) { + System.out.println(o); + } + + + private static void print(Object o) { + System.out.print(o); + } + + + private void setEndTime(LocalTime endTime) { + this.endTime = endTime; + this.totalMinutes = startTime.until(endTime, ChronoUnit.MINUTES); + this.totalMinutesBD = BigDecimal.valueOf(totalMinutes); + } + + + private void showLoadingBar() { + long passedMinutes = startTime.until(LocalTime.now().truncatedTo(ChronoUnit.MINUTES), ChronoUnit.MINUTES); + // long passedMinutes = 0; // DEBUG + if (passedMinutes > totalMinutes) { + passedMinutes = totalMinutes; + } else if (passedMinutes < 0) { + var now = LocalTime.now().truncatedTo(ChronoUnit.SECONDS); + println("!ACHTUNG! Startzeit \"" + startTime + ":00\" liegt in der Zukunft von jetzt an (" + now + ") gesehen."); + } + println(minutesToTimeString(totalMinutesBD) + " gesamt; Endzeit: " + TIME_FORMATTER.format(endTime)); + while (passedMinutes < totalMinutes) { + print(fillLoadingBar(passedMinutes, true)); + waitUntilNextMinute(); + passedMinutes++; + } + println(fillLoadingBar(passedMinutes, false)); + } + + + private String fillLoadingBar(long passedMinutes, boolean progressive) { + long effectivePassedMinutes = passedMinutes; + if (passedMinutes > MINUTES_BEFORE_PAUSE && passedMinutes <= MINUTES_WITH_PAUSE) { + effectivePassedMinutes = MINUTES_BEFORE_PAUSE; + } + double currentLitres = 2.0 / totalMinutes * effectivePassedMinutes + 0.25; + double printedLitres = currentLitres - (currentLitres % 0.25); + // double currentProgressToNextStep = 100 / 0.25 * (currentLitres - printedLitres); + BigDecimal minutesToNextStep = getMinutesToNextStep(currentLitres); + String progressivePart = progressive ? "\r" : ""; + return progressivePart + "Aktuelles Volumen: " + LITER_FORMAT.format(printedLitres) + "L - " + // + PERCENTAGE_FORMAT.format(currentProgressToNextStep) + "% - " + + minutesToTimeString(minutesToNextStep); + } + + + private BigDecimal getMinutesToNextStep(double currentLitres) { // berechne Liter benötigt bis zum nächsten 0.25er Schritt double litresToNextStep = 0.25 - (currentLitres % 0.25); // berechne Minuten benötigt für 1 Liter double minutesPerLitre = totalMinutes / 2.0; // berechne Minuten benötigt bis zum nächsten 0.25er Schritt - return (long) (minutesPerLitre * litresToNextStep) + 1; + return BigDecimal.valueOf((minutesPerLitre * litresToNextStep) + 1); } - private static String minutesToTimeString(long minutes) { // DEBUG - var minutesBD = BigDecimal.valueOf(minutes); - BigDecimal[] hoursAndMinutes = minutesBD.divideAndRemainder(MINS_PER_HOUR_BD, MC_INTEGER); + private String minutesToTimeString(BigDecimal minutes) { + BigDecimal[] hoursAndMinutes = minutes.divideAndRemainder(MINS_PER_HOUR_BD, MC_INTEGER); return LocalTime.of(hoursAndMinutes[0].intValue(), hoursAndMinutes[1].intValue()).format(TIME_FORMATTER); } + + + private void waitUntilNextMinute() { + try { + var now = LocalTime.now(); + var oneMinuteLater = now.plusMinutes(1).truncatedTo(ChronoUnit.MINUTES); + /* We wait whole seconds to not make it overly complicated. + That results in cut milliseconds: if we would have to wait 1 second and 526 milliseconds, we wait only 1 second. + So, adjust for ignored milliseconds, add +1 second 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.MILLISECONDS.sleep(100L); // DEBUG + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + throw new RuntimeException(ie); + } + } } diff --git a/LoadingBar.java b/LoadingBar.java index 776dcde..5bce1fe 100644 --- a/LoadingBar.java +++ b/LoadingBar.java @@ -33,6 +33,7 @@ public class LoadingBar { private static final long MAX_NUMBER_WORK_MINS = 8L * MINS_PER_HOUR; private static final int LINE_LENGTH = 100; private static final MathContext MC_INTEGER = new MathContext(1, RoundingMode.HALF_EVEN); + private static final BigDecimal ONE_HUNDRED_PERCENT = BigDecimal.valueOf(100); private enum DaySection { @@ -65,6 +66,24 @@ public class LoadingBar { } + private final LocalTime startTime; + private LocalTime endTime; + private long totalMinutes; + private BigDecimal totalMinutesBD; + + + private LoadingBar(LocalTime startTime) { + this.startTime = startTime; + } + + + private void setEndTime(LocalTime endTime) { + this.endTime = endTime; + this.totalMinutes = startTime.until(endTime, ChronoUnit.MINUTES); + this.totalMinutesBD = BigDecimal.valueOf(totalMinutes); + } + + public static void main(String[] args) throws IOException { if (args.length == 0) { askParametersAndRun(); @@ -78,10 +97,13 @@ public class LoadingBar { var br = new BufferedReader(new InputStreamReader(System.in, StandardCharsets.UTF_8)); print("Ankunftszeit: "); var startTime = LocalTime.parse(br.readLine(), TIME_FORMATTER).truncatedTo(ChronoUnit.MINUTES); - if (startTime.until(LocalTime.now(), ChronoUnit.MINUTES) < DEFAULT_NUMBER_WORK_MINS_BEFORE_LUNCH) { - handleMittagspause(br, startTime); + var lb = new LoadingBar(startTime); + if (lb.hasMittagspauseArrived()) { + handleMittagspause(br, lb); + lb.showLoadingBar(); } - handleZapfenstreich(br, startTime); + handleZapfenstreich(br, lb); + lb.showLoadingBar(); } @@ -95,33 +117,33 @@ public class LoadingBar { } - private static void handleMittagspause(BufferedReader br, LocalTime startTime) throws IOException { + private static void handleMittagspause(BufferedReader br, LoadingBar lb) throws IOException { print("Mittagspause verschieben um (optional): "); String mittagspauseOffsetRaw = br.readLine(); if (mittagspauseOffsetRaw != null && !mittagspauseOffsetRaw.isBlank()) { var mittagspauseOffset = Integer.parseInt(mittagspauseOffsetRaw); - showLoadingBarMittagspause(startTime, mittagspauseOffset); + lb.initMittagspause(mittagspauseOffset); return; } print("Mittagspause um (optional): "); String manualMittagspauseRaw = br.readLine(); if (manualMittagspauseRaw != null && !manualMittagspauseRaw.isBlank()) { var manualMittagspause = LocalTime.parse(manualMittagspauseRaw, TIME_FORMATTER).truncatedTo(ChronoUnit.MINUTES); - showLoadingBarMittagspause(startTime, manualMittagspause); + lb.initMittagspause(manualMittagspause); } else { - showLoadingBarMittagspause(startTime); + lb.initMittagspause(); } } - private static void handleZapfenstreich(BufferedReader br, LocalTime startTime) throws IOException { + private static void handleZapfenstreich(BufferedReader br, LoadingBar lb) throws IOException { print("Mittagspause hat gedauert (optional): "); String mittagspauseDurationRaw = br.readLine(); Integer mittagspauseDuration = null; if (mittagspauseDurationRaw != null && !mittagspauseDurationRaw.isBlank()) { mittagspauseDuration = Integer.valueOf(mittagspauseDurationRaw); } - LocalTime vorlaeufigeEndzeit = startTime.plusMinutes(MAX_NUMBER_WORK_MINS) + LocalTime vorlaeufigeEndzeit = lb.startTime.plusMinutes(MAX_NUMBER_WORK_MINS) .plusMinutes(mittagspauseDuration != null ? mittagspauseDuration : MIN_LUNCH_DURATION); println("Endzeit: " + TIME_FORMATTER.format(vorlaeufigeEndzeit)); print("Feierabend verschieben um (optional): "); @@ -129,7 +151,7 @@ public class LoadingBar { Integer zapfenstreichOffset = null; if (zapfenstreichOffsetRaw != null && !zapfenstreichOffsetRaw.isBlank()) { zapfenstreichOffset = Integer.valueOf(zapfenstreichOffsetRaw); - showLoadingBarZapfenstreich(startTime, mittagspauseDuration, zapfenstreichOffset); + lb.initZapfenstreich(mittagspauseDuration, zapfenstreichOffset); return; } print("Manuelle Uhrzeit Feierabend (optional): "); @@ -137,18 +159,24 @@ public class LoadingBar { LocalTime manualZapfenstreich = null; if (manualZapfenstreichRaw != null && !manualZapfenstreichRaw.isBlank()) { manualZapfenstreich = LocalTime.parse(manualZapfenstreichRaw, TIME_FORMATTER).truncatedTo(ChronoUnit.MINUTES); - showLoadingBarZapfenstreich(startTime, mittagspauseDuration, manualZapfenstreich); - return; + lb.initZapfenstreich(mittagspauseDuration, manualZapfenstreich); + } else { + lb.initZapfenstreich(mittagspauseDuration); } - showLoadingBarZapfenstreich(startTime, mittagspauseDuration); } private static void parseParametersAndRun(String[] args) { + LoadingBar lb = getLoadingBarFromCLI(args); + lb.showLoadingBar(); + } + + + private static LoadingBar getLoadingBarFromCLI(String[] args) { String nextArg = args[0]; if ("--help".equals(nextArg)) { printHelp(); - return; + System.exit(1); } verifyMinimumNumberOfArgs(args); verifyTimeFormat(nextArg, "Erstes Argument"); @@ -156,34 +184,33 @@ public class LoadingBar { nextArg = args[1]; var section = DaySection.byParam(nextArg); verifyDaySection(section, nextArg); - if (section == DaySection.MITTAG) { - handleMittagspause(args, startTime); - } else { - handleZapfenstreich(args, startTime); - } + return section == DaySection.MITTAG ? getLoadingBarMittagspause(args, startTime) : getLoadingBarZapfenstreich(args, startTime); } - private static void handleMittagspause(String[] args, LocalTime startTime) { + private static LoadingBar getLoadingBarMittagspause(String[] args, LocalTime startTime) { + var lb = new LoadingBar(startTime); if (args.length == 2) { - showLoadingBarMittagspause(startTime); - return; + lb.initMittagspause(); + return lb; } String nextArg = args[2]; if (OFFSET_PATTERN.matcher(nextArg).matches()) { - showLoadingBarMittagspause(startTime, Integer.parseInt(nextArg)); - return; + lb.initMittagspause(Integer.parseInt(nextArg)); + return lb; } verifyTimeFormat(nextArg, "Argument nach " + DaySection.MITTAG.getParam()); var manualMittagspause = LocalTime.parse(nextArg, TIME_FORMATTER).truncatedTo(ChronoUnit.MINUTES); - showLoadingBarMittagspause(startTime, manualMittagspause); + lb.initMittagspause(manualMittagspause); + return lb; } - private static void handleZapfenstreich(String[] args, LocalTime startTime) { + private static LoadingBar getLoadingBarZapfenstreich(String[] args, LocalTime startTime) { + var lb = new LoadingBar(startTime); if (args.length == 2) { - showLoadingBarZapfenstreich(startTime); - return; + lb.initZapfenstreich(); + return lb; } String nextArg = args[2]; LocalTime maxZapfenstreich = null; @@ -199,13 +226,13 @@ public class LoadingBar { } if (args.length == 3) { if (maxZapfenstreich == null && endTimeOffset == 0) { - showLoadingBarZapfenstreich(startTime, lunchDuration); + lb.initZapfenstreich(lunchDuration); } else if (maxZapfenstreich == null) { - showLoadingBarZapfenstreich(startTime, lunchDuration, endTimeOffset); + lb.initZapfenstreich(lunchDuration, endTimeOffset); } else { - showLoadingBarZapfenstreich(startTime, lunchDuration, maxZapfenstreich); + lb.initZapfenstreich(lunchDuration, maxZapfenstreich); } - return; + return lb; } nextArg = args[3]; if (lunchDuration == null) { @@ -215,12 +242,13 @@ public class LoadingBar { if (maxZapfenstreich == null && !OFFSET_PATTERN.matcher(nextArg).matches()) { verifyTimeFormat(nextArg, "Letztes Argument nach " + DaySection.ZAPFENSTREICH.getParam() + " und Mittagspausendauer"); maxZapfenstreich = LocalTime.parse(nextArg, TIME_FORMATTER).truncatedTo(ChronoUnit.MINUTES); - showLoadingBarZapfenstreich(startTime, lunchDuration, maxZapfenstreich); - return; + lb.initZapfenstreich(lunchDuration, maxZapfenstreich); + return lb; } verifyOffsetFormat(nextArg, "Letztes Argument nach " + DaySection.ZAPFENSTREICH.getParam() + " und Enduhrzeit"); endTimeOffset = Integer.parseInt(nextArg); - showLoadingBarZapfenstreich(startTime, lunchDuration, endTimeOffset); + lb.initZapfenstreich(lunchDuration, endTimeOffset); + return lb; } @@ -302,55 +330,65 @@ public class LoadingBar { } - private static void showLoadingBarMittagspause(LocalTime startTime) { - showLoadingBarMittagspause(startTime, null); + private boolean hasMittagspauseArrived() { + return startTime.until(LocalTime.now(), ChronoUnit.MINUTES) < DEFAULT_NUMBER_WORK_MINS_BEFORE_LUNCH; } - private static void showLoadingBarMittagspause(LocalTime startTime, int endTimeOffset) { - LocalTime endTime = startTime.plusMinutes(DEFAULT_NUMBER_WORK_MINS_BEFORE_LUNCH + endTimeOffset); - LocalTime trueEndTime = endTime.isAfter(LATEST_LUNCH_TIME) ? LATEST_LUNCH_TIME : endTime; - showLoadingBar(startTime, trueEndTime); + private void initMittagspause() { + LocalTime defaultEndTime = startTime.plusMinutes(DEFAULT_NUMBER_WORK_MINS_BEFORE_LUNCH); + realInitMittagspause(defaultEndTime); } - 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 void initMittagspause(int endTimeOffset) { + LocalTime offsetEndTime = startTime.plusMinutes(DEFAULT_NUMBER_WORK_MINS_BEFORE_LUNCH + endTimeOffset); + realInitMittagspause(offsetEndTime); } - private static void showLoadingBarZapfenstreich(LocalTime startTime) { - showLoadingBarZapfenstreich(startTime, -1, 0); + private void initMittagspause(LocalTime manualEndTime) { + LocalTime effectiveEndTime = manualEndTime != null ? manualEndTime : startTime.plusMinutes(DEFAULT_NUMBER_WORK_MINS_BEFORE_LUNCH); + realInitMittagspause(effectiveEndTime); } - private static void showLoadingBarZapfenstreich(LocalTime startTime, Integer manualLunchDuration) { - showLoadingBarZapfenstreich(startTime, manualLunchDuration, 0); + private void realInitMittagspause(LocalTime theoreticalEndTime) { + setEndTime(theoreticalEndTime.isAfter(LATEST_LUNCH_TIME) ? LATEST_LUNCH_TIME : theoreticalEndTime); } - private static void showLoadingBarZapfenstreich(LocalTime startTime, Integer manualLunchDuration, int endTimeOffset) { + private void initZapfenstreich() { + LocalTime trueEndTime = startTime.plusMinutes(MAX_NUMBER_WORK_MINS + MIN_LUNCH_DURATION); + realInitZapfenstreich(MIN_LUNCH_DURATION, trueEndTime); + } + + + private void initZapfenstreich(Integer manualLunchDuration) { + initZapfenstreich(manualLunchDuration, 0); + } + + + private void initZapfenstreich(Integer manualLunchDuration, int endTimeOffset) { long minLunchDuration = getMinLunchDuration(endTimeOffset); long realLunchDuration = getRealLunchDuration(manualLunchDuration, minLunchDuration); LocalTime trueEndTime = startTime.plusMinutes(MAX_NUMBER_WORK_MINS + realLunchDuration + endTimeOffset); - realShowLoadingBarZapfenstreich(startTime, realLunchDuration, trueEndTime); + realInitZapfenstreich(realLunchDuration, trueEndTime); } - private static void showLoadingBarZapfenstreich(LocalTime startTime, Integer manualLunchDuration, LocalTime manualEndTime) { + private void initZapfenstreich(Integer manualLunchDuration, LocalTime manualEndTime) { LocalTime trueEndTime = manualEndTime; - long minLunchDuration = getMinLunchDuration(startTime, trueEndTime); + long minLunchDuration = getMinLunchDuration(trueEndTime); long realLunchDuration = getRealLunchDuration(manualLunchDuration, minLunchDuration); if (trueEndTime == null) { trueEndTime = startTime.plusMinutes(MAX_NUMBER_WORK_MINS + realLunchDuration); } - realShowLoadingBarZapfenstreich(startTime, realLunchDuration, trueEndTime); + realInitZapfenstreich(realLunchDuration, trueEndTime); } - private static long getMinLunchDuration(int endTimeOffset) { + private long getMinLunchDuration(int endTimeOffset) { if (endTimeOffset == 0) { return MIN_LUNCH_DURATION; } @@ -360,17 +398,17 @@ public class LoadingBar { } - private static long getMinLunchDuration(LocalTime startTime, LocalTime endTime) { - if (endTime == null) { + private long getMinLunchDuration(LocalTime manualEndTime) { + if (manualEndTime == null) { return MIN_LUNCH_DURATION; } - long totalDuration = startTime.until(endTime, ChronoUnit.MINUTES); + long totalDuration = startTime.until(manualEndTime, ChronoUnit.MINUTES); long effectiveLunchDuration = totalDuration - MAX_NUMBER_WORK_MINS_WITHOUT_LUNCH; return getMinLunchDuration(effectiveLunchDuration); } - private static long getMinLunchDuration(long effectiveLunchDuration) { + private long getMinLunchDuration(long effectiveLunchDuration) { if (effectiveLunchDuration < 0) { effectiveLunchDuration = 0; } @@ -378,53 +416,61 @@ public class LoadingBar { } - private static long getRealLunchDuration(Integer manualLunchDuration, long minLunchDuration) { + private long getRealLunchDuration(Integer manualLunchDuration, long minLunchDuration) { return manualLunchDuration != null && manualLunchDuration >= minLunchDuration ? manualLunchDuration : minLunchDuration; } - private static void realShowLoadingBarZapfenstreich(LocalTime startTime, long manualLunchDuration, LocalTime endTime) { - if (manualLunchDuration > 0) { - var totalWorkTime = LocalTime.MIDNIGHT.plusMinutes(startTime.until(endTime, ChronoUnit.MINUTES) - manualLunchDuration); + private void realInitZapfenstreich(long effectiveLunchDuration, LocalTime effectiveEndTime) { + if (effectiveLunchDuration > 0) { + var totalWorkTime = LocalTime.MIDNIGHT.plusMinutes(startTime.until(effectiveEndTime, ChronoUnit.MINUTES) - effectiveLunchDuration); print("Arbeitszeit: " + TIME_FORMATTER.format(totalWorkTime) + "; "); } - showLoadingBar(startTime, endTime); + setEndTime(effectiveEndTime); } - private static void showLoadingBar(LocalTime startTime, LocalTime endTime) { - long totalMinutes = startTime.until(endTime, ChronoUnit.MINUTES); + private void showLoadingBar() { long passedMinutes = startTime.until(LocalTime.now().truncatedTo(ChronoUnit.MINUTES), ChronoUnit.MINUTES); + // long passedMinutes = 0; // DEBUG if (passedMinutes > totalMinutes) { passedMinutes = totalMinutes; } else if (passedMinutes < 0) { var now = LocalTime.now().truncatedTo(ChronoUnit.SECONDS); println("!ACHTUNG! Startzeit \"" + startTime + ":00\" liegt in der Zukunft von jetzt an (" + now + ") gesehen."); } - println(minutesToTimeString(totalMinutes) + " gesamt; Endzeit: " + TIME_FORMATTER.format(endTime)); + println(minutesToTimeString(totalMinutesBD) + " gesamt; Endzeit: " + TIME_FORMATTER.format(endTime)); while (passedMinutes < totalMinutes) { - print(fillLoadingBar(totalMinutes, 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); - } + print(fillLoadingBar(passedMinutes, true)); + waitUntilNextMinute(); passedMinutes++; } - println(fillLoadingBar(totalMinutes, passedMinutes, false)); + println(fillLoadingBar(passedMinutes, false)); } - private static String fillLoadingBar(long totalMinutes, long passedMinutes, boolean progressive) { - var nonNegativePassedMinutes = passedMinutes < 0 ? 0 : passedMinutes; - BigDecimal wholePercentage = BigDecimal.valueOf(100) + private void waitUntilNextMinute() { + try { + var now = LocalTime.now(); + var oneMinuteLater = now.plusMinutes(1).truncatedTo(ChronoUnit.MINUTES); + /* We wait whole seconds to not make it overly complicated. + That results in cut milliseconds: if we would have to wait 1 second and 526 milliseconds, we wait only 1 second. + So, adjust for ignored milliseconds, add +1 second 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.MILLISECONDS.sleep(100L); // DEBUG + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + throw new RuntimeException(ie); + } + } + + + private String fillLoadingBar(long passedMinutes, boolean progressive) { + var nonNegativePassedMinutes = BigDecimal.valueOf(passedMinutes < 0 ? 0 : passedMinutes); + BigDecimal wholePercentage = ONE_HUNDRED_PERCENT // kind of reverse dreisatz to avoid having e.g. 99.9999 instead of 100 % - .multiply(BigDecimal.valueOf(nonNegativePassedMinutes)) - .divide(BigDecimal.valueOf(totalMinutes), MathContext.DECIMAL64); + .multiply(nonNegativePassedMinutes) + .divide(totalMinutesBD, MathContext.DECIMAL64); int numberOfEquals = wholePercentage.intValue(); var sb = new StringBuilder("["); for (int i = 0; i < LINE_LENGTH; i++) { @@ -434,7 +480,7 @@ public class LoadingBar { sb.append("-"); } } - long remainingMinutes = totalMinutes - nonNegativePassedMinutes; + BigDecimal remainingMinutes = totalMinutesBD.subtract(nonNegativePassedMinutes); sb.append("] ").append(PERCENTAGE_FORMAT.format(wholePercentage)).append("% ") .append(minutesToTimeString(nonNegativePassedMinutes)).append(" - ").append(minutesToTimeString(remainingMinutes)); if (progressive) { @@ -444,9 +490,8 @@ public class LoadingBar { } - private static String minutesToTimeString(long minutes) { - var minutesBD = BigDecimal.valueOf(minutes); - BigDecimal[] hoursAndMinutes = minutesBD.divideAndRemainder(MINS_PER_HOUR_BD, MC_INTEGER); + private String minutesToTimeString(BigDecimal minutes) { + BigDecimal[] hoursAndMinutes = minutes.divideAndRemainder(MINS_PER_HOUR_BD, MC_INTEGER); return LocalTime.of(hoursAndMinutes[0].intValue(), hoursAndMinutes[1].intValue()).format(TIME_FORMATTER); } } diff --git a/SimpleLoadingBar.java b/SimpleLoadingBar.java index 7ff799a..927932d 100644 --- a/SimpleLoadingBar.java +++ b/SimpleLoadingBar.java @@ -19,6 +19,20 @@ public class SimpleLoadingBar { private static final BigDecimal MINS_PER_HOUR_BD = BigDecimal.valueOf(MINS_PER_HOUR); private static final int LINE_LENGTH = 100; private static final MathContext MC_INTEGER = new MathContext(1, RoundingMode.HALF_EVEN); + private static final BigDecimal ONE_HUNDRED_PERCENT = BigDecimal.valueOf(100); + + private final String title; + private final LocalTime startTime; + private LocalTime endTime; + private long totalMinutes; + private BigDecimal totalMinutesBD; + + + private SimpleLoadingBar(LocalTime startTime, LocalTime endTime, String title) { + this.startTime = startTime; + setEndTime(endTime); + this.title = initTitle(title); + } public static void main(String[] args) { @@ -28,13 +42,12 @@ public class SimpleLoadingBar { } verifyMinimumNumberOfArgs(args); String nextArg = args[0]; - verifyTimeFormat(nextArg, "Erstes Argument"); // FSFIXME package - var firstTime = LocalTime.parse(nextArg, TIME_FORMATTER); // FSFIXME package + verifyTimeFormat(nextArg, "Erstes Argument"); + var firstTime = LocalTime.parse(nextArg, TIME_FORMATTER); LocalTime startTime = null; String title = ""; LocalTime endTime = null; if (args.length > 1) { - startTime = firstTime; nextArg = args[1]; if ("-msg".equals(nextArg)) { title = args.length > 2 ? args[2] : title; @@ -46,23 +59,32 @@ public class SimpleLoadingBar { if (endTime == null) { startTime = LocalTime.now(); endTime = firstTime; + } else { + startTime = 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); + new SimpleLoadingBar(startTime, endTime, title).showLoadingBar(); return; } // if there are 3 arguments, the third will be discarded. boolean hasTitleArg = args.length > 3 && "-msg".equals(args[2]); title = hasTitleArg ? args[3] : title; - title = title.isBlank() ? fallbackTitle : title; - showLoadingBar(startTime, endTime, title); + new SimpleLoadingBar(startTime, endTime, title).showLoadingBar(); + } + + + private static void println(Object o) { + System.out.println(o); + } + + + private static void print(Object o) { + System.out.print(o); } private static void printHelp() { - System.out.println("Mögliche Argumente für LoadingBar:\n" + println("Mögliche Argumente für LoadingBar:\n" + "Startzeit, Endzeit, Endnachricht (Optional)\n" + TIME_FORMAT + " " + TIME_FORMAT + " -msg \n" + "Endzeit (Startzeit = jetzt), Endnachricht (Optional)\n" @@ -75,7 +97,7 @@ public class SimpleLoadingBar { if (args.length >= 1) { return; } - System.out.println("Mindestens 1 Argument muss gegeben sein."); + println("Mindestens 1 Argument muss gegeben sein."); printHelp(); System.exit(1); } @@ -85,44 +107,52 @@ public class SimpleLoadingBar { if (TIME_PATTERN.matcher(param).matches()) { return; } - System.out.println(errMsgPrefix + " \"" + param + "\" muss Uhrzeitformat (" + TIME_FORMAT + ") entsprechen."); + 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 void setEndTime(LocalTime endTime) { + this.endTime = endTime; + this.totalMinutes = startTime.until(endTime, ChronoUnit.MINUTES); + this.totalMinutesBD = BigDecimal.valueOf(totalMinutes); } - private static String fillLoadingBar(long initialMinutes, long passedMinutes, boolean progressive) { - BigDecimal wholePercentage = BigDecimal.valueOf(100) - .multiply(BigDecimal.valueOf(passedMinutes) // kind of reverse dreisatz to avoid to have e.g. 99.9999 instead of 100 % - .divide(BigDecimal.valueOf(initialMinutes), MathContext.DECIMAL64)); - long remainingMinutes = initialMinutes - passedMinutes; + private String initTitle(String inputTitle) { + String fallbackTitle = "Ende! Endzeit " + TIME_FORMATTER.format(endTime) + " erreicht."; + String effectiveTitle = inputTitle == null || inputTitle.isBlank() ? fallbackTitle : inputTitle; + String separator = "*".repeat(effectiveTitle.length()); + return separator + "\n" + effectiveTitle + "\n" + separator; + } + + + private void showLoadingBar() { + long passedMinutes = startTime.until(LocalTime.now().truncatedTo(ChronoUnit.MINUTES), ChronoUnit.MINUTES); + // long passedMinutes = 0; // DEBUG + if (passedMinutes > totalMinutes) { + passedMinutes = totalMinutes; + } else if (passedMinutes < 0) { + var now = LocalTime.now().truncatedTo(ChronoUnit.SECONDS); + println("!ACHTUNG! Startzeit \"" + startTime + ":00\" liegt in der Zukunft von jetzt an (" + now + ") gesehen."); + } + println(minutesToTimeString(totalMinutesBD) + " gesamt; Endzeit: " + TIME_FORMATTER.format(endTime)); + while (passedMinutes < totalMinutes) { + print(fillLoadingBar(passedMinutes, true)); + waitUntilNextMinute(); + passedMinutes++; + } + println(fillLoadingBar(passedMinutes, false)); + println(title); + } + + + private String fillLoadingBar(long passedMinutes, boolean progressive) { + var nonNegativePassedMinutes = BigDecimal.valueOf(passedMinutes < 0 ? 0 : passedMinutes); + BigDecimal wholePercentage = ONE_HUNDRED_PERCENT + // kind of reverse dreisatz to avoid having e.g. 99.9999 instead of 100 % + .multiply(nonNegativePassedMinutes) + .divide(totalMinutesBD, MathContext.DECIMAL64); int numberOfEquals = wholePercentage.intValue(); var sb = new StringBuilder("["); for (int i = 0; i < LINE_LENGTH; i++) { @@ -132,8 +162,9 @@ public class SimpleLoadingBar { sb.append("-"); } } + BigDecimal remainingMinutes = totalMinutesBD.subtract(nonNegativePassedMinutes); sb.append("] ").append(PERCENTAGE_FORMAT.format(wholePercentage)).append("% ") - .append(minutesToTimeString(passedMinutes)).append(" - ").append(minutesToTimeString(remainingMinutes)); + .append(minutesToTimeString(nonNegativePassedMinutes)).append(" - ").append(minutesToTimeString(remainingMinutes)); if (progressive) { sb.append("\r"); } @@ -141,15 +172,24 @@ public class SimpleLoadingBar { } - private static String minutesToTimeString(long minutes) { - var minutesBD = BigDecimal.valueOf(minutes); - BigDecimal[] hoursAndMinutes = minutesBD.divideAndRemainder(MINS_PER_HOUR_BD, MC_INTEGER); + private String minutesToTimeString(BigDecimal minutes) { + BigDecimal[] hoursAndMinutes = minutes.divideAndRemainder(MINS_PER_HOUR_BD, MC_INTEGER); return LocalTime.of(hoursAndMinutes[0].intValue(), hoursAndMinutes[1].intValue()).format(TIME_FORMATTER); } - private static String formatTitle(String title) { - String separator = "*".repeat(title.length()); - return separator + "\n" + title + "\n" + separator; + private void waitUntilNextMinute() { + try { + var now = LocalTime.now(); + var oneMinuteLater = now.plusMinutes(1).truncatedTo(ChronoUnit.MINUTES); + /* We wait whole seconds to not make it overly complicated. + That results in cut milliseconds: if we would have to wait 1 second and 526 milliseconds, we wait only 1 second. + So, adjust for ignored milliseconds, add +1 second 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.MILLISECONDS.sleep(100L); // DEBUG + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + throw new RuntimeException(ie); + } } }