Java tutorial
/* * ---------------------------------------------------------------------------- * "THE BEER-WARE LICENSE" (Revision 42): * <martinbraun123@aol.com> wrote this file. As long as you retain this notice you * can do whatever you want with this stuff. If we meet some day, and you think * this stuff is worth it, you can buy me a beer in return. Martin Braun * ---------------------------------------------------------------------------- */ package com.github.s4ke.worktimegen; import java.io.BufferedReader; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.lang.reflect.Type; import java.net.URL; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Random; import java.util.Set; import java.util.TreeSet; import java.util.stream.Collectors; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; import org.apache.poi.hssf.usermodel.HSSFSheet; import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.ss.usermodel.Row; /** * Created by Martin on 09.03.2016. */ public class Main { private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd"); //stackoverflow: http://stackoverflow.com/questions/11697581/check-if-a-list-of-dates-contains-a-particular-date private static class DateComparator implements Comparator<Date> { public int compare(Date d1, Date d2) { return DATE_FORMAT.format(d1).compareTo(DATE_FORMAT.format(d2)); } } private static final String BUNDESLAND = "BY"; private static final Gson GSON = new Gson(); private static final String[][] ADDITIONAL_RANGES = { { "2014-12-24", "2015-01-06" }, { "2015-12-24", "2016-01-06" }, { "2016-05-17" }, { "2016-12-24", "2017-01-06" }, }; public static Set<Date> getHolidaysForYear(int year, String bundesland) throws IOException { String jsonFromWebsite = downloadHTMLFromWebsite(new URL("http://feiertage.jarmedia.de/api/?jahr=" + year)); //hacky-hack-hack, but works Type type = new TypeToken<Map<String, Map<String, Info>>>() { }.getType(); Map<String, Map<String, Info>> allInfo = GSON.fromJson(jsonFromWebsite, type); Set<Date> dates = allInfo.get(bundesland).entrySet().stream().map(entry -> entry.getValue().datum) .collect(Collectors.toSet()); TreeSet<Date> ret = new TreeSet<>(new DateComparator()); ret.addAll(dates); return ret; } public static Set<Date> getAdditionalExceptions() { Set<Date> ret = new TreeSet<>(new DateComparator()); for (String[] tuple : ADDITIONAL_RANGES) { if (tuple.length == 2) { Date start = GSON.fromJson(tuple[0], Date.class); Date end = GSON.fromJson(tuple[1], Date.class); ret.add(start); ret.add(end); GregorianCalendar startCalendar = new GregorianCalendar(); startCalendar.setTime(start); GregorianCalendar endCalendar = new GregorianCalendar(); endCalendar.setTime(end); GregorianCalendar calendar = new GregorianCalendar(); calendar.setTime(start); while (calendar.before(endCalendar)) { ret.add(calendar.getTime()); calendar.add(Calendar.DAY_OF_YEAR, 1); } } else if (tuple.length == 1) { ret.add(GSON.fromJson(tuple[0], Date.class)); } else { throw new AssertionError(); } } return ret; } public static Set<Date> generateAllWorkingDatesForMonth(int year, int month) throws IOException { Set<Date> ret = new TreeSet<>(new DateComparator()); Set<Date> holidays = getHolidaysForYear(year, BUNDESLAND); Set<Date> vorlesungsfrei = getAdditionalExceptions(); GregorianCalendar gregorianCalendar = new GregorianCalendar(); gregorianCalendar.set(year, month, 1, 0, 0, 0); GregorianCalendar end = new GregorianCalendar(); int endYear = year; int endMonth = month + 1; if (endMonth == 12) { endMonth = 0; endYear = endYear + 1; } end.set(endYear, endMonth, 1, 0, 0, 0); System.out.println(end.getTime()); while (gregorianCalendar.before(end)) { Date date = gregorianCalendar.getTime(); int dayOfWeek = gregorianCalendar.get(GregorianCalendar.DAY_OF_WEEK); if (dayOfWeek != GregorianCalendar.SATURDAY && dayOfWeek != GregorianCalendar.SUNDAY) { if (!holidays.contains(date) && !vorlesungsfrei.contains(date)) { ret.add(date); } } gregorianCalendar.add(GregorianCalendar.DAY_OF_YEAR, 1); } return ret; } private static String downloadHTMLFromWebsite(URL url) throws IOException { StringBuilder builder = new StringBuilder(); try (InputStream is = url.openStream(); BufferedReader br = new BufferedReader(new InputStreamReader(is))) { String line; while ((line = br.readLine()) != null) { builder.append(line); } } return builder.toString(); } public static class Work { private String date; private int startHours; private int startMinutes; private int endHours; private int endMinutes; public int minutes() { return (endHours - startHours) * 60 + (endMinutes - startMinutes); } @Override public String toString() { return "Work{" + "date='" + date + '\'' + ", startHours=" + startHours + ", startMinutes=" + startMinutes + ", endHours=" + endHours + ", endMinutes=" + endMinutes + '}'; } } public static class Info { private Date datum; private String hinweis; public String getHinweis() { return hinweis; } public void setHinweis(String hinweis) { this.hinweis = hinweis; } public Date getDatum() { return datum; } public void setDatum(Date datum) { this.datum = datum; } @Override public String toString() { return "Info{" + "datum='" + datum + '\'' + ", hinweis='" + hinweis + '\'' + '}'; } } private static final double MINIMAL_WORK_TIME_PER_DAY = 15 / 60; private static final int MIN_START_HOURS = 8; private static final int MAX_END_HOURS = 19; private static final int MAX_HOURS_PER_DAY = 6; private static final Random random = new Random(); public static List<Work> scheduleWork(int year, int month, double hours, double maxHoursPerWeek) throws IOException { List<Work> ret = new ArrayList<>(); Set<Date> workingDaysInMonth = generateAllWorkingDatesForMonth(year, month); //split into weeks Map<Integer, Set<Date>> splitIntoWeeks = new HashMap<>(); for (Date date : workingDaysInMonth) { GregorianCalendar calendar = new GregorianCalendar(); calendar.setTime(date); int weekOfMonth = calendar.get(GregorianCalendar.WEEK_OF_MONTH); splitIntoWeeks.computeIfAbsent(weekOfMonth, (_$) -> new TreeSet<>(new DateComparator())).add(date); } //schedule the general workload over the days double dayCount = workingDaysInMonth.size(); double perDay = hours / dayCount; double maxPerDay = 2.2 * perDay; double diffPerDay = maxPerDay - perDay; int maxStartHours = MAX_END_HOURS - (int) Math.ceil(perDay); int diffStartHours = maxStartHours - MIN_START_HOURS; if (perDay > MAX_HOURS_PER_DAY) { throw new AssertionError("perDay > MAX_HOURS_PER_DAY"); } if (perDay > maxHoursPerWeek) { throw new AssertionError("perDay > maxHoursPerWeek"); } if (maxPerDay > maxHoursPerWeek) { throw new AssertionError("maxPerDay > maxHoursPerWeek"); } if (maxPerDay > MAX_HOURS_PER_DAY) { throw new AssertionError("maxPerPay > maxHoursPerDay"); } if (5 * perDay > maxHoursPerWeek) { throw new AssertionError("5 * perDay > maxHoursPerWeek"); } int totalMinutes = 0; for (Map.Entry<Integer, Set<Date>> entry : splitIntoWeeks.entrySet()) { //calculate the budget for this week double budget = entry.getValue().size() * perDay; //shuffle the dates so we get arbitrary shuffling //of dates that are more work-intensive List<Date> dates = new ArrayList<>(); dates.addAll(entry.getValue()); Collections.shuffle(dates); for (Date date : dates) { double additionalWork = random.nextDouble() * diffPerDay; double workHours = perDay + additionalWork; boolean shouldBreak = false; if (workHours > budget) { workHours = budget; shouldBreak = true; } budget -= workHours; //we don't want days with a too low working time if (budget < MINIMAL_WORK_TIME_PER_DAY && workHours < maxPerDay) { workHours += budget; budget = 0; shouldBreak = true; } Work workObj = new Work(); workObj.date = DATE_FORMAT.format(date); workObj.startHours = MIN_START_HOURS + random.nextInt(diffStartHours); int endHours = workObj.startHours + (int) Math.floor(workHours); workObj.startMinutes = random.nextInt(60); int endMinutes = workObj.startMinutes + (int) Math.round((workHours - Math.floor(workHours)) * 60); if (endMinutes >= 60) { endMinutes -= 60; endHours += 1; } workObj.endHours = endHours; workObj.endMinutes = endMinutes; totalMinutes += workObj.minutes(); System.out.println("working " + workHours + " hours (" + workObj.minutes() + " minutes ) on " + date + ", workObj: " + workObj); ret.add(workObj); if (shouldBreak) { break; } } } if (Math.abs(totalMinutes / 60d - hours) > 0.1) { throw new AssertionError("zu groe Abweichung vom Vertrag!"); } System.out.println(totalMinutes / 60d); Collections.sort(ret, (o1, o2) -> o1.date.compareTo(o2.date)); return ret; } public static void generateExcelSheet(int year, int month, List<Work> workObjs) throws IOException { try (InputStream is = Main.class.getResourceAsStream("/template_urlaub.xls")) { HSSFWorkbook workbook = new HSSFWorkbook(is); HSSFSheet sheet = workbook.getSheetAt(0); GregorianCalendar calendar = new GregorianCalendar(); calendar.set(year, month - 1, 1); sheet.getRow(7).getCell(2).setCellValue(DATE_FORMAT.format(calendar.getTime())); calendar.set(year, month - 1, calendar.getActualMaximum(Calendar.DAY_OF_MONTH)); sheet.getRow(7).getCell(4).setCellValue(DATE_FORMAT.format(calendar.getTime())); int startRow = 11; int endRow = 33; if (workObjs.size() > (endRow - startRow)) { throw new AssertionError("template has too few rows"); } int curRow = startRow; for (Work work : workObjs) { Row row = sheet.getRow(curRow); row.getCell(0).setCellValue(work.date); row.getCell(1).setCellValue(pad(work.startHours) + ":" + pad(work.startMinutes)); row.getCell(2).setCellValue(pad(work.endHours) + ":" + pad(work.endMinutes)); ++curRow; } try (FileOutputStream fos = new FileOutputStream( new File("zeiterfassung_braun_" + year + "_" + month + ".xls"))) { workbook.write(fos); } } } public static String pad(int num) { if (num < 10) { return "0" + num; } else { return String.valueOf(num); } } public static void main(String args[]) throws IOException { List<Work> workObjs; int year = 2015; for (int month = 3; month < 8; ++month) { System.out.println(workObjs = scheduleWork(year, month, 36.5, 20)); generateExcelSheet(year, month + 1, workObjs); } } }