diff --git a/src/main/java/com/qoomon/banking/swift/submessage/field/StatementLine.java b/src/main/java/com/qoomon/banking/swift/submessage/field/StatementLine.java index 6f47a7c..59b15d5 100644 --- a/src/main/java/com/qoomon/banking/swift/submessage/field/StatementLine.java +++ b/src/main/java/com/qoomon/banking/swift/submessage/field/StatementLine.java @@ -5,9 +5,7 @@ import com.qoomon.banking.swift.notation.FieldNotationParseException; import com.qoomon.banking.swift.notation.SwiftDecimalFormatter; import com.qoomon.banking.swift.notation.SwiftNotation; -import com.qoomon.banking.swift.submessage.field.subfield.DebitCreditMark; -import com.qoomon.banking.swift.submessage.field.subfield.DebitCreditType; -import com.qoomon.banking.swift.submessage.field.subfield.TransactionTypeIdentificationCode; +import com.qoomon.banking.swift.submessage.field.subfield.*; import java.math.BigDecimal; import java.time.LocalDate; @@ -100,6 +98,10 @@ public StatementLine(LocalDate valueDate, } public static StatementLine of(GeneralField field) throws FieldNotationParseException { + return of(field, new EarlierMonthImpliesFollowingYearEntryDateResolutionStrategy()); + } + + public static StatementLine of(GeneralField field, EntryDateResolutionStrategy entryDateResolutionStrategy) throws FieldNotationParseException { Preconditions.checkArgument(field.getTag().equals(FIELD_TAG_61), "unexpected field tag '%s'", field.getTag()); @@ -110,11 +112,7 @@ public static StatementLine of(GeneralField field) throws FieldNotationParseExce // calculate entry date if (subFields.get(1) != null) { MonthDay entryMonthDay = MonthDay.parse(subFields.get(1), ENTRY_DATE_FORMATTER); - // calculate entry year - int entryYear = entryMonthDay.getMonthValue() >= valueDate.getMonthValue() - ? valueDate.getYear() - : valueDate.getYear() + 1; - entryDate = entryMonthDay.atYear(entryYear); + entryDate = entryDateResolutionStrategy.resolve(entryMonthDay, valueDate); } DebitCreditType debitCreditType; DebitCreditMark debitCreditMark; diff --git a/src/main/java/com/qoomon/banking/swift/submessage/field/subfield/EarlierMonthImpliesFollowingYearEntryDateResolutionStrategy.java b/src/main/java/com/qoomon/banking/swift/submessage/field/subfield/EarlierMonthImpliesFollowingYearEntryDateResolutionStrategy.java new file mode 100644 index 0000000..8e1848d --- /dev/null +++ b/src/main/java/com/qoomon/banking/swift/submessage/field/subfield/EarlierMonthImpliesFollowingYearEntryDateResolutionStrategy.java @@ -0,0 +1,14 @@ +package com.qoomon.banking.swift.submessage.field.subfield; + +import java.time.LocalDate; +import java.time.MonthDay; + +public class EarlierMonthImpliesFollowingYearEntryDateResolutionStrategy implements EntryDateResolutionStrategy { + @Override + public LocalDate resolve(MonthDay entryMonthDay, LocalDate valueDate) { + int entryYear = entryMonthDay.getMonthValue() >= valueDate.getMonthValue() + ? valueDate.getYear() + : valueDate.getYear() + 1; + return entryMonthDay.atYear(entryYear); + } +} diff --git a/src/main/java/com/qoomon/banking/swift/submessage/field/subfield/EntryDateResolutionStrategy.java b/src/main/java/com/qoomon/banking/swift/submessage/field/subfield/EntryDateResolutionStrategy.java new file mode 100644 index 0000000..6163f35 --- /dev/null +++ b/src/main/java/com/qoomon/banking/swift/submessage/field/subfield/EntryDateResolutionStrategy.java @@ -0,0 +1,18 @@ +package com.qoomon.banking.swift.submessage.field.subfield; + +import java.time.LocalDate; +import java.time.MonthDay; + +/** + * Strategy to determine the correct {@link com.qoomon.banking.swift.submessage.field.StatementLine} entry date. + * + * Statement line :61: sub field 2 contains an optional entry date: + * + * Notation: [4!n] + * Format: 'MMDD' + * + * Implementers of this strategy must determine the correct year of the entry date based on the the given value date. + */ +public interface EntryDateResolutionStrategy { + LocalDate resolve(MonthDay entryMonthDay, LocalDate valueDate); +} diff --git a/src/main/java/com/qoomon/banking/swift/submessage/field/subfield/ShortestDeltaEntryDateResolutionStrategy.java b/src/main/java/com/qoomon/banking/swift/submessage/field/subfield/ShortestDeltaEntryDateResolutionStrategy.java new file mode 100644 index 0000000..c4fe74a --- /dev/null +++ b/src/main/java/com/qoomon/banking/swift/submessage/field/subfield/ShortestDeltaEntryDateResolutionStrategy.java @@ -0,0 +1,47 @@ +package com.qoomon.banking.swift.submessage.field.subfield; + +import java.time.LocalDate; +import java.time.MonthDay; + +import static java.lang.Math.abs; +import static java.lang.Math.min; + +/** + * Resolve entry date based on the shortest delta between the entry date and the value date. + *
+ * This strategy views the {@link MonthDay} timeline as a circle, like the hours on a clock. If the shortest delta + * around the circle does not span 31 December, the entry date's year is the same as that of the value date. Otherwise + * it is either the previous year if the entry date is before the value date on the circular timeline, or the next year + * if the entry date is after the value date. + *
+ * Limitations: This strategy can not account for an entry date more than 12 months in the past (or future).
+ */
+public class ShortestDeltaEntryDateResolutionStrategy implements EntryDateResolutionStrategy {
+ @Override
+ public LocalDate resolve(MonthDay entryMonthDay, LocalDate valueDate) {
+ int e = LocalDate.from(entryMonthDay.adjustInto(valueDate)).getDayOfYear();
+ int v = valueDate.getDayOfYear();
+ int lengthOfYear = valueDate.lengthOfYear();
+
+ int valueToEntryDelta = v - e;
+ boolean valueAfterEntry = valueToEntryDelta > 0;
+ int shortestDelta = min(abs(valueToEntryDelta), lengthOfYear - abs(valueToEntryDelta));
+
+ boolean spans31December = valueAfterEntry ?
+ e + shortestDelta != v :
+ v + shortestDelta != e;
+
+ int entryYear;
+ if (!spans31December) {
+ entryYear = valueDate.getYear();
+ } else {
+ if (valueAfterEntry) {
+ entryYear = valueDate.getYear() + 1;
+ } else {
+ entryYear = valueDate.getYear() - 1;
+ }
+ }
+
+ return entryMonthDay.atYear(entryYear);
+ }
+}
diff --git a/src/main/java/com/qoomon/banking/swift/submessage/mt940/MT940PageReader.java b/src/main/java/com/qoomon/banking/swift/submessage/mt940/MT940PageReader.java
index dd1dbd6..663dbbd 100644
--- a/src/main/java/com/qoomon/banking/swift/submessage/mt940/MT940PageReader.java
+++ b/src/main/java/com/qoomon/banking/swift/submessage/mt940/MT940PageReader.java
@@ -7,6 +7,8 @@
import com.qoomon.banking.swift.submessage.PageSeparator;
import com.qoomon.banking.swift.submessage.exception.PageParserException;
import com.qoomon.banking.swift.submessage.field.*;
+import com.qoomon.banking.swift.submessage.field.subfield.EarlierMonthImpliesFollowingYearEntryDateResolutionStrategy;
+import com.qoomon.banking.swift.submessage.field.subfield.EntryDateResolutionStrategy;
import java.io.Reader;
import java.util.LinkedList;
@@ -19,13 +21,19 @@
public class MT940PageReader extends PageReader