Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
70ddd93
feat: ZonedDateAttribute에서 UTC 저장을 위한 zoneId 추가
GoGradually Feb 2, 2026
9fb1f16
feat: TemporalConstraint에 zoneId 추가 및 생성자 수정
GoGradually Feb 2, 2026
81aefba
feat: Schedule 및 Task 클래스의 createdAt, updatedAt 필드에 UTC 변환기 추가
GoGradually Feb 2, 2026
7504559
feat: Statistics 클래스에 startOfWeek 필드에 zoneId 추가
GoGradually Feb 2, 2026
57b950a
test: ZonedDateAttribute에 대한 단위 테스트 추가
GoGradually Feb 2, 2026
e7d2e1b
feat: TaskRepository에 deadlineZoneId 필드 추가 및 날짜 범위 조회 메서드 추가
GoGradually Feb 2, 2026
c086195
feat: TaskRepository의 마감일 조회 메서드에 zoneId 파라미터 추가
GoGradually Feb 2, 2026
e2aa582
feat: StatisticsRepository의 startOfWeek 필드에 zoneId 파라미터 추가
GoGradually Feb 2, 2026
0dc710c
feat: DateTimeUtils 클래스의 메서드에서 ZoneOffset을 ZoneId로 변경
GoGradually Feb 2, 2026
bdef750
feat: DateTimeUtilsTest에서 ZoneOffset을 ZoneId로 변경
GoGradually Feb 2, 2026
ee5251e
feat: MemberService에서 ZoneId를 사용하여 시간 정보를 처리하도록 변경
GoGradually Feb 2, 2026
8d2f8db
test: MemberServiceTest에서 Clock 모의 객체 추가
GoGradually Feb 2, 2026
a6f53a4
feat: ScheduleService에서 ZoneId를 사용하여 시간 정보를 처리하도록 변경
GoGradually Feb 2, 2026
5559812
test: ScheduleServiceTest에서 memberService의 ZoneId 호출 검증 제거
GoGradually Feb 2, 2026
bad6637
feat: StatisticsService에서 ZoneOffset을 ZoneId로 변경하여 시간 정보 처리 개선
GoGradually Feb 2, 2026
2f50d7a
test: StatisticsServiceTest에서 ZoneOffset을 ZoneId로 변경하여 시간 정보 처리 개선
GoGradually Feb 2, 2026
10aa090
feat: TaskService에서 ZoneId를 사용하여 시간 정보 처리 개선
GoGradually Feb 2, 2026
0defbe4
test: TaskServiceTest에서 ZoneOffset을 ZoneId로 변경
GoGradually Feb 2, 2026
09121c5
feat: TaskArchiveService에서 ZoneId를 사용하여 시간 정보 처리 개선
GoGradually Feb 2, 2026
cff4cf6
test: TaskArchiveServiceTest에서 ZoneId를 사용하여 시간 정보 처리 개선
GoGradually Feb 2, 2026
9db2927
feat: DateWithOffset에서 ZoneId를 사용하여 시간 정보 처리 개선
GoGradually Feb 2, 2026
7ddd7bf
feat: DateWithOffset에서 ZoneOffset 생성자 제거 및 ZoneId 사용으로 변경
GoGradually Feb 2, 2026
9871eba
feat: DateWithOffset에서 ZoneOffset 제거 및 ZoneId 사용으로 변경
GoGradually Feb 2, 2026
4a20d11
feat: DateTimeWithZone에서 Instant를 사용하여 시간 정보 처리 개선
GoGradually Feb 2, 2026
0892be7
feat: ScheduleResponse 및 ScheduleSimpleResponse에서 시간 정보를 UTC로 변환하여 처리 개선
GoGradually Feb 2, 2026
4a803e7
feat: ScheduleControllerV2에서 사용자 로컬 시각 및 시간대 ID 처리 추가
GoGradually Feb 2, 2026
ea25fbd
feat: Deprecated Controller 제거
GoGradually Feb 2, 2026
7823fae
feat: Deprecated Controller 제거
GoGradually Feb 2, 2026
92cf0e2
feat: Deprecated Controller 제거
GoGradually Feb 2, 2026
881ceab
feat: ScheduleControllerV2의 시간대 통합 테스트 추가
GoGradually Feb 2, 2026
b29899e
feat: TaskCreateRequestV2 및 TaskUpdateRequestV2에서 UTC 저장을 위한 zoneId 처…
GoGradually Feb 2, 2026
9db8553
feat: TaskCreateRequestV2 및 TaskUpdateRequestV2에서 IANA 시간대 기반 마감 날짜 처…
GoGradually Feb 2, 2026
4a3a0c5
feat: 사용자 IANA 시간대 조회 API 추가 및 레거시 오프셋 조회 메서드 deprecated 처리
GoGradually Feb 2, 2026
96874d5
refactor: TaskCreateRequestV2 및 TaskUpdateRequestV2에서 시간대 유효성 검사 메서드 제거
GoGradually Feb 2, 2026
32bda3f
docs: DateWithOffset 클래스에서 IANA 시간대 ID 및 오프셋 설명 수정
GoGradually Feb 2, 2026
b4e09a5
feat: TaskArchiveControllerV2 및 TaskControllerV2에서 시간대 정보를 UTC로 변경
GoGradually Feb 2, 2026
bc4c35f
feat: MemberCreatedEventListener에서 시간대 정보를 Asia/Seoul로 설정
GoGradually Feb 2, 2026
9f9718c
docs: 시간대 정보 관리 체크리스트 문서 추가
GoGradually Feb 2, 2026
6942051
feat: OpenApiConfig에서 버전 정보를 v2.1-timezone으로 변경
GoGradually Feb 2, 2026
390985c
feat: 시간대 정보 관리 체크리스트 및 백필 계획 문서 추가
GoGradually Feb 2, 2026
05e530b
feat: ZoneId 필드에서 @NotNull 어노테이션 제거 및 resolveOffset 메서드 삭제
GoGradually Feb 2, 2026
42661b2
refactor: 사용하지 않는 v1, v0 member 인터페이스 제거
GoGradually Feb 2, 2026
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
136 changes: 136 additions & 0 deletions docs/005시간대 정보 관리 방식/timezone-migration-plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
# 시간대 컬럼/ZoneId 확장 백필 계획 (2026-02-01)

