어플리케이션을 글로벌 하게 서비스 하게되면 서버의 시간과 클라이언트 시간이 일치하지 않게 됩니다. 그래서 클라이언트에 ServerTime class를 만들어서 서버 시간을 받아서 처리하는 경우들이 있습니다. 서버에 접속하면 현재 서버 시간을 클라이언트에 전송해 주고 상점에서 상품 판매나 이벤트 시간등을 서버 시간을 토대로 처리하면 전세계 사용자들이 동일한 타이밍에 시간 처리가 가능합니다.
물론 가장 이상적인 것은 각 사용자별로 현재 자신의 시간에 맞춰서 이벤트가 진행되는게 가장 좋겠지만 의도적으로 디바이스 시간을 조정해서 보상을 받거나 어뷰징을 하는 사용자가 존재하기 때문에 그 방법을 안전하게 처리하는 것은 고민이 좀 필요한 부분입니다.
패킷으로 DateTime 타입을 전송할 때 ToBinary() 함수를 사용합니다.
문제는 서버에서 DateTime.Now.ToBinary() 를 사용하여 시간을 전송하고 클라이언트가 FromBinary로 받게되면 서버 시간을 받는게 아니라 클라이언트 시간을 그대로 받게 됩니다.
서버는 분명이 2:00PM 인데 받는 클라이언트는 자신의 시간대인 6:00PM이 되어버리는 경우가 발생해 버리는 것이죠.
DateTime이 이런 부분까지 신경을 쓰고 있다고 생각은 안 했지만 의외로 내부적으로 Kind라는 Enum을 이용하여 시간에 대한 처리를 하고 있었습니다.
https://docs.microsoft.com/ko-kr/dotnet/api/system.datetime.kind?view=net-5.0
var local = DateTime.Now;
var utc = DateTime.UtcNow;
var unspecified = new DateTime(2021, 4, 4);
위의 코드를 보면 변수명에 적혀 있듯 각각의 DateTime객체에 대한 Kind는 Local, Utc, Unspecified 가 됩니다.
ToBinary()를 이용해서 시차가 있는 다른 기기에서 해당 값을 FromBinary()로 읽어들이게 되면 Kind에 따라서 표시되는 시간이 달라집니다.
Local은 OS에 지정된 TimeZone 설정에 맞춰서 시간이 바뀝니다. 보내는 쪽에서는 한국 시간을 보냈지만 받는 곳이 미국이면 미국 시간으로 표시됩니다. UTC 시간을 기준으로 TimeZone 시차만큼을 계산해서 표시를 해주기 때문입니다.
즉, 모든 DateTime은 내부적으로 Utc 시간을 가지고 있고 Kind가 Local인 경우에는 값을 참조할 때 OS의 TimeZone설정에 따라 시차를 더해서 계산해 주는 것입니다.
같은 이치로 Kind가 Utc인 경우에는 전송한 곳이나 전송 받은 곳이나 기준이 동일하기 때문에 동일한 시간을 표시할 것입니다. 그래서 서버는 Utc 시간을 사용하라는 내용을 많은 곳에서 찾아볼 수 있습니다.
마지막으로 Unspecified는 사용자가 DateTime의 Now, UtcNow 같은 메소드를 사용해서 시간을 받아온게 아니라 임의로 날짜를 명시하여 생성한 경우입니다. 객체가 생성될 때 시간을 지정해 주지만 Kind를 명시해 주지 않으면 이게 지역 시간인지 Utc인지 알 방법이 없습니다. 그래서 Kind로 Unspecified를 지정해 둡니다. 이 시간은 전송할 때 보내는 쪽이나 받는쪽 모두에서 시간 표시가 그대로 유지됩니다.
여기에서 문제는 Server를 구현할 때 DateTime의 Kind를 염두에 두지 않고 구현을 하게되면 클라이언트가 전송 받은 시간의 기준이 완전히 제각각이 됩니다.
가장 대표적인 케이스가 Database에 저장되어 있던 시간을 받아온 것과 DateTime.Now를 통해서 새로 생성한 데이터는 Kind가 다르게 설정되지만 서버에서는 큰 문제가 없어보이지만 클라이언트로 시간을 전송해보면 엄청난 혼란을 가중 시키는 대표적인 예시입니다.