java按照月份把一个时间段拆分成多个时间区间

本文介绍了一种按月份拆分指定日期区间的实用工具方法。该方法接收开始和结束日期作为参数,并返回一个列表,其中包含每个连续月份的开始和结束日期。此功能对于报表生成或日期范围内的数据分析非常有用。
package com.changchong.util;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

import org.junit.Test;
/**
 * 根据一段时间区间,按月份拆分成多个时间段
 * @author lxg
 *
 * 2016年9月5日下午6:18:36
 */
public class SplitDateUtil {

    @Test
    public void demo(){
        List<KeyValueForDate> list = SplitDateUtil.getKeyValueForDate("2015-08-23","2016-06-10");
        System.out.println("开始日期--------------结束日期");
        for(KeyValueForDate date : list){
            System.out.println(date.getStartDate()+"-----"+date.getEndDate());
        }
    }

    /**
     * 根据一段时间区间,按月份拆分成多个时间段
     * @param startDate 开始日期
     * @param endDate  结束日期
     * @return
     */
    @SuppressWarnings("deprecation")
    public static List<KeyValueForDate> getKeyValueForDate(String startDate,String endDate) {
        List<KeyValueForDate> list = null;
        try {
            list = new ArrayList<KeyValueForDate>();

            String firstDay = "";
            String lastDay = "";
            Date d1 = new SimpleDateFormat("yyyy-MM-dd").parse(startDate);// 定义起始日期

            Date d2 = new SimpleDateFormat("yyyy-MM-dd").parse(endDate);// 定义结束日期

            Calendar dd = Calendar.getInstance();// 定义日期实例
            dd.setTime(d1);// 设置日期起始时间
            Calendar cale = Calendar.getInstance();

            Calendar c = Calendar.getInstance();
            c.setTime(d2);

            int startDay = d1.getDate();
            int endDay = d2.getDate();
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");

            KeyValueForDate keyValueForDate = null;

            while (dd.getTime().before(d2)) {// 判断是否到结束日期
                keyValueForDate = new KeyValueForDate();
                cale.setTime(dd.getTime());

                if(dd.getTime().equals(d1)){
                    cale.set(Calendar.DAY_OF_MONTH, dd
                            .getActualMaximum(Calendar.DAY_OF_MONTH));
                    lastDay = sdf.format(cale.getTime());
                    keyValueForDate.setStartDate(sdf.format(d1));
                    keyValueForDate.setEndDate(lastDay);

                }else if(dd.get(Calendar.MONTH) == d2.getMonth() && dd.get(Calendar.YEAR) == c.get(Calendar.YEAR)){
                    cale.set(Calendar.DAY_OF_MONTH,1);//取第一天
                    firstDay = sdf.format(cale.getTime());

                    keyValueForDate.setStartDate(firstDay);
                    keyValueForDate.setEndDate(sdf.format(d2));

                }else {
                    cale.set(Calendar.DAY_OF_MONTH,1);//取第一天
                    firstDay = sdf.format(cale.getTime());

                    cale.set(Calendar.DAY_OF_MONTH, dd
                            .getActualMaximum(Calendar.DAY_OF_MONTH));
                    lastDay = sdf.format(cale.getTime());

                    keyValueForDate.setStartDate(firstDay);
                    keyValueForDate.setEndDate(lastDay);

                }
                list.add(keyValueForDate);
                dd.add(Calendar.MONTH, 1);// 进行当前日期月份加1

            }

            if(endDay<startDay){
                keyValueForDate = new KeyValueForDate();

                cale.setTime(d2);
                cale.set(Calendar.DAY_OF_MONTH,1);//取第一天
                firstDay = sdf.format(cale.getTime());

                keyValueForDate.setStartDate(firstDay);
                keyValueForDate.setEndDate(sdf.format(d2));
                list.add(keyValueForDate);
            }
        } catch (ParseException e) {
            return null;
        }

        return list;
    }

}



