打卡第十七天

聚类的指标

聚类分析中常用的评估指标包括轮廓系数(Silhouette Score)、Calinski-Harabasz指数、Davies-Bouldin指数等。这些指标用于评估聚类结果的质量,帮助选择最佳的聚类算法和参数。

聚类常见算法

常见的聚类算法包括KMeans聚类、DBSCAN聚类和层次聚类。这些算法各有特点,适用于不同的数据集和场景。

三种算法对应的流程

KMeans聚类:首先随机选择K个中心点,然后迭代更新中心点,直到收敛。最终将每个样本分配到最近的中心点。

DBSCAN聚类:通过定义核心点和密度可达的关系,将样本划分为不同的簇。该算法不需要预先指定簇的数量。

层次聚类:通过构建树状结构,逐层合并或分裂簇。层次聚类可以是凝聚的(自底向上)或分裂的(自顶向下)。

实际在论文中聚类的策略

在实际研究中,聚类的策略不一定是针对所有特征,可以选择其中几个可解释的特征进行聚类。这样得到的聚类结果在后续解释时更加符合逻辑。

聚类的流程

标准化数据:对数据进行标准化处理,确保不同特征的量纲一致。

选择合适的算法,根据评估指标调参:KMeans和层次聚类的参数是K值,选择合适的K值后指标确定。DBSCAN的参数是eps和min_samples,选择合适的参数后出现K和评估指标。层次聚类的linkage准则等也需要仔细调优。

将聚类后的特征添加到原数据中:将聚类结果作为新特征添加到原数据中,便于后续分析。

原则t-SNE或者PCA进行2D或3D可视化:使用t-SNE或PCA方法将高维数据降维至2D或3D,进行可视化展示。

作业:对心脏病数据集进行聚类

对心脏病数据集进行聚类分析,可以按照上述流程进行。首先对数据进行标准化处理,然后选择合适的聚类算法和参数,最后将聚类结果添加到原数据中,并进行可视化展示。

首先在进行聚类之前首先要对数据进行标准化处理

from sklearn.cluster import KMeans, DBSCAN, AgglomerativeClustering
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
scaler = StandardScaler()
x_scaled = scaler.fit_transform(x)

确定K值范围,看聚类图,看转折点

from sklearn.metrics import silhouette_score, calinski_harabasz_score, davies_bouldin_score
k_range = range(2, 11)  # 测试 k 从 2 到 10
inertia_values = []
silhouette_scores = []
ch_scores = []
db_scores = []

for k in k_range:
    kmeans = KMeans(n_clusters=k, random_state=42) #n_cluster是一个参数,用于指定K-均值算法中的簇的数量。模型实例化
    kmeans_labels = kmeans.fit_predict(x_scaled)#根据输入的数据来训练K-均值模型,返回每个数据点所属的簇标签。
    inertia_values.append(kmeans.inertia_)  # 惯性(肘部法则)将惯性值储存在惯性值列表垢面
    silhouette = silhouette_score(x_scaled, kmeans_labels)  # 计算轮廓系数,X_scaled是标准化后的特征数据,kmeans_labels是X_scaled每个数据点所属聚类结果的标签
    silhouette_scores.append(silhouette)
    ch = calinski_harabasz_score(x_scaled, kmeans_labels)  # CH 指数
    ch_scores.append(ch)
    db = davies_bouldin_score(x_scaled, kmeans_labels)  # DB 指数
    db_scores.append(db)
    print(f"k={k}, 惯性: {kmeans.inertia_:.2f}, 轮廓系数: {silhouette:.3f}, CH 指数: {ch:.2f}, DB 指数: {db:.3f}")

# 绘制评估指标图
plt.figure(figsize=(15, 10))

# 肘部法则图(Inertia)
plt.subplot(2, 2, 1)#subplot是创建子图的函数,2行2列,第1个图
plt.plot(k_range, inertia_values, marker='o')#plt.plot是绘制折线图的函数,k_range是x轴数据,inertia_values是y轴数据,marker='o'是折线图中的标记点
plt.title('肘部法则确定最优聚类数 k(惯性,越小越好)')
plt.xlabel('聚类数 (k)')
plt.ylabel('惯性')
plt.grid(True)

# 轮廓系数图
plt.subplot(2, 2, 2)
plt.plot(k_range, silhouette_scores, marker='o', color='orange')
plt.title('轮廓系数确定最优聚类数 k(越大越好)')
plt.xlabel('聚类数 (k)')
plt.ylabel('轮廓系数')
plt.grid(True)

