
이렇게 눈이 온걸 본적은 모니터 화면에 나오는 설경이 아름답다. 이 겨울도 이렇게 눈 구경 한번 제대로 못하고 지나갈까? 이 겨울엔 한 번쯤 꼭 눈 구경하러 가야겠다.
낼모레가 크리스마스라는데... 나이를 먹어서 인가? 그다지 흥이 나지 않는다. 그저 또 한 해가 가버리는구나 하는 생각만 들뿐...
빌코리아의 홈페이지 입니다.

이렇게 눈이 온걸 본적은 모니터 화면에 나오는 설경이 아름답다. 이 겨울도 이렇게 눈 구경 한번 제대로 못하고 지나갈까? 이 겨울엔 한 번쯤 꼭 눈 구경하러 가야겠다.
낼모레가 크리스마스라는데... 나이를 먹어서 인가? 그다지 흥이 나지 않는다. 그저 또 한 해가 가버리는구나 하는 생각만 들뿐...

난 고작 $7.75을 유지 하고 있을 뿐인데...

언제면 처음으로 외화벌이(?)를 할 수 있을까 ??? 나도 앵벌이 인가 ??? ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ
$100가 되려면 아직 갈길이 멀다...
예전에 어떤 앱을 만들기 할 때 사용했던 음력 변환 하는 소스를 어딘가에서 찾았던 거 같은데... 다시 한번 기억해 보기 위해서 적어 보기로 했다.
https://billcorea.tistory.com/4
구글로간 음력 설명서
새로운 버젼으로 업데이트 해 드립니다. Google Calendar 에서는 아직 음력 관리를 원할하게 해 주지 않는 현상이 있었습니다. 그래서 과거에 만들었던 앱을 다시 만들어 보았습니다. 2020.06.20 구글에
billcorea.tistory.com
이건 어딘가에서 퍼왔던 소스인데, 기억이 가물 가물 하다.
import java.text.SimpleDateFormat;
import java.util.Date;
public class LunarTranser {
private static final int kk[] = {
1, 2, 1, 2, 1, 2, 2, 3, 2, 2,
1, 2, 1, 1, 2, 1, 2, 1, 2, 1,
2, 2, 1, 2, 2, 0, 1, 1, 2, 1,
1, 2, 1, 2, 2, 2, 1, 2, 0, 2,
1, 1, 2, 1, 3, 2, 1, 2, 2, 1,
2, 2, 2, 1, 1, 2, 1, 1, 2, 1,
2, 1, 2, 2, 0, 2, 1, 2, 1, 2,
1, 1, 2, 1, 2, 1, 2, 0, 2, 2,
1, 2, 3, 2, 1, 1, 2, 1, 2, 1,
2, 2, 1, 2, 2, 1, 2, 1, 1, 2,
1, 2, 1, 0, 2, 1, 2, 2, 1, 2,
1, 2, 1, 2, 1, 2, 0, 1, 2, 3,
2, 1, 2, 2, 1, 2, 1, 2, 1, 2,
1, 2, 1, 2, 1, 2, 1, 2, 2, 1,
2, 2, 0, 1, 1, 2, 1, 1, 2, 3,
2, 2, 1, 2, 2, 2, 1, 1, 2, 1,
1, 2, 1, 2, 1, 2, 2, 2, 0, 1,
2, 1, 2, 1, 1, 2, 1, 2, 1, 2,
2, 0, 2, 1, 2, 1, 2, 3, 1, 2,
1, 2, 1, 2, 1, 2, 2, 2, 1, 2,
1, 1, 2, 1, 2, 1, 2, 0, 1, 2,
2, 1, 2, 1, 2, 1, 2, 1, 2, 1,
0, 2, 1, 2, 3, 2, 2, 1, 2, 1,
2, 1, 2, 1, 2, 1, 2, 1, 2, 1,
2, 2, 1, 2, 1, 2, 0, 1, 2, 1,
1, 2, 1, 2, 2, 3, 2, 2, 1, 2,
1, 2, 1, 1, 2, 1, 2, 1, 2, 2,
2, 1, 0, 2, 1, 2, 1, 1, 2, 1,
2, 1, 2, 2, 2, 0, 1, 2, 1, 2,
1, 3, 2, 1, 1, 2, 2, 1, 2, 2,
2, 1, 2, 1, 1, 2, 1, 1, 2, 2,
1, 0, 2, 2, 1, 2, 2, 1, 1, 2,
1, 2, 1, 2, 0, 1, 2, 2, 1, 4,
1, 2, 1, 2, 1, 2, 1, 2, 1, 2,
1, 2, 1, 2, 2, 1, 2, 1, 2, 1,
0, 2, 1, 1, 2, 2, 1, 2, 1, 2,
2, 1, 2, 0, 1, 2, 3, 1, 2, 1,
2, 1, 2, 2, 2, 1, 2, 1, 2, 1,
1, 2, 1, 2, 1, 2, 2, 2, 1, 0,
2, 1, 2, 1, 1, 2, 3, 1, 2, 2,
1, 2, 2, 2, 1, 2, 1, 1, 2, 1,
1, 2, 2, 1, 2, 0, 2, 2, 1, 2,
1, 1, 2, 1, 1, 2, 1, 2, 0, 2,
2, 1, 2, 2, 3, 1, 2, 1, 2, 1,
1, 2, 2, 1, 2, 2, 1, 2, 1, 2,
1, 2, 1, 2, 0, 1, 2, 1, 2, 1,
2, 2, 1, 2, 1, 2, 1, 0, 2, 1,
3, 2, 1, 2, 2, 1, 2, 2, 1, 2,
1, 2, 1, 1, 2, 1, 2, 1, 2, 2,
2, 1, 2, 0, 1, 2, 1, 1, 2, 1,
2, 3, 2, 2, 1, 2, 2, 1, 2, 1,
1, 2, 1, 1, 2, 2, 1, 2, 2, 0,
2, 1, 2, 1, 1, 2, 1, 1, 2, 1,
2, 2, 0, 2, 1, 2, 2, 1, 3, 2,
1, 1, 2, 1, 2, 2, 1, 2, 2, 1,
2, 1, 2, 1, 2, 1, 1, 2, 0, 2,
1, 2, 1, 2, 2, 1, 2, 1, 2, 1,
1, 0, 2, 1, 2, 2, 3, 2, 1, 2,
2, 1, 2, 1, 2, 1, 1, 2, 1, 2,
1, 2, 2, 1, 2, 2, 1, 0, 2, 1,
1, 2, 1, 2, 1, 2, 2, 1, 2, 2,
0, 1, 2, 3, 1, 2, 1, 1, 2, 2,
1, 2, 2, 2, 1, 2, 1, 1, 2, 1,
1, 2, 1, 2, 2, 2, 0, 1, 2, 2,
1, 1, 2, 3, 1, 2, 1, 2, 2, 1,
2, 2, 2, 1, 1, 2, 1, 1, 2, 1,
2, 1, 0, 2, 2, 2, 1, 2, 1, 2,
1, 1, 2, 1, 2, 0, 1, 2, 2, 1,
2, 4, 1, 2, 1, 2, 1, 1, 2, 1,
2, 1, 2, 2, 1, 2, 2, 1, 2, 1,
2, 0, 1, 1, 2, 1, 2, 1, 2, 2,
1, 2, 2, 1, 0, 2, 1, 1, 4, 1,
2, 1, 2, 1, 2, 2, 2, 1, 2, 1,
1, 2, 1, 1, 2, 1, 2, 2, 2, 1,
0, 2, 2, 1, 1, 2, 1, 1, 4, 1,
2, 2, 1, 2, 2, 2, 1, 1, 2, 1,
1, 2, 1, 2, 1, 2, 0, 2, 2, 1,
2, 1, 2, 1, 1, 2, 1, 2, 1, 0,
2, 2, 1, 2, 2, 1, 4, 1, 1, 2,
1, 2, 1, 2, 1, 2, 2, 1, 2, 2,
1, 2, 1, 1, 2, 0, 1, 2, 1, 2,
1, 2, 2, 1, 2, 2, 1, 2, 0, 1,
1, 2, 1, 4, 1, 2, 1, 2, 2, 1,
2, 2, 1, 1, 2, 1, 1, 2, 1, 2,
2, 2, 1, 2, 0, 2, 1, 1, 2, 1,
1, 2, 1, 2, 2, 1, 2, 0, 2, 2,
3, 1, 2, 1, 1, 2, 1, 2, 1, 2,
2, 2, 1, 2, 1, 2, 1, 1, 2, 1,
2, 1, 2, 0, 2, 2, 1, 2, 1, 2,
1, 3, 2, 1, 2, 1, 2, 2, 1, 2,
2, 1, 2, 1, 1, 2, 1, 2, 1, 0,
2, 1, 2, 2, 1, 2, 1, 2, 1, 2,
1, 2, 0, 1, 2, 1, 2, 1, 4, 2,
1, 2, 1, 2, 1, 2, 1, 2, 1, 1,
2, 2, 1, 2, 2, 1, 2, 2, 0, 1,
1, 2, 1, 1, 2, 1, 2, 2, 1, 2,
2, 0, 2, 1, 1, 4, 1, 1, 2, 1,
2, 1, 2, 2, 2, 1, 2, 1, 2, 1,
1, 2, 1, 2, 1, 2, 2, 0, 2, 1,
2, 1, 2, 1, 1, 2, 3, 2, 1, 2,
2, 1, 2, 2, 1, 2, 1, 1, 2, 1,
2, 1, 2, 0, 1, 2, 2, 1, 2, 1,
2, 1, 2, 1, 2, 1, 0, 2, 1, 2,
1, 2, 2, 3, 2, 1, 2, 1, 2, 1,
2, 1, 2, 1, 2, 1, 2, 2, 1, 2,
1, 2, 0, 1, 2, 1, 1, 2, 1, 2,
2, 1, 2, 2, 1, 0, 2, 1, 2, 1,
3, 2, 1, 2, 1, 2, 2, 2, 1, 2,
1, 2, 1, 1, 2, 1, 2, 1, 2, 2,
2, 0, 1, 2, 1, 2, 1, 1, 2, 1,
1, 2, 2, 1, 0, 2, 2, 2, 3, 2,
1, 1, 2, 1, 1, 2, 2, 1, 2, 2,
1, 2, 2, 1, 1, 2, 1, 2, 1, 2,
0, 1, 2, 2, 1, 2, 1, 2, 3, 2,
1, 2, 1, 2, 1, 2, 1, 2, 1, 2,
2, 1, 2, 1, 2, 1, 0, 2, 1, 1,
2, 2, 1, 2, 1, 2, 2, 1, 2, 0,
1, 2, 1, 1, 2, 3, 2, 1, 2, 2,
2, 1, 2, 1, 2, 1, 1, 2, 1, 2,
1, 2, 2, 2, 1, 0, 2, 1, 2, 1,
1, 2, 1, 1, 2, 2, 2, 1, 0, 2,
2, 1, 2, 3, 1, 2, 1, 1, 2, 2,
1, 2, 2, 2, 1, 2, 1, 1, 2, 1,
1, 2, 1, 2, 0, 2, 2, 1, 2, 1,
2, 1, 2, 3, 2, 1, 1, 2, 2, 1,
2, 2, 1, 2, 1, 2, 1, 2, 1, 1,
0, 2, 2, 1, 2, 1, 2, 2, 1, 2,
1, 2, 1, 0, 2, 1, 1, 2, 1, 2,
4, 1, 2, 2, 1, 2, 1, 2, 1, 1,
2, 1, 2, 1, 2, 2, 1, 2, 2, 0,
1, 2, 1, 1, 2, 1, 1, 2, 2, 1,
2, 2, 0, 2, 1, 2, 1, 3, 2, 1,
1, 2, 2, 1, 2, 2, 2, 1, 2, 1,
1, 2, 1, 1, 2, 1, 2, 2, 0, 2,
1, 2, 2, 1, 1, 2, 1, 1, 2, 3,
2, 2, 1, 2, 2, 1, 2, 1, 2, 1,
1, 2, 1, 2, 0, 1, 2, 2, 1, 2,
2, 1, 2, 1, 2, 1, 1, 0, 2, 1,
2, 2, 1, 2, 3, 2, 2, 1, 2, 1,
2, 1, 1, 2, 1, 2, 1, 2, 2, 1,
2, 2, 1, 0, 2, 1, 1, 2, 1, 2,
1, 2, 2, 1, 2, 2, 0, 1, 2, 1,
1, 2, 3, 1, 2, 1, 2, 2, 2, 2,
1, 2, 1, 1, 2, 1, 1, 2, 1, 2,
2, 2, 0, 1, 2, 2, 1, 1, 2, 1,
1, 2, 1, 2, 2, 0, 1, 2, 2, 3,
2, 1, 2, 1, 1, 2, 1, 2, 1, 2,
2, 2, 1, 2, 1, 2, 1, 1, 2, 1,
2, 0, 1, 2, 2, 1, 2, 2, 1, 2,
3, 2, 1, 1, 2, 1, 2, 1, 2, 2,
1, 2, 1, 2, 2, 1, 2, 0, 1, 1,
2, 1, 2, 1, 2, 2, 1, 2, 2, 1,
0, 2, 1, 1, 2, 1, 3, 2, 2, 1,
2, 2, 2, 1, 2, 1, 1, 2, 1, 1,
2, 1, 2, 2, 2, 1, 0, 2, 2, 1,
1, 2, 1, 1, 2, 1, 2, 2, 1, 0,
2, 2, 2, 1, 3, 2, 1, 1, 2, 1,
2, 1, 2, 2, 2, 1, 2, 1, 2, 1,
1, 2, 1, 2, 1, 0, 2, 2, 1, 2,
2, 1, 2, 1, 1, 2, 1, 2, 0, 1,
2, 3, 2, 2, 1, 2, 1, 2, 2, 1,
1, 2, 1, 2, 1, 2, 1, 2, 2, 1,
2, 2, 1, 2, 0, 1, 1, 2, 1, 2,
1, 2, 3, 2, 2, 1, 2, 2, 1, 1,
2, 1, 1, 2, 1, 2, 2, 2, 1, 2,
0, 2, 1, 1, 2, 1, 1, 2, 1, 2,
2, 1, 2, 0, 2, 2, 1, 1, 2, 3,
1, 2, 1, 2, 1, 2, 2, 2, 1, 2,
1, 2, 1, 1, 2, 1, 2, 1, 2, 0,
2, 1, 2, 2, 1, 2, 1, 1, 2, 1,
2, 1, 0, 2, 1, 2, 4, 2, 1, 2,
1, 1, 2, 1, 2, 1, 2, 1, 2, 2,
1, 2, 1, 2, 1, 2, 1, 2, 0, 1,
2, 1, 2, 1, 2, 1, 2, 2, 3, 2,
1, 2, 1, 2, 1, 1, 2, 1, 2, 2,
2, 1, 2, 2, 0, 1, 1, 2, 1, 1,
2, 1, 2, 2, 1, 2, 2, 0, 2, 1,
1, 2, 1, 3, 2, 1, 2, 1, 2, 2,
2, 1, 2, 1, 2, 1, 1, 2, 1, 2,
1, 2, 2, 0, 2, 1, 2, 1, 2, 1,
1, 2, 1, 2, 1, 2, 0, 2, 1, 2,
2, 3, 2, 1, 1, 2, 1, 2, 1, 2,
1, 2, 2, 1, 2, 1, 2, 1, 2, 1,
2, 1, 0, 2, 1, 2, 1, 2, 2, 1,
2, 1, 2, 1, 2, 0, 1, 2, 3, 2,
1, 2, 1, 2, 2, 1, 2, 1, 2, 1,
2, 1, 1, 2, 1, 2, 2, 1, 2, 2,
1, 0, 2, 1, 2, 1, 1, 2, 3, 2,
1, 2, 2, 2, 1, 2, 1, 2, 1, 1,
2, 1, 2, 1, 2, 2, 2, 0, 1, 2,
1, 2, 1, 1, 2, 1, 1, 2, 2, 2,
0, 1, 2, 2, 1, 2, 3, 1, 2, 1,
1, 2, 2, 1, 2, 2, 1, 2, 2, 1,
1, 2, 1, 1, 2, 2, 0, 1, 2, 1,
2, 2, 1, 2, 1, 2, 1, 2, 1, 0,
2, 1, 2, 3, 2, 1, 2, 2, 1, 2,
1, 2, 1, 2, 1, 1, 2, 1, 2, 2,
1, 2, 2, 1, 2, 0, 1, 2, 1, 1,
2, 1, 2, 3, 2, 2, 2, 1, 2, 1,
2, 1, 1, 2, 1, 2, 1, 2, 2, 2,
1, 0, 2, 1, 2, 1, 1, 2, 1, 1,
2, 2, 1, 2, 0, 2, 2, 1, 2, 1,
1, 4, 1, 1, 2, 1, 2, 2, 2, 2,
1, 2, 1, 1, 2, 1, 1, 2, 1, 2,
0, 2, 2, 1, 2, 1, 2, 1, 2, 1,
1, 2, 1, 0, 2, 2, 1, 2, 2, 3,
2, 1, 2, 1, 2, 1, 1, 2, 1, 2,
2, 1, 2, 2, 1, 2, 1, 2, 1, 0,
2, 1, 1, 2, 1, 2, 2, 1, 2, 2,
1, 2, 0, 1, 2, 3, 1, 2, 1, 2,
1, 2, 2, 2, 1, 2, 1, 2, 1, 1,
2, 1, 1, 2, 2, 1, 2, 2, 0
};
private final static String yuk[] = {"갑", "을", "병", "정", "무", "기", "경", "신", "임", "계"};
private final static String gap[] = {"자", "축", "인", "묘", "진", "사", "오", "미", "신", "유", "술", "해"};
private final static String ddi[] = {"쥐띠", "소띠", "범띠", "토끼띠", "용띠", "뱀띠", "말띠", "양띠", "원숭이띠", "닭띠", "개띠", "돼지띠"};
private final static String week[] = {"일요일", "월요일", "화요일", "수요일", "목요일", "금요일", "토요일"};
private static final int m[] = {
31, 28, 31, 30, 31, 30, 31, 31, 30, 31,
30, 31
};
private static Date init_date;
private static final String errMsg = "입력값 에러";
/**
* 음력을 양력으로
*
* @param TranseDay 음력일('yyyyMMdd')
* @param leapyes 윤달 여부
* @return String 처리결과 양력일 엔티티
* @throws java.lang.Exception
*/
public static String LunarTranse(String TranseDay, boolean leapyes) throws Exception {
@SuppressWarnings("unused")
int dt[] = new int[163];
int lyear = Integer.parseInt(TranseDay.substring(0,4));
int lmonth = Integer.parseInt(TranseDay.substring(4,6));
int lday = Integer.parseInt(TranseDay.substring(6,8));
if(!leapyes && !verifyDate(lyear, lmonth, lday, "solar-"))
{
return "";
}
if(leapyes && !verifyDate(lyear, lmonth, lday, "solar+"))
{
return "";
}
int m1 = -1;
long td = 0L;
if(lyear != 1881)
{
m1 = lyear - 1882;
for(int i = 0; i <= m1; i++)
{
for(int j = 0; j < 13; j++)
td = td + (long)kk[i * 13 + j];
if(kk[i * 13 + 12] == 0)
td = td + 336L;
else
td = td + 362L;
}
}
m1++;
int n2 = lmonth - 1;
int m2 = -1;
do
{
m2++;
if(kk[m1 * 13 + m2] > 2)
{
td = td + 26L + (long)kk[m1 * 13 + m2];
n2++;
continue;
}
if(m2 == n2)
break;
td = td + 28L + (long)kk[m1 * 13 + m2];
} while(true);
if(leapyes)
td = td + 28L + (long)kk[m1 * 13 + m2];
td = td + (long)lday + 29L;
m1 = 1880;
do
{
m1++;
boolean leap = m1 % 400 == 0 || m1 % 100 != 0 && m1 % 4 == 0;
if(leap)
m2 = 366;
else
m2 = 365;
if(td < (long)m2)
break;
td = td - (long)m2;
} while(true);
int syear = m1;
m[1] = m2 - 337;
m1 = 0;
do
{
m1++;
if(td <= (long)m[m1 - 1])
break;
td = td - (long)m[m1 - 1];
} while(true);
int smonth = m1;
int sday = (int)td;
long y = (long)syear - 1L;
td = ((y * 365L + y / 4L) - y / 100L) + y / 400L;
boolean leap = syear % 400 == 0 || syear % 100 != 0 && syear % 4 == 0;
if(leap)
m[1] = 29;
else
m[1] = 28;
for(int i = 0; i < smonth - 1; i++)
td = td + (long)m[i];
td = td + (long)sday;
@SuppressWarnings("unused")
int w = (int)(td % 7L);
int i = (int)(td % 10L);
i = (i + 4) % 10;
int j = (int)(td % 12L);
j = (j + 2) % 12;
@SuppressWarnings("unused")
int k1 = (lyear + 6) % 10;
@SuppressWarnings("unused")
int k2 = (lyear + 8) % 12;
String sValue= String.valueOf(syear);
if(smonth<10)
sValue+="0";
sValue+= String.valueOf(smonth);
if(sday<10)
sValue+="0";
sValue+= String.valueOf(sday);
String return_value = sValue ;
return return_value;
}
/**
* 양력을 음력으로
*
* @param TranseDay 양력일('yyyyMMdd')
* @return String 처리결과 음력일
* @throws java.lang.Exception
*/
public static String solarTranse(String TranseDay) throws Exception {
// 2020.04.15 : 음력의 날자도 당해년도 음력 날자를 찾아서 주는 것으로.
long time = System.currentTimeMillis();
SimpleDateFormat sdf = new SimpleDateFormat("yyyymmdd");
java.sql.Date dd = new java.sql.Date(time);
TranseDay = sdf.format(dd).substring(0, 4) + TranseDay.substring(4, 8) ;
int dt[] = new int[163];
for(int i = 0; i < 163; i++)
{
dt[i] = 0;
for(int j = 0; j < 12; j++)
switch(kk[i * 13 + j])
{
case 1: // '\001'
case 3: // '\003'
dt[i] = dt[i] + 29;
break;
case 2: // '\002'
case 4: // '\004'
dt[i] = dt[i] + 30;
break;
}
switch(kk[i * 13 + 12])
{
case 1: // '\001'
case 3: // '\003'
dt[i] = dt[i] + 29;
break;
case 2: // '\002'
case 4: // '\004'
dt[i] = dt[i] + 30;
break;
}
}
int syear = Integer.parseInt(TranseDay.substring(0,4));
int smonth = Integer.parseInt(TranseDay.substring(4,6));
int sday = Integer.parseInt(TranseDay.substring(6,8));
long k11 = syear - 1;
long td2 = ((k11 * 365L + k11 / 4L) - k11 / 100L) + k11 / 400L;
boolean ll = syear % 400 == 0 || syear % 100 != 0 && syear % 4 == 0;
if(ll)
m[1] = 29;
else
m[1] = 28;
if(!verifyDate(syear, smonth, sday, "lunar"))
{
throw new Exception("Date Error [" + syear + smonth + sday + "]");
}
for(int i = 0; i < smonth - 1; i++)
td2 = td2 + (long)m[i];
td2 = td2 + (long)sday;
long td = (td2 - 0xa7a5eL) + 1L;
long td0 = dt[0];
int i=0;
for(i = 0; i < 163; i++)
{
if(td <= td0)
break;
td0 = td0 + (long)dt[i + 1];
}
int lyear = i + 1881;
td0 = td0 - (long)dt[i];
td = td - td0;
int jcount=0;
if(kk[i * 13 + 12] != 0)
jcount = 13;
else
jcount = 12;
int m2 = 0;
int j=0;
int m1;
for(j = 0; j < jcount; j++)
{
if(kk[i * 13 + j] <= 2)
m2++;
if(kk[i * 13 + j] <= 2)
m1 = kk[i * 13 + j] + 28;
else
m1 = kk[i * 13 + j] + 26;
if(td <= (long)m1)
break;
td = td - (long)m1;
}
@SuppressWarnings("unused")
int m0 = j;
long lmonth = m2;
int lday = (int)td;
@SuppressWarnings("unused")
int w = (int)(td2 % 7L);
i = (int)((td2 + 4L) % 10L);
j = (int)((td2 + 2L) % 12L);
@SuppressWarnings("unused")
int i1 = (lyear + 6) % 10;
@SuppressWarnings("unused")
int j1 = (lyear + 8) % 12;
String sValue= String.valueOf(lyear);
if(lmonth<10)
sValue+="0";
sValue+= String.valueOf(lmonth);
if(lday<10)
sValue+="0";
sValue+= String.valueOf(lday);
String return_value = sValue ;
return return_value;
}
private static boolean verifyDate(int k, int l, int l1, String s)
{
if(k < 1881 || k > 2043 || l < 1 || l > 12)
return false;
if(s.equals("lunar") && l1 > m[l - 1])
return false;
if(s.equals("solar+"))
{
if(kk[(k - 1881) * 13 + 12] < 1)
return false;
if(kk[(k - 1881) * 13 + l] < 3)
return false;
if(kk[(k - 1881) * 13 + l] + 26 < l1)
return false;
}
if(s.equals("solar-"))
{
int j = l - 1;
for(int i = 1; i <= 12; i++)
if(kk[((k - 1881) * 13 + i) - 1] > 2)
j++;
if(l1 > kk[(k - 1881) * 13 + j] + 28)
return false;
}
return true;
}
public static String[] getYuk() {
return yuk;
}
public static String[] getGap() {
return gap;
}
public static String[] getDdi() {
return ddi;
}
public static String[] getWeek() {
return week;
}
public static void setInit_date(Date init_date) {
LunarTranser.init_date = init_date;
}
public static Date getInit_date() {
return init_date;
}
public static String getErrmsg() {
return errMsg;
}
}그 예전 부터 지금까지 잘 사용하고 있으니, 소스의 변환 능력은 검증이 되었다고 볼 수 있을 것 같다.
사용하는 부분은 다음과 같다. 먼저 음력을 양력으로 변환 하기
LunarTranse(String TranseDay, boolean leapyes)기준일자(yyyyMMdd : 8자리 숫자) 와 윤달 여부만 전달 하면 양력 으로 변환된 날자가 돌아온다.
다음은 양력을 음력으로 변환하기
solarTranse(String TranseDay)기준일자(yyyyMMdd : 8자리 숫자)을 전달하면 음력으로 변환된 날자가 돌아온다.
이런 기능의 사용예시는 앞에서 기술한 바와 같이 앱으로 활용하고 있다.
https://play.google.com/store/apps/details?id=com.nari.lunar3google
음력달력, 일정관리 - Google Play 앱
음력을 양력으로 변환하여 관리 합니다. 양력은 음력으로 관리 합니다.
play.google.com
이전 포스팅에서 제주버스의 정보를 수집했다.
https://billcorea.tistory.com/111
이제 그 정보를 나의 앱에 넣는 작업을 해 봐야겠다. 일단은 데이터를 저장할 table 을 구성해 보았다. 뭐 말그대로 앞전 포스팅에서 작성한 class 중에서 item 이 들어 있는 class 구조를 그대로 적용해 보면 될 것 같다.
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
public class DBHelper extends SQLiteOpenHelper {
private static final String DB_NAME = "opdGangDB";
private static final int DB_Ver = 4;
private static final String TAG = "DBHelper";
public DBHelper(Context context) {
super(context, DB_NAME, null, DB_Ver);
}
/**
* aClassCode, bClassCode, cClassCode : 코드 분류 1,2,3 차
* cntData : 위치별-상호별 중복 건수
* infoData : 상세 정보 y,x,place_name 으로 cntData 와 연계
* @param db
*/
@Override
public void onCreate(SQLiteDatabase db) {
StringBuffer sb = new StringBuffer();
sb.append("create table ClassCode");
sb.append("( _id integer primary key autoincrement, ");
sb.append(" classNameA text, ");
sb.append(" classNameB text, ");
sb.append(" classNameC text ");
sb.append(" ) ");
db.execSQL(sb.toString());
sb = new StringBuffer();
sb.append("create table cntData");
sb.append("( _id integer primary key autoincrement, ");
sb.append(" y text, ");
sb.append(" x text, ");
sb.append(" place_name text, ");
sb.append(" atCnt number ");
sb.append(" ) ");
db.execSQL(sb.toString());
sb = new StringBuffer();
sb.append("create table infoData");
sb.append("( _id integer primary key autoincrement, ");
sb.append(" y text, ");
sb.append(" x text, ");
sb.append(" address_name text, ");
sb.append(" category_group_code text, ");
sb.append(" category_group_name text, ");
sb.append(" category_name text, ");
sb.append(" distance text, ");
sb.append(" id text, ");
sb.append(" phone text, ");
sb.append(" place_name text, ");
sb.append(" place_url text, ");
sb.append(" road_address_name text, ");
sb.append(" url text, ");
sb.append(" regTime text, ");
sb.append(" likeCnt text, ");
sb.append(" unlikeCnt text ");
sb.append(" ) ");
db.execSQL(sb.toString());
/**
* 제주 버스 정류소 정보 수집 2021.12.20 ~
*/
sb = new StringBuffer();
sb.append("create table stationItem ");
sb.append("( _id integer primary key autoincrement, ");
sb.append(" dirTp text, ");
sb.append(" govNm text, ");
sb.append(" localX real, ");
sb.append(" localY real, ");
sb.append(" mobiNum text, ");
sb.append(" stationId text, ");
sb.append(" stationNm text, ");
sb.append(" upd text, ");
sb.append(" useYn text ");
sb.append(" ) ");
db.execSQL(sb.toString());
}
/**
* @param db
* @param oldVersion
* @param newVersion
*/
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
StringBuffer sb = new StringBuffer();
Log.e(TAG, "newVersion=" + newVersion) ;
switch (newVersion) {
case 4:
sb = new StringBuffer();
sb.append("create table stationItem ");
sb.append("( _id integer primary key autoincrement, ");
sb.append(" dirTp text, ");
sb.append(" govNm text, ");
sb.append(" localX real, ");
sb.append(" localY real, ");
sb.append(" mobiNum text, ");
sb.append(" stationId text, ");
sb.append(" stationNm text, ");
sb.append(" upd text, ");
sb.append(" useYn text ");
sb.append(" ) ");
db.execSQL(sb.toString());
break;
case 3:
db.execSQL("alter table infoData add column likeCnt text ");
db.execSQL("alter table infoData add column unlikeCnt text ");
break;
}
}
}sqlite 에서 데이터를 구성해 앱을 만들다 보면 나중에 추가 하거나 구조를 변경 하게 되는 경우가 있는데, 그 때 마다 data을 다 지우고 다시 만들면 사용자도 힘들고, 개발자도 힘들고 ... 그래서 db을 생성할 때 버전에 따라서 변하게 하는 것이 좋을 듯 하다. 그러면 이전 앱을 설치한 사용자도 upgrade 되면 새로 데이터를 넣지 않아도 되기 때문이다.
이 앱은 벌써 데이터 구조를 3, 4 버전까지 늘렸다. 2 버전은 어디 갔는 지 모르겠지만... (사실은 한번 잘못 빌드를 했다가.. 실폐를 해서 ) 아무튼 새로 버전을 받더라도 사용자는 아무런 의심 없이 사용하게 될 것이다.
이번에는 이 데이터를 불러서 사용할 것들을 정리해 봐야겠다.
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;
import com.billcoreatech.opdgang1127.JejuBusInfo.StationItemBean;
import com.billcoreatech.opdgang1127.JejuFD6.JejuFD6infoBean;
import com.billcoreatech.opdgang1127.Utils.StringUtils;
public class DBHandler extends RuntimeException {
DBHelper helper ;
SQLiteDatabase db ;
String TAG = "DBHandler";
public DBHandler(Context context) {
helper = new DBHelper(context) ;
db = helper.getWritableDatabase() ;
}
public static DBHandler open (Context ctx) throws SQLException {
DBHandler handler = new DBHandler(ctx) ;
return handler ;
}
public void close() {
helper.close();
}
...
/**
* 제주 버스 정류소 정보 2021.12.20 ~
* @param station
* @return
*/
public long appendBusStation(StationItemBean station) {
long result = 0 ;
Cursor rs = selectBusStation(station.getStationId());
if (rs.moveToNext()) {
result = updateBusStation(station);
} else {
result = insertBusStation(station);
}
rs.close();
return result ;
}
/**
* 제주 버스 정류소 정보 2021.12.20 ~
* @param busStation
* @return
*/
public Cursor selectBusStation(String busStation) {
StringBuffer sb = new StringBuffer();
sb.append("select * from stationItem ");
if (!"".equals(busStation)) {
sb.append(" where stationId = '" + busStation.trim() + "' ");
}
return db.rawQuery(sb.toString(), null);
}
/**
* 주변 정류소 찾기
* @param x : 경도
* @param y : 위도
* @param roundValue : 주변범위 m
* @return
*/
public Cursor selectBusStationArea(double x, double y, int roundValue) {
double x1 = x - StringUtils.LongitudeInDifference(y, roundValue);
double x2 = x + StringUtils.LongitudeInDifference(y, roundValue);
double y1 = y - StringUtils.LatitudeInDifference(roundValue) ;
double y2 = y + StringUtils.LatitudeInDifference(roundValue) ;
StringBuffer sb = new StringBuffer();
sb.append("select * from stationItem ");
sb.append(" where (localX >= " + x1 + " and localX <= " + x2 + ") ");
sb.append(" and (localY >= " + y1 + " and localY <= " + y2 + ") ");
sb.append(" and useYn = 'Y' ");
Log.e(TAG, sb.toString());
return db.rawQuery(sb.toString(), null);
}
/**
* 제주 버스 정류소 정보 2021.12.20 ~
* @param station
* @return
*/
public long insertBusStation(StationItemBean station) {
ContentValues values = new ContentValues();
values.put("dirTp", station.getDirTp());
values.put("govNm", station.getGovNm());
values.put("localX", station.getLocalX());
values.put("localY", station.getLocalY());
values.put("mobiNum", station.getMobiNum());
values.put("stationId", station.getStationId());
values.put("stationNM", station.getStationNm());
values.put("upd", station.getUpd()) ;
values.put("useYn", station.getUseYn()) ;
return db.insert("stationItem", null, values) ;
}
/**
* 제주 버스 정류소 정보 2021.12.20 ~
* @param station
* @return
*/
public long updateBusStation(StationItemBean station) {
ContentValues values = new ContentValues();
values.put("dirTp", station.getDirTp());
values.put("govNm", station.getGovNm());
values.put("localX", station.getLocalX());
values.put("localY", station.getLocalY());
values.put("mobiNum", station.getMobiNum());
values.put("stationNM", station.getStationNm());
values.put("upd", station.getUpd()) ;
values.put("useYn", station.getUseYn()) ;
return db.update("stationItem", values, "stationId = '" + station.getStationId() + "' ", null) ;
}
}이렇게 insert, update, select 을 구성했고, append 도 추가 했다. append 는 새로 데이터를 받아온 다음 그것을 저장하기 위해서,
이제 데이터를 받아와서 저장하는 구현을 해 봐야지.
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.actionGetBusStation:
fd6Binding = Updatejejufd6infoViewBinding.inflate(getLayoutInflater());
fd6Binding.adView.loadAd(adRequest);
AlertDialog.Builder builder1 = new AlertDialog.Builder(MainActivity.this, R.style.ThemeDialog)
.setTitle(getString(R.string.titleUpdateBusStation) + "(last:" + sp.getString("lastUpdateBusStation", "1970-01-01") + ")")
.setView(fd6Binding.getRoot())
.setPositiveButton(getString(R.string.OK), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
doGetBusStation();
}
})
.setNegativeButton(getString(R.string.CLOSE), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
}
});
AlertDialog dialog1 = builder1.create();
dialog1.show();
break;
}
return super.onOptionsItemSelected(item);
}option menu 를 선택 하면 아래 처럼 데이터를 수신 받고 그것을 이용해서 저장하는 for 문을 구성 하였다.
/**
* 2021.12.20 제주 버스 정류소 정보 수집 기능 추가
*/
private void doGetBusStation() {
binding.baseProgressBar.setVisibility(View.VISIBLE);
retrofit = new Retrofit.Builder()
.baseUrl(RetrofitApi.baseURL)
.client(new OkHttpClient())
.addConverterFactory(SimpleXmlConverterFactory.create())
.build();
service = retrofit.create(RetrofitApi.class);
String lastUpdateDate = sp.getString("lastUpdateBusStation", "1970-01-01");
service.getStationInfo(lastUpdateDate).enqueue(new Callback<StationBean>() {
@Override
public void onResponse(Call<StationBean> call, Response<StationBean> response) {
Log.e(TAG, "response=" + response.code() + " Total=" + response.body().getBodyClass().getTotalCount()
+ "\n" + response.message());
if (!"0".equals(response.body().getBodyClass().getTotalCount())) {
dbHandler = DBHandler.open(getApplicationContext());
for (StationItemBean item : response.body().getBodyClass().getItems()) {
Log.e(TAG, item.getStationNm());
long cnt = dbHandler.appendBusStation(item);
Log.e(TAG, cnt + " " + item.getStationNm());
}
dbHandler.close();
editor = sp.edit();
editor.putString("lastUpdateBusStation", StringUtils.getToday());
editor.commit();
}
binding.baseProgressBar.setVisibility(View.GONE);
}
@Override
public void onFailure(Call<StationBean> call, Throwable t) {
Log.e(TAG, "ERROR=" + t.toString()) ;
}
});
}저번과 달라진 부분은 service 호출할 때 최종 변경일자를 구해서 전달하도록 수정한 부분이다. 그래서 retrofit api 에서도 다음과 같이 날자를 받아서 파라미터로 전달 하도록 구성을 변경 하였다.
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Query;
public interface RetrofitApi {
String baseURL = "http://busopen.jeju.go.kr";
String getStation = "/OpenAPI/service/bis/Station";
@GET(getStation)
Call<StationBean> getStationInfo(@Query(value="last") String lastUpdate);
}이렇게 하면 저번 포스팅에서 보았던 제주버스api 에 최종 변경된 정보만 수집하는 모양으로 작업을 할 수 있다.
이제 가져온 데이터가 앱에서 어떻게 활용이 되는 지 잠시 보도록 하겠다.