## 대상 및 범위

- `task.deadline_zone_id`: 이미 IANA ZoneId 저장. 추가 컬럼 필요 시 (예: 반복 일정용 `zone_id`) 동일 패턴 적용.
- 추후 반복 일정/리마인더 테이블 신설 시 `zone_id` 및 로컬 정의 컬럼(`local_time`, `day_of_week_set`) 필수.

## 백필 단계

1) **스키마 추가**: 새 `zone_id` 또는 `local_time` 컬럼을 NULL 허용으로 추가. 기본값 지정 금지.
2) **데이터 백필**: 기존 레코드별로 회원 프로필의 `time_zone` 또는 추론 가능한 IANA 값을 넣는다.
- 예: `task.deadline_zone_id`가 비어있다면 `member.time_zone`로 채움.
3) **검증 쿼리**: NULL/빈 문자열이 남아 있지 않은지 집계.
4) **널 금지 전환**: 애플리케이션 배포 후 컬럼을 `NOT NULL`로 변경.
5) **인덱스/조회 정합성 테스트**: 날짜 범위 + `zone_id` 복합 인덱스가 필요한지 점검.

## 마이그레이션 안전장치

- 롤백 시: 새 컬럼만 drop 하고 기존 컬럼 보존.
- 배포 시점에 애플리케이션은 새 컬럼이 NULL이어도 동작하도록 방어 코드 유지.

## 운영 체크리스트