# CH 指数图
plt.subplot(2, 2, 3)
plt.plot(k_range, ch_scores, marker='o', color='green')
plt.title('Calinski-Harabasz 指数确定最优聚类数 k(越大越好)')
plt.xlabel('聚类数 (k)')
plt.ylabel('CH 指数')
plt.grid(True)

# DB 指数图
plt.subplot(2, 2, 4)
plt.plot(k_range, db_scores, marker='o', color='red')
plt.title('Davies-Bouldin 指数确定最优聚类数 k(越小越好)')
plt.xlabel('聚类数 (k)')
plt.ylabel('DB 指数')
plt.grid(True)

plt.tight_layout()
plt.show()

肘部法则图: 找下降速率变慢的拐点,这里都差不多
轮廓系数图:找局部最高点
CH指数图: 找局部最高点
DB指数图:找局部最低点

 综合比较,4和8差不太多,我选4

然后对选择的k值进行聚类,pca降维,并将聚类结果可视化。

selected_k = 4

# 使用选择的 k 值进行 KMeans 聚类
kmeans = KMeans(n_clusters=selected_k, random_state=42)
kmeans_labels = kmeans.fit_predict(x_scaled)
x['KMeans_Cluster'] = kmeans_labels

# 使用 PCA 降维到 2D 进行可视化
pca = PCA(n_components=2)
x_pca = pca.fit_transform(x_scaled)

# KMeans 聚类结果可视化
plt.figure(figsize=(6, 5))
sns.scatterplot(x=x_pca[:, 0], y=x_pca[:, 1], hue=kmeans_labels, palette='viridis')
plt.title(f'KMeans Clustering with k={selected_k} (PCA Visualization)')
plt.xlabel('PCA Component 1')
plt.ylabel('PCA Component 2')
plt.show()

# 打印 KMeans 聚类标签的前几行
print(f"KMeans Cluster labels (k={selected_k}) added to X:")
print(x[['KMeans_Cluster']].value_counts())

DBSCAN聚类

首先,我增加了一部,为了确定eps的取值范围

import numpy as np
from scipy.spatial import distance_matrix

# 生成示例数据
x_data = np.array(x_scaled)

# 计算所有点之间的距离矩阵
dist_matrix = distance_matrix(x_data, x_data)

# 找到每个点的最近邻距离(排除自身)
nearest_distances = np.sort(dist_matrix)[:, 1]

print("最近邻距离:")
print(nearest_distances)

# 对最近邻距离值进行排序
sorted_distances = np.sort(nearest_distances)

# 绘制曲线
import matplotlib.pyplot as plt

plt.figure(figsize=(10, 6))
plt.plot(sorted_distances)
plt.title('排序后的最近邻距离曲线')
plt.xlabel('数据点索引')
plt.ylabel('最近邻距离')
plt.grid(True)
plt.show()

通过最近邻距离排序所获得的曲线来确定

也就是两个斜率变化较大的点为取值范围

rom sklearn.metrics import silhouette_score, calinski_harabasz_score, davies_bouldin_score
eps_range = np.arange(0.8,3.5 , 0.5)  
min_samples_range = range(3, 8)  
results = []

for eps in eps_range:
    for min_samples in min_samples_range:
        dbscan = DBSCAN(eps=eps, min_samples=min_samples)
        dbscan_labels = dbscan.fit_predict(x_scaled)
        # 计算簇的数量(排除噪声点 -1)
        n_clusters = len(np.unique(dbscan_labels)) - (1 if -1 in dbscan_labels else 0)
        # 计算噪声点数量
        n_noise = list(dbscan_labels).count(-1)
        # 只有当簇数量大于 1 且有有效簇时才计算评估指标
        if n_clusters > 1:
            # 排除噪声点后计算评估指标
            mask = dbscan_labels != -1
            if mask.sum() > 0:  # 确保有非噪声点
                silhouette = silhouette_score(x_scaled[mask], dbscan_labels[mask])
                ch = calinski_harabasz_score(x_scaled[mask], dbscan_labels[mask])
                db = davies_bouldin_score(x_scaled[mask], dbscan_labels[mask])
                results.append({
                    'eps': eps,
                    'min_samples': min_samples,
                    'n_clusters': n_clusters,
                    'n_noise': n_noise,
                    'silhouette': silhouette,
                    'ch_score': ch,
                    'db_score': db
                })
                print(f"eps={eps:.1f}, min_samples={min_samples}, 簇数: {n_clusters}, 噪声点: {n_noise}, "
                      f"轮廓系数: {silhouette:.3f}, CH 指数: {ch:.2f}, DB 指数: {db:.3f}")
        else:
            print(f"eps={eps:.1f}, min_samples={min_samples}, 簇数: {n_clusters}, 噪声点: {n_noise}, 无法计算评估指标")

