Skip to content

Commit a7359cd

Browse files
authored
Merge pull request #7 from DojoCodingLabs/daniel/doj-2430-date-handling
feat: cross-platform date handling library (DOJ-2430)
2 parents 485329b + 55104f9 commit a7359cd

File tree

3 files changed

+63
-9
lines changed

3 files changed

+63
-9
lines changed

scripts/lib/date-compat.sh

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
#!/usr/bin/env bash
2+
# Cross-platform date utilities for CodeSensei
3+
# Supports: GNU date (Linux), BSD date (macOS), python3 fallback
4+
5+
# Get today's date in YYYY-MM-DD format (UTC)
6+
date_today() {
7+
date -u '+%Y-%m-%d' 2>/dev/null || python3 -c "from datetime import datetime; print(datetime.utcnow().strftime('%Y-%m-%d'))"
8+
}
9+
10+
# Get yesterday's date in YYYY-MM-DD format (UTC)
11+
date_yesterday() {
12+
# Try GNU date first
13+
date -u -d 'yesterday' '+%Y-%m-%d' 2>/dev/null && return
14+
# Try BSD date (macOS)
15+
date -u -v-1d '+%Y-%m-%d' 2>/dev/null && return
16+
# Python fallback
17+
python3 -c "from datetime import datetime, timedelta; print((datetime.utcnow() - timedelta(days=1)).strftime('%Y-%m-%d'))" 2>/dev/null && return
18+
# Last resort: empty string
19+
echo ""
20+
}
21+
22+
# Convert date string (YYYY-MM-DD) to epoch seconds
23+
date_to_epoch() {
24+
local date_str="$1"
25+
# Try GNU date
26+
date -u -d "$date_str" '+%s' 2>/dev/null && return
27+
# Try BSD date (macOS)
28+
date -u -j -f '%Y-%m-%d %H:%M:%S' "${date_str} 00:00:00" '+%s' 2>/dev/null && return
29+
# Python fallback
30+
python3 -c "from datetime import datetime, timezone; print(int(datetime.strptime('$date_str', '%Y-%m-%d').replace(tzinfo=timezone.utc).timestamp()))" 2>/dev/null && return
31+
echo "0"
32+
}
33+
34+
# Get date N days ago in YYYY-MM-DD format (UTC)
35+
date_days_ago() {
36+
local days="$1"
37+
# Try GNU date
38+
date -u -d "${days} days ago" '+%Y-%m-%d' 2>/dev/null && return
39+
# Try BSD date (macOS)
40+
date -u -v-${days}d '+%Y-%m-%d' 2>/dev/null && return
41+
# Python fallback
42+
python3 -c "from datetime import datetime, timedelta; print((datetime.utcnow() - timedelta(days=${days})).strftime('%Y-%m-%d'))" 2>/dev/null && return
43+
echo ""
44+
}
45+
46+
# Get current UTC timestamp in ISO format
47+
date_now_iso() {
48+
date -u '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null || python3 -c "from datetime import datetime; print(datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ'))"
49+
}

scripts/quiz-selector.sh

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ else
3838
check_jq() { command -v jq &>/dev/null; }
3939
fi
4040

41+
# shellcheck source=scripts/lib/date-compat.sh
42+
source "$PLUGIN_ROOT/scripts/lib/date-compat.sh"
43+
4144
# Default output if we can't determine anything
4245
DEFAULT_OUTPUT='{"mode":"dynamic","concept":null,"reason":"No profile data available","static_question":null,"belt":"white","quiz_format":"multiple_choice"}'
4346

@@ -89,8 +92,8 @@ if [ $? -ne 0 ]; then
8992
CORRECT_QUIZZES=0
9093
fi
9194

92-
TODAY=$(date -u +%Y-%m-%d)
93-
NOW_EPOCH=$(date +%s)
95+
TODAY=$(date_today)
96+
NOW_EPOCH=$(date_to_epoch "$TODAY")
9497

