Project/[약올림] Final Project

[Server] Get Monthly Checked API_String type to Date type (Dec 7, 2020회고)

HJChung 2020. 12. 13. 23:46

주기 계산, 1일전, 8일전 등의 날짜 계산 등을 해야하는 일이 생기면서

기존의 DB의 schedules_date 테이블의 year, month, date로 나뉘는게 매우 비효율적이라는 의견이 제시되었다.

1일 전, 8일 전 year, month, date을 3번의 between을 걸어주는게 

- 년도가 다를경우, 뒤의 월의 앞의 월보다 작은 경우, 

- 다음월로 넘어간 경우, 뒤의 날짜 일이 앞의 날짜 일보다 작은 경우

에 제대로 되지 않는 것이었다.

 

HI님과 같이 구현하면서도 이런 점이 걱정된다고 얘기 나누었었고 마침 1일전, 8일전 등의 날짜 계산을 할 때도 그냥 Date 타입의 필드 하나만 있는 것이 좋을 것 같다는 생각을 하고 있었는데, HJ님께서 질문을 주신 김에 year, month, date 필드를 없애고 Date 타입의 새 필드를 만드는게 어떻겠냐고 말씀드렸다.

그리고 그 결과 아래의 테이블에서

이렇게 테이블을 수정하기로 하였다. 

그러면 기존에 구현되었던 API도 약간 수정되어야 하는 것이 생긴다. 

그 중 Get Monthly Checked API를 대표로 발생한 에러의 원인이 무엇이었는지, 날짜 데이터를 처리하기 위해서는 어떤 과정이 필요해졌는지등을 정리해보고자 한다.

 

1. String type to Date type

schedules_date 테이블에서 해당 사용자의 한달치 데이터를 가져와야 하는 Get Monthly Checked API는

request 형태가

start_day = 'YYYY-MM(보고싶은 달)-DD'
end_day = 'YYYY-MM(보고싶은 달의 다음 달)-DD'
users_id: 인증방식으로 세션 유지되는 users_id 인증값

이다. 즉, client에서 전달해주는 start_day, end_day는 string형이므로

Date타입을 가진 alarmdate 필드에 between을 걸어주려면 먼저 start_day와 end_day를 string->Date 형변환을 해주어야 한다.

string\_type\_alarmdate = datetime.datetime.strptime(String타입인 데이터, '%Y-%m-%d')을 통해 형변환을 해준다.

ex)

start_day = datetime.datetime.strptime(data['start_day'], '%Y-%m-%d')
end_day = datetime.datetime.strptime(data['end_day'], '%Y-%m-%d')

2. Date type to String type

반대로 Date type을 json에러 없이 응답해주려면 String 형태로 바꿔야할 때가 생긴다. 그때는

date\_type\_alarmdate = datetime.datetime.strftime(Date타입인 데이터, '%Y-%m-%d') 을 통해 형변환을 해준다.

ex)

class TimeFormat(fields.Raw):
    def format(self, value):
        return datetime.time.strftime(value, "%H:%M")
class DateFormat(fields.Raw):
    def format(self, value):
        return datetime.datetime.strftime(value, "%d")

 

3. 에러가 났던 이유

처음엔 한달치 데이터를 가져와야겠다고 생각했을 때 between필터를 걸어주기 위한 start_day는 'YYYY-MM(보고싶은달)-01', end_day는 'YYYY-MM(보고싶은 달)-31(28, 29, 30, 31 일등 분기가 너무 많으니 그냥 제일 마지막인 31일로 하자)' 라고 생각했는데,

이렇게 전달된 enddate를 Date형으로 변환하려고 하니,

이때 2020-11-31 처럼 실제 존재하지 않는 날짜인 경우 형변환이 되지 않고 에러가 발생했다.

그러면 client에서 request를 할때 if 1, 3, 5, 7, 8, ..월은 30일
else if 2월은 29 or 28로 윤년계산해서
else 4, 6, …월은 31일 로 분기를 해서 주어야 하는걸까?

 