# 将结果转为 DataFrame 以便可视化和选择参数
results_df = pd.DataFrame(results)
# 绘制评估指标图,增加点论文中的工作量
plt.figure(figsize=(15, 10))
# 轮廓系数图
plt.subplot(2, 2, 1)
for min_samples in min_samples_range:
    subset = results_df[results_df['min_samples'] == min_samples] # 
    plt.plot(subset['eps'], subset['silhouette'], marker='o', label=f'min_samples={min_samples}')
plt.title('轮廓系数确定最优参数(越大越好)')
plt.xlabel('eps')
plt.ylabel('轮廓系数')
plt.legend()
plt.grid(True)

# CH 指数图
plt.subplot(2, 2, 2)
for min_samples in min_samples_range:
    subset = results_df[results_df['min_samples'] == min_samples]
    plt.plot(subset['eps'], subset['ch_score'], marker='o', label=f'min_samples={min_samples}')
plt.title('Calinski-Harabasz 指数确定最优参数(越大越好)')
plt.xlabel('eps')
plt.ylabel('CH 指数')
plt.legend()
plt.grid(True)

# DB 指数图
plt.subplot(2, 2, 3)
for min_samples in min_samples_range:
    subset = results_df[results_df['min_samples'] == min_samples]
    plt.plot(subset['eps'], subset['db_score'], marker='o', label=f'min_samples={min_samples}')
plt.title('Davies-Bouldin 指数确定最优参数(越小越好)')
plt.xlabel('eps')
plt.ylabel('DB 指数')
plt.legend()
plt.grid(True)

# 簇数量图
plt.subplot(2, 2, 4)
for min_samples in min_samples_range:
    subset = results_df[results_df['min_samples'] == min_samples]
    plt.plot(subset['eps'], subset['n_clusters'], marker='o', label=f'min_samples={min_samples}')
plt.title('簇数量变化')
plt.xlabel('eps')
plt.ylabel('簇数量')
plt.legend()
plt.grid(True)

plt.tight_layout()
plt.show()

通过排除噪声,选出最佳EPS和min_samples

通过图片可以观察出eps最佳在2.25-2.50之间,min_samples最佳为4.

# 选择 eps 和 min_samples 值(根据图表选择最佳参数)
selected_eps = 2.3 # 根据图表调整
selected_min_samples = 4 # 根据图表调整

# 使用选择的参数进行 DBSCAN 聚类
dbscan = DBSCAN(eps=selected_eps, min_samples=selected_min_samples)
dbscan_labels = dbscan.fit_predict(x_scaled)
x['DBSCAN_Cluster'] = dbscan_labels
# 使用 PCA 降维到 2D 进行可视化
pca = PCA(n_components=2)
x_pca = pca.fit_transform(x_scaled)

# DBSCAN 聚类结果可视化
plt.figure(figsize=(6, 5))
sns.scatterplot(x=x_pca[:, 0], y=x_pca[:, 1], hue=dbscan_labels, palette='viridis')
plt.title(f'DBSCAN Clustering with eps={selected_eps}, min_samples={selected_min_samples} (PCA Visualization)')
plt.xlabel('PCA Component 1')
plt.ylabel('PCA Component 2')
plt.show()

# 打印 DBSCAN 聚类标签的分布
print(f"DBSCAN Cluster labels (eps={selected_eps}, min_samples={selected_min_samples}) added to X:")
print(x[['DBSCAN_Cluster']].value_counts())

进行聚类

层次聚类

n_clusters_range = range(2, 11)  # 测试簇数量从 2 到 10
silhouette_scores = []
ch_scores = []
db_scores = []

