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); boolean passedMinutesZero = true; if (lb.hasMittagspauseArrived()) { // if (lb.hasMittagspauseArrived(passedMinutesZero)) { // DEBUG handleMittagspause(br, lb); lb.showLoadingBar(); // lb.showLoadingBarDebug(passedMinutesZero); // DEBUG } handleZapfenstreich(br, lb); lb.showLoadingBar(); // lb.showLoadingBarDebug(passedMinutesZero); // 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(); // wlb.showLoadingBarDebug(true); // 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); 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 hasMittagspauseArrived(false); } protected boolean hasMittagspauseArrived(boolean debugWithPassedMinutesZero) { return getPassedMinutes(debugWithPassedMinutesZero) < 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 effectiveEndTime) { if (effectiveLunchDuration > 0) { var totalWorkTime = LocalTime.MIDNIGHT.plusMinutes(getStartTime().until(effectiveEndTime, ChronoUnit.MINUTES) - effectiveLunchDuration); print("Arbeitszeit: " + FormatTools.TIME_FORMATTER.format(totalWorkTime) + "; "); } setEndTime(effectiveEndTime); } }