구글에서는 개발자에게 재무보고서를 제공하고 있습니다.
'예상판매실적 보고서', '수익 보고서', '대한민국 Play 잔액 차감 보고서'를 제공하는데, 이 중 실제 회계처리는 수익 보고서를 이용해야합니다.
그런데 수익 보고서 CSV파일을 분석해보니 조금 귀찮은 문제(타임존 처리, 필드 포맷 등)가 있습니다.
이에 간단히 프로그램을 만들었습니다.
프로그램은 한국 개발자에게 익숙한 java로 만들었습니다.
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVParser;
import org.apache.commons.csv.CSVRecord;
import org.apache.commons.lang3.StringUtils;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Locale;
import java.util.Map;
/**
* 구글 재무보고서 중에서 '수익'보고서 CSV파일을 읽어서 파싱
* - 구글 '재무보고서 수익'에 대한 설명: https://support.google.com/googleplay/android-developer/answer/6135870#zippy=%2C%EC%88%98%EC%9D%B5
* - apache commons-csv를 이용해서 CSV파일을 파싱( https://mvnrepository.com/artifact/org.apache.commons/commons-csv/1.9.0 )
*
* @author eomsh
*/
@Slf4j
public class EarningsReportFileParser {
private static String targetFilePath = "파싱할 CSV파일 경로"; //파싱할 CSV파일
private static DateTimeFormatter parseFMT = DateTimeFormatter.ofPattern("MMM d, yyyy hh:mm:ss a z", Locale.ENGLISH); //CSV 일시 파싱을 위한 포맷(예. Sep 1, 2021 12:00:20 AM PST)
//private static ZoneId kstZoneId = ZoneId.of("Asia/Seoul");
private static ZoneId pstZoneId = ZoneId.of("America/Los_Angeles"); //PST는 ZoneId가 'America/Los_Angeles' 임
private static ZoneId pdtZoneId = ZoneId.of("GMT-07:00"); //PDT는 ZoneId가 'GMT-07:00' 임.
private static Map<String, Integer> csvHeadMap = null; //CSV 헤더 맵
/**
* 구글 개발자콘솔에서 다운로드한 재무보고서-수익 CSV파일을 파싱하는 프로그램
*
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
LocalDateTime start = LocalDateTime.now();
log.info("\n\n==== Start: {} ====", start);
List<CSVRecord> records = getCsvRecords(new File(targetFilePath)); //파싱하여 데이터 records를 얻음
log.info("CSV 데이터 records 수: {}", records.size());
//log.debug("records 0번째 데이터:{}", records.get(0));
for (CSVRecord record : records) {
//Transaction 일시를 유닉스타임스탬프로 변환(CSV문서의 데이터는 PDT, PST 등의 일치하지 않는 타임존 정보로 되어 있기에 내부 처리의 용이함을 위해 유닉스타임스탬프로 변환
getUnixTs(record.get("Transaction Date"), record.get("Transaction Time"));
}
LocalDateTime end = LocalDateTime.now();
log.info("\n\n==== End: {} ==== 소요시간: {}(second)", end, Duration.between(start, end).getSeconds()); //로컬 PC에서 50만건 대략 10초 소요됨
}
/**
* 수익 CSV파일을 파싱하여 List데이를 얻음
*
* @param targetFile
* @return
* @throws IOException
*/
private static List<CSVRecord> getCsvRecords(File targetFile) throws IOException {
//구글 개발자콘솔의 재무보고서 기능을 통해서 내려받은 '수익.csv파일'
int sampleDataRow = 0; //샘플 데이터 row번호
try (BufferedReader bufferedReader = new BufferedReader(new FileReader(targetFile))) {
//구글 수익보고서를 분석하여 필요에 맞게 적절하게 CSV포맷 지정
//@formatter:off
CSVFormat csvFormat = CSVFormat.EXCEL.builder() //기본 엑셀타입 포맷
.setHeader().setSkipHeaderRecord(true) //헤더 포함&skip
.setQuote('"') //쌍따옴표 escape처리
.setNullString("") //empty는 null처리
.build();
//@formatter:on
CSVParser parser = csvFormat.parse(bufferedReader); //파서 처리
//CSVParser parser = CSVFormat.EXCEL.withFirstRecordAsHeader().withQuote('"').parse(bufferedReader); //엑셀타입 & 쌍따옴표 escape처리
List<CSVRecord> records = parser.getRecords();
csvHeadMap = parser.getHeaderMap();
// @formatter:off
log.debug("\n"
+ "CSV 데이터 records 수: {}\n"
+ "CSV 헤더 정보\n\t"
+ "CSV헤더 필드수: {}\n\t"
+ "헤더 필드리스트: {}\n"
+ "{}번째 row 데이터 정보\n\t"
+ "데이터 필드수: {}\n\t"
+ "데이터: {}\n",
records.size(),
parser.getHeaderMap().size(),
parser.getHeaderMap(),
sampleDataRow,
records.get(sampleDataRow).size(),
records.get(sampleDataRow)
);
// @formatter:on
return records;
}
}
/**
* transaction 일시 정보를 유닉스타임스탬프로 변환하여 리턴
*
* @param transactionDate CSV의 Transaction Date 필드의 값
* @param transactionTime CSV의 Transaction Time 필드의 값
* @return
*/
private static long getUnixTs(String transactionDate, String transactionTime) {
String timeField = transactionTime;
String timeArray[] = StringUtils.split(transactionTime, ":");
if (timeArray[0].length() != 2) { //1:01:30 AM PDT 와 같이 시간필드에 0이 누락된 경우는 0을 붙여줘서 파싱 포맷에 맞게 변환
timeField = "0" + timeField;
}
final String targetStr = transactionDate + " " + timeField; // 예) Sep 1, 2021 12:00:20 AM PDT
if (StringUtils.contains(transactionTime, "PST")) {
LocalDateTime targetDT = LocalDateTime.parse(targetStr, parseFMT); //파싱
ZonedDateTime pstZDT = targetDT.atZone(pstZoneId);
LocalDateTime pstDT = pstZDT.withZoneSameInstant(pstZoneId).toLocalDateTime();
return pstDT.toEpochSecond(pstZoneId.getRules().getOffset(pstDT));
} else if (StringUtils.contains(transactionTime, "PDT")) {
LocalDateTime targetDT = LocalDateTime.parse(targetStr, parseFMT); //파싱
ZonedDateTime pdtZDT = targetDT.atZone(pdtZoneId);
LocalDateTime pdtDT = pdtZDT.withZoneSameInstant(pdtZoneId).toLocalDateTime();
return pdtDT.toEpochSecond(pdtZoneId.getRules().getOffset(pdtDT));
}
throw new IllegalStateException(String.format("Not supported timezone data. transactionDate:'%s' | transactionTime:'%s'", transactionDate, transactionTime));
}
}
'JAVA > Java 일반' 카테고리의 다른 글
대용량 처리시 부하분산을 위한 데이터 분할 처리(간략) (0) | 2021.12.10 |
---|---|
구글 재무보고서 중에서 '수익'보고서를 다운로드하는 샘플 프로그램(download google earnings report) (1) | 2021.11.26 |
java timeZone변경(PST->KST 또는 PDT->KST) 샘플 소스 (0) | 2021.11.16 |
CSV파일을 읽어서 파싱하여 DB(Mysql)에 저장하는 프로그램 샘플 (0) | 2021.11.12 |
CSV파일 파싱 샘플(with apache commons-csv) (0) | 2021.11.12 |