이렇게 앱에 버스 정류소를 표시할 수 있는 자료가 만들어 졌다. 다음은 뭐 해야하지 ? 내가 있는 위치에서 저 버스 정류소까지 가는 방법을 연구해 보아야 겠다.
그럼 오늘도 즐~ 코딩...
앱에서 지원하고 싶은 것중 우선 나의 주변에 버스 정류소를 찾는다. 어떻게 찾을까 ? 모든 데이터는 data.go.kr 공공데이터 포털을 중심으로 ... 찾아보니 제주도에서 제공하는 버스 정보가 있다.
http://www.jeju.go.kr/help/open.htm?page=3&act=view&seq=967654
다만, 가이드의 정보를 기준으로 보면 http:// 으로 시작하는 기본url 과 xml 형식으로 자료를 전송한다는 것이 조금 예전 방식인 것 같은 생각이 들었다.
이제 앱에 retrofit 통신을 하기 위한 준비를 해 보자. gradle 파일에 수정을
// 데이터 주고 받기
implementation 'com.squareup.retrofit2:retrofit:2.7.2'
implementation 'com.squareup.retrofit2:converter-simplexml:2.1.0'retrofit 과 xml 파싱을 위해서 추가 했다.
다음은 manifest 에 internet 접속을 위한 권한 등록
<uses-permission android:name="android.permission.INTERNET" />이제 준비는 되었고. 그럼 데이터를 가져오기 위해서 일단 open api 가 제공하는 데이터 구조를 확인해 보아야겠다.


