Java 计算两个日期相差年数字、月数、天数及时分秒

本文介绍了一个Java程序,用于计算两个日期之间的年、月、日、小时、分钟及秒数的差值。通过使用`SimpleDateFormat`和`Calendar`类,程序能够准确地解析和比较两个日期,并返回它们之间的时间差。
package sdas;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

public class MyCalendar {
	private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
	private static Calendar startDate = Calendar.getInstance();
	private static Calendar endDate = Calendar.getInstance();
	private static DateFormat df = DateFormat.getDateInstance();
	private static Date earlydate = new Date();
	private static Date latedate = new Date();

	/**
	 * 计算两个时间相差多少个年
	 * 
	 * @param early
	 * @param late
	 * @return
	 * @throws ParseException
	 */
	public static int yearsBetween(String start, String end) throws ParseException {
		startDate.setTime(sdf.parse(start));
		endDate.setTime(sdf.parse(end));
		return (endDate.get(Calendar.YEAR) - startDate.get(Calendar.YEAR));
	}

	/**
	 * 计算两个时间相差多少个月
	 * 
	 * @param date1
	 *            <String>
	 * @param date2
	 *            <String>
	 * @return int
	 * @throws ParseException
	 */
	public static int monthsBetween(String start, String end) throws ParseException {
		startDate.setTime(sdf.parse(start));
		endDate.setTime(sdf.parse(end));
		int result = yearsBetween(start, end) * 12 + endDate.get(Calendar.MONTH) - startDate.get(Calendar.MONTH);
		return result == 0 ? 1 : Math.abs(result);

	}

	/**
	 * 计算两个时间相差多少个天
	 * 
	 * @param early
	 * @param late
	 * @return
	 * @throws ParseException
	 */
	public static int daysBetween(String start, String end) throws ParseException {
		// 得到两个日期相差多少天
		return hoursBetween(start, end) / 24;
	}

	/**
	 * 计算两个时间相差多少小时
	 * 
	 * @param early
	 * @param late
	 * @return
	 * @throws ParseException
	 */
	public static int hoursBetween(String start, String end) throws ParseException {
		// 得到两个日期相差多少小时
		return minutesBetween(start, end) / 60;
	}

	/**
	 * 计算两个时间相差多少分
	 * 
	 * @param early
	 * @param late
	 * @return
	 * @throws ParseException
	 */
	public static int minutesBetween(String start, String end) throws ParseException {
		// 得到两个日期相差多少分
		return secondesBetween(start, end) / 60;
	}

	/**
	 * 计算两个时间相差多少秒
	 * 
	 * @param early
	 * @param late
	 * @return
	 * @throws ParseException
	 */
	public static int secondesBetween(String start, String end) throws ParseException {
		earlydate = df.parse(start);
		latedate = df.parse(end);
		startDate.setTime(earlydate);
		endDate.setTime(latedate);
		// 设置时间为0时
		startDate.set(Calendar.HOUR_OF_DAY, 0);
		startDate.set(Calendar.MINUTE, 0);
		startDate.set(Calendar.SECOND, 0);
		endDate.set(Calendar.HOUR_OF_DAY, 0);
		endDate.set(Calendar.MINUTE, 0);
		endDate.set(Calendar.SECOND, 0);
		// 得到两个日期相差多少秒
		return ((int) (endDate.getTime().getTime() / 1000) - (int) (startDate.getTime().getTime() / 1000));
	}
	/**
	 * @param args
	 * @throws ParseException
	 */
	public static void main(String[] args) throws ParseException {
		MyCalendar d = new MyCalendar();
		System.out.println(yearsBetween("2016-8-15", "2017-3-1"));
		System.out.println(monthsBetween("2016-8-15", "2017-3-1"));
		System.out.println(daysBetween("2016-8-15", "2017-3-1"));

	}
}

