日期推算:EDATE、EOMONTH、DATEDIF、NETWORKDAYS
四个推算函数各解决什么问题

知道今天是几号之后,下一步几乎总是要算:三个月后是哪天、这个月最后一天是几号、两个日期之间差了几年几个月、这中间有多少个工作日。这些需求靠 DATE 硬拼也能做,但公式会又长又脆,真正该用的四个函数是 EDATE、EOMONTH、DATEDIF、NETWORKDAYS。
它们的分工非常清晰。EDATE 负责按整月平移,回答若干月之后的同一天是几号;EOMONTH 负责定位月末,回答某个月的最后一天是几号;DATEDIF 负责算间隔,回答两个日期之间差了多少年、多少月、多少天;NETWORKDAYS 负责算工作日,回答一段时间内扣掉周末和法定假日后还剩几天。
这四个函数里,EDATE 和 EOMONTH 在老版本 Excel 里属于分析工具库,需要加载宏才能用,从 Excel 2007 起已经内置,直接使用即可。NETWORKDAYS 同样从 2007 起内置。而 DATEDIF 是个特殊的存在,它从很早的版本就有,却一直是个隐藏函数,在函数列表里找不到它,输入时也不会弹出参数提示,但输入完整之后能正常计算。
| 函数 | 语法 | 返回值 | 典型用途 |
|---|---|---|---|
EDATE |
=EDATE(A2,3) |
三个月后的同日 | 合同到期日、保修期 |
EOMONTH |
=EOMONTH(A2,0) |
当月最后一天 | 月末结算日、账期 |
DATEDIF |
=DATEDIF(A2,B2,"M") |
相距整月数 | 工龄、账龄、年龄 |
NETWORKDAYS |
=NETWORKDAYS(A2,B2) |
工作日天数 | 工期、出勤统计 |
WORKDAY |
=WORKDAY(A2,10) |
十个工作日后的日期 | 承诺交付日 |
表格里多列了一个 WORKDAY,它是 NETWORKDAYS 的逆向函数:后者问“这段时间有几个工作日”,前者问“过这么多工作日是几号”。两者通常成对出现,做项目排期时缺一不可。
在动手写公式之前,建议先花一分钟想清楚业务规则到底怎么定。同样是“三个月后”,有的公司是自然月三个月,有的公司是九十天,有的公司还要求落在月末或者遇节假日顺延。规则不同,公式完全不同。把规则用一句话写在表格的备注区,既能提醒自己,也方便日后别人接手时核对,避免因为理解偏差导致整批数据的到期日都算错。
使用这一组函数有个共同前提:参与计算的必须是真正的日期,不能是文本。如果 A2 是文本日期,EDATE 会返回 #VALUE!。遇到这种情况,先用上一章讲过的分列或者 DATEVALUE 转成真日期,再套推算函数。转换的具体手法参见 日期基础:TODAY、NOW、DATE 和年月日提取。
EDATE 与 EOMONTH:按月平移与月末定位
EDATE 的语法是 =EDATE(开始日期, 月数),返回开始日期之前或之后指定月数的同一天。月数可以是正数、负数或零,正数往后推,负数往前推,零返回原日期。
算一年后的同日:
=EDATE(A2,12)
算三个月前的同日:
=EDATE(A2,-3)
EDATE 最贴心的设计是自动处理月末。如果开始日期是某月的最后一天,结果也会落在目标月的最后一天:=EDATE("2023-1-31",1) 得到 2023-2-28,而不是顺延到三月。反过来,如果开始日期是一月三十日,加一个月得到二月二十八日,再回到一月三十日则不会发生,因为平移是单向的。
这个特性让它非常适合算合同到期日。一份合同签三年,签字日是 2023-2-28,到期日就是 =EDATE(A2,36),结果是 2026-2-28,完全符合业务预期,不需要人工去核对每年的天数。
EOMONTH 的语法是 =EOMONTH(开始日期, 月数),返回开始日期所在月(再偏移指定月数)的最后一天。第二个参数写零表示当月月末,写负一表示上个月月末,写一表示下个月月末。
当月最后一天:
=EOMONTH(A2,0)
上个月最后一天:
=EOMONTH(A2,-1)
EOMONTH 最常见的用途有三个。一是算当月一共有几天,配合 DAY 即可:=DAY(EOMONTH(A2,0)),一月返回三十一,二月返回二十八或二十九,闰年自动处理,比自己写判断闰年的公式靠谱得多。
二是算月末结算日。很多公司的账期规则是“次月月末付款”,写法就是 =EOMONTH(开票日期,1)。如果规则是“开票后六十天,遇到月末顺延到当月最后一天”,则写成 =EOMONTH(开票日期+60,0)。
三是做按月的区间统计。求某个月的第一天和最后一天,可以分别写 =EOMONTH(A2,-1)+1 和 =EOMONTH(A2,0),把这两个值作为统计区间的上下界,配合条件求和就能得到整月数据。多条件统计的写法在 SUMIFS 多条件统计:日期区间加多字段 里有详细展开。
EOMONTH 还能轻松定位季度末和半年末。某个日期所在季度的最后一天:
=EOMONTH(A2,MOD(3-MONTH(A2),3))
原理是先算出当前月距离本季度末还差几个月,再交给 EOMONTH 平移。比如四月距离六月末差两个月,公式算出的偏移量就是二,结果正好是六月三十日。半年末的写法同理,把三换成六即可:=EOMONTH(A2,MOD(6-MONTH(A2),6))。这两个公式在按季度、按半年度出报表时非常省事,不用每次手工翻日历。
还有一个实用技巧:判断某年是否为闰年,可以写 =IF(DAY(EOMONTH(DATE(A2,2,1),0))=29,"闰年","平年"),其中 A2 是年份。原理是算出二月最后一天是不是二十九号,逻辑直白,也不需要记那些复杂的闰年规则。
DATEDIF:被藏起来的间隔计算神器