for n_clusters in n_clusters_range:
    agglo = AgglomerativeClustering(n_clusters=n_clusters, linkage='ward')  # 使用 Ward 准则合并簇
    agglo_labels = agglo.fit_predict(x_scaled)
    
    # 计算评估指标
    silhouette = silhouette_score(x_scaled, agglo_labels)
    ch = calinski_harabasz_score(x_scaled, agglo_labels)
    db = davies_bouldin_score(x_scaled, agglo_labels)
    
    silhouette_scores.append(silhouette)
    ch_scores.append(ch)
    db_scores.append(db)
    
    print(f"n_clusters={n_clusters}, 轮廓系数: {silhouette:.3f}, CH 指数: {ch:.2f}, DB 指数: {db:.3f}")

# 绘制评估指标图
plt.figure(figsize=(15, 5))

# 轮廓系数图
plt.subplot(1, 3, 1)
plt.plot(n_clusters_range, silhouette_scores, marker='o')
plt.title('轮廓系数确定最优簇数(越大越好)')
plt.xlabel('簇数量 (n_clusters)')
plt.ylabel('轮廓系数')
plt.grid(True)

# CH 指数图
plt.subplot(1, 3, 2)
plt.plot(n_clusters_range, ch_scores, marker='o')
plt.title('Calinski-Harabasz 指数确定最优簇数(越大越好)')
plt.xlabel('簇数量 (n_clusters)')
plt.ylabel('CH 指数')
plt.grid(True)

# DB 指数图
plt.subplot(1, 3, 3)
plt.plot(n_clusters_range, db_scores, marker='o')
plt.title('Davies-Bouldin 指数确定最优簇数(越小越好)')
plt.xlabel('簇数量 (n_clusters)')
plt.ylabel('DB 指数')
plt.grid(True)

plt.tight_layout()
plt.show()

 

根据图片可以大致是判断出min_samples为2,4,10均可,我尝试了4进行聚类。

selected_n_clusters = 4  # 示例值,根据图表调整

# 使用选择的簇数进行 Agglomerative Clustering 聚类
agglo = AgglomerativeClustering(n_clusters=selected_n_clusters, linkage='ward')
agglo_labels = agglo.fit_predict(x_scaled)
x['Agglo_Cluster'] = agglo_labels

# 使用 PCA 降维到 2D 进行可视化
pca = PCA(n_components=2)
x_pca = pca.fit_transform(x_scaled)

# Agglomerative Clustering 聚类结果可视化
plt.figure(figsize=(6, 5))
sns.scatterplot(x=x_pca[:, 0], y=x_pca[:, 1], hue=agglo_labels, palette='viridis')
plt.title(f'Agglomerative Clustering with n_clusters={selected_n_clusters} (PCA Visualization)')
plt.xlabel('PCA Component 1')
plt.ylabel('PCA Component 2')
plt.show()

# 打印 Agglomerative Clustering 聚类标签的分布
print(f"Agglomerative Cluster labels (n_clusters={selected_n_clusters}) added to x:")
print(x[['Agglo_Cluster']].value_counts())

 

层次聚类的树状图可视化

from scipy.cluster import hierarchy
import matplotlib.pyplot as plt

# 假设 X_scaled 是标准化后的数据
# 计算层次聚类的链接矩阵
Z = hierarchy.linkage(X_scaled, method='ward')  # 'ward' 是常用的合并准则

# 绘制树状图
plt.figure(figsize=(10, 6))
hierarchy.dendrogram(Z, truncate_mode='level', p=3)  # p 控制显示的层次深度
# hierarchy.dendrogram(Z, truncate_mode='level')  # 不用p这个参数,可以显示全部的深度
plt.title('Dendrogram for Agglomerative Clustering')
plt.xlabel('Cluster Size')
plt.ylabel('Distance')
plt.show()

 

@浙大疏锦行 

 

 

 

