package name.abuchen.portfolio.snapshot; import java.io.IOException; import java.text.MessageFormat; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.time.format.FormatStyle; import java.util.function.Predicate; import name.abuchen.portfolio.Messages; import name.abuchen.portfolio.model.Transaction; import name.abuchen.portfolio.util.Interval; import name.abuchen.portfolio.util.TradeCalendar; public abstract class ReportingPeriod { private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM); protected final LocalDate startDate; protected final LocalDate endDate; public ReportingPeriod(LocalDate startDate, LocalDate endDate) { this.startDate = startDate; this.endDate = endDate; } public static final ReportingPeriod from(String code) throws IOException { char type = code.charAt(0); if (type == LastX.CODE) return new LastX(code); else if (type == LastXDays.CODE) return new LastXDays(code); else if (type == LastXTradingDays.CODE) return new LastXTradingDays(code); else if (type == FromXtoY.CODE) return new FromXtoY(code); else if (type == SinceX.CODE) return new SinceX(code); else if (type == YearX.CODE) return new YearX(code); // backward compatible if (code.charAt(code.length() - 1) == 'Y') return new LastX(Integer.parseInt(code.substring(0, code.length() - 1)), 0); throw new IOException(code); } public final LocalDate getStartDate() { return startDate; } public final LocalDate getEndDate() { return endDate; } public final Predicate<Transaction> containsTransaction() { return t -> t.getDate().isAfter(startDate) && !t.getDate().isAfter(endDate); } public final Interval toInterval() { // reported via forum: if the user selects as 'since' date something in // the future if (endDate.isBefore(startDate)) return Interval.of(endDate, startDate); else return Interval.of(startDate, endDate); } public abstract void writeTo(StringBuilder buffer); public String getCode() { StringBuilder buf = new StringBuilder(); writeTo(buf); return buf.toString(); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((endDate == null) ? 0 : endDate.hashCode()); result = prime * result + ((startDate == null) ? 0 : startDate.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; ReportingPeriod other = (ReportingPeriod) obj; if (endDate == null) { if (other.endDate != null) return false; } else if (!endDate.equals(other.endDate)) return false; if (startDate == null) { if (other.startDate != null) return false; } else if (!startDate.equals(other.startDate)) return false; return true; } public static class LastX extends ReportingPeriod { private static final char CODE = 'L'; private final int years; private final int months; /* package */ LastX(String code) { this(Integer.parseInt(code.substring(1, code.indexOf('Y'))), // Integer.parseInt(code.substring(code.indexOf('Y') + 1))); } public LastX(int years, int months) { super(LocalDate.now().minusYears(years).minusMonths(months), LocalDate.now()); this.years = years; this.months = months; } @Override public void writeTo(StringBuilder buffer) { buffer.append(CODE).append(years).append('Y').append(months); } @Override public String toString() { StringBuilder buf = new StringBuilder(); if (years != 0) { buf.append(MessageFormat.format(Messages.LabelReportingPeriodYears, years)); if (months != 0) buf.append(", "); //$NON-NLS-1$ } if (months != 0) buf.append(MessageFormat.format(Messages.LabelReportingPeriodMonths, months)); return buf.toString(); } } public static class LastXDays extends ReportingPeriod { private static final char CODE = 'D'; private final int days; /* package */ LastXDays(String code) { this(Integer.parseInt(code.substring(1))); } public LastXDays(int days) { super(LocalDate.now().minusDays(days), LocalDate.now()); this.days = days; } @Override public void writeTo(StringBuilder buffer) { buffer.append(CODE).append(days); } @Override public String toString() { return MessageFormat.format(Messages.LabelReportingPeriodLastXDays, days); } } public static class LastXTradingDays extends ReportingPeriod { private static final char CODE = 'T'; private final int tradingDays; /* package */ LastXTradingDays(String code) { this(Integer.parseInt(code.substring(1))); } public LastXTradingDays(int tradingDays) { super(tradingDaysSince(LocalDate.now(), tradingDays), LocalDate.now()); this.tradingDays = tradingDays; } /* testing */ static final LocalDate tradingDaysSince(LocalDate start, int tradingDays) { TradeCalendar calendar = new TradeCalendar(); LocalDate date = start; int daysToGo = tradingDays; while (daysToGo > 0) { if (!calendar.isHoliday(date)) daysToGo--; date = date.minusDays(1); } while (calendar.isHoliday(date)) date = date.minusDays(1); return date; } @Override public void writeTo(StringBuilder buffer) { buffer.append(CODE).append(tradingDays); } @Override public String toString() { return MessageFormat.format(Messages.LabelReportingPeriodLastXTradingDays, tradingDays); } } public static class FromXtoY extends ReportingPeriod { private static final char CODE = 'F'; /* package */ FromXtoY(String code) { super(LocalDate.parse(code.substring(1, code.indexOf('_'))), LocalDate.parse(code.substring(code.indexOf('_') + 1))); } public FromXtoY(LocalDate startDate, LocalDate endDate) { super(startDate, endDate); } @Override public void writeTo(StringBuilder buffer) { buffer.append(CODE).append(getStartDate().toString()).append('_').append(getEndDate().toString()); } @Override public String toString() { return MessageFormat.format(Messages.LabelReportingPeriodFromXtoY, getStartDate().format(DATE_FORMATTER), getEndDate().format(DATE_FORMATTER)); } } public static class SinceX extends ReportingPeriod { private static final char CODE = 'S'; /* package */ SinceX(String code) { super(LocalDate.parse(code.substring(1)), LocalDate.now()); } public SinceX(LocalDate startDate) { super(startDate, LocalDate.now()); } @Override public void writeTo(StringBuilder buffer) { buffer.append(CODE).append(getStartDate().toString()); } @Override public String toString() { return MessageFormat.format(Messages.LabelReportingPeriodSince, getStartDate().format(DATE_FORMATTER)); } } public static class YearX extends ReportingPeriod { private static final char CODE = 'Y'; /* package */ YearX(String code) { super(LocalDate.of(Integer.parseInt(code.substring(1)) - 1, 12, 31), LocalDate.of(Integer.parseInt(code.substring(1)), 12, 31)); } public YearX(int year) { super(LocalDate.of(year - 1, 12, 31), LocalDate.of(year, 12, 31)); } @Override public void writeTo(StringBuilder buffer) { buffer.append(CODE).append(getEndDate().getYear()); } @Override public String toString() { return String.valueOf(getEndDate().getYear()); } } }