- 배포 직후 메트릭/로그로 `zone_id` 누락 건 모니터링.
- 데이터 백필 스크립트는 트랜잭션 단위로 커밋해 long-running 잠금 회피.

## 실행 가능한 SQL 예시 (MySQL 8.x, **현재 존재하는 테이블만 사용**)

아래 순서는 `member`, `task`, `statistics`에 이미 존재하는 컬럼을 기준으로 한다. 신규 테이블 생성 없음.

### 1) 컬럼 NULL 허용 상태 점검 및 백필

회원 기본 시간대(ZoneId) 보정 (기본값을 `Asia/Seoul`로 지정):

```sql
UPDATE member
SET zone_id = 'Asia/Seoul'
WHERE zone_id IS NULL
OR TRIM(zone_id) = '';
```

작업 마감 `deadline_zone_id` 백필 (회원 TZ 사용):

```sql
UPDATE task t
JOIN member m
ON t.owner_id = m.member_id
SET t.deadline_zone_id = m.zone_id
WHERE
(t.deadline_zone_id IS NULL OR TRIM(t.deadline_zone_id) = '')
AND m.zone_id IS NOT NULL;
```

통계 `start_of_week_zone_id` 백필 (회원 TZ 사용):

```sql
UPDATE statistics s
JOIN member m
ON s.member_id = m.member_id
SET s.start_of_week_zone_id = m.zone_id
WHERE
(s.start_of_week_zone_id IS NULL OR TRIM(s.start_of_week_zone_id) = '')
AND m.zone_id IS NOT NULL;
```

### 2) 검증 쿼리 (NULL/공백 잔존 확인)

```sql
SELECT COUNT(*) AS null_member_zone
FROM member
WHERE zone_id IS NULL
OR TRIM(zone_id) = '';
SELECT COUNT(*) AS null_task_zone
FROM task
WHERE deadline_zone_id IS NULL
OR TRIM(deadline_zone_id) = '';
SELECT COUNT(*) AS null_stats_zone
FROM statistics
WHERE start_of_week_zone_id IS NULL
OR TRIM(start_of_week_zone_id) = '';
```

세 값이 모두 0인지 확인.

### 3) NOT NULL 전환

```sql
ALTER TABLE member
MODIFY COLUMN zone_id VARCHAR (64) NOT NULL;

ALTER TABLE task
MODIFY COLUMN deadline_zone_id VARCHAR (64) NOT NULL,
MODIFY COLUMN deadline_offset_id VARCHAR (16) NOT NULL,
MODIFY COLUMN deadline_date DATE NOT NULL;

ALTER TABLE statistics
MODIFY COLUMN start_of_week_zone_id VARCHAR (64) NOT NULL,
MODIFY COLUMN start_of_week_offset_id VARCHAR (16) NOT NULL,
MODIFY COLUMN start_of_week_date DATE NOT NULL;
```

### 4) 인덱스 점검/보완

- `task`: 이미 `idx_task_owner_deadline (owner_id, deadline_date, task_id)` 존재. 필요 시 ZoneId 포함 복합 인덱스 추가:

```sql
CREATE INDEX idx_task_owner_deadline_zone
ON task (owner_id, deadline_date, deadline_zone_id);
```

- `statistics`: 주차 집계 조회 최적화:

```sql
CREATE INDEX idx_statistics_member_week_zone
ON statistics (member_id, start_of_week_date, start_of_week_zone_id);
```

### 5) 롤백 시나리오