너무 지저분할거 같아서 그냥start_day = 2020-11(보고싶은 달)-01, end_day = 2020-12(보고싶은 달의 다음 달)-01
로 응답주는 것을 가정하고 구현하는게 어떻겠냐고 말씀드렸다. 1일은 어떤 달이든 다 있으니까!

그래서 핵심 코드는

start_day = datetime.datetime.strptime(data['start_day'], '%Y-%m-%d')
end_day = datetime.datetime.strptime(data['end_day'], '%Y-%m-%d')

topic_fields = {
          'alarmdate': DateFormat(readonly=True, description='Date in DD', default='DD'),
          'time': TimeFormat(readonly=True, description='Time in HH:MM', default='HH:MM'),
          'check': fields.Boolean(required=True),
        }
data = [marshal(topic, topic_fields) for topic in Schedules_date.query
                 .filter(and_(Schedules_date.alarmdate>=start_day, 
                                 Schedules_date.alarmdate<end_day,
                                Schedules_date.user_id==user_id))
                 .all()]

이다.

 

 

최종 코드)

#-*- coding: utf-8 -*-
#schedules_date 테이블에 관련된 쿼리문 작성하는 파일
from flask import request, jsonify, redirect
from flask_restx import Resource, fields, marshal
from sqlalchemy import and_
import json
import jwt
import re
import datetime
from operator import itemgetter
from app.main import db
from app.main.model.schedules_date import Schedules_date
from app.main.model.schedules_common import Schedules_common
from app.main.model.users import Users
from ..config import jwt_key, jwt_alg
import re

class TimeFormat(fields.Raw):
    def format(self, value):
        return datetime.time.strftime(value, "%H:%M")

class DateFormat(fields.Raw):
    def format(self, value):
        return datetime.datetime.strftime(value, "%d")

def sorting_alarmdate_time(data):
  """ date로 오름차순 정렬 후 time으로 오름차순 정렬하는 함수 """
  data = sorted(data, key=itemgetter('alarmdate', 'time'))
  return data

def sorting_time(data):
  """ time으로 오름차순 정렬하는 함수 """
  data = sorted(data, key=itemgetter('time'))
  return data

def get_monthly_checked(data): 
  """ Get monthly checked API for calendar"""
  try: 
    start_day = datetime.datetime.strptime(data['start_day'], '%Y-%m-%d')
    end_day = datetime.datetime.strptime(data['end_day'], '%Y-%m-%d')
    try: 
      token = request.headers.get('Authorization')
      decoded_token = jwt.decode(token, jwt_key, jwt_alg)
      user_id = decoded_token['id']

      topic_fields = {
          'alarmdate': DateFormat(readonly=True, description='Date in DD', default='DD'),
          'time': TimeFormat(readonly=True, description='Time in HH:MM', default='HH:MM'),
          'check': fields.Boolean(required=True),
      }
      data = [marshal(topic, topic_fields) for topic in Schedules_date.query
                       .filter(and_(Schedules_date.alarmdate>=start_day, 
                                       Schedules_date.alarmdate<end_day, 
                                    Schedules_date.user_id==user_id))
                        .all()]
      results = sorting_alarmdate_time(data)
      response_object = {
          'status': 'OK',
          'message': 'Successfully get monthly checked.',
          'results': results
      }
      return response_object, 200
    except Exception as e:
      response_object = {
        'status': 'fail',
        'message': 'Provide a valid auth token.',
      }
      return response_object, 401
  except Exception as e:
    response_object = {
      'status': 'Internal Server Error',
      'message': 'Some Internal Server Error occurred.',
    }
    return response_object, 500

 

 

reference

brownbears.tistory.com/432

 

[Python] 문자열을 Datetime으로 변경하는 방법

문자열로 되어 있는 시간을 Datetime 객체로 변경하는 방법은 많이 찾아 볼 수 있습니다. 여기서는 기존에 존재하는 방법과 추가 설치 모듈로 좀 더 간편하게 사용하는 방법을 설명하겠습니다. 1. D

brownbears.tistory.com