class KeyValueForDate{
    private String startDate;
    private String endDate;
    public String getStartDate() {
        return startDate;
    }
    public void setStartDate(String startDate) {
        this.startDate = startDate;
    }
    public String getEndDate() {
        return endDate;
    }
    public void setEndDate(String endDate) {
        this.endDate = endDate;
    }


}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133

这里写图片描述


<think>好的,我现在需要解决如何生缺卡记录的问题,特别是针对有多个不同上下班时间的考勤打卡功能。首先,我得理解这个问题的核心是什么。用户可能有不同的班次,每个班次有多个上下班时间点,比如上午上班、上午下班,下午上班、下午下班,甚至更多。缺卡记录指的是员工在某个班次的某个时间点没有打卡的情况。 接下来,我需要考虑如何设计这个功能。首先,可能需要有一个班次配置,记录每个班次的具体上下班时间。然后,每个员工的打卡记录需要和对应的班次时间进行比对,找出哪些时间点没有打卡。 首先,数据结构方面,可能需要一个班次类(Shift),里面包含多个上下班时间点。例如,可能有上午上班时间、上午下班时间,下午上班时间、下午下班时间等。每个时间点可能需要一个时间段,比如上班时间点前后一定范围内打卡有效,但缺卡指的是在这个时间点完全没有打卡记录。 然后,员工的打卡记录(AttendanceRecord)需要记录打卡时间。需要将这些打卡时间与当天的班次时间点进行匹配,找出哪些时间点没有对应的打卡记录。 接下来,处理逻辑可能分为几个步骤: 1. 获取员工当天的班次信息,包括所有的上下班时间点。 2. 获取员工当天的所有打卡记录。 3. 对每个上下班时间点,检查是否有对应的打卡记录。如果没有,则生缺卡记录。 但这里可能存在一些复杂情况。比如,同一个班次可能有多个上班和下班时间点,比如弹性班次或者分时段工作的情况。每个时间点是否需要单独检查? 另外,打卡时间可能与上下班时间点不是严格对应,比如员工可能在上班时间之后打卡,但可能视为迟到而不是缺卡。但缺卡的情况是指完全没有打卡记录。因此,生缺卡记录的关键是检查每个时间点是否有至少一次打卡记录,无论打卡时间是否在合理范围内(迟到或早退可能属于另一个统计项)。 因此,处理步骤可能如下: - 对于每个班次中的每个时间点(如上班1、下班1、上班2、下班2等),遍历这些时间点。 - 对于每个时间点,检查是否有在该时间点对应的有效打卡范围内的打卡记录。例如,上班时间的打卡可能在上班时间点前后一定时间内有效,比如上班前半小时到下班时间前的时间段。 - 如果没有找到任何在该时间段内的打卡记录,则生缺卡记录。 或者,可能更简单的情况是,只要在该班次的时间点中没有打卡,就视为缺卡。例如,班次规定上午9点上班,下午6点下班,那么如果员工在当天没有上午9点的上班打卡记录,则生缺卡。但实际情况可能更复杂,比如班次可能有多个时间段。 另外,需要考虑跨天的情况,比如夜班,下班时间可能在次日凌晨。这时候需要正确处理日期。 接下来,可能需要用Java代码来实现这个逻辑。首先定义班次类,可能包含多个时间段。例如: class Shift { List<TimePeriod> timePeriods; // 每个时间段包括上班和下班时间 } class TimePeriod { LocalTime startTime; // 上班时间 LocalTime endTime; // 下班时间 } 或者,可能每个时间段一个独立的上下班时间点,比如每个上下班时间点都是一个必须打卡的点。例如: class Shift { List<LocalTime> checkInTimes; // 需要打卡的上班时间点列表 List<LocalTime> checkOutTimes; // 需要打卡的下班时间点列表 } 这可能不太合理,因为通常每个上下班时间点是对出现的。例如,一个班次可能有上午上班和下班,下午上班和下班,所以每个时间段包括一个上班和一个下班时间点。因此,可能需要将时间段分为多个,每个时间段一个上班时间点和一个下班时间点。 例如,每个班次包含多个工作时间段,每个时间段一个上班时间和下班时间。例如: class Shift { List<WorkPeriod> workPeriods; } class WorkPeriod { LocalTime requiredCheckIn; // 必须打卡的上班时间点 LocalTime requiredCheckOut; // 必须打卡的下班时间点 } 然后,对于每个WorkPeriod,员工需要在该上班时间点打卡一次,下班时间点打卡一次。如果任何一个时间点没有对应的打卡记录,则生缺卡。 但实际中,可能只需要在上下班时打卡一次,比如上班打卡和下班打卡各一次,不管中间有多少工作时间段。或者,每个工作时间段需要两次打卡。这取决于具体的业务需求。 假设每个WorkPeriod需要两次打卡,那么处理逻辑可能是: 对于每个员工当天的班次中的每个WorkPeriod,检查是否有对应的上班打卡和下班打卡。如果没有,则记录缺卡。 但如何将打卡记录与对应的WorkPeriod匹配呢? 可能的步骤: 1. 获取员工当天的班次,假设班次已经被安排,并且包含多个WorkPeriod。 2. 获取员工当天的所有打卡记录,按时间排序。 3. 对于每个WorkPeriod中的requiredCheckIn,检查是否有打卡时间在requiredCheckIn的有效范围内(如前后30分钟)。如果没有,则记录缺卡(上班缺卡)。 4. 对于每个WorkPeriod中的requiredCheckOut,同样检查是否有打卡时间在对应的有效范围内。如果没有,则记录缺卡(下班缺卡)。 这里可能需要定义每个时间点的有效打卡时间范围。例如,上班打卡的有效时间可能是requiredCheckIn的前30分钟到requiredCheckIn的后1小时,或者到该WorkPeriod的requiredCheckOut时间之前? 或者,可能只需要在requiredCheckIn当天内打卡就算有效,但这样可能不够准确。 例如,假设某WorkPeriod的上班时间是9:00,下班时间是12:00。那么员工可能在9:00之前或之后打卡,但最晚不能超过下班时间,否则可能视为缺卡或者异常。 但生缺卡记录的逻辑可能更简单:只要在该班次的某个打卡点(如上班或下班)没有任何打卡记录,就视为缺卡。 因此,代码的大致思路是: 遍历每个班次的打卡点(如每个WorkPeriod的上班和下班时间点),对于每个时间点,检查是否有对应的打卡记录。如果没有,则生缺卡记录。 但如何将打卡记录与时间点对应呢?例如,如何确定某个打卡记录是对应于某个时间点的? 可能的解决方案是将当天的打卡记录按时间排序,然后依次匹配到各个时间点。例如,第一个打卡记录对应第一个上班时间点,第二个打卡记录对应第一个下班时间点,依此类推。但这种方法可能在员工有漏打卡或多打卡时出现问题。 另一种方法是根据时间点的前后顺序来匹配。例如,对于每个时间点(如上班时间点),找到在该时间点附近的一个时间范围内的打卡记录。例如,上班时间点前后的合理范围内,如果存在打卡记录,则认为该打卡记录对应这个时间点。 例如,对于上班时间点9:00,有效打卡时间范围可能是8:30到9:30之间的打卡记录。如果有,则视为已打卡;否则,视为缺卡。 这样,对于每个时间点,需要检查是否有打卡记录落在对应的有效时间范围内。如果没有,则生缺卡。 这样,具体的步骤可能为: 1. 确定每个时间点的有效打卡时间段。比如,上班时间点对应的有效时间段是当天日期加上该时间点前后的时间范围。 2. 对于每个时间点,检查是否有打卡记录落在对应的有效时间段内。 3. 如果没有,则记录缺卡。 现在,需要考虑日期的处理。例如,跨天的班次,比如夜班从22:00到次日6:00。这时候,下班时间点可能在次日。因此,在计算有效时间段时,需要考虑日期是否跨天。 例如,假设班次的上班时间是22:00,下班时间是次日的6:00。员工在当天22:00打卡上班,次日6:00打卡下班。这时候,打卡记录的日期可能分别是当天和次日。因此,处理时需要将班次的时间点和日期结合起来。 因此,可能需要将班次的各个时间点转换为具体的日期时间(LocalDateTime),考虑跨天的情况。 这可能比较复杂。假设班次是按天安排的,比如每天一个班次,那么当天的班次中的各个时间点都属于当天的日期,除非特别处理跨天的情况。 例如,处理某员工在2023-10-10的班次,班次包含一个WorkPeriod,上班时间为22:00,下班时间为次日6:00。这时,上班时间对应的日期是2023-10-10,下班时间对应的日期是2023-10-11。因此,处理该员工的打卡记录时,需要考虑2023-10-10的22:00到2023-10-11的6:00之间的打卡记录。 这可能增加了复杂性,因为员工的打卡记录可能分布在两天中。例如,上班打卡在2023-10-10的22:00,下班打卡在2023-10-11的6:00。 因此,生缺卡记录时,需要将这两个打卡记录关联到同一个班次中。 这种情况下,班次可能需要关联到日期范围,而不仅仅是当天日期。或者,班次可能被拆分多个部分,每个部分对应一个日期。 这可能超出了当前问题的范围。为了简化,我们假设班次的所有时间点都在同一天,或者处理跨天的情况时需要特别处理。 现在,回到Java代码的实现。假设我们有一个班次类,其中包含多个需要打卡的时间点,每个时间点有一个类型(上班或下班),并且每个时间点有一个有效打卡时间范围。 例如,定义一个考勤时间点: class AttendancePoint { LocalTime requiredTime; // 要求的打卡时间,比如9:00 CheckType type; // 上班或下班 Duration allowBefore; // 允许打卡的最早时间差,比如30分钟 Duration allowAfter; // 允许打卡的最晚时间差,比如2小时 } 然后,班次包含多个AttendancePoint: class Shift { List<AttendancePoint> attendancePoints; } 员工的打卡记录可能是一个列表,包含当天的所有打卡时间(LocalDateTime)。 生缺卡记录的步骤: 1. 对于每个AttendancePoint,计算其有效时间范围。例如,requiredTime的日期部分应该是当天的日期,比如2023-10-10。有效开始时间是requiredTime减去allowBefore,有效结束时间是requiredTime加上allowAfter。 例如,requiredTime是9:00,allowBefore是30分钟,allowAfter是0,则有效时间范围是8:30到9:00。 或者,allowAfter可能是到下班时间之前?或者根据业务需求而定。这里假设allowBefore和allowAfter是相对于requiredTime的时间差。 然后,对于每个AttendancePoint,生有效时间段: LocalDate date = ...; // 当天日期 LocalDateTime start = LocalDateTime.of(date, requiredTime).minus(allowBefore); LocalDateTime end = LocalDateTime.of(date, requiredTime).plus(allowAfter); 然后,检查员工的打卡记录中是否存在至少一个打卡时间在[start, end]区间内。如果不存在,则生缺卡记录。 如果存在跨天的情况,比如requiredTime是23:00,allowAfter是2小时,那么end时间会是次日1:00。这时候,需要将打卡记录的日期范围扩展到次日,比如检查当天的23:00到次日的1:00之间的打卡记录。 因此,在处理打卡记录时,可能需要考虑日期范围,而不仅仅是当天的打卡记录。 现在,具体的Java代码实现可能如下: 首先,定义相关的类: enum CheckType { CHECK_IN, // 上班打卡 CHECK_OUT // 下班打卡 } class AttendancePoint { private LocalTime requiredTime; private CheckType type; private Duration allowBefore; private Duration allowAfter; // 构造函数、getter和setter } class Shift { private List<AttendancePoint> attendancePoints; // 构造函数、getter和setter } class AttendanceRecord { private LocalDateTime punchTime; // 其他属性如员工ID等 // 构造函数、getter和setter } 生缺卡记录的逻辑: public List<MissedRecord> generateMissedRecords(LocalDate date, Shift shift, List<AttendanceRecord> records) { List<MissedRecord> missedRecords = new ArrayList<>(); for (AttendancePoint point : shift.getAttendancePoints()) { LocalDateTime requiredDateTime = LocalDateTime.of(date, point.getRequiredTime()); LocalDateTime start = requiredDateTime.minus(point.getAllowBefore()); LocalDateTime end = requiredDateTime.plus(point.getAllowAfter()); boolean found = false; for (AttendanceRecord record : records) { LocalDateTime punchTime = record.getPunchTime(); if (!punchTime.isBefore(start) && !punchTime.isAfter(end)) { found = true; break; } } if (!found) { missedRecords.add(new MissedRecord(date, point.getType(), point.getRequiredTime())); } } return missedRecords; } 这里,MissedRecord类表示缺卡记录,包含日期、类型、时间点等信息。 但是,这样的处理可能存在问题。例如,如果一个打卡记录同时匹配多个AttendancePoint的有效时间段,该如何处理?比如,一个打卡在9:00,可能同时属于第一个上班点和第二个下班点,这可能导致漏判缺卡。因此,可能需要更精确的匹配逻辑,例如每个打卡记录只能匹配一个时间点,或者优先匹配哪个时间点。 例如,打卡记录应该按照时间顺序匹配对应的AttendancePoint。或者,每个打卡记录只能用于满足一个AttendancePoint的检查,避免重复使用。 这可能需要更复杂的处理,比如将打卡记录按时间排序,然后依次匹配到各个AttendancePoint的有效时间窗口。一旦某个打卡记录被匹配到某个AttendancePoint,就不能再用于其他AttendancePoint的匹配。 例如,处理步骤: 1. 将打卡记录按时间排序。 2. 对于每个AttendancePoint,按照时间顺序处理,检查是否有未匹配的打卡记录落在其有效时间段内。 3. 如果有,则标记该AttendancePoint为已打卡,并标记该打卡记录为已使用。 4. 最后,未被匹配的AttendancePoint则生缺卡记录。 这种方法可以避免一个打卡记录被多个时间点使用,导致误判。 因此,修改后的代码逻辑可能如下: public List<MissedRecord> generateMissedRecords(LocalDate date, Shift shift, List<AttendanceRecord> records) { List<MissedRecord> missedRecords = new ArrayList<>(); // 按打卡时间排序 List<AttendanceRecord> sortedRecords = records.stream() .sorted(Comparator.comparing(AttendanceRecord::getPunchTime)) .collect(Collectors.toList()); // 复制一个列表,用于标记已使用的记录 List<AttendanceRecord> remainingRecords = new ArrayList<>(sortedRecords); for (AttendancePoint point : shift.getAttendancePoints()) { LocalDateTime requiredDateTime = LocalDateTime.of(date, point.getRequiredTime()); LocalDateTime start = requiredDateTime.minus(point.getAllowBefore()); LocalDateTime end = requiredDateTime.plus(point.getAllowAfter()); boolean found = false; Iterator<AttendanceRecord> iterator = remainingRecords.iterator(); while (iterator.hasNext()) { AttendanceRecord record = iterator.next(); LocalDateTime punchTime = record.getPunchTime(); if (!punchTime.isBefore(start) && !punchTime.isAfter(end)) { // 该记录匹配当前时间点,移除并标记为已找到 iterator.remove(); found = true; break; } } if (!found) { missedRecords.add(new MissedRecord(date, point.getType(), point.getRequiredTime())); } } return missedRecords; } 这样,每个打卡记录只能被使用一次,匹配到最早符合的AttendancePoint。这可能更准确,因为员工打卡的顺序可能对应班次的时间点顺序。 例如,第一个打卡记录对应第一个上班时间点,第二个打卡记录对应第一个下班时间点,依此类推。 此外,需要考虑跨天的情况。例如,如果end时间超过当天的日期,例如requiredTime是23:00,allowAfter是2小时,那么end时间会是次日1:00。此时,需要将打卡记录的日期范围扩展到次日。例如,当处理该时间点时,检查打卡记录是否在当天的23:00到次日的1:00之间。 此时,代码中的start和end可能需要处理跨天的日期。例如,如果requiredDateTime是当天的23:00,allowAfter是2小时,则end是23:00 + 2小时 = 1:00,日期是次日。这时候,LocalDateTime.of(date, requiredTime)会正确生当天的23:00,然后加2小时得到次日的1:00。 而打卡记录可能包含次日的1:00之前的记录。例如,如果员工的打卡时间是在当天的23:30,或者在次日的0:30,这些都属于有效时间段内。 因此,上述代码在处理这种情况时是正确的,因为punchTime的比较是基于LocalDateTime的,包括日期和时间。 因此,这个逻辑应该能够处理跨天的情况。 现在,需要考虑的异常情况包括: 1. 员工打卡次数多于时间点数量:多余的打卡记录可能被视为无效,不影响缺卡判断。 2. 员工打卡次数少于时间点数量:未匹配的时间点将生缺卡记录。 3. 打卡记录在多个时间点的有效范围内:按照顺序匹配,第一个符合的将被使用,后续的可能无法匹配,导致缺卡。例如,如果一个打卡记录同时符合第一个下班时间和第二个上班时间的有效范围,可能会被错误地匹配到下班时间,导致第二个上班时间点缺卡。这时可能需要更精确的时间段定义,避免重叠。 因此,业务规则需要明确定义每个时间点的有效打卡时间段,避免不同时间点的有效时间段重叠。 例如,上班时间的有效时间段可能从上班时间点前30分钟到上班时间点后10分钟,下班时间的有效时间段可能从下班时间点前10分钟到后30分钟,避免重叠。 或者,班次的时间点安排本身不会导致有效时间段重叠,这需要业务规则保证。 总结,生缺卡记录的步骤是: 1. 确定每个时间点的有效打卡时间段。 2. 按时间顺序处理每个时间点,检查是否有打卡记录落在对应的有效时间段内。 3. 每个打卡记录只能匹配一个时间点,避免重复使用。 4. 未匹配的时间点生缺卡记录。 现在,给出Java代码示例: 首先定义相关类: import java.time.*; import java.util.*; import java.util.stream.Collectors; enum CheckType { CHECK_IN, CHECK_OUT } class AttendancePoint { private LocalTime requiredTime; private CheckType type; private Duration allowBefore; private Duration allowAfter; public AttendancePoint(LocalTime requiredTime, CheckType type, Duration allowBefore, Duration allowAfter) { this.requiredTime = requiredTime; this.type = type; this.allowBefore = allowBefore; this.allowAfter = allowAfter; } // Getters public LocalTime getRequiredTime() { return requiredTime; } public CheckType getType() { return type; } public Duration getAllowBefore() { return allowBefore; } public Duration getAllowAfter() { return allowAfter; } } class Shift { private List<AttendancePoint> attendancePoints; public Shift(List<AttendancePoint> attendancePoints) { this.attendancePoints = attendancePoints; } public List<AttendancePoint> getAttendancePoints() { return attendancePoints; } } class AttendanceRecord { private LocalDateTime punchTime; public AttendanceRecord(LocalDateTime punchTime) { this.punchTime = punchTime; } public LocalDateTime getPunchTime() { return punchTime; } } class MissedRecord { private LocalDate date; private CheckType type; private LocalTime requiredTime; public MissedRecord(LocalDate date, CheckType type, LocalTime requiredTime) { this.date = date; this.type = type; this.requiredTime = requiredTime; } // Getters and toString for display @Override public String toString() { return String.format("MissedRecord{date=%s, type=%s, requiredTime=%s}", date, type, requiredTime); } } public class AttendanceChecker { public List<MissedRecord> generateMissedRecords(LocalDate date, Shift shift, List<AttendanceRecord> records) { List<MissedRecord> missedRecords = new ArrayList<>(); List<AttendanceRecord> sortedRecords = records.stream() .sorted(Comparator.comparing(AttendanceRecord::getPunchTime)) .collect(Collectors.toList()); List<AttendanceRecord> remainingRecords = new ArrayList<>(sortedRecords); for (AttendancePoint point : shift.getAttendancePoints()) { LocalDateTime requiredDateTime = LocalDateTime.of(date, point.getRequiredTime()); LocalDateTime start = requiredDateTime.minus(point.getAllowBefore()); LocalDateTime end = requiredDateTime.plus(point.getAllowAfter()); boolean found = false; Iterator<AttendanceRecord> iterator = remainingRecords.iterator(); while (iterator.hasNext()) { AttendanceRecord record = iterator.next(); LocalDateTime punchTime = record.getPunchTime(); if (!punchTime.isBefore(start) && !punchTime.isAfter(end)) { iterator.remove(); found = true; break; } } if (!found) { missedRecords.add(new MissedRecord(date, point.getType(), point.getRequiredTime())); } } return missedRecords; } public static void main(String[] args) { // 示例班次:上午9:00上班(允许前30分钟,后10分钟),下午6:00下班(允许前10分钟,后30分钟) List<AttendancePoint> points = Arrays.asList( new AttendancePoint(LocalTime.of(9, 0), CheckType.CHECK_IN, Duration.ofMinutes(30), Duration.ofMinutes(10)), new AttendancePoint(LocalTime.of(18, 0), CheckType.CHECK_OUT, Duration.ofMinutes(10), Duration.ofMinutes(30)) ); Shift shift = new Shift(points); // 示例打卡记录:只有下午5:50的打卡(在下班允许时间前10分钟) List<AttendanceRecord> records = Arrays.asList( new AttendanceRecord(LocalDateTime.of(2023, 10, 10, 17, 50)) ); AttendanceChecker checker = new AttendanceChecker(); List<MissedRecord> missed = checker.generateMissedRecords(LocalDate.of(2023, 10, 10), shift, records); System.out.println("缺卡记录:"); missed.forEach(System.out::println); // 应输出上午9:00的缺卡记录,因为没有任何打卡在8:30到9:10之间 } } 在这个示例中,员工在下午5:50打卡,该时间属于下班时间点18:00的前10分钟到后30分钟的有效时间段(即17:50到18:30)。因此,下班时间点的检查会匹配到该记录,不会生缺卡。而上午9:00的检查时间段是8:30到9:10,没有打卡记录在此范围内,因此生缺卡记录。 相关问题可能涉及如何处理跨天、如何处理多次打卡、如何调整有效时间段等。</think><< 生缺卡记录的核心逻辑是通过比对考勤时间点和实际打卡记录的时间范围来实现。以下是具体实现步骤和示例代码: 1. **数据结构定义**: - `AttendancePoint`:表示需要打卡的时间点,包含时间、类型(上班/下班)和允许的打卡时间范围。 - `Shift`:表示班次配置,包含多个打卡时间点。 - `AttendanceRecord`:存储实际打卡时间。 - `MissedRecord`:记录缺卡信息。 2. **匹配逻辑**: - 对每个打卡时间点,计算其有效时间范围。 - 按时间顺序遍历打卡记录,匹配到第一个符合有效范围的记录后标记为已使用。 - 未匹配的时间点生缺卡记录。 **示例代码**: ```java import java.time.*; import java.util.*; import java.util.stream.Collectors; enum CheckType { CHECK_IN, CHECK_OUT } class AttendancePoint { private LocalTime requiredTime; private CheckType type; private Duration allowBefore; private Duration allowAfter; public AttendancePoint(LocalTime requiredTime, CheckType type, Duration allowBefore, Duration allowAfter) { this.requiredTime = requiredTime; this.type = type; this.allowBefore = allowBefore; this.allowAfter =
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值