토이 프로젝트 개발을 하면서 시간관련 함수를 깊게 사용해 본 적이 없었습니다.
회사에 입사 후 시간이라는 데이터가 상당히 중요하다는 것을 알게되었습니다.
회사에서는 정해진 시간에 데이터를 전송하거나 읽는 로직이 있었습니다.
예시로 스케쥴러가 돌면서 정해진 시간에 접속한 유저의 수(CCU)를 통계 데이터에 전송하는 것이 그 중 하나입니다.
정해진 시간에 통계 데이터가 갱신이 되지 않으면 지표를 파악하는 팀에서는 문제가 발생할 수 있습니다.
이 외에도 시간 데이터의 중요성은 상당히 많습니다.
저는 이번에 시간 데이터를 이해하기 위해 C#에서 DateTime 함수를 직접 구현해봤습니다.
UTC(협정 세계시, Coordinated Universal Time)는 세계 표준 시간의 기준이 되는 시간대입니다.
UTC는 1972년부터 사용되기 시작했으며, 이는 과학적으로 정확하고 균일한 시간 측정을 위해 개발되었습니다.
1970년 1월 1일 자정을 0 밀리초로 설정하여 기준을 삼아 그 후로 시간의 흐름을 밀리초로 계산합니다.
시간에 대한 데이터를 생각하면 한국 표준 시간(KST)을 기준으로 하는 것을 생각했었습니다.
하지만 글로벌 유저를 고려해서 작업하는 곳은 UTC 시간대를 기준으로 하는 곳도 있다는 것을 알게되었습니다.
Unix 타임스탬프(Unix timestamp)는 1970년 1월 1일 00:00:00 UTC협정 세계시)부터 경과한 시간을 초 단위로 나타낸 것입니다.
데이터베이스에 "2024년 01월 01일 00:00:00"와 같은 문자열 형식으로 저장할 수 있습니다.
근데 문자열을 사용하면 정수 데이터보다 더 많은 메모리를 차지 할 수 있습니다.
문자열로 형식은 글자 수가 정해져 있지 않기 때문입니다.
그래서 정수로된 유닉스 타임스탬프를 사용하면 데이터베이스 혹은 개발을 하는데 이점을 줄 수 있습니다.
하지만 Unix 타임스탬프는 사람이 보기에는 힘들다는 단점이 있습니다.
유닉스 타임스탬프를 문자열 시간 포맷으로 변경하는 코드를 작성합니다.
개발에 필요한 시간 데이터를 이론 뿐만 아니라 직접 채득해 볼 수 있습니다.
internal class Program
{
// 유닉스 시간을 사람이 읽기 편한 포맷으로 변경하는 함수
static String unixTimeToHumanReadable(long seconds)
{
// 문자열로 출력되는 시간
String ans = "";
// 윤년이 아닌 년도의 월에 일 수
int[] daysOfMonth = { 31, 28, 31, 30, 31, 30,
31, 31, 30, 31, 30, 31 };
int currYear, extraDays,
index, date, month, hours, minutes, secondss,
flag = 0;
long daysTillNow = 0;
long extraTime = 0;
// UnixTime을 기준으로 지난 일 수, 총 시간
daysTillNow = seconds / (24 * 60 * 60);
extraTime = seconds % (24 * 60 * 60);
currYear = 1970;
// 현재 년도 구하기
while (true)
{
// 윤년 확인
if (currYear % 400 == 0 || (currYear % 4 == 0 && currYear % 100 != 0))
{
if (daysTillNow < 366)
{
break;
}
daysTillNow -= 366;
}
else
{
if (daysTillNow < 365)
{
break;
}
daysTillNow -= 365;
}
currYear += 1;
}
// 현재 일 수를 구하기 위한 로직
// 모든 일 수 - 1년의 일 수(ex 365일)
// 현재 년도를 구하기 전까지 while 문을 돌면서 일 수를 구합니다.
extraDays = (int)daysTillNow + 1;
if (currYear % 400 == 0
|| (currYear % 4 == 0 && currYear % 100 != 0))
flag = 1;
month = 0;
index = 0;
// 월, 일을 구하는 로직
if (flag == 1) // 윤년에 해당하는 경우
{
while (true)
{
if (index == 1)
{
if (extraDays - 29 < 0)
break;
month += 1;
extraDays -= 29;
}
else
{
if (extraDays - daysOfMonth[index]
< 0)
{
break;
}
month += 1;
extraDays -= daysOfMonth[index];
}
index += 1;
}
}
else
{
while (true)
{
if (extraDays - daysOfMonth[index] < 0)
{
break;
}
month += 1;
extraDays -= daysOfMonth[index];
index += 1;
}
}
// 현재 개월(Month) 수
if (extraDays > 0)
{
month += 1;
date = extraDays;
}
else
{
if (month == 2 && flag == 1)
date = 29;
else
{
date = daysOfMonth[month - 1];
}
}
// 시간(hrous):분(minutes):초(seconds) 계산
// 남은 시간(초)에 1시간을 나타내는 3600(60분 * 60초)초를 나누면 현재 시간이 나오게 된다.
hours = (int)extraTime / 3600;
// 남은 시간(초)에 1시간을 나타내는 3600초를 나눈 나머지에 60초를 나눈다
minutes = (int)(extraTime % 3600) / 60;
// 남은 시간(초)에 1시간을 나타내는 3600초를 나눈 나머지에 60초로 나눈 나머지
secondss = (int)extraTime % 60;
ans = $"{currYear}/{month}/{date} {hours}:{minutes}:{secondss}";
return ans;
}
// Main함수
public static void Main(String[] args)
{
long timestamp = ((DateTimeOffset)DateTime.UtcNow).ToUnixTimeSeconds();
// Unix 타임스탬프를 유저가 읽을 수 있는 시간 형식으로 변경
String ans = unixTimeToHumanReadable(timestamp);
// DD:MM:YYYY:HH:MM:SS
Console.Write(ans + "\n");
}
}
로직은 위와 같습니다.
위 예시 코드를 분석하면 알 수 있는 것은 아래와 같습니다.