Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it is easier if we just pass the EntryDate year as parameter here.


Preconditions.checkArgument(field.getTag().equals(FIELD_TAG_61), "unexpected field tag '%s'", field.getTag());

Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
Original file line number Diff line number Diff line change
@@ -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.
* <p>
* 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.
* <p>
* 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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -19,13 +21,19 @@
public class MT940PageReader extends PageReader<MT940Page> {

private final SwiftFieldReader fieldReader;
private final EntryDateResolutionStrategy entryDateResolutionStrategy;


public MT940PageReader(Reader textReader) {
this(textReader, new EarlierMonthImpliesFollowingYearEntryDateResolutionStrategy());
}

public MT940PageReader(Reader textReader, EntryDateResolutionStrategy entryDateResolutionStrategy) {

Preconditions.checkArgument(textReader != null, "textReader can't be null");

this.fieldReader = new SwiftFieldReader(textReader);
this.entryDateResolutionStrategy = entryDateResolutionStrategy;
}

@Override
Expand Down Expand Up @@ -94,7 +102,7 @@ public MT940Page read() throws SwiftMessageParseException {
break;
}
case StatementLine.FIELD_TAG_61: {
StatementLine statementLine = StatementLine.of(currentField);
StatementLine statementLine = StatementLine.of(currentField, entryDateResolutionStrategy);
transactionList.add(new TransactionGroup(statementLine, null));
nextValidFieldSet = ImmutableSet.of(
InformationToAccountOwner.FIELD_TAG_86,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import com.qoomon.banking.swift.submessage.exception.PageParserException;
import com.qoomon.banking.swift.submessage.field.*;
import com.qoomon.banking.swift.submessage.field.subfield.DebitCreditMark;
import com.qoomon.banking.swift.submessage.field.subfield.EarlierMonthImpliesFollowingYearEntryDateResolutionStrategy;
import com.qoomon.banking.swift.submessage.field.subfield.EntryDateResolutionStrategy;
import org.joda.money.BigMoney;
import org.joda.money.CurrencyUnit;

Expand All @@ -25,13 +27,19 @@
public class MT942PageReader extends PageReader<MT942Page> {

private final SwiftFieldReader fieldReader;
private final EntryDateResolutionStrategy entryDateResolutionStrategy;


public MT942PageReader(Reader textReader) {
this(textReader, new EarlierMonthImpliesFollowingYearEntryDateResolutionStrategy());
}

public MT942PageReader(Reader textReader, EntryDateResolutionStrategy entryDateResolutionStrategy) {

Preconditions.checkArgument(textReader != null, "textReader can't be null");

this.fieldReader = new SwiftFieldReader(textReader);
this.entryDateResolutionStrategy = entryDateResolutionStrategy;
}

@Override
Expand Down Expand Up @@ -149,7 +157,7 @@ public MT942Page read() throws SwiftMessageParseException {
break;
}
case StatementLine.FIELD_TAG_61: {
StatementLine statementLine = StatementLine.of(currentField);
StatementLine statementLine = StatementLine.of(currentField, entryDateResolutionStrategy);
transactionList.add(new TransactionGroup(statementLine, null));
nextValidFieldSet = ImmutableSet.of(
StatementLine.FIELD_TAG_61,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

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.EarlierMonthImpliesFollowingYearEntryDateResolutionStrategy;
import com.qoomon.banking.swift.submessage.field.subfield.TransactionTypeIdentificationCode;
import org.assertj.core.api.Assertions;
import org.junit.Test;
Expand All @@ -22,7 +23,7 @@ public void of() throws Exception {
GeneralField generalField = new GeneralField(StatementLine.FIELD_TAG_61, "160130" + "C" + "R" + "123,456" + "NSTO" + "abcdef" + "//xyz" + "\nfoobar");

// When
StatementLine field = StatementLine.of(generalField);
StatementLine field = StatementLine.of(generalField, new EarlierMonthImpliesFollowingYearEntryDateResolutionStrategy());

// Then
assertThat(field.getTag()).isEqualTo(generalField.getTag());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.qoomon.banking.swift.submessage.field.subfield;

import org.junit.Test;

import java.time.LocalDate;
import java.time.MonthDay;

import static org.assertj.core.api.Assertions.assertThat;

public class ShortestDeltaEntryDateResolutionStrategyTest {
private final EntryDateResolutionStrategy strategy = new ShortestDeltaEntryDateResolutionStrategy();

@Test
public void of_WHEN_smallest_delta_between_entry_date_and_value_date_does_not_span_December_THEN_entry_year_is_same_as_value_year() {
// When
LocalDate entryDate = strategy.resolve(MonthDay.parse("--05-12"), LocalDate.parse("2016-03-31"));

// Then
assertThat(entryDate).isEqualTo(LocalDate.parse("2016-05-12"));
}

@Test
public void of_WHEN_smallest_delta_does_not_span_December_and_entry_date_before_value_date_THEN_entry_year_is_same_as_value_year() {
// When
LocalDate entryDate = strategy.resolve(MonthDay.parse("--03-12"), LocalDate.parse("2016-05-31"));

// Then
assertThat(entryDate).isEqualTo(LocalDate.parse("2016-03-12"));
}

@Test
public void of_WHEN_smallest_delta_spans_December_and_entry_date_before_value_date_THEN_entry_year_is_next_year() {
// When
LocalDate entryDate = strategy.resolve(MonthDay.parse("--01-12"), LocalDate.parse("2015-10-31"));

// Then
assertThat(entryDate).isEqualTo(LocalDate.parse("2016-01-12"));
}

@Test
public void of_WHEN_smallest_delta_spans_December_and_entry_date_after_value_date_THEN_entry_year_is_previous_year() {
// When
LocalDate entryDate = strategy.resolve(MonthDay.parse("--11-12"), LocalDate.parse("2016-03-31"));

// Then
assertThat(entryDate).isEqualTo(LocalDate.parse("2015-11-12"));
}
}