Fix iOS widget date formatting for RFC3339 dates

- Add centralized formatWidgetDate() helper that handles both yyyy-MM-dd and ISO8601 formats
- Update widget date display to show "Today", "in X days", or "X days ago"
- Fix isTaskOverdue() to parse ISO8601 dates from Go API
- Remove duplicate date formatting functions from widget views
- Switch API environment back to DEV

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Trey t
2025-12-01 18:09:16 -06:00
parent c07821711f
commit fe2e8275f5
3 changed files with 75 additions and 75 deletions

View File

@@ -9,7 +9,7 @@ package com.example.casera.network
*/
object ApiConfig {
// ⚠️ CHANGE THIS TO TOGGLE ENVIRONMENT ⚠️
val CURRENT_ENV = Environment.LOCAL
val CURRENT_ENV = Environment.DEV
enum class Environment {
LOCAL,

View File

@@ -8,6 +8,53 @@
import WidgetKit
import SwiftUI
// MARK: - Date Formatting Helper
/// Parses date strings in either yyyy-MM-dd or ISO8601 (RFC3339) format
/// and returns a user-friendly string like "Today" or "in X days"
private func formatWidgetDate(_ dateString: String) -> String {
let calendar = Calendar.current
var date: Date?
// Try parsing as yyyy-MM-dd first
let dateOnlyFormatter = DateFormatter()
dateOnlyFormatter.dateFormat = "yyyy-MM-dd"
date = dateOnlyFormatter.date(from: dateString)
// Try parsing as ISO8601 (RFC3339) if that fails
if date == nil {
let isoFormatter = ISO8601DateFormatter()
isoFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
date = isoFormatter.date(from: dateString)
// Try without fractional seconds
if date == nil {
isoFormatter.formatOptions = [.withInternetDateTime]
date = isoFormatter.date(from: dateString)
}
}
guard let parsedDate = date else {
return dateString
}
let today = calendar.startOfDay(for: Date())
let dueDay = calendar.startOfDay(for: parsedDate)
if calendar.isDateInToday(parsedDate) {
return "Today"
}
let components = calendar.dateComponents([.day], from: today, to: dueDay)
let days = components.day ?? 0
if days > 0 {
return days == 1 ? "in 1 day" : "in \(days) days"
} else {
let overdueDays = abs(days)
return overdueDays == 1 ? "1 day ago" : "\(overdueDays) days ago"
}
}
/// CacheManager reads tasks from the App Group shared container
/// Data is written by the main app via WidgetDataManager
class CacheManager {
@@ -192,7 +239,7 @@ struct SmallWidgetView: View {
HStack(spacing: 4) {
Image(systemName: "calendar")
.font(.system(size: 9))
Text(formatDate(dueDate))
Text(formatWidgetDate(dueDate))
.font(.system(size: 10, weight: .medium))
}
.foregroundStyle(.orange)
@@ -225,27 +272,6 @@ struct SmallWidgetView: View {
}
.padding(16)
}
private func formatDate(_ dateString: String) -> String {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
if let date = formatter.date(from: dateString) {
let now = Date()
let calendar = Calendar.current
if calendar.isDateInToday(date) {
return "Today"
} else if calendar.isDateInTomorrow(date) {
return "Tomorrow"
} else {
formatter.dateFormat = "MMM d"
return formatter.string(from: date)
}
}
return dateString
}
}
// MARK: - Medium Widget View
@@ -338,15 +364,15 @@ struct TaskRowView: View {
HStack(spacing: 3) {
Image(systemName: "calendar")
.font(.system(size: 8))
Text(formatDate(dueDate))
Text(formatWidgetDate(dueDate))
.font(.system(size: 9, weight: .medium))
}
.foregroundStyle(.secondary)
}
}
Spacer()
if let priority = task.priority {
Text(priority.prefix(1).uppercased())
.font(.system(size: 9, weight: .bold))
@@ -360,7 +386,7 @@ struct TaskRowView: View {
}
.padding(.vertical, 4)
}
private var priorityColor: Color {
switch task.priority?.lowercased() {
case "urgent": return .red
@@ -369,27 +395,6 @@ struct TaskRowView: View {
default: return .gray
}
}
private func formatDate(_ dateString: String) -> String {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
if let date = formatter.date(from: dateString) {
let now = Date()
let calendar = Calendar.current
if calendar.isDateInToday(date) {
return "Today"
} else if calendar.isDateInTomorrow(date) {
return "Tomorrow"
} else {
formatter.dateFormat = "MMM d"
return formatter.string(from: date)
}
}
return dateString
}
}
// MARK: - Large Widget View
@@ -483,7 +488,7 @@ struct LargeTaskRowView: View {
HStack(spacing: 2) {
Image(systemName: "calendar")
.font(.system(size: 7))
Text(formatDate(dueDate))
Text(formatWidgetDate(dueDate))
.font(.system(size: 9, weight: isOverdue ? .semibold : .regular))
}
.foregroundStyle(isOverdue ? .red : .secondary)
@@ -518,28 +523,6 @@ struct LargeTaskRowView: View {
default: return .gray
}
}
private func formatDate(_ dateString: String) -> String {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
if let date = formatter.date(from: dateString) {
let calendar = Calendar.current
if calendar.isDateInToday(date) {
return "Today"
} else if calendar.isDateInTomorrow(date) {
return "Tomorrow"
} else if calendar.isDateInYesterday(date) {
return "Yesterday"
} else {
formatter.dateFormat = "MMM d"
return formatter.string(from: date)
}
}
return dateString
}
}
struct Casera: Widget {

View File

@@ -150,15 +150,32 @@ final class WidgetDataManager {
private func isTaskOverdue(dueDate: String?, status: String?) -> Bool {
guard let dueDateStr = dueDate else { return false }
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
var date: Date?
guard let date = formatter.date(from: dueDateStr) else { return false }
// Try parsing as yyyy-MM-dd first
let dateOnlyFormatter = DateFormatter()
dateOnlyFormatter.dateFormat = "yyyy-MM-dd"
date = dateOnlyFormatter.date(from: dueDateStr)
// Try parsing as ISO8601 (RFC3339) if that fails
if date == nil {
let isoFormatter = ISO8601DateFormatter()
isoFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
date = isoFormatter.date(from: dueDateStr)
// Try without fractional seconds
if date == nil {
isoFormatter.formatOptions = [.withInternetDateTime]
date = isoFormatter.date(from: dueDateStr)
}
}
guard let parsedDate = date else { return false }
// Task is overdue if due date is in the past and status is not completed
let statusLower = status?.lowercased() ?? ""
let isCompleted = statusLower == "completed" || statusLower == "done"
return !isCompleted && date < Calendar.current.startOfDay(for: Date())
return !isCompleted && parsedDate < Calendar.current.startOfDay(for: Date())
}
}