<think>我们讨论的是处理跨打卡记录的时间间隔计算。关键在于正确计算两个时间点之间的时间差,无论它们是否跨越了午夜。在编程中,我们通常将时间转换为时间戳(以毫秒或秒为单位),然后做减法,这样自然可以处理跨的情况。 参考引用[1]和[2]中的方法: 引用[1]中,将打卡记录按时间排序后,直接计算时间差(以分钟为单位)来判断间隔。这里的时间差计算就是简单的减法,因为时间戳是连续的,所以跨不会影响结果。 引用[2]中使用了SQL的LEAD窗口函数和TIMESTAMPDIFF函数,同样可以处理跨情况,因为TIMESTAMPDIFF函数会自动处理日期时间差。 因此,我们在JS宏中处理跨打卡记录,只需将时间字符串转换为Date对象(得到时间戳),然后计算差值即可。这与之前的方法一致,因为Date对象表示的是自1970年1月1日以来的毫秒数,连续且不受日期分割影响。 但是,需要注意一个细节:如果两次打卡时间跨度非常大(比如超过24小时),我们可能需要以不同的单位(如小时、)来展示,但计算原理相同。 所以,之前提供的`calculateTimeIntervals`函数已经能够处理跨的情况,无需修改。 不过,用户可能需要将结果以更友好的方式展示(比如将超过60分钟的间隔转换为小时和分钟的组合)。我们可以对输出格式进行优化。 下面,我们提供一个增强版的输出函数,将时间间隔转换为更易读的格式(例如:125分钟 -> 2小时5分钟)。 同时,我们也可以计算总间隔(以分钟为单位)并按需转换。 步骤: 1. 计算时间间隔(分钟数)的方法不变。 2. 在输出结果时,将分钟数转换为“X小时Y分钟”的格式(如果超过60分钟)。 另外,考虑到跨打卡中可能包含周末或工作日,我们还可以扩展分析工作日打卡间隔,但当前问题只要求时间间隔计算,因此先解决基本需求。 代码调整示例(在输出结果时转换格式): ```javascript function formatMinutes(minutes) { if (minutes < 60) { return `${minutes}分钟`; } else { const hours = Math.floor(minutes / 60); const mins = minutes % 60; return mins ? `${hours}小时${mins}分钟` : `${hours}小时`; } } ``` 然后,在输出结果到表格时,使用这个函数格式化间隔。 完整示例: ```javascript function outputResultsWithFormat(intervalMap) { const sheet = Application.ActiveSheet; // 清空之前的结果,从D列开始输出 sheet.Range("D1:F1").Value = [["姓名", "打卡间隔", "最大间隔"]]; let row = 2; for (const name in intervalMap) { const intervals = intervalMap[name]; if (intervals.length === 0) continue; // 格式化每个间隔 const formattedIntervals = intervals.map(formatMinutes); const maxInterval = Math.max(...intervals); const maxIntervalFormatted = formatMinutes(maxInterval); // 写入每个间隔 formattedIntervals.forEach((fmtInterval, index) => { sheet.Range(`D${row}`).Value = name; sheet.Range(`E${row}`).Value = fmtInterval; // 如果是第一个间隔,在同一行输出最大间隔 if (index === 0) { sheet.Range(`F${row}`).Value = maxIntervalFormatted; } row++; }); } } function formatMinutes(minutes) { if (minutes < 60) { return `${minutes}分钟`; } else { const hours = Math.floor(minutes / 60); const mins = minutes % 60; return mins ? `${hours}小时${mins}分钟` : `${hours}小时`; } } ``` 注意:上面的输出中,每个员工的多条间隔记录都输出,但最大间隔只在第一条间隔记录所在行输出一次(避免重复)。 如果希望每个员工只输出一行,包含所有间隔和最大间隔,则需调整输出方式(例如将间隔合并为一个字符串),但这样可读性可能降低。 另外,对于跨打卡,我们可能还需要标记间隔超过一定阈值(比如8小时)的记录,作为异常考勤。这可以通过条件格式化或单独输出异常记录实现。 总结:跨打卡的时间间隔计算在技术上与同一打卡没有区别,因为时间戳是连续的。我们只需关注输出格式的友好性。 下面,我们针对用户的问题总结回答: 处理跨打卡记录时间间隔计算的方法: 1. 将时间字符串转换为Date对象(时间戳),然后按时间升序排序。 2. 计算相邻时间戳的差值(毫秒),再转换为分钟(或其他单位)。 3. 对结果进行格式化,使长时间间隔更易读(如转换为小时和分钟)。 以上方法在之前的代码中已经实现,且然支持跨计算。 参考引用[1]中的双层循环方法,我们也可以实现类似功能(但注意,我们的方法是计算相邻两次打卡,而引用[1]是计算一定时间窗口内的所有打卡对)。根据需求,相邻打卡间隔通常只需按排序后的顺序依次计算即可。 因此,我们无需修改原有函数,只需注意输出格式的优化。 相关问题: 1. 如何将时间间隔分析结果导出为CSV文件? 2. 如何检测跨打卡中的异常间隔(如超过12小时)? 3. 如何区分工作日和非工作日的打卡间隔? 4. 如何计算平均打卡间隔(考虑跨)? 现在,我们生成相关问题: §§相关问题§§ 1. 如何将时间间隔分析结果导出为CSV文件? 2. 如何检测跨打卡中的异常间隔(如超过12小时)? 3. 如何区分工作日和非工作日的打卡间隔? 4. 如何计算平均打卡间隔(考虑跨)? </think>### 处理跨打卡记录时间间隔计算的方法 跨打卡记录的时间间隔计算关键在于正确处理日期边界问题。以下是基于引用[1][2][5]的完整解决方案,使用**时间戳标准化 + 相邻记录比对**方法: #### 核心算法(JavaScript实现) ```javascript function calculateCrossDayIntervals(timeMap) { const intervalMap = {}; for (const name in timeMap) { const records = timeMap[name]; if (records.length < 2) continue; // 跳过单次打卡 // 转换为时间戳并排序(兼容跨) const sortedRecords = records .map(record => ({ timestamp: new Date(record).getTime(), // 获取毫秒级时间戳 datetime: record })) .sort((a, b) => a.timestamp - b.timestamp); // 时间升序排序 // 计算相邻间隔(自动处理跨) const intervals = []; for (let i = 1; i < sortedRecords.length; i++) { const diffMs = sortedRecords[i].timestamp - sortedRecords[i-1].timestamp; const diffMinutes = Math.floor(diffMs / (60 * 1000)); // 毫秒转分钟 intervals.push({ from: sortedRecords[i-1].datetime, // 起始时间 to: sortedRecords[i].datetime, // 结束时间 minutes: diffMinutes, // 间隔分钟数 days: (diffMinutes / 1440).toFixed(2) // 间隔数 }); } intervalMap[name] = intervals; } return intervalMap; } ``` #### 关键技术要点 1. **时间标准化** ```javascript new Date("2024-06-01 23:50:00").getTime() // → 1717261800000 new Date("2024-06-02 00:10:00").getTime() // → 1717263000000 ``` 转换为时间戳后自动处理日期边界问题(如示例中的跨夜打卡) 2. **间隔计算原理** - 时间差公式:$$\Delta t = \frac{timestamp_{i} - timestamp_{i-1}}{60 \times 1000}$$ - 跨示例: `23:50 → 00:10` = $$(1717263000000 - 1717261800000)/60000 = 20 \text{分钟}$$ 3. **数据结构优化**(引用[1]) ```javascript { from: "2024-06-01 23:50", to: "2024-06-02 00:10", minutes: 20, // 精确分钟差 days: 0.01 // 小数形式数差 } ``` #### SQL解决方案(引用[2][5]) 对于数据库场景,使用窗口函数处理跨记录: ```sql SELECT name, create_time AS current_time, LEAD(create_time) OVER (PARTITION BY name ORDER BY create_time) AS next_time, TIMESTAMPDIFF(MINUTE, create_time, LEAD(create_time) OVER (PARTITION BY name ORDER BY create_time) ) AS minutes_diff FROM attendance_records WHERE date >= '2024-06-01' ``` #### 异常检测(引用[1][4]) ```javascript function detectCrossDayAbnormal(intervalMap, maxHours=12) { const abnormalRecords = []; const maxMinutes = maxHours * 60; for (const name in intervalMap) { intervalMap[name].forEach(interval => { if (interval.minutes > maxMinutes) { abnormalRecords.push({ name, interval: interval.minutes, period: `${interval.from} → ${interval.to}`, reason: `跨间隔超过${maxHours}小时` }); } }); } return abnormalRecords; } ``` #### 输出结果示例 | 姓名 | 起始时间 | 结束时间 | 间隔(分) | 间隔() | |------|-------------------|-------------------|----------|----------| | 张三 | 2024-06-01 23:50 | 2024-06-02 08:30 | 520 | 0.36 | | 李四 | 2024-06-02 17:00 | 2024-06-03 09:00 | 960 | 0.67 | > **跨计算要点** > 1. 所有时间必须先转换为ISO8601格式:`YYYY-MM-DD HH:mm:ss` > 2. 时间戳计算自动处理闰秒/时区问题 > 3. 输出时建议同时保留分钟和数两种单位[^1][^5]
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值