데이터는 이렇게 조회가 되고 있고, open api 가이드의 내용도 이와 같다. 그래서 일단은 데이터를 받아올 class 을 만들어 보았다.
맨 먼저 제일 바깥쪽에 구성되는 class 부터
import org.simpleframework.xml.Element;
import org.simpleframework.xml.Root;
@Root(name="response", strict = false)
public class StationBean {
@Element(name="header")
HeaderClass headerClass;
@Element(name="body")
BodyClass bodyClass ;
public HeaderClass getHeaderClass() {
return headerClass;
}
public BodyClass getBodyClass() {
return bodyClass;
}
public void setHeaderClass(HeaderClass headerClass) {
this.headerClass = headerClass;
}
public void setBodyClass(BodyClass bodyClass) {
this.bodyClass = bodyClass;
}
}Root 는 response 로 구성 되며, 그 안에는 header 와 body 가 들어간다.
다음은 header class
import org.simpleframework.xml.Element;
import org.simpleframework.xml.Root;
@Root(name="header", strict = false)
public class HeaderClass {
@Element(name="resultCode")
String resultCode;
@Element(name="resultMsg")
String resultMsg ;
public String getResultCode() {
return resultCode;
}
public String getResultMsg() {
return resultMsg;
}
public void setResultCode(String resultCode) {
this.resultCode = resultCode;
}
public void setResultMsg(String resultMsg) {
this.resultMsg = resultMsg;
}
}header 에는 resultCode, 와 resultMsg 만 들어 있고, 실제 데이터는 body 에 들어간다. 다음은 body class
import org.simpleframework.xml.Element;
import org.simpleframework.xml.ElementList;
import org.simpleframework.xml.Root;
import java.util.ArrayList;
@Root(name="body", strict = false)
public class BodyClass {
@ElementList(entry = "items")
ArrayList<ItemClass> items ;
@Element(name="numOfRows")
String numOfRows ;
@Element(name="pageNo")
String pageNo ;
@Element(name="totalCount")
String totalCount ;
public ArrayList<ItemClass> getItems() {
return items;
}
public String getNumOfRows() {
return numOfRows;
}
public String getPageNo() {
return pageNo;
}
public String getTotalCount() {
return totalCount;
}
public void setItems(ArrayList<ItemClass> items) {
this.items = items;
}
public void setNumOfRows(String numOfRows) {
this.numOfRows = numOfRows;
}
public void setPageNo(String pageNo) {
this.pageNo = pageNo;
}
public void setTotalCount(String totalCount) {
this.totalCount = totalCount;
}
}body 에는 데이터가 들어가는 items 와 건수등을 표시하는 데이터 들이 들어 있는데, 구현하면서 조금 방황(?)한 부분은 ArrayList 로 표시하는 items 에 대한 구현을 어떻게 해야 하는 가에 대한 부분 이었다. gson 으로 파싱을 할 때는 그런거 없이 수월했는데, xml 형식에서는 EntryList 가 표현이 되도록 구현을 해야 되는 것이었다.
다음은 item이 들어가는 item class
import org.simpleframework.xml.Element;
import org.simpleframework.xml.Root;
@Root(name="item", strict = false)
public class ItemClass {
@Element(name="dirTp")
String dirTp ;
@Element(name="govNm")
String govNm;
@Element(name="localX")
double localX ;
@Element(name="localY")
double localY ;
@Element(name="mobiNum")
String mobiNum ;
@Element(name="stationId")
String stationId ;
@Element(name="stationNm")
String stationNm ;
@Element(name="upd")
String upd ;
@Element(name="useYn")
String useYn ;
public double getLocalX() {
return localX;
}
public double getLocalY() {
return localY;
}
public String getMobiNum() {
return mobiNum;
}
public String getDirTp() {
return dirTp;
}
public String getGovNm() {
return govNm;
}
public String getStationId() {
return stationId;
}
public String getStationNm() {
return stationNm;
}
public String getUpd() {
return upd;
}
public String getUseYn() {
return useYn;
}
public void setDirTp(String dirTp) {
this.dirTp = dirTp;
}
public void setGovNm(String govNm) {
this.govNm = govNm;
}
public void setLocalX(double localX) {
this.localX = localX;
}
public void setLocalY(double localY) {
this.localY = localY;
}
public void setMobiNum(String mobiNum) {
this.mobiNum = mobiNum;
}
public void setStationId(String stationId) {
this.stationId = stationId;
}
public void setStationNm(String stationNm) {
this.stationNm = stationNm;
}
public void setUpd(String upd) {
this.upd = upd;
}
public void setUseYn(String useYn) {
this.useYn = useYn;
}
}이렇게 구현 하면 데이터를 받아올 준비는 되었다. 이제 interface 을 구현해 볼 차례 인데, openapi 에는 호출시 파라미터를 last 와 serviceKey 를 받는다고 되어 있지만, 현재( 2021.12.19 )에는 아무것도 전달하지 않아도 되고, last 뒤에 날자만 전달해도 된다. last 을 넣어보내면 최근 업데이트 된 정보만 받아오는 것으로 확인이 되었다. 다만, 나의 앱에서는 정보가 처음이라, 전체 데이터를 받기 위해서 아무것도 파라미터로 사용하지 않다.
import retrofit2.Call;
import retrofit2.http.GET;
public interface RetrofitApi {
String baseURL = "http://busopen.jeju.go.kr";
String getStation = "/OpenAPI/service/bis/Station";
@GET(getStation)
Call<StationBean> getStationInfo();
}interface class 는 이렇게 구성을 했다. query 가 없어서 아무것도 전달하지는 않았다.
이번에는 activity 에서 호출을 해 보도록 하겠다.
public class MainActivity extends AppCompatActivity {
...
Retrofit retrofit ;
RetrofitApi service ;
...
private void doGetBusStation() {
retrofit = new Retrofit.Builder()
.baseUrl(RetrofitApi.baseURL)
.client(new OkHttpClient())
.addConverterFactory(SimpleXmlConverterFactory.create())
.build();
service = retrofit.create(RetrofitApi.class);
service.getStationInfo().enqueue(new Callback<StationBean>() {
@Override
public void onResponse(Call<StationBean> call, Response<StationBean> response) {
Log.e(TAG, "response=" + response.code() + " Total=" + response.body().getBodyClass().getTotalCount());
for(ItemClass item : response.body().getBodyClass().getItems()) {
Log.e(TAG, item.getStationNm());
}
}
@Override
public void onFailure(Call<StationBean> call, Throwable t) {
Log.e(TAG, "ERROR=" + t.toString()) ;
}
});
}
...
}아직 데이터를 받아와서 어떻게 하고자 하는 지 결정을 하지 못했다. 일단, 데이터가 들어 오는 것은 확인을 하였다. 이 구현을 하는데, CLEARTEXT communication to XXXX not permitted by network security policy 을 만나게 되면, 어떻게 할 것인가를 고민하지 말고 다음을 따라해 보면 된다. 이유는 http:// 으로 된 주소에 통신을 시도하기 때문에 발생하는 보안 문제인데, 나 앱이 모든 호출에서 http:// 로 할 것이 아니기 때문에 특정 url 만 http 로 호출할 것이라고 등록을 해 주면 된다.
먼저 xml 파일을 하나 만든다. res/xml/network_config.xml 이라고 .. 그안에는 다음과 같이 넣어 준다.
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">busopen.jeju.go.kr</domain>
</domain-config>
</network-security-config>특정 주소는 http 로 통신을 하겠다는 것이다. 보안정책 때문에 누군가 싫어할지 모르지만.
그리고 다음은 manifest 에 다음을 추가해 준다.
<application
android:allowBackup="true"
android:icon="@mipmap/ic_info_logo"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_info_logo_round"
android:supportsRtl="true"
android:networkSecurityConfig="@xml/network_config"networkSecurityConfig 을 추가해 주면 끝.

제주도에는 4358개이 정류소 정보가 있나 보다. 다음엔 이것을 저장해서 데이터로 활용해 보도록 하겠다.
배경화면 이미지 이렇게 눈이 온걸 본적은 모니터 화면에 나오는 설경이 아름답다. 이 겨울도 이렇게 눈 구경 한번 제대로 못하고 지나갈까? 이 겨울엔 한 번쯤 꼭 눈 구경하러 가야겠다. 낼모레가 크리스마스라는데... 나이를 먹어서 인가? 그다지 ...