날짜만 있는 공지에서 홈페이지에는 시간이 생길 수 있다. 저장에 필요한 시각을 임시로 자정에 맞춘 뒤 그 값을 화면으로 옮기면, 방문자는 자정이 실제 발표된 시간이라고 읽는다. K-POP PULSE의 일정 기록에는 이 문제를 시간 미고지 상태로 구별한 대응이 있다.
이번에는 과거 코드뿐 아니라 현재 공개 이벤트 JSON과 ICS를 다시 읽었다. 시간 미정인56개 항목은 날짜형 캘린더 값과 짝이 맞았다. 다만 같은 사이트의 검색용 구조화 데이터 한 항목에는 자정이 남아 있었다. 나는 출력 경로마다 확인한 상태와 남은 차이를 함께 설명한다.
빈 시각에 넣은 값이 독자에게 약속이 될 수 있다
당시 일정 등록 안내는 시각이 발표되지 않았으면 별도의 플래그를 전달하도록 했다. 이를 생략하면 공지에 없는 00:00 KST가 나타날 수 있다고 적었다. 날짜를 정렬하거나 저장하기 위한 내부 값과 독자에게 알려 줄 확정 시각이 같은 의미가 아니었다.
실제로 자정이 발표된 일정도 있으므로 시간이0인지 검사해서 전부 숨기면 다른 사실을 잃는다. 판단할 정보는 숫자의 모양보다 시각이 발표됐는가다. 날짜만 알고 있을 때는 날짜와 미고지 상태를 남기고, 공지가 시각을 알렸을 때는 그 시각과 기준을 함께 다룬다.
현재 피드가 구별하는 값을 짝으로 읽었다
지금 읽은 JSON과 ICS에는 각각89개 일정이 있다. JSON에서 time_announced가 false인56개를 식별자로 캘린더 항목과 연결했다. 그 항목들은 time_kst가 비어 있고, ICS의 날짜형 시작 값이 JSON의 한국 날짜와 일치했다. 내부 타임스탬프가 있어도 공개 시각 필드에는 미고지를 남긴 것이다.
이 검사는 실제 HTTP 응답과 값의 비교다. 모든 캘린더 앱에 가져와 본 결과나 앞으로 들어올 일정 전체의 보증으로 쓰지 않는다. 날짜형 일정은 그 날을 찾는 정보로 사용할 수 있지만 실제 행사가 하루 종일 진행된다는 근거는 아니다. 이 차이를 독자에게도 설명할 필요가 있다.
같은 홈페이지의 검색용 데이터에는 차이가 남아 있었다
공개 컴백 페이지의 서버 HTML에 있는 구조화 데이터를 JSON 피드와 대조했다. 한 신곡 일정은 피드에서 시간 미정이고 시각이 비어 있는데, 구조화 데이터의 startDate에는 같은 날짜의 00:00:00과 한국 오프셋이 들어 있었다. 화면 밖의 출력도 같은 의미를 유지하는지 따로 살펴야 한다는 실제 차이다.
이 값을 이번 글에서 발견했다고 원본 사이트를 수정한 것은 아니다. 검색 결과가 실제로 어떻게 표시됐는지도 확인하지 않았다. 날짜와 날짜시각을 받을 수 있는 구조화 속성에서는 발표되지 않은 시각을 더하지 않는 출력 조건을 검토할 수 있다. 한 경로가 맞다는 검사만으로 다른 경로까지 완료했다고 말하지 않는다.
상태를 기준으로 값을 고르는 작은 함수를 만든다
아래는 이번에 새로 만든 설명용 순수 함수다. 미고지면 날짜만 돌려주고 저장용 자정 시각은 읽지 않는다. 실제 시각이 발표됐으면 오프셋이 있는 시각과 공급한 현지 날짜를 대조한 뒤 반환한다. 실제 발표된 자정은 그 상태를 지정하면 시각으로 유지한다.
새 함수는 날짜형식과 플래그, 시각 누락과 날짜 불일치를 포함한9검사를 통과했다. 합성 조건8개와 현재 기록에서 확인한 한 입력 형태를 사용했다. 원본 코드 수정, 전체 일정 스키마 검증이나 캘린더 앱 조작을 수행한 프로그램은 아니다. 적용할 홈페이지에서 각 출력과 연결하는 작업은 따로 필요하다.
코드와 구현 살펴보기
새 날짜/발표 시각 선택 함수. 원본 사이트의 적용 완료 코드가 아니다.
"""New teaching projection of a date or an explicitly announced local instant."""
from datetime import date,datetime
def event_start_value(date_text,time_announced,local_start=None):
if type(date_text) is not str or type(time_announced) is not bool:
raise ValueError('Explicit date string and boolean announcement state required')
day=date.fromisoformat(date_text)
if day.isoformat()!=date_text:raise ValueError('Use YYYY-MM-DD date form')
if not time_announced:return day.isoformat()
if not isinstance(local_start,str):raise ValueError('Announced time requires a local datetime')
instant=datetime.fromisoformat(local_start.replace('Z','+00:00'))
if instant.tzinfo is None or instant.date()!=day:
raise ValueError('Offset and local date must match the supplied date')
return instant.isoformat()
출력마다 다른 기본값을 만들지 않게 연결한다
화면, API, 구독 달력, 검색용 데이터가 각각 저장 시각을 해석하면 한 곳에는 날짜만 보이고 다른 곳에는 자정이 생길 수 있다. 날짜와 발표 여부를 함께 읽는 공통 의미를 정하고 각 형식에 맞춰 변환하는 편이 다시 검사하기 쉽다.
캘린더의 날짜형 시작과 끝도 형식의 규칙에 맞게 다뤄야 한다. 끝 날짜는 그 날짜를 포함하는 마감으로 읽지 않는다. 그리고 날짜형으로 보낸 표현과 실제 행사의 길이를 혼동하지 않는다. 같은 상태를 공유하더라도 각 출력 형식의 계약과 소비자의 동작은 별도로 확인해야 한다.
추가 발표가 들어오면 모름에서 확정으로 바꾼다
미고지는 영원히 비워 두는 상태가 아니다. 나중에 시각이 발표되면 어떤 공지를 근거로 상태를 바꾸었는지 남기고 관련 화면과 구독 경로를 다시 확인한다. 이전의 모름을 지워 마치 처음부터 알고 있었던 것처럼 기록하지 않는다.
나는 이번 글을 모든 일정 표현을 해결한 결과로 쓰지 않는다. 과거의 미고지 대응, 현재56개 피드 짝의 일치, 한 구조화 출력의 차이, 새 함수의 작은 검사 범위를 남긴다. 홈페이지를 계속 만드는 일에는 없는 정보를 채우는 능력보다 모르는 상태를 끝까지 정확하게 전달하는 능력도 필요했다.