diff --git a/zeitlaeufer/src/main/java/de/szimnau/AbstractLoadingBar.java b/zeitlaeufer/src/main/java/de/szimnau/AbstractLoadingBar.java new file mode 100644 index 0000000..fc34220 --- /dev/null +++ b/zeitlaeufer/src/main/java/de/szimnau/AbstractLoadingBar.java @@ -0,0 +1,148 @@ +package de.szimnau; + +import de.szimnau.tools.FormatTools; +import java.math.BigDecimal; +import java.time.LocalTime; +import java.time.temporal.ChronoUnit; +import java.util.concurrent.TimeUnit; + +import static de.szimnau.tools.CommonTools.print; +import static de.szimnau.tools.CommonTools.println; + + +public abstract class AbstractLoadingBar { + + private final LocalTime startTime; + private LocalTime endTime; + private long totalMinutes; + private BigDecimal totalMinutesBD; + + + protected AbstractLoadingBar(LocalTime startTime) { + this.startTime = startTime; + } + + + protected AbstractLoadingBar(LocalTime startTime, LocalTime endTime) { + this.startTime = startTime; + setEndTime(endTime); + } + + + protected AbstractLoadingBar(LocalTime startTime, long totalMinutes) { + this.startTime = startTime; + this.endTime = startTime.plusMinutes(totalMinutes); + this.totalMinutes = totalMinutes; + this.totalMinutesBD = BigDecimal.valueOf(totalMinutes); + } + + + protected LocalTime getStartTime() { + return startTime; + } + + + protected LocalTime getEndTime() { + return endTime; + } + + + protected void setEndTime(LocalTime endTime) { + this.endTime = endTime; + this.totalMinutes = startTime.until(endTime, ChronoUnit.MINUTES); + this.totalMinutesBD = BigDecimal.valueOf(totalMinutes); + } + + + protected long getTotalMinutes() { + return totalMinutes; + } + + + protected BigDecimal getTotalMinutesBD() { + return totalMinutesBD; + } + + + protected void showLoadingBar() { + showLoadingBar(false, false, 0); + } + + + protected void showLoadingBarDebug() { + showLoadingBar(true, true, 100L); + } + + + protected void showLoadingBarDebug(long millisWaiting) { + showLoadingBar(true, true, millisWaiting); + } + + + protected void showLoadingBarDebug(boolean passedMinutesZero) { + showLoadingBar(true, passedMinutesZero, 100L); + } + + + protected void showLoadingBarDebug(boolean passedMinutesZero, long millisWaiting) { + showLoadingBar(true, passedMinutesZero, millisWaiting); + } + + + private void showLoadingBar(boolean debug, boolean passedMinutesZero, long millisWaiting) { + long passedMinutes = startTime.until(LocalTime.now().truncatedTo(ChronoUnit.MINUTES), ChronoUnit.MINUTES); + if (debug && passedMinutesZero) { + passedMinutes = 0; + } + 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(FormatTools.minutesToTimeString(totalMinutes) + " gesamt; Endzeit: " + FormatTools.TIME_FORMATTER.format(endTime)); + while (passedMinutes < totalMinutes) { + print(fillLoadingBar(passedMinutes, true)); + waitUntilNextMinute(debug, millisWaiting); + passedMinutes++; + } + println(fillLoadingBar(passedMinutes, false)); + } + + + protected abstract String fillLoadingBar(long passedMinutes, boolean progressive); + + + protected void waitUntilNextMinute() { + waitUntilNextMinute(false, 0L); + } + + + protected void waitUntilNextMinuteDebug() { + waitUntilNextMinute(true, 100L); + } + + + protected void waitUntilNextMinuteDebug(long millisWaiting) { + waitUntilNextMinute(true, millisWaiting); + } + + + private void waitUntilNextMinute(boolean debug, long millisWaiting) { + try { + if (debug) { + TimeUnit.MILLISECONDS.sleep(millisWaiting); + return; + } + 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); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + throw new RuntimeException(ie); + } + } +} diff --git a/zeitlaeufer/src/main/java/de/szimnau/AbstractProgressBar.java b/zeitlaeufer/src/main/java/de/szimnau/AbstractProgressBar.java new file mode 100644 index 0000000..3ab75a9 --- /dev/null +++ b/zeitlaeufer/src/main/java/de/szimnau/AbstractProgressBar.java @@ -0,0 +1,50 @@ +package de.szimnau; + +import de.szimnau.tools.FormatTools; +import java.math.BigDecimal; +import java.math.MathContext; +import java.time.LocalTime; + + +public abstract class AbstractProgressBar extends AbstractLoadingBar { + + protected static final int LINE_LENGTH = 100; + protected static final BigDecimal ONE_HUNDRED_PERCENT = BigDecimal.valueOf(100); + + + protected AbstractProgressBar(LocalTime startTime) { + super(startTime); + } + + + protected AbstractProgressBar(LocalTime startTime, LocalTime endTime) { + super(startTime, endTime); + } + + + @Override + protected 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(getTotalMinutesBD(), MathContext.DECIMAL64); + int numberOfEquals = wholePercentage.intValue(); + var sb = new StringBuilder("["); + for (int i = 0; i < LINE_LENGTH; i++) { + if (i < numberOfEquals) { + sb.append("="); + } else { + sb.append("-"); + } + } + BigDecimal remainingMinutes = getTotalMinutesBD().subtract(nonNegativePassedMinutes); + sb.append("] ").append(FormatTools.PERCENTAGE_FORMAT.format(wholePercentage)).append("% ") + .append(FormatTools.minutesToTimeString(nonNegativePassedMinutes)).append(" - ") + .append(FormatTools.minutesToTimeString(remainingMinutes)); + if (progressive) { + sb.append("\r"); + } + return sb.toString(); + } +} diff --git a/zeitlaeufer/src/main/java/de/szimnau/DrinkingBar.java b/zeitlaeufer/src/main/java/de/szimnau/DrinkingBar.java new file mode 100644 index 0000000..9eab88f --- /dev/null +++ b/zeitlaeufer/src/main/java/de/szimnau/DrinkingBar.java @@ -0,0 +1,65 @@ +package de.szimnau; + +import de.szimnau.tools.CommonTools; +import de.szimnau.tools.FormatTools; +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.IOException; +import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; +import java.text.DecimalFormat; +import java.time.LocalTime; +import java.time.temporal.ChronoUnit; + +import static de.szimnau.tools.CommonTools.print; + + +public class DrinkingBar extends AbstractLoadingBar { + + private static final int MINS_PER_HALF_HOUR = CommonTools.MINS_PER_HOUR / 2; + private static final int MINUTES_BEFORE_PAUSE = 4 * CommonTools.MINS_PER_HOUR + MINS_PER_HALF_HOUR; + private static final int MINUTES_WITH_PAUSE = 6 * CommonTools.MINS_PER_HOUR; + private static final DecimalFormat LITER_FORMAT = new DecimalFormat("0.00"); + + + protected DrinkingBar(LocalTime startTime) { + super(startTime, 8 * CommonTools.MINS_PER_HOUR + MINS_PER_HALF_HOUR); + } + + + 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(), FormatTools.TIME_FORMATTER).truncatedTo(ChronoUnit.MINUTES); + long totalMinutes = 8 * CommonTools.MINS_PER_HOUR + MINS_PER_HALF_HOUR; + var db = new DrinkingBar(startTime); + db.showLoadingBar(); + } + + + @Override + protected 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 / getTotalMinutes() * 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 - " + + FormatTools.PERCENTAGE_FORMAT.format(currentProgressToNextStep) + "% - " + + FormatTools.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 = getTotalMinutes() / 2.0; + // berechne Minuten benötigt bis zum nächsten 0.25er Schritt + return BigDecimal.valueOf((minutesPerLitre * litresToNextStep) + 1); + } +} diff --git a/zeitlaeufer/src/main/java/de/szimnau/LoadingBar.java b/zeitlaeufer/src/main/java/de/szimnau/LoadingBar.java new file mode 100644 index 0000000..04a7e15 --- /dev/null +++ b/zeitlaeufer/src/main/java/de/szimnau/LoadingBar.java @@ -0,0 +1,403 @@ +package de.szimnau; + +import de.szimnau.tools.CommonTools; +import de.szimnau.tools.FormatTools; +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.time.LocalTime; +import java.time.temporal.ChronoUnit; +import java.util.*; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import static de.szimnau.tools.CommonTools.print; +import static de.szimnau.tools.CommonTools.println; + + +public class LoadingBar extends AbstractProgressBar { + + private static final Pattern LUNCH_DURATION_PATTERN = Pattern.compile("\\d+"); + private static final Pattern OFFSET_PATTERN = Pattern.compile("[+-]\\d+"); + private static final int MIN_LUNCH_DURATION = 30; + private static final LocalTime LATEST_LUNCH_TIME = LocalTime.of(13, 30); + private static final long DEFAULT_NUMBER_WORK_MINS_BEFORE_LUNCH = 5L * CommonTools.MINS_PER_HOUR; + private static final int MAX_NUMBER_WORK_MINS_WITHOUT_LUNCH = 6 * CommonTools.MINS_PER_HOUR; + private static final long MAX_NUMBER_WORK_MINS = 8L * CommonTools.MINS_PER_HOUR; + + + private enum DaySection { + MITTAG("-m", "Mittag"), + ZAPFENSTREICH("-z", "Zapfenstreich"); + + private final String param; + private final String description; + + + 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; + } + } + + + protected LoadingBar(LocalTime startTime) { + super(startTime); + } + + + public static void main(String[] args) throws IOException { + if (args.length == 0) { + askParametersAndRun(); + } else { + parseParametersAndRun(args); + } + } + + + private static void askParametersAndRun() throws IOException { + var br = new BufferedReader(new InputStreamReader(System.in, StandardCharsets.UTF_8)); + print("Ankunftszeit: "); + var startTime = LocalTime.parse(br.readLine(), FormatTools.TIME_FORMATTER).truncatedTo(ChronoUnit.MINUTES); + var lb = new LoadingBar(startTime); + if (lb.hasMittagspauseArrived()) { + handleMittagspause(br, lb); + lb.showLoadingBar(); + // lb.showLoadingBarDebug(); // DEBUG + } + handleZapfenstreich(br, lb); + lb.showLoadingBar(); + // lb.showLoadingBarDebug(); // DEBUG + } + + + protected 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); + lb.initMittagspause(mittagspauseOffset); + return; + } + print("Mittagspause um (optional): "); + String manualMittagspauseRaw = br.readLine(); + if (manualMittagspauseRaw != null && !manualMittagspauseRaw.isBlank()) { + var manualMittagspause = LocalTime.parse(manualMittagspauseRaw, FormatTools.TIME_FORMATTER).truncatedTo(ChronoUnit.MINUTES); + lb.initMittagspause(manualMittagspause); + } else { + lb.initMittagspause(); + } + } + + + protected 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 = lb.getStartTime().plusMinutes(MAX_NUMBER_WORK_MINS) + .plusMinutes(mittagspauseDuration != null ? mittagspauseDuration : MIN_LUNCH_DURATION); + println("Endzeit: " + FormatTools.TIME_FORMATTER.format(vorlaeufigeEndzeit)); + print("Feierabend verschieben um (optional): "); + String zapfenstreichOffsetRaw = br.readLine(); + Integer zapfenstreichOffset = null; + if (zapfenstreichOffsetRaw != null && !zapfenstreichOffsetRaw.isBlank()) { + zapfenstreichOffset = Integer.valueOf(zapfenstreichOffsetRaw); + lb.initZapfenstreich(mittagspauseDuration, zapfenstreichOffset); + return; + } + print("Manuelle Uhrzeit Feierabend (optional): "); + String manualZapfenstreichRaw = br.readLine(); + LocalTime manualZapfenstreich = null; + if (manualZapfenstreichRaw != null && !manualZapfenstreichRaw.isBlank()) { + manualZapfenstreich = LocalTime.parse(manualZapfenstreichRaw, FormatTools.TIME_FORMATTER).truncatedTo(ChronoUnit.MINUTES); + lb.initZapfenstreich(mittagspauseDuration, manualZapfenstreich); + } else { + lb.initZapfenstreich(mittagspauseDuration); + } + } + + + private static void parseParametersAndRun(String[] args) { + LoadingBar lb = getLoadingBarFromCLI(args); + lb.showLoadingBar(); + // lb.showLoadingBarDebug(); // DEBUG + } + + + protected static LoadingBar getLoadingBarFromCLI(String[] args) { + String nextArg = args[0]; + if ("--help".equals(nextArg)) { + printHelp(); + System.exit(1); + } + verifyMinimumNumberOfArgs(args); + verifyTimeFormat(nextArg, "Erstes Argument"); + var startTime = LocalTime.parse(nextArg, FormatTools.TIME_FORMATTER).truncatedTo(ChronoUnit.MINUTES); + nextArg = args[1]; + var section = DaySection.byParam(nextArg); + verifyDaySection(section, nextArg); + LoadingBar lb; + return section == DaySection.MITTAG ? getLoadingBarMittagspause(args, startTime) : getLoadingBarZapfenstreich(args, startTime); + } + + + private static LoadingBar getLoadingBarMittagspause(String[] args, LocalTime startTime) { + var lb = new LoadingBar(startTime); + if (args.length == 2) { + lb.initMittagspause(); + return lb; + } + String nextArg = args[2]; + if (OFFSET_PATTERN.matcher(nextArg).matches()) { + lb.initMittagspause(Integer.parseInt(nextArg)); + return lb; + } + verifyTimeFormat(nextArg, "Argument nach " + DaySection.MITTAG.getParam()); + var manualMittagspause = LocalTime.parse(nextArg, FormatTools.TIME_FORMATTER).truncatedTo(ChronoUnit.MINUTES); + lb.initMittagspause(manualMittagspause); + return lb; + } + + + private static LoadingBar getLoadingBarZapfenstreich(String[] args, LocalTime startTime) { + var lb = new LoadingBar(startTime); + if (args.length == 2) { + lb.initZapfenstreich(); + return lb; + } + String nextArg = args[2]; + LocalTime maxZapfenstreich = null; + int endTimeOffset = 0; + Integer lunchDuration = null; + if (FormatTools.TIME_PATTERN.matcher(nextArg).matches()) { + maxZapfenstreich = LocalTime.parse(nextArg, FormatTools.TIME_FORMATTER).truncatedTo(ChronoUnit.MINUTES); + } else if (OFFSET_PATTERN.matcher(nextArg).matches()) { + endTimeOffset = Integer.parseInt(nextArg); + } else { + verifyDurationFormat(nextArg, "Argument nach " + DaySection.ZAPFENSTREICH.getParam()); + lunchDuration = Integer.parseInt(nextArg); + } + if (args.length == 3) { + if (maxZapfenstreich == null && endTimeOffset == 0) { + lb.initZapfenstreich(lunchDuration); + } else if (maxZapfenstreich == null) { + lb.initZapfenstreich(lunchDuration, endTimeOffset); + } else { + lb.initZapfenstreich(lunchDuration, maxZapfenstreich); + } + return lb; + } + nextArg = args[3]; + if (lunchDuration == null) { + 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, FormatTools.TIME_FORMATTER).truncatedTo(ChronoUnit.MINUTES); + lb.initZapfenstreich(lunchDuration, maxZapfenstreich); + return lb; + } + verifyOffsetFormat(nextArg, "Letztes Argument nach " + DaySection.ZAPFENSTREICH.getParam() + " und Enduhrzeit"); + endTimeOffset = Integer.parseInt(nextArg); + lb.initZapfenstreich(lunchDuration, endTimeOffset); + return lb; + } + + + private static void verifyMinimumNumberOfArgs(String[] args) { + if (args.length >= 2) { + return; + } + println("Mindestens 2 Argumente müssen gegeben sein."); + printHelp(); + System.exit(1); + } + + + private static void verifyTimeFormat(String param, String errMsgPrefix) { + verifyInputFormat(FormatTools.TIME_PATTERN, param, errMsgPrefix, "Uhrzeitformat (" + FormatTools.TIME_FORMAT + ")", false); + } + + + private static void verifyDurationFormat(String param, String errMsgPrefix) { + verifyInputFormat(LUNCH_DURATION_PATTERN, param, errMsgPrefix, "Minutenanzahl (ganze Zahl)", true); + } + + + private static void verifyOffsetFormat(String param, String errMsgPrefix) { + verifyInputFormat(OFFSET_PATTERN, param, errMsgPrefix, "Minutendifferenz (ganze Zahl mit Vorzeichen)", false); + } + + + private static void verifyInputFormat(Pattern pattern, String param, String errMsgPrefix, String firstInputPart, boolean timeInputPossible) { + if (pattern.matcher(param).matches()) { + return; + } + var possibleTimeInputPart = timeInputPossible ? " oder Uhrzeitformat (" + FormatTools.TIME_FORMAT + ")" : ""; + 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); + println("Argument nach Startzeit \"" + param + "\" muss Angabe für " + sectionDescsJoined + " sein."); + System.exit(1); + } + + + private static void printHelp() { + println("Mögliche Argumente für LoadingBar:\n" + + "Normalfall Vormittag (Mittagspause <= " + LATEST_LUNCH_TIME + ")\n" + + FormatTools.TIME_FORMAT + " " + DaySection.MITTAG.getParam() + "\n" + + "Vormittag mit expliziter Mittagspause (<= " + LATEST_LUNCH_TIME + ")\n" + + FormatTools.TIME_FORMAT + " " + DaySection.MITTAG.getParam() + " " + FormatTools.TIME_FORMAT + "\n" + + "Vormittag mit abweichender Minutenanzahl Arbeitszeit\n" + + FormatTools.TIME_FORMAT + " " + DaySection.MITTAG.getParam() + " -+mm\n" + + "Normalfall Nachmittag (Mittagspause " + MIN_LUNCH_DURATION + " min)\n" + + FormatTools.TIME_FORMAT + " " + DaySection.ZAPFENSTREICH.getParam() + "\n" + + "Nachmittag mit expliziter Länge Mittagspause (Mittagspause unter " + MIN_LUNCH_DURATION + " min wird auf " + MIN_LUNCH_DURATION + " min korrigiert)\n" + + FormatTools.TIME_FORMAT + " " + DaySection.ZAPFENSTREICH.getParam() + " mm\n" + + "Nachmittag mit explizitem Feierabend (Mittagspause je nach Minimum (s.u.))\n" + + FormatTools.TIME_FORMAT + " " + DaySection.ZAPFENSTREICH.getParam() + " " + FormatTools.TIME_FORMAT + "\n" + + "Nachmittag mit abweichender Minutenanzahl Arbeitszeit\n" + + FormatTools.TIME_FORMAT + " " + DaySection.ZAPFENSTREICH.getParam() + " -+mm\n" + + "Nachmittag mit explizitem Feierabend u. expliziter Länge Mittagspause (Mittagspause unter Minimum (s.u.) wird auf Minimum korrigiert)\n" + + FormatTools.TIME_FORMAT + " " + DaySection.ZAPFENSTREICH.getParam() + " mm " + FormatTools.TIME_FORMAT + "\n" + + "Nachmittag mit explizitem Feierabend u. abweichender Minutenanzahl Arbeitszeit\n" + + FormatTools.TIME_FORMAT + " " + DaySection.ZAPFENSTREICH.getParam() + " " + FormatTools.TIME_FORMAT + " -+mm\n\n" + + "Mittagspause minimum in Minuten:\n" + + " - bis " + MAX_NUMBER_WORK_MINS_WITHOUT_LUNCH + " min (" + + MAX_NUMBER_WORK_MINS_WITHOUT_LUNCH / CommonTools.MINS_PER_HOUR + " std): 0\n" + + " - bis " + MAX_NUMBER_WORK_MINS_WITHOUT_LUNCH + " min + " + + MIN_LUNCH_DURATION + " min: Arbeitszeit - " + MAX_NUMBER_WORK_MINS_WITHOUT_LUNCH + " min\n" + + " - ab " + MAX_NUMBER_WORK_MINS_WITHOUT_LUNCH + " min + " + MIN_LUNCH_DURATION + " min: " + + MIN_LUNCH_DURATION + " min\n" + ); + } + + + protected boolean hasMittagspauseArrived() { + return getStartTime().until(LocalTime.now(), ChronoUnit.MINUTES) < DEFAULT_NUMBER_WORK_MINS_BEFORE_LUNCH; + } + + + private void initMittagspause() { + LocalTime defaultEndTime = getStartTime().plusMinutes(DEFAULT_NUMBER_WORK_MINS_BEFORE_LUNCH); + realInitMittagspause(defaultEndTime); + } + + + private void initMittagspause(int endTimeOffset) { + LocalTime offsetEndTime = getStartTime().plusMinutes(DEFAULT_NUMBER_WORK_MINS_BEFORE_LUNCH + endTimeOffset); + realInitMittagspause(offsetEndTime); + } + + + private void initMittagspause(LocalTime manualEndTime) { + LocalTime effectiveEndTime = manualEndTime != null ? manualEndTime : getStartTime().plusMinutes(DEFAULT_NUMBER_WORK_MINS_BEFORE_LUNCH); + realInitMittagspause(effectiveEndTime); + } + + + private void realInitMittagspause(LocalTime theoreticalEndTime) { + setEndTime(theoreticalEndTime.isAfter(LATEST_LUNCH_TIME) ? LATEST_LUNCH_TIME : theoreticalEndTime); + } + + + private void initZapfenstreich() { + LocalTime trueEndTime = getStartTime().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 = getStartTime().plusMinutes(MAX_NUMBER_WORK_MINS + realLunchDuration + endTimeOffset); + realInitZapfenstreich(realLunchDuration, trueEndTime); + } + + + private void initZapfenstreich(Integer manualLunchDuration, LocalTime manualEndTime) { + LocalTime trueEndTime = manualEndTime; + long minLunchDuration = getMinLunchDuration(trueEndTime); + long realLunchDuration = getRealLunchDuration(manualLunchDuration, minLunchDuration); + if (trueEndTime == null) { + trueEndTime = getStartTime().plusMinutes(MAX_NUMBER_WORK_MINS + realLunchDuration); + } + realInitZapfenstreich(realLunchDuration, trueEndTime); + } + + + private long getMinLunchDuration(int endTimeOffset) { + if (endTimeOffset == 0) { + return MIN_LUNCH_DURATION; + } + long totalDuration = MAX_NUMBER_WORK_MINS + endTimeOffset; + long effectiveLunchDuration = totalDuration - MAX_NUMBER_WORK_MINS_WITHOUT_LUNCH; + return getMinLunchDuration(effectiveLunchDuration); + } + + + private long getMinLunchDuration(LocalTime manualEndTime) { + if (manualEndTime == null) { + return MIN_LUNCH_DURATION; + } + long totalDuration = getStartTime().until(manualEndTime, ChronoUnit.MINUTES); + long effectiveLunchDuration = totalDuration - MAX_NUMBER_WORK_MINS_WITHOUT_LUNCH; + return getMinLunchDuration(effectiveLunchDuration); + } + + + private long getMinLunchDuration(long effectiveLunchDuration) { + if (effectiveLunchDuration < 0) { + effectiveLunchDuration = 0; + } + return Math.min(effectiveLunchDuration, MIN_LUNCH_DURATION); + } + + + private long getRealLunchDuration(Integer manualLunchDuration, long minLunchDuration) { + return manualLunchDuration != null && manualLunchDuration >= minLunchDuration ? manualLunchDuration : minLunchDuration; + } + + + private void realInitZapfenstreich(long effectiveLunchDuration, LocalTime endTime) { + if (effectiveLunchDuration > 0) { + var totalWorkTime = LocalTime.MIDNIGHT.plusMinutes(getStartTime().until(endTime, ChronoUnit.MINUTES) - effectiveLunchDuration); + print("Arbeitszeit: " + FormatTools.TIME_FORMATTER.format(totalWorkTime) + "; "); + } + setEndTime(endTime); + } +} diff --git a/zeitlaeufer/src/main/java/de/szimnau/SimpleLoadingBar.java b/zeitlaeufer/src/main/java/de/szimnau/SimpleLoadingBar.java new file mode 100644 index 0000000..11d2789 --- /dev/null +++ b/zeitlaeufer/src/main/java/de/szimnau/SimpleLoadingBar.java @@ -0,0 +1,108 @@ +package de.szimnau; + +import de.szimnau.tools.FormatTools; +import java.time.LocalTime; +import java.util.*; + +import static de.szimnau.tools.CommonTools.println; + + +public class SimpleLoadingBar extends AbstractProgressBar { + + private final String title; + + + private SimpleLoadingBar(LocalTime startTime, LocalTime endTime, String title) { + super(startTime, endTime); + this.title = initTitle(title); + } + + + 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 firstTime = LocalTime.parse(nextArg, FormatTools.TIME_FORMATTER); + LocalTime startTime = null; + String title = ""; + LocalTime endTime = null; + if (args.length > 1) { + nextArg = args[1]; + if ("-msg".equals(nextArg)) { + title = args.length > 2 ? args[2] : title; + } else { + verifyTimeFormat(nextArg, "Zweites Argument"); + endTime = LocalTime.parse(nextArg, FormatTools.TIME_FORMATTER); + } + } + if (endTime == null) { + startTime = LocalTime.now(); + endTime = firstTime; + } else { + startTime = firstTime; + } + if (args.length == 2 || !title.isBlank()) { + 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; + new SimpleLoadingBar(startTime, endTime, title).showLoadingBar(); + } + + + private static void printHelp() { + println("Mögliche Argumente für LoadingBar:\n" + + "Startzeit, Endzeit, Endnachricht (Optional)\n" + + FormatTools.TIME_FORMAT + " " + FormatTools.TIME_FORMAT + " -msg \n" + + "Endzeit (Startzeit = jetzt), Endnachricht (Optional)\n" + + FormatTools.TIME_FORMAT + " -msg \n" + ); + } + + + private static void verifyMinimumNumberOfArgs(String[] args) { + if (args.length >= 1) { + return; + } + println("Mindestens 1 Argument muss gegeben sein."); + printHelp(); + System.exit(1); + } + + + private static void verifyTimeFormat(String param, String errMsgPrefix) { + if (FormatTools.TIME_PATTERN.matcher(param).matches()) { + return; + } + println(errMsgPrefix + " \"" + param + "\" muss Uhrzeitformat (" + FormatTools.TIME_FORMAT + ") entsprechen."); + System.exit(1); + } + + + private String initTitle(String inputTitle) { + String fallbackTitle = "Ende! Endzeit " + FormatTools.TIME_FORMATTER.format(getEndTime()) + " erreicht."; + String effectiveTitle = inputTitle == null || inputTitle.isBlank() ? fallbackTitle : inputTitle; + String separator = "*".repeat(effectiveTitle.length()); + return separator + "\n" + effectiveTitle + "\n" + separator; + } + + + @Override + protected void showLoadingBar() { + showLoadingBar(); + // showLoadingBarDebug(); // DEBUG + println(title); + } + + + private String formatTitle(String title) { + String separator = "*".repeat(title.length()); + return separator + "\n" + title + "\n" + separator; + } +} diff --git a/zeitlaeufer/src/main/java/de/szimnau/WorkLoadingBar.java b/zeitlaeufer/src/main/java/de/szimnau/WorkLoadingBar.java new file mode 100644 index 0000000..3e98e7f --- /dev/null +++ b/zeitlaeufer/src/main/java/de/szimnau/WorkLoadingBar.java @@ -0,0 +1,91 @@ +package de.szimnau; + +import de.szimnau.tools.CommonTools; +import de.szimnau.tools.FormatTools; +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.time.LocalTime; +import java.time.temporal.ChronoUnit; + +import static de.szimnau.tools.CommonTools.print; + + +public class WorkLoadingBar extends AbstractProgressBar { + + private final LoadingBar loadingBar; + private final DrinkingBar drinkingBar; + + + private WorkLoadingBar(LoadingBar loadingBar, DrinkingBar drinkingBar) { + super(loadingBar.getStartTime()); + this.loadingBar = loadingBar; + this.drinkingBar = drinkingBar; + } + + + public static void main(String[] args) throws IOException { + if (args.length == 0) { + askParametersAndRun(); + } else { + parseParametersAndRun(args); + } + } + + + private static void askParametersAndRun() throws IOException { + var br = new BufferedReader(new InputStreamReader(System.in, StandardCharsets.UTF_8)); + print("Ankunftszeit: "); + var startTime = LocalTime.parse(br.readLine(), FormatTools.TIME_FORMATTER).truncatedTo(ChronoUnit.MINUTES); + var lb = new LoadingBar(startTime); + var db = new DrinkingBar(startTime); + var wlb = new WorkLoadingBar(lb, db); + if (lb.hasMittagspauseArrived()) { + handleMittagspause(br, wlb); + wlb.showLoadingBar(); + // wlb.showLoadingBarDebug(); // DEBUG + } + handleZapfenstreich(br, wlb); + wlb.showLoadingBar(); + // wlb.showLoadingBarDebug(); // DEBUG + } + + + private static void handleMittagspause(BufferedReader br, WorkLoadingBar wlb) throws IOException { + LoadingBar.handleMittagspause(br, wlb.loadingBar); + wlb.setEndTime(wlb.loadingBar.getEndTime(), false); + } + + + private static void handleZapfenstreich(BufferedReader br, WorkLoadingBar wlb) throws IOException { + LoadingBar.handleZapfenstreich(br, wlb.loadingBar); + wlb.setEndTime(wlb.loadingBar.getEndTime(), true); + } + + + private static void parseParametersAndRun(String[] args) { + LoadingBar lb = LoadingBar.getLoadingBarFromCLI(args); + var db = new DrinkingBar(lb.getStartTime()); + var wlb = new WorkLoadingBar(lb, db); + wlb.setEndTime(wlb.loadingBar.getEndTime(), true); + wlb.showLoadingBar(); + // wlb.showLoadingBarDebug(); // DEBUG + } + + + protected void setEndTime(LocalTime endTime, boolean finalEndTime) { + setEndTime(endTime); + if (finalEndTime) { + drinkingBar.setEndTime(endTime); + } + } + + + @Override + protected String fillLoadingBar(long passedMinutes, boolean progressive) { + String filledLoadingBar = loadingBar.fillLoadingBar(passedMinutes, false); + filledLoadingBar += " | " + drinkingBar.fillLoadingBar(passedMinutes, false); + return filledLoadingBar + (progressive ? "\r" : ""); + } +} diff --git a/zeitlaeufer/src/main/java/de/szimnau/tools/CommonTools.java b/zeitlaeufer/src/main/java/de/szimnau/tools/CommonTools.java new file mode 100644 index 0000000..9395596 --- /dev/null +++ b/zeitlaeufer/src/main/java/de/szimnau/tools/CommonTools.java @@ -0,0 +1,28 @@ +package de.szimnau.tools; + +import java.lang.*; +import java.math.BigDecimal; +import java.math.MathContext; +import java.math.RoundingMode; +import java.util.*; + + +public class CommonTools { + + public static final int MINS_PER_HOUR = 60; + public static final BigDecimal MINS_PER_HOUR_BD = BigDecimal.valueOf(MINS_PER_HOUR); + public static final MathContext MC_INTEGER = new MathContext(1, RoundingMode.HALF_EVEN); + + + private CommonTools() {} + + + public static void print(Object o) { + System.out.print(o); + } + + + public static void println(Object o) { + System.out.println(o); + } +} diff --git a/zeitlaeufer/src/main/java/de/szimnau/tools/FormatTools.java b/zeitlaeufer/src/main/java/de/szimnau/tools/FormatTools.java new file mode 100644 index 0000000..2e9e23d --- /dev/null +++ b/zeitlaeufer/src/main/java/de/szimnau/tools/FormatTools.java @@ -0,0 +1,32 @@ +package de.szimnau.tools; + +import java.lang.*; +import java.math.BigDecimal; +import java.text.DecimalFormat; +import java.time.format.DateTimeFormatter; +import java.time.LocalTime; +import java.util.*; +import java.util.regex.Pattern; + + +public class FormatTools { + + public static final String TIME_FORMAT = "HH:mm"; + public static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern(TIME_FORMAT); + public static final Pattern TIME_PATTERN = Pattern.compile("(?>[01]\\d|2[0-4]):[0-5]\\d"); + public static final DecimalFormat PERCENTAGE_FORMAT = new DecimalFormat("00.00"); + + + private FormatTools() {} + + + public static String minutesToTimeString(long minutes) { + return minutesToTimeString(BigDecimal.valueOf(minutes)); + } + + + public static String minutesToTimeString(BigDecimal minutes) { + BigDecimal[] hoursAndMinutes = minutes.divideAndRemainder(CommonTools.MINS_PER_HOUR_BD, CommonTools.MC_INTEGER); + return LocalTime.of(hoursAndMinutes[0].intValue(), hoursAndMinutes[1].intValue()).format(TIME_FORMATTER); + } +}