[Django] datetime을 활용해 시간 비교하기
구글링해보니 datetime으로 현재 시간과 지정해준 시간을 비교하는 로직은 많지만
DB에 기록해둔 시간과 시간을 비교하는 로직이 보이지 않아서 누군가에게 도움이 되길 바라며 이 글을 작성하게 되었다:)
(최대한 정확한 정보를 적기위해 노력했지만 작성자가 아직 만 2개월짜리 병아리 개발자라는 것에 유의하자)
datetime
datetime은 날짜와 시간 데이터를 처리하는 장고 모듈이다.
어떤 CRUD를 작성하던지 날짜와 시간을 기본적으로 사용하게 되기 때문에 (ex. created_at, updated_at, joined_at 등등...)
timezone이든 datetime이든 하나는 진득하게 무엇인지 연구해보는 시간을 가지면 좋다. 둘다하면 더 좋고!
timezone은 따로 설정하지 않으면 UTC 시간 기준, 그러니까 영국 시간 기준의 시간을 불러온다
datetime은 컴퓨터의 현재 위치, 현재 시간을 고려하여 시간을 가져온다
그리고 import문을 정말 기억하기 좋은 모듈이다ㅋㅋ!
from datetime import datetime
파이썬으로 날짜와 시간을 계산할때는 timedelta를 많이 활용하는데, + - 같은 산술 연산자로 매우 직관적인 계산이 가능하다!
아래는 공식 문서의 예시
>>> from datetime import timedelta
>>> year = timedelta(days=365)
>>> ten_years = 10 * year
>>> ten_years
datetime.timedelta(days=3650)
>>> ten_years.days // 365
10
>>> nine_years = ten_years - year
>>> nine_years
datetime.timedelta(days=3285)
>>> three_years = nine_years // 3
>>> three_years, three_years.days // 365
(datetime.timedelta(days=1095), 3)
[참조] : https://docs.python.org/3/library/datetime.html#timedelta-objects
[datetime 활용] :
https://www.daleseo.com/python-datetime/
[git hub에서 발견한 예시!] :
https://github.com/HyunSangHan/MeetingTime/blob/master/server/meeting/models.py#LL42C13-L45C58
이제 본격적으로 DB테이블에 저장된 시간 정보들을 비교해보자!
먼저, 아래와 같은 모델이 있다고 가정하고 field마다의 고려사항에 대해 생각해본다
class Time(models.Model):
...
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
meeting_at = models.DateTimeField(help_text="만날 시간")
open_at = models.DateTimeField(null=False, help_text="모집 시작")
close_at = models.DateTimeField(null=True, help_text="모집 종료")
is_ended = models.BooleanField(default=False, help_text="완전히 종료")
시간에 대한 고려사항 :
- created_at 은 글을 작성하는 현재 = datetime.now 다
- updated_at 은 작성자 마음이니 언제가 될지는 모르겠지만 close_at, is_ended, meeting_at 보다는 이전일 것 같다
- open_at 은 글을 작성하는 현재와 같거나 현재보다 나중의 시간이 되어야한다
- close_at은 open_at 보다 나중이어야하고, meeting_at보다는 빨라야한다
- meeting_at은 모든 시간 중 가장 마지막에 위치해야한다
쓰다보니 무슨 논리 풀이 문제처럼 보인다ㅋㅋ
고려사항을 잘 생각하며 글이 작성될 때(post)와 글이 수정될 때(put) 필요한 시간 유효성 점검 코드를 작성해본다.
- 시도 1 -
처음엔 timezone.now와 DB테이블을 가져와 integer형태로 만들어 서로 빼보았으나 실패했다
timezone.now에 연산자를 사용하면 function 오류
datetime.now에 연산자를 사용하면 module 오류가 발생한다^^;
- 시도 2 -
열심히 정보를 찾아보다가 스택오버플로우에서 사용하기 적절한 정보를 알게되었고, strptime 을 공부했다.
datetime.strftime(형식)
날짜와 시간을 문자열로 출력하는 함수
from datetime import datetime
now = datetime.now()
date = now.strftime('%Y-%m-%d')
print(date) # 2023-월-일
datetime.strptime(날짜 문자열, 형식)
날짜와 시간 형식의 문자열을 datetime으로 변환하는 함수
from datetime import datetime
string_datetime = "2023-00-00T00:00:00"
datetime = datetime.strptime(string_datetime, '%Y-%m-%d %H:%M:%S')
print(type(datetime)) # <class 'datetime.datetime'> datetime 형식으로 출력!
strptime으로 필요한 정보를 datetime 형식으로 변환한다.
이때 datetime.now는 이미 datetime 형식이고, 마이크로 초까지 나오는 것을 감안하자
나머지 시간 정보들은 request.data에 string 형태로 저장되어있기때문에
data를 get하여 datetime 형식으로 바꾸어준다.
주의할점 : 연도를 표현하는 %Y 는 %y소문자로 쓸 경우 두 자리 연도(2023 이면 23)로 반환하므로 주의!!
#serializers.py
class TimeCreateSerializer(serializers.ModelSerializer):
...
def validate_datetime(self, data):
now = datetime.now()
open_at = datetime.strptime(data.get("open_at"), "%Y-%m-%dT%H:%M:%S")
close_at = datetime.strptime(data.get("close_at"), "%Y-%m-%dT%H:%M:%S")
meeting_at = datetime.strptime(data.get("meeting_at"), "%Y-%m-%dT%H:%M:%S")
이후 적절하게 if문과 비교연산자를 사용하고 입력 정보가 적절하지 않으면 Error를 발생시켜준다.
if now >= open_at or meeting_at < open_at:
raise serializers.ValidationError({"error": "현재 이후의 시점을 선택해주세요."})
if close_at and open_at > close_at:
raise serializers.ValidationError({"error": "모집 시작 시간보다 이후의 시점을 선택해주세요."})
if close_at and meeting_at < close_at:
raise serializers.ValidationError({"error": "모집이 끝나는 시간보다 이후의 시점을 선택해주세요."})
return data
- 끗 -
생각은 간단했지만 만드는데에 꽤나 오랜 시간이 걸린 코드
나도 적어두고 두고두고 보자!
활용 :
게시글의 모집 시작 open_at, 모집 종료 close_at,
시스템 상으로 완전히 모집 종료 is_ended(bool) 에 대한 정보를
게시글 list, 게시글 상세에 띄워주는 serializer 필드 생성!
#serializers.py
class GroupPurchaseDetailSerializer(serializers.ModelSerializer):
grouppurchase_status = serializers.SerializerMethodField()
class Meta:
model = GroupPurchase
fields = "__all__"
def get_grouppurchase_status(self, obj):
now = datetime.now()
is_ended = obj.is_ended
open_at = datetime.strptime(str(obj.open_at), "%Y-%m-%d %H:%M:%S")
if not obj.close_at:
#close_at이 null일 경우
if is_ended == True:
return "종료"
elif is_ended == False and open_at > now:
return "시작 전"
elif is_ended == False and open_at < now:
return "진행 중"
else:
close_at = datetime.strptime(str(obj.close_at), "%Y-%m-%d %H:%M:%S")
if ...