Skip to content

[Refactor/#309] 타임존을 고려한 API 수정사항을 반영합니다.#311

Merged
SYAAINN merged 7 commits intodevelopfrom
refactor/#309-timezone-api-refactor
Jul 31, 2025
Merged

[Refactor/#309] 타임존을 고려한 API 수정사항을 반영합니다.#311
SYAAINN merged 7 commits intodevelopfrom
refactor/#309-timezone-api-refactor

Conversation

@SYAAINN
Copy link
Contributor

@SYAAINN SYAAINN commented Jul 29, 2025

📌 ISSUE

closed #309

📄 Work Description

  • 알림 관련 API 를 위한 KST <-> UST(User's Time Zone) 변환 함수 정의
  • 일기 관련 API 헤더에 TimeZone을 추가하는 TimeZoneInterceptor 구현
  • TimeZone 이 한국일 경우에는 전날 일기 작성 가능, 이외에는 전날 일기 작성 불가능

✨ PR Point

일일이 할까하다가 API 개수가 상당해서 .. Interceptor가 편할것 같아 진행했는데
일일이 하는게 바람직하다면 말씀해주십시오 ㅠㅠ 수정해보겠습니다.

📸 ScreenShot/Video

Summary by CodeRabbit

  • New Features

    • Added automatic detection and handling of the user's system time zone for relevant features, including HTTP requests and time display.
  • Improvements

    • Enhanced time selection and notification settings to accurately convert and display times based on the user's local time zone.
    • Updated onboarding and notification-related text to reflect new time zone logic and clarify journaling availability.
    • Adjusted diary writing availability based on user’s time zone.
    • Introduced structured nickname validation messages for clearer user feedback.
  • Bug Fixes

    • Corrected time format display in notification settings for consistency and accuracy.
  • Refactor

    • Simplified and unified time conversion logic across the app for easier maintenance and improved reliability.
    • Replaced raw nickname message strings with typed message representations across signup and settings screens.

@SYAAINN SYAAINN requested a review from MoonsuKang July 29, 2025 10:48
@SYAAINN SYAAINN self-assigned this Jul 29, 2025
@SYAAINN SYAAINN added ♻️ REFACTOR 코드 리팩토링(전면 수정) 🔥 민재 민재 labels Jul 29, 2025
@coderabbitai
Copy link

coderabbitai bot commented Jul 29, 2025

Walkthrough

Time zone handling has been integrated across the API and UI layers. A new OkHttp interceptor injects the user's system time zone into HTTP requests for specific endpoints. Time conversion utilities were introduced, and notification time selection, display, and submission logic were refactored to consistently use time zone-aware conversions. Related ViewModel and composable interfaces were updated.

Changes

Cohort / File(s) Change Summary
Time Zone Interceptor Integration
app/src/main/java/com/sopt/clody/data/remote/util/TimeZoneInterceptor.kt, app/src/main/java/com/sopt/clody/di/NetworkModule.kt
Added a TimeZoneInterceptor to inject the system time zone into relevant HTTP requests. Updated dependency injection in NetworkModule to provide and register the interceptor in the OkHttp client.
Time Zone Conversion Utilities
app/src/main/java/com/sopt/clody/presentation/utils/extension/TimeZoneExt.kt
Introduced convertKSTtoUTZ and convertUTZtoKST functions for converting between Korea Standard Time and the user's local time zone, handling AM/PM, hour, and minute conversions.
Notification Time Selection and Display
app/src/main/java/com/sopt/clody/presentation/ui/setting/notificationsetting/component/NotificationSettingTimePicker.kt, app/src/main/java/com/sopt/clody/presentation/ui/setting/notificationsetting/component/NotificationTimeSelector.kt, app/src/main/java/com/sopt/clody/presentation/ui/setting/notificationsetting/screen/NotificationSettingScreen.kt, app/src/main/java/com/sopt/clody/presentation/ui/setting/notificationsetting/screen/NotificationSettingViewModel.kt
Refactored notification time picker and selector to use time zone-aware conversions. Updated composable and ViewModel interfaces to pass and handle time as AM/PM, hour, and minute, using the new utilities for conversion and formatting.
Time Reminder Logic Refactor
app/src/main/java/com/sopt/clody/presentation/ui/auth/timereminder/TimeReminderScreen.kt, app/src/main/java/com/sopt/clody/presentation/ui/auth/timereminder/TimeReminderViewModel.kt
Changed time setting logic to use setSelectedTime with time zone conversion, removed redundant methods, and updated callback usage in the screen.
Diary Write Logic Update
app/src/main/java/com/sopt/clody/presentation/ui/home/screen/HomeViewModel.kt
Modified diary write eligibility logic to consider the user's time zone, allowing yesterday's entry only if the user is in "Asia/Seoul". Adjusted draft alarm time logic to use the provided time without fallback.
Nickname Message Type Refactor
app/src/main/java/com/sopt/clody/presentation/ui/auth/signup/NicknameMessage.kt, app/src/main/java/com/sopt/clody/presentation/ui/auth/signup/SignUpContract.kt, app/src/main/java/com/sopt/clody/presentation/ui/auth/signup/SignUpViewModel.kt, app/src/main/java/com/sopt/clody/presentation/ui/auth/signup/page/NicknamePage.kt, app/src/main/java/com/sopt/clody/presentation/ui/setting/component/NicknameChangeBottomSheet.kt, app/src/main/java/com/sopt/clody/presentation/ui/setting/screen/AccountManagementScreen.kt, app/src/main/java/com/sopt/clody/presentation/ui/setting/screen/AccountManagementViewModel.kt
Replaced nickname message representation from plain strings to a typed NicknameMessage enum with localized message retrieval. Updated all related UI components and ViewModels accordingly.
String Resource Updates
app/src/main/res/values/strings.xml, app/src/main/res/values-ko/strings.xml
Added nickname validation messages. Updated notification time string formatting to use string format specifiers for hour. Changed onboarding guide text to reflect updated diary write policy.
UI Layout Adjustment
app/src/main/java/com/sopt/clody/presentation/ui/auth/component/timepicker/BottomSheetTimePicker.kt
Added flexible spacing around time picker components to center them horizontally.
Deprecated Time Format Extensions Removed
app/src/main/java/com/sopt/clody/presentation/utils/extension/StringExt.kt
Removed old string extension functions for 12-hour and 24-hour time format conversions, replaced by new time zone aware utilities.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI (TimePicker/Selector)
    participant ViewModel
    participant TimeZoneUtil
    participant OkHttpClient
    participant TimeZoneInterceptor
    participant API Server

    User->>UI (TimePicker/Selector): Selects notification time (AM/PM, hour, minute)
    UI (TimePicker/Selector)->>ViewModel: onConfirm(TimePeriod, hour, minute)
    ViewModel->>TimeZoneUtil: convertUTZtoKST(TimePeriod, hour, minute)
    TimeZoneUtil-->>ViewModel: timeString (KST)
    ViewModel->>OkHttpClient: Send API request (with timeString)
    OkHttpClient->>TimeZoneInterceptor: Intercept request
    TimeZoneInterceptor->>OkHttpClient: Add Time-Zone header (if endpoint matches)
    OkHttpClient->>API Server: Send request with Time-Zone header
    API Server-->>OkHttpClient: Response
    OkHttpClient-->>ViewModel: Response
    ViewModel-->>UI (TimePicker/Selector): Update state
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Assessment against linked issues

Objective Addressed Explanation
Time zone-aware API modifications (#309)

Assessment against linked issues: Out-of-scope changes

No out-of-scope changes found.

Suggested labels

🌊 문수

Poem

Oh, what a time to zone and cheer,
The clocks now know just where we’re near!
With headers sent and times converted,
No diary entry is ever subverted.
From Seoul to far-off rabbit holes,
Our code now honors all time roles!
🕰️🐇✨

Note

⚡️ Unit Test Generation is now available in beta!

Learn more here, or try it out under "Finishing Touches" below.


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8f0e438 and 90ba7af.

📒 Files selected for processing (12)
  • app/src/main/java/com/sopt/clody/presentation/ui/auth/component/timepicker/BottomSheetTimePicker.kt (3 hunks)
  • app/src/main/java/com/sopt/clody/presentation/ui/auth/signup/NicknameMessage.kt (1 hunks)
  • app/src/main/java/com/sopt/clody/presentation/ui/auth/signup/SignUpContract.kt (1 hunks)
  • app/src/main/java/com/sopt/clody/presentation/ui/auth/signup/SignUpViewModel.kt (2 hunks)
  • app/src/main/java/com/sopt/clody/presentation/ui/auth/signup/page/NicknamePage.kt (4 hunks)
  • app/src/main/java/com/sopt/clody/presentation/ui/setting/component/NicknameChangeBottomSheet.kt (4 hunks)
  • app/src/main/java/com/sopt/clody/presentation/ui/setting/notificationsetting/component/NotificationSettingTimePicker.kt (2 hunks)
  • app/src/main/java/com/sopt/clody/presentation/ui/setting/screen/AccountManagementScreen.kt (2 hunks)
  • app/src/main/java/com/sopt/clody/presentation/ui/setting/screen/AccountManagementViewModel.kt (3 hunks)
  • app/src/main/java/com/sopt/clody/presentation/utils/extension/StringExt.kt (0 hunks)
  • app/src/main/res/values-ko/strings.xml (2 hunks)
  • app/src/main/res/values/strings.xml (3 hunks)
💤 Files with no reviewable changes (1)
  • app/src/main/java/com/sopt/clody/presentation/utils/extension/StringExt.kt
✅ Files skipped from review due to trivial changes (4)
  • app/src/main/java/com/sopt/clody/presentation/ui/auth/signup/SignUpContract.kt
  • app/src/main/java/com/sopt/clody/presentation/ui/auth/component/timepicker/BottomSheetTimePicker.kt
  • app/src/main/java/com/sopt/clody/presentation/ui/auth/signup/NicknameMessage.kt
  • app/src/main/java/com/sopt/clody/presentation/ui/auth/signup/SignUpViewModel.kt
🚧 Files skipped from review as they are similar to previous changes (3)
  • app/src/main/res/values-ko/strings.xml
  • app/src/main/java/com/sopt/clody/presentation/ui/setting/notificationsetting/component/NotificationSettingTimePicker.kt
  • app/src/main/res/values/strings.xml
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (4)
app/src/main/java/com/sopt/clody/presentation/ui/setting/screen/AccountManagementScreen.kt (1)

21-21: LGTM! Clean refactoring to use typed NicknameMessage.

The import addition and parameter type change from String to NicknameMessage are consistent with the broader refactoring for improved type safety and localization support.

Also applies to: 107-107

app/src/main/java/com/sopt/clody/presentation/ui/auth/signup/page/NicknamePage.kt (1)

30-30: LGTM! Proper implementation of NicknameMessage refactoring.

All changes are correctly implemented:

  • Import added appropriately
  • Parameter type changed to NicknameMessage
  • Usage updated to call getMessage() for proper localization
  • Preview updated with the correct enum value

Also applies to: 43-43, 123-123, 153-153

app/src/main/java/com/sopt/clody/presentation/ui/setting/component/NicknameChangeBottomSheet.kt (1)

30-30: LGTM! Consistent NicknameMessage implementation across both functions.

The refactoring is properly implemented:

  • Import added correctly
  • Both function signatures updated to use NicknameMessage type
  • Usage properly calls getMessage() for localized display
  • Parameter passing between functions maintains consistency

Also applies to: 42-42, 66-66, 138-138

app/src/main/java/com/sopt/clody/presentation/ui/setting/screen/AccountManagementViewModel.kt (1)

10-10: LGTM! ViewModel properly refactored to use NicknameMessage enum.

The changes maintain the original validation logic while improving type safety:

  • Import added correctly
  • StateFlow type changed to NicknameMessage with proper default value
  • Validation logic updated to use NicknameMessage.INVALID and NicknameMessage.DEFAULT
  • Public property type matches the private StateFlow

Also applies to: 39-40, 118-118, 121-121

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/#309-timezone-api-refactor

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🧹 Nitpick comments (4)
app/src/main/java/com/sopt/clody/presentation/ui/auth/timereminder/TimeReminderScreen.kt (1)

105-105: LGTM! Consider making default time configurable.

The change correctly replaces the removed setFixedTime method with setSelectedTime using the new time zone conversion approach. The hardcoded default time (9:30 PM) is reasonable.

Consider extracting the default time values to constants for maintainability:

companion object {
    private val DEFAULT_TIME_PERIOD = TimePeriod.PM
    private const val DEFAULT_HOUR = "9"
    private const val DEFAULT_MINUTE = "30"
}

// Usage:
viewModel.setSelectedTime(DEFAULT_TIME_PERIOD, DEFAULT_HOUR, DEFAULT_MINUTE)
app/src/main/java/com/sopt/clody/presentation/ui/home/screen/HomeViewModel.kt (1)

236-245: Business logic correctly implements time zone-based diary writing rules.

The implementation properly enforces the business requirement where Korean users (Asia/Seoul) can write diary entries for today or yesterday, while users in other time zones can only write for today.

Consider extracting the time zone constant for better maintainability:

+    companion object {
+        private const val KOREAN_TIME_ZONE = "Asia/Seoul"
+    }
+
     fun canWriteDiary(): Boolean {
         val userTimeZone = ZoneId.systemDefault().id
         val today = LocalDate.now()
         val selected = _selectedDate.value
-        val isAvailableDay = if (userTimeZone == "Asia/Seoul") {
+        val isAvailableDay = if (userTimeZone == KOREAN_TIME_ZONE) {
             selected == today || selected == today.minusDays(1)
         } else {
             selected == today
         }
         return _diaryCount.value == 0 && isAvailableDay
     }
app/src/main/java/com/sopt/clody/data/remote/util/TimeZoneInterceptor.kt (2)

8-11: Consider thread safety and error handling for timezone capture.

The current implementation captures the system timezone once at instantiation, which is efficient. However, consider these potential issues:

  1. Timezone changes: If the user changes their system timezone while the app is running, this won't be reflected.
  2. Error handling: ZoneId.systemDefault() could potentially throw exceptions in edge cases.

Consider this more robust approach:

-    private val userTimeZone = ZoneId.systemDefault().id
+    private fun getUserTimeZone(): String = try {
+        ZoneId.systemDefault().id
+    } catch (e: Exception) {
+        "UTC" // fallback to UTC if system timezone is unavailable
+    }

And update the usage:

-        chain.proceed(request.newBuilder().addHeader(TIME_ZONE, userTimeZone).build())
+        chain.proceed(request.newBuilder().addHeader(TIME_ZONE, getUserTimeZone()).build())

23-31: Consider improving URL matching precision.

The current string-based matching works but has some concerns:

  1. Redundant patterns: "api/v1/diary" already matches "api/v1/diary/time"
  2. Potential false positives with simple contains() checks
  3. Could match unintended URLs containing these substrings

Consider this more precise approach:

private fun shouldAddTimeZoneHeader(url: String): Boolean {
    val path = url.substringAfter("//").substringAfter("/")
    return path.startsWith("api/v1/diary") ||
           path.startsWith("api/v1/calendar") ||
           path.startsWith("api/v1/reply") ||
           path.startsWith("api/v1/draft")
}

Or use regex for even more precision if needed.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a78740f and c0df468.

📒 Files selected for processing (12)
  • app/src/main/java/com/sopt/clody/data/remote/util/TimeZoneInterceptor.kt (1 hunks)
  • app/src/main/java/com/sopt/clody/di/NetworkModule.kt (2 hunks)
  • app/src/main/java/com/sopt/clody/presentation/ui/auth/timereminder/TimeReminderScreen.kt (1 hunks)
  • app/src/main/java/com/sopt/clody/presentation/ui/auth/timereminder/TimeReminderViewModel.kt (2 hunks)
  • app/src/main/java/com/sopt/clody/presentation/ui/home/screen/HomeViewModel.kt (3 hunks)
  • app/src/main/java/com/sopt/clody/presentation/ui/setting/notificationsetting/component/NotificationSettingTimePicker.kt (2 hunks)
  • app/src/main/java/com/sopt/clody/presentation/ui/setting/notificationsetting/component/NotificationTimeSelector.kt (3 hunks)
  • app/src/main/java/com/sopt/clody/presentation/ui/setting/notificationsetting/screen/NotificationSettingScreen.kt (2 hunks)
  • app/src/main/java/com/sopt/clody/presentation/ui/setting/notificationsetting/screen/NotificationSettingViewModel.kt (3 hunks)
  • app/src/main/java/com/sopt/clody/presentation/utils/extension/TimeZoneExt.kt (1 hunks)
  • app/src/main/res/values-ko/strings.xml (1 hunks)
  • app/src/main/res/values/strings.xml (2 hunks)
🧰 Additional context used
🪛 detekt (1.23.8)
app/src/main/java/com/sopt/clody/presentation/utils/extension/TimeZoneExt.kt

[warning] 55-55: String.format("%02s:%02s", kstHour, kstMinute) uses implicitly default locale for string formatting.

(detekt.potential-bugs.ImplicitDefaultLocale)

🔇 Additional comments (24)
app/src/main/res/values-ko/strings.xml (1)

107-107: LGTM! Format specifier change aligns with time zone refactoring.

The change from %3$s to %3$d for the minute component correctly supports the new time handling approach where minutes are passed as integers rather than strings, ensuring proper formatting in the Korean locale.

app/src/main/java/com/sopt/clody/presentation/ui/home/screen/HomeViewModel.kt (1)

285-285: LGTM! More precise time handling.

Removing the fallback default and directly using info.time makes the time handling more precise and consistent with the new time zone-aware approach.

app/src/main/res/values/strings.xml (2)

34-34: LGTM! Guide text correctly reflects new time zone-based business rules.

The updated text removes the reference to writing diary entries for yesterday, which aligns with the new business logic where only Korean users can write for yesterday while others can only write for today.


107-107: LGTM! Format specifier change supports integer minute values.

The change from %3$s to %3$d for the minute component is consistent with the Korean locale version and properly supports the new time handling approach where minutes are passed as integers.

app/src/main/java/com/sopt/clody/presentation/ui/setting/notificationsetting/screen/NotificationSettingScreen.kt (3)

68-70: LGTM: Formatting improvement

The multi-line parameter formatting improves readability.


73-74: LGTM: Callback signature updated for timezone handling

The callback signature change from a single newNotificationTime parameter to separate timePeriod, hour, and minute parameters aligns well with the new timezone-aware architecture. This allows for more precise time handling and conversion.


157-157: Time format is consistent; no action needed
Verified that the ViewModel stores notificationTime as a KST "HH:mm" string (from the API) and that NotificationTimeSelector correctly applies convertKSTtoUTZ(time) for local display. The reverse conversion convertUTZtoKST(...) in changeNotificationTime maintains symmetry when sending updates back to the server.

app/src/main/java/com/sopt/clody/presentation/ui/setting/notificationsetting/component/NotificationTimeSelector.kt (2)

17-17: LGTM: Proper timezone conversion for display

The import and usage of convertKSTtoUTZ correctly converts the stored KST time to the user's local timezone for display purposes. This maintains the separation between storage format (KST) and display format (user's timezone).

Also applies to: 26-26


47-47: Placeholders and conversion logic verified

  • The %1$s (label), %2$d (hour), and %3$d (minute) placeholders in
    • app/src/main/res/values/strings.xml:107 (%2$d:%3$d %1$s) and
    • app/src/main/res/values-ko/strings.xml:107 (%1$s %2$d시 %3$d분)
      match the (String, Int, Int) arguments passed via stringResource(...).
  • convertKSTtoUTZ in TimeZoneExt.kt uses ZonedDateTime.withZoneSameInstant, which correctly applies DST and offset transitions. It will propagate a DateTimeParseException if the input doesn’t match "HH:mm"—ensure any external time strings are pre-validated if necessary.

No changes required.

app/src/main/java/com/sopt/clody/presentation/ui/setting/notificationsetting/component/NotificationSettingTimePicker.kt (2)

31-31: LGTM: Improved interface design

The callback signature change from a single String parameter to TimePeriod, String, String parameters provides better type safety and clearer intent. Using the TimePeriod enum instead of string-based AM/PM handling is a good design choice.

Also applies to: 37-37


140-141: LGTM: Better separation of concerns

Removing the time format conversion logic from the UI component and passing raw selected values improves separation of concerns. The ViewModel is now responsible for time conversion, which is more appropriate.

app/src/main/java/com/sopt/clody/presentation/ui/setting/notificationsetting/screen/NotificationSettingViewModel.kt (2)

10-11: LGTM: Proper imports for timezone conversion

The imports for TimePeriod and convertUTZtoKST support the new timezone-aware notification time handling.


145-145: LGTM: Updated method signature for better parameter handling

The method signature change to accept separate timePeriod, hour, and minute parameters provides better type safety and aligns with the UI component changes.

app/src/main/java/com/sopt/clody/di/NetworkModule.kt (3)

10-10: LGTM: Proper dependency injection setup

The import and singleton provider for TimeZoneInterceptor follow proper DI patterns and ensure the interceptor is available throughout the application.

Also applies to: 45-47


54-54: LGTM: Interceptor properly integrated into HTTP client

The TimeZoneInterceptor is correctly added to the OkHttp client's interceptor chain. The parameter addition to the provider method signature maintains proper dependency injection.

Also applies to: 59-59


45-47: Confirm TimeZoneInterceptor Presence and Behavior

I ran automated searches but could not locate the TimeZoneInterceptor implementation in the repo. Please manually verify that:

  • TimeZoneInterceptor.kt exists under com.sopt.clody.data.remote.util.
  • It correctly identifies and intercepts diary‐related API endpoints.
  • It adds the timezone header in the expected format.
  • It handles edge cases (e.g., system timezone unavailable).
app/src/main/java/com/sopt/clody/presentation/ui/auth/timereminder/TimeReminderViewModel.kt (3)

82-84: LGTM! Clean refactoring of time conversion logic.

The consolidation of time formatting into the setSelectedTime method using the utility function is a good improvement. The method signature is clear and the conversion to KST for notification scheduling makes sense in the Korean app context.


13-13: LGTM!

The import addition supports the time zone conversion refactoring and aligns with the PR objectives.


82-84: LGTM!

The method correctly uses the time zone conversion utility to convert user input to KST format before storing it. This ensures consistent time handling when sending notifications to the API.

app/src/main/java/com/sopt/clody/data/remote/util/TimeZoneInterceptor.kt (5)

36-38: LGTM! Good use of companion object for constants.

The constant definition follows Kotlin best practices and makes the header name easily maintainable.


23-31: Manual verification required: ensure complete timezone‐header coverage

I wasn’t able to extract any endpoint paths from the Retrofit interfaces via automation. Please manually review all of your api/v1/* diary, calendar, reply, and draft endpoints and confirm they’re included in shouldAddTimeZoneHeader.

  • Verify every new or existing endpoint that needs the timezone header is matched.
  • Update the timeZoneRequiredPaths set if any are missing.

8-11: LGTM with note on timezone caching behavior.

The class structure is well-designed with proper dependency injection and efficient timezone capture. Note that the timezone is cached at instantiation time, so runtime timezone changes won't be reflected until app restart. This is likely acceptable for most use cases but consider if dynamic timezone updates are needed.


12-21: LGTM!

The intercept method is well-structured with clear conditional logic and proper delegation to helper methods. The implementation correctly handles both cases for adding or skipping the timezone header.


33-38: LGTM!

The header injection implementation correctly uses OkHttp's builder pattern and follows best practices with the constant header name. The implementation is clean and efficient.

Comment on lines +33 to +34
private fun proceedWithTimeZoneHeader(chain: Interceptor.Chain, request: okhttp3.Request): Response =
chain.proceed(request.newBuilder().addHeader(TIME_ZONE, userTimeZone).build())
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Consider privacy implications of timezone header.

Adding the user's timezone to API requests has privacy implications:

  1. Location inference: Timezone can be used to infer approximate user location.
  2. User tracking: Combined with other data, timezone can contribute to user fingerprinting.

Ensure this aligns with your privacy policy and consider:

  • Whether this data is necessary for all matched endpoints
  • If the timezone data is properly handled and not logged unnecessarily on the server side
  • Whether users should be informed about timezone data collection

🌐 Web query:

What are the privacy implications of sending user timezone in HTTP headers for mobile applications?

💡 Result:

Sending user timezone in HTTP headers for mobile applications has several privacy implications to consider:

  1. User Profiling: Timezone information can be combined with other data (such as IP addresses, device IDs, or behavioral data) to build more comprehensive user profiles. Knowing a user's timezone narrows down their approximate geographic location, which can be used to infer habits, routines, or preferences.

  2. Location Tracking: While timezone is less precise than GPS location, it still reveals approximate geographic information. This can be a privacy concern especially in regions where timezone boundaries are closely packed, allowing for more precise location inference.

  3. Cross-site or Cross-service Tracking: If multiple services or domains receive timezone information consistently via HTTP headers, they could potentially correlate this data to track users across different platforms or apps.

  4. Potential Regulatory Concerns: Depending on jurisdiction, location and user profiling data have special handling requirements under laws like GDPR or CCPA. Timezone data might be considered personal data if it contributes to identifying an individual.

  5. Unintended Data Exposure: Since HTTP headers are often included in many requests, sending timezone publicly or to third-party services without explicit user consent may expose this data unnecessarily.

Mitigation steps include:

  • Only send timezone data when necessary and with user consent.
  • Consider anonymizing or obfuscating timezone data if precise location isn't needed.
  • Use encrypted connections (HTTPS) to prevent interception.
  • Document the use and purpose of timezone data in the privacy policy.

In summary, while sending timezone data is less invasive than sending precise location, it still carries privacy risks related to profiling and location inference that should be carefully managed.


Review TimeZoneInterceptor: Assess and Mitigate Privacy Implications of Timezone Header

The addition of the user’s timezone header can expose approximate location and enable user profiling or cross-service tracking. Please review and implement appropriate safeguards:

• File & Lines

  • app/src/main/java/com/sopt/clody/data/remote/util/TimeZoneInterceptor.kt (lines 33–34)

• Key privacy concerns

  • User profiling: narrows down geographic region, can be combined with other identifiers
  • Location inference: timezone boundaries can reveal more precise location
  • Cross-service tracking: consistent headers across domains enable fingerprinting
  • Regulatory risk: timezone may be “personal data” under GDPR/CCPA if it aids identification
  • Unintended exposure: headers sent to third-party services without consent

• Recommended actions

  1. Only add the timezone header on endpoints that truly require it.
  2. Obtain explicit user consent before including timezone in requests.
  3. Consider obfuscating or rounding the timezone if full precision isn’t needed.
  4. Ensure all requests use HTTPS to prevent header interception.
  5. Confirm on the server side that timezone data is handled securely (not logged or stored unnecessarily).
  6. Document the collection and use of timezone data in your privacy policy or user-facing disclosures.

Please address these points to align with privacy best practices and relevant regulations.

🤖 Prompt for AI Agents
In app/src/main/java/com/sopt/clody/data/remote/util/TimeZoneInterceptor.kt at
lines 33-34, the current code adds the user's timezone header to all requests,
which raises privacy concerns. Modify the interceptor to add the timezone header
only for specific endpoints that require it, check for explicit user consent
before including the header, and consider obfuscating or rounding the timezone
value to reduce precision. Also, ensure all requests are made over HTTPS to
protect the header in transit. Finally, coordinate with backend to handle
timezone data securely and update privacy documentation accordingly.

import com.sopt.clody.data.remote.util.NetworkUtil
import com.sopt.clody.domain.repository.NotificationRepository
import com.sopt.clody.presentation.utils.extension.TimePeriod
import com.sopt.clody.presentation.utils.extension.convertUTZtoKST
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Verify the function name accuracy.

The import convertUTZtoKST suggests converting from "UTZ" (possibly User Time Zone) to KST, but this naming could be confusing. Ensure the function name accurately reflects the conversion direction and consider using more descriptive names like convertUserTimeToKST.


🏁 Script executed:

#!/bin/bash
# Description: Verify the actual implementation and naming of the timezone conversion function
# Expected: Find the function definition to understand the conversion direction

ast-grep --pattern 'fun convertUTZtoKST($$$) {
  $$$
}'

Length of output: 57


🏁 Script executed:

#!/bin/bash
# Search for the definition and usages of convertUTZtoKST to verify its direction and naming
rg -n "convertUTZtoKST" -C 3 .

Length of output: 5295


Rename convertUTZtoKST for clarity

The convertUTZtoKST utility indeed reads from the system default (user) timezone and converts that time to KST. However, “UTZ” is non-standard and can be easily confused with “UTC.” Consider renaming this function to more clearly express its behavior, for example:

  • convertLocalTimeToKST
  • convertUserTimeToKST

Locations to update:

  • app/src/main/java/com/sopt/clody/presentation/utils/extension/TimeZoneExt.kt (line 39)
  • All call sites in NotificationSettingViewModel.kt and TimeReminderViewModel.kt
🤖 Prompt for AI Agents
In app/src/main/java/com/sopt/clody/presentation/utils/extension/TimeZoneExt.kt
around line 39, rename the function convertUTZtoKST to a clearer name such as
convertLocalTimeToKST or convertUserTimeToKST to better reflect that it converts
from the system default timezone to KST. Then update all call sites of this
function in NotificationSettingViewModel.kt and TimeReminderViewModel.kt
accordingly to use the new function name.

isDraftAlarm = _draftAlarm.value,
isReplyAlarm = _replyAlarm.value,
time = time,
time = convertUTZtoKST(timePeriod, hour, minute),
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Verify timezone conversion function robustness

The conversion from user's timezone to KST using convertUTZtoKST is correct for API communication. However, please verify that the conversion function handles edge cases properly:

  1. Timezone transitions (DST changes)
  2. Invalid input parameters
  3. Timezone offset calculations

🏁 Script executed:

#!/bin/bash
# Find and examine the convertUTZtoKST function implementation
ast-grep --pattern 'fun convertUTZtoKST($_, $_, $_) { $$$ }'

# Also check for any error handling in the conversion functions
fd . -name "TimeZoneExt.kt" | head -1 | xargs cat

Length of output: 343


🏁 Script executed:

#!/bin/bash
# 1. Locate the implementation of convertUTZtoKST
rg --context 5 'fun convertUTZtoKST' -n

# 2. Find and print the contents of TimeZoneExt.kt (first 200 lines) to inspect any helper functions or error handling
find . -type f -name 'TimeZoneExt.kt' -print -exec sed -n '1,200p' {} \;

Length of output: 3182


Fix convertUTZtoKST conversion logic and formatting

The current implementation mistakenly concatenates the hour string with 12 (e.g. "1" + 12 → "112") and uses %02s for formatting, which doesn’t zero-pad numbers. Update the function as follows:

• File: app/src/main/java/com/sopt/clody/presentation/utils/extension/TimeZoneExt.kt
• Method: convertUTZtoKST

 fun convertUTZtoKST(timePeriod: TimePeriod, hour: String, minute: String): String {
     val userZoneId = ZoneId.systemDefault()
     val kstZoneId = ZoneId.of("Asia/Seoul")

-    val hour24 = when (timePeriod) {
-        TimePeriod.AM -> if (hour == "12") 0 else hour.toInt()
-        TimePeriod.PM -> if (hour == "12") 12 else (hour + 12).toInt()
-    }
+    val hour24 = when (timePeriod) {
+        TimePeriod.AM -> if (hour == "12") 0 else hour.toInt()
+        TimePeriod.PM -> if (hour == "12") 12 else hour.toInt() + 12
+    }

     val userTime = LocalTime.of(hour24, minute.toInt())
     val userZoned = ZonedDateTime.of(LocalDate.now(userZoneId), userTime, userZoneId)
     val kstZoned = userZoned.withZoneSameInstant(kstZoneId)

     val kstHour = kstZoned.hour
     val kstMinute = kstZoned.minute

-    return String.format("%02s:%02s", kstHour, kstMinute)
+    return String.format("%02d:%02d", kstHour, kstMinute)
 }

Additional recommendations:

  • Change hour and minute parameters to Int to avoid unhandled NumberFormatException.
  • Optionally wrap parsing in a try/catch or validate inputs before calling.
🤖 Prompt for AI Agents
In
app/src/main/java/com/sopt/clody/presentation/ui/setting/notificationsetting/screen/NotificationSettingViewModel.kt
at line 162, the call to convertUTZtoKST passes hour and minute as strings
causing incorrect concatenation and formatting issues. To fix this, update the
convertUTZtoKST function in
app/src/main/java/com/sopt/clody/presentation/utils/extension/TimeZoneExt.kt to
accept hour and minute as Int parameters, correctly perform arithmetic for time
conversion, and use proper zero-padding formatting (e.g., %02d). Also, add input
validation or try/catch around parsing to handle potential NumberFormatException
safely.

Comment on lines +239 to +243
val isAvailableDay = if (userTimeZone == "Asia/Seoul") {
selected == today || selected == today.minusDays(1)
} else {
selected == today
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

깜빡하고 이 부분도 해당 커밋에 합쳐버렸는데, 전날 일기 작성 불가능하게 분기처리 해놓은 부분입니다!

- referenceDate 주입으로 안정성 증가 (동일 기준일자 보장, 날짜 경계 오류 방, 디버깅 및 테스트 용이)
- hour, minute 을 Int -> String 으로 타입 및 포맷 변경
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
app/src/main/java/com/sopt/clody/presentation/utils/extension/TimeZoneExt.kt (1)

47-50: Add input validation for hour and minute parameters.

The previous review comment about hour validation hasn't been addressed. The function assumes valid input but doesn't validate that hour is within the valid range (1-12) or that minute can be parsed as an integer.

Apply this diff to add comprehensive input validation:

     val hour24 = when (timePeriod) {
-        TimePeriod.AM -> if (hour == "12") 0 else hour.toInt()
-        TimePeriod.PM -> if (hour == "12") 12 else hour.toInt() + 12
+        TimePeriod.AM -> if (hour == "12") 0 else {
+            val h = hour.toIntOrNull() ?: throw IllegalArgumentException("Invalid hour format: $hour")
+            require(h in 1..11) { "Invalid hour for AM: $hour. Must be 1-12." }
+            h
+        }
+        TimePeriod.PM -> if (hour == "12") 12 else {
+            val h = hour.toIntOrNull() ?: throw IllegalArgumentException("Invalid hour format: $hour")
+            require(h in 1..11) { "Invalid hour for PM: $hour. Must be 1-12." }
+            h + 12
+        }
     }
🧹 Nitpick comments (1)
app/src/main/java/com/sopt/clody/presentation/utils/extension/TimeZoneExt.kt (1)

16-17: Consider adding input validation or error handling.

The function parses the time string without validation. While DateTimeFormatter.parse will throw an exception for invalid formats, consider adding explicit validation or wrapping in a try-catch for better error handling.

     val formatter = DateTimeFormatter.ofPattern("HH:mm")
-    val localTime = LocalTime.parse(time, formatter)
+    val localTime = try {
+        LocalTime.parse(time, formatter)
+    } catch (e: Exception) {
+        throw IllegalArgumentException("Invalid time format. Expected HH:mm format, got: $time", e)
+    }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c0df468 and 8f0e438.

📒 Files selected for processing (3)
  • app/src/main/java/com/sopt/clody/presentation/utils/extension/TimeZoneExt.kt (1 hunks)
  • app/src/main/res/values-ko/strings.xml (1 hunks)
  • app/src/main/res/values/strings.xml (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • app/src/main/res/values/strings.xml
  • app/src/main/res/values-ko/strings.xml
🧰 Additional context used
🪛 detekt (1.23.8)
app/src/main/java/com/sopt/clody/presentation/utils/extension/TimeZoneExt.kt

[warning] 33-33: String.format("%02d", minute) uses implicitly default locale for string formatting.

(detekt.potential-bugs.ImplicitDefaultLocale)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (1)
app/src/main/java/com/sopt/clody/presentation/utils/extension/TimeZoneExt.kt (1)

12-60: Good improvements addressing previous feedback.

The developer has successfully addressed the major concerns from previous reviews:

  • Added referenceDate parameter to both functions to handle date boundary edge cases
  • Fixed locale issue in convertUTZtoKST by using Locale.ROOT
  • The core time zone conversion logic is sound and handles the KST ↔ user timezone conversions correctly

}

val hourFormatted = hour12.toString()
val minuteFormatted = String.format("%02d", minute)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix implicit default locale usage.

The static analysis correctly identifies that String.format without an explicit locale can cause issues in different regions.

Apply this diff to specify the locale explicitly:

-    val minuteFormatted = String.format("%02d", minute)
+    val minuteFormatted = String.format(java.util.Locale.ROOT, "%02d", minute)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
val minuteFormatted = String.format("%02d", minute)
val minuteFormatted = String.format(java.util.Locale.ROOT, "%02d", minute)
🧰 Tools
🪛 detekt (1.23.8)

[warning] 33-33: String.format("%02d", minute) uses implicitly default locale for string formatting.

(detekt.potential-bugs.ImplicitDefaultLocale)

🤖 Prompt for AI Agents
In app/src/main/java/com/sopt/clody/presentation/utils/extension/TimeZoneExt.kt
at line 33, the use of String.format without specifying a locale can lead to
inconsistent formatting across different regions. Fix this by explicitly passing
Locale.US (or another appropriate locale) as the first argument to String.format
to ensure consistent behavior regardless of the device's locale settings.

TimePeriod.PM -> if (hour == "12") 12 else hour.toInt() + 12
}

val userTime = LocalTime.of(hour24, minute.toInt())
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add input validation for minute parameter.

The minute parameter is converted to integer without validation, which could cause a NumberFormatException.

Apply this diff to add minute validation:

-    val userTime = LocalTime.of(hour24, minute.toInt())
+    val minuteInt = minute.toIntOrNull() ?: throw IllegalArgumentException("Invalid minute format: $minute")
+    require(minuteInt in 0..59) { "Invalid minute: $minute. Must be 0-59." }
+    val userTime = LocalTime.of(hour24, minuteInt)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
val userTime = LocalTime.of(hour24, minute.toInt())
val minuteInt = minute.toIntOrNull()
?: throw IllegalArgumentException("Invalid minute format: $minute")
require(minuteInt in 0..59) { "Invalid minute: $minute. Must be 0-59." }
val userTime = LocalTime.of(hour24, minuteInt)
🤖 Prompt for AI Agents
In app/src/main/java/com/sopt/clody/presentation/utils/extension/TimeZoneExt.kt
at line 52, the minute parameter is converted to an integer without validation,
risking a NumberFormatException. Add input validation to ensure the minute
string can be safely converted to an integer before calling LocalTime.of. If the
conversion fails or the value is out of the valid range (0-59), handle the error
appropriately, such as by returning a default value or throwing a controlled
exception.

Copy link
Contributor

@MoonsuKang MoonsuKang left a comment

Choose a reason for hiding this comment

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

현생이슈.. 죄송합니다ㅜㅠ

@SYAAINN SYAAINN merged commit e74c5e6 into develop Jul 31, 2025
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

♻️ REFACTOR 코드 리팩토링(전면 수정) 🔥 민재 민재

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[REFACTOR] 타임존을 고려한 API 수정사항을 반영합니다.

2 participants