9598
# Determine quiz format based on belt level
9699
# Orange Belt+ gets a mix of formats; lower belts get multiple choice
@@ -149,10 +152,10 @@ if [ "$QUIZ_HISTORY" != "[]" ]; then
149152
continue
150153
fi
151154

152-
# Calculate days since last wrong answer (handles both GNU and BSD date)
155+
# Calculate days since last wrong answer using cross-platform helpers
153156
LAST_WRONG_DATE=$(printf '%s' "$LAST_WRONG" | cut -d'T' -f1)
154-
LAST_EPOCH=$(date -j -f "%Y-%m-%d" "$LAST_WRONG_DATE" +%s 2>/dev/null || date -d "$LAST_WRONG_DATE" +%s 2>/dev/null)
155-
if [ -n "$LAST_EPOCH" ]; then
157+
LAST_EPOCH=$(date_to_epoch "$LAST_WRONG_DATE")
158+
if [ -n "$LAST_EPOCH" ] && [ "$LAST_EPOCH" != "0" ]; then
156159
DAYS_SINCE=$(( (NOW_EPOCH - LAST_EPOCH) / 86400 ))
157160
else
158161
log_error "$SCRIPT_NAME" "Could not parse date '$LAST_WRONG_DATE' for spaced repetition; defaulting days_since=999"

scripts/session-start.sh

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,21 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
77

88
# shellcheck source=lib/profile-io.sh
99
source "${SCRIPT_DIR}/lib/profile-io.sh"
10+
# shellcheck source=lib/date-compat.sh
11+
source "${SCRIPT_DIR}/lib/date-compat.sh"
1012

1113
LIB_DIR="${SCRIPT_DIR}/lib"
1214
if [ -f "${LIB_DIR}/error-handling.sh" ]; then
1315
# shellcheck source=lib/error-handling.sh
1416
source "${LIB_DIR}/error-handling.sh"
1517
else
1618
LOG_FILE="${PROFILE_DIR}/error.log"
17-
log_error() { printf '[%s] [%s] %s\n' "$(date -u '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null || date '+%Y-%m-%d')" "${1:-unknown}" "$2" >> "$LOG_FILE" 2>/dev/null; }
19+
log_error() { printf '[%s] [%s] %s\n' "$(date_now_iso 2>/dev/null || date -u '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null || date '+%Y-%m-%d')" "${1:-unknown}" "$2" >> "$LOG_FILE" 2>/dev/null; }
1820
check_jq() { command -v jq &>/dev/null; }
1921
fi
2022

2123
SESSION_LOG="${PROFILE_DIR}/sessions.log"
22-
TODAY=$(date -u +%Y-%m-%d)
24+
TODAY=$(date_today)
2325

2426
ensure_profile_dir
2527
if [ ! -d "$PROFILE_DIR" ]; then
@@ -28,7 +30,7 @@ if [ ! -d "$PROFILE_DIR" ]; then
2830
fi
2931

3032
if [ ! -f "$PROFILE_FILE" ]; then
31-
CREATED_AT=$(date -u +%Y-%m-%dT%H:%M:%SZ)
33+
CREATED_AT=$(date_now_iso)
3234
if ! cat <<PROFILE | write_profile
3335
{
3436
"version": "1.0.0",
@@ -113,7 +115,7 @@ fi
113115
if [ "$LAST_SESSION" = "$TODAY" ]; then
114116
NEW_STREAK=$CURRENT_STREAK
115117
elif [ -n "$LAST_SESSION" ]; then
116-
YESTERDAY=$(date -u -d "yesterday" +%Y-%m-%d 2>/dev/null || date -u -v-1d +%Y-%m-%d 2>/dev/null)
118+
YESTERDAY=$(date_yesterday)
117119
if [ -z "$YESTERDAY" ]; then
118120
log_error "$SCRIPT_NAME" "Failed to compute yesterday's date; resetting streak to 1"
119121
YESTERDAY=""

0 commit comments

Comments
 (0)