本文目录导读:

纯前端 JavaScript 实现(本地存储)
function calculateStreak(records) {
if (records.length === 0) return 0;
// 按日期降序排序
records.sort((a, b) => new Date(b.date) - new Date(a.date));
let streak = 1;
const today = new Date();
today.setHours(0, 0, 0, 0);
// 检查今天是否打卡
const lastRecord = new Date(records[0].date);
lastRecord.setHours(0, 0, 0, 0);
const diffDays = Math.floor((today - lastRecord) / (1000 * 60 * 60 * 24));
if (diffDays > 1) return 0; // 最后打卡超过1天,连续中断
// 计算连续天数
for (let i = 1; i < records.length; i++) {
const currentDate = new Date(records[i].date);
const prevDate = new Date(records[i - 1].date);
const diff = Math.floor((prevDate - currentDate) / (1000 * 60 * 60 * 24));
if (diff === 1) {
streak++;
} else {
break;
}
}
return streak;
}
// 使用示例
const records = [
{ date: '2024-01-10' },
{ date: '2024-01-09' },
{ date: '2024-01-08' },
{ date: '2024-01-06' } // 中断
];
console.log(calculateStreak(records)); // 输出: 3
后端 Python 实现
from datetime import datetime, timedelta
from typing import List, Dict
def calculate_streak(records: List[Dict]) -> int:
if not records:
return 0
# 提取日期并排序
dates = [datetime.strptime(r['date'], '%Y-%m-%d').date() for r in records]
dates.sort(reverse=True)
# 检查今天是否打卡
today = datetime.now().date()
if dates[0] == today:
streak = 1
elif dates[0] == today - timedelta(days=1):
streak = 1
else:
return 0 # 最后打卡不是今天或昨天
# 计算连续天数
for i in range(1, len(dates)):
diff = (dates[i-1] - dates[i]).days
if diff == 1:
streak += 1
else:
break
return streak
# 使用示例
records = [
{'date': '2024-01-10'},
{'date': '2024-01-09'},
{'date': '2024-01-08'},
{'date': '2024-01-07'}
]
print(calculate_streak(records)) # 输出: 4
数据库查询(SQL)
-- 假设打卡记录表: check_ins(user_id, check_date)
-- 计算用户连续打卡天数
WITH daily_check AS (
SELECT
user_id,
check_date,
LAG(check_date) OVER (PARTITION BY user_id ORDER BY check_date DESC) as prev_date
FROM check_ins
WHERE user_id = 123
),
streak_calc AS (
SELECT
*,
CASE
WHEN prev_date IS NULL THEN 1
WHEN DATEDIFF(prev_date, check_date) = 1 THEN 0
ELSE 1
END as is_break
FROM daily_check
ORDER BY check_date DESC
)
SELECT COUNT(*) as streak_days
FROM streak_calc
WHERE is_break = 0
LIMIT 1;
考虑特殊情况的增强版本
function calculateEnhancedStreak(records, options = {}) {
const {
allowTodayGap = true, // 今天未打卡是否中断
includeWeekends = true, // 是否包含周末
maxGap = 0 // 允许的最大间隔天数
} = options;
if (records.length === 0) return { streak: 0, isActive: false };
// 排序并去重
const dates = [...new Set(records.map(r => r.date))].sort();
let streak = 1;
let gaps = 0;
for (let i = 1; i < dates.length; i++) {
const curr = new Date(dates[i]);
const prev = new Date(dates[i - 1]);
const diff = Math.floor((curr - prev) / (1000 * 60 * 60 * 24));
if (diff === 1) {
streak++;
gaps = 0;
} else if (diff <= maxGap + 1) {
// 允许有间隔
streak++;
gaps += diff - 1;
} else {
// 中断
return {
streak: streak,
isActive: false,
breakDate: dates[i]
};
}
}
return { streak, isActive: true, gaps };
}
// 使用示例
const records = [
{ date: '2024-01-10' },
{ date: '2024-01-09' },
{ date: '2024-01-07' }, // 跳过一天
{ date: '2024-01-06' }
];
console.log(calculateEnhancedStreak(records, { maxGap: 1 }));
// 输出: { streak: 4, isActive: true, gaps: 1 }
完整的存储 + 计算方案
class StreakTracker {
constructor(storageKey = 'streak_data') {
this.storageKey = storageKey;
this.data = this.load();
}
// 打卡
checkIn() {
const today = new Date().toISOString().split('T')[0];
if (!this.data.dates.includes(today)) {
this.data.dates.push(today);
this.save();
}
return this.calculateStreak();
}
// 计算连续天数
calculateStreak() {
const dates = [...this.data.dates].sort((a, b) =>
new Date(b) - new Date(a)
);
if (dates.length === 0) return 0;
let streak = 1;
const today = new Date().toISOString().split('T')[0];
// 检查最后打卡日期
const lastDate = dates[0];
const diff = Math.floor(
(new Date(today) - new Date(lastDate)) / (1000 * 60 * 60 * 24)
);
if (diff > 1) return 0; // 中断
// 计算连续
for (let i = 1; i < dates.length; i++) {
const prevDate = dates[i - 1];
const currDate = dates[i];
const diff = Math.floor(
(new Date(prevDate) - new Date(currDate)) / (1000 * 60 * 60 * 24)
);
if (diff === 1) {
streak++;
} else {
break;
}
}
return streak;
}
load() {
const data = localStorage.getItem(this.storageKey);
return data ? JSON.parse(data) : { dates: [] };
}
save() {
localStorage.setItem(this.storageKey, JSON.stringify(this.data));
}
// 获取打卡统计
getStats() {
return {
streak: this.calculateStreak(),
totalCheckins: this.data.dates.length,
lastCheckin: this.data.dates[this.data.dates.length - 1] || null,
dates: this.data.dates
};
}
}
// 使用示例
const tracker = new StreakTracker();
tracker.checkIn(); // 打卡
console.log(tracker.getStats()); // 获取统计
关键考虑因素
- 时间处理:注意时区问题,使用 UTC 或统一时区
- 边界情况:今天未打卡、昨天未打卡等情况
- 数据去重:同一天多次打卡应只算一次
- 性能优化:大量数据时考虑使用缓存
选择哪种实现取决于你的具体场景:前端、后端、还是数据库层面。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播