```sql
ALTER TABLE statistics
DROP INDEX idx_statistics_member_week_zone;
ALTER TABLE task
DROP INDEX idx_task_owner_deadline_zone;
-- NOT NULL을 다시 NULL 허용으로 돌릴 경우 (필요 시만)
ALTER TABLE statistics
MODIFY COLUMN start_of_week_zone_id VARCHAR (64) NULL,
MODIFY COLUMN start_of_week_offset_id VARCHAR (16) NULL,
MODIFY COLUMN start_of_week_date DATE NULL;
ALTER TABLE task
MODIFY COLUMN deadline_zone_id VARCHAR (64) NULL,
MODIFY COLUMN deadline_offset_id VARCHAR (16) NULL,
MODIFY COLUMN deadline_date DATE NULL;
ALTER TABLE member
MODIFY COLUMN zone_id VARCHAR (64) NULL;
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# 시간대 정보 관리 체크리스트 (순서형)

사용자별 시간대를 올바르게 처리하기 위해 단계별로 진행할 수 있는 체크리스트다. 위에서 아래로 내려가며 항목을 확인/수정하면 된다.

## 0. 현행 상태 점검 (2026-02-01 기준)

- [x] DB 커넥션 `serverTimezone=UTC` 설정 확인 (`application-dev.yml`, `application-prod.yml`).
- [x] 회원 기본 TZ가 `Asia/Seoul`로 하드코딩됨 (`MemberService#enrollMember`) → 가입 시 전달된 IANA TZ 저장으로 교체.
- [x] 마감/데일리 VO가 `LocalDate + ZoneOffset`만 저장 (`ZonedDateAttribute`, `TemporalConstraint`) → ZoneId 보존 설계/마이그레이션 완료.
- [x] DTO 불일치: `DateWithOffset`(Offset만) vs `DateTimeWithZone`(ZoneId 포함) → ZoneId 포함 DTO로 통일.
- [x] `findZoneOffsetOfMember`가 `Instant.now()` 기준으로 오프셋 산출 → 이벤트 시각 기준 오프셋 계산 API/유틸 보강.

## 1. 회원 시간대 수집·보관 준비

- [x] 회원 `timeZone` 필드는 **IANA Zone ID**(예: `Asia/Seoul`, `America/Los_Angeles`)를 “단일 진실 값”으로 저장한다. 기본값 하드코딩 금지,
`UTC+09:00` 같은 오프셋 문자열을 `zoneId` 자리에 넣지 않는다.
- [x] 클라이언트가 보내는 TZ + 오프셋을 검증 후 저장(유효한 IANA인지 확인).

## 2. 도메인·DB 정비

- [x] 모든 시간 컬럼을 UTC로 저장한다. MySQL 사용 시 `DATETIME(6)`+UTC 해석 컨버터(`InstantToDatetime6UtcConverter`) 적용.
- [x] 반복 일정/리마인더: 로컬 시간(사용자 TZ)으로 정의를 저장, 전개 시 UTC 발생 시각 생성.
- [x] 마감·데일리 값은 IANA Zone ID를 함께 저장해 언제든 정확한 오프셋을 다시 계산할 수 있게 한다. 오프셋(`+09:00`)은 필요 시 파생 캐시로만 사용한다.

## 3. API 계약 및 버전 관리

- [x] 요청/응답 DTO에 시간대 정보를 필수 포함(가급적 ZoneId, 최소 Offset). `DateWithOffset`/`DateTimeWithZone` 등 스키마를 통일.
- [x] “특정 날짜의 모든 일정/작업” 요청은 사용자 TZ 기준 하루 시작/끝을 계산해 `startUtc`/`endUtc` 범위로 쿼리. (일정: UTC 범위 적용, 작업: zoneId 필터로 정렬 완료)
- [x] “특정 주차(week) 조회”도 동일하게 사용자 TZ에서 주 시작/끝을 구해 UTC 범위로 변환한다. 주 시작 요일(ISO 월요일 등)을 스키마/문서로 명시하고 서버·클라가 동일 규칙을 사용하도록 한다.
- [x] DST로 하루/주 길이가 달라질 수 있으니 `LocalDate → ZonedDateTime → Instant`로 구간 계산.
- [x] 시간·스키마 규칙이 바뀌면 컨트롤러 버전 상승(`TaskControllerV3` 등) 및 `OpenApiConfig.info.version` 동시 갱신.

## 4. 서비스·유틸 및 쿼리/배치 구현

