[C#] 시간(DateTime)관련해서 파보기

Arthur·2024년 7월 3일
0
post-thumbnail

작성하게 된 계기


토이 프로젝트 개발을 하면서 시간관련 함수를 깊게 사용해 본 적이 없었습니다.
회사에 입사 후 시간이라는 데이터가 상당히 중요하다는 것을 알게되었습니다.

회사에서는 정해진 시간에 데이터를 전송하거나 읽는 로직이 있었습니다.
예시로 스케쥴러가 돌면서 정해진 시간에 접속한 유저의 수(CCU)를 통계 데이터에 전송하는 것이 그 중 하나입니다.

정해진 시간에 통계 데이터가 갱신이 되지 않으면 지표를 파악하는 팀에서는 문제가 발생할 수 있습니다.
이 외에도 시간 데이터의 중요성은 상당히 많습니다.

저는 이번에 시간 데이터를 이해하기 위해 C#에서 DateTime 함수를 직접 구현해봤습니다.



UTC 시간


UTC(협정 세계시, Coordinated Universal Time)는 세계 표준 시간의 기준이 되는 시간대입니다.
UTC는 1972년부터 사용되기 시작했으며, 이는 과학적으로 정확하고 균일한 시간 측정을 위해 개발되었습니다.
1970년 1월 1일 자정을 0 밀리초로 설정하여 기준을 삼아 그 후로 시간의 흐름을 밀리초로 계산합니다.

  • UTC는 원자 시계의 초정밀 시간 측정에 기반을 두고 있으며, 전 지구적으로 일관된 시간을 제공합니다.
  • 전 세계의 표준 시간대는 UTC를 기준으로 정해집니다. 예를 들어, 서울의 표준 시간은 UTC+9입니다.
    • ex) KST가 15:00이면 여기에 9시간을 뺀 06:00이 UTC 시간입니다.

시간에 대한 데이터를 생각하면 한국 표준 시간(KST)을 기준으로 하는 것을 생각했었습니다.
하지만 글로벌 유저를 고려해서 작업하는 곳은 UTC 시간대를 기준으로 하는 곳도 있다는 것을 알게되었습니다.



Unix 타임스탬프


Unix 타임스탬프(Unix timestamp)는 1970년 1월 1일 00:00:00 UTC협정 세계시)부터 경과한 시간을 초 단위로 나타낸 것입니다.

  • Epoch time 또는 POSIX time 이라고도 불립니다.
  • 일반적으로 10자리의 정수로 표현됩니다.

장점

  • 단일 숫자로 시간을 표현할 수 있어서 단순합니다.
  • 시간 간격 계산이 쉽습니다.
  • 날짜와 시간을 컴팩트하게 저장할 수 있습니다.

데이터베이스에 "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");
    }
}

로직은 위와 같습니다.
위 예시 코드를 분석하면 알 수 있는 것은 아래와 같습니다.

  • UnixTime을 기준으로 지난 일의 수(dayTillNow 변수) 구하기
  • UnixTime을 기준으로 총 시간(extraTime 변수) 구하기
  • 윤년에 대한 개념
  • UTC 시간에 대한 개념
  • 유닉스 시간에 대한 개념
  • 유닉스 시간과 일반 문자열 시계의 차이점


참고 자료


  • geeksforgeeks - Convert Unix timestamp to DD/MM/YYYY HH:MM:SS format => 링크
  • ChatGPT
profile
기술에 대한 고민과 배운 것을 회고하는 게임 서버 개발자의 블로그입니다.

0개의 댓글