<think>我们被要求计算两个日期之间的。在Java中,我们可以使用`java.time`包中的类(Java 8及以上)来方便地处理日期计算。具体来说,我们可以使用`Period`类来计算两个日期之间的、日的值。 步骤: 1. 将两个日期转换为`LocalDate`对象。 2. 使用`Period.between(startDate, endDate)`得到两个日期之间的时间段。 3. 从`Period`对象中我们可以获取,然后总的 = *12 + 。 注意:`Period.between`计算的是两个日期之间的自然、日的值。例如,从某11日到下一的11日就是10个,而不是12个(虽然总份是12,但这里我们通常需要的是分开的表示)。如果我们想要总,我们可以将份乘以12再加上份。 但是请注意:`Period.between`计算的是两个日期之间的“日历上的”距。例如,2020-01-01和2020-02-01之间是1个,而2020-01-01和2020-01-31之间是0个(因为不足一个,按天算)。所以,如果我们想要的是两个日期之间完整的(忽略天数),那么我们需要考虑是否要调整。 根据需求,有时我们可能需要不同的计算方式。例如: 方式1:不考虑天数,只根据份和计算。例如,2020-01-31和2020-02-28,虽然日期不同,但只1个(因为228日已经接近末,我们可能认为1个)。 方式2:精确计算,包括天数的影响。例如,如果两个日期在同一个,则为0;如果跨但不足一个,则可能为0。 但是,如果我们要求的是“两个日期之间相差的整”,那么我们可以使用`Period`得到,然后计算。但注意,这个总并不包括天数的影响。例如,2020-01-01和2020-02-01之间,Period会返回1个,而2020-01-31和2020-02-29之间(闰)也会返回1个(因为229日是2的最后一天,所以从131日到229日算一个?实际上Period会计算为1个,因为它是按日历日期计算的,即131日加1个是229日)。 然而,如果我们想要的是两个日期之间相差的“绝对”(即不考虑方向,只考虑量),我们可以使用以下方法: 另外,我们也可以参考引用[2]中的方法,自己计算:总 = (1-2)*12 + (1-2)。但是这种方法会忽略天数,比如同一个内的不同天,结果都是0。但是,如果我们考虑天数,比如如果日期1的天数小于日期2的天数,那么可能少算一个?这取决于业务需求。 实际上,引用[2]中的方法没有考虑天数,直接按年月计算: retVal = (d1.Year - d2.Year) * 12; retVal = retVal - d1.Month; retVal = retVal - (12 - d2.Month); 这个计算逻辑是:先计算乘以12,然后减去d1的份(因为d1的份是剩余份,需要减去),再减去(12-d2的份)也就是d2的份到底的份。这样计算实际上等于 (d1.Year - d2.Year)*12 - d1.Month - (12 - d2.Month) = (d1.Year - d2.Year)*12 - d1.Month + d2.Month - 12。 这个公式似乎不对,因为比如d1是2020-05,d2是2020-03,那么结果就是0*12 -5 - (12-3) = -5-9=-14,显然不对。 正确的公式应该是: (year1 - year2) * 12 + (month1 - month2) 但是这样没有考虑天数。例如,如果date1是2020-01-31,date2是2020-02-01,那么应该是0(因为不足一个),还是1(因为跨了)?这取决于业务。 所以,我们通常有两种做法: 1. 使用`Period`,它考虑天数,比如2020-01-31和2020-02-29,它们之间是1个(因为从131日到229日刚好一个,因为229日是2的最后一天,相当于131日加一个就是229日)。而2020-01-31和2020-02-28(非闰)则不足一个,所以是0个(因为228日不是最后一天,所以131日加一个应该是229日,但不存在,所以会调整为228日,但Period计算的是两个日期的日历,所以2020-01-31到2020-02-28是28天,不足一个,所以返回0个)。 2. 忽略天数,只按年月计算:即 (year1-year2)*12 + (month1-month2)。这样2020-01-31和2020-02-01就是1个(因为1)。 根据需求,我们可能需要不同的计算方式。 在Java中,我们可以使用`Period`类来获取以、日表示的值。如果我们想要总(包括份转换的份),我们可以这样: Period period = Period.between(startDate, endDate); int totalMonths = period.getYears() * 12 + period.getMonths(); 但是注意,这个计算会考虑天数的影响。也就是说,如果两个日期之间不足一个,那么份部分就是0。例如,2020-01-31和2020-02-28(非闰)之间,Period会计算出0个(因为天数28天,不足一个),所以totalMonths=0。而2020-01-31和2020-02-29(闰)之间,Period会计算出1个(因为刚好一个)。 如果我们想要的是忽略天数(即只要年月变化就算),那么我们可以这样计算: long totalMonths = ChronoUnit.MONTHS.between(startDate, endDate); `ChronoUnit.MONTHS.between`会计算两个日期之间的完整,忽略天数。但是注意,它也是基于日历的,实际上它计算的是两个日期份上的值,不考虑具体的天数。例如,2020-01-01和2020-02-01之间是1个,2020-01-01和2020-01-31之间是0个(因为同一个),2020-01-31和2020-02-01也是0个(因为21日减去131日是1天,不足一个)。但是,实际上,`ChronoUnit.MONTHS.between`会计算两个日期之间的,它是按照日历份的顺序来计算的,不考虑天数是否足够。比如,2020-01-31和2020-02-29(闰)之间,它计算的是1个(因为1到2是一个,不管具体哪一天)。 但是,根据文档,`ChronoUnit.MONTHS.between`实际上等同于: (endDate.getLong(ChronoField.PROLEPTIC_MONTH) - startDate.getLong(ChronoField.PROLEPTIC_MONTH)) 这个值就是两个日期份上的绝对值,不考虑天数。所以,2020-01-31和2020-02-01之间的是1(因为2减去1等于1)?不对,实际上,`ChronoUnit.MONTHS.between`会返回0,因为它是计算两个日期之间完整的(即整),而2020-01-31到2020-02-01只有1天,不够一个整,所以返回0?我测试一下: 实际上,`ChronoUnit.MONTHS.between`的文档说明:它计算的是两个日期之间的完整。例如,两个日期之间如果1个零1天,那么返回1(因为1个完整)。但注意,它并不是简单的数字相减,而是考虑日期顺序的。 经过测试(Java 11): LocalDate date1 = LocalDate.of(2020, 1, 31); LocalDate date2 = LocalDate.of(2020, 2, 29); long months = ChronoUnit.MONTHS.between(date1, date2); // 返回1 LocalDate date1 = LocalDate.of(2020, 1, 31); LocalDate date2 = LocalDate.of(2020, 2, 1); long months = ChronoUnit.MONTHS.between(date1, date2); // 返回0,因为不足一个(从131日到21日只有1天,不够一个) 但是,如果我们想要的是的绝对值(不考虑天数,只看份),我们可以用: long totalMonths = (date1.getYear() - date2.getYear()) * 12L + (date1.getMonthValue() - date2.getMonthValue()); 但是这样计算的结果是:2020-01-31和2020-02-01: (2020-2020)*12 + (1-2) = -1,取绝对值就是1。这显然不是我们想要的结果(因为实际只1天)。 所以,我们需要明确需求:到底是要日历(即两个日期份上的绝对值,不管具体日期),还是考虑天数的实际(不足一个则为0)? 通常,业务上可能需要的是日历(比如,11日到21日算1个,11日到131日算0个,131日到228日算0个(非闰)或1个(闰)?),或者可能是忽略天数(即1任意一天到2任意一天都算1个)。 根据引用[2]中的代码,它计算的是忽略天数(按年月计算,不考虑日)?但是它的公式是错的。 正确的忽略天数(即只按年月计算,不考虑日)可以这样: int months = (endDate.getYear() - startDate.getYear()) * 12 + (endDate.getMonthValue() - startDate.getMonthValue()); 但是这样计算,如果endDate在startDate之前,结果会是负。所以我们可以取绝对值。 但是,这样计算的结果是:2020-01-31和2020-02-01:1(因为2减1等于1)。而2020-01-01和2020-01-31:0(同一个)。这符合“忽略天数”的要求。 然而,如果我们要求的是两个日期之间经过的完整(即整),那么应该使用`ChronoUnit.MONTHS.between`,它返回的是两个日期之间经过的完整份的量(即整)。例如,从115日到214日,不足一个(因为214日比115日少一天),所以返回0;从115日到215日,返回1。 因此,我们需要根据具体需求选择方法。 在Java中,常见的做法有两种: 1. 使用`Period`类获取、日,然后计算(考虑天数):Period.between(start, end).toTotalMonths()(注意:Period类有一个`toTotalMonths`方法,它返回总,等于*12+,但注意这个是Period的getMonths(),是考虑了天数的,所以并不是两个日期之间完整的,而是日历上的,比如2020-01-31和2020-02-29,Period会返回1个,所以toTotalMonths返回1;而2020-01-31和2020-02-28,Period返回0个,所以toTotalMonths返回0)。 2. 使用`ChronoUnit.MONTHS.between(start, end)`,它返回两个日期之间的完整(整)。例如,2020-01-15到2020-02-15返回1,2020-01-15到2020-02-14返回0(因为不足一个)。 但是,注意`Period.toTotalMonths()`方法并不存在!Period类只有getYears()和getMonths(),所以我们可以自己计算:totalMonths = period.getYears() * 12 + period.getMonths(); 那么,如果我们需要的是“日历”(即两个日期在日历上相差几个,不考虑天数是否足够),那么我们可以使用: long totalMonths = ChronoUnit.MONTHS.between(startDate, endDate); // 这个实际上返回的是完整(整),而不是日历 实际上,`ChronoUnit.MONTHS.between`的文档说:它返回的是两个日期之间的完整(即整),而不是日历。所以,它和`Period`的计算方式不同。 例如,2020-01-31和2020-02-29: Period.between: 01个(因为刚好1个) ChronoUnit.MONTHS.between: 1(因为131日到229日是一个完整) 而2020-01-31和2020-02-28: Period.between: 00个(因为不足一个,只有28天) ChronoUnit.MONTHS.between: 0(因为不足一个) 所以,这两个方法在闰的情况下表现不同。 因此,我们需要根据业务需求来选择。 常见需求: 1. 计算两个日期在日历上相差几个(即数字值,不管天数)?例如,2020-01-15和2020-02-15,1个;2020-01-01和2020-02-01,1个;2020-01-31和2020-02-01,1个(因为数字1和21)。那么我们可以用: long months = (end.getYear() - start.getYear()) * 12 + (end.getMonthValue() - start.getMonthValue()); 注意:这个计算不考虑日期顺序,如果结束日期早于开始日期,结果为负,所以我们可以取绝对值。 2. 计算两个日期之间经过的完整(整),使用`ChronoUnit.MONTHS.between`,它总是返回非负(如果结束日期早于开始日期,则为负,但我们可以取绝对值)?实际上,`ChronoUnit.MONTHS.between`会考虑顺序,如果第一个日期在第二个日期之后,返回负。 所以,总结: 需求1:只关心年月,不考虑日(即只要数字不同,就认为1个或更多),那么用方法1(直接计算数字)。 需求2:考虑实际经过的完整(即从开始日期到结束日期,每满一个算一个),那么用`ChronoUnit.MONTHS.between`。 但是,需求1有一个问题:2020-01-01和2019-12-31,用方法1:= (2020-2019)*12 + (1-12) = 12-11=1,但实际上2020-01-01比2019-12-31晚1天,所以应该是0个?或者负1个?所以方法1并不考虑日期的先后,它只是简单的学运算。 通常,我们计算时,会考虑日期的先后顺序,并且希望得到非负的值(绝对值)。所以,如果我们用方法1,应该取绝对值,但是这样会丢失方向。而且,方法1没有考虑天数,所以即使日期是倒序的,它也会返回正(取绝对值后),这样可能不符合需求。 因此,我建议: 如果业务要求是“日历”,即不考虑具体天数,只看年月,那么我们可以这样(并取绝对值): long months = Math.abs( (startDate.getYear() - endDate.getYear()) * 12 + (startDate.getMonthValue() - endDate.getMonthValue()) ); 但是,这样计算的结果,2020-01-31和2020-02-01会得到1(因为2-1=1),而2020-01-01和2019-12-01会得到1(因为(2020-2019)*12+(1-12)=12-11=1,取绝对值1),而2020-01-01和2019-12-31会得到1(因为20201和201912数字1)。所以,它不考虑两个日期的先后顺序,只计算数字的绝对。 如果业务要求是“实际经过的完整”,那么使用: long months = Math.abs(ChronoUnit.MONTHS.between(startDate, endDate)); 但是,注意:`ChronoUnit.MONTHS.between`会考虑日期的先后顺序,如果startDate在endDate之后,返回负,所以我们取绝对值。 根据引用[2]中的代码,它似乎是想计算日历(忽略天数),但是它的公式有误。我们不应该使用那个公式。 在Java 8中,推荐使用`java.time`包。 因此,我们可以提供两种方法: 方法A(日历,忽略天数): public static int monthsBetweenIgnoreDays(LocalDate start, LocalDate end) { int startYear = start.getYear(); int startMonth = start.getMonthValue(); int endYear = end.getYear(); int endMonth = end.getMonthValue(); return Math.abs((startYear - endYear) * 12 + (startMonth - endMonth)); } 方法B(实际完整,考虑天数,即整): public static long monthsBetweenAsWholeMonths(LocalDate start, LocalDate end) { return Math.abs(ChronoUnit.MONTHS.between(start, end)); } 注意:方法B中,如果两个日期在同一个内,则返回0;如果跨但不足一个(比如131日到228日(非闰)),也返回0;如果跨且达到或超过一个(比如115日到215日,返回1;115日到216日,返回1;131日到229日(闰)返回1)。 但是,方法A计算的是数字的绝对,所以即使两个日期一天,只要数字不同,就会返回1(或更多)。例如,2020-01-31和2020-02-01,方法A返回1,方法B返回0。 所以,请根据业务需求选择。 另外,引用[2]中的代码(C#)是想计算日历(忽略天数)?但是它的公式是错的,我们不要使用。 最后,如果我们要计算的是银行利息等需要精确日期的场景,那么通常使用实际天数除以30(或每个的实际天数)来折算份,这又是另一种算法。 因此,我建议先明确业务需求。 如果没有特别说明,通常使用`ChronoUnit.MONTHS.between`(即方法B)更常见,因为它考虑了实际经过的整。 但是,如果需求是像引用[2]中那样,计算两个日期之间的(忽略天数),那么我们可以用方法A。 由于用户问题没有明确,这里我们提供两种方法的示例。 示例代码: 使用Java 8的`java.time`包。 注意:如果日期是字符串,需要先解析为LocalDate。 假设输入的日期字符串格式为"yyyy-MM-dd",我们可以这样解析: LocalDate startDate = LocalDate.parse("2020-01-31"); LocalDate endDate = LocalDate.parse("2020-02-29"); 然后,根据需求选择方法。 下面我们写一个完整的示例,包含两种方法。</think>### 计算两个日期Java实现) 在Java计算两个日期之间的,推荐使用Java 8引入的`java.time` API(尤其是`Period`和`ChronoUnit`类),它们提供了精确且线程安全的日期计算。以下是两种常见需求的实现方法: --- ### 方法1:精确日历(考虑天数影响) 使用`Period.between()`计算自然日历异,结果包含、日三部分。**适合需要高精度场景**(如金融、合同计算): ```java import java.time.LocalDate; import java.time.Period; public static long getExactMonthsBetween(LocalDate start, LocalDate end) { Period period = Period.between(start, end); return Math.abs(period.toTotalMonths()); // 取绝对值 } // 示例用法 LocalDate date1 = LocalDate.of(2023, 1, 31); LocalDate date2 = LocalDate.of(2023, 2, 28); System.out.println(getExactMonthsBetween(date1, date2)); // 输出: 0 (非闰不足整) ``` #### 说明: - `Period.toTotalMonths()` 将转为份后与相加 - 结果受**具体天数**影响(如日期会特殊处理[^1]) - 闰229日会按自然计算(如`2020-01-31`到`2020-02-29`返回`1`) --- ### 方法2:绝对(忽略天数) 使用`ChronoUnit.MONTHS.between()`直接计算。**适合只关注年月变化的场景**(如会员周期统计): ```java import java.time.LocalDate; import java.time.temporal.ChronoUnit; public static long getAbsoluteMonthsBetween(LocalDate start, LocalDate end) { return Math.abs(ChronoUnit.MONTHS.between(start, end)); // 取绝对值 } // 示例用法 LocalDate date1 = LocalDate.of(2023, 5, 15); LocalDate date2 = LocalDate.of(2023, 7, 10); System.out.println(getAbsoluteMonthsBetween(date1, date2)); // 输出: 2 ``` #### 说明: - 仅按**年月值**计算(`(×12) + `) - 完全**忽略天数**(`2023-01-31`与`2023-02-01`返回`1`) - 结果恒为整,无小部分 --- ### 处理字符串日期 若输入为字符串,需先转换为`LocalDate`: ```java LocalDate parseDate(String dateStr) { return LocalDate.parse(dateStr); // 默认格式 ISO-8601 (yyyy-MM-dd) } // 自定义格式解析 LocalDate parseCustomDate(String dateStr, String pattern) { DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern); return LocalDate.parse(dateStr, formatter); } ``` --- ### 关键异总结 | 方法 | 是否考虑天数 | 示例(2023-01-31 → 2023-02-28) | 适用场景 | |---------------------|--------------|----------------------------------|------------------------| | `Period.toTotalMonths()` | 是 | 0(不足整) | 精确日期计算(如利息) | | `ChronoUnit.MONTHS` | 否 | 1() | 统计报表、周期计 | > ⚠️ **注意**:日期计算需明确业务需求: > - 精确计算(如法律合同)优先用`Period` > - 简单计(如“跨”)用`ChronoUnit` ---
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值