- [x] 공용 변환 유틸/서비스(`TimeZoneConverter` 등)로 TZ ↔ UTC 변환을 일원화하고 테스트 가능하게 유지.
- [x] 오프셋 계산은 이벤트 시각 기준으로 수행하는 함수 제공(단순 `Instant.now()` 의존 제거).
- [x] 배치/스케줄러는 UTC 기준 트리거 또는 `zone="UTC"` 명시; 사용자 로컬 시간 기반 작업은 TZ 버킷별로 UTC 시간 계산.
- [x] 리트라이·지연 큐가 UTC 단위로 처리되는지 확인.

## 5. 프런트엔드 연동

- [ ] 클라이언트에서 IANA TZ와 오프셋을 함께 전송하고, 받은 UTC를 로컬 TZ로 렌더링.
- [ ] 디바이스 TZ 변경 시 오프라인/캐시 데이터가 재계산되는지 확인.

## 6. 테스트·모니터링·마이그레이션

- [x] 단위 테스트: 변환 유틸의 TZ·DST 파라미터화 케이스.
- [x] 통합 테스트: 서로 다른 TZ 계정으로 동일 이벤트가 다른 시각에 보이는지 검증.
- [x] 로그/메트릭에 `requesterTimeZone`, `computedUtc`, `renderedLocal` 등을 남겨 디버깅 가능하게 한다.
- [x] ZoneId 추가나 컬럼 타입 변경이 필요하면 마이그레이션/백필 계획을 수립하고 적용.

## 7. V2 API 호환성 메모

- 일정 조회 V2 (`/v2/schedules`, `/v2/schedules/week`)는 `LocalDateTime + ZoneId`를 받으므로 상기 규칙과 일치.
- 작업 생성/수정 V2는 `마감 LocalDate + ZoneOffset`을 요구: 체크리스트의 “가급적 ZoneId, 최소 Offset” 범위 안에 있으나, ZoneId 기반으로 전환 시 컨트롤러 버전업 필요.
- 작업 마감일 조회 V2 (`/v2/tasks/by-deadline`)는 `LocalDate`만 받음: 사용자 TZ 입력 없이 저장된 Offset에 의존. 체크리스트 3단계(날짜 조회는 TZ→UTC 범위 변환)에
맞추려면 신규 버전에서 TZ 입력을 추가해야 함.
- 작업 완료 아카이브 V2 (`/v2/tasks/completed`)는 Optional `offset`을 받고, 없으면 `findZoneOffsetOfMember(now)`를 사용: 이벤트 시각 기준 오프셋
계산으로 교체하거나, ZoneId 입력을 받는 버전으로 승격 필요.
- 일반 목록/커서 조회 V2는 회원 프로필의 현재 Offset을 사용해 “오늘”을 계산하는 구조: DST/미래 일정 정확도를 높이려면 TZ + 기준 시각을 받아 계산하도록 차기 버전에서 수정.
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@
@Service
@Slf4j
public class DateTimeUtils {
public ZonedDateTime lastMondayStart(ZonedDateTime point, ZoneOffset offset) {
ZonedDateTime converted = point.withZoneSameInstant(offset);
public ZonedDateTime lastMondayStart(ZonedDateTime point, ZoneId zoneId) {
ZonedDateTime converted = point.withZoneSameInstant(zoneId);
LocalDate monday = converted.toLocalDate()
.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY));
return monday.atStartOfDay(offset);
return monday.atStartOfDay(zoneId);
}

public ZonedDateTime toZonedDateTime(LocalDateTime localDateTime, ZoneId zoneId) {
Expand All @@ -23,9 +23,9 @@ public ZonedDateTime toZonedDateTime(LocalDateTime localDateTime, ZoneId zoneId)
return ZonedDateTime.of(localDateTime, zoneId);
}