DATEDIF 的语法是 =DATEDIF(开始日期, 结束日期, 单位代码),返回两个日期之间的间隔。它的强大之处在于可以按不同单位返回,而且能返回“扣除整年后剩下的月数”这种复合结果。
六个单位代码的含义如下:
| 单位代码 | 含义 | 示例结果 |
|---|---|---|
"Y" |
相隔的完整年数 | =DATEDIF(A2,B2,"Y") |
"M" |
相隔的完整月数 | =DATEDIF(A2,B2,"M") |
"D" |
相隔的总天数 | =DATEDIF(A2,B2,"D") |
"YM" |
扣除整年后剩余的月数 | =DATEDIF(A2,B2,"YM") |
"YD" |
扣除整年后剩余的天数 | =DATEDIF(A2,B2,"YD") |
"MD" |
扣除整年月后剩余的天数 | =DATEDIF(A2,B2,"MD") |
后三个代码是 DATEDIF 独有的能力,其他函数做不到。利用它们可以拼出“三年零五个月”这样的表达:
=DATEDIF(A2,B2,"Y")&"年"&DATEDIF(A2,B2,"YM")&"个月"
算周岁年龄是最经典的用法,把结束日期换成 TODAY() 即可:
=DATEDIF(A2,TODAY(),"Y")
算工龄并精确到月:
=DATEDIF(入职日期,TODAY(),"Y")&"年"&DATEDIF(入职日期,TODAY(),"YM")&"个月"
DATEDIF 有三个必须记住的坑。第一,开始日期必须小于等于结束日期,否则返回 #NUM!。如果数据的先后不确定,用 IF 兜一下:=IF(A2>B2,DATEDIF(B2,A2,"D"),DATEDIF(A2,B2,"D"))。
第二,单位代码必须加英文双引号,写 Y 不加引号会被当成名称而返回 #NAME?。这是新手最高频的报错原因。
第三,"MD" 这个代码在少数情况下会返回零甚至负数,属于该函数历史上遗留的计算缺陷。如果你要的就是剩余天数,更稳妥的做法是用 =B2-EDATE(A2,DATEDIF(A2,B2,"M")),先算出整月数对应的日期,再用结束日期减它,结果稳定且可验证。
NETWORKDAYS 与 WORKDAY:按工作日推算
NETWORKDAYS 的语法是 =NETWORKDAYS(开始日期, 结束日期, [节假日]),返回两个日期之间(含首尾两天)的工作日天数,默认周六周日为休息日。
不加节假日参数:
=NETWORKDAYS(A2,B2)
加上节假日参数:
=NETWORKDAYS(A2,B2,$F$2:$F$20)
第三个参数是一个节假日日期区域,建议单独在工作表的某个角落列出来,比如放在 F 列,并且用绝对引用 $F$2:$F$20 锁定,这样公式向下填充时区域不会偏移。节假日表每年更新一次即可,比把日期硬编码在公式里好维护得多。
NETWORKDAYS.INTL 是它的增强版,多了一个周末参数,可以自定义哪几天休息。周末参数用一串七位的零一字符串表示,从周一开始,0 表示上班,1 表示休息:
=NETWORKDAYS.INTL(A2,B2,"0000011",$F$2:$F$20)
上面的 "0000011" 表示周一到周五上班、周六周日休息,效果等同于普通的 NETWORKDAYS。如果公司只有周日单休,就写 "0000001";如果是轮休周三周四,就写 "0011000"。这个灵活度让它能适配各种排班制度。
WORKDAY 是反向计算,语法是 =WORKDAY(开始日期, 工作日天数, [节假日]),返回从开始日期起经过指定工作日之后的日期。它同样有增强版 WORKDAY.INTL。
算十个工作日之后的交付日:
=WORKDAY(A2,10,$F$2:$F$20)
算项目结束日,已知需要二十五个工作日:
=WORKDAY(开始日期,25,节假日区域)
这两个函数经常配合使用:先用 NETWORKDAYS 算出实际用了几个工作日,再用 WORKDAY 反推应该什么时候结束,两者对比就能看出是否延期。判断是否延期可以写成 =IF(实际完成日>WORKDAY(开始日,计划工作日,节假日),"延期","准时")。
需要提醒的是,NETWORKDAYS 和 WORKDAY 只认你给出的节假日清单,不会自动包含法定节假日。中国的春节、国庆每年日期不同,需要人工维护到节假日区域里,一般把未来两三年的假期一次性列齐即可。
算一整月的工作日天数也是高频需求,把月初月末和 NETWORKDAYS 拼起来即可:
=NETWORKDAYS(EOMONTH(A2,-1)+1,EOMONTH(A2,0),法定假日)
其中 EOMONTH(A2,-1)+1 是当月第一天,EOMONTH(A2,0) 是当月最后一天。有了应出勤天数,再拿实际出勤天数相除就得到了出勤率,考勤月报的核心指标就是这么算出来的。
如果你想算某段时间内每个工作日的日期清单(比如生成排班表的表头),可以用 WORKDAY 配合行号:=WORKDAY(起始日-1,ROW(A1)),往下填充就能依次得到第一个、第二个、第 n 个工作日。减一是因为 WORKDAY 从起始日的次日开始数。
真实场景与兼容提示
账龄分档
应收账款表里,A 列是开票日期,需要按账龄分成三十天内、三十一到六十天、六十一到九十天、九十天以上四档。先算天数差,再分档:
=DATEDIF(A2,TODAY(),"D")
分档公式:
=IF(B2<=30,"30天内",IF(B2<=60,"31-60天",IF(B2<=90,"61-90天","90天以上")))
这里用了一层套一层的写法,判断顺序必须从最小的区间开始,否则会被上一个条件截住。分档之后配合条件求和就能得到各档位的金额合计。
合同到期提醒与自动续期
合同表里,A 列是签订日期,B 列是期限月数。到期日:
=EDATE(A2,B2)
是否即将到期:
=IF(C2-TODAY()<=30,"即将到期","正常")
自动续期一年:
=IF(C2<TODAY(),EDATE(C2,12),C2)
意思是已经过期的自动往后延十二个月,未过期的保持原到期日。这类自动续期公式在管理大量合同时非常省事,但要注意保留原始列,别让公式把历史信息覆盖掉。
员工年假天数计算
假设公司规定:满一年不满五年给五天,满五年不满十年给十天,满十年及以上给十五天。先用 DATEDIF 算出整年工龄,再分档:
=DATEDIF(入职日期,TODAY(),"Y")
然后分档:
=IF(B2<1,0,IF(B2<5,5,IF(B2<10,10,15)))
这个公式的判断顺序必须从低到高,因为 IF 一旦命中就不再往下走。如果先写大于等于十,那所有工龄都会被截在最低档。写好之后建议拿几个边界值验一遍:正好满一年、正好满五年、正好满十年的员工,看给的档位是否符合制度。
如果公司还规定入职当年按月折算,可以在第一档里再用 DATEDIF 取剩余月数:=IF(B2<1,ROUNDDOWN(DATEDIF(入职日期,TODAY(),"M")/12*5,0),...),按整月折算后向下取整。这类规则因公司而异,写公式前务必先把制度条款逐条列清楚,再翻译成判断条件。
版本兼容性
EDATE、EOMONTH、NETWORKDAYS、WORKDAY 从 Excel 2007 起内置,WPS 表格同样支持,可以放心使用。如果你的文件需要在 Excel 2003 里打开,需要启用分析工具库,否则会返回 #NAME?,这种情况建议直接改用 DATE 组合写法。
NETWORKDAYS.INTL 和 WORKDAY.INTL 是 Excel 2010 才加入的,WPS 较新版本支持,老版本不支持。如果打开文件后出现 #NAME?,退回不带 INTL 的普通版本即可,代价是只能用周六周日双休的默认规则。
DATEDIF 在所有版本里都能用,包括 WPS,但它没有参数提示,输入时要格外小心参数顺序:先开始日期,再结束日期,最后单位代码。顺序写反会直接报 #NUM!。另外要记住结束日期如果是 TODAY(),结果会每天自动变化,做历史快照时记得转成数值固定下来。
常见问题
DATEDIF 为什么在函数列表里找不到
它确实不在函数列表里,也不会弹出参数提示,这是微软历史遗留的处理方式:这个函数最早是为了兼容 Lotus 而保留的,后来一直没有被正式纳入函数向导。但它能正常计算,只要完整输入 =DATEDIF(A2,B2,"Y") 就能得到结果。
因为没有提示,输入时全靠记忆,最容易错的地方是单位代码忘了加英文双引号,以及开始日期和结束日期写反。写反的表现是返回 #NUM!,把两个参数调换过来即可。另外该函数在 WPS 里同样可用,行为与 Excel 完全一致。
怎么算两个日期之间相差几年几个月零几天
分三次调用 DATEDIF,分别取整年、扣除整年后的剩余月数、扣除整年月后的剩余天数,再用连接符拼成一句话:
=DATEDIF(A2,B2,"Y")&"年"&DATEDIF(A2,B2,"YM")&"个月"&DATEDIF(A2,B2,"MD")&"天"
如果觉得三个 DATEDIF 太长,可以在辅助列里分别算出这三个值,最后一列拼装。辅助列的写法虽然多占三列,但每一列都能单独检查,出问题时一眼就能看出是哪一段算错了,比一长串嵌套公式好维护。
EDATE 加一个月,为什么一月三十一号变成了二月二十八号
这是 EDATE 的正常行为,也是它的设计优点。当目标月份没有对应的日期时,函数会返回该月的最后一天,而不是顺延到下个月。所以一月三十一号加一个月得到二月二十八号(闰年二十九号),三月三十一号减一个月得到二月二十八号。
如果你的业务规则恰好相反,要求顺延而不是回退,那就不要用 EDATE,改用 =DATE(YEAR(A2),MONTH(A2)+1,DAY(A2)),这个写法会因为自动进位而顺延到三月三日。两种规则没有对错,关键在于和业务方确认清楚。
节假日表要怎么维护才不容易出错
三条建议。第一,单独放在一张工作表里,或者至少放在当前表的固定列,比如 F 列从第二行开始,写清年份和放假日期,方便每年更新。第二,公式里一律用绝对引用锁定这个区域,写成 $F$2:$F$50,并且多留一些空行给未来新增,这样向下填充公式时区域不会跑偏。
第三,把节假日区域定义为名称,比如取名叫“法定假日”,之后公式就可以写成 =NETWORKDAYS(A2,B2,法定假日),可读性好得多,区域调整时也只需要改一次定义。定义名称的方法是在公式选项卡里点定义名称,或者在名称框里直接输入。
为什么 NETWORKDAYS 算出来的天数比日历天数还多
这种情况几乎只可能是日期顺序写反了。当开始日期晚于结束日期时,NETWORKDAYS 会返回一个负数,而不是报错。如果你在别处取了绝对值或者做了其他处理,就容易看到不合理的数字。
排查办法是加一列 =B2-A2 看日历天数差,正常情况下工作日数应该小于等于日历天数。如果工作日数比它还大,就说明数据顺序或者区域引用出了问题。另外要注意,NETWORKDAYS 的计数包含首尾两天,所以同一天开始同一天结束会返回一,而不是零。
