Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions apps/client/src/pages/myBookmark/MyBookmark.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,6 @@ const MyBookmark = () => {
return <div>Loading...</div>;
}

console.log(category);

return (
<div className="flex h-screen flex-col py-[5.2rem] pl-[8rem] pr-[5rem]">
<div className="flex items-center gap-[0.4rem]">
Expand Down Expand Up @@ -241,6 +239,7 @@ const MyBookmark = () => {
/>
<div className="absolute inset-0 flex items-center justify-center p-4">
<CardEditModal
key={articleDetail.id}
onClose={() => setIsEditOpen(false)}
prevData={articleDetail}
/>
Expand Down
1 change: 1 addition & 0 deletions apps/client/src/pages/remind/Remind.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ const Remind = () => {
/>
<div className="absolute inset-0 flex items-center justify-center p-4">
<CardEditModal
key={articleDetail.id}
onClose={() => setIsEditOpen(false)}
prevData={articleDetail}
/>
Expand Down
2 changes: 1 addition & 1 deletion apps/client/src/shared/apis/axios.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export const putCategory = async (id: number, categoryName: string) => {

export const getAcorns = async () => {
const now = formatLocalDateTime(new Date());
const { data } = await apiRequest.get('/api/v1/users/acorns?now=', {
const { data } = await apiRequest.get('/api/v1/users/acorns?', {
params: { now },
});
return data.data;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
} from '@shared/apis/queries';
import { usePageMeta } from '@shared/hooks/usePageMeta';
import { ArticleDetailResponse, EditArticleRequest } from '@shared/types/api';
import { buildUtcIso } from '@shared/utils/datetime';
import { combineDateTime } from '@shared/utils/datetime';
import { updateDate, updateTime } from '@shared/utils/formatDateTime';
import { useQueryClient } from '@tanstack/react-query';
import { useEffect, useState } from 'react';
Expand Down Expand Up @@ -86,7 +86,7 @@ export default function CardEditModal({
}

const remindTime =
isRemindOn && date && time ? buildUtcIso(date, time) : null;
isRemindOn && date && time ? combineDateTime(date, time) : null;

const editArticleData: EditArticleRequest = {
memo,
Expand All @@ -96,6 +96,7 @@ export default function CardEditModal({
now: new Date().toISOString(),
remindTime,
};
console.log('remindTime', remindTime);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

editArticle(
{
Expand Down
79 changes: 54 additions & 25 deletions apps/client/src/shared/utils/datetime.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,54 @@
function parseDateString(d: string) {
const s = d.replace(/[^\d]/g, '');
if (s.length !== 8) return null;
const Y = Number(s.slice(0, 4));
const M = Number(s.slice(4, 6));
const D = Number(s.slice(6, 8));
return { Y, M, D };
}

function parseTimeString(t: string) {
const s = t.replace(/[^\d]/g, '');
if (s.length !== 4) return null;
const h = Number(s.slice(0, 2));
const m = Number(s.slice(2, 4));
return { h, m };
}

export function buildUtcIso(dateStr: string, timeStr: string) {
const d = parseDateString(dateStr);
const t = parseTimeString(timeStr);
if (!d || !t) return null;

const dt = new Date(d.Y, d.M - 1, d.D, t.h, t.m, 0, 0);
return dt.toISOString();
}
// YYYY-MM-DD → YYYY.MM.DD
export const updateDate = (date: string) => {
if (!date) return '';
return date.replace(/-/g, '.');
};

// HH:mm:ss → HH:mm
export const updateTime = (time: string) => {
if (!time) return '';
return time.slice(0, 5);
};

export const to24Hour = (time: string) => {
const match = time.match(/(오전|오후)\s(\d{1,2}):(\d{2})/);
if (!match) return time;

const [, period, hourStr, minute] = match;
let hour = parseInt(hourStr, 10);

if (period === '오전' && hour === 12) {
hour = 0;
} else if (period === '오후' && hour !== 12) {
hour += 12;
}

return `${hour.toString().padStart(2, '0')}:${minute}`;
};

export const combineDateTime = (date: string, time: string) => {
if (!date || !time) return null;

// date가 YYYYMMDD 형태라면 가공 필요
let formattedDate = date;
if (/^\d{8}$/.test(date)) {
const year = date.slice(0, 4);
const month = date.slice(4, 6);
const day = date.slice(6, 8);
formattedDate = `${year}-${month}-${day}`;
} else {
formattedDate = date.replace(/\./g, '-'); // YYYY.MM.DD → YYYY-MM-DD
}

// time이 HHmm 형태라면 HH:mm으로 변환
let normalizedTime = time;
if (/^\d{4}$/.test(time)) {
normalizedTime = `${time.slice(0, 2)}:${time.slice(2, 4)}`;
}

// 24시간 포맷 변환 함수가 있다면 적용
const to24 = to24Hour ? to24Hour(normalizedTime) : normalizedTime;
const formattedTime = to24.length === 5 ? `${to24}:00` : to24;

return `${formattedDate}T${formattedTime}`;
};
Comment on lines +29 to +54
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

combineDateTime: 타임존/UTC 계약 확인 필요 + 파싱 실패 가드 추가 제안

  • 현재 반환값은 "YYYY-MM-DDTHH:mm(:ss)"의 로컬 타임스탬프(타임존 미포함)입니다. 이전 buildUtcIso(UTC ISO 생성) 대비 의미가 바뀌었을 수 있어 서버가 기대하는 시간대(로컬/UTC/오프셋 포함)를 확인해야 합니다. 계약이 UTC라면 now는 UTC(ISOString)인데 remindTime은 로컬(오프셋 없음)이라 불일치합니다.
  • to24Hour 존재 여부 체크는 항상 truthy라 의미가 없어지고, 변환 실패 시에도 그대로 문자열을 이어붙이면 유효하지 않은 ISO가 생성될 수 있습니다.

아래는 안전성 향상을 위한 최소 수정입니다(형식 검증 + 명시적 반환 타입 + 불필요 조건 제거):

-export const combineDateTime = (date: string, time: string) => {
+export const combineDateTime = (date: string, time: string): string | null => {
   if (!date || !time) return null;
@@
-  // 24시간 포맷 변환 함수가 있다면 적용
-  const to24 = to24Hour ? to24Hour(normalizedTime) : normalizedTime;
-  const formattedTime = to24.length === 5 ? `${to24}:00` : to24;
+  // 24시간 포맷 변환
+  const to24 = to24Hour(normalizedTime);
+  // 유효한 HH:mm(:ss) 아니면 null
+  if (!/^\d{2}:\d{2}(?::\d{2})?$/.test(to24)) return null;
+  const formattedTime = to24.length === 5 ? `${to24}:00` : to24;
 
   return `${formattedDate}T${formattedTime}`;
 };

서버가 UTC를 요구한다면 아래 대안으로 변환하십시오(로컬 해석 → UTC ISO). 유효하지 않은 날짜면 null 반환:

export const combineDateTimeUTC = (date: string, time: string): string | null => {
  const local = combineDateTime(date, time);
  if (!local) return null;
  const d = new Date(local); // 로컬로 파싱
  if (Number.isNaN(d.getTime())) return null;
  return d.toISOString(); // UTC ISO (Z 포함)
};

검증 스크립트(레포 루트에서 실행):


🏁 Script executed:

#!/bin/bash
# 1) 기존 buildUtcIso 잔존 사용처 확인
rg -nP -C2 '\bbuildUtcIso\s*\('

# 2) EditArticleRequest 내 remindTime 타입/계약 확인
rg -nPU -C3 '(?s)\binterface\s+EditArticleRequest\b.*?\{.*?\n.*remindTime.*\n.*\}'

# 3) remindTime을 서버에 전송하는 쿼리 함수 확인
rg -nP -C3 'usePutEditArticle|remindTime'

# 4) 서버가 기대하는 포맷(UTC/오프셋/로컬) 문서/주석 단서 탐색
rg -nP -C2 'remind|timezone|utc|offset|Z\b|ISO'

Length of output: 289459


🏁 Script executed:

#!/bin/bash
set -eo pipefail

echo "=== Show file: apps/client/src/shared/utils/datetime.ts ==="
sed -n '1,240p' apps/client/src/shared/utils/datetime.ts || true

echo
echo "=== Search for to24Hour, combineDateTime, buildUtcIso across repo ==="
rg -n --hidden --no-ignore-vcs 'to24Hour|combineDateTime|buildUtcIso' || true

echo
echo "=== Show remindTimeFormat utility (used by extension imports) if exists ==="
rg -n --hidden --no-ignore-vcs 'remindTimeFormat' -S || true
rg -n --hidden --no-ignore-vcs '@utils/remindTimeFormat' -S || true

Length of output: 2991


combineDateTime: 타임존 계약 확인 + 파싱 실패 방어(클라이언트·익스텐션 동시 수정 필요)

combineDateTime은 타임존/오프셋 정보 없이 로컬 형식으로 반환하며(to24Hour 호출 후 "YYYY-MM-DDTHH:mm(:ss)" 형태), to24Hour 존재 체크는 무의미합니다. 입력 유효성(날짜/시간 파싱 실패) 처리와 명시적 반환 타입을 추가하고, 서버가 UTC를 요구하면 변환 로직을 별도 함수로 제공하세요.

수정 위치(정확한 포인트): apps/client/src/shared/utils/datetime.ts, apps/extension/src/utils/remindTimeFormat.ts
호출부 확인: apps/client/src/shared/components/cardEditModal/CardEditModal.tsx, apps/extension/src/pages/MainPop.tsx
서버 타입 참조: apps/client/src/shared/types/api.ts (remindTime: string | null) — 서버가 UTC(Z 포함)를 기대하는지 확인 필요.

권장 최소 변경(파일 내 combineDateTime 대체안 — 유효성 검증 + 타입 명시):

-export const combineDateTime = (date: string, time: string) => {
+export const combineDateTime = (date: string, time: string): string | null => {
   if (!date || !time) return null;
@@
-  // 24시간 포맷 변환 함수가 있다면 적용
-  const to24 = to24Hour ? to24Hour(normalizedTime) : normalizedTime;
-  const formattedTime = to24.length === 5 ? `${to24}:00` : to24;
+  // 24시간 포맷 변환
+  const to24 = to24Hour(normalizedTime);
+  if (!/^\d{2}:\d{2}(?::\d{2})?$/.test(to24)) return null;
+  const formattedTime = to24.length === 5 ? `${to24}:00` : to24;
 
   return `${formattedDate}T${formattedTime}`;
 };

서버가 UTC ISO(Z)를 요구한다면(로컬 해석 → UTC ISO 변환) 보조 함수 추가:

export const combineDateTimeUTC = (date: string, time: string): string | null => {
  const local = combineDateTime(date, time);
  if (!local) return null;
  const d = new Date(local); // 로컬로 파싱
  if (Number.isNaN(d.getTime())) return null;
  return d.toISOString(); // UTC ISO (Z 포함)
};

검증 요약: 리포지토리에 동일 로직이 client/extension에 존재함을 확인(동기 수정 필요). 서버가 UTC를 요구할 경우 클라이언트에서 toISOString()으로 변환해 전송하거나 서버 계약에 맞게 명확히 합의해야 합니다.

Loading