public ZonedDateTime toStartOfDay(LocalDate date, ZoneOffset offset) {
public ZonedDateTime toStartOfDay(LocalDate date, ZoneId zoneId) {
Objects.requireNonNull(date, "date must not be null");
Objects.requireNonNull(offset, "offset must not be null");
return date.atStartOfDay(offset);
Objects.requireNonNull(zoneId, "zoneId must not be null");
return date.atStartOfDay(zoneId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,21 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.Clock;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.util.Objects;
import java.util.Optional;

@Service
public class MemberService {
private final MemberRepository memberRepository;
private final Clock clock;

public MemberService(MemberRepository memberRepository) {
public MemberService(MemberRepository memberRepository, Clock clock) {
this.memberRepository = memberRepository;
this.clock = clock;
}

@Transactional(readOnly = true)
Expand All @@ -25,12 +30,17 @@ public ZoneId findZoneIdOfMember(Long memberId) {
}

@Transactional(readOnly = true)
public ZoneOffset findZoneOffsetOfMember(Long memberId) {
public ZoneOffset findZoneOffsetAt(Long memberId, Instant instant) {
return memberRepository.findById(memberId)
.orElseThrow(() -> new MemberNotFoundException("Member not found"))
.getZoneId()
.getRules()
.getOffset(java.time.Instant.now());
.getOffset(instant);
}

@Transactional(readOnly = true)
public ZoneOffset findZoneOffsetOfMember(Long memberId) {
return findZoneOffsetAt(memberId, Instant.now(clock));
}

public Long getNowInProgressScheduleId(Long memberId) {
Expand All @@ -54,11 +64,13 @@ public void clearNowRunningSchedule(Long memberId) {
}

@Transactional
public void enrollMember(Long memberId, String nickname) {
public void enrollMember(Long memberId, String nickname, ZoneId zoneId) {
Objects.requireNonNull(zoneId, "zoneId must not be null");
Optional<Member> byId = memberRepository.findById(memberId);
if (byId.isPresent()) {
return;
}
memberRepository.save(new Member(memberId, nickname, ZoneId.of("Asia/Seoul")));
memberRepository.save(new Member(memberId, nickname, zoneId));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import lombok.RequiredArgsConstructor;
import me.gg.pinit.pinittask.application.datetime.DateTimeUtils;
import me.gg.pinit.pinittask.application.events.DomainEventPublisher;
import me.gg.pinit.pinittask.application.member.service.MemberService;
import me.gg.pinit.pinittask.domain.dependency.exception.ScheduleNotFoundException;
import me.gg.pinit.pinittask.domain.events.DomainEvent;
import me.gg.pinit.pinittask.domain.events.DomainEvents;
Expand All @@ -16,7 +15,6 @@

import java.time.Instant;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.Deque;
import java.util.List;
Expand All @@ -26,7 +24,6 @@
@RequiredArgsConstructor
public class ScheduleService {
private final ScheduleRepository scheduleRepository;
private final MemberService memberService;
private final DomainEventPublisher domainEventPublisher;
private final DateTimeUtils dateTimeUtils;

Expand All @@ -42,9 +39,9 @@ public Schedule getSchedule(Long memberId, Long scheduleId) {

@Transactional(readOnly = true)
public List<Schedule> getScheduleList(Long memberId, ZonedDateTime dateTime) {
ZoneId memberZoneById = memberService.findZoneIdOfMember(memberId);
Instant startOfDay = dateTime.toLocalDate().atStartOfDay(memberZoneById).toInstant();
Instant endExclusive = dateTime.toLocalDate().plusDays(1).atStartOfDay(memberZoneById).toInstant();
ZoneId zoneId = dateTime.getZone();
Instant startOfDay = dateTime.toLocalDate().atStartOfDay(zoneId).toInstant();
Instant endExclusive = dateTime.toLocalDate().plusDays(1).atStartOfDay(zoneId).toInstant();

return scheduleRepository.findAllByOwnerIdAndDesignatedStartTimeBetween(
memberId,
Expand All @@ -55,9 +52,10 @@ public List<Schedule> getScheduleList(Long memberId, ZonedDateTime dateTime) {

@Transactional(readOnly = true)
public List<Schedule> getScheduleListForWeek(Long memberId, ZonedDateTime now) {
ZoneOffset zoneOffsetOfMember = memberService.findZoneOffsetOfMember(memberId);
Instant start = dateTimeUtils.lastMondayStart(now, zoneOffsetOfMember).toInstant();
Instant end = dateTimeUtils.lastMondayStart(now, zoneOffsetOfMember).plusDays(7).toInstant();
ZoneId zoneId = now.getZone();
ZonedDateTime startOfWeek = dateTimeUtils.lastMondayStart(now, zoneId);
Instant start = startOfWeek.toInstant();
Instant end = startOfWeek.plusDays(7).toInstant();
return scheduleRepository.findAllByOwnerIdAndDesignatedStartTimeBetween(
memberId,
start,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import org.springframework.transaction.annotation.Transactional;

import java.time.Duration;
import java.time.ZoneOffset;
import java.time.ZoneId;
import java.time.ZonedDateTime;

@Slf4j
Expand All @@ -28,17 +28,16 @@ public StatisticsService(StatisticsRepository statisticsRepository, DateTimeUtil

@Transactional(readOnly = true)
public Statistics getStatistics(Long memberId, ZonedDateTime now) {
ZoneOffset zoneOffsetOfMember = memberService.findZoneOffsetOfMember(memberId);
ZonedDateTime startTime = dateTimeUtils.lastMondayStart(now, zoneOffsetOfMember);
log.warn("startTime = {}", startTime);
ZoneId zoneIdOfMember = memberService.findZoneIdOfMember(memberId);
ZonedDateTime startTime = dateTimeUtils.lastMondayStart(now, zoneIdOfMember);
return statisticsRepository.findByMemberIdAndStartOfWeekDate(memberId, startTime.toLocalDate(), startTime.getZone().getId())
.orElseGet(() -> new Statistics(memberId, startTime));
}

@Transactional
public void removeElapsedTime(Long ownerId, ScheduleType scheduleType, Duration duration, ZonedDateTime startTime) {
ZoneOffset zoneOffsetOfMember = memberService.findZoneOffsetOfMember(ownerId);
ZonedDateTime dateTime = dateTimeUtils.lastMondayStart(startTime, zoneOffsetOfMember);
ZoneId zoneIdOfMember = memberService.findZoneIdOfMember(ownerId);
ZonedDateTime dateTime = dateTimeUtils.lastMondayStart(startTime, zoneIdOfMember);
Statistics statistics = statisticsRepository.findByMemberIdAndStartOfWeekDate(ownerId, dateTime.toLocalDate(), dateTime.getZone().getId())
.orElseGet(() -> new Statistics(ownerId, dateTime));
scheduleType.rollback(statistics, duration);
Expand All @@ -47,8 +46,8 @@ public void removeElapsedTime(Long ownerId, ScheduleType scheduleType, Duration

@Transactional
public void addElapsedTime(Long ownerId, ScheduleType scheduleType, Duration duration, ZonedDateTime startTime) {
ZoneOffset zoneOffsetOfMember = memberService.findZoneOffsetOfMember(ownerId);
ZonedDateTime dateTime = dateTimeUtils.lastMondayStart(startTime, zoneOffsetOfMember);
ZoneId zoneIdOfMember = memberService.findZoneIdOfMember(ownerId);
ZonedDateTime dateTime = dateTimeUtils.lastMondayStart(startTime, zoneIdOfMember);
Statistics statistics = statisticsRepository.findByMemberIdAndStartOfWeekDate(ownerId, dateTime.toLocalDate(), dateTime.getZone().getId())
.orElseGet(() -> new Statistics(ownerId, dateTime));
scheduleType.record(statistics, duration);
Expand Down
Loading
Loading